///|
/// The construct the parser is inside, innermost last. A failure takes its
/// `elm make` title from the innermost context, as Elm's parser does.
priv enum Context {
  ModuleHeader
  Exposing
  Import
  Definition(String)
  TypeAlias
  CustomType
  Port
  Infix
  Let
  Case
  If
  Lambda
  List
  Record
  Parens
  RecordType
  ParensType
  ListPattern
  RecordPattern
  ParensPattern
}

///|
/// A context and the span of its first token.
priv struct Frame {
  context : Context
  start : @scanner.Span
}

///|
/// The title and report of a syntax error, as `elm make` would show them.
priv struct Problem {
  title : String
  report : Array[@scanner.Block]
}

///|
fn Context::title(self : Context) -> String {
  match self {
    ModuleHeader => "UNFINISHED MODULE DECLARATION"
    Exposing => "PROBLEM IN EXPOSING"
    Import => "UNFINISHED IMPORT"
    Definition(_) => "UNFINISHED DEFINITION"
    TypeAlias => "UNFINISHED TYPE ALIAS"
    CustomType => "UNFINISHED CUSTOM TYPE"
    Port => "UNFINISHED PORT"
    Infix => "SYNTAX PROBLEM"
    Let => "UNFINISHED LET"
    Case => "UNFINISHED CASE"
    If => "UNFINISHED IF"
    Lambda => "UNFINISHED ANONYMOUS FUNCTION"
    List => "UNFINISHED LIST"
    Record => "UNFINISHED RECORD"
    Parens => "UNFINISHED PARENTHESES"
    RecordType => "UNFINISHED RECORD TYPE"
    ParensType => "UNFINISHED PARENTHESES"
    ListPattern => "UNFINISHED LIST PATTERN"
    RecordPattern => "UNFINISHED RECORD PATTERN"
    ParensPattern => "UNFINISHED PARENTHESES"
  }
}

///|
/// How the report names the construct: "I was partway through parsing ".
fn Context::construct(self : Context) -> Array[@scanner.Chunk] {
  match self {
    ModuleHeader => [Plain("the module declaration")]
    Exposing =>
      [Plain("an "), @scanner.Chunk::keyword("exposing"), Plain(" list")]
    Import => [Plain("an "), @scanner.Chunk::keyword("import")]
    Definition(name) =>
      [Plain("the "), @scanner.Chunk::code(name), Plain(" definition")]
    TypeAlias => [Plain("a type alias")]
    CustomType => [Plain("a custom type")]
    Port =>
      [Plain("a "), @scanner.Chunk::keyword("port"), Plain(" declaration")]
    Infix => [Plain("an infix declaration")]
    Let => [Plain("a "), @scanner.Chunk::keyword("let"), Plain(" expression")]
    Case => [Plain("a "), @scanner.Chunk::keyword("case"), Plain(" expression")]
    If => [Plain("an "), @scanner.Chunk::keyword("if"), Plain(" expression")]
    Lambda => [Plain("an anonymous function")]
    List => [Plain("a list")]
    Record => [Plain("a record")]
    Parens => [Plain("an expression in parentheses")]
    RecordType => [Plain("a record type")]
    ParensType => [Plain("a type in parentheses")]
    ListPattern => [Plain("a list pattern")]
    RecordPattern => [Plain("a record pattern")]
    ParensPattern => [Plain("a pattern in parentheses")]
  }
}

///|
/// A short valid example for the report's note, when one helps.
fn Context::example(self : Context) -> (String, String)? {
  match self {
    Let =>
      Some(
        (
          "a valid `let` expression", "viewPerson person =\n  let\n    fullName =\n      person.firstName ++ \" \" ++ person.lastName\n  in\n  div [] [ text fullName ]",
        ),
      )
    Case =>
      Some(
        (
          "a valid `case` expression", "case maybeWidth of\n  Just width ->\n    width + 10\n\n  Nothing ->\n    0",
        ),
      )
    If =>
      Some(
        (
          "a valid `if` expression", "if x < 0 then\n  \"negative\"\nelse\n  \"not negative\"",
        ),
      )
    Lambda => Some(("a valid anonymous function", "\\x -> x + 1"))
    _ => None
  }
}

///|
fn plain(text : String) -> @scanner.Block {
  Text([Plain(text)])
}

///|
/// The report sentence for what was expected: "I was expecting to see 
/// next." `expected` is the parser's description, for example "`,` or `]`".
fn expecting(expected : String) -> @scanner.Block {
  Text([Plain("I was expecting to see \{expected} next.")])
}

///|
/// A problem with a title, an opening sentence, an excerpt from `context` to
/// `highlight`, and more blocks after it.
fn make_problem(
  title : String,
  opening : Array[@scanner.Chunk],
  context : @scanner.Span,
  highlight : @scanner.Span,
  after : Array[@scanner.Block],
) -> Problem {
  { title, report: [Text(opening), Excerpt(context~, highlight~), ..after], }
}

///|
/// A problem whose excerpt is the line of `span`, marking `span`.
fn span_problem(
  title : String,
  opening : String,
  span : @scanner.Span,
  after : Array[@scanner.Block],
) -> Problem {
  make_problem(title, [Plain(opening)], span, span, after)
}

///|
/// A zero-width span at `p`.
fn point(p : @scanner.Position) -> @scanner.Span {
  { start: p, end: p, }
}

///|
/// `p` moved `columns` code units to the right on the same line.
fn shift_position(p : @scanner.Position, columns : Int) -> @scanner.Position {
  { offset: p.offset + columns, line: p.line, column: p.column + columns, }
}