///|
/// URIs, URI references, IRIs and IRI references.

///|
/// The internal, encoder-agnostic form of a URI/IRI (reference).
///
/// `text` always equals the concatenation of the components, so that the four
/// public types can cache it instead of rebuilding it on every use.
priv struct Ri {
  text : String
  scheme : Scheme?
  authority : IAuth?
  path : String
  query : String?
  fragment : String?
}

///|
/// Rebuilds the text of a URI/IRI (reference) from its components.
fn ri_text(
  scheme : Scheme?,
  authority : IAuth?,
  path : String,
  query : String?,
  fragment : String?,
) -> String {
  let sb = StringBuilder::new(size_hint=path.length() + 32)
  if scheme is Some(s) {
    sb.write_string(s.as_str())
    sb.write_char(':')
  }
  if authority is Some(a) {
    sb.write_string("//")
    sb.write_string(a.as_str())
  }
  sb.write_string(path)
  if query is Some(q) {
    sb.write_char('?')
    sb.write_string(q)
  }
  if fragment is Some(f) {
    sb.write_char('#')
    sb.write_string(f)
  }
  sb.to_string()
}

///|
/// Creates a `Ri` from components, computing its text.
fn ri(
  scheme? : Scheme? = None,
  authority? : IAuth? = None,
  path~ : String,
  query? : String? = None,
  fragment? : String? = None,
) -> Ri {
  {
    text: ri_text(scheme, authority, path, query, fragment),
    scheme,
    authority,
    path,
    query,
    fragment,
  }
}

///|
/// Returns the text up to, but not including, the `'#'` of the fragment.
fn Ri::strip_fragment_text(self : Ri) -> String {
  match self.fragment {
    Some(f) => slice(self.text, 0, self.text.length() - f.length() - 1)
    None => self.text
  }
}

///|
/// Replaces the fragment. Altering only the fragment does not change the
/// other components.
fn Ri::set_fragment(self : Ri, fragment : String?) -> Ri {
  let stripped = self.strip_fragment_text()
  let text = match fragment {
    Some(f) => stripped + "#" + f
    None => stripped
  }
  { ..self, text, fragment }
}

// -----------------------------------------------------------------------
// Uri
// -----------------------------------------------------------------------

///|
/// A URI.
///
/// # Examples
///
/// ```mbt check
/// test {
///   let uri = @uri.Uri::parse(
///     "foo://user@example.com:8042/over/there?name=ferret#nose",
///   )
///   assert_eq(uri.scheme, @uri.Scheme::new_or_panic("foo"))
///   let auth = uri.authority.unwrap()
///   assert_eq(auth.as_str(), "user@example.com:8042")
///   assert_eq(auth.userinfo.unwrap().as_str(), "user")
///   assert_eq(auth.host, "example.com")
///   assert_eq(auth.port.unwrap().as_str(), "8042")
///   assert_eq(uri.path.as_str(), "/over/there")
///   assert_eq(uri.query.unwrap().as_str(), "name=ferret")
///   assert_eq(uri.fragment.unwrap().as_str(), "nose")
/// }
/// ```
pub struct Uri {
  /// The URI as a string.
  text : String
  /// The [scheme] component.
  ///
  /// Note that the scheme component is *case-insensitive* and its canonical
  /// form is *lowercase*.
  ///
  /// [scheme]: https://datatracker.ietf.org/doc/html/rfc3986#section-3.1
  scheme : Scheme
  /// The optional [authority] component.
  ///
  /// [authority]: https://datatracker.ietf.org/doc/html/rfc3986#section-3.2
  authority : Authority[@enc.Userinfo, @enc.RegName]?
  /// The [path] component, which is always present but may be empty.
  ///
  /// [path]: https://datatracker.ietf.org/doc/html/rfc3986#section-3.3
  path : EStr[@enc.Path]
  /// The optional [query] component.
  ///
  /// [query]: https://datatracker.ietf.org/doc/html/rfc3986#section-3.4
  query : EStr[@enc.Query]?
  /// The optional [fragment] component.
  ///
  /// [fragment]: https://datatracker.ietf.org/doc/html/rfc3986#section-3.5
  fragment : EStr[@enc.Fragment]?
}

///|
/// A URI reference, i.e., either a URI or a relative reference.
pub struct UriRef {
  /// The URI reference as a string.
  text : String
  /// The optional [scheme] component.
  ///
  /// [scheme]: https://datatracker.ietf.org/doc/html/rfc3986#section-3.1
  scheme : Scheme?
  /// The optional [authority] component.
  authority : Authority[@enc.Userinfo, @enc.RegName]?
  /// The [path] component, which is always present but may be empty.
  path : EStr[@enc.Path]
  /// The optional [query] component.
  query : EStr[@enc.Query]?
  /// The optional [fragment] component.
  fragment : EStr[@enc.Fragment]?
}

///|
/// An IRI, i.e., an internationalized URI which may contain non-ASCII
/// characters.
pub struct Iri {
  /// The IRI as a string.
  text : String
  /// The [scheme] component.
  ///
  /// [scheme]: https://datatracker.ietf.org/doc/html/rfc3986#section-3.1
  scheme : Scheme
  /// The optional [authority] component.
  authority : Authority[@enc.IUserinfo, @enc.IRegName]?
  /// The [path] component, which is always present but may be empty.
  path : EStr[@enc.IPath]
  /// The optional [query] component.
  query : EStr[@enc.IQuery]?
  /// The optional [fragment] component.
  fragment : EStr[@enc.IFragment]?
}

///|
/// An IRI reference, i.e., either an IRI or a relative reference.
pub struct IriRef {
  /// The IRI reference as a string.
  text : String
  /// The optional [scheme] component.
  scheme : Scheme?
  /// The optional [authority] component.
  authority : Authority[@enc.IUserinfo, @enc.IRegName]?
  /// The [path] component, which is always present but may be empty.
  path : EStr[@enc.IPath]
  /// The optional [query] component.
  query : EStr[@enc.IQuery]?
  /// The optional [fragment] component.
  fragment : EStr[@enc.IFragment]?
}

// Conversions between the public types and the internal form.

///|
fn Uri::to_ri(self : Uri) -> Ri {
  {
    text: self.text,
    scheme: Some(self.scheme),
    authority: opt_map(self.authority, a => a.cast()),
    path: self.path.as_str(),
    query: opt_map(self.query, v => v.as_str()),
    fragment: opt_map(self.fragment, v => v.as_str()),
  }
}

///|
fn UriRef::to_ri(self : UriRef) -> Ri {
  {
    text: self.text,
    scheme: self.scheme,
    authority: opt_map(self.authority, a => a.cast()),
    path: self.path.as_str(),
    query: opt_map(self.query, v => v.as_str()),
    fragment: opt_map(self.fragment, v => v.as_str()),
  }
}

///|
fn Iri::to_ri(self : Iri) -> Ri {
  {
    text: self.text,
    scheme: Some(self.scheme),
    authority: self.authority,
    path: self.path.as_str(),
    query: opt_map(self.query, v => v.as_str()),
    fragment: opt_map(self.fragment, v => v.as_str()),
  }
}

///|
fn IriRef::to_ri(self : IriRef) -> Ri {
  {
    text: self.text,
    scheme: self.scheme,
    authority: self.authority,
    path: self.path.as_str(),
    query: opt_map(self.query, v => v.as_str()),
    fragment: opt_map(self.fragment, v => v.as_str()),
  }
}

///|
/// Builds a `Uri` from the internal form, which must have a scheme.
fn uri_of_ri(r : Ri) -> Uri {
  {
    text: r.text,
    scheme: r.scheme.unwrap(),
    authority: opt_map(r.authority, a => a.cast()),
    path: estr(r.path),
    query: opt_map(r.query, estr),
    fragment: opt_map(r.fragment, estr),
  }
}

///|
fn uri_ref_of_ri(r : Ri) -> UriRef {
  {
    text: r.text,
    scheme: r.scheme,
    authority: opt_map(r.authority, a => a.cast()),
    path: estr(r.path),
    query: opt_map(r.query, estr),
    fragment: opt_map(r.fragment, estr),
  }
}

///|
/// Builds an `Iri` from the internal form, which must have a scheme.
fn iri_of_ri(r : Ri) -> Iri {
  {
    text: r.text,
    scheme: r.scheme.unwrap(),
    authority: r.authority,
    path: estr(r.path),
    query: opt_map(r.query, estr),
    fragment: opt_map(r.fragment, estr),
  }
}

///|
fn iri_ref_of_ri(r : Ri) -> IriRef {
  {
    text: r.text,
    scheme: r.scheme,
    authority: r.authority,
    path: estr(r.path),
    query: opt_map(r.query, estr),
    fragment: opt_map(r.fragment, estr),
  }
}

///|
fn[A, B] opt_map(x : A?, f : (A) -> B) -> B? {
  match x {
    Some(v) => Some(f(v))
    None => None
  }
}

// Parsing.

///|
/// Parses a URI from a string, matching the [`URI`][abnf] ABNF rule
/// from RFC 3986.
///
/// [abnf]: https://datatracker.ietf.org/doc/html/rfc3986#section-3
///
/// ```mbt check
/// test {
///   let uri = @uri.Uri::parse("http://example.com/")
///   inspect(uri, content="http://example.com/")
/// }
/// ```
pub fn Uri::parse(s : String) -> Uri raise ParseError {
  uri_of_ri(parse_ri(s, ascii_only=true, scheme_required=true))
}

///|
/// Parses a URI reference from a string, matching the [`URI-reference`][abnf]
/// ABNF rule from RFC 3986.
///
/// [abnf]: https://datatracker.ietf.org/doc/html/rfc3986#section-4.1
pub fn UriRef::parse(s : String) -> UriRef raise ParseError {
  uri_ref_of_ri(parse_ri(s, ascii_only=true, scheme_required=false))
}

///|
/// Parses an IRI from a string, matching the [`IRI`][abnf] ABNF rule
/// from RFC 3987.
///
/// [abnf]: https://datatracker.ietf.org/doc/html/rfc3987#section-2.2
pub fn Iri::parse(s : String) -> Iri raise ParseError {
  iri_of_ri(parse_ri(s, ascii_only=false, scheme_required=true))
}

///|
/// Parses an IRI reference from a string, matching the [`IRI-reference`][abnf]
/// ABNF rule from RFC 3987.
///
/// [abnf]: https://datatracker.ietf.org/doc/html/rfc3987#section-2.2
pub fn IriRef::parse(s : String) -> IriRef raise ParseError {
  iri_ref_of_ri(parse_ri(s, ascii_only=false, scheme_required=false))
}

// Common instances.

///|
pub impl Show for Uri with fn output(self, logger) {
  logger.write_string(self.text)
}

///|
pub impl Show for UriRef with fn output(self, logger) {
  logger.write_string(self.text)
}

///|
pub impl Show for Iri with fn output(self, logger) {
  logger.write_string(self.text)
}

///|
pub impl Show for IriRef with fn output(self, logger) {
  logger.write_string(self.text)
}

///|
pub impl Debug for Uri with fn to_repr(self) {
  Repr(self.text)
}

///|
pub impl Debug for UriRef with fn to_repr(self) {
  Repr(self.text)
}

///|
pub impl Debug for Iri with fn to_repr(self) {
  Repr(self.text)
}

///|
pub impl Debug for IriRef with fn to_repr(self) {
  Repr(self.text)
}

///|
pub impl Eq for Uri with fn equal(self, other) {
  self.text == other.text
}

///|
pub impl Eq for UriRef with fn equal(self, other) {
  self.text == other.text
}

///|
pub impl Eq for Iri with fn equal(self, other) {
  self.text == other.text
}

///|
pub impl Eq for IriRef with fn equal(self, other) {
  self.text == other.text
}

///|
pub impl Compare for Uri with fn compare(self, other) {
  self.text.compare(other.text)
}

///|
pub impl Compare for UriRef with fn compare(self, other) {
  self.text.compare(other.text)
}

///|
pub impl Compare for Iri with fn compare(self, other) {
  self.text.compare(other.text)
}

///|
pub impl Compare for IriRef with fn compare(self, other) {
  self.text.compare(other.text)
}

///|
pub impl Hash for Uri with fn hash_combine(self, hasher) {
  hasher.combine_string(self.text)
}

///|
pub impl Hash for UriRef with fn hash_combine(self, hasher) {
  hasher.combine_string(self.text)
}

///|
pub impl Hash for Iri with fn hash_combine(self, hasher) {
  hasher.combine_string(self.text)
}

///|
pub impl Hash for IriRef with fn hash_combine(self, hasher) {
  hasher.combine_string(self.text)
}

// @enc.Fragment manipulation.

///|
/// Returns the URI with its fragment removed.
///
/// ```mbt check
/// test {
///   let uri = @uri.Uri::parse("http://example.com/#title")
///   inspect(uri.strip_fragment(), content="http://example.com/")
/// }
/// ```
pub fn Uri::strip_fragment(self : Uri) -> Uri {
  uri_of_ri(self.to_ri().set_fragment(None))
}

///|
/// Returns the URI reference with its fragment removed.
pub fn UriRef::strip_fragment(self : UriRef) -> UriRef {
  uri_ref_of_ri(self.to_ri().set_fragment(None))
}

///|
/// Returns the IRI with its fragment removed.
pub fn Iri::strip_fragment(self : Iri) -> Iri {
  iri_of_ri(self.to_ri().set_fragment(None))
}

///|
/// Returns the IRI reference with its fragment removed.
pub fn IriRef::strip_fragment(self : IriRef) -> IriRef {
  iri_ref_of_ri(self.to_ri().set_fragment(None))
}

///|
/// Returns the URI with its fragment replaced by `fragment`.
pub fn Uri::with_fragment(self : Uri, fragment : EStr[@enc.Fragment]?) -> Uri {
  uri_of_ri(self.to_ri().set_fragment(opt_map(fragment, v => v.as_str())))
}

///|
/// Returns the URI reference with its fragment replaced by `fragment`.
pub fn UriRef::with_fragment(
  self : UriRef,
  fragment : EStr[@enc.Fragment]?,
) -> UriRef {
  uri_ref_of_ri(self.to_ri().set_fragment(opt_map(fragment, v => v.as_str())))
}

///|
/// Returns the IRI with its fragment replaced by `fragment`.
pub fn Iri::with_fragment(self : Iri, fragment : EStr[@enc.IFragment]?) -> Iri {
  iri_of_ri(self.to_ri().set_fragment(opt_map(fragment, v => v.as_str())))
}

///|
/// Returns the IRI reference with its fragment replaced by `fragment`.
pub fn IriRef::with_fragment(
  self : IriRef,
  fragment : EStr[@enc.IFragment]?,
) -> IriRef {
  iri_ref_of_ri(self.to_ri().set_fragment(opt_map(fragment, v => v.as_str())))
}