unit uFormatosImpresionService;

interface

uses
  System.SysUtils, System.Classes, System.IOUtils, Vcl.StdCtrls, Data.DB,
  FireDAC.Comp.Client, FireDAC.Stan.Param;

type
  TFormatoInfo = record
    Id: Integer;
    EmpresaId: Integer;
    TipoDocumento: string;
    Descripcion: string;
    Archivo: string;
    AplicarA: string;
    ClienteId: Integer;
    PorDefecto: Boolean;
    Activo: Boolean;
    RutaCompleta: string;
  end;

  TFormatoItemObject = class
  private
    FInfo: TFormatoInfo;
  public
    constructor Create(const AInfo: TFormatoInfo);
    property Info: TFormatoInfo read FInfo;
  end;

  TFormatosImpresionService = class
  public
    class function ResolverRutaArchivo(const ANombreArchivo: string): string;
    class function GetRutaPlantillaDefecto(const ATipoDoc: string; AEmpresaId: Integer = 1; AClienteId: Integer = 0): string;
    class function GetRutaPlantillaPorId(AIdFormato: Integer): string;
    class function GetPlantillasDisponibles(const ATipoDoc: string; AEmpresaId: Integer = 1; AClienteId: Integer = 0): TArray<TFormatoInfo>;
    class procedure CargarComboFormatos(AComboBox: TComboBox; const ATipoDoc: string; AEmpresaId: Integer = 1; AClienteId: Integer = 0; ASelectedId: Integer = 0);
    class function MarcarPorDefecto(AIdFormato: Integer; const ATipoDoc: string; AEmpresaId: Integer = 1): Boolean;
    class function GetFallbackTemplate(const ATipoDoc: string): string;
    class function NormalizeTipoDoc(const AValue: string): string;
    class function PluralTipoDoc(const AValue: string): string;
  end;

implementation

uses
  dmg_Main;

{ TFormatoItemObject }

constructor TFormatoItemObject.Create(const AInfo: TFormatoInfo);
begin
  inherited Create;
  FInfo := AInfo;
end;

{ TFormatosImpresionService }

class function TFormatosImpresionService.GetFallbackTemplate(const ATipoDoc: string): string;
var
  LDoc: string;
begin
  LDoc := UpperCase(Trim(ATipoDoc));
  if LDoc = 'ALBARAN' then Result := 'Albaran.fr3'
  else if LDoc = 'FACTURA' then Result := 'Factura.fr3'
  else if LDoc = 'PRESUPUESTO' then Result := 'Presupuesto.fr3'
  else if LDoc = 'PEDIDO' then Result := 'Pedido.fr3'
  else if LDoc = 'ETIQUETA_PRODUCTO' then Result := 'EtiquetaProducto.fr3'
  else if LDoc = 'ETIQUETA_CAJA' then Result := 'EtiquetaCaja.fr3'
  else if LDoc = 'FICHA_TECNICA' then Result := 'FichaTecnica.fr3'
  else if LDoc = 'RECETA' then Result := 'RecetaDetalle.fr3'
  else if LDoc = 'TARIFA_CLIENTE' then Result := 'TarifaPreciosCliente.fr3'
  else if LDoc = 'TARIFA_GRUPO' then Result := 'TarifaPreciosGrupo.fr3'
  else if (LDoc = 'ETIQUETA_BULTO_ECI') or (LDoc = 'ETIQUETA_BULTO') then Result := 'EtiquetaBultoECI.fr3'
  else Result := ATipoDoc + '.fr3';
end;

class function TFormatosImpresionService.NormalizeTipoDoc(const AValue: string): string;
var
  S: string;
begin
  S := UpperCase(Trim(AValue));
  if (S = 'ALBARAN') or (S = 'ALBARANES') then Result := 'ALBARAN'
  else if (S = 'FACTURA') or (S = 'FACTURAS') then Result := 'FACTURA'
  else if (S = 'PRESUPUESTO') or (S = 'PRESUPUESTOS') then Result := 'PRESUPUESTO'
  else if (S = 'PEDIDO') or (S = 'PEDIDOS') then Result := 'PEDIDO'
  else if (S = 'ETIQUETA_PRODUCTO') or (S = 'ETIQUETAS DE PRODUCTOS') or (S = 'ETIQUETA DE PRODUCTO') or (S = 'ETIQUETAS PRODUCTO') then Result := 'ETIQUETA_PRODUCTO'
  else if (S = 'ETIQUETA_CAJA') or (S = 'ETIQUETAS DE CAJAS') or (S = 'ETIQUETA DE CAJA') or (S = 'ETIQUETAS CAJA') then Result := 'ETIQUETA_CAJA'
  else if (S = 'ETIQUETA_BULTO_ECI') or (S = 'ETIQUETAS DE BULTOS ECI') or (S = 'ETIQUETA DE BULTO ECI') or (S = 'ETIQUETAS BULTO ECI') or (S = 'ETIQUETA_BULTO') then Result := 'ETIQUETA_BULTO_ECI'
  else if (S = 'FICHA_TECNICA') or (S = 'FICHAS TECNICAS') or (S = 'FICHA TECNICA') then Result := 'FICHA_TECNICA'
  else if (S = 'RECETA') or (S = 'RECETAS') then Result := 'RECETA'
  else if (S = 'TARIFA_CLIENTE') or (S = 'TARIFAS DE CLIENTES') or (S = 'TARIFA CLIENTE') then Result := 'TARIFA_CLIENTE'
  else if (S = 'TARIFA_GRUPO') or (S = 'TARIFAS DE GRUPOS') or (S = 'TARIFA GRUPO') then Result := 'TARIFA_GRUPO'
  else if (S = 'TODOS') or (S = 'GENERAL') then Result := 'GENERAL'
  else Result := S;
end;

class function TFormatosImpresionService.PluralTipoDoc(const AValue: string): string;
var
  S: string;
begin
  S := UpperCase(Trim(AValue));
  if (S = 'ALBARAN') or (S = 'ALBARANES') then Result := 'ALBARANES'
  else if (S = 'FACTURA') or (S = 'FACTURAS') then Result := 'FACTURAS'
  else if (S = 'PRESUPUESTO') or (S = 'PRESUPUESTOS') then Result := 'PRESUPUESTOS'
  else if (S = 'PEDIDO') or (S = 'PEDIDOS') then Result := 'PEDIDOS'
  else if (S = 'ETIQUETA_PRODUCTO') or (S = 'ETIQUETAS DE PRODUCTOS') or (S = 'ETIQUETA DE PRODUCTO') or (S = 'ETIQUETAS PRODUCTO') then Result := 'ETIQUETAS DE PRODUCTOS'
  else if (S = 'ETIQUETA_CAJA') or (S = 'ETIQUETAS DE CAJAS') or (S = 'ETIQUETA DE CAJA') or (S = 'ETIQUETAS CAJA') then Result := 'ETIQUETAS DE CAJAS'
  else if (S = 'FICHA_TECNICA') or (S = 'FICHAS TECNICAS') or (S = 'FICHA TECNICA') then Result := 'FICHAS TECNICAS'
  else if (S = 'RECETA') or (S = 'RECETAS') then Result := 'RECETAS'
  else if (S = 'TARIFA_CLIENTE') or (S = 'TARIFAS DE CLIENTES') or (S = 'TARIFA CLIENTE') then Result := 'TARIFAS DE CLIENTES'
  else if (S = 'TARIFA_GRUPO') or (S = 'TARIFAS DE GRUPOS') or (S = 'TARIFA GRUPO') then Result := 'TARIFAS DE GRUPOS'
  else Result := 'TODOS';
end;

class function TFormatosImpresionService.ResolverRutaArchivo(const ANombreArchivo: string): string;
var
  LFileName, LPath: string;
  LBasePaths: array[0..5] of string;
  I: Integer;
begin
  Result := '';
  LFileName := Trim(ANombreArchivo);
  if LFileName = '' then Exit;

  // Si ya es una ruta absoluta existente
  if TFile.Exists(LFileName) then
  begin
    Result := LFileName;
    Exit;
  end;

  LFileName := ExtractFileName(LFileName);
  if ExtractFileExt(LFileName) = '' then
    LFileName := LFileName + '.fr3';

  LBasePaths[0] := ExtractFilePath(ParamStr(0)) + 'reportes\' + LFileName;
  LBasePaths[1] := ExtractFilePath(ParamStr(0)) + '..\reportes\' + LFileName;
  LBasePaths[2] := ExtractFilePath(ParamStr(0)) + '..\..\reportes\' + LFileName;
  LBasePaths[3] := ExtractFilePath(ParamStr(0)) + 'reportes\samples\' + LFileName;
  LBasePaths[4] := TPath.Combine(GetCurrentDir, 'reportes\' + LFileName);
  LBasePaths[5] := 'reportes\' + LFileName;

  for I := Low(LBasePaths) to High(LBasePaths) do
  begin
    LPath := LBasePaths[I];
    if TFile.Exists(LPath) then
    begin
      Result := LPath;
      Exit;
    end;
  end;

  // Si no se encuentra físicamente, devolver la ruta canónica por defecto
  Result := ExtractFilePath(ParamStr(0)) + 'reportes\' + LFileName;
end;

class function TFormatosImpresionService.GetPlantillasDisponibles(const ATipoDoc: string; AEmpresaId: Integer = 1; AClienteId: Integer = 0): TArray<TFormatoInfo>;
var
  LQry: TFDQuery;
  LInfo: TFormatoInfo;
  LTipoDoc: string;
begin
  SetLength(Result, 0);
  if not Assigned(dmgMain) or not Assigned(dmgMain.dbConn) or not dmgMain.dbConn.Connected then
    Exit;

  LTipoDoc := UpperCase(Trim(ATipoDoc));
  var LTipoPlural := PluralTipoDoc(LTipoDoc);
  LTipoDoc := NormalizeTipoDoc(LTipoDoc);
  LQry := TFDQuery.Create(nil);
  try
    LQry.Connection := dmgMain.dbConn;
    LQry.SQL.Text :=
      'SELECT id, ide, tipo_documento, descripcion, archivo, aplicar_a, cliente_id, pordefecto, activo ' +
      'FROM ge_formatosimpresion ' +
      'WHERE activo = 1 AND ide = :ide ' +
      '  AND (' +
      '    aplicar_a = :tipo_plural ' +
      '    OR aplicar_a = :tipo ' +
      '    OR aplicar_a = ''TODOS'' ' +
      '    OR tipo_documento = :tipo ' +
      '    OR (tipo_documento = ''GENERAL'' AND LOWER(archivo) LIKE :like_arch)' +
      '  ) ' +
      '  AND (:cliente_id < 0 OR cliente_id IS NULL OR cliente_id = 0 OR cliente_id = :cliente_id) ' +
      'ORDER BY pordefecto DESC, descripcion ASC';
    LQry.ParamByName('ide').AsInteger := AEmpresaId;
    LQry.ParamByName('tipo').AsString := LTipoDoc;
    LQry.ParamByName('tipo_plural').AsString := LTipoPlural;
    LQry.ParamByName('like_arch').AsString := LowerCase(LTipoDoc) + '%';
    LQry.ParamByName('cliente_id').AsInteger := AClienteId;

    try
      LQry.Open;
      if LQry.IsEmpty and (AEmpresaId <> 1) then
      begin
        LQry.Close;
        LQry.ParamByName('ide').AsInteger := 1;
        LQry.Open;
      end;
      while not LQry.Eof do
      begin
        LInfo.Id := LQry.FieldByName('id').AsInteger;
        LInfo.EmpresaId := LQry.FieldByName('ide').AsInteger;
        LInfo.TipoDocumento := LQry.FieldByName('tipo_documento').AsString;
        LInfo.Descripcion := Trim(LQry.FieldByName('descripcion').AsString);
        LInfo.Archivo := Trim(LQry.FieldByName('archivo').AsString);
        if LInfo.Descripcion = '' then
          LInfo.Descripcion := ChangeFileExt(LInfo.Archivo, '');
        LInfo.AplicarA := LQry.FieldByName('aplicar_a').AsString;
        LInfo.ClienteId := LQry.FieldByName('cliente_id').AsInteger;
        if LQry.FieldByName('pordefecto').DataType = ftBoolean then
          LInfo.PorDefecto := LQry.FieldByName('pordefecto').AsBoolean
        else
          LInfo.PorDefecto := (LQry.FieldByName('pordefecto').AsInteger = 1);

        if LQry.FieldByName('activo').DataType = ftBoolean then
          LInfo.Activo := LQry.FieldByName('activo').AsBoolean
        else
          LInfo.Activo := (LQry.FieldByName('activo').AsInteger = 1);
        LInfo.RutaCompleta := ResolverRutaArchivo(LInfo.Archivo);

        SetLength(Result, Length(Result) + 1);
        Result[High(Result)] := LInfo;
        LQry.Next;
      end;
    except
      SetLength(Result, 0);
    end;
  finally
    LQry.Free;
  end;
end;

class function TFormatosImpresionService.GetRutaPlantillaDefecto(const ATipoDoc: string; AEmpresaId: Integer = 1; AClienteId: Integer = 0): string;
var
  LFormatos: TArray<TFormatoInfo>;
  I: Integer;
begin
  Result := '';
  LFormatos := GetPlantillasDisponibles(ATipoDoc, AEmpresaId, AClienteId);

  // 1. Si hay un formato específico para este cliente, priorizarlo
  if AClienteId > 0 then
  begin
    for I := 0 to High(LFormatos) do
    begin
      if LFormatos[I].ClienteId = AClienteId then
      begin
        Result := LFormatos[I].RutaCompleta;
        Exit;
      end;
    end;
  end;

  // 2. Si hay un formato marcado con pordefecto = 1
  for I := 0 to High(LFormatos) do
  begin
    if LFormatos[I].PorDefecto then
    begin
      Result := LFormatos[I].RutaCompleta;
      Exit;
    end;
  end;

  // 3. Si hay al menos un formato disponible, usar el primero
  if Length(LFormatos) > 0 then
  begin
    Result := LFormatos[0].RutaCompleta;
    Exit;
  end;

  // 4. Fallback estándar al nombre por defecto en disco
  Result := ResolverRutaArchivo(GetFallbackTemplate(ATipoDoc));
end;

class function TFormatosImpresionService.GetRutaPlantillaPorId(AIdFormato: Integer): string;
var
  LQry: TFDQuery;
  LArchivo: string;
begin
  Result := '';
  if (AIdFormato <= 0) or not Assigned(dmgMain) or not Assigned(dmgMain.dbConn) then Exit;

  LQry := TFDQuery.Create(nil);
  try
    LQry.Connection := dmgMain.dbConn;
    LQry.SQL.Text := 'SELECT archivo FROM ge_formatosimpresion WHERE id = :id';
    LQry.ParamByName('id').AsInteger := AIdFormato;
    try
      LQry.Open;
      if not LQry.IsEmpty then
      begin
        LArchivo := LQry.FieldByName('archivo').AsString;
        Result := ResolverRutaArchivo(LArchivo);
      end;
    except
      Result := '';
    end;
  finally
    LQry.Free;
  end;
end;

class procedure TFormatosImpresionService.CargarComboFormatos(AComboBox: TComboBox; const ATipoDoc: string; AEmpresaId: Integer = 1; AClienteId: Integer = 0; ASelectedId: Integer = 0);
var
  LFormatos: TArray<TFormatoInfo>;
  I, LSelectIdx: Integer;
  LObj: TFormatoItemObject;
  LDisplayText: string;
begin
  if not Assigned(AComboBox) then Exit;

  for I := 0 to AComboBox.Items.Count - 1 do
  begin
    if Assigned(AComboBox.Items.Objects[I]) then
      AComboBox.Items.Objects[I].Free;
  end;
  AComboBox.Items.Clear;

  LFormatos := GetPlantillasDisponibles(ATipoDoc, AEmpresaId, AClienteId);
  LSelectIdx := -1;

  if Length(LFormatos) = 0 then
  begin
    AComboBox.Items.Add(GetFallbackTemplate(ATipoDoc));
    AComboBox.ItemIndex := 0;
    Exit;
  end;

  for I := 0 to High(LFormatos) do
  begin
    LObj := TFormatoItemObject.Create(LFormatos[I]);
    if (LFormatos[I].Descripcion <> '') and not SameText(LFormatos[I].Descripcion, LFormatos[I].Archivo) then
      LDisplayText := LFormatos[I].Descripcion + ' (' + LFormatos[I].Archivo + ')'
    else
      LDisplayText := LFormatos[I].Archivo;

    AComboBox.Items.AddObject(LDisplayText, LObj);

    if (ASelectedId > 0) and (LFormatos[I].Id = ASelectedId) then
      LSelectIdx := I
    else if (LSelectIdx = -1) and LFormatos[I].PorDefecto then
      LSelectIdx := I;
  end;

  if (LSelectIdx = -1) and (AComboBox.Items.Count > 0) then
    LSelectIdx := 0;

  AComboBox.ItemIndex := LSelectIdx;
end;

class function TFormatosImpresionService.MarcarPorDefecto(AIdFormato: Integer; const ATipoDoc: string; AEmpresaId: Integer = 1): Boolean;
var
  LQry: TFDQuery;
begin
  Result := False;
  if not Assigned(dmgMain) or not Assigned(dmgMain.dbConn) then Exit;

  LQry := TFDQuery.Create(nil);
  try
    LQry.Connection := dmgMain.dbConn;
    try
      // Desmarcar anteriores del mismo tipo o ámbito
      var LNormTipo := NormalizeTipoDoc(ATipoDoc);
      var LPluralTipo := PluralTipoDoc(ATipoDoc);
      LQry.SQL.Text :=
        'UPDATE ge_formatosimpresion SET pordefecto = 0 ' +
        'WHERE ide = :ide AND (tipo_documento = :tipo OR aplicar_a = :tipo_plural OR aplicar_a = :tipo)';
      LQry.ParamByName('ide').AsInteger := AEmpresaId;
      LQry.ParamByName('tipo').AsString := LNormTipo;
      LQry.ParamByName('tipo_plural').AsString := LPluralTipo;
      LQry.ExecSQL;

      // Marcar nuevo
      LQry.SQL.Text := 'UPDATE ge_formatosimpresion SET pordefecto = 1 WHERE id = :id';
      LQry.ParamByName('id').AsInteger := AIdFormato;
      LQry.ExecSQL;

      Result := True;
    except
      Result := False;
    end;
  finally
    LQry.Free;
  end;
end;

end.
