///|
priv struct RequestFile {
  path : String
  text : String
  template : Bool
}

///|
fn protocol_error(message : String) -> Json {
  {
    "exit_code": Json(2),
    "text": Json("szs: " + message + "\n"),
    "data": {
      "schema_version": Json(1),
      "error": Json(message),
      "exit_code": Json(2),
    },
  }
}

///|
fn request_files(request : Json) -> Result[Array[RequestFile], String] {
  let raw = json_field(request, "files").unwrap_or(Json::array([]))
  let files = match raw {
    Array(files) => files
    _ => return Err("files must be an array")
  }
  if files.length() > 1000 {
    return Err("A request supports at most 1000 files")
  }
  let result : Array[RequestFile] = []
  let paths : Map[String, Bool] = Map([])
  for file in files {
    let path = match json_field(file, "path").bind(json_string) {
      Some(path) => path
      None => return Err("Every input requires a string path")
    }
    let text = match json_field(file, "text").bind(json_string) {
      Some(text) => text
      None => return Err("Every input requires UTF-8 decoded text")
    }
    if paths.contains(path) {
      return Err("Duplicate input path: " + path)
    }
    paths.set(path, true)
    let template = match json_field(file, "template") {
      None => false
      Some(value) =>
        match json_bool(value) {
          Some(value) => value
          None => return Err("template must be a boolean")
        }
    }
    result.push({ path, text, template, })
  }
  Ok(result)
}

///|
fn protocol_plural(request : Json, policy : Policy) -> Json {
  let rule_text = match json_field(request, "rule").bind(json_string) {
    Some(text) => text
    None => return protocol_error("plural command requires a rule string")
  }
  let rule = match
    parse_plural_rule(rule_text, maximum_depth=policy.options.expression_depth) {
    Ok(rule) => rule
    Err(error) => return protocol_error(error)
  }
  let coverage = match
    plural_coverage(
      rule,
      maximum=policy.options.plural_max,
      budget=policy.options.expression_budget,
      maximum_depth=policy.options.expression_depth,
    ) {
    Ok(value) => value
    Err(error) => return protocol_error(error)
  }
  let exit_code = if coverage.failures.is_empty() { 0 } else { 1 }
  let lines : Array[String] = ["nplurals=" + rule.nplurals.to_string()]
  for i, form in coverage.reached {
    lines.push(
      "form " +
      form.to_string() +
      " reached at n=" +
      coverage.examples[i].to_string(),
    )
  }
  for failure in coverage.failures {
    lines.push(failure)
  }
  lines.push(
    "Checked through n=" +
    coverage.checked_through.to_string() +
    "; requested maximum=" +
    coverage.maximum.to_string() +
    "; finite sampling only.",
  )
  {
    "exit_code": Json(exit_code),
    "text": Json(join_strings(lines, "\n") + "\n"),
    "data": {
      "schema_version": Json(1),
      "nplurals": Json(rule.nplurals),
      "expression": Json(rule.expression),
      "maximum": Json(coverage.maximum),
      "checked_through": Json(coverage.checked_through),
      "reached": Json(coverage.reached),
      "examples": Json(coverage.examples),
      "failures": Json(coverage.failures),
      "exit_code": Json(exit_code),
    },
  }
}

///|
fn run_request(request : Json) -> Json {
  if !(request is Object(_)) {
    return protocol_error("Request must be a JSON object")
  }
  let command = match json_field(request, "command") {
    None => "check"
    Some(String(command)) => command
    _ => return protocol_error("command must be a string")
  }
  let mut project = default_project_policy()
  if json_field(request, "config") is Some(value) {
    let text = match value {
      String(text) => text
      _ => return protocol_error("config must be a JSON text string")
    }
    project = match parse_project_policy(text) {
      Ok(project) => project
      Err(error) => return protocol_error(error)
    }
  }
  if json_field(request, "quality") is Some(value) {
    project = match quality_from_json(project, value) {
      Ok(project) => project
      Err(error) => return protocol_error(error)
    }
  }
  let mut policy = project.policy
  if json_field(request, "options") is Some(value) {
    match options_from_json(policy.options, value) {
      Ok(options) => policy = { ..policy, options, }
      Err(error) => return protocol_error(error)
    }
  }
  project = { ..project, policy, }
  if command == "plural" {
    return protocol_plural(request, policy)
  }
  if command == "rules" {
    return rules_response()
  }
  if !["check", "inspect", "normalize", "stats"].contains(command) {
    return protocol_error("Unknown command: " + command)
  }
  let files = match request_files(request) {
    Ok(files) => files
    Err(error) => return protocol_error(error)
  }
  if files.is_empty() {
    return protocol_error("At least one PO/POT file is required")
  }
  if command == "normalize" && files.length() != 1 {
    return protocol_error("normalize accepts exactly one input")
  }
  let template = match json_field(request, "reference") {
    None => None
    Some(value) => {
      let path = json_field(value, "path")
        .bind(json_string)
        .unwrap_or("