///|
// Completion-only projection. Optional spellings model argparse's independently
// shadowable builtin long and short flags without changing public OptionDef.
priv struct CompletionOption {
  long : String?
  short : Char?
  description : String
  takes_value : Bool
}

///|
priv struct CompletionCommand {
  name : String
  description : String
  options : Array[CompletionOption]
  subcommands : Array[CompletionCommand]
}

///|
fn builtin_completion_options(
  options : ArrayView[OptionDef],
  version : Bool,
) -> Array[CompletionOption] {
  let builtins : Array[CompletionOption] = []
  fn add_builtin(name, short, description) {
    let long = if options.any(opt => opt.name == name) {
      None
    } else {
      Some(name)
    }
    let short = if options.iter().any(opt => opt.short == Some(short)) {
      None
    } else {
      Some(short)
    }
    if long is Some(_) || short is Some(_) {
      builtins.push({ long, short, description, takes_value: false, })
    }
  }
  add_builtin("help", 'h', "Show help information.")
  if version {
    add_builtin("version", 'V', "Show version information.")
  }
  builtins
}

///|
fn completion_options(
  options : ArrayView[OptionDef],
  version~ : Bool = false,
) -> Array[CompletionOption] {
  let projected = options.map(opt => CompletionOption::{
    long: Some(opt.name),
    short: opt.short,
    description: opt.description,
    takes_value: opt.kind != BoolOpt,
  })
  projected + builtin_completion_options(options, version)
}

///|
fn completion_help_targets(
  commands : ArrayView[CommandDef],
  help_options : Array[CompletionOption],
) -> Array[CompletionCommand] {
  commands.map(cmd => CompletionCommand::{
    name: cmd.name,
    description: cmd.description,
    options: help_options,
    subcommands: completion_help_targets(cmd.subcommands, help_options),
  })
}

///|
fn completion_commands(
  commands : ArrayView[CommandDef],
  options : ArrayView[OptionDef],
  inherited~ : ArrayView[OptionDef] = [],
) -> Array[CompletionCommand] {
  let globals = inherited.to_owned() + options.filter(opt => opt.global)
  let projected = commands.map(cmd => CompletionCommand::{
    name: cmd.name,
    description: cmd.description,
    options: completion_options(globals + cmd.options[:].to_owned()),
    subcommands: completion_commands(
      cmd.subcommands,
      cmd.options,
      inherited=globals,
    ),
  })
  if !commands.is_empty() {
    // `help` traverses command names, not application options. Its optional
    // trailing help flags belong to the command where the help verb occurred.
    let help_options = builtin_completion_options(
      inherited.to_owned() + options.to_owned(),
      false,
    )
    projected.push({
      name: "help",
      description: "Print help for the subcommand(s).",
      options: help_options,
      subcommands: completion_help_targets(commands, help_options),
    })
  }
  projected
}