// Subresource discovery and pointer-traversal rules for the JSON Schema
// specifications (Python's `referencing.jsonschema` factories).

///|
/// Which "funky" keywords of older drafts a `subresources_of` handles in
/// addition to the plain keyword sets.
priv enum Quirks {
  /// draft 2020-12: none
  NoQuirks
  /// draft 2019-09: `items` may be an array
  CrazyItems
  /// drafts 6 and 7: also `dependencies`
  CrazyItemsDependencies
  /// drafts 3 and 4: also `additionalItems` / `additionalProperties` objects
  CrazyAPItemsDependencies
}

///|
fn make_subresources_of(
  in_value~ : Array[String],
  in_subvalues~ : Array[String],
  in_subarray~ : Array[String],
  quirks~ : Quirks,
) -> (Json) -> Array[Json] {
  contents => {
    guard contents is Object(obj) else { return [] }
    let out = []
    for each in in_value {
      if obj.get(each) is Some(value) {
        out.push(value)
      }
    }
    for each in in_subarray {
      if obj.get(each) is Some(Array(values)) {
        out.append(values)
      }
    }
    for each in in_subvalues {
      if obj.get(each) is Some(Object(values)) {
        for _, value in values {
          out.push(value)
        }
      }
    }
    if quirks is NoQuirks {
      return out
    }
    match obj.get("items") {
      Some(Array(items)) => out.append(items)
      Some(items) => out.push(items)
      None => ()
    }
    if quirks is CrazyItems {
      return out
    }
    if obj.get("dependencies") is Some(Object(dependencies)) {
      // Only the *first* dependency's type is checked, exactly as upstream.
      let values = dependencies.values().to_array()
      if values is [Object(_), ..] {
        out.append(values)
      }
    }
    if quirks is CrazyAPItemsDependencies {
      for each in ["additionalItems", "additionalProperties"] {
        if obj.get(each) is Some(Object(_) as value) {
          out.push(value)
        }
      }
    }
    out
  }
}

///|
/// Which keywords' values always enter a subresource while following a JSON
/// pointer, in addition to the plain keyword sets.
priv enum PointerQuirks {
  /// draft 2020-12
  NoPointerQuirks
  /// draft 2019-09: `items` into an object enters a subresource
  ItemsPointer
  /// drafts 3 to 7: `items` / `dependencies` into an object
  ItemsDependenciesPointer
}

///|
fn make_maybe_in_subresource(
  in_value~ : Array[String],
  in_subvalues~ : Array[String],
  in_subarray~ : Array[String],
  quirks~ : PointerQuirks,
) -> (Array[Segment], Resolver, Resource) -> Resolver raise {
  let in_child = [..in_subvalues, ..in_subarray]
  (segments, resolver, subresource) => {
    let is_object = subresource.contents is Object(_)
    let mut i = 0
    while i < segments.length() {
      let segment = segments[i]
      i += 1
      let entering = match quirks {
        NoPointerQuirks => false
        ItemsPointer => segment.is_key("items")
        ItemsDependenciesPointer =>
          segment.is_key("items") || segment.is_key("dependencies")
      }
      if entering && is_object {
        return resolver.in_subresource(subresource)
      }
      let in_value_ = in_value.iter().any(k => segment.is_key(k))
      if !in_value_ {
        let in_child_ = in_child.iter().any(k => segment.is_key(k))
        if !in_child_ {
          return resolver
        }
        // `next(_segments, None) is None`: consume the child's key/index.
        if i >= segments.length() {
          return resolver
        }
        i += 1
      }
    }
    resolver.in_subresource(subresource)
  }
}