///|
pub(all) enum CliCommand {
  Help
  List(config_path~ : String)
  Graph(config_path~ : String, target~ : String?)
  Explain(config_path~ : String, target~ : String?)
  Stats(config_path~ : String)
  Audit(config_path~ : String)
  Clean(config_path~ : String)
  Doctor(config_path~ : String)
  Run(config_path~ : String, target~ : String?, jobs~ : Int)
} derive(@debug.Debug)

///|
pub fn help_text() -> String {
  (
    #|MoonForge
    #|
    #|Usage:
    #|  moonforge list [--file PATH]
    #|  moonforge graph [TASK] [--file PATH]
    #|  moonforge explain [TASK] [--file PATH]
    #|  moonforge stats [--file PATH]
    #|  moonforge audit [--file PATH]
    #|  moonforge clean [--file PATH]
    #|  moonforge doctor [--file PATH]
    #|  moonforge run [TASK] [--file PATH] [-j N]
    #|
    #|Defaults:
    #|  config path: Moonforge.toml
    #|  run target : build, default, or the first task alphabetically
    #|
  )
}

///|
pub fn parse_cli(args : Array[String]) -> CliCommand raise MoonforgeError {
  if args.is_empty() {
    Help
  } else {
    let command = args[0]
    let rest = args[1:].to_owned()
    match command {
      "help" | "--help" | "-h" => Help
      "list" => {
        let (config_path, _jobs, target) = parse_common_args(rest)
        if target is Some(_) {
          config_error("list does not accept a target argument")
        }
        List(config_path~)
      }
      "graph" => {
        let (config_path, _jobs, target) = parse_common_args(rest)
        Graph(config_path~, target~)
      }
      "explain" => {
        let (config_path, _jobs, target) = parse_common_args(rest)
        Explain(config_path~, target~)
      }
      "stats" => {
        let (config_path, _jobs, target) = parse_common_args(rest)
        if target is Some(_) {
          config_error("stats does not accept a target argument")
        }
        Stats(config_path~)
      }
      "audit" => {
        let (config_path, _jobs, target) = parse_common_args(rest)
        if target is Some(_) {
          config_error("audit does not accept a target argument")
        }
        Audit(config_path~)
      }
      "clean" => {
        let (config_path, _jobs, target) = parse_common_args(rest)
        if target is Some(_) {
          config_error("clean does not accept a target argument")
        }
        Clean(config_path~)
      }
      "doctor" => {
        let (config_path, _jobs, target) = parse_common_args(rest)
        if target is Some(_) {
          config_error("doctor does not accept a target argument")
        }
        Doctor(config_path~)
      }
      "run" => {
        let (config_path, jobs, target) = parse_common_args(rest)
        Run(config_path~, target~, jobs~)
      }
      _ => config_error("unknown command '\{command}'")
    }
  }
}

///|
fn parse_common_args(
  args : Array[String],
) -> (String, Int, String?) raise MoonforgeError {
  let config_path = Ref("Moonforge.toml")
  let jobs = Ref(1)
  let target = Ref(None)
  let i = Ref(0)
  while i.val < args.length() {
    let arg = args[i.val]
    match arg {
      "--file" | "-f" => {
        guard i.val + 1 < args.length() else {
          config_error("\{arg} requires a value")
        }
        config_path.val = args[i.val + 1]
        i.val += 2
      }
      "--jobs" | "-j" => {
        guard i.val + 1 < args.length() else {
          config_error("\{arg} requires a value")
        }
        jobs.val = parse_jobs(args[i.val + 1])
        i.val += 2
      }
      value if value.has_prefix("-") =>
        config_error("unknown option '\{value}'")
      value => {
        guard target.val is None else {
          config_error("multiple target arguments are not supported")
        }
        target.val = Some(value)
        i.val += 1
      }
    }
  }
  (config_path.val, jobs.val, target.val)
}

///|
fn parse_jobs(raw : String) -> Int raise MoonforgeError {
  @string.parse_int(raw) catch {
    _ => config_error("invalid jobs value '\{raw}'")
  }
}