///|
/// Compile # digit, A ASCII letter, * ASCII alphanumeric, X printable scalar.
/// A single optional suffix in brackets is supported. Backslash escapes literals.
pub fn pattern(source : String) -> Result[Mask, InputError] {
  if source.length() > 4096 {
    return Err(TooLong(4096))
  }
  let chars = source.to_array()
  let slots : Array[Slot] = []
  let prefixes : Array[String] = []
  let mut prefix = ""
  let mut optional = false
  let mut closed = false
  let mut required = 0
  let mut i = 0
  while i < chars.length() {
    let c = chars[i]
    if closed {
      return Err(InvalidPattern("optional group must be a suffix"))
    }
    if c == '\\' {
      i += 1
      if i == chars.length() {
        return Err(InvalidPattern("trailing escape"))
      }
      if !chars[i].is_printable() {
        return Err(InvalidPattern("control literal"))
      }
      prefix += chars[i].to_string()
    } else if c == '[' {
      if optional {
        return Err(InvalidPattern("nested optional group"))
      }
      optional = true
      required = slots.length()
    } else if c == ']' {
      if !optional {
        return Err(InvalidPattern("unexpected closing bracket"))
      }
      if slots.length() == required {
        return Err(InvalidPattern("empty optional group"))
      }
      closed = true
    } else {
      let slot = match c {
        '#' => Some(Digit)
        'A' => Some(Letter)
        '*' => Some(Alnum)
        'X' => Some(Printable)
        _ => None
      }
      match slot {
        Some(s) => {
          slots.push(s)
          prefixes.push(prefix)
          prefix = ""
        }
        None => {
          if !c.is_printable() {
            return Err(InvalidPattern("control literal"))
          }
          prefix += c.to_string()
        }
      }
    }
    i += 1
  }
  if slots.is_empty() {
    return Err(InvalidPattern("at least one slot is required"))
  }
  if optional && !closed {
    return Err(InvalidPattern("unclosed optional group"))
  }
  if prefix != "" {
    return Err(InvalidPattern("trailing literals are not supported"))
  }
  if !optional {
    required = slots.length()
  }
  Ok({ kind: PatternKind({ slots, prefixes, required, }), })
}

///|
fn parse_raw(
  p : Pattern,
  text : Array[Char],
  start : Int,
) -> Result[Array[Char], InputError] {
  if text.length() + start > p.slots.length() {
    return Err(TooLong(p.slots.length()))
  }
  let result : Array[Char] = []
  for i, c in text {
    match p.slots[start + i].accept(c) {
      Some(value) => result.push(value)
      None => return Err(InvalidCharacter(start + i, c.to_string()))
    }
  }
  Ok(result)
}

///|
fn matches_at(text : Array[Char], offset : Int, prefix : Array[Char]) -> Bool {
  if offset + prefix.length() > text.length() {
    return false
  }
  for j, c in prefix {
    if text[offset + j] != c {
      return false
    }
  }
  true
}

///|
fn parse_pattern(
  p : Pattern,
  text : String,
  start : Int,
) -> Result[Array[Char], InputError] {
  if text.length() > 8192 {
    return Err(TooLong(8192))
  }
  let chars = text.to_array()
  // Raw input takes precedence where a literal could also be a valid slot value.
  match parse_raw(p, chars, start) {
    Ok(result) => return Ok(result)
    Err(_) => ()
  }
  let result : Array[Char] = []
  let mut i = 0
  while i < chars.length() {
    let slot = start + result.length()
    if slot >= p.slots.length() {
      return Err(TooLong(p.slots.length()))
    }
    let prefix = p.prefixes[slot].to_array()
    if !prefix.is_empty() && matches_at(chars, i, prefix) {
      i += prefix.length()
      if i == chars.length() {
        break
      }
    }
    match p.slots[slot].accept(chars[i]) {
      Some(value) => result.push(value)
      None => return Err(InvalidCharacter(slot, chars[i].to_string()))
    }
    i += 1
  }
  Ok(result)
}

///|
fn render_pattern(p : Pattern, raw : Array[Char]) -> Render {
  let starts : Array[Int] = []
  let ends : Array[Int] = []
  let mut display = ""
  for i, c in raw {
    display += p.prefixes[i]
    starts.push(display.length())
    display += c.to_string()
    ends.push(display.length())
  }
  { raw, display, starts, ends, }
}

///|
/// Normalize a raw or exactly formatted value. Does not allocate an editor.
pub fn Mask::format(self : Mask, text : String) -> Result[String, InputError] {
  match self.kind {
    PatternKind(p) | DateKind(p) =>
      match parse_pattern(p, text, 0) {
        Ok(raw) => Ok(render_pattern(p, raw).display)
        Err(e) => Err(e)
      }
    DecimalKind(precision, signed, grouped) =>
      match parse_decimal(text, precision, signed, grouped) {
        Ok(render) => Ok(render.display)
        Err(e) => Err(e)
      }
  }
}