///|
fn validate_expression_depth(
  root : Expr,
  maximum : Int32,
) -> Unit raise QueryError {
  let stack = [(root, 1)]
  while stack.pop() is Some((expression, depth)) {
    if depth > maximum {
      raise Resource("expression tree depth")
    }
    match expression {
      Unary(_, e) => stack.push((e, depth + 1))
      Binary(_, a, b) => {
        stack.push((a, depth + 1))
        stack.push((b, depth + 1))
      }
      Call(_, args) =>
        for e in args {
          stack.push((e, depth + 1))
        }
      Aggregate(_, e, _) => if e is Some(e) { stack.push((e, depth + 1)) }
      _ => ()
    }
  }
}

///|
fn validate_path_depth(root : Path, maximum : Int32) -> Unit raise QueryError {
  let stack = [(root, 1)]
  while stack.pop() is Some((path, depth)) {
    if depth > maximum {
      raise Resource("property path tree depth")
    }
    match path {
      Inverse(p) | ZeroMore(p) | OneMore(p) | ZeroOne(p) =>
        stack.push((p, depth + 1))
      Sequence(a, b) | Alternative(a, b) => {
        stack.push((a, depth + 1))
        stack.push((b, depth + 1))
      }
      _ => ()
    }
  }
}

///|
fn validate_group_depth(
  group : Group,
  maximum : Int32,
) -> Unit raise QueryError {
  for pattern in group.patterns {
    match pattern {
      Triple(_, path, _) => validate_path_depth(path, maximum)
      Filter(e) | Bind(e, _) => validate_expression_depth(e, maximum)
      Nested(g) | Optional(g) | Graph(_, g) | Minus(g) =>
        validate_group_depth(g, maximum)
      Union(groups) =>
        for g in groups {
          validate_group_depth(g, maximum)
        }
      _ => ()
    }
  }
}

///|
fn validate_depth(q : Query, maximum : Int32) -> Unit raise QueryError {
  validate_group_depth(q.group, maximum)
  for p in q.projections {
    validate_expression_depth(p.expression, maximum)
  }
  for e in q.group_by {
    validate_expression_depth(e, maximum)
  }
  for e in q.having {
    validate_expression_depth(e, maximum)
  }
  for (e, _) in q.order {
    validate_expression_depth(e, maximum)
  }
}