///|
/// Also see: moon.mod - `version`
pub const VERSION = "0.4.4"

///|
const DEFAULT_LICENSE : String = "AGPL-3.0"

///|
const DEFAULT_HOST : String = "github.com"

///|
const DEFAULT_TARGET : String = "native"

///|
const CLI_NAME : String = "momoka"

///|
const CFG_COMMAND : String = "cfg"

///|
priv struct ProjectLocation {
  path : String
  mode : ProjectMode
}

///|
priv struct ProjectOverrides {
  license : String?
  host : String?
  preferred_target : String?
}

///|
priv struct CliConfig {
  location : ProjectLocation
  overrides : ProjectOverrides
}

///|
priv enum ProjectMode {
  Create
  Init
}

///|
priv enum PreferencesRequest {
  Configure
  Show
}

///|
priv enum CliRequest {
  Help(HelpTopic)
  Project(CliConfig)
  Preferences(PreferencesRequest)
}

///|
priv enum HelpTopic {
  TopLevelHelp
  PreferencesHelp
}

///|
priv struct NamedArgSpec {
  id : String
  short : Char?
  about : String
}

///|
priv struct PositionArgSpec {
  id : String
  about : String
}

///|
fn NamedArgSpec::to_option(self : NamedArgSpec) -> @argparse.OptionArg {
  @argparse.OptionArg(
    self.id,
    short?=self.short,
    long=self.id,
    about=self.about,
  )
}

///|
fn NamedArgSpec::to_flag(self : NamedArgSpec) -> @argparse.FlagArg {
  @argparse.FlagArg(self.id, short?=self.short, long=self.id, about=self.about)
}

///|
fn PositionArgSpec::to_positional(
  self : PositionArgSpec,
) -> @argparse.PositionArg {
  @argparse.PositionArg(
    self.id,
    about=self.about,
    num_args=@argparse.ValueRange(lower=0, upper=1),
  )
}

///|
fn preferences_command(
  name : String,
  hidden? : Bool = false,
) -> @argparse.Command {
  @argparse.Command(
    name,
    about="Configure default license / host / target, or print them.",
    flags=[
      @argparse.FlagArg("show", long="show", about="Print current preferences."),
    ],
    hidden~,
  )
}

///|
fn cli_command() -> @argparse.Command {
  let project_options = [
    {
      id: "license",
      short: Some('l'),
      about: "License for moon.mod [default: prefs or \{DEFAULT_LICENSE}].",
    },
    {
      id: "host",
      short: None,
      about: "Repository hosting domain [default: prefs or \{DEFAULT_HOST}].",
    },
    {
      id: "target",
      short: Some('t'),
      about: "Preferred target [default: prefs or \{DEFAULT_TARGET}].",
    },
  ]
  let project_flags = [
    {
      id: "init",
      short: Some('i'),
      about: "Initialize in an existing directory instead of creating it.",
    },
  ]
  let project_positionals = [{ id: "path", about: "Project directory path." }]
  @argparse.Command(
    CLI_NAME,
    version=VERSION,
    options=project_options.map(NamedArgSpec::to_option),
    flags=project_flags.map(NamedArgSpec::to_flag),
    positionals=project_positionals.map(PositionArgSpec::to_positional),
    subcommands=[preferences_command(CFG_COMMAND)],
    disable_help_subcommand=true,
  )
}

///|
fn matched_value(matches : @argparse.Matches, value_id : String) -> String? {
  matches.values.get(value_id).bind(values => values.last())
}

///|
fn decode_project_request(
  matches : @argparse.Matches,
) -> CliRequest raise CliError {
  let mode = if matches.flags.get("init").unwrap_or(false) {
    Init
  } else {
    Create
  }
  let path = match matched_value(matches, "path") {
    Some(path) => path
    None => if mode is Init { "." } else { raise CliError(usage_text()) }
  }
  Project({
    location: { path, mode },
    overrides: {
      license: matched_value(matches, "license"),
      host: matched_value(matches, "host"),
      preferred_target: matched_value(matches, "target"),
    },
  })
}

///|
fn decode_preferences_request(matches : @argparse.Matches) -> CliRequest {
  if matches.flags.get("show").unwrap_or(false) {
    Preferences(Show)
  } else {
    Preferences(Configure)
  }
}

///|
fn decode_subcommand(
  _name : String,
  submatches : @argparse.Matches,
) -> CliRequest {
  decode_preferences_request(submatches)
}

///|
fn decode_cli_matches(matches : @argparse.Matches) -> CliRequest raise CliError {
  match matches.subcommand {
    Some((name, submatches)) => decode_subcommand(name, submatches)
    None => decode_project_request(matches)
  }
}

///|
fn is_help_arg(arg : String) -> Bool {
  arg == "-h" || arg == "--help"
}

///|
fn help_request(args : ArrayView[String]) -> CliRequest? {
  match args {
    [] => Some(Help(TopLevelHelp))
    [arg] if is_help_arg(arg) => Some(Help(TopLevelHelp))
    ["cfg", arg] if is_help_arg(arg) => Some(Help(PreferencesHelp))
    _ => None
  }
}

///|
fn parse_cli_args(args : ArrayView[String]) -> CliRequest raise CliError {
  guard help_request(args) is Some(request) else {
    try @argparse.Command::parse(cli_command(), argv=args) catch {
      err => raise CliError("\{err}")
    } noraise {
      matches => decode_cli_matches(matches)
    }
  }
  request
}