///|
/// Structured errors for the moon-webfinger library.
///
/// Every public API in this package converts failures into
/// `Result[_, WebFingerError]`; nothing panics and nothing propagates a
/// bare `Error` to callers. Internally the package raises a single
/// `WebFingerError` suberror and converts it at each public boundary.
///
/// Errors carry four components:
///
/// * `stage` — which processing area produced the error,
/// * `kind` — the concrete error category,
/// * `offset` — a precise UTF-8 byte offset when one is known, otherwise
///   `None` (the underlying JSON parser does not expose byte offsets, so
///   JSON syntax errors report `None` rather than a fabricated offset),
/// * `context` — a human-readable, one-line description.
///
/// The `WebFingerErrorStage` and `WebFingerErrorKind` enums are read-only
/// for consumer packages: consumers compare them through
/// `stage_name()` / `kind_name()` and `to_string()`.

///|
/// The processing area that produced a `WebFingerError`.
pub enum WebFingerErrorStage {
  Input
  Json
  Request
  Uri
  Jrd
  Subject
  Alias
  Property
  Link
  Context
  Limit
  Builder
}

///|
pub impl Show for WebFingerErrorStage with fn to_string(self) -> String {
  match self {
    Input => "Input"
    Json => "Json"
    Request => "Request"
    Uri => "Uri"
    Jrd => "Jrd"
    Subject => "Subject"
    Alias => "Alias"
    Property => "Property"
    Link => "Link"
    Context => "Context"
    Limit => "Limit"
    Builder => "Builder"
  }
}

///|
/// The concrete error category of a `WebFingerError`.
pub enum WebFingerErrorKind {
  InvalidJson
  RootNotObject
  MissingResource
  InvalidResource
  InvalidUri
  InvalidPercentEncoding
  WrongMemberType
  MissingRequiredMember
  InvalidLink
  InvalidProperty
  InvalidContentType
  LimitExceeded
  DuplicateReservedExtension
  InvalidRelValue
  InvalidLanguageTag
  InvalidAcctUri
  InvalidOrigin
  NonHttpsOrigin
  InvalidHttpStatus
  NonHttpsFinalUrl
  EmptyInput
}

///|
pub impl Show for WebFingerErrorKind with fn to_string(self) -> String {
  match self {
    InvalidJson => "InvalidJson"
    RootNotObject => "RootNotObject"
    MissingResource => "MissingResource"
    InvalidResource => "InvalidResource"
    InvalidUri => "InvalidUri"
    InvalidPercentEncoding => "InvalidPercentEncoding"
    WrongMemberType => "WrongMemberType"
    MissingRequiredMember => "MissingRequiredMember"
    InvalidLink => "InvalidLink"
    InvalidProperty => "InvalidProperty"
    InvalidContentType => "InvalidContentType"
    LimitExceeded => "LimitExceeded"
    DuplicateReservedExtension => "DuplicateReservedExtension"
    InvalidRelValue => "InvalidRelValue"
    InvalidLanguageTag => "InvalidLanguageTag"
    InvalidAcctUri => "InvalidAcctUri"
    InvalidOrigin => "InvalidOrigin"
    NonHttpsOrigin => "NonHttpsOrigin"
    InvalidHttpStatus => "InvalidHttpStatus"
    NonHttpsFinalUrl => "NonHttpsFinalUrl"
    EmptyInput => "EmptyInput"
  }
}

///|
/// The single structured error type of this package.
pub suberror WebFingerError {
  WebFingerError(WebFingerErrorStage, WebFingerErrorKind, Int?, String)
}

///|
/// The processing area of this error.
pub fn WebFingerError::stage(self : WebFingerError) -> WebFingerErrorStage {
  match self {
    WebFingerError(stage, _, _, _) => stage
  }
}

///|
/// The concrete category of this error.
pub fn WebFingerError::kind(self : WebFingerError) -> WebFingerErrorKind {
  match self {
    WebFingerError(_, kind, _, _) => kind
  }
}

///|
/// A precise UTF-8 byte offset into the original input, or `None` when the
/// failing component cannot report one (notably JSON syntax errors).
pub fn WebFingerError::offset(self : WebFingerError) -> Int? {
  match self {
    WebFingerError(_, _, offset, _) => offset
  }
}

///|
/// A human-readable, one-line description of the failure.
pub fn WebFingerError::context(self : WebFingerError) -> String {
  match self {
    WebFingerError(_, _, _, context) => context
  }
}

///|
/// The stage name, for consumer packages that cannot match the enum.
pub fn WebFingerError::stage_name(self : WebFingerError) -> String {
  Show::to_string(self.stage())
}

///|
/// The kind name, for consumer packages that cannot match the enum.
pub fn WebFingerError::kind_name(self : WebFingerError) -> String {
  Show::to_string(self.kind())
}

///|
/// A single-line rendering like
/// `Uri error InvalidUri: not an absolute URI (offset: None)`.
pub fn WebFingerError::to_display(self : WebFingerError) -> String {
  match self.offset() {
    Some(n) =>
      "\{self.stage_name()} error \{self.kind_name()}: \{self.context()} (offset: \{n})"
    None =>
      "\{self.stage_name()} error \{self.kind_name()}: \{self.context()} (offset: None)"
  }
}

///|
/// Internal: unwrap a caught `Error` back into `WebFingerError`. Every
/// public boundary in this package only ever raises `WebFingerError`; a
/// foreign error reaching this point is an internal bug.
fn unwrap_webfinger_error(e : Error) -> WebFingerError {
  match e {
    WebFingerError(stage, kind, offset, context) =>
      WebFingerError(stage, kind, offset, context)
    _ =>
      abort(
        "internal error: unexpected non-WebFingerError propagated to a Result boundary",
      )
  }
}