﻿unit frm_PlantaMain;

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, FireDAC.Comp.Client,
  uScaleConfig, uScaleSerial, uSyncEngine, dm_LocalData, FireDAC.Stan.Intf,
  FireDAC.Stan.Option, FireDAC.Stan.Param, FireDAC.Stan.Error, FireDAC.DatS,
  FireDAC.Phys.Intf, FireDAC.DApt.Intf, FireDAC.Stan.Async, FireDAC.DApt,
  FireDAC.Comp.DataSet;

type
  TfrmPlantaMain = class(TForm)
    pnlHeader: TPanel;
    lblAppTitle: TLabel;
    lblReloj: TLabel;
    pnlNetStatus: TPanel;
    lblNetStatus: TLabel;
    lblNetDetalle: TLabel;
    btnSyncManual: TButton;
    btnConfig: TButton;
    pnlCenter: TPanel;
    pnlOrdersList: TPanel;
    lblListTitle: TLabel;
    dbgOrders: TDBGrid;
    dsOrders: TDataSource;
    qryOrders: TFDQuery;
    pnlActions: TPanel;
    btnNuevaOrden: TButton;
    btnIniciarPesaje: TButton;
    btnControlAmasado: TButton;
    btnVerConsumos: TButton;
    btnRefrescar: TButton;
    tmrReloj: TTimer;
    procedure FormCreate(Sender: TObject);
    procedure FormDestroy(Sender: TObject);
    procedure tmrRelojTimer(Sender: TObject);
    procedure btnConfigClick(Sender: TObject);
    procedure btnSyncManualClick(Sender: TObject);
    procedure btnRefrescarClick(Sender: TObject);
    procedure btnNuevaOrdenClick(Sender: TObject);
    procedure btnIniciarPesajeClick(Sender: TObject);
    procedure btnControlAmasadoClick(Sender: TObject);
    procedure btnVerConsumosClick(Sender: TObject);
    procedure dbgOrdersDrawColumnCell(Sender: TObject; const Rect: TRect;
      DataCol: Integer; Column: TColumn; State: TGridDrawState);
  private
    FScaleConfig: TScaleConfig;
    FScaleReader: TScaleSerialReader;
    FSyncEngine: TSyncEngine;

    procedure OnSyncStatus(Sender: TObject; AStatus: TNetStatus; APendingCount: Integer; const AMessage: string);
    procedure RefrescarOrdenes;
    function GetSelectedGuid: string;
  public
  end;

var
  frmPlantaMain: TfrmPlantaMain;

implementation

uses
  frm_PlantaConfig, frm_PlantaPesaje, frm_PlantaAmasado;

{$R *.dfm}

procedure TfrmPlantaMain.FormCreate(Sender: TObject);
begin
  Tag := 999;
  FScaleConfig := TScaleConfig.LoadFromIni;

  // 1. Iniciar hardware de báscula
  FScaleReader := TScaleSerialReader.Create(FScaleConfig);
  FScaleReader.Connect;

  // 2. Iniciar motor de sincronización
  FSyncEngine := TSyncEngine.Create(FScaleConfig);
  FSyncEngine.OnSyncStatus := OnSyncStatus;
  FSyncEngine.Start;

  // 3. Cargar listado local de órdenes
  RefrescarOrdenes;
end;

procedure TfrmPlantaMain.FormDestroy(Sender: TObject);
begin
  if Assigned(FSyncEngine) then
  begin
    FSyncEngine.Stop;
    FreeAndNil(FSyncEngine);
  end;

  if Assigned(FScaleReader) then
  begin
    FScaleReader.Disconnect;
    FreeAndNil(FScaleReader);
  end;
end;

procedure TfrmPlantaMain.tmrRelojTimer(Sender: TObject);
begin
  lblReloj.Caption := FormatDateTime('hh:nn:ss', Now);
end;

procedure TfrmPlantaMain.OnSyncStatus(Sender: TObject; AStatus: TNetStatus; APendingCount: Integer; const AMessage: string);
begin
  case AStatus of
    nsOnline:
      begin
        pnlNetStatus.Color := $00D4FCD4; // Verde pastel
        lblNetStatus.Caption := '🟢 EN LÍNEA';
        lblNetStatus.Font.Color := $00006600;
        lblNetDetalle.Caption := 'Sincronizado con Servidor Central';
      end;
    nsOffline:
      begin
        pnlNetStatus.Color := $00D6D6FF; // Rojo pastel
        lblNetStatus.Caption := '🔴 MODO DESCONECTADO (OFFLINE)';
        lblNetStatus.Font.Color := $00000099;
        lblNetDetalle.Caption := Format('Operando en local (%d transacciones pendientes)', [APendingCount]);
      end;
    nsSyncing:
      begin
        pnlNetStatus.Color := $00FFF3D6; // Amarillo pastel
        lblNetStatus.Caption := '🟡 SINCRONIZANDO...';
        lblNetStatus.Font.Color := $00664400;
        lblNetDetalle.Caption := Format('Enviando %d registros a la central...', [APendingCount]);
      end;
  end;
end;

procedure TfrmPlantaMain.RefrescarOrdenes;
begin
  qryOrders.DisableControls;
  try
    qryOrders.Close;
    qryOrders.Connection := dmLocalData.localConn;
    qryOrders.SQL.Text :=
      'SELECT local_guid, server_id, receta_nombre, numero_masas, multiplicador, ' +
      '       masa_actual, estado, sync_status, hora_inicio, hora_fin, created_at ' +
      'FROM local_elaboraciones ' +
      'ORDER BY CASE estado ' +
      '  WHEN ''PENDIENTE'' THEN 1 ' +
      '  WHEN ''EN_PESAJE'' THEN 2 ' +
      '  WHEN ''EN_AMASADO'' THEN 3 ' +
      '  WHEN ''FINALIZADA'' THEN 4 ' +
      '  ELSE 5 END, created_at DESC';
    qryOrders.Open;
  finally
    qryOrders.EnableControls;
  end;
end;

function TfrmPlantaMain.GetSelectedGuid: string;
begin
  Result := '';
  if qryOrders.Active and not qryOrders.IsEmpty then
    Result := qryOrders.FieldByName('local_guid').AsString;
end;

procedure TfrmPlantaMain.btnRefrescarClick(Sender: TObject);
begin
  RefrescarOrdenes;
end;

procedure TfrmPlantaMain.btnConfigClick(Sender: TObject);
begin
  if TfrmPlantaConfig.Execute(FScaleConfig) then
  begin
    FScaleReader.Disconnect;
    FScaleReader.Config := FScaleConfig;
    FScaleReader.Connect;

    FSyncEngine.Stop;
    FSyncEngine.Free;
    FSyncEngine := TSyncEngine.Create(FScaleConfig);
    FSyncEngine.OnSyncStatus := OnSyncStatus;
    FSyncEngine.Start;
  end;
end;

procedure TfrmPlantaMain.btnSyncManualClick(Sender: TObject);
begin
  if Assigned(FSyncEngine) then
    FSyncEngine.TriggerSync;
  RefrescarOrdenes;
end;

procedure TfrmPlantaMain.btnNuevaOrdenClick(Sender: TObject);
var
  LQryRecetas: TFDQuery;
  LListaRecetas: string;
  LRes: string;
  I: Integer;
  LRecetaId: Integer;
  LRecetaNombre: string;
  LNumMasasStr: string;
  LNumMasas: Integer;
  LMultiplicadorStr: string;
  LMultiplicador: Double;
  LNewGuid: string;
begin
  // Listar recetas locales disponibles
  LQryRecetas := dmLocalData.OpenLocalQuery('SELECT id_servidor, descripcion FROM local_recetas WHERE activo = 1');
  try
    if LQryRecetas.IsEmpty then
    begin
      ShowMessage('No hay recetas en la base de datos local. Conéctese al servidor para descargarlas.');
      Exit;
    end;

    LListaRecetas := 'Seleccione la Receta a Elaborar:' + sLineBreak;
    I := 1;
    while not LQryRecetas.Eof do
    begin
      LListaRecetas := LListaRecetas + Format('[%d] %s' + sLineBreak,
        [I, LQryRecetas.FieldByName('descripcion').AsString]);
      Inc(I);
      LQryRecetas.Next;
    end;

    LRes := InputBox('Nueva Elaboración de Fábrica', LListaRecetas + sLineBreak + 'Escriba el nombre o número de receta:', '');
    if Trim(LRes) = '' then Exit;

    LRecetaId := 0;
    LRecetaNombre := '';

    if StrToIntDef(LRes, 0) > 0 then
    begin
      // Selección por número de índice
      LQryRecetas.First;
      I := 1;
      while not LQryRecetas.Eof do
      begin
        if I = StrToInt(LRes) then
        begin
          LRecetaId := LQryRecetas.FieldByName('id_servidor').AsInteger;
          LRecetaNombre := LQryRecetas.FieldByName('descripcion').AsString;
          Break;
        end;
        Inc(I);
        LQryRecetas.Next;
      end;
    end
    else
    begin
      // Selección por nombre
      LQryRecetas.First;
      while not LQryRecetas.Eof do
      begin
        if Pos(UpperCase(LRes), UpperCase(LQryRecetas.FieldByName('descripcion').AsString)) > 0 then
        begin
          LRecetaId := LQryRecetas.FieldByName('id_servidor').AsInteger;
          LRecetaNombre := LQryRecetas.FieldByName('descripcion').AsString;
          Break;
        end;
        LQryRecetas.Next;
      end;
    end;

    if LRecetaId = 0 then
    begin
      ShowMessage('Receta no encontrada.');
      Exit;
    end;

    LNumMasasStr := InputBox('Número de Masas', '¿Cuántas masas completas se van a preparar?', '1');
    LNumMasas := StrToIntDef(LNumMasasStr, 1);
    if LNumMasas <= 0 then LNumMasas := 1;

    LMultiplicadorStr := InputBox('Factor de Escala', 'Multiplicador de receta (1.0 = normal):', '1.0');
    LMultiplicador := StrToFloatDef(StringReplace(LMultiplicadorStr, ',', '.', [rfReplaceAll]), 1.0);

    LNewGuid := dmLocalData.NewGuidString;
    dmLocalData.ExecLocalSQL(
      'INSERT INTO local_elaboraciones (' +
      '  local_guid, server_id, id_obrador, id_receta, receta_nombre, ' +
      '  numero_masas, multiplicador, masa_actual, estado, sync_status, created_at' +
      ') VALUES (?, 0, ?, ?, ?, ?, ?, 1, ''PENDIENTE'', ''PENDING'', ?)',
      [LNewGuid, FScaleConfig.DefaultObradorId, LRecetaId, LRecetaNombre,
       LNumMasas, LMultiplicador, FormatDateTime('yyyy-mm-dd hh:nn:ss', Now)]
    );

    RefrescarOrdenes;

    if MessageDlg(Format('Orden de "%s" creada con éxito.' + sLineBreak + '¿Desea iniciar el pesaje de ingredientes ahora?', [LRecetaNombre]),
      mtConfirmation, [mbYes, mbNo], 0) = mrYes then
    begin
      TfrmPlantaPesaje.IniciarPesaje(
        LNewGuid, LRecetaId, LRecetaNombre, LNumMasas, LMultiplicador, 1, FScaleReader
      );
      RefrescarOrdenes;
    end;

  finally
    LQryRecetas.Free;
  end;
end;

procedure TfrmPlantaMain.btnIniciarPesajeClick(Sender: TObject);
var
  LGuid: string;
  LRecetaId: Integer;
  LRecetaNombre: string;
  LMasas, LMasaActual: Integer;
  LMult: Double;
  LEstado: string;
begin
  LGuid := GetSelectedGuid;
  if LGuid = '' then
  begin
    ShowMessage('Por favor, seleccione una orden de fabricación de la lista.');
    Exit;
  end;

  LEstado := qryOrders.FieldByName('estado').AsString;
  if (LEstado = 'FINALIZADA') then
  begin
    ShowMessage('Esta orden ya ha sido completada y finalizada.');
    Exit;
  end;

  LRecetaId := 0;
  // Obtener id_receta de la orden
  with dmLocalData.OpenLocalQuery('SELECT id_receta FROM local_elaboraciones WHERE local_guid = ?', [LGuid]) do
  try
    if not IsEmpty then
      LRecetaId := FieldByName('id_receta').AsInteger;
  finally
    Free;
  end;

  LRecetaNombre := qryOrders.FieldByName('receta_nombre').AsString;
  LMasas := qryOrders.FieldByName('numero_masas').AsInteger;
  LMasaActual := qryOrders.FieldByName('masa_actual').AsInteger;
  if LMasaActual <= 0 then LMasaActual := 1;
  LMult := qryOrders.FieldByName('multiplicador').AsFloat;
  if LMult <= 0 then LMult := 1.0;

  dmLocalData.ExecLocalSQL(
    'UPDATE local_elaboraciones SET estado = ''EN_PESAJE'' WHERE local_guid = ?',
    [LGuid]
  );

  TfrmPlantaPesaje.IniciarPesaje(
    LGuid, LRecetaId, LRecetaNombre, LMasas, LMult, LMasaActual, FScaleReader
  );

  RefrescarOrdenes;
end;

procedure TfrmPlantaMain.btnControlAmasadoClick(Sender: TObject);
var
  LGuid: string;
  LRecetaNombre: string;
  LEstado: string;
begin
  LGuid := GetSelectedGuid;
  if LGuid = '' then
  begin
    ShowMessage('Por favor, seleccione una orden de fabricación de la lista.');
    Exit;
  end;

  LEstado := qryOrders.FieldByName('estado').AsString;
  if LEstado = 'FINALIZADA' then
  begin
    ShowMessage('Esta orden ya ha sido finalizada.');
    Exit;
  end;

  LRecetaNombre := qryOrders.FieldByName('receta_nombre').AsString;
  TfrmPlantaAmasado.ControlarAmasado(LGuid, LRecetaNombre, 15);
  RefrescarOrdenes;
end;

procedure TfrmPlantaMain.btnVerConsumosClick(Sender: TObject);
var
  LGuid: string;
  LQry: TFDQuery;
  LMsg: string;
begin
  LGuid := GetSelectedGuid;
  if LGuid = '' then
  begin
    ShowMessage('Seleccione una orden para ver sus consumos de materias primas.');
    Exit;
  end;

  LQry := dmLocalData.OpenLocalQuery(
    'SELECT materia_prima_nombre, lote_texto, cantidad, unidad, masa_numero, sync_status ' +
    'FROM local_lotes_consumidos WHERE elaboracion_guid = ? ORDER BY masa_numero, local_guid',
    [LGuid]
  );
  try
    if LQry.IsEmpty then
    begin
      ShowMessage('Aún no se han registrado consumos de materias primas para esta orden.');
      Exit;
    end;

    LMsg := 'TRAZABILIDAD Y LOTES PESADOS:' + sLineBreak + sLineBreak;
    while not LQry.Eof do
    begin
      LMsg := LMsg + Format('• Masa %d: %s -> %.1f %s [Lote: %s] (%s)' + sLineBreak,
        [LQry.FieldByName('masa_numero').AsInteger,
         LQry.FieldByName('materia_prima_nombre').AsString,
         LQry.FieldByName('cantidad').AsFloat,
         LQry.FieldByName('unidad').AsString,
         LQry.FieldByName('lote_texto').AsString,
         LQry.FieldByName('sync_status').AsString]);
      LQry.Next;
    end;

    ShowMessage(LMsg);
  finally
    LQry.Free;
  end;
end;

procedure TfrmPlantaMain.dbgOrdersDrawColumnCell(Sender: TObject;
  const Rect: TRect; DataCol: Integer; Column: TColumn; State: TGridDrawState);
var
  LEstado: string;
begin
  if qryOrders.Active and not qryOrders.IsEmpty then
  begin
    LEstado := qryOrders.FieldByName('estado').AsString;
    if Column.FieldName = 'estado' then
    begin
      if LEstado = 'PENDIENTE' then
      begin
        dbgOrders.Canvas.Brush.Color := $00E0E0E0;
        dbgOrders.Canvas.Font.Color := clBlack;
      end
      else if LEstado = 'EN_PESAJE' then
      begin
        dbgOrders.Canvas.Brush.Color := $00FFF3D6;
        dbgOrders.Canvas.Font.Color := $00663300;
      end
      else if LEstado = 'EN_AMASADO' then
      begin
        dbgOrders.Canvas.Brush.Color := $00E1FBE1;
        dbgOrders.Canvas.Font.Color := $00006600;
      end
      else if LEstado = 'FINALIZADA' then
      begin
        dbgOrders.Canvas.Brush.Color := $00D4FCD4;
        dbgOrders.Canvas.Font.Color := $00004400;
      end;
    end;
  end;

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

end.
