unit uHCLCmdLineBuilder;

{$mode objfpc}{$H+}

/// <summary>
/// uHCLCmdLineBuilder — pure command-line builder for the HC Game Engine (GE).
///
/// Assembles GE command-line flags from a flat record of typed inputs with no
/// dependency on any UI framework or LCL. Suitable for unit testing and for use
/// by any host (GUI or headless).
///
/// The flag ordering, validation rules, and ID mappings faithfully replicate the
/// original <c>BuildCommandLineGE</c> procedure from uMain1.pas.
/// </summary>
///
/// <remarks>
/// Zero LCL/UI dependency — only <c>SysUtils</c> is required.
/// </remarks>

interface

uses
  SysUtils;

// ---------------------------------------------------------------------------
// Constants
// ---------------------------------------------------------------------------

const
  /// <summary>
  /// Maps BSNumIndex (0-based combobox index) to the GE battle-set ID passed
  /// on the command line. Note the gap: index 4 maps to ID 6 (no ID 5).
  /// </summary>
  BS_INDEX_TO_ID: array[0..31] of Integer = (
     1,  2,  3,  4,  6,  7,  8,  9,
    10, 11, 12, 13, 14, 15, 16, 17,
    18, 19, 20, 21, 22, 23, 24, 25,
    26, 27, 28, 29, 30, 31, 32, 35
  );

  /// <summary>
  /// Maps AffinityIndex (0-based) to the CPU affinity bitmask passed as -p.
  /// </summary>
  AFFINITY_MASK: array[0..3] of Integer = (1, 2, 4, 8);

// ---------------------------------------------------------------------------
// THCGECmdLineInputs — flat record capturing all GE command-line inputs
// ---------------------------------------------------------------------------

type
  /// <summary>
  /// Flat record capturing every value that <c>BuildCommandLine</c> needs to
  /// assemble the GE command-line string.  Mirrors the UI-control states read
  /// by the original <c>BuildCommandLineGE</c> procedure, but without any
  /// dependency on UI types.
  ///
  /// All boolean fields default to False, string fields to '', and index fields
  /// to -1 (meaning "not selected") or 0 where 0 is a valid default selection.
  /// </summary>
  THCGECmdLineInputs = record

    // -----------------------------------------------------------------------
    // Log file  (-f)
    // -----------------------------------------------------------------------
    /// <summary>Emit -f flag when True and directory of LogFNPath exists.</summary>
    LogFN: Boolean;
    /// <summary>Full path to the log file supplied to -f.</summary>
    LogFNPath: String;

    // -----------------------------------------------------------------------
    // Logging flags  (-l)
    // -----------------------------------------------------------------------
    /// <summary>Master switch: any -l flags are suppressed when False.</summary>
    LoggingEnabled: Boolean;
    /// <summary>When True (and LoggingEnabled), emit -l all and skip individuals.</summary>
    LogAll: Boolean;
    // Individual -l sub-flags (only evaluated when LoggingEnabled=True, LogAll=False)
    LogInitAnnex: Boolean;
    LogAcAI:      Boolean;
    LogSubAI:     Boolean;
    LogEmconAI:   Boolean;
    LogSonar:     Boolean;
    LogRefuel:    Boolean;
    LogMission:   Boolean;
    LogIntercept: Boolean;
    LogResFile:   Boolean;
    LogLoad:      Boolean;
    LogRadar:     Boolean;
    LogECM:       Boolean;
    LogMissile:   Boolean;
    LogAreaDef:   Boolean;
    LogPerf:      Boolean;
    LogBadDB:     Boolean;
    LogGraphic:   Boolean;
    LogUnit:      Boolean;

    // -----------------------------------------------------------------------
    // Debug  (-I)
    // -----------------------------------------------------------------------
    /// <summary>Master switch for debug flags.</summary>
    DebugEnabled:   Boolean;
    /// <summary>Emit -I I32501 when True (and DebugEnabled=True).</summary>
    DebugI32501:    Boolean;

    // -----------------------------------------------------------------------
    // Window hide  (-h)
    // -----------------------------------------------------------------------
    /// <summary>Master switch for window-hide flags.</summary>
    WindowHideEnabled: Boolean;
    WHGroup:      Boolean;
    WHUnit:       Boolean;
    WHStratMap:   Boolean;
    WHMiniReport: Boolean;
    WHMessageLog: Boolean;

    // -----------------------------------------------------------------------
    // Simple switches
    // -----------------------------------------------------------------------
    /// <summary>Emit -i (ignore registry).</summary>
    IgnoreReg:    Boolean;
    /// <summary>Emit -a (auto-play).</summary>
    AutoPlay:     Boolean;
    /// <summary>Emit -x (disable substitute AI).</summary>
    SubAIDisable: Boolean;
    /// <summary>Emit -e (use 1.15 effect values).</summary>
    Eff115Old:    Boolean;

    // -----------------------------------------------------------------------
    // Save interval  (-t)
    // -----------------------------------------------------------------------
    /// <summary>Master switch for the save-interval flag.</summary>
    SavIntEnabled: Boolean;
    /// <summary>
    /// Numeric text for the save interval.  Must parse as a float using
    /// the invariant locale (decimal separator = '.') to avoid locale bugs.
    /// </summary>
    SavIntText: String;

    // -----------------------------------------------------------------------
    // Iterative save  (-s)
    // -----------------------------------------------------------------------
    /// <summary>Emit -s (iterative save).</summary>
    IterSave: Boolean;

    // -----------------------------------------------------------------------
    // Iterative save GT  (-S, -k)
    // -----------------------------------------------------------------------
    /// <summary>Master switch: emit -S when True.</summary>
    IterSaveGT:          Boolean;
    /// <summary>Save interval passed as the 5-digit portion of -S.</summary>
    IterSaveGTInterval:  Integer;
    /// <summary>Save count passed as the 4-digit portion of -S.</summary>
    IterSaveGTSaves:     Integer;
    /// <summary>When True and IterSaveGTFNPath is non-empty, emit -k.</summary>
    IterSaveGTFNEnabled: Boolean;
    /// <summary>Base filename for iterative save files (-k).</summary>
    IterSaveGTFNPath:    String;

    // -----------------------------------------------------------------------
    // Message log  (-m)
    // -----------------------------------------------------------------------
    /// <summary>Emit -m when True and LogMLFNPath is non-empty.</summary>
    LogMLFNEnabled: Boolean;
    /// <summary>Path passed to -m.</summary>
    LogMLFNPath:    String;

    // -----------------------------------------------------------------------
    // Battle set  (-b)
    // -----------------------------------------------------------------------
    /// <summary>Select battle set by numeric index from the combobox.</summary>
    BSByNum:    Boolean;
    /// <summary>0-based index into the battle-set combobox (-1 = none).</summary>
    BSNumIndex: Integer;
    /// <summary>
    /// A battleset number to pass as <c>-b</c> directly (e.g. from a scenario
    /// file's extension); overrides BSByNum/BSByFile when greater than 0.
    /// Any number the GE knows, including ones the picker does not list
    /// (33, 34, 120).
    /// </summary>
    BSNumber: Integer;
    /// <summary>Select battle set by file path.</summary>
    BSByFile:   Boolean;
    /// <summary>File path for -b (only used when BSByFile=True and file exists).</summary>
    BSFilePath: String;

    // -----------------------------------------------------------------------
    // Scenario  (-c)
    // -----------------------------------------------------------------------
    /// <summary>Select scenario by number (1-based, no gaps).</summary>
    ScenByNum:    Boolean;
    /// <summary>0-based index; ID = index + 1.  -1 = none.</summary>
    ScenNumIndex: Integer;
    /// <summary>Select scenario by file path.</summary>
    ScenByFile:   Boolean;
    /// <summary>File path for -c (only used when ScenByFile=True and file exists).</summary>
    ScenFilePath: String;

    // -----------------------------------------------------------------------
    // Save file  (-v)
    // -----------------------------------------------------------------------
    /// <summary>Emit -v when True and file exists.</summary>
    SavFNEnabled: Boolean;
    /// <summary>Save file path for -v.</summary>
    SavFNPath: String;

    // -----------------------------------------------------------------------
    // CD databases  (-d, -r)
    // -----------------------------------------------------------------------
    /// <summary>Emit -d when True and CDBresFNPath file exists.</summary>
    CDBresFNEnabled: Boolean;
    CDBresFNPath:    String;
    /// <summary>Emit -r when True and CDBrsrFNPath file exists.</summary>
    CDBrsrFNEnabled: Boolean;
    CDBrsrFNPath:    String;

    // -----------------------------------------------------------------------
    // Game options  (-g)
    // -----------------------------------------------------------------------
    /// <summary>Master switch: emit -g when True.</summary>
    GameOptsEnabled:   Boolean;
    /// <summary>
    /// When True, the GE is an older version using a bitmask for sides
    /// (GOSidesBitmask used as first digit).  When False, a combobox index
    /// is used (GOSideIndex used as first digit).
    /// </summary>
    GOUseSideChecklist: Boolean;
    /// <summary>Side index from combobox (new GE, GOUseSideChecklist=False).</summary>
    GOSideIndex:     Integer;
    /// <summary>Sides bitmask (old GE, GOUseSideChecklist=True).</summary>
    GOSidesBitmask:  Integer;
    GONukeRelIndex:  Integer;
    GOSnorkSubIndex: Integer;
    GORealWeathIndex:Integer;
    GOMaintFailIndex:Integer;
    GOFullOrdIndex:  Integer;
    GOFormPatIndex:  Integer;
    GOExitIndex:     Integer;
    GOTimeCompIndex: Integer;

    // -----------------------------------------------------------------------
    // CPU affinity  (-p)
    // -----------------------------------------------------------------------
    /// <summary>Master switch for -p.</summary>
    AffinityEnabled: Boolean;
    /// <summary>0-based index into AFFINITY_MASK; -1 = none.</summary>
    AffinityIndex: Integer;

    // -----------------------------------------------------------------------
    // Process priority  (-u)
    // -----------------------------------------------------------------------
    /// <summary>Master switch for -u.</summary>
    PriorityEnabled: Boolean;
    /// <summary>
    /// 0-based combobox index; emitted value = index - 2.
    /// Range 0..4 → -2..-1..0..1..2.  -1 = none.
    /// </summary>
    PriorityIndex: Integer;
  end;

// ---------------------------------------------------------------------------
// IHCLCmdLineBuilder interface
// ---------------------------------------------------------------------------

  /// <summary>
  /// Assembles a GE command-line argument string from a <see cref="THCGECmdLineInputs"/>
  /// record.  Implementations must be free of UI dependencies.
  /// </summary>
  IHCLCmdLineBuilder = interface
    ['{5C3A8E7F-2B14-4D9C-A061-F83E1D7C4B02}']

    /// <summary>
    /// Builds the GE command-line string from the supplied inputs.
    /// </summary>
    /// <param name="AInputs">Fully populated inputs record.</param>
    /// <returns>
    /// The assembled argument string (trimmed of trailing whitespace).
    /// Returns an empty string when no flags are active.
    /// </returns>
    function BuildCommandLine(const AInputs: THCGECmdLineInputs): String;

    /// <summary>
    /// Builds the Scenario Editor (SE) command-line string from the same
    /// inputs, keeping only what the SE acts on.
    /// </summary>
    /// <param name="AInputs">Fully populated inputs record.</param>
    /// <returns>
    /// <c>-l</c> logging flags (without <c>perf</c>), a numeric <c>-b</c>
    /// battleset, and <c>-d</c>/<c>-r</c> database files, in that order;
    /// trimmed, and empty when none apply.
    /// </returns>
    /// <remarks>
    /// The SE's getopt accepts only <c>l: b: c: d: r: t:</c> (HC
    /// <c>SE/src/winmain.c</c>), so GE-only flags are never passed.  Its
    /// <c>-t</c> is a working directory, not the GE's save interval, so it
    /// is never emitted.  A <c>-b</c> file path and <c>-c</c> are accepted
    /// but only the GE's loader reads them, so they are left out.  <c>-l
    /// perf</c> exists only in PERF_MEASURE builds; others answer it with a
    /// modal "unrecognized -l option" box.
    /// </remarks>
    function BuildCommandLineSE(const AInputs: THCGECmdLineInputs): String;
  end;

// ---------------------------------------------------------------------------
// THCLCmdLineBuilder class
// ---------------------------------------------------------------------------

  /// <summary>
  /// Default implementation of <see cref="IHCLCmdLineBuilder"/>.
  ///
  /// Flags are emitted in the exact order used by the original
  /// <c>BuildCommandLineGE</c> procedure:
  /// -f, -l, -I, -h, -i, -a, -t, -s, -S/-k, -m, -x, -e, -b, -c, -v, -d, -r, -g, -p, -u.
  /// </summary>
  THCLCmdLineBuilder = class(TInterfacedObject, IHCLCmdLineBuilder)
  private
    /// <summary>The <c>-l</c> logging flags, each followed by a space.</summary>
    /// <param name="AInputs">Inputs record.</param>
    /// <param name="AIncludePerf">False to leave out <c>-l perf</c>.</param>
    /// <returns>'' when logging is off or nothing is selected.</returns>
    function LoggingFlags(const AInputs: THCGECmdLineInputs; AIncludePerf: Boolean): String;
  public
    /// <summary>
    /// Builds the SE command-line string; see
    /// <see cref="IHCLCmdLineBuilder.BuildCommandLineSE"/>.
    /// </summary>
    /// <param name="AInputs">Fully populated inputs record.</param>
    /// <returns>The trimmed argument string; '' when none apply.</returns>
    function BuildCommandLineSE(const AInputs: THCGECmdLineInputs): String;
    /// <summary>
    /// Builds the GE command-line string from the supplied inputs.
    /// </summary>
    /// <param name="AInputs">Fully populated inputs record.</param>
    /// <returns>
    /// The assembled argument string (trimmed of trailing whitespace).
    /// Returns an empty string when no flags are active.
    /// </returns>
    function BuildCommandLine(const AInputs: THCGECmdLineInputs): String;
  end;

// ---------------------------------------------------------------------------
// Helper — create a zeroed THCGECmdLineInputs with sensible defaults
// ---------------------------------------------------------------------------

/// <summary>
/// Returns a <see cref="THCGECmdLineInputs"/> record initialised to safe
/// zero-value defaults: all booleans False, all strings empty, index fields
/// set to -1 (unselected), and game-option indices set to 0.
/// </summary>
/// <returns>Zero-value inputs record.</returns>
function DefaultCmdLineInputs: THCGECmdLineInputs;

implementation

// ---------------------------------------------------------------------------
// DefaultCmdLineInputs
// ---------------------------------------------------------------------------

function DefaultCmdLineInputs: THCGECmdLineInputs;
begin
  // Boolean fields — all False
  Result.LogFN              := False;
  Result.LoggingEnabled     := False;
  Result.LogAll             := False;
  Result.LogInitAnnex       := False;
  Result.LogAcAI            := False;
  Result.LogSubAI           := False;
  Result.LogEmconAI         := False;
  Result.LogSonar           := False;
  Result.LogRefuel          := False;
  Result.LogMission         := False;
  Result.LogIntercept       := False;
  Result.LogResFile         := False;
  Result.LogLoad            := False;
  Result.LogRadar           := False;
  Result.LogECM             := False;
  Result.LogMissile         := False;
  Result.LogAreaDef         := False;
  Result.LogPerf            := False;
  Result.LogBadDB           := False;
  Result.LogGraphic         := False;
  Result.LogUnit            := False;
  Result.DebugEnabled       := False;
  Result.DebugI32501        := False;
  Result.WindowHideEnabled  := False;
  Result.WHGroup            := False;
  Result.WHUnit             := False;
  Result.WHStratMap         := False;
  Result.WHMiniReport       := False;
  Result.WHMessageLog       := False;
  Result.IgnoreReg          := False;
  Result.AutoPlay           := False;
  Result.SubAIDisable       := False;
  Result.Eff115Old          := False;
  Result.SavIntEnabled      := False;
  Result.IterSave           := False;
  Result.IterSaveGT         := False;
  Result.IterSaveGTFNEnabled:= False;
  Result.LogMLFNEnabled     := False;
  Result.BSByNum            := False;
  Result.BSByFile           := False;
  Result.ScenByNum          := False;
  Result.ScenByFile         := False;
  Result.SavFNEnabled       := False;
  Result.CDBresFNEnabled    := False;
  Result.CDBrsrFNEnabled    := False;
  Result.GameOptsEnabled    := False;
  Result.GOUseSideChecklist := False;
  Result.AffinityEnabled    := False;
  Result.PriorityEnabled    := False;

  // String fields — all empty
  Result.LogFNPath        := '';
  Result.SavIntText       := '';
  Result.IterSaveGTFNPath := '';
  Result.LogMLFNPath      := '';
  Result.BSFilePath       := '';
  Result.ScenFilePath     := '';
  Result.SavFNPath        := '';
  Result.CDBresFNPath     := '';
  Result.CDBrsrFNPath     := '';

  // Index fields: -1 means "nothing selected"
  Result.BSNumIndex    := -1;
  Result.BSNumber      := 0;
  Result.ScenNumIndex  := -1;
  Result.AffinityIndex := -1;
  Result.PriorityIndex := -1;

  // Iterative save GT: zero is a valid starting value
  Result.IterSaveGTInterval := 0;
  Result.IterSaveGTSaves    := 0;

  // Game options: 0 is the first combobox item (valid selection)
  Result.GOSideIndex       := 0;
  Result.GOSidesBitmask    := 0;
  Result.GONukeRelIndex    := 0;
  Result.GOSnorkSubIndex   := 0;
  Result.GORealWeathIndex  := 0;
  Result.GOMaintFailIndex  := 0;
  Result.GOFullOrdIndex    := 0;
  Result.GOFormPatIndex    := 0;
  Result.GOExitIndex       := 0;
  Result.GOTimeCompIndex   := 0;
end;

// ---------------------------------------------------------------------------
// THCLCmdLineBuilder.LoggingFlags
// ---------------------------------------------------------------------------

function THCLCmdLineBuilder.LoggingFlags(const AInputs: THCGECmdLineInputs;
  AIncludePerf: Boolean): String;
begin
  Result := '';
  if not AInputs.LoggingEnabled then
    Exit;

  if AInputs.LogAll then
    Exit('-l all ');

  if AInputs.LogInitAnnex then Result := Result + '-l init_annex ';
  if AInputs.LogAcAI      then Result := Result + '-l ac_ai ';
  if AInputs.LogSubAI     then Result := Result + '-l sub_ai ';
  if AInputs.LogEmconAI   then Result := Result + '-l emcon_ai ';
  if AInputs.LogSonar     then Result := Result + '-l sonar ';
  if AInputs.LogRefuel    then Result := Result + '-l refuel ';
  if AInputs.LogMission   then Result := Result + '-l mission ';
  if AInputs.LogIntercept then Result := Result + '-l intercept ';
  if AInputs.LogResFile   then Result := Result + '-l res_file ';
  if AInputs.LogLoad      then Result := Result + '-l load ';
  if AInputs.LogRadar     then Result := Result + '-l radar ';
  if AInputs.LogECM       then Result := Result + '-l ecm ';
  if AInputs.LogMissile   then Result := Result + '-l missile ';
  if AInputs.LogAreaDef   then Result := Result + '-l areadef ';
  if AInputs.LogPerf and AIncludePerf then Result := Result + '-l perf ';
  if AInputs.LogBadDB     then Result := Result + '-l bad_db ';
  if AInputs.LogGraphic   then Result := Result + '-l graphic ';
  if AInputs.LogUnit      then Result := Result + '-l unit ';
end;

// ---------------------------------------------------------------------------
// THCLCmdLineBuilder.BuildCommandLineSE
// ---------------------------------------------------------------------------

function THCLCmdLineBuilder.BuildCommandLineSE(const AInputs: THCGECmdLineInputs): String;
begin
  Result := LoggingFlags(AInputs, False);

  // The SE reads -b only as a battleset number.
  if AInputs.BSNumber > 0 then
    Result := Result + '-b ' + IntToStr(AInputs.BSNumber) + ' '
  else if AInputs.BSByNum and (AInputs.BSNumIndex >= 0)
     and (AInputs.BSNumIndex <= High(BS_INDEX_TO_ID)) then
    Result := Result + '-b ' + IntToStr(BS_INDEX_TO_ID[AInputs.BSNumIndex]) + ' ';

  // Same rules as the GE: both need the switch on and a file to name.
  if AInputs.CDBresFNEnabled and (AInputs.CDBresFNPath <> '')
     and FileExists(AInputs.CDBresFNPath) then
    Result := Result + '-d "' + AInputs.CDBresFNPath + '" ';
  if AInputs.CDBrsrFNEnabled and (AInputs.CDBrsrFNPath <> '')
     and FileExists(AInputs.CDBrsrFNPath) then
    Result := Result + '-r "' + AInputs.CDBrsrFNPath + '" ';

  Result := TrimRight(Result);
end;

// ---------------------------------------------------------------------------
// THCLCmdLineBuilder.BuildCommandLine
// ---------------------------------------------------------------------------

function THCLCmdLineBuilder.BuildCommandLine(const AInputs: THCGECmdLineInputs): String;
var
  Result_: String;  // local accumulator (avoid shadowing the implicit Result)
  strAccum: String; // sub-accumulator for -l, -I, -h groups
  blnAccum: Boolean;
  FmtSettings: TFormatSettings;
  FloatVal: Double;
  SideFirst: Integer;
begin
  Result_ := '';

  // -----------------------------------------------------------------
  // 1. -f  (log file)
  // -----------------------------------------------------------------
  if AInputs.LogFN and (AInputs.LogFNPath <> '') then
  begin
    if DirectoryExists(ExtractFilePath(AInputs.LogFNPath)) then
      Result_ := Result_ + '-f "' + AInputs.LogFNPath + '" ';
  end;

  // -----------------------------------------------------------------
  // 2. -l  (logging flags)
  // -----------------------------------------------------------------
  Result_ := Result_ + LoggingFlags(AInputs, True);

  // -----------------------------------------------------------------
  // 3. -I  (debug flags)
  // -----------------------------------------------------------------
  if AInputs.DebugEnabled then
  begin
    strAccum := '';
    blnAccum := False;

    if AInputs.DebugI32501 then
    begin
      strAccum := strAccum + '-I I32501 ';
      blnAccum := True;
    end;

    if blnAccum then
      Result_ := Result_ + strAccum;
  end;

  // -----------------------------------------------------------------
  // 4. -h  (window hide flags)
  // -----------------------------------------------------------------
  if AInputs.WindowHideEnabled then
  begin
    strAccum := '';
    blnAccum := False;

    if AInputs.WHGroup      then begin strAccum := strAccum + '-h group ';       blnAccum := True; end;
    if AInputs.WHUnit       then begin strAccum := strAccum + '-h unit ';        blnAccum := True; end;
    if AInputs.WHStratMap   then begin strAccum := strAccum + '-h strategicmap '; blnAccum := True; end;
    if AInputs.WHMiniReport then begin strAccum := strAccum + '-h minireport '; blnAccum := True; end;
    if AInputs.WHMessageLog then begin strAccum := strAccum + '-h messagelog '; blnAccum := True; end;

    if blnAccum then
      Result_ := Result_ + strAccum;
  end;

  // -----------------------------------------------------------------
  // 5. -i  (ignore registry)
  // -----------------------------------------------------------------
  if AInputs.IgnoreReg then
    Result_ := Result_ + '-i ';

  // -----------------------------------------------------------------
  // 6. -a  (auto-play)
  // -----------------------------------------------------------------
  if AInputs.AutoPlay then
    Result_ := Result_ + '-a ';

  // -----------------------------------------------------------------
  // 7. -t  (save interval)
  //    Validate using invariant format settings (decimal separator = '.')
  //    to avoid locale-dependent bugs.
  // -----------------------------------------------------------------
  if AInputs.SavIntEnabled then
  begin
    FmtSettings := DefaultFormatSettings;
    FmtSettings.DecimalSeparator  := '.';
    FmtSettings.ThousandSeparator := ',';

    if TryStrToFloat(AInputs.SavIntText, FloatVal, FmtSettings) then
      Result_ := Result_ + '-t ' + AInputs.SavIntText + ' ';
  end;

  // -----------------------------------------------------------------
  // 8. -s  (iterative save)
  // -----------------------------------------------------------------
  if AInputs.IterSave then
    Result_ := Result_ + '-s ';

  // -----------------------------------------------------------------
  // 9. -S and -k  (iterative save GT)
  // -----------------------------------------------------------------
  if AInputs.IterSaveGT then
  begin
    Result_ := Result_
      + '-S'
      + Format('%0.5d', [AInputs.IterSaveGTInterval])
      + Format('%0.4d ', [AInputs.IterSaveGTSaves]);

    if AInputs.IterSaveGTFNEnabled and (AInputs.IterSaveGTFNPath <> '') then
      Result_ := Result_ + '-k ' + AInputs.IterSaveGTFNPath + ' ';
  end;

  // -----------------------------------------------------------------
  // 10. -m  (message log)
  // -----------------------------------------------------------------
  if AInputs.LogMLFNEnabled and (AInputs.LogMLFNPath <> '') then
    Result_ := Result_ + '-m ' + AInputs.LogMLFNPath + ' ';

  // -----------------------------------------------------------------
  // 11. -x  (substitute AI disable)
  // -----------------------------------------------------------------
  if AInputs.SubAIDisable then
    Result_ := Result_ + '-x ';

  // -----------------------------------------------------------------
  // 12. -e  (effect 1.15 old values)
  // -----------------------------------------------------------------
  if AInputs.Eff115Old then
    Result_ := Result_ + '-e ';

  // -----------------------------------------------------------------
  // 13. -b  (battle set)
  //     An explicit number first, then the picker, then a file path.
  // -----------------------------------------------------------------
  if AInputs.BSNumber > 0 then
  begin
    Result_ := Result_ + '-b ' + IntToStr(AInputs.BSNumber) + ' ';
  end
  else if AInputs.BSByNum and (AInputs.BSNumIndex >= 0)
     and (AInputs.BSNumIndex <= High(BS_INDEX_TO_ID)) then
  begin
    Result_ := Result_ + '-b ' + IntToStr(BS_INDEX_TO_ID[AInputs.BSNumIndex]) + ' ';
  end
  else if AInputs.BSByFile and (AInputs.BSFilePath <> '')
          and FileExists(AInputs.BSFilePath) then
  begin
    Result_ := Result_ + '-b "' + AInputs.BSFilePath + '" ';
  end;

  // -----------------------------------------------------------------
  // 14. -c  (scenario)
  //     Numeric selection: ID = index + 1 (1-based, no gaps).
  // -----------------------------------------------------------------
  if AInputs.ScenByNum and (AInputs.ScenNumIndex >= 0) then
  begin
    Result_ := Result_ + '-c ' + IntToStr(AInputs.ScenNumIndex + 1) + ' ';
  end
  else if AInputs.ScenByFile and (AInputs.ScenFilePath <> '')
          and FileExists(AInputs.ScenFilePath) then
  begin
    Result_ := Result_ + '-c "' + AInputs.ScenFilePath + '" ';
  end;

  // -----------------------------------------------------------------
  // 15. -v  (save file)
  // -----------------------------------------------------------------
  if AInputs.SavFNEnabled and (AInputs.SavFNPath <> '')
     and FileExists(AInputs.SavFNPath) then
  begin
    Result_ := Result_ + '-v "' + AInputs.SavFNPath + '" ';
  end;

  // -----------------------------------------------------------------
  // 16. -d  (CD database res)
  // -----------------------------------------------------------------
  if AInputs.CDBresFNEnabled and (AInputs.CDBresFNPath <> '')
     and FileExists(AInputs.CDBresFNPath) then
  begin
    Result_ := Result_ + '-d "' + AInputs.CDBresFNPath + '" ';
  end;

  // -----------------------------------------------------------------
  // 17. -r  (CD database rsr)
  // -----------------------------------------------------------------
  if AInputs.CDBrsrFNEnabled and (AInputs.CDBrsrFNPath <> '')
     and FileExists(AInputs.CDBrsrFNPath) then
  begin
    Result_ := Result_ + '-r "' + AInputs.CDBrsrFNPath + '" ';
  end;

  // -----------------------------------------------------------------
  // 18. -g  (game options)
  //     9-digit string: first digit = side selection, next 8 = options.
  // -----------------------------------------------------------------
  if AInputs.GameOptsEnabled then
  begin
    if AInputs.GOUseSideChecklist then
      SideFirst := AInputs.GOSidesBitmask
    else
      SideFirst := AInputs.GOSideIndex;

    Result_ := Result_ + '-g ' + Format('%d%d%d%d%d%d%d%d%d', [
      SideFirst,
      AInputs.GONukeRelIndex,
      AInputs.GOSnorkSubIndex,
      AInputs.GORealWeathIndex,
      AInputs.GOMaintFailIndex,
      AInputs.GOFullOrdIndex,
      AInputs.GOFormPatIndex,
      AInputs.GOExitIndex,
      AInputs.GOTimeCompIndex
    ]) + ' ';
  end;

  // -----------------------------------------------------------------
  // 19. -p  (CPU affinity)
  // -----------------------------------------------------------------
  if AInputs.AffinityEnabled
     and (AInputs.AffinityIndex >= 0)
     and (AInputs.AffinityIndex <= High(AFFINITY_MASK)) then
  begin
    Result_ := Result_ + '-p ' + IntToStr(AFFINITY_MASK[AInputs.AffinityIndex]) + ' ';
  end;

  // -----------------------------------------------------------------
  // 20. -u  (process priority)
  //     Value = index - 2 → range [-2 .. 2].
  // -----------------------------------------------------------------
  if AInputs.PriorityEnabled and (AInputs.PriorityIndex >= 0) then
    Result_ := Result_ + '-u ' + IntToStr(AInputs.PriorityIndex - 2) + ' ';

  Result := TrimRight(Result_);
end;

end.
