///|
/// Fact projection and extraction.
///
/// The pick operation projects a fact record onto an explicit list of
/// dotted paths, producing a nested object containing only those paths.
/// Missing paths are reported separately instead of being silently
/// dropped, and array indexing uses the standard `path.0` numeric
/// segment convention shared with the test traversal helper.

///|
fn pick_at(value : Value, parts : Array[String]) -> Value? {
  let mut current = value
  for part in parts {
    match current {
      Object(fields) =>
        match fields.get(part) {
          Some(next) => current = next
          None => return None
        }
      Array(items) => {
        let index = num(parse_value(part)).to_int() catch { _ => return None }
        if index < 0 || index >= items.length() {
          return None
        }
        current = items[index]
      }
      _ => return None
    }
  }
  Some(current)
}

///|
fn insert_at(
  target : Map[String, Value],
  parts : Array[String],
  value : Value,
) -> Unit {
  if parts.is_empty() {
    return
  }
  if parts.length() == 1 {
    target[parts[0]] = value
    return
  }
  let rest : Array[String] = []
  for i = 1; i < parts.length(); i = i + 1 {
    rest.push(parts[i])
  }
  match target.get(parts[0]) {
    Some(Object(existing)) => insert_at(existing, rest, value)
    _ => {
      let nested : Map[String, Value] = Map([])
      insert_at(nested, rest, value)
      target[parts[0]] = Object(nested)
    }
  }
}

///|
fn split_path(path : String) -> Array[String] {
  let parts : Array[String] = []
  for view in path.split(".") {
    parts.push(view.to_owned())
  }
  parts
}

///|
fn pick_request(request : Value) -> Value raise {
  let fact = get(request, "facts")
  let paths : Array[String] = []
  for item in arr(get(request, "paths")) {
    paths.push(str(item))
  }
  let unique : Map[String, Bool] = Map([])
  let picked : Map[String, Value] = Map([])
  let missing : Array[String] = []
  let order : Array[String] = []
  for path in paths {
    if unique.contains(path) {
      raise input_error("E4002", "duplicate path: " + path)
    }
    unique[path] = true
    order.push(path)
  }
  order.sort_by(fn(a, b) { stable_string_compare(a, b) })
  for path in order {
    let segments = split_path(path)
    match pick_at(fact, segments) {
      Some(value) => insert_at(picked, segments, value)
      None => missing.push(path)
    }
  }
  missing.sort_by(fn(a, b) { stable_string_compare(a, b) })
  record([
    ("picked", Object(picked)),
    ("missing", strings(missing)),
    ("requested", Number(paths.length().to_double())),
  ])
}