// Structured errors for public parser and matcher boundaries.

///|
pub(all) enum RobotsErrorStage {
  Input
  Line
  UserAgent
  Rule
  Pattern
  Utf8
  Limit
} derive(Eq)

///|
pub(all) enum RobotsErrorKind {
  EmptyInput
  MissingColon
  EmptyUserAgent
  InvalidUserAgent
  InvalidRecordName
  InvalidPattern
  InvalidPercentEncoding
  InvalidUtf8
  LimitExceeded
  RuleBeforeFirstGroup
  ControlCharacter
} derive(Eq)

///|
pub(all) suberror RobotsError {
  RobotsError(RobotsErrorStage, RobotsErrorKind, Int, Int, String)
}

///|
pub fn robots_error(
  stage : RobotsErrorStage,
  kind : RobotsErrorKind,
  line : Int,
  byte_offset : Int,
  context : String,
) -> RobotsError {
  RobotsError(stage, kind, line, byte_offset, bound_context(context))
}

///|
pub fn RobotsError::stage(self : RobotsError) -> RobotsErrorStage {
  match self {
    RobotsError(stage, _, _, _, _) => stage
  }
}

///|
pub fn RobotsError::kind(self : RobotsError) -> RobotsErrorKind {
  match self {
    RobotsError(_, kind, _, _, _) => kind
  }
}

///|
pub fn RobotsError::line(self : RobotsError) -> Int {
  match self {
    RobotsError(_, _, line, _, _) => line
  }
}

///|
pub fn RobotsError::byte_offset(self : RobotsError) -> Int {
  match self {
    RobotsError(_, _, _, offset, _) => offset
  }
}

///|
pub fn RobotsError::context(self : RobotsError) -> String {
  match self {
    RobotsError(_, _, _, _, context) => context
  }
}

///|
pub fn RobotsErrorStage::to_string(self : RobotsErrorStage) -> String {
  match self {
    Input => "Input"
    Line => "Line"
    UserAgent => "UserAgent"
    Rule => "Rule"
    Pattern => "Pattern"
    Utf8 => "Utf8"
    Limit => "Limit"
  }
}

///|
pub fn RobotsErrorKind::to_string(self : RobotsErrorKind) -> String {
  match self {
    EmptyInput => "EmptyInput"
    MissingColon => "MissingColon"
    EmptyUserAgent => "EmptyUserAgent"
    InvalidUserAgent => "InvalidUserAgent"
    InvalidRecordName => "InvalidRecordName"
    InvalidPattern => "InvalidPattern"
    InvalidPercentEncoding => "InvalidPercentEncoding"
    InvalidUtf8 => "InvalidUtf8"
    LimitExceeded => "LimitExceeded"
    RuleBeforeFirstGroup => "RuleBeforeFirstGroup"
    ControlCharacter => "ControlCharacter"
  }
}

///|
pub fn RobotsError::to_display(self : RobotsError) -> String {
  let RobotsError(stage, kind, line, offset, context) = self
  "\{stage.to_string()}::\{kind.to_string()} at byte \{offset}, line \{line}: \{context}"
}

///|
pub fn max_context_bytes() -> Int {
  80
}

///|
fn bound_context(context : String) -> String {
  if context.length() <= max_context_bytes() {
    context
  } else {
    let chars = context.to_array()
    String::from_array(chars[0:max_context_bytes()]) + "..."
  }
}

///|
fn unwrap_robots_error(e : Error) -> RobotsError {
  match e {
    RobotsError(stage, kind, line, offset, context) =>
      RobotsError(stage, kind, line, offset, context)
    _ => abort("internal error: unexpected non-RobotsError")
  }
}