///|
/// Validate a parsed cucumber expression AST.
/// Raises ValidationError for invalid expression structures.
pub fn validate(node : Node) -> Unit raise ExpressionError {
  validate_node(node, inside_optional=false)
}

///|
/// Recursively validate a node. `inside_optional` tracks whether we are
/// currently inside an OptionalNode (to detect nested optionals and
/// parameters inside optionals).
fn validate_node(
  node : Node,
  inside_optional~ : Bool,
) -> Unit raise ExpressionError {
  match node {
    ExpressionNode(children) =>
      for child in children {
        validate_node(child, inside_optional~)
      }
    OptionalNode(children) => {
      if inside_optional {
        raise ExpressionError::ValidationError(
          position=0,
          message="An optional may not contain another optional",
        )
      }
      if children.length() == 0 {
        raise ExpressionError::ValidationError(
          position=0,
          message="An optional must contain at least one node",
        )
      }
      for child in children {
        validate_node(child, inside_optional=true)
      }
    }
    ParameterNode(_) =>
      if inside_optional {
        raise ExpressionError::ValidationError(
          position=0,
          message="An optional may not contain a parameter",
        )
      }
    AlternationNode(alternatives) =>
      for arm in alternatives {
        // Each arm must have at least one TextNode
        let mut has_text = false
        for child in arm {
          match child {
            TextNode(_) => has_text = true
            _ => ()
          }
        }
        if not(has_text) {
          raise ExpressionError::ValidationError(
            position=0,
            message="Each alternation arm must contain at least one text node",
          )
        }
        // Recursively validate children of each arm
        for child in arm {
          validate_node(child, inside_optional~)
        }
      }
    TextNode(_) => ()
  }
}