///|
/// The shim's own argv, shared by both dialects: engine settings that are
/// deployment configuration, never part of the request. Everything after
/// `--` is passed to the CLI verbatim — the escape hatch for a flag the
/// shim does not know yet.
///
///   --result-file   where to write the result (required; the runner
///                     passes its `{result_file}` placeholder here)
///   --command    the CLI binary (default: the dialect's name on PATH)
///   --model     forwarded as the CLI's model flag
///   --cwd        the CLI's working directory
///   --writable        write-capable run (default: read-only unless kind = worker)
///   --schema    JSON Schema the final answer must match
///   --       passed through to the CLI unchanged
///
/// A dialect READS one; `parse` is how a shim gets it, off argv. A caller
/// fabricating one (a dialect's own tests) goes through `Options(...)`,
/// so a later setting costs no dialect anything.
pub struct Options {
  /// Where the one result file goes (the child contract).
  result_file : String
  command : String
  model : String?
  cwd : String?
  writable : Bool
  schema : String?
  extra : Array[String]
}

///|
/// Options from their parts, for a caller that FABRICATES them instead of
/// parsing argv. Only the binary and the result path are required — every
/// other setting is a flag the operator did not pass, and `writable`
/// defaults to the read-only run that argv defaults to.
pub fn Options::Options(
  command~ : String,
  result_file~ : String,
  model? : String,
  cwd? : String,
  writable? : Bool = false,
  schema? : String,
  extra? : Array[String] = [],
) -> Options {
  { result_file, command, model, cwd, writable, schema, extra, }
}

///|
/// The shim's own flags, as argparse sees them. `--` and everything after
/// it is cut away before parsing, so a passthrough flag can never be read
/// as one of ours (nor ours be swallowed as passthrough).
fn spec(default_command : String) -> @argparse.Command {
  @argparse.Command(
    default_command,
    about=(
      $|Drive the \{default_command} CLI as a workflow child: read one request
      $|line on stdin, write one result file to --result-file. Everything
      $|after `--` reaches the CLI unchanged.
    ),
    options=[
      @argparse.OptionArg(
        "result-file",
        about="where to write the result file",
        required=true,
      ),
      @argparse.OptionArg("command", about="the CLI binary", default_values=[
        default_command,
      ]),
      @argparse.OptionArg("model", about="forwarded as the CLI's model flag"),
      @argparse.OptionArg("cwd", about="the CLI's working directory"),
      @argparse.OptionArg(
        "schema",
        about="file holding the JSON Schema the final answer must match",
      ),
    ],
    flags=[
      @argparse.FlagArg(
        "writable",
        about="write-capable run (default: read-only unless kind = worker)",
      ),
    ],
  )
}

///|
/// Parse the shim's argv (without argv[0]). Unknown options are refused —
/// a typo must not silently become a prompt or a CLI flag.
fn Options::parse(
  args : ArrayView[String],
  default_command~ : String,
) -> Result[Options, String] {
  let cut = args.search("--").unwrap_or(args.length())
  let extra = if cut < args.length() { args[cut + 1:].to_owned() } else { [] }
  let matches = spec(default_command).parse(argv=args[:cut], env=Map([])) catch {
    // Display-ready text: the error and this command's full help.
    error => return Err("\{error}")
  }
  guard first(matches, "result-file") is Some(result_file) &&
    !result_file.is_blank() else {
    return Err("--result-file needs a path")
  }
  Ok({
    result_file,
    command: first(matches, "command").unwrap_or(default_command),
    model: first(matches, "model"),
    cwd: first(matches, "cwd"),
    writable: matches.flags.get("writable").unwrap_or(false),
    schema: first(matches, "schema"),
    extra,
  })
}

///|
/// The first value parsed for an argument, if it was given at all.
fn first(matches : @argparse.Matches, name : String) -> String? {
  match matches.values.get(name) {
    Some([value, ..]) => Some(value)
    _ => None
  }
}