///|
/// Deepest AST a declaration may have. Encoding an AST and printing its JSON
/// recurse once per level, so deeper trees overflow the stack on wasm.
const MAX_AST_DEPTH : Int = 400

///|
/// A node of any AST type, for the depth walk.
priv enum Item {
  E(@ast.Expression)
  P(@ast.Pattern)
  T(@ast.TypeAnnotation)
  F(@ast.Function)
}

///|
fn function_items(f : @ast.Function) -> Array[Item] {
  let items = []
  if f.signature is Some(s) {
    items.push(T(s.value.type_annotation.value))
  }
  for a in f.declaration.value.arguments {
    items.push(P(a.value))
  }
  items.push(E(f.declaration.value.expression.value))
  items
}

///|
/// The direct children of `item`.
fn children(item : Item) -> Array[Item] {
  match item {
    F(f) => function_items(f)
    E(e) =>
      match e {
        Application(xs) | TupledExpression(xs) | ListExpr(xs) =>
          xs.map(x => E(x.value))
        OperatorApplication(_, _, l, r) => [E(l.value), E(r.value)]
        IfBlock(a, b, c) => [E(a.value), E(b.value), E(c.value)]
        Negation(x) | ParenthesizedExpression(x) | RecordAccess(x, _) =>
          [E(x.value)]
        LetExpression(block) => {
          let items = []
          for d in block.declarations {
            match d.value {
              LetFunction(f) => items.push(F(f))
              LetDestructuring(p, x) => {
                items.push(P(p.value))
                items.push(E(x.value))
              }
            }
          }
          items.push(E(block.expression.value))
          items
        }
        CaseExpression(block) => {
          let items = [E(block.expression.value)]
          for c in block.cases {
            items.push(P(c.pattern.value))
            items.push(E(c.expression.value))
          }
          items
        }
        LambdaExpression(l) =>
          [..l.args.map(a => P(a.value)), E(l.expression.value)]
        RecordExpr(setters) | RecordUpdateExpression(_, setters) =>
          setters.map(s => E(s.value.expression.value))
        _ => []
      }
    P(p) =>
      match p {
        TuplePattern(xs) | ListPattern(xs) | NamedPattern(_, xs) =>
          xs.map(x => P(x.value))
        UnConsPattern(a, b) => [P(a.value), P(b.value)]
        AsPattern(x, _) | ParenthesizedPattern(x) => [P(x.value)]
        _ => []
      }
    T(t) =>
      match t {
        Typed(_, xs) | Tupled(xs) => xs.map(x => T(x.value))
        Record(fields) => fields.map(f => T(f.value.type_annotation.value))
        GenericRecord(_, fields) =>
          fields.value.map(f => T(f.value.type_annotation.value))
        FunctionTypeAnnotation(a, b) => [T(a.value), T(b.value)]
        _ => []
      }
  }
}

///|
/// The depth of `decl`'s AST, measured without recursion (an explicit stack),
/// so it works on trees of any depth.
fn declaration_depth(decl : @ast.Declaration) -> Int {
  let roots : Array[Item] = match decl {
    FunctionDeclaration(f) => [F(f)]
    AliasDeclaration(a) => [T(a.type_annotation.value)]
    CustomTypeDeclaration(t) => {
      let items = []
      for c in t.constructors {
        for a in c.value.arguments {
          items.push(T(a.value))
        }
      }
      items
    }
    PortDeclaration(s) => [T(s.type_annotation.value)]
    Destructuring(p, e) => [P(p.value), E(e.value)]
    InfixDeclaration(_) => []
  }
  let stack = roots.map(r => (r, 1))
  let mut deepest = 0
  while stack.pop() is Some((item, depth)) {
    if depth > deepest {
      deepest = depth
    }
    for child in children(item) {
      stack.push((child, depth + 1))
    }
  }
  deepest
}