unit uMain1;

{$mode objfpc}{$H+}

/// <summary>
/// uMain1 — Main form for HC Launcher (Lazarus/FPC port).
///
/// Provides a GUI for configuring and launching the Harpoon Classic (HC)
/// Game Engine (GE) with complex command-line argument assembly.
///
/// This is a ONE-WAY migration from the Delphi/VCL/TMS original to
/// Lazarus/LCL with standard controls.
/// </summary>

interface

uses
  LCLIntf, LCLType, Classes, SysUtils, Forms, Controls, StdCtrls, ComCtrls,
  Dialogs, ExtCtrls, Spin, CheckLst, FileUtil,
  {$IFDEF MSWINDOWS}Registry,{$ENDIF}
  IniFiles, Math,
  uHCLProcessUtils, uCICVersionInformation, uHCLCmdLineBuilder,
  uHCLFileOrganization;

type

  /// <summary>
  /// Main application form.  Hosts all HC GE command-line configuration
  /// controls, assembles the argument string via <see cref="IHCLCmdLineBuilder"/>,
  /// and launches the GE executable.
  /// </summary>
  TfrmMain1 = class(TForm)
    // -----------------------------------------------------------------------
    // Top-bar controls (above the page control)
    // -----------------------------------------------------------------------
    lblHCDirGE: TLabel;
    edtHCDirGE: TEdit;
    btnHCDirGE: TButton;
    lblGEName: TLabel;
    edtGEName: TEdit;
    btnLaunchGE: TButton;
    lblBaseDir: TLabel;
    edtBaseDir: TEdit;
    btnBaseDir: TButton;
    ckbBaseDirOverride: TCheckBox;

    // -----------------------------------------------------------------------
    // Page control
    // -----------------------------------------------------------------------
    pct1: TPageControl;
    tshGE: TTabSheet;

    // -----------------------------------------------------------------------
    // Scroll box inside tshGE
    // -----------------------------------------------------------------------
    scbGE: TScrollBox;

    // -----------------------------------------------------------------------
    // Group: Command Line Options
    // -----------------------------------------------------------------------
    grpCmdOptsGE: TGroupBox;

    // Simple flag checkboxes
    ckbAutoPlayGE:   TCheckBox;
    ckbIgnoreRegGE:  TCheckBox;
    ckbSubAIGE:      TCheckBox;
    ckbEff115GE:     TCheckBox;
    ckbIterSaveGE:   TCheckBox;

    // Battleset by filename
    ckbBSFNGE:   TCheckBox;
    edtBSFNGE:   TEdit;
    btnBSFNGE:   TButton;

    // Battleset by number
    ckbBSNumGE:  TCheckBox;
    cbxBSNumGE:  TComboBox;

    // Scenario by filename
    ckbScenFNGE: TCheckBox;
    edtScenFNGE: TEdit;
    btnScenFNGE: TButton;

    // Scenario by number
    ckbScenNumGE: TCheckBox;
    cbxScenNumGE: TComboBox;

    // CDB res filename
    ckbCDBresFNGE: TCheckBox;
    edtCDBresFNGE: TEdit;
    btnCDBresFNGE: TButton;

    // CDB rsr filename
    ckbCDBrsrFNGE: TCheckBox;
    edtCDBrsrFNGE: TEdit;
    btnCDBrsrFNGE: TButton;

    // Save game filename
    ckbSavFNGE:  TCheckBox;
    edtSavFNGE:  TEdit;
    btnSavFNGE:  TButton;

    // Log filename
    ckbLogFNGE:  TCheckBox;
    edtLogFNGE:  TEdit;
    btnLogFNGE:  TButton;

    // Auto save interval
    ckbSavIntGE: TCheckBox;
    edtSavIntGE: TEdit;

    // Iterative saving (GT)
    ckbIterSaveGTGE:            TCheckBox;
    edtIterSaveGTIntervalGE:    TSpinEdit;
    edtIterSaveGTSavesGE:       TSpinEdit;
    lblIterSaveGTIntervalGE:    TLabel;
    lblIterSaveGTSavesGE:       TLabel;

    // Iterative save base filename (version-gated >= 2022.0.2.5)
    ckbIterSaveGTFNGE: TCheckBox;
    edtIterFNGE:       TEdit;
    btnIterFNGE:       TButton;

    // Message log filename (version-gated >= 2022.0.2.5)
    ckbLogMLFNGE: TCheckBox;
    edtLogMLFNGE: TEdit;
    btnLogMLFNGE: TButton;

    // CPU Affinity
    ckbAffinityGE: TCheckBox;
    cbxAffinityGE: TComboBox;

    // Process Priority
    ckbPriorityGE: TCheckBox;
    cbxPriorityGE: TComboBox;

    // -----------------------------------------------------------------------
    // Group: Game Play Options
    // -----------------------------------------------------------------------
    grpGameOptsGE: TGroupBox;
    ckbGOGE:           TCheckBox;
    lblGOSideGE:       TLabel;
    cbxGOSideGE:       TComboBox;
    chkGOSidesGE:      TCheckListBox;
    lblGONukeRelGE:    TLabel;
    cbxGONukeRelGE:    TComboBox;
    lblGOSnorkSubGE:   TLabel;
    cbxGOSnorkSubGE:   TComboBox;
    lblGORealWeathGE:  TLabel;
    cbxGORealWeathGE:  TComboBox;
    lblGOMaintFailGE:  TLabel;
    cbxGOMaintFailGE:  TComboBox;
    lblGOFullOrdGE:    TLabel;
    cbxGOFullOrdGE:    TComboBox;
    lblGOFormPatGE:    TLabel;
    cbxGOFormPatGE:    TComboBox;
    lblGOExitGE:       TLabel;
    cbxGOExitGE:       TComboBox;
    lblGOTimeCompGE:   TLabel;
    cbxGOTimeCompGE:   TComboBox;

    // -----------------------------------------------------------------------
    // Group: Logging Options
    // -----------------------------------------------------------------------
    grpLogOptsGE: TGroupBox;
    ckbLOGE:             TCheckBox;
    ckbLOinit_annexGE:   TCheckBox;
    ckbLOac_aiGE:        TCheckBox;
    ckbLOsub_aiGE:       TCheckBox;
    ckbLOemcon_aiGE:     TCheckBox;
    ckbLOsonarGE:        TCheckBox;
    ckbLOrefuelGE:       TCheckBox;
    ckbLOmissionGE:      TCheckBox;
    ckbLOinterceptGE:    TCheckBox;
    ckbLOres_fileGE:     TCheckBox;
    ckbLOloadGE:         TCheckBox;
    ckbLOradarGE:        TCheckBox;
    ckbLOecmGE:          TCheckBox;
    ckbLOmissileGE:      TCheckBox;
    ckbLOareadefGE:      TCheckBox;
    ckbLOperfGE:         TCheckBox;
    ckbLObad_dbGE:       TCheckBox;
    ckbLOgraphicGE:      TCheckBox;
    ckbLOunitGE:         TCheckBox;
    ckbLOallGE:          TCheckBox;

    // -----------------------------------------------------------------------
    // Group: Hide specified default game windows
    // -----------------------------------------------------------------------
    grpWindowHideGE: TGroupBox;
    ckbWHGE:            TCheckBox;
    ckbWHGroupGE:       TCheckBox;
    ckbWHUnitGE:        TCheckBox;
    ckbWHStratMapGE:    TCheckBox;
    ckbWHMiniReportGE:  TCheckBox;
    ckbWHMessageLogGE:  TCheckBox;

    // -----------------------------------------------------------------------
    // Group: Issue specific debugging and logging (version-gated >= 2022.0.1.5)
    // -----------------------------------------------------------------------
    grpDebugOptsGE: TGroupBox;
    ckbIDLGE:        TCheckBox;
    ckbIDLI32501GE:  TCheckBox;

    // -----------------------------------------------------------------------
    // Command line preview
    // -----------------------------------------------------------------------
    edtCmdLineGE: TEdit;

    // -----------------------------------------------------------------------
    // Status bar
    // -----------------------------------------------------------------------
    StatusBar1: TStatusBar;

    // -----------------------------------------------------------------------
    // Open dialogs (8 file pickers)
    // -----------------------------------------------------------------------
    dlgBSFNGE:      TOpenDialog;
    dlgScenFNGE:    TOpenDialog;
    dlgCDBresFNGE:  TOpenDialog;
    dlgCDBrsrFNGE:  TOpenDialog;
    dlgSavFNGE:     TOpenDialog;
    dlgLogFNGE:     TOpenDialog;
    dlgIterFNGE:    TOpenDialog;
    dlgLogMLFNGE:   TOpenDialog;

    // -----------------------------------------------------------------------
    // Event handlers — form lifecycle
    // -----------------------------------------------------------------------
    procedure FormCreate(Sender: TObject);
    procedure FormDestroy(Sender: TObject);

    // -----------------------------------------------------------------------
    // Event handlers — top-bar
    // -----------------------------------------------------------------------
    /// <summary>Opens a directory picker to select the HC install directory.</summary>
    procedure btnHCDirGEClick(Sender: TObject);
    /// <summary>Triggers version detection and command-line rebuild.</summary>
    procedure edtGENameChange(Sender: TObject);
    /// <summary>Launches the configured GE executable.</summary>
    procedure btnLaunchGEClick(Sender: TObject);
    /// <summary>Rebuilds the command line when the HC directory changes.</summary>
    procedure edtHCDirGEChange(Sender: TObject);
    /// <summary>Opens a directory picker for the managed files base directory.</summary>
    procedure btnBaseDirClick(Sender: TObject);
    /// <summary>Toggles custom basedir override on/off.</summary>
    procedure ckbBaseDirOverrideChange(Sender: TObject);

    // -----------------------------------------------------------------------
    // Event handlers — mutual-exclusion checkboxes
    // -----------------------------------------------------------------------
    /// <summary>Ensures BS-by-filename and BS-by-number are mutually exclusive.</summary>
    procedure ckbBSNumGEChange(Sender: TObject);
    /// <summary>Ensures BS-by-filename and BS-by-number are mutually exclusive.</summary>
    procedure ckbBSFNGEChange(Sender: TObject);
    /// <summary>Ensures Scen-by-filename and Scen-by-number are mutually exclusive.</summary>
    procedure ckbScenNumGEChange(Sender: TObject);
    /// <summary>Ensures Scen-by-filename and Scen-by-number are mutually exclusive.</summary>
    procedure ckbScenFNGEChange(Sender: TObject);
    /// <summary>When "all" is checked, clears individual log checkboxes.</summary>
    procedure ckbLOallGEChange(Sender: TObject);
    /// <summary>When any individual log checkbox is checked, clears the "all" checkbox.</summary>
    procedure ckbLOIndividualChange(Sender: TObject);
    /// <summary>Sets the master debug checkbox when the I32501 sub-checkbox changes.</summary>
    procedure ckbIDLI32501GEChange(Sender: TObject);
    /// <summary>Sets the master window-hide checkbox when any sub-checkbox is enabled.</summary>
    procedure ckbWHSubChange(Sender: TObject);

    // -----------------------------------------------------------------------
    // Event handlers — generic rebuild triggers
    // -----------------------------------------------------------------------
    /// <summary>Rebuilds the command line when any plain checkbox changes.</summary>
    procedure AnyCheckBoxChange(Sender: TObject);
    /// <summary>Rebuilds the command line when any combobox selection changes.</summary>
    procedure AnyComboBoxChange(Sender: TObject);
    /// <summary>Rebuilds the command line when any spin edit value changes.</summary>
    procedure AnySpinEditChange(Sender: TObject);
    /// <summary>Rebuilds the command line when any text edit changes.</summary>
    procedure AnyEditChange(Sender: TObject);

    // -----------------------------------------------------------------------
    // Event handlers — side checklist (old GE)
    // -----------------------------------------------------------------------
    /// <summary>Recomputes the side bitmask when checklist items are toggled.</summary>
    procedure chkGOSidesGEClickCheck(Sender: TObject);

    // -----------------------------------------------------------------------
    // Event handlers — file browse buttons
    // -----------------------------------------------------------------------
    procedure btnBSFNGEClick(Sender: TObject);
    procedure btnScenFNGEClick(Sender: TObject);
    procedure btnCDBresFNGEClick(Sender: TObject);
    procedure btnCDBrsrFNGEClick(Sender: TObject);
    procedure btnSavFNGEClick(Sender: TObject);
    procedure btnLogFNGEClick(Sender: TObject);
    procedure btnIterFNGEClick(Sender: TObject);
    procedure btnLogMLFNGEClick(Sender: TObject);

  private
    /// <summary>Builder that assembles the GE command-line string from a snapshot record.</summary>
    FBuilder: IHCLCmdLineBuilder;
    /// <summary>Managed file organization interface.</summary>
    FFileOrg: IHCLFileOrganization;
    /// <summary>Bitmask for the old-GE side checklist (Blue=1, Red=2, Green=4).</summary>
    FIntGOSidesGE: Integer;
    /// <summary>Most recently assembled command-line argument string.</summary>
    FStrCmdLine: String;

    /// <summary>Assembles the command line from current control states and updates the preview.</summary>
    procedure BuildCommandLineGE;
    /// <summary>Captures all control values into a flat record for the builder.</summary>
    function SnapshotInputs: THCGECmdLineInputs;
    /// <summary>Loads persisted settings from Launcher.ini.</summary>
    procedure SettingsLoad;
    /// <summary>Saves current control states to Launcher.ini.</summary>
    procedure SettingsSave;
    /// <summary>Rebuilds FFileOrg from current basedir state and updates dialog defaults.</summary>
    procedure UpdateFileOrganization;

  public
  end;

var
  frmMain1: TfrmMain1;

implementation

{$R *.lfm}

// ---------------------------------------------------------------------------
// FormCreate
// ---------------------------------------------------------------------------

/// <summary>Formats a 4-part version string (e.g. '2026.0.0.3') as 'yyyy.###' (e.g. '2026.3').</summary>
function FormatVersionShort(const AFullVer: String): String;
var
  Parts: TStringArray;
begin
  Parts := AFullVer.Split(['.']);
  if Length(Parts) = 4 then
    Result := Parts[0] + '.' + Format('%.3d', [StrToIntDef(Parts[3], 0)])
  else
    Result := AFullVer;
end;

procedure TfrmMain1.FormCreate(Sender: TObject);
{$IFDEF MSWINDOWS}
var
  Reg: TRegistry;
{$ENDIF}
begin
  try
    Caption := Caption + ' v.' + FormatVersionShort(CICGetAppVersionStr(Application.ExeName))
      + ' on ' + {$IFDEF MSWINDOWS}'Windows'{$ELSE}'Linux'{$ENDIF};
  except
    Caption := Caption + ' on ' + {$IFDEF MSWINDOWS}'Windows'{$ELSE}'Linux'{$ENDIF};
  end;

  FBuilder := THCLCmdLineBuilder.Create as IHCLCmdLineBuilder;
  FIntGOSidesGE := 0;

  SettingsLoad;

  // Resolve HC directory: INI → registry → app dir
  if not DirectoryExists(edtHCDirGE.Text) then
  begin
{$IFDEF MSWINDOWS}
    Reg := TRegistry.Create(KEY_READ);
    try
      Reg.RootKey := HKEY_CURRENT_USER;
      if Reg.OpenKeyReadOnly('Software\Matrix Games\HCE\Options') then
      begin
        if Reg.ValueExists('BattlesetPath') then
          edtHCDirGE.Text := Reg.ReadString('BattlesetPath');
        Reg.CloseKey;
      end;
    finally
      Reg.Free;
    end;
{$ENDIF}
  end;

  if not DirectoryExists(edtHCDirGE.Text) then
    edtHCDirGE.Text := ExtractFilePath(Application.ExeName);

  // Strip trailing path delimiter
  if (edtHCDirGE.Text <> '') and
     (edtHCDirGE.Text[Length(edtHCDirGE.Text)] = PathDelim) then
    edtHCDirGE.Text := Copy(edtHCDirGE.Text, 1, Length(edtHCDirGE.Text) - 1);

  // Initialize managed file organization and set dialog defaults
  UpdateFileOrganization;

  // First-run: offer to create managed directory tree
  if Assigned(FFileOrg) and not DirectoryExists(FFileOrg.GetManagedRoot) then
  begin
    if MessageDlg('Create managed file directory at:' + LineEnding +
         FFileOrg.GetManagedRoot + '?' + LineEnding + LineEnding +
         'This organizes BattleSets, Scenarios, PlatformDBs, and other ' +
         'HC artifacts into a structured directory tree.',
         mtConfirmation, [mbYes, mbNo], 0) = mrYes then
      FFileOrg.EnsureAllCategoriesExist;
  end;

  // Trigger version detection
  edtGENameChange(nil);

  BuildCommandLineGE;
end;

// ---------------------------------------------------------------------------
// FormDestroy
// ---------------------------------------------------------------------------

procedure TfrmMain1.FormDestroy(Sender: TObject);
begin
  SettingsSave;
end;

// ---------------------------------------------------------------------------
// BuildCommandLineGE
// ---------------------------------------------------------------------------

procedure TfrmMain1.BuildCommandLineGE;
begin
  FStrCmdLine := FBuilder.BuildCommandLine(SnapshotInputs);
  edtCmdLineGE.Text := 'Command Line: "' + edtHCDirGE.Text + PathDelim
    + edtGEName.Text + '" ' + FStrCmdLine;
end;

// ---------------------------------------------------------------------------
// SnapshotInputs
// ---------------------------------------------------------------------------

function TfrmMain1.SnapshotInputs: THCGECmdLineInputs;
var
  Inputs: THCGECmdLineInputs;
  i: Integer;
  Mask: Integer;
begin
  Inputs := DefaultCmdLineInputs;

  // Log file
  Inputs.LogFN     := ckbLogFNGE.Checked;
  Inputs.LogFNPath := edtLogFNGE.Text;

  // Logging flags
  Inputs.LoggingEnabled  := ckbLOGE.Checked;
  Inputs.LogAll          := ckbLOallGE.Checked;
  Inputs.LogInitAnnex    := ckbLOinit_annexGE.Checked;
  Inputs.LogAcAI         := ckbLOac_aiGE.Checked;
  Inputs.LogSubAI        := ckbLOsub_aiGE.Checked;
  Inputs.LogEmconAI      := ckbLOemcon_aiGE.Checked;
  Inputs.LogSonar        := ckbLOsonarGE.Checked;
  Inputs.LogRefuel       := ckbLOrefuelGE.Checked;
  Inputs.LogMission      := ckbLOmissionGE.Checked;
  Inputs.LogIntercept    := ckbLOinterceptGE.Checked;
  Inputs.LogResFile      := ckbLOres_fileGE.Checked;
  Inputs.LogLoad         := ckbLOloadGE.Checked;
  Inputs.LogRadar        := ckbLOradarGE.Checked;
  Inputs.LogECM          := ckbLOecmGE.Checked;
  Inputs.LogMissile      := ckbLOmissileGE.Checked;
  Inputs.LogAreaDef      := ckbLOareadefGE.Checked;
  Inputs.LogPerf         := ckbLOperfGE.Checked;
  Inputs.LogBadDB        := ckbLObad_dbGE.Checked;
  Inputs.LogGraphic      := ckbLOgraphicGE.Checked;
  Inputs.LogUnit         := ckbLOunitGE.Checked;

  // Debug flags
  Inputs.DebugEnabled := ckbIDLGE.Checked;
  Inputs.DebugI32501  := ckbIDLI32501GE.Checked;

  // Window hide
  Inputs.WindowHideEnabled := ckbWHGE.Checked;
  Inputs.WHGroup      := ckbWHGroupGE.Checked;
  Inputs.WHUnit       := ckbWHUnitGE.Checked;
  Inputs.WHStratMap   := ckbWHStratMapGE.Checked;
  Inputs.WHMiniReport := ckbWHMiniReportGE.Checked;
  Inputs.WHMessageLog := ckbWHMessageLogGE.Checked;

  // Simple switches
  Inputs.IgnoreReg    := ckbIgnoreRegGE.Checked;
  Inputs.AutoPlay     := ckbAutoPlayGE.Checked;
  Inputs.SubAIDisable := ckbSubAIGE.Checked;
  Inputs.Eff115Old    := ckbEff115GE.Checked;

  // Save interval
  Inputs.SavIntEnabled := ckbSavIntGE.Checked;
  Inputs.SavIntText    := edtSavIntGE.Text;

  // Iterative save
  Inputs.IterSave := ckbIterSaveGE.Checked;

  // Iterative save GT
  Inputs.IterSaveGT         := ckbIterSaveGTGE.Checked;
  Inputs.IterSaveGTInterval := edtIterSaveGTIntervalGE.Value;
  Inputs.IterSaveGTSaves    := edtIterSaveGTSavesGE.Value;
  Inputs.IterSaveGTFNEnabled := ckbIterSaveGTFNGE.Checked;
  Inputs.IterSaveGTFNPath   := edtIterFNGE.Text;

  // Message log
  Inputs.LogMLFNEnabled := ckbLogMLFNGE.Checked;
  Inputs.LogMLFNPath    := edtLogMLFNGE.Text;

  // Battleset
  Inputs.BSByNum    := ckbBSNumGE.Checked;
  Inputs.BSNumIndex := cbxBSNumGE.ItemIndex;
  Inputs.BSByFile   := ckbBSFNGE.Checked;
  Inputs.BSFilePath := edtBSFNGE.Text;

  // Scenario
  Inputs.ScenByNum    := ckbScenNumGE.Checked;
  Inputs.ScenNumIndex := cbxScenNumGE.ItemIndex;
  Inputs.ScenByFile   := ckbScenFNGE.Checked;
  Inputs.ScenFilePath := edtScenFNGE.Text;

  // Save file
  Inputs.SavFNEnabled := ckbSavFNGE.Checked;
  Inputs.SavFNPath    := edtSavFNGE.Text;

  // CDB databases
  Inputs.CDBresFNEnabled := ckbCDBresFNGE.Checked;
  Inputs.CDBresFNPath    := edtCDBresFNGE.Text;
  Inputs.CDBrsrFNEnabled := ckbCDBrsrFNGE.Checked;
  Inputs.CDBrsrFNPath    := edtCDBrsrFNGE.Text;

  // Game options
  Inputs.GameOptsEnabled := ckbGOGE.Checked;
  if not chkGOSidesGE.Visible then
  begin
    // New GE: single-side combobox
    Inputs.GOUseSideChecklist := False;
    Inputs.GOSideIndex        := cbxGOSideGE.ItemIndex;
  end
  else
  begin
    // Old GE: bitmask from checklist
    Inputs.GOUseSideChecklist := True;
    Mask := 0;
    for i := 0 to chkGOSidesGE.Items.Count - 1 do
      if chkGOSidesGE.Checked[i] then
        Mask := Mask or (1 shl i);
    FIntGOSidesGE := Mask;
    Inputs.GOSidesBitmask := FIntGOSidesGE;
  end;
  Inputs.GONukeRelIndex  := cbxGONukeRelGE.ItemIndex;
  Inputs.GOSnorkSubIndex := cbxGOSnorkSubGE.ItemIndex;
  Inputs.GORealWeathIndex:= cbxGORealWeathGE.ItemIndex;
  Inputs.GOMaintFailIndex:= cbxGOMaintFailGE.ItemIndex;
  Inputs.GOFullOrdIndex  := cbxGOFullOrdGE.ItemIndex;
  Inputs.GOFormPatIndex  := cbxGOFormPatGE.ItemIndex;
  Inputs.GOExitIndex     := cbxGOExitGE.ItemIndex;
  Inputs.GOTimeCompIndex := cbxGOTimeCompGE.ItemIndex;

  // CPU affinity
  Inputs.AffinityEnabled := ckbAffinityGE.Checked;
  Inputs.AffinityIndex   := cbxAffinityGE.ItemIndex;

  // Process priority
  Inputs.PriorityEnabled := ckbPriorityGE.Checked;
  Inputs.PriorityIndex   := cbxPriorityGE.ItemIndex;

  Result := Inputs;
end;

// ---------------------------------------------------------------------------
// SettingsLoad
// ---------------------------------------------------------------------------

procedure TfrmMain1.SettingsLoad;
var
  INI: TIniFile;
  IniPath: String;
begin
  IniPath := ExtractFilePath(Application.ExeName) + 'Launcher.ini';
  INI := TIniFile.Create(IniPath);
  try
    // [Main]
    edtHCDirGE.Text := INI.ReadString('Main', 'DirHC', '');
    edtGEName.Text  := INI.ReadString('Main', 'FileGE', '');
    ckbBaseDirOverride.Checked := INI.ReadBool('Main', 'BaseDirOverride', False);
    edtBaseDir.Text := INI.ReadString('Main', 'BaseDir', '');

    // [GECLO] — checkboxes
    ckbAutoPlayGE.Checked    := INI.ReadBool('GECLO', 'blnAutoPlay',  False);
    ckbIgnoreRegGE.Checked   := INI.ReadBool('GECLO', 'blnRegIgnore', False);
    ckbSubAIGE.Checked       := INI.ReadBool('GECLO', 'blnSubAIDis',  False);
    ckbEff115GE.Checked      := INI.ReadBool('GECLO', 'blnEff115Old', False);
    ckbBSFNGE.Checked        := INI.ReadBool('GECLO', 'blnBSFN',      False);
    ckbScenFNGE.Checked      := INI.ReadBool('GECLO', 'blnScenFN',    False);
    ckbCDBresFNGE.Checked    := INI.ReadBool('GECLO', 'blnCDBresFN',  False);
    ckbSavFNGE.Checked       := INI.ReadBool('GECLO', 'blnSaveFN',    False);
    ckbIterSaveGE.Checked    := INI.ReadBool('GECLO', 'blnSaveIter',  False);
    ckbSavIntGE.Checked      := INI.ReadBool('GECLO', 'blnSaveAuto',  False);
    ckbBSNumGE.Checked       := INI.ReadBool('GECLO', 'blnBSNum',     False);
    ckbScenNumGE.Checked     := INI.ReadBool('GECLO', 'blnScenNum',   False);
    ckbCDBrsrFNGE.Checked    := INI.ReadBool('GECLO', 'blnCDBrsrFN',  False);
    ckbLogFNGE.Checked       := INI.ReadBool('GECLO', 'blnLogFN',     False);
    ckbAffinityGE.Checked    := INI.ReadBool('GECLO', 'blnCPUAff',    False);
    ckbPriorityGE.Checked    := INI.ReadBool('GECLO', 'blnProcPrio',  False);
    ckbIterSaveGTGE.Checked  := INI.ReadBool('GECLO', 'IterSaveGT',   False);
    ckbLogMLFNGE.Checked     := INI.ReadBool('GECLO', 'ckbLogMLFNGE', False);
    ckbIterSaveGTFNGE.Checked:= INI.ReadBool('GECLO', 'ckbIterSaveGTFNGE', False);

    // [GECLO] — text edits
    edtBSFNGE.Text    := INI.ReadString('GECLO', 'edtBSFN',    '');
    edtScenFNGE.Text  := INI.ReadString('GECLO', 'edtScenFN',  '');
    edtCDBresFNGE.Text:= INI.ReadString('GECLO', 'edtCDBresFN','');
    edtCDBrsrFNGE.Text:= INI.ReadString('GECLO', 'edtCDBrsrFN','');
    edtSavFNGE.Text   := INI.ReadString('GECLO', 'edtSavFN',   '');
    edtSavIntGE.Text  := INI.ReadString('GECLO', 'edtSavAuto', '');
    edtLogFNGE.Text   := INI.ReadString('GECLO', 'edtLogFN',   '');
    edtLogMLFNGE.Text := INI.ReadString('GECLO', 'edtLogMLFNGE','');
    edtIterFNGE.Text  := INI.ReadString('GECLO', 'edtIterFNGE', '');

    // [GECLO] — comboboxes
    cbxBSNumGE.ItemIndex     := INI.ReadInteger('GECLO', 'cbxBSNum',     0);
    cbxScenNumGE.ItemIndex   := INI.ReadInteger('GECLO', 'cbxScenNum',   0);
    cbxAffinityGE.ItemIndex  := INI.ReadInteger('GECLO', 'cbxCPUAff',    0);
    cbxPriorityGE.ItemIndex  := INI.ReadInteger('GECLO', 'cbxProcPrio',  2);

    // [GECLO] — spin edits
    edtIterSaveGTIntervalGE.Value := INI.ReadInteger('GECLO', 'IterSaveGTInterval', 0);
    edtIterSaveGTSavesGE.Value    := INI.ReadInteger('GECLO', 'IterSaveGTSaves',    0);

    // [GECLOGO]
    ckbGOGE.Checked           := INI.ReadBool('GECLOGO', 'blnGO', False);
    FIntGOSidesGE             := INI.ReadInteger('GECLOGO', 'intGOSidesGE', 0);
    cbxGOSideGE.ItemIndex     := INI.ReadInteger('GECLOGO', 'cbxGOSide',      0);
    cbxGONukeRelGE.ItemIndex  := INI.ReadInteger('GECLOGO', 'cbxGONukeRel',   0);
    cbxGOSnorkSubGE.ItemIndex := INI.ReadInteger('GECLOGO', 'cbxGOSnorkSub',  0);
    cbxGORealWeathGE.ItemIndex:= INI.ReadInteger('GECLOGO', 'cbxGORealWeath', 0);
    cbxGOMaintFailGE.ItemIndex:= INI.ReadInteger('GECLOGO', 'cbxGOMaintFail', 0);
    cbxGOFullOrdGE.ItemIndex  := INI.ReadInteger('GECLOGO', 'cbxGOFullOrd',   0);
    cbxGOFormPatGE.ItemIndex  := INI.ReadInteger('GECLOGO', 'cbxGOFormPat',   0);
    cbxGOExitGE.ItemIndex     := INI.ReadInteger('GECLOGO', 'cbxGOExit',      0);
    cbxGOTimeCompGE.ItemIndex := INI.ReadInteger('GECLOGO', 'cbxGOTimeComp',  0);

    // Restore side checklist from bitmask
    if chkGOSidesGE.Items.Count >= 3 then
    begin
      chkGOSidesGE.Checked[0] := (FIntGOSidesGE and 1) <> 0;
      chkGOSidesGE.Checked[1] := (FIntGOSidesGE and 2) <> 0;
      chkGOSidesGE.Checked[2] := (FIntGOSidesGE and 4) <> 0;
    end;

    // [GECLOLO]
    ckbLOGE.Checked          := INI.ReadBool('GECLOLO', 'blnLO',             False);
    ckbLOinit_annexGE.Checked:= INI.ReadBool('GECLOLO', 'blnLOinit_annex',   False);
    ckbLOac_aiGE.Checked     := INI.ReadBool('GECLOLO', 'ckbLOac_ai',        False);
    ckbLOsub_aiGE.Checked    := INI.ReadBool('GECLOLO', 'ckbLOsub_ai',       False);
    ckbLOemcon_aiGE.Checked  := INI.ReadBool('GECLOLO', 'ckbLOemcon_ai',     False);
    ckbLOsonarGE.Checked     := INI.ReadBool('GECLOLO', 'ckbLOsonar',        False);
    ckbLOrefuelGE.Checked    := INI.ReadBool('GECLOLO', 'ckbLOrefuel',       False);
    ckbLOmissionGE.Checked   := INI.ReadBool('GECLOLO', 'ckbLOmission',      False);
    ckbLOinterceptGE.Checked := INI.ReadBool('GECLOLO', 'ckbLOintercept',    False);
    ckbLOres_fileGE.Checked  := INI.ReadBool('GECLOLO', 'ckbLOres_file',     False);
    ckbLOloadGE.Checked      := INI.ReadBool('GECLOLO', 'ckbLOload',         False);
    ckbLOradarGE.Checked     := INI.ReadBool('GECLOLO', 'ckbLOradar',        False);
    ckbLOecmGE.Checked       := INI.ReadBool('GECLOLO', 'ckbLOecm',          False);
    ckbLOmissileGE.Checked   := INI.ReadBool('GECLOLO', 'ckbLOmissile',      False);
    ckbLOareadefGE.Checked   := INI.ReadBool('GECLOLO', 'ckbLOareadef',      False);
    ckbLOperfGE.Checked      := INI.ReadBool('GECLOLO', 'ckbLOperf',         False);
    ckbLObad_dbGE.Checked    := INI.ReadBool('GECLOLO', 'ckbLObad_db',       False);
    ckbLOgraphicGE.Checked   := INI.ReadBool('GECLOLO', 'ckbLOgraphic',      False);
    ckbLOunitGE.Checked      := INI.ReadBool('GECLOLO', 'ckbLOunit',         False);
    ckbLOallGE.Checked       := INI.ReadBool('GECLOLO', 'ckbLOall',          False);
    ckbIDLGE.Checked         := INI.ReadBool('GECLOLO', 'ckbIDLGE',          False);
    ckbIDLI32501GE.Checked   := INI.ReadBool('GECLOLO', 'ckbIDLI32501GE',    False);
    ckbWHGE.Checked          := INI.ReadBool('GECLOLO', 'ckbWHGE',           False);
    ckbWHGroupGE.Checked     := INI.ReadBool('GECLOLO', 'ckbWHGroupGE',      False);
    ckbWHUnitGE.Checked      := INI.ReadBool('GECLOLO', 'ckbWHUnitGE',       False);
    ckbWHStratMapGE.Checked  := INI.ReadBool('GECLOLO', 'ckbWHStratMapGE',   False);
    ckbWHMiniReportGE.Checked:= INI.ReadBool('GECLOLO', 'ckbWHMiniReportGE', False);
    ckbWHMessageLogGE.Checked:= INI.ReadBool('GECLOLO', 'ckbWHMessageLogGE', False);
  finally
    INI.Free;
  end;
end;

// ---------------------------------------------------------------------------
// SettingsSave
// ---------------------------------------------------------------------------

procedure TfrmMain1.SettingsSave;
var
  INI: TIniFile;
  IniPath: String;
begin
  IniPath := ExtractFilePath(Application.ExeName) + 'Launcher.ini';
  INI := TIniFile.Create(IniPath);
  try
    // [Main]
    INI.WriteString('Main', 'DirHC',  edtHCDirGE.Text);
    INI.WriteString('Main', 'FileGE', edtGEName.Text);
    INI.WriteBool('Main', 'BaseDirOverride', ckbBaseDirOverride.Checked);
    INI.WriteString('Main', 'BaseDir', edtBaseDir.Text);

    // [GECLO] — checkboxes
    INI.WriteBool('GECLO', 'blnAutoPlay',  ckbAutoPlayGE.Checked);
    INI.WriteBool('GECLO', 'blnRegIgnore', ckbIgnoreRegGE.Checked);
    INI.WriteBool('GECLO', 'blnSubAIDis',  ckbSubAIGE.Checked);
    INI.WriteBool('GECLO', 'blnEff115Old', ckbEff115GE.Checked);
    INI.WriteBool('GECLO', 'blnBSFN',      ckbBSFNGE.Checked);
    INI.WriteBool('GECLO', 'blnScenFN',    ckbScenFNGE.Checked);
    INI.WriteBool('GECLO', 'blnCDBresFN',  ckbCDBresFNGE.Checked);
    INI.WriteBool('GECLO', 'blnSaveFN',    ckbSavFNGE.Checked);
    INI.WriteBool('GECLO', 'blnSaveIter',  ckbIterSaveGE.Checked);
    INI.WriteBool('GECLO', 'blnSaveAuto',  ckbSavIntGE.Checked);
    INI.WriteBool('GECLO', 'blnBSNum',     ckbBSNumGE.Checked);
    INI.WriteBool('GECLO', 'blnScenNum',   ckbScenNumGE.Checked);
    INI.WriteBool('GECLO', 'blnCDBrsrFN',  ckbCDBrsrFNGE.Checked);
    INI.WriteBool('GECLO', 'blnLogFN',     ckbLogFNGE.Checked);
    INI.WriteBool('GECLO', 'blnCPUAff',    ckbAffinityGE.Checked);
    INI.WriteBool('GECLO', 'blnProcPrio',  ckbPriorityGE.Checked);
    INI.WriteBool('GECLO', 'IterSaveGT',   ckbIterSaveGTGE.Checked);
    INI.WriteBool('GECLO', 'ckbLogMLFNGE', ckbLogMLFNGE.Checked);
    INI.WriteBool('GECLO', 'ckbIterSaveGTFNGE', ckbIterSaveGTFNGE.Checked);

    // [GECLO] — text edits
    INI.WriteString('GECLO', 'edtBSFN',     edtBSFNGE.Text);
    INI.WriteString('GECLO', 'edtScenFN',   edtScenFNGE.Text);
    INI.WriteString('GECLO', 'edtCDBresFN', edtCDBresFNGE.Text);
    INI.WriteString('GECLO', 'edtCDBrsrFN', edtCDBrsrFNGE.Text);
    INI.WriteString('GECLO', 'edtSavFN',    edtSavFNGE.Text);
    INI.WriteString('GECLO', 'edtSavAuto',  edtSavIntGE.Text);
    INI.WriteString('GECLO', 'edtLogFN',    edtLogFNGE.Text);
    INI.WriteString('GECLO', 'edtLogMLFNGE', edtLogMLFNGE.Text);
    INI.WriteString('GECLO', 'edtIterFNGE', edtIterFNGE.Text);

    // [GECLO] — comboboxes
    INI.WriteInteger('GECLO', 'cbxBSNum',    cbxBSNumGE.ItemIndex);
    INI.WriteInteger('GECLO', 'cbxScenNum',  cbxScenNumGE.ItemIndex);
    INI.WriteInteger('GECLO', 'cbxCPUAff',   cbxAffinityGE.ItemIndex);
    INI.WriteInteger('GECLO', 'cbxProcPrio', cbxPriorityGE.ItemIndex);

    // [GECLO] — spin edits
    INI.WriteInteger('GECLO', 'IterSaveGTInterval', edtIterSaveGTIntervalGE.Value);
    INI.WriteInteger('GECLO', 'IterSaveGTSaves',    edtIterSaveGTSavesGE.Value);

    // [GECLOGO]
    INI.WriteBool('GECLOGO', 'blnGO',           ckbGOGE.Checked);
    INI.WriteInteger('GECLOGO', 'intGOSidesGE', FIntGOSidesGE);
    INI.WriteInteger('GECLOGO', 'cbxGOSide',      cbxGOSideGE.ItemIndex);
    INI.WriteInteger('GECLOGO', 'cbxGONukeRel',   cbxGONukeRelGE.ItemIndex);
    INI.WriteInteger('GECLOGO', 'cbxGOSnorkSub',  cbxGOSnorkSubGE.ItemIndex);
    INI.WriteInteger('GECLOGO', 'cbxGORealWeath', cbxGORealWeathGE.ItemIndex);
    INI.WriteInteger('GECLOGO', 'cbxGOMaintFail', cbxGOMaintFailGE.ItemIndex);
    INI.WriteInteger('GECLOGO', 'cbxGOFullOrd',   cbxGOFullOrdGE.ItemIndex);
    INI.WriteInteger('GECLOGO', 'cbxGOFormPat',   cbxGOFormPatGE.ItemIndex);
    INI.WriteInteger('GECLOGO', 'cbxGOExit',      cbxGOExitGE.ItemIndex);
    INI.WriteInteger('GECLOGO', 'cbxGOTimeComp',  cbxGOTimeCompGE.ItemIndex);

    // [GECLOLO]
    INI.WriteBool('GECLOLO', 'blnLO',             ckbLOGE.Checked);
    INI.WriteBool('GECLOLO', 'blnLOinit_annex',   ckbLOinit_annexGE.Checked);
    INI.WriteBool('GECLOLO', 'ckbLOac_ai',        ckbLOac_aiGE.Checked);
    INI.WriteBool('GECLOLO', 'ckbLOsub_ai',       ckbLOsub_aiGE.Checked);
    INI.WriteBool('GECLOLO', 'ckbLOemcon_ai',     ckbLOemcon_aiGE.Checked);
    INI.WriteBool('GECLOLO', 'ckbLOsonar',        ckbLOsonarGE.Checked);
    INI.WriteBool('GECLOLO', 'ckbLOrefuel',       ckbLOrefuelGE.Checked);
    INI.WriteBool('GECLOLO', 'ckbLOmission',      ckbLOmissionGE.Checked);
    INI.WriteBool('GECLOLO', 'ckbLOintercept',    ckbLOinterceptGE.Checked);
    INI.WriteBool('GECLOLO', 'ckbLOres_file',     ckbLOres_fileGE.Checked);
    INI.WriteBool('GECLOLO', 'ckbLOload',         ckbLOloadGE.Checked);
    INI.WriteBool('GECLOLO', 'ckbLOradar',        ckbLOradarGE.Checked);
    INI.WriteBool('GECLOLO', 'ckbLOecm',          ckbLOecmGE.Checked);
    INI.WriteBool('GECLOLO', 'ckbLOmissile',      ckbLOmissileGE.Checked);
    INI.WriteBool('GECLOLO', 'ckbLOareadef',      ckbLOareadefGE.Checked);
    INI.WriteBool('GECLOLO', 'ckbLOperf',         ckbLOperfGE.Checked);
    INI.WriteBool('GECLOLO', 'ckbLObad_db',       ckbLObad_dbGE.Checked);
    INI.WriteBool('GECLOLO', 'ckbLOgraphic',      ckbLOgraphicGE.Checked);
    INI.WriteBool('GECLOLO', 'ckbLOunit',         ckbLOunitGE.Checked);
    INI.WriteBool('GECLOLO', 'ckbLOall',          ckbLOallGE.Checked);
    INI.WriteBool('GECLOLO', 'ckbIDLGE',          ckbIDLGE.Checked);
    INI.WriteBool('GECLOLO', 'ckbIDLI32501GE',    ckbIDLI32501GE.Checked);
    INI.WriteBool('GECLOLO', 'ckbWHGE',           ckbWHGE.Checked);
    INI.WriteBool('GECLOLO', 'ckbWHGroupGE',      ckbWHGroupGE.Checked);
    INI.WriteBool('GECLOLO', 'ckbWHUnitGE',       ckbWHUnitGE.Checked);
    INI.WriteBool('GECLOLO', 'ckbWHStratMapGE',   ckbWHStratMapGE.Checked);
    INI.WriteBool('GECLOLO', 'ckbWHMiniReportGE', ckbWHMiniReportGE.Checked);
    INI.WriteBool('GECLOLO', 'ckbWHMessageLogGE', ckbWHMessageLogGE.Checked);
  finally
    INI.Free;
  end;
end;

// ---------------------------------------------------------------------------
// edtGENameChange — version detection and panel visibility
// ---------------------------------------------------------------------------

procedure TfrmMain1.edtGENameChange(Sender: TObject);
var
  FullPath, VerStr: String;
begin
  FullPath := edtHCDirGE.Text + PathDelim + edtGEName.Text;
  if not FileExists(FullPath) then
  begin
    BuildCommandLineGE;
    Exit;
  end;

  VerStr := CICGetAppVersionStr(FullPath);

  // Side selector: checklist for 2011.002 < ver < 2014.001, else combobox
  if (VerStr > '2011.002') and (VerStr < '2014.001') then
  begin
    chkGOSidesGE.Visible := True;
    cbxGOSideGE.Visible  := False;
  end
  else
  begin
    chkGOSidesGE.Visible := False;
    cbxGOSideGE.Visible  := True;
  end;

  // Debug panel: visible for >= 2022.0.1.5
  grpDebugOptsGE.Visible := (VerStr >= '2022.0.1.5');

  // Message log + iter save filename: visible for >= 2022.0.2.5
  ckbLogMLFNGE.Visible      := (VerStr >= '2022.0.2.5');
  edtLogMLFNGE.Visible      := (VerStr >= '2022.0.2.5');
  btnLogMLFNGE.Visible      := (VerStr >= '2022.0.2.5');
  ckbIterSaveGTFNGE.Visible := (VerStr >= '2022.0.2.5');
  edtIterFNGE.Visible       := (VerStr >= '2022.0.2.5');
  btnIterFNGE.Visible       := (VerStr >= '2022.0.2.5');

  BuildCommandLineGE;
end;

// ---------------------------------------------------------------------------
// btnLaunchGEClick
// ---------------------------------------------------------------------------

procedure TfrmMain1.btnLaunchGEClick(Sender: TObject);
var
  FullPath: String;
begin
  FullPath := edtHCDirGE.Text + PathDelim + edtGEName.Text;
  if FileExists(FullPath) then
    ExecuteFile('"' + FullPath + '"', FStrCmdLine, edtHCDirGE.Text, 1)
  else
    MessageDlg('Listed GE file does not exist, please enter a valid exe',
      mtInformation, [mbOK], 0);
end;

// ---------------------------------------------------------------------------
// btnHCDirGEClick
// ---------------------------------------------------------------------------

procedure TfrmMain1.btnHCDirGEClick(Sender: TObject);
var
  Dir: String;
begin
  Dir := edtHCDirGE.Text;
  if SelectDirectory('Select HC Directory', '', Dir) then
    edtHCDirGE.Text := Dir;
end;

// ---------------------------------------------------------------------------
// edtHCDirGEChange
// ---------------------------------------------------------------------------

procedure TfrmMain1.edtHCDirGEChange(Sender: TObject);
begin
  if not ckbBaseDirOverride.Checked then
    UpdateFileOrganization;
  BuildCommandLineGE;
end;

// ---------------------------------------------------------------------------
// Mutual-exclusion checkbox handlers
// ---------------------------------------------------------------------------

procedure TfrmMain1.ckbBSNumGEChange(Sender: TObject);
begin
  if ckbBSNumGE.Checked then
    ckbBSFNGE.Checked := False;
  BuildCommandLineGE;
end;

procedure TfrmMain1.ckbBSFNGEChange(Sender: TObject);
begin
  if ckbBSFNGE.Checked then
    ckbBSNumGE.Checked := False;
  BuildCommandLineGE;
end;

procedure TfrmMain1.ckbScenNumGEChange(Sender: TObject);
begin
  if ckbScenNumGE.Checked then
    ckbScenFNGE.Checked := False;
  BuildCommandLineGE;
end;

procedure TfrmMain1.ckbScenFNGEChange(Sender: TObject);
begin
  if ckbScenFNGE.Checked then
    ckbScenNumGE.Checked := False;
  BuildCommandLineGE;
end;

procedure TfrmMain1.ckbLOallGEChange(Sender: TObject);
begin
  if ckbLOallGE.Checked then
  begin
    ckbLOinit_annexGE.Checked := False;
    ckbLOac_aiGE.Checked      := False;
    ckbLOsub_aiGE.Checked     := False;
    ckbLOemcon_aiGE.Checked   := False;
    ckbLOsonarGE.Checked      := False;
    ckbLOrefuelGE.Checked     := False;
    ckbLOmissionGE.Checked    := False;
    ckbLOinterceptGE.Checked  := False;
    ckbLOres_fileGE.Checked   := False;
    ckbLOloadGE.Checked       := False;
    ckbLOradarGE.Checked      := False;
    ckbLOecmGE.Checked        := False;
    ckbLOmissileGE.Checked    := False;
    ckbLOareadefGE.Checked    := False;
    ckbLOperfGE.Checked       := False;
    ckbLObad_dbGE.Checked     := False;
    ckbLOgraphicGE.Checked    := False;
    ckbLOunitGE.Checked       := False;
  end;
  BuildCommandLineGE;
end;

procedure TfrmMain1.ckbLOIndividualChange(Sender: TObject);
begin
  if (Sender is TCheckBox) and TCheckBox(Sender).Checked then
    ckbLOallGE.Checked := False;
  BuildCommandLineGE;
end;

procedure TfrmMain1.ckbIDLI32501GEChange(Sender: TObject);
begin
  ckbIDLGE.Checked := ckbIDLI32501GE.Checked;
  BuildCommandLineGE;
end;

procedure TfrmMain1.ckbWHSubChange(Sender: TObject);
begin
  if (Sender is TCheckBox) and TCheckBox(Sender).Checked then
    ckbWHGE.Checked := True;
  BuildCommandLineGE;
end;

// ---------------------------------------------------------------------------
// Generic rebuild triggers
// ---------------------------------------------------------------------------

procedure TfrmMain1.AnyCheckBoxChange(Sender: TObject);
begin
  BuildCommandLineGE;
end;

procedure TfrmMain1.AnyComboBoxChange(Sender: TObject);
begin
  BuildCommandLineGE;
end;

procedure TfrmMain1.AnySpinEditChange(Sender: TObject);
begin
  BuildCommandLineGE;
end;

procedure TfrmMain1.AnyEditChange(Sender: TObject);
begin
  BuildCommandLineGE;
end;

// ---------------------------------------------------------------------------
// chkGOSidesGEClickCheck — side checklist bitmask
// ---------------------------------------------------------------------------

procedure TfrmMain1.chkGOSidesGEClickCheck(Sender: TObject);
var
  i: Integer;
  Mask: Integer;
begin
  Mask := 0;
  for i := 0 to chkGOSidesGE.Items.Count - 1 do
    if chkGOSidesGE.Checked[i] then
      Mask := Mask or (1 shl i);
  FIntGOSidesGE := Mask;
  BuildCommandLineGE;
end;

// ---------------------------------------------------------------------------
// File browse button handlers
// ---------------------------------------------------------------------------

procedure TfrmMain1.btnBSFNGEClick(Sender: TObject);
begin
  if dlgBSFNGE.Execute then
    edtBSFNGE.Text := dlgBSFNGE.FileName;
end;

procedure TfrmMain1.btnScenFNGEClick(Sender: TObject);
begin
  if dlgScenFNGE.Execute then
    edtScenFNGE.Text := dlgScenFNGE.FileName;
end;

procedure TfrmMain1.btnCDBresFNGEClick(Sender: TObject);
begin
  if dlgCDBresFNGE.Execute then
    edtCDBresFNGE.Text := dlgCDBresFNGE.FileName;
end;

procedure TfrmMain1.btnCDBrsrFNGEClick(Sender: TObject);
begin
  if dlgCDBrsrFNGE.Execute then
    edtCDBrsrFNGE.Text := dlgCDBrsrFNGE.FileName;
end;

procedure TfrmMain1.btnSavFNGEClick(Sender: TObject);
begin
  if dlgSavFNGE.Execute then
    edtSavFNGE.Text := dlgSavFNGE.FileName;
end;

procedure TfrmMain1.btnLogFNGEClick(Sender: TObject);
begin
  if dlgLogFNGE.Execute then
    edtLogFNGE.Text := dlgLogFNGE.FileName;
end;

procedure TfrmMain1.btnIterFNGEClick(Sender: TObject);
begin
  if dlgIterFNGE.Execute then
    edtIterFNGE.Text := dlgIterFNGE.FileName;
end;

procedure TfrmMain1.btnLogMLFNGEClick(Sender: TObject);
begin
  if dlgLogMLFNGE.Execute then
    edtLogMLFNGE.Text := dlgLogMLFNGE.FileName;
end;

// ---------------------------------------------------------------------------
// Managed files base directory
// ---------------------------------------------------------------------------

procedure TfrmMain1.btnBaseDirClick(Sender: TObject);
var
  Dir: String;
begin
  Dir := edtBaseDir.Text;
  if SelectDirectory('Select Managed Files Base Directory', '', Dir) then
  begin
    edtBaseDir.Text := Dir;
    UpdateFileOrganization;
  end;
end;

procedure TfrmMain1.ckbBaseDirOverrideChange(Sender: TObject);
begin
  btnBaseDir.Enabled   := ckbBaseDirOverride.Checked;
  edtBaseDir.ReadOnly  := not ckbBaseDirOverride.Checked;
  UpdateFileOrganization;
end;

// ---------------------------------------------------------------------------
// UpdateFileOrganization
// ---------------------------------------------------------------------------

procedure TfrmMain1.UpdateFileOrganization;
var
  BaseDir: String;

  function ResolveInitialDir(const AManagedPath, AFallback: String): String;
  begin
    if DirectoryExists(AManagedPath) then
      Result := AManagedPath
    else
      Result := AFallback;
  end;

begin
  // Resolve basedir: custom override or HC directory
  if ckbBaseDirOverride.Checked and (edtBaseDir.Text <> '') then
    BaseDir := edtBaseDir.Text
  else
  begin
    BaseDir := edtHCDirGE.Text;
    if not ckbBaseDirOverride.Checked then
      edtBaseDir.Text := BaseDir;
  end;

  FFileOrg := CreateFileOrganization(BaseDir);

  // Update file dialog initial directories to use managed paths
  dlgBSFNGE.InitialDir     := ResolveInitialDir(FFileOrg.GetCategoryPath(hclBattleSets), edtHCDirGE.Text);
  dlgScenFNGE.InitialDir   := ResolveInitialDir(FFileOrg.GetCategoryPath(hclBattleSets), edtHCDirGE.Text);
  dlgCDBresFNGE.InitialDir := ResolveInitialDir(FFileOrg.GetCategoryPath(hclPlatformDBs), edtHCDirGE.Text);
  dlgCDBrsrFNGE.InitialDir := ResolveInitialDir(FFileOrg.GetCategoryPath(hclPlatformDBs), edtHCDirGE.Text);
  dlgSavFNGE.InitialDir    := ResolveInitialDir(FFileOrg.GetCategoryPath(hclBattleSets), edtHCDirGE.Text);
  dlgLogFNGE.InitialDir    := ResolveInitialDir(FFileOrg.GetCategoryPath(hclLogs), edtHCDirGE.Text);
  dlgIterFNGE.InitialDir   := ResolveInitialDir(FFileOrg.GetCategoryPath(hclBattleSets), edtHCDirGE.Text);
  dlgLogMLFNGE.InitialDir  := ResolveInitialDir(FFileOrg.GetCategoryPath(hclLogs), edtHCDirGE.Text);
end;

end.
