///|
fn object(value : Json) -> Map[String, Json] raise {
  match value {
    Object(fields) => fields
    _ => raise @config.Invalid("Expected discovery object")
  }
}

///|
fn required(fields : Map[String, Json], key : String) -> Json raise {
  match fields.get(key) {
    Some(v) => v
    None => raise @config.Invalid("Missing discovery field: " + key)
  }
}

///|
fn string(value : Json) -> String raise {
  match value {
    String(s) => s
    _ => raise @config.Invalid("Expected discovery string")
  }
}

///|
pub fn Tree::from_json(value : Json) -> Tree raise {
  let entries = object(required(object(value), "entries"))
  let files = Map([])
  let dirs = Map::from_array([("", true)])
  for path, entry in entries {
    let normalized = normalize(path)
    if normalized != path && !(path == "." && normalized == "") {
      raise @config.Invalid("Noncanonical discovery path: " + path)
    }
    let fields = object(entry)
    match string(required(fields, "kind")) {
      "file" => files[normalized] = string(required(fields, "text"))
      "directory" => dirs[normalized] = true
      "symlink" =>
        raise @config.Invalid(
          "Protocol v1 requires materialized symlinks: " + path,
        )
      _ => raise @config.Invalid("Unknown discovery entry kind")
    }
  }
  let directories = dirs.keys().collect()
  directories.sort()
  { files, directories, }
}

///|
fn mount_document(value : Json?) -> @config.Document raise {
  match value {
    None | Some(Null) => @config.empty()
    Some(value) => {
      let tree = Tree::from_json(value)
      let loaded = resolve_configuration(
        tree,
        "",
        None,
        ConfigurationOptions::new(isolated=true, profiles=Disabled),
        @config.empty(),
        @config.empty(),
        Map([]),
        @config.empty(),
      )
      loaded.primary
    }
  }
}

///|
/// Discovery protocol 0.1.0. Numeric v1 requests remain readable at this compatibility boundary.
pub fn discover_request(request : Json) -> Workspace raise {
  let fields = object(request)
  match required(fields, "protocolVersion") {
    String("0.1.0") => ()
    Number(n, ..) if n == 1 => ()
    _ => raise @config.Invalid("Unsupported workspace discovery protocol")
  }
  let tree = Tree::from_json(required(fields, "developmentRoot"))
  let environment = Map([])
  if fields.get("environment") is Some(value) {
    for key, value in object(value) {
      environment[key] = string(value)
    }
  }
  let overlay = match fields.get("cliOverlay") {
    None | Some(Null) => @config.empty()
    Some(value) => @config.from_json(value, origin="CLI")
  }
  discover(
    tree,
    system=mount_document(fields.get("systemConfig")),
    global=mount_document(fields.get("morphirHome")),
    environment~,
    overlay~,
  )
}