///|
pub suberror JsonPathError {
  InvalidJsonPath(String)
} derive(Debug)

///|
pub extend JsonPathError with Debug::{to_repr}

///|
priv enum PathToken {
  Root
  Current
  Member(String)
  Index(Int32)
  Wildcard
  Descend
  Slice(UInt32, UInt32, UInt32)
  Members(Array[String])
  Indices(Array[Int32])
}

///|
/// The JSONPath subset supported by Yrs: members, indices, wildcards, recursive
/// descent, slices and unions. Predicate expressions are not supported.
pub struct JsonPath {
  priv tokens : Array[PathToken]
}

///|
fn path_in_ranges(c : Char, ranges : ReadOnlyArray[(Int32, Int32)]) -> Bool {
  let value = c.to_int()
  let mut lo = 0
  let mut hi = ranges.length()
  while lo < hi {
    let mid = lo + (hi - lo) / 2
    let (start, end) = ranges[mid]
    if value < start {
      hi = mid
    } else if value >= end {
      lo = mid + 1
    } else {
      return true
    }
  }
  false
}

///|
fn path_alpha(c : Char) -> Bool {
  path_in_ranges(c, path_alphabetic_ranges)
}

///|
fn path_alnum(c : Char) -> Bool {
  path_alpha(c) || path_in_ranges(c, path_numeric_ranges)
}

///|
fn path_string(chars : Array[Char], start : Int32, end : Int32) -> String {
  let out = StringBuilder()
  for i in start.. Int64? {
  let chars = s.iter().to_array()
  if chars.is_empty() {
    return None
  }
  let negative = chars[0] == '-'
  let start = if negative || chars[0] == '+' { 1 } else { 0 }
  if start == chars.length() {
    return None
  }
  let mut number = 0L
  for i in start.. 0xffff_ffffL {
      return None
    }
  }
  Some(if negative { -number } else { number })
}

///|
fn path_index(s : String) -> Int32? {
  match path_number(s) {
    Some(n) if n >= -2147483648L && n <= 2147483647L => Some(n.to_int())
    _ => None
  }
}

///|
fn path_unsigned(s : String, fallback : UInt32) -> UInt32 {
  match path_number(s) {
    Some(n) if n >= 0L => n.reinterpret_as_uint64().to_uint()
    _ => fallback
  }
}

///|
fn path_bracket(s : String) -> PathToken raise JsonPathError {
  if s == "*" {
    return Wildcard
  }
  if path_index(s) is Some(index) {
    return Index(index)
  }
  if s.has_prefix("?") {
    raise InvalidJsonPath("JSONPath predicates are not supported")
  }
  if s.contains(":") {
    let parts = s.split(":").map(fn(p) { p.to_owned() }).to_array()
    let start = path_unsigned(parts[0], 0U)
    let end = path_unsigned(parts.get(1).unwrap_or(""), 0xffff_ffffU)
    let step = path_unsigned(parts.get(2).unwrap_or(""), 1U)
    guard step > 0U else {
      raise InvalidJsonPath("slice step must be positive")
    }
    return Slice(start, end, step)
  }
  if s.contains(",") {
    let parts = s.split(",").map(fn(p) { p.trim().to_owned() }).to_array()
    if parts[0].has_prefix("'") && parts[0].has_suffix("'") {
      let members = []
      for part in parts {
        guard part.length() >= 2 && part.has_prefix("'") && part.has_suffix("'") else {
          raise InvalidJsonPath("unpaired member quote")
        }
        members.push(part[1:part.length() - 1].to_owned())
      }
      return Members(members)
    }
    let indices = []
    for part in parts {
      guard path_index(part) is Some(index) else {
        raise InvalidJsonPath("invalid index union")
      }
      indices.push(index)
    }
    return Indices(indices)
  }
  if s.length() >= 2 && s.has_prefix("'") && s.has_suffix("'") {
    return Member(s[1:s.length() - 1].to_owned())
  }
  raise InvalidJsonPath("unsupported bracket expression")
}

///|
pub fn JsonPath::parse(path : String) -> JsonPath raise JsonPathError {
  let chars = path.iter().to_array()
  let tokens : Array[PathToken] = []
  let mut i = 0
  while i < chars.length() {
    let c = chars[i]
    i += 1
    match c {
      '$' => tokens.push(Root)
      '@' => tokens.push(Current)
      '*' => tokens.push(Wildcard)
      '.' => {
        guard i < chars.length() else {
          raise InvalidJsonPath("path cannot end with '.'")
        }
        if chars[i] == '.' {
          tokens.push(Descend)
          i += 1
        } else if chars[i] == '*' {
          tokens.push(Wildcard)
          i += 1
        } else if path_alpha(chars[i]) {
          let start = i
          while i < chars.length() && (path_alnum(chars[i]) || chars[i] == '_') {
            i += 1
          }
          tokens.push(Member(path_string(chars, start, i)))
        } else {
          raise InvalidJsonPath("invalid member name")
        }
      }
      '[' => {
        let start = i
        let mut quoted = false
        let mut nesting = 1
        while i < chars.length() {
          let c = chars[i]
          if c == '[' {
            nesting += 1
          }
          if c == ']' {
            nesting -= 1
            if nesting == 0 && !quoted {
              break
            }
          }
          if c == '\'' {
            quoted = !quoted
          }
          i += 1
        }
        guard i < chars.length() && !quoted else {
          raise InvalidJsonPath("unclosed bracket expression")
        }
        tokens.push(path_bracket(path_string(chars, start, i)))
        i += 1
      }
      _ =>
        if path_alpha(c) {
          let start = i - 1
          while i < chars.length() && (path_alnum(chars[i]) || chars[i] == '_') {
            i += 1
          }
          tokens.push(Member(path_string(chars, start, i)))
        } else {
          raise InvalidJsonPath("invalid path character")
        }
    }
  }
  { tokens, }
}