// Copyright 2026 Leo Cheng
// SPDX-License-Identifier: Apache-2.0

///|
/// Where in the text something went wrong.
#derive(Eq, Debug)
pub(all) struct At {
  /// The offset in UTF-16 code units from the start.
  at : Int32
  /// The line, counting from one.
  line : Int32
  /// The column, counting from one.
  column : Int32
}

///|
pub extend At with Eq::{equal, not_equal}

///|
pub extend At with Debug::{to_repr}

///|
/// Why a text was refused.
pub(all) suberror Refused {
  /// A `${` with no `}`.
  Unclosed(at~ : At)
  /// A name that is not a name: `${}`, `${1x}`, `${a-b}` outside an operator.
  BadName(at~ : At, got~ : String)
  /// An operator this reader does not know, such as the substring form.
  BadOperator(at~ : At, got~ : String)
  /// A variable with no value, under the policy that refuses those.
  Undefined(name~ : String, at~ : At)
  /// `${A:=b}` with nowhere to write the assignment.
  ReadOnly(name~ : String, at~ : At)
  /// `${A:?}` when `A` is unset or empty.
  Required(name~ : String, why~ : String, at~ : At)
  /// Nesting past the limit.
  TooDeep(at~ : At, limit~ : Int32)
}

///|
fn Refused::message(self : Refused) -> String {
  match self {
    Unclosed(at~) =>
      "line " + at.line.to_string() + ": a reference is never closed"
    BadName(at~, got~) =>
      "line " + at.line.to_string() + ": `" + got + "` is not a name"
    BadOperator(at~, got~) =>
      "line " + at.line.to_string() + ": `" + got + "` is not an operator"
    Undefined(name~, at~) =>
      "line " + at.line.to_string() + ": " + name + " is not set"
    ReadOnly(name~, at~) =>
      "line " + at.line.to_string() + ": " + name + " cannot be assigned here"
    Required(name~, why~, at~) =>
      "line " +
      at.line.to_string() +
      ": " +
      name +
      " is required" +
      (if why == "" { "" } else { ": " + why })
    TooDeep(at~, limit~) =>
      "line " +
      at.line.to_string() +
      ": nested deeper than " +
      limit.to_string()
  }
}

///|
pub impl Show for Refused with fn to_string(self) {
  self.message()
}

///|
pub extend Refused with Show::{to_string, output}