unit frm_BaseSelect;

interface

uses
  Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
  Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls, Vcl.ExtCtrls, Vcl.Grids, Vcl.DBGrids,
  Data.DB, FireDAC.Comp.Client, FireDAC.Comp.DataSet, uAppTheme, uDBGridHelper, uUIAutoScaler,
  System.Rtti, System.TypInfo;

type
  TfrmBaseSelect = class(TForm)
    pnlSearch: TPanel;
    edtSearch: TEdit;
    btnSearch: TButton;
    dbGridSelect: TDBGrid;
    dsSelect: TDataSource;
    pnlFooter: TPanel;
    btnCancel: TButton;
    btnSelect: TButton;
    procedure FormShow(Sender: TObject);
    procedure btnSearchClick(Sender: TObject); virtual;
    procedure dbGridSelectDblClick(Sender: TObject); virtual;
    procedure btnSelectClick(Sender: TObject); virtual;
    procedure edtSearchChange(Sender: TObject);
  private
    FSelectedId: string;
    FSelectedCode: string;
    FSelectedDesc: string;
    FMultiSelect: Boolean;
    FSelectedIds: TStringList;
    FSelectedCodes: TStringList;
    FSelectedDescs: TStringList;
    FTmrSearch: TTimer;
    FSearchDelay: Integer;
    procedure SetMultiSelect(const Value: Boolean);
    procedure dbGridSelectDrawColumnCell(Sender: TObject; const Rect: TRect;
      DataCol: Integer; Column: TColumn; State: TGridDrawState);
    procedure dbGridSelectTitleClick(Column: TColumn);
    procedure OnSearchTimer(Sender: TObject);
    procedure edtSearchKeyDown(Sender: TObject; var Key: Word; Shift: TShiftState);
  protected
    procedure DoSearch; virtual;
    procedure DoSelect; virtual;
  public
    constructor Create(AOwner: TComponent); override;
    destructor Destroy; override;
    property SelectedId: string read FSelectedId write FSelectedId;
    property SelectedCode: string read FSelectedCode write FSelectedCode;
    property SelectedDesc: string read FSelectedDesc write FSelectedDesc;
    property MultiSelect: Boolean read FMultiSelect write SetMultiSelect;
    property SelectedIds: TStringList read FSelectedIds;
    property SelectedCodes: TStringList read FSelectedCodes;
    property SelectedDescs: TStringList read FSelectedDescs;
    property SearchDelay: Integer read FSearchDelay write FSearchDelay;
  end;

var
  frmBaseSelect: TfrmBaseSelect;

implementation

uses
  dmg_Main;

{$R *.dfm}

constructor TfrmBaseSelect.Create(AOwner: TComponent);
begin
  inherited Create(AOwner);
  FSelectedIds := TStringList.Create;
  FSelectedCodes := TStringList.Create;
  FSelectedDescs := TStringList.Create;
  FSearchDelay := 500; // 500 ms debounce para puestos remotos
  FTmrSearch := TTimer.Create(Self);
  FTmrSearch.Enabled := False;
  FTmrSearch.Interval := FSearchDelay;
  FTmrSearch.OnTimer := OnSearchTimer;
end;

destructor TfrmBaseSelect.Destroy;
begin
  if Assigned(FTmrSearch) then
  begin
    FTmrSearch.Enabled := False;
    FreeAndNil(FTmrSearch);
  end;
  TUIAutoScaler.UnregisterForm(Self);
  FSelectedIds.Free;
  FSelectedCodes.Free;
  FSelectedDescs.Free;
  inherited Destroy;
end;

procedure TfrmBaseSelect.SetMultiSelect(const Value: Boolean);
begin
  FMultiSelect := Value;
  if FMultiSelect then
    dbGridSelect.Options := dbGridSelect.Options + [dgMultiSelect]
  else
    dbGridSelect.Options := dbGridSelect.Options - [dgMultiSelect];
end;

procedure TfrmBaseSelect.btnSearchClick(Sender: TObject);
begin
  if Assigned(FTmrSearch) then
    FTmrSearch.Enabled := False;
  DoSearch;
end;

procedure TfrmBaseSelect.btnSelectClick(Sender: TObject);
begin
  DoSelect;
end;

procedure TfrmBaseSelect.dbGridSelectDblClick(Sender: TObject);
begin
  DoSelect;
end;

procedure TfrmBaseSelect.DoSearch;
begin
  // Implementado por las clases hijas
end;

procedure TfrmBaseSelect.DoSelect;
var
  i: Integer;
  BookmarkList: TBookmarkList;
  DataSet: TDataSet;
begin
  DataSet := dbGridSelect.DataSource.DataSet;
  if not Assigned(DataSet) or not DataSet.Active then
    Exit;

  if FMultiSelect then
  begin
    FSelectedIds.Clear;
    FSelectedCodes.Clear;
    FSelectedDescs.Clear;

    BookmarkList := dbGridSelect.SelectedRows;
    if BookmarkList.Count > 0 then
    begin
      DataSet.DisableControls;
      try
        for i := 0 to BookmarkList.Count - 1 do
        begin
          DataSet.GotoBookmark(BookmarkList[i]);
          FSelectedIds.Add(DataSet.FieldByName('id').AsString);
          
          if DataSet.FindField('codigo') <> nil then
            FSelectedCodes.Add(DataSet.FieldByName('codigo').AsString)
          else if DataSet.FindField('codigo_str') <> nil then
            FSelectedCodes.Add(DataSet.FieldByName('codigo_str').AsString);
            
          if DataSet.FindField('descripcion') <> nil then
            FSelectedDescs.Add(DataSet.FieldByName('descripcion').AsString)
          else if DataSet.FindField('nombre') <> nil then
            FSelectedDescs.Add(DataSet.FieldByName('nombre').AsString);
        end;
      finally
        DataSet.EnableControls;
      end;
      ModalResult := mrOk;
    end
    else
    begin
      if not DataSet.IsEmpty then
      begin
        FSelectedIds.Add(DataSet.FieldByName('id').AsString);
        
        if DataSet.FindField('codigo') <> nil then
          FSelectedCodes.Add(DataSet.FieldByName('codigo').AsString)
        else if DataSet.FindField('codigo_str') <> nil then
          FSelectedCodes.Add(DataSet.FieldByName('codigo_str').AsString);
          
        if DataSet.FindField('descripcion') <> nil then
          FSelectedDescs.Add(DataSet.FieldByName('descripcion').AsString)
        else if DataSet.FindField('nombre') <> nil then
          FSelectedDescs.Add(DataSet.FieldByName('nombre').AsString);
        ModalResult := mrOk;
      end;
    end;
  end
  else
  begin
    // Comportamiento por defecto para selección simple
    if not DataSet.IsEmpty then
    begin
      FSelectedId := DataSet.FieldByName('id').AsString;
      
      if DataSet.FindField('codigo') <> nil then
        FSelectedCode := DataSet.FieldByName('codigo').AsString
      else if DataSet.FindField('codigo_str') <> nil then
        FSelectedCode := DataSet.FieldByName('codigo_str').AsString;
        
      if DataSet.FindField('descripcion') <> nil then
        FSelectedDesc := DataSet.FieldByName('descripcion').AsString
      else if DataSet.FindField('nombre') <> nil then
        FSelectedDesc := DataSet.FieldByName('nombre').AsString;
        
      ModalResult := mrOk;
    end;
  end;
end;

procedure TfrmBaseSelect.edtSearchChange(Sender: TObject);
begin
  if Assigned(FTmrSearch) then
  begin
    FTmrSearch.Enabled := False;
    FTmrSearch.Interval := FSearchDelay;
    FTmrSearch.Enabled := True;
  end
  else
    DoSearch;
end;

procedure TfrmBaseSelect.OnSearchTimer(Sender: TObject);
begin
  if Assigned(FTmrSearch) then
    FTmrSearch.Enabled := False;
  DoSearch;
end;

procedure TfrmBaseSelect.edtSearchKeyDown(Sender: TObject; var Key: Word; Shift: TShiftState);
begin
  if Key = VK_RETURN then
  begin
    Key := 0; // Prevent beep
    if Assigned(FTmrSearch) then
      FTmrSearch.Enabled := False;
    DoSearch;
  end;
end;

procedure TfrmBaseSelect.FormShow(Sender: TObject);
begin
  TUIAutoScaler.RegisterForm(Self);
  TUIAutoScaler.ScaleForm(Self);

  TAppTheme.ApplyToForm(Self);
  TAppTheme.ApplyUpperCaseToEdits(Self);

  // IMPORTANTE: Restaurar CharCase del campo de búsqueda a ecNormal.
  // La API ES_UPPERCASE de Windows (usada por ecUpperCase) corrompe
  // caracteres especiales como Ñ, Ç y vocales con tilde en la entrada de texto.
  // La conversión a mayúsculas se hace en DoSearch con UpperCase() de Delphi.
  edtSearch.CharCase := ecNormal;

  dsSelect.DataSet := dmgMain.qryExec;

  edtSearch.OnChange := edtSearchChange;
  edtSearch.OnKeyDown := edtSearchKeyDown;

  // Asegurar que la grilla responde al clic en las cabeceras
  dbGridSelect.Options := dbGridSelect.Options + [dgTitleClick, dgTitleHotTrack];

  if not Assigned(dbGridSelect.OnDrawColumnCell) then
    dbGridSelect.OnDrawColumnCell := dbGridSelectDrawColumnCell;

  if not Assigned(dbGridSelect.OnTitleClick) then
    dbGridSelect.OnTitleClick := dbGridSelectTitleClick;

  DoSearch;
  dbGridSelect.AutoFitColumns;
end;

procedure TfrmBaseSelect.dbGridSelectDrawColumnCell(Sender: TObject; const Rect: TRect;
  DataCol: Integer; Column: TColumn; State: TGridDrawState);
var
  Grid: TDBGrid;
  IsOddRow: Boolean;
begin
  Grid := TDBGrid(Sender);

  // Aplicar color de fondo alterno si la fila no está seleccionada
  if not (gdSelected in State) then
  begin
    if Assigned(Grid.DataSource) and Assigned(Grid.DataSource.DataSet) and Grid.DataSource.DataSet.Active then
      IsOddRow := Odd(Grid.DataSource.DataSet.RecNo)
    else
      IsOddRow := False;

    if IsOddRow then
      Grid.Canvas.Brush.Color := TAppTheme.COLOR_GRID_ROW_ODD
    else
      Grid.Canvas.Brush.Color := TAppTheme.COLOR_GRID_ROW_EVEN;
  end;

  Grid.DefaultDrawColumnCell(Rect, DataCol, Column, State);
end;

procedure TfrmBaseSelect.dbGridSelectTitleClick(Column: TColumn);
var
  LDataSet: TDataSet;
  Context: TRttiContext;
  RType: TRttiType;
  Prop: TRttiProperty;
  SortStr: string;
  CurrentIndex: string;
begin
  if (dbGridSelect.DataSource = nil) or (dbGridSelect.DataSource.DataSet = nil) then Exit;
  LDataSet := dbGridSelect.DataSource.DataSet;
  if not LDataSet.Active then Exit;
  
  Context := TRttiContext.Create;
  try
    RType := Context.GetType(LDataSet.ClassType);
    Prop := RType.GetProperty('IndexFieldNames');
    if Assigned(Prop) and (Prop.Visibility in [System.TypInfo.mvPublic, System.TypInfo.mvPublished]) then
    begin
      CurrentIndex := Prop.GetValue(LDataSet).AsString;
      if SameText(CurrentIndex, Column.FieldName) or SameText(CurrentIndex, Column.FieldName + ':A') then
        SortStr := Column.FieldName + ':D'
      else
        SortStr := Column.FieldName + ':A';
        
      Prop.SetValue(LDataSet, SortStr);
    end;
  finally
    Context.Free;
  end;
end;

end.
