///|
/// Errors never modify an existing editor.
pub(all) enum InputError {
  InvalidPattern(String)
  InvalidCharacter(Int, String)
  TooLong(Int)
  InvalidSelection
  InvalidConfig(String)
  InvalidValue(String)
} derive(Eq, @debug.Debug)

///|
/// Positions use UTF-16 offsets, including on Wasm GC.
pub(all) struct Selection {
  start : Int
  end : Int
} derive(Eq, @debug.Debug)

///|
pub(all) enum Status {
  Empty
  Incomplete
  Complete
  Invalid(String)
} derive(Eq, @debug.Debug)

///|
priv enum Slot {
  Digit
  Letter
  Alnum
  Printable
}

///|
priv struct Pattern {
  slots : Array[Slot]
  prefixes : Array[String]
  required : Int
}

///|
priv enum Kind {
  PatternKind(Pattern)
  DateKind(Pattern)
  DecimalKind(Int, Bool, Bool)
}

///|
/// A compiled immutable input specification.
pub struct Mask {
  priv kind : Kind
}

///|
priv struct Render {
  raw : Array[Char]
  display : String
  starts : Array[Int]
  ends : Array[Int]
}

///|
/// An immutable snapshot; edits return a new editor or an error.
pub struct Editor {
  priv mask : Mask
  priv render : Render
  priv selection : Selection
}

///|
fn Slot::accept(self : Slot, c : Char) -> Char? {
  match self {
    Digit => if c.is_ascii_digit() { Some(c) } else { None }
    Letter =>
      if c.is_ascii_alphabetic() {
        Some(c.to_ascii_uppercase())
      } else {
        None
      }
    Alnum =>
      if c.is_ascii_alphabetic() || c.is_ascii_digit() {
        Some(c.to_ascii_uppercase())
      } else {
        None
      }
    Printable => if c.is_printable() { Some(c) } else { None }
  }
}

///|
pub extend InputError with @debug.Debug::{to_repr}

///|
pub extend Selection with @debug.Debug::{to_repr}

///|
pub extend Status with @debug.Debug::{to_repr}

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

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

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