///|
/// A reference resolved to its contents by a `Resolver`, along with the
/// resolver to use for resolving any references *within* those contents
/// (which carries the new base URI, the updated registry and the dynamic
/// scope).
pub(all) struct Resolved {
  contents : Json
  resolver : Resolver
}

///|
/// A reference resolver.
///
/// Resolvers help resolve references (including relative ones) by pairing a
/// fixed base URI with a `Registry`.
///
/// This object, under normal circumstances, is expected to be used by
/// *implementers of libraries* built on top of `referencing` (e.g. JSON
/// Schema implementations or other libraries resolving JSON references), not
/// directly by end-users populating registries or while writing schemas or
/// other resources.
///
/// References are resolved against the base URI, and the combined URI is then
/// looked up within the registry.
///
/// The process of resolving a reference may itself involve calculating a
/// *new* base URI for future reference resolution (e.g. if an intermediate
/// resource sets a new base URI), or may involve encountering additional
/// subresources and adding them to a new registry.
pub struct Resolver {
  priv base_uri : String
  priv registry : Registry
  priv previous : @list.List[String]
}

///|
/// Create a resolver (Python's `Resolver(base_uri=..., registry=...)`).
/// Prefer `Registry::resolver`.
pub fn Resolver::new(
  base_uri~ : String,
  registry~ : Registry,
  previous? : @list.List[String] = @list.empty(),
) -> Resolver {
  { base_uri, registry, previous, }
}

///|
/// The base URI against which references are resolved.
pub fn Resolver::base_uri(self : Resolver) -> String {
  self.base_uri
}

///|
/// The registry references are looked up in.
pub fn Resolver::registry(self : Resolver) -> Registry {
  self.registry
}

///|
/// The previous base URIs (most recent first), i.e. the URIs in the dynamic
/// scope (Python's `_previous`).
pub fn Resolver::previous(self : Resolver) -> @list.List[String] {
  self.previous
}

///|
/// Resolve the given reference to the resource it points to.
///
/// Raises (as `ReferencingError`s):
///
/// * `Unresolvable` (or `PointerToNowhere`, `NoSuchAnchor`, `InvalidAnchor`,
///   see `ReferencingError::is_unresolvable`) if the reference isn't
///   resolvable: `NoSuchAnchor` if the reference is to a URI where a resource
///   exists but contains a plain name fragment which does not exist within
///   the resource, `PointerToNowhere` if the reference is to a URI where a
///   resource exists but contains a JSON pointer to a location within the
///   resource that does not exist.
/// * `CannotDetermineSpecification` if a retrieved resource raised it.
///
/// It may also raise `@urllib.ValueError` for malformed URIs, or any error
/// raised by custom specification / anchor callbacks.
pub fn Resolver::lookup(self : Resolver, reference : String) -> Resolved raise {
  let (uri, fragment) = if reference.has_prefix("#") {
    (self.base_uri, reference.view(start_offset=1).to_owned())
  } else {
    let defragged = @urllib.urldefrag(@urllib.urljoin(self.base_uri, reference))
    (defragged.url, defragged.fragment)
  }
  let retrieved = self.registry.get_or_retrieve(uri) catch {
    NoSuchResource(..) | Unretrievable(..) => raise Unresolvable(reference~)
    error => raise error
  }
  if fragment.has_prefix("/") {
    let resolver = self.evolve(registry=retrieved.registry, base_uri=uri)
    return retrieved.value.pointer(fragment, resolver)
  }
  if fragment != "" {
    let retrieved = retrieved.registry.anchor(uri, fragment)
    let resolver = self.evolve(registry=retrieved.registry, base_uri=uri)
    return retrieved.value.resolve(resolver)
  }
  let resolver = self.evolve(registry=retrieved.registry, base_uri=uri)
  { contents: retrieved.value.contents, resolver, }
}

///|
/// Create a resolver for a subresource (which may have a new base URI).
///
/// Returns this very resolver (physically) if the subresource has no ID.
pub fn Resolver::in_subresource(
  self : Resolver,
  subresource : Resource,
) -> Resolver raise @urllib.ValueError {
  match subresource.id() {
    None => self
    Some(id) => { ..self, base_uri: @urllib.urljoin(self.base_uri, id), }
  }
}

///|
/// In specs with such a notion, return the URIs in the dynamic scope (most
/// recent first), each paired with this resolver's registry.
pub fn Resolver::dynamic_scope(self : Resolver) -> Iter[(String, Registry)] {
  self.previous.iter().map(uri => (uri, self.registry))
}

///|
/// Evolve, appending to the dynamic scope.
fn Resolver::evolve(
  self : Resolver,
  base_uri~ : String,
  registry~ : Registry,
) -> Resolver {
  let previous = if self.base_uri != "" &&
    (self.previous.is_empty() || base_uri != self.base_uri) {
    @list.cons(self.base_uri, self.previous)
  } else {
    self.previous
  }
  { base_uri, registry, previous, }
}

///|
/// `Resolver(_base_uri='...', _registry=)`, as Python's `repr`.
pub impl Show for Resolver with fn output(self, logger) {
  logger.write_string(
    "Resolver(_base_uri=\{py_str_repr(self.base_uri)}, _registry=\{self.registry})",
  )
}