///|
/// The reserved words of Elm 0.19.1 (the scanner's `KeywordKind`).
let reserved : Array[String] = [
  "module", "exposing", "import", "as", "type", "if", "then", "else", "let", "in",
  "case", "of", "port", "where",
]

///|
fn is_name(s : String, start : (Char) -> Bool) -> Bool {
  let mut first = true
  for c in s {
    if first {
      if !start(c) {
        return false
      }
      first = false
    } else if !@scanner.is_name_part(c) {
      return false
    }
  }
  !first
}

///|
fn Ctx::is_lower(self : Ctx, s : String) -> Bool {
  is_name(s, @scanner.is_lower_start) &&
  !reserved.contains(s) &&
  !self.dialect.reserved_words.contains(s)
}

///|
fn is_upper(s : String) -> Bool {
  is_name(s, @scanner.is_upper_start)
}

///|
fn Ctx::lower_name(
  self : Ctx,
  path : @syntax.NodePath,
  s : String,
) -> @pretty.Doc raise PrintError {
  guard self.is_lower(s) else {
    raise PrintError(path~, problem=InvalidName(Lower, s))
  }
  @pretty.text(s)
}

///|
fn upper_name(
  path : @syntax.NodePath,
  s : String,
) -> @pretty.Doc raise PrintError {
  guard is_upper(s) else {
    raise PrintError(path~, problem=InvalidName(Upper, s))
  }
  @pretty.text(s)
}

///|
/// An operator symbol in a prefix operator, an exposing list or an infix
/// declaration. Elm 0.19 has no user-defined operators, so a symbol is
/// valid only when the dialect's operator table has it.
fn Ctx::operator_symbol(
  self : Ctx,
  path : @syntax.NodePath,
  s : String,
) -> String raise PrintError {
  guard self.dialect.operator(s) is Some(_) else {
    raise PrintError(path~, problem=InvalidName(Operator, s))
  }
  s
}

///|
/// `Html.Events` from its segments.
fn module_name(
  path : @syntax.NodePath,
  segments : ArrayView[String],
) -> String raise PrintError {
  guard !segments.is_empty() else {
    raise PrintError(path~, problem=EmptyModuleName)
  }
  let out = StringBuilder()
  for i, s in segments {
    guard is_upper(s) else {
      raise PrintError(path~, problem=InvalidName(Upper, s))
    }
    if i > 0 {
      out.write_char('.')
    }
    out.write_string(s)
  }
  out.to_string()
}

///|
/// A value or constructor reference: `x`, `Just`, `List.map`.
fn Ctx::qualified_value(
  self : Ctx,
  path : @syntax.NodePath,
  module_ : ArrayView[String],
  name : String,
) -> @pretty.Doc raise PrintError {
  guard self.is_lower(name) || is_upper(name) else {
    raise PrintError(path~, problem=InvalidName(Lower, name))
  }
  if module_.is_empty() {
    @pretty.text(name)
  } else {
    @pretty.text(module_name(path, module_) + "." + name)
  }
}

///|
/// A type or constructor-pattern reference: `Maybe`, `Dict.Dict`.
fn qualified_upper(
  path : @syntax.NodePath,
  module_ : ArrayView[String],
  name : String,
) -> @pretty.Doc raise PrintError {
  guard is_upper(name) else {
    raise PrintError(path~, problem=InvalidName(Upper, name))
  }
  if module_.is_empty() {
    @pretty.text(name)
  } else {
    @pretty.text(module_name(path, module_) + "." + name)
  }
}

///|
/// The dialect's entry for an operator.
fn Ctx::operator_def(
  self : Ctx,
  path : @syntax.NodePath,
  symbol : String,
) -> @dialect.OperatorDef raise PrintError {
  match self.dialect.operator(symbol) {
    Some(def) => def
    None => raise PrintError(path~, problem=UnknownOperator(symbol))
  }
}