///|
/// Whether the next token can start a pattern.
fn starts_pattern(c : Cursor) -> Bool {
  match c.peek() {
    Some(
      {
        kind: LParen
        | LBrace
        | LBracket
        | Underscore
        | StringLiteral
        | CharLiteral
        | IntLiteral,
        ..
      }
    ) => true
    Some(t) => is_upper(t) || is_lower(t)
    None => false
  }
}

///|
/// `Elm.Parser.Patterns.pattern`: a pattern that may take constructor
/// arguments and be followed by `as name` or `:: pattern`.
fn pattern(c : Cursor) -> @ast.Node[@ast.Pattern] raise SyntaxError {
  let left = single_pattern(c, true)
  if c.is_keyword(As) {
    ignore(c.advance())
    let name = c.lower("a name after `as`")
    let name_node = token_node(name, name.lexeme)
    return node(
      combine(left.range, name_node.range),
      AsPattern(left, name_node),
    )
  }
  if c.at_kind(Operator("::")) {
    ignore(c.advance())
    let right = c.chain(() => pattern(c))
    return node(combine(left.range, right.range), UnConsPattern(left, right))
  }
  left
}

///|
/// `patternNotDirectlyComposing`: used for function and lambda arguments;
/// constructors take no arguments here and there is no `as` or `::`.
fn argument_pattern(c : Cursor) -> @ast.Node[@ast.Pattern] raise SyntaxError {
  single_pattern(c, false)
}

///|
fn single_pattern(
  c : Cursor,
  constructor_args : Bool,
) -> @ast.Node[@ast.Pattern] raise SyntaxError {
  c.nested(() => single_pattern_at_depth(c, constructor_args))
}

///|
fn single_pattern_at_depth(
  c : Cursor,
  constructor_args : Bool,
) -> @ast.Node[@ast.Pattern] raise SyntaxError {
  guard c.peek() is Some(t) else { raise c.fail("a pattern") }
  match t.kind {
    Underscore => {
      ignore(c.advance())
      token_node(t, AllPattern)
    }
    StringLiteral => {
      ignore(c.advance())
      token_node(t, StringPattern(string_value(t, c.dialect)))
    }
    CharLiteral => {
      ignore(c.advance())
      token_node(t, CharPattern(char_value(t, c.dialect)))
    }
    IntLiteral => {
      ignore(c.advance())
      weird_number_check(c, t)
      // elm-syntax reads `01` as `0` then `1`, which cannot be a pattern.
      if t.lexeme.length() > 1 &&
        t.lexeme.has_prefix("0") &&
        !t.lexeme.has_prefix("0x") {
        raise c.fail("`->` after the pattern")
      }
      match int_value(c, t) {
        Ok(n) => token_node(t, IntPattern(n))
        Err(n) => token_node(t, HexPattern(n))
      }
    }
    LParen => c.within(ParensPattern, () => parens_pattern(c))
    LBracket => c.within(ListPattern, () => list_pattern(c))
    LBrace => c.within(RecordPattern, () => record_pattern(c))
    Identifier if is_lower(t) => {
      ignore(c.advance())
      token_node(t, VarPattern(t.lexeme))
    }
    Identifier => {
      let name = qualified_type_name(c)
      let (module_name, value) = name.value
      let name_ref : @ast.QualifiedNameRef = { module_name, name: value, }
      if !constructor_args {
        return node(name.range, NamedPattern(name_ref, []))
      }
      let args = []
      while c.positively_indented() && starts_pattern(c) {
        args.push(argument_pattern(c))
      }
      let range = match args.last() {
        Some(last) => combine(name.range, last.range)
        None => name.range
      }
      node(range, NamedPattern(name_ref, args))
    }
    _ => raise c.fail("a pattern")
  }
}

///|
fn parens_pattern(c : Cursor) -> @ast.Node[@ast.Pattern] raise SyntaxError {
  let open = c.advance()
  if c.at_kind(RParen) {
    // `(  )` is a valid pattern, unlike a unit type or expression.
    let close = c.advance()
    return node(range_from(open, close), UnitPattern)
  }
  let first = pattern(c)
  if c.at_kind(RParen) {
    let close = c.advance()
    return node(range_from(open, close), ParenthesizedPattern(first))
  }
  ignore(c.expect(Comma, "`,` or `)`"))
  let parts = [first, pattern(c)]
  if c.at_kind(Comma) {
    ignore(c.advance())
    parts.push(pattern(c))
  }
  let close = c.expect(RParen, "`)`")
  node(range_from(open, close), TuplePattern(parts))
}

///|
fn list_pattern(c : Cursor) -> @ast.Node[@ast.Pattern] raise SyntaxError {
  let open = c.advance()
  let items = []
  if !c.at_kind(RBracket) {
    items.push(pattern(c))
    while c.at_kind(Comma) {
      ignore(c.advance())
      items.push(pattern(c))
    }
  }
  let close = c.expect(RBracket, "`]`")
  node(range_from(open, close), ListPattern(items))
}

///|
fn record_pattern(c : Cursor) -> @ast.Node[@ast.Pattern] raise SyntaxError {
  let open = c.advance()
  let fields = []
  if !c.at_kind(RBrace) {
    let first = c.lower("a field name")
    fields.push(token_node(first, first.lexeme))
    while c.at_kind(Comma) {
      ignore(c.advance())
      let field = c.lower("a field name")
      fields.push(token_node(field, field.lexeme))
    }
  }
  let close = c.expect(RBrace, "`}`")
  node(range_from(open, close), RecordPattern(fields))
}