///|
/// Where a type is: this decides its parentheses.
priv enum TypeAt {
  AnyType // nothing needs parentheses
  ArrowLeft // left of `->`: a function type needs them
  TypeArg // argument of a type application: also an application with arguments
}

///|
fn Ctx::type_doc(
  self : Ctx,
  path : @syntax.NodePath,
  level : Int,
  t : @ast.Node[@ast.TypeAnnotation],
  at : TypeAt,
) -> @pretty.Doc raise PrintError {
  check_level(path, level)
  match t.value {
    GenericType(name) => self.lower_name(path, name)
    Unit => @pretty.text("()")
    Typed(name, args) => {
      let (module_, base_name) = name.value
      let head = qualified_upper(path, module_, base_name)
      guard !args.is_empty() else { return head }
      let docs = []
      for i, a in args {
        docs.push(self.type_doc(path.child("args", i), level + 1, a, TypeArg))
      }
      let d = spaced(head, docs)
      if at is TypeArg {
        parens(d)
      } else {
        d
      }
    }
    Tupled(items) => {
      guard items.length() >= 2 else {
        raise PrintError(path~, problem=ShortTuple)
      }
      let docs = []
      for i, x in items {
        docs.push(self.type_doc(path.child("values", i), level + 1, x, AnyType))
      }
      sequence("(", ")", docs)
    }
    Record(fields) =>
      sequence("{", "}", self.record_fields(path, "value", level, fields))
    GenericRecord(name, fields) => {
      guard !fields.value.is_empty() else {
        raise PrintError(path~, problem=NoFields)
      }
      extension(
        self.lower_name(path.child("name", 0), name.value),
        self.record_fields(path, "values", level, fields.value),
      )
    }
    FunctionTypeAnnotation(_, _) => {
      let d = @pretty.group(self.arrow_chain(path, level, t))
      if at is AnyType {
        d
      } else {
        parens(d)
      }
    }
  }
}

///|
/// `a : A` for each field, at `path.child(field_name, i)`.
fn Ctx::record_fields(
  self : Ctx,
  path : @syntax.NodePath,
  field_name : String,
  level : Int,
  fields : @ast.RecordDefinition,
) -> Array[@pretty.Doc] raise PrintError {
  let docs = []
  for i, f in fields {
    let p = path.child(field_name, i)
    docs.push(
      field(
        self.lower_name(p.child("name", 0), f.value.name.value),
        ":",
        self.type_doc(
          p.child("typeAnnotation", 0),
          level + 1,
          f.value.type_annotation,
          AnyType,
        ),
      ),
    )
  }
  docs
}

///|
/// `a -> b -> c`, flattened without recursion and without its own group (a
/// signature shares its group with `name :`). Not a function type: the type
/// itself.
fn Ctx::arrow_chain(
  self : Ctx,
  path : @syntax.NodePath,
  level : Int,
  t : @ast.Node[@ast.TypeAnnotation],
) -> @pretty.Doc raise PrintError {
  let mut node = t
  let mut p = path
  let mut d = @pretty.empty()
  let mut first = true
  while node.value is FunctionTypeAnnotation(left, right) {
    let l = self.type_doc(p.child("left", 0), level + 1, left, ArrowLeft)
    d = if first { l } else { d + @pretty.line() + @pretty.text("-> ") + l }
    first = false
    p = p.child("right", 0)
    node = right
  }
  let last = self.type_doc(p, level + 1, node, AnyType)
  if first {
    last
  } else {
    d + @pretty.line() + @pretty.text("-> ") + last
  }
}