unit frm_PrintOptions;

interface

uses
  Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
  Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls, Vcl.Buttons, Vcl.ExtCtrls, System.UITypes,
  frxClass, Vcl.DBGrids, Data.DB, uAppTheme;

type
  TIndividualDocExportCallback = reference to function(const AOutputDir: string; out AFileNames: TArray<string>): Boolean;
  TMultiEmailCallback = reference to function(const APdfPath: string): Boolean;
  TChangeReportFormatCallback = reference to procedure(const AReportPath: string);

  TfrmPrintOptions = class(TForm)
    pnlHeader: TPanel;
    lblTitle: TLabel;
    pnlFormatSelector: TPanel;
    lblFormato: TLabel;
    cbbFormato: TComboBox;
    pnlButtons: TPanel;
    btnPreview: TBitBtn;
    btnPrint: TBitBtn;
    btnPDF: TBitBtn;
    btnExcel: TBitBtn;
    btnEmail: TBitBtn;
    pnlFooter: TPanel;
    btnCancel: TBitBtn;
    procedure FormCreate(Sender: TObject);
    procedure btnPreviewClick(Sender: TObject);
    procedure btnPrintClick(Sender: TObject);
    procedure btnPDFClick(Sender: TObject);
    procedure btnExcelClick(Sender: TObject);
    procedure btnEmailClick(Sender: TObject);
    procedure cbbFormatoChange(Sender: TObject);
  private
    FReport: TfrxReport;
    FGrid: TDBGrid;
    FReportName: string;
    FEmail: string;
    FTelefono: string;
    FDocCount: Integer;
    FTipoDoc: string;
    FEmpresaId: Integer;
    FClienteId: Integer;
    FIndividualCallback: TIndividualDocExportCallback;
    FMultiEmailCallback: TMultiEmailCallback;
    FOnFormatChange: TChangeReportFormatCallback;
    procedure ExportReportToPDF(const APath: string);
    procedure ExportGridToExcel;
    function SeleccionarDirectorio(out ADir: string): Boolean;
  public
    destructor Destroy; override;
    class procedure ShowOptions(AOwner: TComponent; AReport: TfrxReport; AGrid: TDBGrid = nil;
      const AReportName: string = ''; const AEmail: string = ''; const ATelefono: string = '';
      const ADocCount: Integer = 1; const AIndividualCallback: TIndividualDocExportCallback = nil;
      const AMultiEmailCallback: TMultiEmailCallback = nil;
      const ATipoDoc: string = ''; const AEmpresaId: Integer = 1; const AClienteId: Integer = 0;
      const AOnFormatChange: TChangeReportFormatCallback = nil);
  end;

var
  frmPrintOptions: TfrmPrintOptions;

implementation

{$R *.dfm}

uses
  dmg_Main, System.Win.ComObj, uEnviarDocumento, uEmailWhatsApp, uDbErrorHandler, frxExportPDF, System.IOUtils,
  uFormatosImpresionService;

function GetAppTempDir: string;
begin
  Result := IncludeTrailingPathDelimiter(ExtractFilePath(ParamStr(0)) + 'tmp');
  ForceDirectories(Result);
end;

function SanitizeFileName(const AName: string): string;
var
  I: Integer;
begin
  Result := AName;
  for I := 1 to Length(Result) do
  begin
    if CharInSet(Result[I], ['\', '/', ':', '*', '?', '"', '<', '>', '|']) then
      Result[I] := '_';
  end;
end;

class procedure TfrmPrintOptions.ShowOptions(AOwner: TComponent; AReport: TfrxReport; AGrid: TDBGrid = nil;
  const AReportName: string = ''; const AEmail: string = ''; const ATelefono: string = '';
  const ADocCount: Integer = 1; const AIndividualCallback: TIndividualDocExportCallback = nil;
  const AMultiEmailCallback: TMultiEmailCallback = nil;
  const ATipoDoc: string = ''; const AEmpresaId: Integer = 1; const AClienteId: Integer = 0;
  const AOnFormatChange: TChangeReportFormatCallback = nil);
var
  Frm: TfrmPrintOptions;
begin
  Frm := TfrmPrintOptions.Create(AOwner);
  try
    Frm.FReport := AReport;
    Frm.FGrid := AGrid;
    Frm.FEmail := AEmail;
    Frm.FTelefono := ATelefono;
    Frm.FDocCount := ADocCount;
    Frm.FTipoDoc := ATipoDoc;
    Frm.FEmpresaId := AEmpresaId;
    Frm.FClienteId := AClienteId;
    Frm.FIndividualCallback := AIndividualCallback;
    Frm.FMultiEmailCallback := AMultiEmailCallback;
    Frm.FOnFormatChange := AOnFormatChange;

    if AReportName <> '' then
      Frm.FReportName := SanitizeFileName(AReportName)
    else if Assigned(AReport) then
      Frm.FReportName := SanitizeFileName(AReport.ReportOptions.Name)
    else
      Frm.FReportName := 'Documento';

    // Cargar combo de formatos si se especifica tipo de documento
    if ATipoDoc <> '' then
    begin
      TFormatosImpresionService.CargarComboFormatos(Frm.cbbFormato, ATipoDoc, AEmpresaId, AClienteId);
      Frm.pnlFormatSelector.Visible := (Frm.cbbFormato.Items.Count > 0);
    end
    else
      Frm.pnlFormatSelector.Visible := False;

    // Deshabilitar opciones según los parámetros pasados
    Frm.btnPreview.Enabled := Assigned(AReport);
    Frm.btnPrint.Enabled := Assigned(AReport);
    Frm.btnPDF.Enabled := Assigned(AReport);
    Frm.btnEmail.Enabled := Assigned(AReport);
    Frm.btnExcel.Enabled := Assigned(AGrid);

    Frm.ShowModal;
  finally
    Frm.Free;
  end;
end;

destructor TfrmPrintOptions.Destroy;
var
  I: Integer;
begin
  if Assigned(cbbFormato) then
  begin
    for I := 0 to cbbFormato.Items.Count - 1 do
    begin
      if Assigned(cbbFormato.Items.Objects[I]) then
      begin
        cbbFormato.Items.Objects[I].Free;
        cbbFormato.Items.Objects[I] := nil;
      end;
    end;
    cbbFormato.Items.Clear;
  end;
  inherited Destroy;
end;

procedure TfrmPrintOptions.cbbFormatoChange(Sender: TObject);
var
  LObj: TObject;
  LPath, LRawText: string;
  P1, P2: Integer;
begin
  if (cbbFormato.ItemIndex < 0) or not Assigned(FOnFormatChange) then Exit;

  LObj := cbbFormato.Items.Objects[cbbFormato.ItemIndex];
  if Assigned(LObj) and (LObj is TFormatoItemObject) then
    LPath := TFormatoItemObject(LObj).Info.RutaCompleta
  else
  begin
    LRawText := Trim(cbbFormato.Text);
    P1 := Pos('(', LRawText);
    P2 := Pos(')', LRawText);
    if (P1 > 0) and (P2 > P1) then
      LRawText := Copy(LRawText, P1 + 1, P2 - P1 - 1);
    LPath := TFormatosImpresionService.ResolverRutaArchivo(LRawText);
  end;

  if LPath <> '' then
  begin
    Screen.Cursor := crHourGlass;
    try
      FOnFormatChange(LPath);
    finally
      Screen.Cursor := crDefault;
    end;
  end;
end;

procedure TfrmPrintOptions.FormCreate(Sender: TObject);
begin
  TAppTheme.ApplyToForm(Self);
end;

procedure TfrmPrintOptions.btnPreviewClick(Sender: TObject);
begin
  if Assigned(FReport) then
  begin
    if FReport.PreviewPages.Count > 0 then
      FReport.ShowPreparedReport
    else
      FReport.ShowReport;
    ModalResult := mrOk;
  end;
end;

procedure TfrmPrintOptions.btnPrintClick(Sender: TObject);
begin
  if Assigned(FReport) then
  begin
    FReport.Print;
    ModalResult := mrOk;
  end;
end;

function TfrmPrintOptions.SeleccionarDirectorio(out ADir: string): Boolean;
var
  LOpenDialog: TFileOpenDialog;
begin
  Result := False;
  ADir := '';
  LOpenDialog := TFileOpenDialog.Create(Self);
  try
    LOpenDialog.Options := [fdoPickFolders];
    LOpenDialog.Title := 'Seleccione la carpeta de destino para los archivos PDF';
    if LOpenDialog.Execute then
    begin
      ADir := LOpenDialog.FileName;
      Result := (Trim(ADir) <> '') and DirectoryExists(ADir);
    end;
  finally
    LOpenDialog.Free;
  end;
end;

procedure TfrmPrintOptions.btnPDFClick(Sender: TObject);
var
  SaveDialog: TSaveDialog;
  LRes: Integer;
  LSelectedDir: string;
  LFiles: TArray<string>;
begin
  if not Assigned(FReport) then Exit;

  // Si hay más de un registro y disponemos de generador individual
  if (FDocCount > 1) and Assigned(FIndividualCallback) then
  begin
    LRes := MessageDlg(
      Format('Ha seleccionado %d registros.' + sLineBreak + sLineBreak +
             '¿Desea generar documentos individuales para cada registro?' + sLineBreak + sLineBreak +
             '• Sí: Se generará un archivo PDF independiente para cada registro.' + sLineBreak +
             '• No: Se generará un único archivo PDF con todos los registros combinados.', [FDocCount]),
      mtConfirmation, [mbYes, mbNo, mbCancel], 0);

    if LRes = mrCancel then
      Exit;

    if LRes = mrYes then
    begin
      if SeleccionarDirectorio(LSelectedDir) then
      begin
        Screen.Cursor := crHourGlass;
        try
          if FIndividualCallback(LSelectedDir, LFiles) and (Length(LFiles) > 0) then
          begin
            MessageDlg(Format('Se han generado %d archivos PDF correctamente en:' + sLineBreak + '%s',
              [Length(LFiles), LSelectedDir]), mtInformation, [mbOK], 0);
            ModalResult := mrOk;
            Exit;
          end
          else
          begin
            MessageDlg('No se pudieron generar los archivos PDF individuales.', mtError, [mbOK], 0);
            Exit;
          end;
        finally
          Screen.Cursor := crDefault;
        end;
      end
      else
        Exit; // Cancelado en la selección de carpeta
    end;
    // Si LRes = mrNo, continúa al flujo estándar de archivo único combinado
  end;

  SaveDialog := TSaveDialog.Create(Self);
  try
    SaveDialog.Filter := 'Archivos PDF (*.pdf)|*.pdf|Todos los archivos (*.*)|*.*';
    SaveDialog.DefaultExt := 'pdf';
    SaveDialog.Title := 'Guardar Reporte como PDF';
    SaveDialog.FileName := FReportName + '_' + FormatDateTime('yyyyMMdd_hhmmss', Now) + '.pdf';

    if SaveDialog.Execute then
    begin
      ExportReportToPDF(SaveDialog.FileName);
      ShowMessage('Reporte exportado correctamente a PDF.');
      ModalResult := mrOk;
    end;
  finally
    SaveDialog.Free;
  end;
end;

procedure TfrmPrintOptions.ExportReportToPDF(const APath: string);
var
  LPDFExport: TfrxPDFExport;
begin
  if not Assigned(FReport) then Exit;
  
  LPDFExport := TfrxPDFExport.Create(nil);
  try
    LPDFExport.ShowDialog := False;
    LPDFExport.ShowProgress := False;
    LPDFExport.OpenAfterExport := False;
    LPDFExport.FileName := APath;
    LPDFExport.Background := True;
    LPDFExport.Compressed := False;

    FReport.EngineOptions.UseFileCache := False;
    FReport.PrepareReport(True);
    FReport.Export(LPDFExport);
  finally
    LPDFExport.Free;
  end;
end;

procedure TfrmPrintOptions.btnExcelClick(Sender: TObject);
begin
  ExportGridToExcel;
  ModalResult := mrOk;
end;

procedure TfrmPrintOptions.ExportGridToExcel;
var
  SaveDialog: TSaveDialog;
  ExcelApp: OleVariant;
  Workbook: OleVariant;
  Sheet: OleVariant;
  Row, Col, VisibleCol: Integer;
  Bookmark: TBookmark;
  DataSet: TDataSet;
  HasMultiSelect: Boolean;
  SelectedIdx: Integer;
begin
  if not Assigned(FGrid) or (FGrid.DataSource = nil) or (FGrid.DataSource.DataSet = nil) then
  begin
    ShowMessage('No hay datos para exportar.');
    Exit;
  end;

  DataSet := FGrid.DataSource.DataSet;
  if not DataSet.Active or DataSet.IsEmpty then
  begin
    ShowMessage('No hay registros activos para exportar.');
    Exit;
  end;

  SaveDialog := TSaveDialog.Create(Self);
  try
    SaveDialog.Filter := 'Archivos de Excel (*.xlsx)|*.xlsx|Todos los archivos (*.*)|*.*';
    SaveDialog.DefaultExt := 'xlsx';
    SaveDialog.Title := 'Exportar Datos a Excel';
    SaveDialog.FileName := FReportName + '_' + FormatDateTime('yyyyMMdd_hhmmss', Now) + '.xlsx';

    if SaveDialog.Execute then
    begin
      try
        ExcelApp := CreateOleObject('Excel.Application');
      except
        on E: Exception do
        begin
          ShowMessage('No se pudo iniciar Microsoft Excel. Asegúrese de tener Excel instalado.' + #13#10 + E.Message);
          Exit;
        end;
      end;

      try
        ExcelApp.Visible := False;
        ExcelApp.DisplayAlerts := False;
        Workbook := ExcelApp.Workbooks.Add;
        Sheet := Workbook.ActiveSheet;
        Sheet.Name := Copy(FReportName, 1, 30);

        // Cabeceras de columnas visibles de la rejilla
        VisibleCol := 1;
        for Col := 0 to FGrid.Columns.Count - 1 do
        begin
          if FGrid.Columns[Col].Visible then
          begin
            Sheet.Cells[1, VisibleCol] := FGrid.Columns[Col].Title.Caption;
            Inc(VisibleCol);
          end;
        end;

        Sheet.Rows[1].Font.Bold := True;

        Row := 2;
        DataSet.DisableControls;
        Bookmark := DataSet.GetBookmark;
        try
          HasMultiSelect := FGrid.SelectedRows.Count > 1;
          if HasMultiSelect then
          begin
            for SelectedIdx := 0 to FGrid.SelectedRows.Count - 1 do
            begin
              DataSet.GotoBookmark(FGrid.SelectedRows[SelectedIdx]);
              VisibleCol := 1;
              for Col := 0 to FGrid.Columns.Count - 1 do
              begin
                if FGrid.Columns[Col].Visible then
                begin
                  if Assigned(FGrid.Columns[Col].Field) then
                  begin
                    if FGrid.Columns[Col].Field.IsNull then
                      Sheet.Cells[Row, VisibleCol] := ''
                    else if FGrid.Columns[Col].Field.DataType in [ftInteger, ftSmallint, ftWord, ftAutoInc, ftLargeint] then
                      Sheet.Cells[Row, VisibleCol] := FGrid.Columns[Col].Field.AsLargeInt
                    else if FGrid.Columns[Col].Field.DataType in [ftFloat, ftCurrency, ftBCD, ftFMTBcd] then
                      Sheet.Cells[Row, VisibleCol] := FGrid.Columns[Col].Field.AsFloat
                    else
                      Sheet.Cells[Row, VisibleCol] := FGrid.Columns[Col].Field.AsString;
                  end
                  else
                    Sheet.Cells[Row, VisibleCol] := '';
                  Inc(VisibleCol);
                end;
              end;
              Inc(Row);
            end;
          end
          else
          begin
            DataSet.First;
            while not DataSet.Eof do
            begin
              VisibleCol := 1;
              for Col := 0 to FGrid.Columns.Count - 1 do
              begin
                if FGrid.Columns[Col].Visible then
                begin
                  if Assigned(FGrid.Columns[Col].Field) then
                  begin
                    if FGrid.Columns[Col].Field.IsNull then
                      Sheet.Cells[Row, VisibleCol] := ''
                    else if FGrid.Columns[Col].Field.DataType in [ftInteger, ftSmallint, ftWord, ftAutoInc, ftLargeint] then
                      Sheet.Cells[Row, VisibleCol] := FGrid.Columns[Col].Field.AsLargeInt
                    else if FGrid.Columns[Col].Field.DataType in [ftFloat, ftCurrency, ftBCD, ftFMTBcd] then
                      Sheet.Cells[Row, VisibleCol] := FGrid.Columns[Col].Field.AsFloat
                    else
                      Sheet.Cells[Row, VisibleCol] := FGrid.Columns[Col].Field.AsString;
                  end
                  else
                    Sheet.Cells[Row, VisibleCol] := '';
                  Inc(VisibleCol);
                end;
              end;
              Inc(Row);
              DataSet.Next;
            end;
          end;
        finally
          if DataSet.BookmarkValid(Bookmark) then
            DataSet.GotoBookmark(Bookmark);
          DataSet.FreeBookmark(Bookmark);
          DataSet.EnableControls;
        end;

        Sheet.Columns.AutoFit;
        Workbook.SaveAs(SaveDialog.FileName, 51); // xlOpenXMLWorkbook
        Workbook.Close(True);
        ShowMessage('Los datos se han exportado correctamente a Excel.');
      finally
        ExcelApp.Quit;
        ExcelApp := Unassigned;
      end;
    end;
  finally
    SaveDialog.Free;
  end;
end;

procedure TfrmPrintOptions.btnEmailClick(Sender: TObject);
var
  TempPDF, LTempDir, LAppTmp: string;
  LRes: Integer;
  LFiles: TArray<string>;
  LFile: string;
begin
  if not Assigned(FReport) then Exit;

  LAppTmp := GetAppTempDir;

  // Si se ha provisto un callback especializado para envío múltiple / selectivo de emails
  if Assigned(FMultiEmailCallback) then
  begin
    TempPDF := LAppTmp + SanitizeFileName(FReportName) + '_Envio.pdf';
    try
      ExportReportToPDF(TempPDF);
      try
        if FMultiEmailCallback(TempPDF) then
          ModalResult := mrOk;
      finally
        if FileExists(TempPDF) then
          DeleteFile(TempPDF);
      end;
      Exit;
    except
      on E: Exception do
      begin
        ShowMessage('Error al preparar el PDF para el envío: ' + E.Message);
        Exit;
      end;
    end;
  end;

  // Si hay más de un registro y disponemos de generador individual
  if (FDocCount > 1) and Assigned(FIndividualCallback) then
  begin
    LRes := MessageDlg(
      Format('Ha seleccionado %d registros.' + sLineBreak + sLineBreak +
             '¿Desea generar documentos individuales para cada registro?' + sLineBreak + sLineBreak +
             '• Sí: Se generarán documentos individuales y se enviarán juntos en un único correo.' + sLineBreak +
             '• No: Se generará un único documento combinado.', [FDocCount]),
      mtConfirmation, [mbYes, mbNo, mbCancel], 0);

    if LRes = mrCancel then
      Exit;

    if LRes = mrYes then
    begin
      LTempDir := IncludeTrailingPathDelimiter(LAppTmp + 'Gestion_Docs_' + FormatDateTime('yyyyMMdd_hhmmss', Now));
      ForceDirectories(LTempDir);
      Screen.Cursor := crHourGlass;
      try
        if FIndividualCallback(LTempDir, LFiles) and (Length(LFiles) > 0) then
        begin
          if not Assigned(fEnviarDocumento) then
            Application.CreateForm(TfEnviarDocumento, fEnviarDocumento);

          fEnviarDocumento.RutasPDF := LFiles;
          fEnviarDocumento.InitData(FTelefono, FEmail, '', FReportName, FReportName);

          try
            if fEnviarDocumento.ShowModal = mrOk then
              ModalResult := mrOk;
          finally
            // Eliminar archivos temporales individuales al terminar el proceso
            for LFile in LFiles do
            begin
              try
                if FileExists(LFile) then
                  DeleteFile(LFile);
              except
              end;
            end;
            try
              if DirectoryExists(LTempDir) then
                TDirectory.Delete(LTempDir, True);
            except
            end;
          end;
          Exit;
        end
        else
        begin
          MessageDlg('No se pudieron generar los documentos individuales para el envío.', mtError, [mbOK], 0);
          Exit;
        end;
      finally
        Screen.Cursor := crDefault;
      end;
    end;
    // Si LRes = mrNo, continúa al flujo normal de archivo único combinado
  end;

  // Generar PDF temporal combinado en la carpeta tmp de la aplicación
  TempPDF := LAppTmp + SanitizeFileName(FReportName) + '_Envio.pdf';
  try
    ExportReportToPDF(TempPDF);
  except
    on E: Exception do
    begin
      ShowMessage('Error al generar el PDF para envío: ' + E.Message);
      Exit;
    end;
  end;

  // Abrir ventana de envío y eliminar el archivo temporal al terminar
  try
    if not Assigned(fEnviarDocumento) then
      Application.CreateForm(TfEnviarDocumento, fEnviarDocumento);

    fEnviarDocumento.RutasPDF := [TempPDF];
    fEnviarDocumento.InitData(FTelefono, FEmail, '', FReportName, FReportName);

    if fEnviarDocumento.ShowModal = mrOk then
      ModalResult := mrOk;
  finally
    try
      if FileExists(TempPDF) then
        DeleteFile(TempPDF);
    except
    end;
  end;
end;

end.
