///|
pub enum CliFormat {
  Text
  Markdown
  Json
  Csv
  JsonLines
} derive(Debug, Eq, ToJson)

///|
pub enum CliRequest {
  Demo
  Eval(
    qrels_path~ : String,
    run_path~ : String,
    format~ : CliFormat,
    cutoffs~ : Array[Int],
    threshold~ : Int,
    gain~ : GainScheme
  )
  Validate(qrels_path~ : String, run_path~ : String, format~ : CliFormat)
  Compare(
    qrels_path~ : String,
    baseline_path~ : String,
    candidate_path~ : String,
    cutoff~ : Int
  )
  Inspect(qrels_path~ : String, run_path~ : String)
} derive(Debug, ToJson)

///|
fn cli_flag_value(args : Array[String], flag : String) -> String? {
  for index in 1.. Result[Int, String] {
  Ok(@strconv.from_str(value)) catch {
    _ => Err("invalid integer for \{flag}: \{value}")
  }
}

///|
fn cli_parse_cutoffs(value : String) -> Result[Array[Int], String] {
  let values : Array[Int] = []
  for piece in value.split(",") {
    let trimmed = piece.trim(chars=" \t\r")
    if trimmed.is_empty() {
      continue
    }
    let parsed = match cli_parse_int(trimmed.to_owned(), "--cutoffs") {
      Ok(number) => number
      Err(error) => return Err(error)
    }
    values.push(parsed)
  }
  if values.is_empty() {
    Err("--cutoffs must contain at least one integer")
  } else {
    Ok(normalize_cutoffs(values))
  }
}

///|
fn cli_parse_format(value : String) -> Result[CliFormat, String] {
  match value {
    "text" => Ok(Text)
    "markdown" => Ok(Markdown)
    "json" => Ok(Json)
    "csv" => Ok(Csv)
    "jsonl" => Ok(JsonLines)
    _ =>
      Err(
        "unsupported format: \{value} (expected text, markdown, json, csv, or jsonl)",
      )
  }
}

///|
fn cli_parse_gain(value : String) -> Result[GainScheme, String] {
  match value {
    "linear" => Ok(GainScheme::linear())
    "exp2" => Ok(GainScheme::exp2())
    _ => Err("unsupported gain: \{value} (expected linear or exp2)")
  }
}

///|
fn cli_required(args : Array[String], flag : String) -> Result[String, String] {
  match cli_flag_value(args, flag) {
    Some(value) => Ok(value)
    None => Err("missing required flag \{flag}")
  }
}

///|
pub fn parse_cli_request(args : Array[String]) -> Result[CliRequest, String] {
  if args.length() <= 1 {
    return Ok(Demo)
  }
  let command = args[1]
  match command {
    "demo" => Ok(Demo)
    "eval" => {
      let qrels_path = match cli_required(args, "--qrels") {
        Ok(value) => value
        Err(error) => return Err(error)
      }
      let run_path = match cli_required(args, "--run") {
        Ok(value) => value
        Err(error) => return Err(error)
      }
      let format = match cli_flag_value(args, "--format") {
        Some(value) =>
          match cli_parse_format(value) {
            Ok(parsed) => parsed
            Err(error) => return Err(error)
          }
        None => Text
      }
      let cutoffs = match cli_flag_value(args, "--cutoffs") {
        Some(value) =>
          match cli_parse_cutoffs(value) {
            Ok(parsed) => parsed
            Err(error) => return Err(error)
          }
        None => default_cutoffs()
      }
      let threshold = match cli_flag_value(args, "--threshold") {
        Some(value) =>
          match cli_parse_int(value, "--threshold") {
            Ok(parsed) => parsed
            Err(error) => return Err(error)
          }
        None => 1
      }
      let gain = match cli_flag_value(args, "--gain") {
        Some(value) =>
          match cli_parse_gain(value) {
            Ok(parsed) => parsed
            Err(error) => return Err(error)
          }
        None => GainScheme::linear()
      }
      Ok(Eval(qrels_path~, run_path~, format~, cutoffs~, threshold~, gain~))
    }
    "validate" => {
      let qrels_path = match cli_required(args, "--qrels") {
        Ok(value) => value
        Err(error) => return Err(error)
      }
      let run_path = match cli_required(args, "--run") {
        Ok(value) => value
        Err(error) => return Err(error)
      }
      let format = match cli_flag_value(args, "--format") {
        Some(value) =>
          match cli_parse_format(value) {
            Ok(parsed) => parsed
            Err(error) => return Err(error)
          }
        None => Markdown
      }
      Ok(Validate(qrels_path~, run_path~, format~))
    }
    "compare" => {
      let qrels_path = match cli_required(args, "--qrels") {
        Ok(value) => value
        Err(error) => return Err(error)
      }
      let baseline_path = match cli_required(args, "--baseline") {
        Ok(value) => value
        Err(error) => return Err(error)
      }
      let candidate_path = match cli_required(args, "--candidate") {
        Ok(value) => value
        Err(error) => return Err(error)
      }
      let cutoff = match cli_flag_value(args, "--cutoff") {
        Some(value) =>
          match cli_parse_int(value, "--cutoff") {
            Ok(parsed) => Int::max(parsed, 1)
            Err(error) => return Err(error)
          }
        None => 10
      }
      Ok(Compare(qrels_path~, baseline_path~, candidate_path~, cutoff~))
    }
    "inspect" => {
      let qrels_path = match cli_required(args, "--qrels") {
        Ok(value) => value
        Err(error) => return Err(error)
      }
      let run_path = match cli_required(args, "--run") {
        Ok(value) => value
        Err(error) => return Err(error)
      }
      Ok(Inspect(qrels_path~, run_path~))
    }
    _ => Err("unknown command '\{command}'")
  }
}