//! Path queries over a parsed TOML document, in the spirit of `jq`.
//!
//! Supported path syntax (all optional leading `.`):
//!
//! - `.` or empty string: the whole document
//! - `.server.host`: nested table keys
//! - `.ports[0]`: array index
//! - `.matrix[1].name`: mixed nesting
//!
//! A lookup that hits a missing key or an out-of-range index yields `None`.

///|
enum PathSeg {
  Key(String)
  Index(Int)
}

///|
fn parse_path(path : String) -> Array[PathSeg] {
  let segs = Array::new()
  let mut s = match path.strip_prefix(".") {
    Some(rest) => rest.to_owned()
    None => path
  }
  while s.length() > 0 {
    if s.has_prefix("[") {
      let end = s.find("]")
      match end {
        Some(e) => {
          let idx = @string.parse_int(s[1:e].to_owned()) catch {
            _ => abort("invalid array index in path `\{path}`")
          }
          segs.push(Index(idx))
          s = s[e + 1:].to_owned()
        }
        None => abort("unbalanced `[` in path `\{path}`")
      }
    } else {
      let dot = s.find(".")
      let bracket = s.find("[")
      let end = match (dot, bracket) {
        (Some(d), Some(b)) => if d < b { d } else { b }
        (Some(d), None) => d
        (None, Some(b)) => b
        (None, None) => s.length()
      }
      segs.push(Key(s[:end].to_owned()))
      s = s[end:].to_owned()
    }
    // Skip the separator between segments (`.` after a key or `]`).
    s = match s.strip_prefix(".") {
      Some(rest) => rest.to_owned()
      None => s
    }
  }
  segs
}

///|
/// Look up `path` inside `root`; `None` when any segment is missing.
pub fn get_path(
  root : @toml_lib.TomlValue,
  path : String,
) -> @toml_lib.TomlValue? {
  if path.is_empty() || path == "." {
    return Some(root)
  }
  let segs = parse_path(path)
  let mut cur : @toml_lib.TomlValue = root
  for seg in segs {
    match (cur, seg) {
      (TomlTable(table), Key(key)) =>
        match table.get(key) {
          Some(value) => cur = value
          None => return None
        }
      (TomlArray(array), Index(i)) =>
        if i >= 0 && i < array.length() {
          cur = array[i]
        } else {
          return None
        }
      _ => return None
    }
  }
  Some(cur)
}

///|
/// Render a value for `get` output: strings are printed raw (no quotes),
/// everything else uses the TOML serializer.
pub fn render(value : @toml_lib.TomlValue) -> String {
  match value {
    TomlString(s) => s
    _ => value.to_string()
  }
}