///|
/// Where a pattern is: this decides its parentheses (checked against
/// elm-syntax: `Just x :: xs` and `a :: b as c` need none).
priv enum PatternAt {
  AnyPattern // case branch, let, tuple and list item, inside parentheses, right of `::`
  ConsLeft // left of `::`: `::` and `as` patterns need parentheses
  AsInner // the pattern of an `as`: `::` and `as` patterns need parentheses
  PatternArg // argument or destructuring pattern: also a constructor with arguments
}

///|
fn Ctx::pattern_doc(
  self : Ctx,
  path : @syntax.NodePath,
  level : Int,
  p : @ast.Node[@ast.Pattern],
  at : PatternAt,
) -> @pretty.Doc raise PrintError {
  check_level(path, level)
  match p.value {
    AllPattern => @pretty.text("_")
    UnitPattern => @pretty.text("()")
    CharPattern(c) => @pretty.text(char_literal(c))
    StringPattern(s) => @pretty.text(string_literal(s))
    IntPattern(v) => {
      guard v >= 0L else { raise PrintError(path~, problem=NegativePattern) }
      @pretty.text(v.to_string())
    }
    HexPattern(v) => {
      guard v >= 0L else { raise PrintError(path~, problem=NegativePattern) }
      @pretty.text(hex_text(v))
    }
    FloatPattern(x) => {
      guard !x.is_nan() && !x.is_inf() else {
        raise PrintError(path~, problem=NonFiniteFloat(x))
      }
      guard !is_negative(x) else {
        raise PrintError(path~, problem=NegativePattern)
      }
      @pretty.text(float_text(x))
    }
    TuplePattern(items) => {
      guard items.length() >= 2 else {
        raise PrintError(path~, problem=ShortTuple)
      }
      flat_sequence("(", ")", self.pattern_items(path, level, items))
    }
    ListPattern(items) =>
      flat_sequence("[", "]", self.pattern_items(path, level, items))
    RecordPattern(names) => {
      let docs = []
      for i, x in names {
        docs.push(self.lower_name(path.child("value", i), x.value))
      }
      flat_sequence("{", "}", docs)
    }
    VarPattern(name) => self.lower_name(path, name)
    NamedPattern(name_ref, args) => {
      let head = qualified_upper(path, name_ref.module_name, name_ref.name)
      guard !args.is_empty() else { return head }
      let mut d = head
      for i, a in args {
        d = d +
          @pretty.text(" ") +
          self.pattern_doc(path.child("patterns", i), level + 1, a, PatternArg)
      }
      match at {
        PatternArg => flat_parens(d)
        AnyPattern | ConsLeft | AsInner => d
      }
    }
    UnConsPattern(_, _) => {
      let mut node = p
      let mut q = path
      let mut d = @pretty.empty()
      while node.value is UnConsPattern(left, right) {
        d = d +
          self.pattern_doc(q.child("left", 0), level + 1, left, ConsLeft) +
          @pretty.text(" :: ")
        q = q.child("right", 0)
        node = right
      }
      d = d + self.pattern_doc(q, level + 1, node, AnyPattern)
      match at {
        AnyPattern => d
        ConsLeft | AsInner | PatternArg => flat_parens(d)
      }
    }
    AsPattern(inner, name) => {
      let d = self.pattern_doc(
          path.child("pattern", 0),
          level + 1,
          inner,
          AsInner,
        ) +
        @pretty.text(" as ") +
        self.lower_name(path.child("name", 0), name.value)
      match at {
        AnyPattern => d
        ConsLeft | AsInner | PatternArg => flat_parens(d)
      }
    }
    ParenthesizedPattern(x) =>
      flat_parens(
        self.pattern_doc(path.child("value", 0), level + 1, x, AnyPattern),
      )
  }
}

///|
fn Ctx::pattern_items(
  self : Ctx,
  path : @syntax.NodePath,
  level : Int,
  items : ArrayView[@ast.Node[@ast.Pattern]],
) -> Array[@pretty.Doc] raise PrintError {
  let docs = []
  for i, x in items {
    docs.push(
      self.pattern_doc(path.child("value", i), level + 1, x, AnyPattern),
    )
  }
  docs
}