///|
fn build_option_to_flag(opt : OptionDef) -> @argparse.FlagArg {
  @argparse.FlagArg(
    opt.name,
    short?=opt.short,
    about=opt.description,
    global=opt.global,
  )
}

///|
fn build_option_to_option_arg(
  opt : OptionDef,
  defer_required : Bool,
) -> @argparse.OptionArg {
  let defaults = option_default(opt).map(value => [value]).unwrap_or([])
  @argparse.OptionArg(
    opt.name,
    short?=opt.short,
    about=opt.description,
    default_values=defaults,
    required=opt.required && !defer_required,
    global=opt.global,
    allow_hyphen_values=opt.kind is IntOpt(_) && !defer_required,
  )
}

///|
fn positional_is_multiple(
  pos : PositionalDef,
  index : Int32,
  total : Int32,
) -> Bool {
  pos.multiple && index == total - 1
}

///|
fn build_positional(
  pos : PositionalDef,
  multiple : Bool,
  defer_required : Bool,
) -> @argparse.PositionArg {
  let lower = if pos.required && !defer_required { 1 } else { 0 }
  let range = if multiple {
    @argparse.ValueRange(lower~)
  } else {
    @argparse.ValueRange(lower~, upper=1)
  }
  @argparse.PositionArg(pos.name, about=pos.description, num_args=range)
}

///|
fn build_parser(
  cmd : CommandDef,
  version? : String,
  defer_required~ : Bool = false,
) -> @argparse.Command {
  let flags : Array[@argparse.FlagArg] = []
  let options : Array[@argparse.OptionArg] = []
  for opt in cmd.options {
    match opt.kind {
      BoolOpt => flags.push(build_option_to_flag(opt))
      StringOpt(_) | IntOpt(_) =>
        options.push(build_option_to_option_arg(opt, defer_required))
    }
  }
  let positionals = cmd.positionals.mapi((i, pos) => {
    build_positional(
      pos,
      positional_is_multiple(pos, i, cmd.positionals.length()),
      defer_required,
    )
  })
  let subcommands = cmd.subcommands.map(sub => {
    build_parser(sub, defer_required~)
  })
  @argparse.Command(
    cmd.name,
    about=cmd.description,
    version?=version.map(v => v[:]),
    flags~,
    options~,
    positionals~,
    subcommands~,
  )
}

///|
/// Explicit argparse interop. Validates definitions and builds the underlying
/// parser with argparse's ancestor-required semantics, without cmder's selected-
/// local required checks, integer checks or callback execution. Normal consumers
/// should use CliApp::parse instead.
///
/// ```mbt check
/// test {
///   let parser = @cmder.build_command(@cmder.command(name="go"))
///   assert_true(parser.render_help().has_prefix("Usage: go"))
/// }
/// ```
pub fn build_command(cmd : CommandDef) -> @argparse.Command raise CliError {
  validate_definition(cmd, [], [])
  let description = if cmd.examples.is_empty() {
    cmd.description
  } else {
    "\{cmd.description}\n\nExamples:\n  \{cmd.examples.join("\n  ")}"
  }
  build_parser({ ..cmd, description, })
}