Json nav

class val JsonNav
  """
  Chained navigation wrapper for JSON values.

  Wraps a JsonValue and provides safe chained access where JsonNotFound propagates
  through the chain — no exceptions until you extract a typed terminal value.

  ```pony
  let nav = JsonNav(json)
  try
    let name = nav("user")("name").as_string()?
    let age = nav("user")("age").as_i64()?
  end
  ```
  """

  let _value: (JsonValue | JsonNotFound)

  new val create(value: JsonValue) =>
    """Wrap a JSON value for navigation."""
    _value = value

  new val _from(value: (JsonValue | JsonNotFound)) =>
    """Internal: wrap a value that may already be JsonNotFound."""
    _value = value

  fun apply(key_or_index: (String | USize)): JsonNav =>
    """
    Navigate into an object by key or array by index.

    If the current value is JsonNotFound, the wrong type, or the key/index
    is missing, returns a JsonNotFound-wrapping nav. JsonNotFound propagates
    through subsequent navigations.
    """
    match (_value, key_or_index)
    | (let obj: JsonObject, let key: String) =>
      try JsonNav._from(obj(key)?)
      else JsonNav._from(JsonNotFound)
      end
    | (let arr: JsonArray, let idx: USize) =>
      try JsonNav._from(arr(idx)?)
      else JsonNav._from(JsonNotFound)
      end
    else
      JsonNav._from(JsonNotFound)
    end

  // --- Terminal extractors ---

  fun as_string(): String ? =>
    """Extract as String. Raises if not a string or JsonNotFound."""
    _value as String

  fun as_i64(): I64 ? =>
    """Extract as I64. Raises if not an integer or JsonNotFound."""
    _value as I64

  fun as_f64(): F64 ? =>
    """Extract as F64. Raises if not a float or JsonNotFound."""
    _value as F64

  fun as_bool(): Bool ? =>
    """Extract as Bool. Raises if not a boolean or JsonNotFound."""
    _value as Bool

  fun as_null(): None ? =>
    """Extract as None (JSON null). Raises if not null or JsonNotFound."""
    _value as None

  fun as_object(): JsonObject ? =>
    """Extract as JsonObject. Raises if not an object or JsonNotFound."""
    _value as JsonObject

  fun as_array(): JsonArray ? =>
    """Extract as JsonArray. Raises if not an array or JsonNotFound."""
    _value as JsonArray

  // --- Inspection ---

  fun json(): (JsonValue | JsonNotFound) =>
    """Get the raw value for pattern matching."""
    _value

  fun found(): Bool =>
    """Check whether navigation succeeded (value is not JsonNotFound)."""
    _value isnt JsonNotFound

  fun size(): USize ? =>
    """Size of the wrapped collection. Raises if not an object or array."""
    match _value
    | let obj: JsonObject => obj.size()
    | let arr: JsonArray => arr.size()
    else error
    end