unit frm_ReportTemplates;

interface

uses
  Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, System.UITypes,
  Vcl.Graphics, Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.ExtCtrls, Vcl.StdCtrls,
  Vcl.Grids, Vcl.DBGrids, Data.DB, Vcl.Buttons, uAppTheme, FireDAC.Comp.Client,
  frm_BaseList, FireDAC.Stan.Option, FireDAC.Stan.Param, FireDAC.Stan.Error,
  FireDAC.DatS, FireDAC.Phys.Intf, FireDAC.DApt.Intf, FireDAC.Comp.DataSet,
  System.IOUtils, System.Types, FireDAC.Stan.Intf, Vcl.ComCtrls,
  uFormatosImpresionService;

const
  WM_AUTOSAVE_RECORD = WM_USER + 101;

type
  TfrmReportTemplates = class(TfrmBaseList)
    mtTemplates: TFDMemTable;
    btnChangeFolder: TSpeedButton;
    btnSetDefault: TSpeedButton;
    btnAsignarCliente: TSpeedButton;
    btnQuitarCliente: TSpeedButton;
    lblFiltroTipo: TLabel;
    cbbFiltroTipo: TComboBox;
    procedure FormShow(Sender: TObject);
    procedure FormClose(Sender: TObject; var Action: TCloseAction);
    procedure btnRefreshClick(Sender: TObject); override;
    procedure btnNewClick(Sender: TObject); override;
    procedure btnEditClick(Sender: TObject); override;
    procedure btnDeleteClick(Sender: TObject); override;
    procedure btnChangeFolderClick(Sender: TObject);
    procedure btnSetDefaultClick(Sender: TObject);
    procedure btnAsignarClienteClick(Sender: TObject);
    procedure btnQuitarClienteClick(Sender: TObject);
    procedure cbbFiltroTipoChange(Sender: TObject);
    procedure dbGridMainCellClick(Column: TColumn);
    procedure dbGridMainDblClick(Sender: TObject);
    procedure dbGridMainEditButtonClick(Sender: TObject);
    procedure dbGridMainKeyDown(Sender: TObject; var Key: Word; Shift: TShiftState);
    procedure dbGridMainColExit(Sender: TObject);
    procedure WMAutoSaveRecord(var Msg: TMessage); message WM_AUTOSAVE_RECORD;
  private
    FReportPath: string;
    FIsPopulating: Boolean;
    procedure InitMemTable;
    procedure InitComboTipos;
    procedure SetupGridColumns;
    procedure SyncDiskFilesWithDatabase(const ASearchPath: string; AEmpresaId: Integer);
    procedure PopulateTemplatesList;
    function GetValidReportPath: string;
    function GuessTipoDocumento(const AFileName: string): string;
    procedure mtTemplatesAfterPost(DataSet: TDataSet);
    procedure TemplateFieldChange(Sender: TField);
  end;

var
  frmReportTemplates: TfrmReportTemplates;

implementation

uses
  dmg_Main, frxDesgn, frxBarcode, Vcl.FileCtrl, frm_SelectCliente;

{$R *.dfm}

procedure TfrmReportTemplates.InitMemTable;
begin
  if not mtTemplates.Active then
  begin
    mtTemplates.FieldDefs.Clear;
    mtTemplates.FieldDefs.Add('id', ftInteger);
    mtTemplates.FieldDefs.Add('tipo_documento', ftString, 40);
    mtTemplates.FieldDefs.Add('descripcion', ftString, 100);
    mtTemplates.FieldDefs.Add('archivo', ftString, 120);
    mtTemplates.FieldDefs.Add('ruta', ftString, 255);
    mtTemplates.FieldDefs.Add('aplicar_a', ftString, 50);
    mtTemplates.FieldDefs.Add('cliente_id', ftInteger);
    mtTemplates.FieldDefs.Add('cliente_nombre', ftString, 100);
    mtTemplates.FieldDefs.Add('pordefecto', ftBoolean);
    mtTemplates.FieldDefs.Add('activo', ftBoolean);
    mtTemplates.FieldDefs.Add('tamano', ftInteger);
    mtTemplates.FieldDefs.Add('fecha_modificacion', ftDateTime);
    mtTemplates.CreateDataSet;
    mtTemplates.AfterPost := mtTemplatesAfterPost;
    mtTemplates.FieldByName('aplicar_a').OnChange := TemplateFieldChange;
    mtTemplates.FieldByName('descripcion').OnChange := TemplateFieldChange;
    mtTemplates.FieldByName('archivo').OnChange := TemplateFieldChange;
    mtTemplates.FieldByName('cliente_id').OnChange := TemplateFieldChange;
    mtTemplates.FieldByName('cliente_nombre').OnChange := TemplateFieldChange;
  end;
end;

procedure TfrmReportTemplates.InitComboTipos;
begin
  cbbFiltroTipo.Items.BeginUpdate;
  try
    cbbFiltroTipo.Items.Clear;
    cbbFiltroTipo.Items.Add('TODOS');
    cbbFiltroTipo.Items.Add('ALBARANES');
    cbbFiltroTipo.Items.Add('FACTURAS');
    cbbFiltroTipo.Items.Add('PRESUPUESTOS');
    cbbFiltroTipo.Items.Add('PEDIDOS');
    cbbFiltroTipo.Items.Add('ETIQUETAS DE PRODUCTOS');
    cbbFiltroTipo.Items.Add('ETIQUETAS DE CAJAS');
    cbbFiltroTipo.Items.Add('FICHAS TECNICAS');
    cbbFiltroTipo.Items.Add('RECETAS');
    cbbFiltroTipo.Items.Add('TARIFAS DE CLIENTES');
    cbbFiltroTipo.Items.Add('TARIFAS DE GRUPOS');
  finally
    cbbFiltroTipo.Items.EndUpdate;
  end;
  cbbFiltroTipo.ItemIndex := 0;
end;

procedure TfrmReportTemplates.SetupGridColumns;
var
  Col: TColumn;
begin
  dbGridMain.Columns.Clear;
  dbGridMain.ReadOnly := False;
  dbGridMain.Options := (dbGridMain.Options - [dgRowSelect, dgMultiSelect]) + [dgEditing];
  dbGridMain.OnCellClick := dbGridMainCellClick;
  dbGridMain.OnKeyDown := dbGridMainKeyDown;
  dbGridMain.OnColExit := dbGridMainColExit;

  Col := dbGridMain.Columns.Add;
  Col.FieldName := 'descripcion';
  Col.Title.Caption := 'Descripción del Formato (Editable)';
  Col.Width := 250;
  Col.ReadOnly := False;

  Col := dbGridMain.Columns.Add;
  Col.FieldName := 'archivo';
  Col.Title.Caption := 'Fichero (.fr3)';
  Col.Width := 180;
  Col.ReadOnly := False;

  Col := dbGridMain.Columns.Add;
  Col.FieldName := 'pordefecto';
  Col.Title.Caption := 'Defecto';
  Col.Width := 65;
  Col.Alignment := taCenter;
  Col.ReadOnly := True;

  Col := dbGridMain.Columns.Add;
  Col.FieldName := 'activo';
  Col.Title.Caption := 'Activo';
  Col.Width := 60;
  Col.Alignment := taCenter;
  Col.ReadOnly := True;

  Col := dbGridMain.Columns.Add;
  Col.FieldName := 'aplicar_a';
  Col.Title.Caption := 'Aplicar A';
  Col.Width := 190;
  Col.PickList.Clear;
  Col.PickList.Add('ALBARANES');
  Col.PickList.Add('FACTURAS');
  Col.PickList.Add('PRESUPUESTOS');
  Col.PickList.Add('PEDIDOS');
  Col.PickList.Add('ETIQUETAS DE PRODUCTOS');
  Col.PickList.Add('ETIQUETAS DE CAJAS');
  Col.PickList.Add('FICHAS TECNICAS');
  Col.PickList.Add('RECETAS');
  Col.PickList.Add('TARIFAS DE CLIENTES');
  Col.PickList.Add('TARIFAS DE GRUPOS');
  Col.PickList.Add('TODOS');
  Col.ReadOnly := False;

  Col := dbGridMain.Columns.Add;
  Col.FieldName := 'cliente_nombre';
  Col.Title.Caption := 'Cliente Específico (Doble Clic)';
  Col.Width := 160;
  Col.ReadOnly := True;
  Col.ButtonStyle := cbsEllipsis;

  Col := dbGridMain.Columns.Add;
  Col.FieldName := 'fecha_modificacion';
  Col.Title.Caption := 'Última Modificación';
  Col.Width := 130;
  Col.ReadOnly := True;
end;

procedure TfrmReportTemplates.FormShow(Sender: TObject);
begin
  if not Assigned(FindComponent('frxDesigner1')) then
  begin
    with TfrxDesigner.Create(Self) do
      Name := 'frxDesigner1';
  end;

  InitMemTable;
  InitComboTipos;

  inherited;

  dsMain.DataSet := mtTemplates;
  dbGridMain.OnEditButtonClick := dbGridMainEditButtonClick;

  btnNuevo.Caption := 'Duplicar';
  btnEditar.Caption := 'Diseñar';
  btnBorrar.Enabled := True;
  btnBorrar.Visible := True;

  SetupGridColumns;
  PopulateTemplatesList;
end;

function TfrmReportTemplates.GuessTipoDocumento(const AFileName: string): string;
var
  LName: string;
begin
  LName := LowerCase(AFileName);
  if Pos('albaran', LName) > 0 then Result := 'ALBARAN'
  else if Pos('factura', LName) > 0 then Result := 'FACTURA'
  else if Pos('presupuesto', LName) > 0 then Result := 'PRESUPUESTO'
  else if Pos('pedido', LName) > 0 then Result := 'PEDIDO'
  else if (Pos('caja', LName) > 0) and (Pos('etiqueta', LName) > 0) then Result := 'ETIQUETA_CAJA'
  else if Pos('etiqueta', LName) > 0 then Result := 'ETIQUETA_PRODUCTO'
  else if (Pos('fichatecnica', LName) > 0) or (Pos('ficha_tecnica', LName) > 0) then Result := 'FICHA_TECNICA'
  else if Pos('receta', LName) > 0 then Result := 'RECETA'
  else if Pos('tarifaprecioscliente', LName) > 0 then Result := 'TARIFA_CLIENTE'
  else if Pos('tarifapreciosgrupo', LName) > 0 then Result := 'TARIFA_GRUPO'
  else Result := 'GENERAL';
end;

procedure TfrmReportTemplates.SyncDiskFilesWithDatabase(const ASearchPath: string; AEmpresaId: Integer);
var
  LFiles: TStringDynArray;
  LFile, LFileName, LTipoDoc, LDesc: string;
  LQry: TFDQuery;
  LDefecto: Integer;
begin
  if (ASearchPath = '') or not TDirectory.Exists(ASearchPath) then Exit;
  if not Assigned(dmgMain) or not Assigned(dmgMain.dbConn) or not dmgMain.dbConn.Connected then Exit;

  LFiles := TDirectory.GetFiles(ASearchPath, '*.fr3');
  if Length(LFiles) = 0 then Exit;

  LQry := TFDQuery.Create(nil);
  try
    LQry.Connection := dmgMain.dbConn;

    for LFile in LFiles do
    begin
      LFileName := ExtractFileName(LFile);
      if Trim(LFileName) = '' then Continue;

      // Verificar si ya existe en ge_formatosimpresion para esta empresa
      LQry.SQL.Text := 'SELECT id FROM ge_formatosimpresion WHERE ide = :ide AND LOWER(archivo) = LOWER(:arch)';
      LQry.ParamByName('ide').AsInteger := AEmpresaId;
      LQry.ParamByName('arch').AsString := LFileName;
      try
        LQry.Open;
        if LQry.IsEmpty then
        begin
          LTipoDoc := TFormatosImpresionService.NormalizeTipoDoc(GuessTipoDocumento(LFileName));
          var LAplicarA := TFormatosImpresionService.PluralTipoDoc(LTipoDoc);
          LDesc := ChangeFileExt(LFileName, '');

          // Si es el archivo canónico principal, marcarlo por defecto si no hay otro
          LDefecto := 0;
          if SameText(LFileName, 'Albaran.fr3') or SameText(LFileName, 'Factura.fr3') or
             SameText(LFileName, 'Presupuesto.fr3') or SameText(LFileName, 'Pedido.fr3') or
             SameText(LFileName, 'EtiquetaProducto.fr3') or SameText(LFileName, 'EtiquetaCaja.fr3') or
             SameText(LFileName, 'FichaTecnica.fr3') or SameText(LFileName, 'RecetaDetalle.fr3') then
          begin
            LDefecto := 1;
          end;

          LQry.SQL.Text :=
            'INSERT INTO ge_formatosimpresion (ide, tipo_documento, descripcion, archivo, aplicar_a, pordefecto, activo) ' +
            'VALUES (:ide, :tipo, :desc, :arch, :aplicar_a, :def, 1)';
          LQry.ParamByName('ide').AsInteger := AEmpresaId;
          LQry.ParamByName('tipo').AsString := LTipoDoc;
          LQry.ParamByName('desc').AsString := LDesc;
          LQry.ParamByName('arch').AsString := LFileName;
          LQry.ParamByName('aplicar_a').AsString := LAplicarA;
          LQry.ParamByName('def').AsInteger := LDefecto;
          LQry.ExecSQL;
        end;
      except
        // Continuar si la tabla aún se está creando
      end;
    end;
  finally
    LQry.Free;
  end;
end;

procedure TfrmReportTemplates.PopulateTemplatesList;
var
  LQry: TFDQuery;
  LSearchPattern, LTipoFiltro: string;
  LSearchPath, LFilePath, LFileName: string;
  LModDate: TDateTime;
  LSize: Int64;
  LStream: TFileStream;
  LHasDbRecords: Boolean;
  LFiles: TStringDynArray;
  LFile: string;
  LEmpresaId: Integer;
begin
  if mtTemplates.Active and (mtTemplates.State in [dsEdit, dsInsert]) then
    mtTemplates.Post;

  InitMemTable;
  FIsPopulating := True;
  try
    mtTemplates.EmptyDataSet;

    LSearchPattern := LowerCase(Trim(edtSearch.Text));
    LTipoFiltro := Trim(cbbFiltroTipo.Text);
    if (cbbFiltroTipo.ItemIndex <= 0) or
       SameText(LTipoFiltro, 'TODOS') or
       SameText(LTipoFiltro, '[TODOS]') or
       SameText(LTipoFiltro, '[TODOS LOS TIPOS]') or
       SameText(LTipoFiltro, 'GENERAL') then
    begin
      LTipoFiltro := '';
    end;

    LEmpresaId := 1;
    if Assigned(dmgMain) and (Trim(dmgMain.CurrentCompanyId) <> '') then
      LEmpresaId := StrToIntDef(dmgMain.CurrentCompanyId, 1);

    LSearchPath := GetValidReportPath;

    // Sincronizar automáticamente cualquier archivo en disco no registrado
    SyncDiskFilesWithDatabase(LSearchPath, LEmpresaId);

    LHasDbRecords := False;

    // 1. Cargar desde ge_formatosimpresion
    if Assigned(dmgMain) and Assigned(dmgMain.dbConn) and dmgMain.dbConn.Connected then
    begin
      LQry := TFDQuery.Create(nil);
      try
        LQry.Connection := dmgMain.dbConn;
        LQry.SQL.Text :=
          'SELECT f.id, f.ide, f.tipo_documento, f.formulario, f.descripcion, f.archivo, ' +
          '       f.aplicar_a, f.cliente_id, c.nombre_fiscal, c.nombre_comercial, f.pordefecto, f.activo, f.updated_at ' +
          'FROM ge_formatosimpresion f ' +
          'LEFT JOIN ge_clientes c ON c.id = f.cliente_id ' +
          'WHERE f.ide = :ide ' +
          'ORDER BY f.tipo_documento ASC, f.pordefecto DESC, f.descripcion ASC';
        LQry.ParamByName('ide').AsInteger := LEmpresaId;

        try
          LQry.Open;
          if LQry.IsEmpty and (LEmpresaId <> 1) then
          begin
            LQry.Close;
            LQry.ParamByName('ide').AsInteger := 1;
            LQry.Open;
          end;
          while not LQry.Eof do
          begin
            LHasDbRecords := True;

            // Filtro por tipo
            if (LTipoFiltro <> '') then
            begin
              var LNormFiltro := TFormatosImpresionService.NormalizeTipoDoc(LTipoFiltro);
              var LPluralFiltro := TFormatosImpresionService.PluralTipoDoc(LTipoFiltro);
              var LRowTipo := LQry.FieldByName('tipo_documento').AsString;
              var LRowAplicar := LQry.FieldByName('aplicar_a').AsString;

              if not SameText(LRowTipo, LNormFiltro) and
                 not SameText(LRowAplicar, LPluralFiltro) and
                 not SameText(LRowAplicar, 'TODOS') and
                 not SameText(LRowTipo, 'GENERAL') then
              begin
                LQry.Next;
                Continue;
              end;
            end;

            // Filtro por texto de búsqueda
            if (LSearchPattern <> '') then
            begin
              if (Pos(LSearchPattern, LowerCase(LQry.FieldByName('descripcion').AsString)) = 0) and
                 (Pos(LSearchPattern, LowerCase(LQry.FieldByName('archivo').AsString)) = 0) and
                 (Pos(LSearchPattern, LowerCase(LQry.FieldByName('tipo_documento').AsString)) = 0) then
              begin
                LQry.Next;
                Continue;
              end;
            end;

            LFileName := LQry.FieldByName('archivo').AsString;
            LFilePath := TFormatosImpresionService.ResolverRutaArchivo(LFileName);
            LSize := 0;
            LModDate := LQry.FieldByName('updated_at').AsDateTime;

            if TFile.Exists(LFilePath) then
            begin
              try
                LStream := TFileStream.Create(LFilePath, fmOpenRead or fmShareDenyNone);
                try
                  LSize := LStream.Size;
                finally
                  LStream.Free;
                end;
                LModDate := TFile.GetLastWriteTime(LFilePath);
              except
              end;
            end;

            var LAplicarAStr := Trim(LQry.FieldByName('aplicar_a').AsString);
            if LAplicarAStr = '' then
            begin
              var LTipoDocStr := LQry.FieldByName('tipo_documento').AsString;
              if (LTipoDocStr <> '') and not SameText(LTipoDocStr, 'GENERAL') then
                LAplicarAStr := TFormatosImpresionService.PluralTipoDoc(LTipoDocStr)
              else
                LAplicarAStr := 'TODOS';
            end
            else
              LAplicarAStr := TFormatosImpresionService.PluralTipoDoc(LAplicarAStr);

            var LClienteNom := LQry.FieldByName('nombre_fiscal').AsString;
            if LClienteNom = '' then
              LClienteNom := LQry.FieldByName('nombre_comercial').AsString;

            mtTemplates.Append;
            mtTemplates.FieldByName('id').AsInteger := LQry.FieldByName('id').AsInteger;
            mtTemplates.FieldByName('tipo_documento').AsString := TFormatosImpresionService.NormalizeTipoDoc(LAplicarAStr);
            mtTemplates.FieldByName('descripcion').AsString := LQry.FieldByName('descripcion').AsString;
            mtTemplates.FieldByName('archivo').AsString := LFileName;
            mtTemplates.FieldByName('ruta').AsString := LFilePath;
            mtTemplates.FieldByName('aplicar_a').AsString := LAplicarAStr;
            mtTemplates.FieldByName('cliente_id').AsInteger := LQry.FieldByName('cliente_id').AsInteger;
            mtTemplates.FieldByName('cliente_nombre').AsString := LClienteNom;
            if LQry.FieldByName('pordefecto').DataType = ftBoolean then
              mtTemplates.FieldByName('pordefecto').AsBoolean := LQry.FieldByName('pordefecto').AsBoolean
            else
              mtTemplates.FieldByName('pordefecto').AsBoolean := (LQry.FieldByName('pordefecto').AsInteger = 1);

            if LQry.FieldByName('activo').DataType = ftBoolean then
              mtTemplates.FieldByName('activo').AsBoolean := LQry.FieldByName('activo').AsBoolean
            else
              mtTemplates.FieldByName('activo').AsBoolean := (LQry.FieldByName('activo').AsInteger = 1);
            mtTemplates.FieldByName('tamano').AsInteger := LSize;
            mtTemplates.FieldByName('fecha_modificacion').AsDateTime := LModDate;
            mtTemplates.Post;

            LQry.Next;
          end;
        except
          on E: Exception do
            LHasDbRecords := False;
        end;
      finally
        LQry.Free;
      end;
    end;

    // 2. Si no hay conexión o falló la BD (fallback a disco solo si está vacío)
    if not LHasDbRecords and mtTemplates.IsEmpty and (LSearchPath <> '') and TDirectory.Exists(LSearchPath) then
    begin
      LFiles := TDirectory.GetFiles(LSearchPath, '*.fr3');
      for LFile in LFiles do
      begin
        LFileName := ExtractFileName(LFile);
        if (LSearchPattern <> '') and (Pos(LSearchPattern, LowerCase(LFileName)) = 0) then
          Continue;

        LSize := 0;
        try
          if TFile.Exists(LFile) then
          begin
            LStream := TFileStream.Create(LFile, fmOpenRead or fmShareDenyNone);
            try
              LSize := LStream.Size;
            finally
              LStream.Free;
            end;
          end;
        except
        end;
        LModDate := TFile.GetLastWriteTime(LFile);

        mtTemplates.Append;
        mtTemplates.FieldByName('id').AsInteger := 0;
        mtTemplates.FieldByName('tipo_documento').AsString := GuessTipoDocumento(LFileName);
        mtTemplates.FieldByName('descripcion').AsString := ChangeFileExt(LFileName, '');
        mtTemplates.FieldByName('archivo').AsString := LFileName;
        mtTemplates.FieldByName('ruta').AsString := LFile;
        mtTemplates.FieldByName('aplicar_a').AsString := 'TODOS';
        mtTemplates.FieldByName('cliente_id').AsInteger := 0;
        mtTemplates.FieldByName('cliente_nombre').AsString := '';
        mtTemplates.FieldByName('cliente_nombre').AsString := '';
        mtTemplates.FieldByName('pordefecto').AsBoolean := False;
        mtTemplates.FieldByName('activo').AsBoolean := True;
        mtTemplates.FieldByName('tamano').AsInteger := LSize;
        mtTemplates.FieldByName('fecha_modificacion').AsDateTime := LModDate;
        mtTemplates.Post;
      end;
    end;
  finally
    FIsPopulating := False;
  end;

  dbGridMain.AutoFitColumns;
  lblRecordCount.Caption := Format('Formatos: %d', [mtTemplates.RecordCount]);
end;

procedure TfrmReportTemplates.mtTemplatesAfterPost(DataSet: TDataSet);
var
  LQry: TFDQuery;
  LEmpresaId, LId: Integer;
  LTipoDoc, LDesc, LArch, LAplicarA: string;
  LDefecto, LActivo: Integer;
begin
  if FIsPopulating then Exit;
  if not Assigned(dmgMain) or not Assigned(dmgMain.dbConn) or not dmgMain.dbConn.Connected then Exit;

  LEmpresaId := 1;
  if Trim(dmgMain.CurrentCompanyId) <> '' then
    LEmpresaId := StrToIntDef(dmgMain.CurrentCompanyId, 1);

  LId := mtTemplates.FieldByName('id').AsInteger;
  LAplicarA := UpperCase(Trim(mtTemplates.FieldByName('aplicar_a').AsString));
  if LAplicarA = '' then LAplicarA := 'TODOS';
  LTipoDoc := TFormatosImpresionService.NormalizeTipoDoc(LAplicarA);
  LAplicarA := TFormatosImpresionService.PluralTipoDoc(LAplicarA);

  LDesc := Trim(mtTemplates.FieldByName('descripcion').AsString);
  if LDesc = '' then LDesc := ChangeFileExt(mtTemplates.FieldByName('archivo').AsString, '');
  LArch := Trim(mtTemplates.FieldByName('archivo').AsString);
  var LClienteId: Integer := mtTemplates.FieldByName('cliente_id').AsInteger;
  if mtTemplates.FieldByName('pordefecto').AsBoolean then LDefecto := 1 else LDefecto := 0;
  if mtTemplates.FieldByName('activo').AsBoolean then LActivo := 1 else LActivo := 0;

  LQry := TFDQuery.Create(nil);
  try
    LQry.Connection := dmgMain.dbConn;

    // Si no tiene ID asignado, buscar si el archivo ya existe para esta empresa para actualizarlo en vez de duplicar
    if LId <= 0 then
    begin
      LQry.SQL.Text := 'SELECT id FROM ge_formatosimpresion WHERE ide = :ide AND LOWER(archivo) = LOWER(:arch)';
      LQry.ParamByName('ide').AsInteger := LEmpresaId;
      LQry.ParamByName('arch').AsString := LArch;
      LQry.Open;
      if not LQry.IsEmpty then
        LId := LQry.FieldByName('id').AsInteger;
      LQry.Close;
    end;

    if LId <= 0 then
    begin
      // INSERT
      LQry.SQL.Text :=
        'INSERT INTO ge_formatosimpresion (ide, tipo_documento, descripcion, archivo, aplicar_a, cliente_id, pordefecto, activo) ' +
        'VALUES (:ide, :tipo, :desc, :arch, :aplicar_a, :cliente_id, :pordefecto, :activo)';
      LQry.ParamByName('ide').AsInteger := LEmpresaId;
      LQry.ParamByName('tipo').AsString := LTipoDoc;
      LQry.ParamByName('desc').AsString := LDesc;
      LQry.ParamByName('arch').AsString := LArch;
      LQry.ParamByName('aplicar_a').AsString := LAplicarA;
      if LClienteId > 0 then
        LQry.ParamByName('cliente_id').AsInteger := LClienteId
      else
        LQry.ParamByName('cliente_id').Clear;
      LQry.ParamByName('pordefecto').AsInteger := LDefecto;
      LQry.ParamByName('activo').AsInteger := LActivo;
      LQry.ExecSQL;

      LQry.SQL.Text := 'SELECT LAST_INSERT_ID() AS new_id';
      LQry.Open;
      if not LQry.IsEmpty then
      begin
        var LNewId := LQry.FieldByName('new_id').AsInteger;
        FIsPopulating := True;
        try
          mtTemplates.Edit;
          mtTemplates.FieldByName('id').AsInteger := LNewId;
          mtTemplates.Post;
        finally
          FIsPopulating := False;
        end;
      end;
    end
    else
    begin
      // UPDATE
      LQry.SQL.Text :=
        'UPDATE ge_formatosimpresion SET ' +
        '  tipo_documento = :tipo, ' +
        '  descripcion = :desc, ' +
        '  archivo = :arch, ' +
        '  aplicar_a = :aplicar_a, ' +
        '  cliente_id = :cliente_id, ' +
        '  pordefecto = :pordefecto, ' +
        '  activo = :activo ' +
        'WHERE id = :id';
      LQry.ParamByName('tipo').AsString := LTipoDoc;
      LQry.ParamByName('desc').AsString := LDesc;
      LQry.ParamByName('arch').AsString := LArch;
      LQry.ParamByName('aplicar_a').AsString := LAplicarA;
      if LClienteId > 0 then
        LQry.ParamByName('cliente_id').AsInteger := LClienteId
      else
        LQry.ParamByName('cliente_id').Clear;
      LQry.ParamByName('pordefecto').AsInteger := LDefecto;
      LQry.ParamByName('activo').AsInteger := LActivo;
      LQry.ParamByName('id').AsInteger := LId;
      LQry.ExecSQL;

      if mtTemplates.FieldByName('id').AsInteger <> LId then
      begin
        FIsPopulating := True;
        try
          mtTemplates.Edit;
          mtTemplates.FieldByName('id').AsInteger := LId;
          mtTemplates.Post;
        finally
          FIsPopulating := False;
        end;
      end;
    end;
  finally
    LQry.Free;
  end;
end;

procedure TfrmReportTemplates.dbGridMainCellClick(Column: TColumn);
var
  LId, LEmpresaId: Integer;
  LTipoDoc: string;
  LNewVal: Boolean;
begin
  if not Assigned(Column) or mtTemplates.IsEmpty then Exit;

  if SameText(Column.FieldName, 'pordefecto') then
  begin
    LId := mtTemplates.FieldByName('id').AsInteger;
    LTipoDoc := mtTemplates.FieldByName('tipo_documento').AsString;
    LNewVal := not mtTemplates.FieldByName('pordefecto').AsBoolean;

    LEmpresaId := 1;
    if Assigned(dmgMain) and (Trim(dmgMain.CurrentCompanyId) <> '') then
      LEmpresaId := StrToIntDef(dmgMain.CurrentCompanyId, 1);

    if LNewVal and (LId > 0) then
    begin
      TFormatosImpresionService.MarcarPorDefecto(LId, LTipoDoc, LEmpresaId);
      btnRefreshClick(nil);
      mtTemplates.Locate('id', LId, []);
    end
    else
    begin
      mtTemplates.Edit;
      mtTemplates.FieldByName('pordefecto').AsBoolean := LNewVal;
      mtTemplates.Post;
    end;
  end
  else if SameText(Column.FieldName, 'activo') then
  begin
    LNewVal := not mtTemplates.FieldByName('activo').AsBoolean;
    mtTemplates.Edit;
    mtTemplates.FieldByName('activo').AsBoolean := LNewVal;
    mtTemplates.Post;
  end;
end;

procedure TfrmReportTemplates.dbGridMainDblClick(Sender: TObject);
begin
  if Assigned(dbGridMain.SelectedField) and
     (SameText(dbGridMain.SelectedField.FieldName, 'pordefecto') or
      SameText(dbGridMain.SelectedField.FieldName, 'activo')) then
    Exit;

  if Assigned(dbGridMain.SelectedField) and
     SameText(dbGridMain.SelectedField.FieldName, 'cliente_nombre') then
  begin
    btnAsignarClienteClick(Sender);
    Exit;
  end;

  btnEditClick(Sender);
end;

procedure TfrmReportTemplates.btnAsignarClienteClick(Sender: TObject);
var
  LFrmSelect: TfrmSelectCliente;
  LClienteId: Integer;
begin
  if mtTemplates.IsEmpty then
  begin
    ShowMessage('Seleccione un formato de impresión en la lista.');
    Exit;
  end;

  LFrmSelect := TfrmSelectCliente.Create(nil);
  try
    if LFrmSelect.ShowModal = mrOk then
    begin
      LClienteId := StrToIntDef(LFrmSelect.SelectedId, 0);
      if LClienteId > 0 then
      begin
        mtTemplates.Edit;
        mtTemplates.FieldByName('cliente_id').AsInteger := LClienteId;
        mtTemplates.FieldByName('cliente_nombre').AsString := LFrmSelect.SelectedDesc;
        mtTemplates.Post;
      end;
    end;
  finally
    LFrmSelect.Free;
  end;
end;

procedure TfrmReportTemplates.btnQuitarClienteClick(Sender: TObject);
begin
  if mtTemplates.IsEmpty then Exit;

  if mtTemplates.FieldByName('cliente_id').AsInteger <= 0 then
  begin
    ShowMessage('El formato seleccionado no tiene ningún cliente específico asignado.');
    Exit;
  end;

  if MessageDlg('¿Desea desvincular el cliente específico de este formato de impresión?' + sLineBreak +
                'El formato pasará a ser general para todos los clientes.', mtConfirmation, [mbYes, mbNo], 0) = mrYes then
  begin
    mtTemplates.Edit;
    mtTemplates.FieldByName('cliente_id').AsInteger := 0;
    mtTemplates.FieldByName('cliente_nombre').AsString := '';
    mtTemplates.Post;
  end;
end;

procedure TfrmReportTemplates.dbGridMainEditButtonClick(Sender: TObject);
begin
  if Assigned(dbGridMain.SelectedField) and
     SameText(dbGridMain.SelectedField.FieldName, 'cliente_nombre') then
  begin
    btnAsignarClienteClick(Sender);
  end;
end;

procedure TfrmReportTemplates.dbGridMainKeyDown(Sender: TObject; var Key: Word; Shift: TShiftState);
begin
  if (Key = VK_RETURN) and mtTemplates.Active and (mtTemplates.State in [dsEdit, dsInsert]) then
  begin
    mtTemplates.Post;
    Key := 0;
    Exit;
  end;

  if (Key = VK_SPACE) and Assigned(dbGridMain.SelectedField) then
  begin
    if SameText(dbGridMain.SelectedField.FieldName, 'pordefecto') or
       SameText(dbGridMain.SelectedField.FieldName, 'activo') then
    begin
      dbGridMainCellClick(dbGridMain.Columns[dbGridMain.SelectedIndex]);
      Key := 0;
    end;
  end;
end;

procedure TfrmReportTemplates.TemplateFieldChange(Sender: TField);
begin
  if not FIsPopulating and Assigned(Sender) and mtTemplates.Active then
    PostMessage(Self.Handle, WM_AUTOSAVE_RECORD, 0, 0);
end;

procedure TfrmReportTemplates.WMAutoSaveRecord(var Msg: TMessage);
begin
  if not FIsPopulating and mtTemplates.Active and (mtTemplates.State in [dsEdit, dsInsert]) then
  begin
    try
      mtTemplates.Post;
    except
    end;
  end;
end;

procedure TfrmReportTemplates.FormClose(Sender: TObject; var Action: TCloseAction);
begin
  if mtTemplates.Active and (mtTemplates.State in [dsEdit, dsInsert]) then
    mtTemplates.Post;
  inherited;
end;

procedure TfrmReportTemplates.dbGridMainColExit(Sender: TObject);
begin
  if mtTemplates.Active and (mtTemplates.State in [dsEdit, dsInsert]) then
    mtTemplates.Post;
end;

procedure TfrmReportTemplates.btnRefreshClick(Sender: TObject);
begin
  if mtTemplates.Active and (mtTemplates.State in [dsEdit, dsInsert]) then
    mtTemplates.Post;
  PopulateTemplatesList;
end;

procedure TfrmReportTemplates.btnSetDefaultClick(Sender: TObject);
var
  LId: Integer;
  LTipoDoc: string;
  LEmpresaId: Integer;
begin
  if mtTemplates.IsEmpty then
  begin
    ShowMessage('Seleccione un formato de impresión en la lista.');
    Exit;
  end;

  LId := mtTemplates.FieldByName('id').AsInteger;
  LTipoDoc := mtTemplates.FieldByName('tipo_documento').AsString;
  if LId <= 0 then
  begin
    // Si aún no tiene ID, guardar primero
    mtTemplatesAfterPost(mtTemplates);
    LId := mtTemplates.FieldByName('id').AsInteger;
  end;

  LEmpresaId := 1;
  if Assigned(dmgMain) and (Trim(dmgMain.CurrentCompanyId) <> '') then
    LEmpresaId := StrToIntDef(dmgMain.CurrentCompanyId, 1);

  if TFormatosImpresionService.MarcarPorDefecto(LId, LTipoDoc, LEmpresaId) then
  begin
    btnRefreshClick(nil);
    mtTemplates.Locate('id', LId, []);
  end
  else
    ShowMessage('No se pudo establecer como predeterminado.');
end;

procedure TfrmReportTemplates.cbbFiltroTipoChange(Sender: TObject);
begin
  PopulateTemplatesList;
end;

procedure TfrmReportTemplates.btnNewClick(Sender: TObject);
var
  LNewName, LDefaultName, LNewDesc: string;
  LSourcePath, LDestPath, LTipoDoc: string;
  LQry: TFDQuery;
  LEmpresaId: Integer;
begin
  if mtTemplates.IsEmpty then
  begin
    ShowMessage('Seleccione una plantilla de origen en la lista para poder duplicarla.');
    Exit;
  end;

  LDefaultName := ChangeFileExt(mtTemplates.FieldByName('archivo').AsString, '') + '_copia';
  LNewName := InputBox('Duplicar Plantilla', 'Nombre del nuevo archivo .fr3 (sin extensión):', LDefaultName);
  if Trim(LNewName) = '' then Exit;

  LNewDesc := InputBox('Descripción del Formato', 'Descripción visible:', mtTemplates.FieldByName('descripcion').AsString + ' (Copia)');
  if Trim(LNewDesc) = '' then LNewDesc := LNewName;

  LNewName := ChangeFileExt(ExtractFileName(LNewName), '') + '.fr3';
  LSourcePath := mtTemplates.FieldByName('ruta').AsString;
  LDestPath := TPath.Combine(ExtractFilePath(LSourcePath), LNewName);

  if TFile.Exists(LDestPath) then
  begin
    ShowMessage('Ya existe un archivo con ese nombre en el disco.');
    Exit;
  end;

  try
    TFile.Copy(LSourcePath, LDestPath);

    if Assigned(dmgMain) and Assigned(dmgMain.dbConn) and dmgMain.dbConn.Connected then
    begin
      LEmpresaId := 1;
      if Trim(dmgMain.CurrentCompanyId) <> '' then
        LEmpresaId := StrToIntDef(dmgMain.CurrentCompanyId, 1);

      LTipoDoc := mtTemplates.FieldByName('tipo_documento').AsString;
      if LTipoDoc = '' then LTipoDoc := 'ALBARAN';

      LQry := TFDQuery.Create(nil);
      try
        LQry.Connection := dmgMain.dbConn;
        LQry.SQL.Text :=
          'INSERT INTO ge_formatosimpresion (ide, tipo_documento, descripcion, archivo, aplicar_a, pordefecto, activo) ' +
          'VALUES (:ide, :tipo, :desc, :arch, ''TODOS'', 0, 1)';
        LQry.ParamByName('ide').AsInteger := LEmpresaId;
        LQry.ParamByName('tipo').AsString := LTipoDoc;
        LQry.ParamByName('desc').AsString := LNewDesc;
        LQry.ParamByName('arch').AsString := LNewName;
        LQry.ExecSQL;
      finally
        LQry.Free;
      end;
    end;

    btnRefreshClick(nil);
    mtTemplates.Locate('archivo', LNewName, []);
  except
    on E: Exception do
      ShowMessage('Error al duplicar la plantilla: ' + E.Message);
  end;
end;

procedure TfrmReportTemplates.btnEditClick(Sender: TObject);
var
  LReportPath: string;
begin
  if mtTemplates.IsEmpty then
  begin
    ShowMessage('No hay ninguna plantilla seleccionada en la lista.');
    Exit;
  end;
  LReportPath := mtTemplates.FieldByName('ruta').AsString;

  if (LReportPath = '') or not TFile.Exists(LReportPath) then
  begin
    LReportPath := TFormatosImpresionService.ResolverRutaArchivo(mtTemplates.FieldByName('archivo').AsString);
  end;

  if not TFile.Exists(LReportPath) then
  begin
    ShowMessage('No se encuentra el archivo físico de la plantilla:' + sLineBreak + LReportPath);
    Exit;
  end;

  try
    dmgMain.frxInforme.LoadFromFile(LReportPath);

    // Vincular conjuntos de datos de pruebas para el editor
    dmgMain.frxInforme.DataSets.Clear;
    dmgMain.frxInforme.DataSets.Add(dmgMain.frxDBMaster);
    dmgMain.frxInforme.DataSets.Add(dmgMain.frxDBDetalle);
    dmgMain.frxInforme.EnabledDataSets.Clear;
    dmgMain.frxInforme.EnabledDataSets.Add(dmgMain.frxDBMaster);
    dmgMain.frxInforme.EnabledDataSets.Add(dmgMain.frxDBDetalle);

    dmgMain.frxInforme.DesignReport;
    btnRefreshClick(nil);
  except
    on E: Exception do
      ShowMessage('Error al abrir el diseñador de FastReport: ' + E.Message);
  end;
end;

procedure TfrmReportTemplates.btnDeleteClick(Sender: TObject);
var
  LFileName: string;
  LId: Integer;
  LQry: TFDQuery;
begin
  if mtTemplates.IsEmpty then Exit;
  LFileName := mtTemplates.FieldByName('archivo').AsString;
  LId := mtTemplates.FieldByName('id').AsInteger;

  if MessageDlg(Format('¿Está seguro de que desea desactivar el formato "%s"?' + sLineBreak +
                       'Se conservará el archivo físico.', [LFileName]),
                 mtConfirmation, [mbYes, mbNo], 0) = mrYes then
  begin
    try
      if (LId > 0) and Assigned(dmgMain) and Assigned(dmgMain.dbConn) then
      begin
        LQry := TFDQuery.Create(nil);
        try
          LQry.Connection := dmgMain.dbConn;
          LQry.SQL.Text := 'UPDATE ge_formatosimpresion SET activo = 0 WHERE id = :id';
          LQry.ParamByName('id').AsInteger := LId;
          LQry.ExecSQL;
        finally
          LQry.Free;
        end;
      end;
      btnRefreshClick(nil);
    except
      on E: Exception do
        ShowMessage('Error al desactivar el formato: ' + E.Message);
    end;
  end;
end;

function TfrmReportTemplates.GetValidReportPath: string;
var
  LPaths: TStringList;
  LPath: string;
  LFiles: TStringDynArray;
begin
  Result := '';

  if (FReportPath <> '') and TDirectory.Exists(FReportPath) then
  begin
    Result := FReportPath;
    Exit;
  end;

  LPaths := TStringList.Create;
  try
    LPaths.Add(ExtractFilePath(ParamStr(0)) + 'reportes');
    LPaths.Add(ExtractFilePath(ParamStr(0)) + '..\reportes');
    LPaths.Add(ExtractFilePath(ParamStr(0)) + '..\..\reportes');
    LPaths.Add(ExtractFilePath(ParamStr(0)) + 'reportes\samples');
    LPaths.Add(TPath.Combine(GetCurrentDir, 'reportes'));

    for LPath in LPaths do
    begin
      if TDirectory.Exists(LPath) then
      begin
        LFiles := TDirectory.GetFiles(LPath, '*.fr3');
        if Length(LFiles) > 0 then
        begin
          FReportPath := LPath;
          Result := LPath;
          Exit;
        end;
      end;
    end;
  finally
    LPaths.Free;
  end;
end;

procedure TfrmReportTemplates.btnChangeFolderClick(Sender: TObject);
var
  LSelectedFolder: string;
begin
  if SelectDirectory('Seleccionar Carpeta de Informes', '', LSelectedFolder) then
  begin
    FReportPath := LSelectedFolder;
    btnRefreshClick(nil);
  end;
end;

end.
