// One error type for every syntax in this package.
//
// The alternative -- an error type per identifier, as @atproto/syntax does with
// its ten Error subclasses -- buys a caller nothing here, because MoonBit
// already forces the raise type into the signature: a function returning
// `Did raise SyntaxError` cannot fail any other way, so the kind is redundant
// at the call site and useful only when errors are collected and reported
// together, which is exactly what `kind` is for.
//
// `reason` is the upstream message, unchanged. That is a deliberate constraint,
// not laziness: it means a test can assert the message and thereby pin that
// this implementation rejects for the same reason the reference one does, not
// merely that it also rejects.

///|
/// Which syntax a value failed to satisfy.
pub(all) enum SyntaxKind {
  Did
  Handle
  AtIdentifier
  Nsid
  RecordKey
  Tid
  AtUri
  Datetime
  Language
  Uri
  Cid
} derive(Eq, Debug)

///|
/// The name used in error messages, matching the reference implementation's
/// spelling -- "ATURI" and "NSID" are shouted upstream, so they are here.
pub fn SyntaxKind::label(self : Self) -> String {
  match self {
    Did => "DID"
    Handle => "handle"
    AtIdentifier => "at-identifier"
    Nsid => "NSID"
    RecordKey => "record key"
    Tid => "TID"
    AtUri => "ATURI"
    Datetime => "datetime"
    Language => "language"
    Uri => "URI"
    Cid => "CID"
  }
}

///|
pub(all) suberror SyntaxError {
  SyntaxError(kind~ : SyntaxKind, input~ : String, reason~ : String)
} derive(Eq, Debug)

///|
pub fn SyntaxError::kind(self : Self) -> SyntaxKind {
  match self {
    SyntaxError(kind~, ..) => kind
  }
}

///|
/// The upstream message, on its own. Callers rendering their own text want this
/// rather than `describe_error`, which adds the offending input.
pub fn SyntaxError::reason(self : Self) -> String {
  match self {
    SyntaxError(reason~, ..) => reason
  }
}

///|
pub fn SyntaxError::input(self : Self) -> String {
  match self {
    SyntaxError(input~, ..) => input
  }
}

///|
/// The input is truncated: a DID may be 2048 characters and an AT-URI 8192, and
/// a validation failure that floods a log is its own bug.
pub fn SyntaxError::describe_error(self : Self) -> String {
  match self {
    SyntaxError(input~, reason~, ..) => {
      let shown = if input.length() > 80 {
        input[:80].to_owned() + "..."
      } else {
        input
      }
      "\{reason} (got: \{shown})"
    }
  }
}

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