///|
/// `Elm.Parser.TypeAnnotation.typeAnnotation`: a type, optionally followed by
/// `-> type` (right-nested).
fn type_annotation(
  c : Cursor,
) -> @ast.Node[@ast.TypeAnnotation] raise SyntaxError {
  let input = type_no_fn(c, true)
  if c.at_kind(Arrow) && c.positively_indented() {
    ignore(c.advance())
    let output = c.chain(() => type_annotation(c))
    node(
      combine(input.range, output.range),
      FunctionTypeAnnotation(input, output),
    )
  } else {
    input
  }
}

///|
fn starts_type(c : Cursor) -> Bool {
  match c.peek() {
    Some({ kind: LParen | LBrace, .. }) => true
    Some(t) => is_upper(t) || is_lower(t)
    None => false
  }
}

///|
/// A type without a top-level arrow. `with_args` lets a named type take
/// arguments (`Maybe Int`); arguments themselves are parsed without.
fn type_no_fn(
  c : Cursor,
  with_args : Bool,
) -> @ast.Node[@ast.TypeAnnotation] raise SyntaxError {
  c.nested(() => type_no_fn_at_depth(c, with_args))
}

///|
fn type_no_fn_at_depth(
  c : Cursor,
  with_args : Bool,
) -> @ast.Node[@ast.TypeAnnotation] raise SyntaxError {
  guard c.peek() is Some(t) else { raise c.fail("a type") }
  match t.kind {
    LParen => c.within(ParensType, () => parens_type(c))
    LBrace => c.within(RecordType, () => record_type(c))
    Identifier if is_lower(t) => {
      ignore(c.advance())
      token_node(t, GenericType(t.lexeme))
    }
    Identifier => {
      let name = qualified_type_name(c)
      if !with_args {
        return node(name.range, Typed(name, []))
      }
      let args = []
      while c.positively_indented() && starts_type(c) {
        args.push(type_no_fn(c, false))
      }
      let range = match args.last() {
        Some(last) => combine(name.range, last.range)
        None => name.range
      }
      node(range, Typed(name, args))
    }
    _ => raise c.fail("a type")
  }
}

///|
/// `Name` or `Module.Name`, with the dots touching the names.
fn qualified_type_name(
  c : Cursor,
) -> @ast.Node[(@ast.ModuleName, String)] raise SyntaxError {
  let first = c.upper("a type name")
  let parts = [first.lexeme]
  let mut last = first
  while c.dotted_name(is_upper) is Some(_) {
    ignore(c.advance())
    last = c.advance()
    parts.push(last.lexeme)
  }
  let name = parts.pop().unwrap()
  node(range_from(first, last), (parts[:], name))
}

///|
fn parens_type(c : Cursor) -> @ast.Node[@ast.TypeAnnotation] raise SyntaxError {
  let open = c.advance()
  if c.at_kind(RParen) && c.next_touches() {
    let close = c.advance()
    return node(range_from(open, close), Unit)
  }
  let first = type_annotation(c)
  if c.at_kind(RParen) {
    // A parenthesized type is the inner type, with a range over the parens.
    let close = c.advance()
    return node(range_from(open, close), first.value)
  }
  ignore(c.expect(Comma, "`,` or `)`"))
  let second = type_annotation(c)
  let parts = [first, second]
  if c.at_kind(Comma) {
    ignore(c.advance())
    parts.push(type_annotation(c))
  }
  let close = c.expect(RParen, "`)`")
  node(range_from(open, close), Tupled(parts))
}

///|
/// A record field definition whose range ends where the next token starts
/// (elm-syntax includes the trailing whitespace).
fn record_field(c : Cursor) -> @ast.Node[@ast.RecordField] raise SyntaxError {
  let name = c.lower("a field name")
  ignore(c.expect(Colon, "`:`"))
  let value = type_annotation(c)
  let end = match c.peek() {
    Some(next) => location(next.span.start)
    None => value.range.end
  }
  node({ start: location(name.span.start), end, }, {
    name: token_node(name, name.lexeme),
    type_annotation: value,
  })
}

///|
fn record_type(c : Cursor) -> @ast.Node[@ast.TypeAnnotation] raise SyntaxError {
  let open = c.advance()
  if c.at_kind(RBrace) {
    let close = c.advance()
    return node(range_from(open, close), Record([]))
  }
  let first_name = c.lower("a field name")
  if c.at_kind(Pipe) {
    let pipe = c.advance()
    let fields = [record_field(c)]
    while c.at_kind(Comma) {
      ignore(c.advance())
      fields.push(record_field(c))
    }
    let fields_end = fields.last().unwrap().range.end
    let close = c.expect(RBrace, "`}`")
    return node(
      range_from(open, close),
      GenericRecord(
        token_node(first_name, first_name.lexeme),
        node({ start: location(pipe.span.end), end: fields_end, }, fields),
      ),
    )
  }
  ignore(c.expect(Colon, "`:` or `|`"))
  let first_value = type_annotation(c)
  // elm-syntax quirk: the first field ends at its type; later fields end
  // where the next token starts (see record_field).
  let fields : Array[@ast.Node[@ast.RecordField]] = [
    node(combine(range_of(first_name), first_value.range), {
      name: token_node(first_name, first_name.lexeme),
      type_annotation: first_value,
    }),
  ]
  while c.at_kind(Comma) {
    ignore(c.advance())
    fields.push(record_field(c))
  }
  let close = c.expect(RBrace, "`}`")
  node(range_from(open, close), Record(fields))
}