//! Typed AST node wrappers for every node kind in the ink CST.
//!
//! Each struct is a zero-cost newtype around [`SyntaxNode`] generated by
//! [`ast_node!`]. Structs with hand-written accessors have `impl` blocks
//! below their definition.

use crate::SyntaxKind::{
    self, AMP, AMP_AMP, BANG, BANG_EQ, BANG_QUESTION, CARET, COLON, DIVERT, DOLLAR, EQ, EQ_EQ,
    FLOAT, GT, GT_EQ, HASH, IDENT, INTEGER, KW_AND, KW_CYCLE, KW_DONE, KW_ELSE, KW_END, KW_FALSE,
    KW_FUNCTION, KW_HAS, KW_HASNT, KW_MOD, KW_NOT, KW_ONCE, KW_OR, KW_REF, KW_SHUFFLE, KW_STOPPING,
    KW_TODO, KW_TRUE, LT, LT_EQ, MINUS, MINUS_EQ, NEWLINE, PERCENT, PIPE, PLUS, PLUS_EQ, QUESTION,
    SLASH, STAR, TILDE,
};
use crate::ast::AstNode as _;
use crate::ast::ast_node;
use crate::ast::support;
use crate::{SyntaxNode, SyntaxToken};

// ── Top-level ────────────────────────────────────────────────────────

ast_node!(SourceFile, SOURCE_FILE);
ast_node!(IncludeStmt, INCLUDE_STMT);
ast_node!(FilePath, FILE_PATH);
ast_node!(ExternalDecl, EXTERNAL_DECL);

// ── Knots & stitches ─────────────────────────────────────────────────

ast_node!(KnotDef, KNOT_DEF);
ast_node!(KnotHeader, KNOT_HEADER);
ast_node!(KnotBody, KNOT_BODY);
ast_node!(KnotParams, KNOT_PARAMS);
ast_node!(KnotParamDecl, KNOT_PARAM_DECL);
ast_node!(StitchDef, STITCH_DEF);
ast_node!(StitchHeader, STITCH_HEADER);
ast_node!(StitchBody, STITCH_BODY);

// ── Lines ────────────────────────────────────────────────────────────

ast_node!(EmptyLine, EMPTY_LINE);
ast_node!(AuthorWarning, AUTHOR_WARNING);
ast_node!(LogicLine, LOGIC_LINE);
ast_node!(ContentLine, CONTENT_LINE);
ast_node!(TagLine, TAG_LINE);
ast_node!(StrayClosingBrace, STRAY_CLOSING_BRACE);

// ── Logic ────────────────────────────────────────────────────────────

ast_node!(ReturnStmt, RETURN_STMT);
ast_node!(TempDecl, TEMP_DECL);
ast_node!(Assignment, ASSIGNMENT);

// ── Content ──────────────────────────────────────────────────────────

ast_node!(MixedContent, MIXED_CONTENT);
ast_node!(Text, TEXT);
ast_node!(Escape, ESCAPE);
ast_node!(GlueNode, GLUE_NODE);

// ── Choices ──────────────────────────────────────────────────────────

ast_node!(Choice, CHOICE);
ast_node!(ChoiceBullets, CHOICE_BULLETS);
ast_node!(Label, LABEL);
ast_node!(ChoiceCondition, CHOICE_CONDITION);
ast_node!(ChoiceStartContent, CHOICE_START_CONTENT);
ast_node!(ChoiceBracketContent, CHOICE_BRACKET_CONTENT);
ast_node!(ChoiceInnerContent, CHOICE_INNER_CONTENT);

// ── Gathers ──────────────────────────────────────────────────────────

ast_node!(Gather, GATHER);
ast_node!(GatherDashes, GATHER_DASHES);

// ── Tags ─────────────────────────────────────────────────────────────

ast_node!(Tags, TAGS);
ast_node!(Tag, TAG);

// ── Inline logic ─────────────────────────────────────────────────────

ast_node!(InlineLogic, INLINE_LOGIC);
ast_node!(MultilineBlock, MULTILINE_BLOCK);
ast_node!(SequenceWithAnnotation, SEQUENCE_WITH_ANNOTATION);
ast_node!(SequenceSymbolAnnotation, SEQUENCE_SYMBOL_ANNOTATION);
ast_node!(SequenceWordAnnotation, SEQUENCE_WORD_ANNOTATION);
ast_node!(InlineBranchesSeq, INLINE_BRANCHES_SEQ);
ast_node!(MultilineBranchesSeq, MULTILINE_BRANCHES_SEQ);
ast_node!(MultilineBranchSeq, MULTILINE_BRANCH_SEQ);
ast_node!(BranchContent, BRANCH_CONTENT);

// ── Conditionals ─────────────────────────────────────────────────────

ast_node!(ConditionalWithExpr, CONDITIONAL_WITH_EXPR);
ast_node!(BranchlessCondBody, BRANCHLESS_COND_BODY);
ast_node!(ElseBranch, ELSE_BRANCH);
ast_node!(InlineBranchesCond, INLINE_BRANCHES_COND);
ast_node!(MultilineBranchesCond, MULTILINE_BRANCHES_COND);
ast_node!(MultilineConditional, MULTILINE_CONDITIONAL);
ast_node!(MultilineBranchCond, MULTILINE_BRANCH_COND);
ast_node!(MultilineBranchBody, MULTILINE_BRANCH_BODY);
ast_node!(ImplicitSequence, IMPLICIT_SEQUENCE);

// ── Expressions ──────────────────────────────────────────────────────

ast_node!(InnerExpression, INNER_EXPRESSION);
ast_node!(PrefixExpr, PREFIX_EXPR);
ast_node!(PostfixExpr, POSTFIX_EXPR);
ast_node!(InfixExpr, INFIX_EXPR);
ast_node!(ParenExpr, PAREN_EXPR);
ast_node!(FunctionCall, FUNCTION_CALL);
ast_node!(ArgList, ARG_LIST);
ast_node!(DivertTargetExpr, DIVERT_TARGET_EXPR);
ast_node!(ListExpr, LIST_EXPR);

// ── Diverts ──────────────────────────────────────────────────────────

ast_node!(DivertNode, DIVERT_NODE);
ast_node!(SimpleDivert, SIMPLE_DIVERT);
ast_node!(DivertTargetWithArgs, DIVERT_TARGET_WITH_ARGS);
ast_node!(ThreadStart, THREAD_START);
ast_node!(TunnelOnwardsNode, TUNNEL_ONWARDS_NODE);
ast_node!(TunnelCallNode, TUNNEL_CALL_NODE);

// ── Identifiers ──────────────────────────────────────────────────────

ast_node!(Identifier, IDENTIFIER);
ast_node!(Path, PATH);

// ── Declarations ─────────────────────────────────────────────────────

ast_node!(VarDecl, VAR_DECL);
ast_node!(ConstDecl, CONST_DECL);
ast_node!(ListDecl, LIST_DECL);
ast_node!(ListDef, LIST_DEF);
ast_node!(ListMember, LIST_MEMBER);
ast_node!(ListMemberOn, LIST_MEMBER_ON);
ast_node!(ListMemberOff, LIST_MEMBER_OFF);
ast_node!(FunctionParamList, FUNCTION_PARAM_LIST);

// ── Literals ─────────────────────────────────────────────────────────

ast_node!(IntegerLit, INTEGER_LIT);
ast_node!(FloatLit, FLOAT_LIT);
ast_node!(StringLit, STRING_LIT);
ast_node!(BooleanLit, BOOLEAN_LIT);

// ── Error recovery ───────────────────────────────────────────────────

ast_node!(Error, ERROR);

// ── Expression enum ──────────────────────────────────────────────────

/// A typed expression node.
///
/// Covers every node kind the Pratt expression parser can produce.
#[derive(Clone, PartialEq, Eq, Hash)]
pub enum Expr {
    Prefix(PrefixExpr),
    Postfix(PostfixExpr),
    Infix(InfixExpr),
    Paren(ParenExpr),
    FunctionCall(FunctionCall),
    IntegerLit(IntegerLit),
    FloatLit(FloatLit),
    StringLit(StringLit),
    BooleanLit(BooleanLit),
    Path(Path),
    ListExpr(ListExpr),
    DivertTarget(DivertTargetExpr),
}

impl std::fmt::Debug for Expr {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        std::fmt::Debug::fmt(self.syntax(), f)
    }
}

impl std::fmt::Display for Expr {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        std::fmt::Display::fmt(&self.syntax().text(), f)
    }
}

impl crate::ast::AstNode for Expr {
    fn can_cast(kind: SyntaxKind) -> bool {
        matches!(
            kind,
            SyntaxKind::PREFIX_EXPR
                | SyntaxKind::POSTFIX_EXPR
                | SyntaxKind::INFIX_EXPR
                | SyntaxKind::PAREN_EXPR
                | SyntaxKind::FUNCTION_CALL
                | SyntaxKind::INTEGER_LIT
                | SyntaxKind::FLOAT_LIT
                | SyntaxKind::STRING_LIT
                | SyntaxKind::BOOLEAN_LIT
                | SyntaxKind::PATH
                | SyntaxKind::LIST_EXPR
                | SyntaxKind::DIVERT_TARGET_EXPR
        )
    }

    fn cast(node: SyntaxNode) -> Option<Self> {
        match node.kind() {
            SyntaxKind::PREFIX_EXPR => PrefixExpr::cast(node).map(Expr::Prefix),
            SyntaxKind::POSTFIX_EXPR => PostfixExpr::cast(node).map(Expr::Postfix),
            SyntaxKind::INFIX_EXPR => InfixExpr::cast(node).map(Expr::Infix),
            SyntaxKind::PAREN_EXPR => ParenExpr::cast(node).map(Expr::Paren),
            SyntaxKind::FUNCTION_CALL => FunctionCall::cast(node).map(Expr::FunctionCall),
            SyntaxKind::INTEGER_LIT => IntegerLit::cast(node).map(Expr::IntegerLit),
            SyntaxKind::FLOAT_LIT => FloatLit::cast(node).map(Expr::FloatLit),
            SyntaxKind::STRING_LIT => StringLit::cast(node).map(Expr::StringLit),
            SyntaxKind::BOOLEAN_LIT => BooleanLit::cast(node).map(Expr::BooleanLit),
            SyntaxKind::PATH => Path::cast(node).map(Expr::Path),
            SyntaxKind::LIST_EXPR => ListExpr::cast(node).map(Expr::ListExpr),
            SyntaxKind::DIVERT_TARGET_EXPR => DivertTargetExpr::cast(node).map(Expr::DivertTarget),
            _ => None,
        }
    }

    fn syntax(&self) -> &SyntaxNode {
        match self {
            Expr::Prefix(n) => n.syntax(),
            Expr::Postfix(n) => n.syntax(),
            Expr::Infix(n) => n.syntax(),
            Expr::Paren(n) => n.syntax(),
            Expr::FunctionCall(n) => n.syntax(),
            Expr::IntegerLit(n) => n.syntax(),
            Expr::FloatLit(n) => n.syntax(),
            Expr::StringLit(n) => n.syntax(),
            Expr::BooleanLit(n) => n.syntax(),
            Expr::Path(n) => n.syntax(),
            Expr::ListExpr(n) => n.syntax(),
            Expr::DivertTarget(n) => n.syntax(),
        }
    }
}

// ── Content node accessor macro ─────────────────────────────────────

/// Generates shared content-element accessors for nodes that contain
/// mixed inline content (`TEXT`, `INLINE_LOGIC`, `GLUE_NODE`, `ESCAPE`).
macro_rules! content_node_accessors {
    ($name:ident) => {
        impl $name {
            pub fn texts(&self) -> impl Iterator<Item = Text> {
                support::children(&self.syntax)
            }

            pub fn inline_logics(&self) -> impl Iterator<Item = InlineLogic> {
                support::children(&self.syntax)
            }

            pub fn glue_nodes(&self) -> impl Iterator<Item = GlueNode> {
                support::children(&self.syntax)
            }

            pub fn escapes(&self) -> impl Iterator<Item = Escape> {
                support::children(&self.syntax)
            }
        }
    };
}

content_node_accessors!(ChoiceStartContent);
content_node_accessors!(ChoiceBracketContent);
content_node_accessors!(ChoiceInnerContent);
content_node_accessors!(BranchContent);

// ═══════════════════════════════════════════════════════════════════════
// Accessors
// ═══════════════════════════════════════════════════════════════════════

// ── SourceFile ───────────────────────────────────────────────────────

impl SourceFile {
    pub fn knots(&self) -> impl Iterator<Item = KnotDef> {
        support::children(&self.syntax)
    }

    pub fn includes(&self) -> impl Iterator<Item = IncludeStmt> {
        support::children(&self.syntax)
    }

    pub fn externals(&self) -> impl Iterator<Item = ExternalDecl> {
        support::children(&self.syntax)
    }

    pub fn stitches(&self) -> impl Iterator<Item = StitchDef> {
        support::children(&self.syntax)
    }

    pub fn var_decls(&self) -> impl Iterator<Item = VarDecl> {
        support::children(&self.syntax)
    }

    pub fn const_decls(&self) -> impl Iterator<Item = ConstDecl> {
        support::children(&self.syntax)
    }

    pub fn list_decls(&self) -> impl Iterator<Item = ListDecl> {
        support::children(&self.syntax)
    }

    pub fn content_lines(&self) -> impl Iterator<Item = ContentLine> {
        support::children(&self.syntax)
    }

    pub fn logic_lines(&self) -> impl Iterator<Item = LogicLine> {
        support::children(&self.syntax)
    }

    pub fn choices(&self) -> impl Iterator<Item = Choice> {
        support::children(&self.syntax)
    }

    pub fn gathers(&self) -> impl Iterator<Item = Gather> {
        support::children(&self.syntax)
    }
}

// ── IncludeStmt ──────────────────────────────────────────────────────

impl IncludeStmt {
    pub fn file_path(&self) -> Option<FilePath> {
        support::child(&self.syntax)
    }
}

// ── FilePath ─────────────────────────────────────────────────────────

impl FilePath {
    /// Returns the raw text of the file path (concatenation of all child tokens).
    pub fn text(&self) -> String {
        self.syntax.text().to_string()
    }
}

// ── ExternalDecl ─────────────────────────────────────────────────────

impl ExternalDecl {
    pub fn identifier(&self) -> Option<Identifier> {
        support::child(&self.syntax)
    }

    pub fn name(&self) -> Option<String> {
        self.identifier().and_then(|id| id.name())
    }

    pub fn param_list(&self) -> Option<FunctionParamList> {
        support::child(&self.syntax)
    }
}

// ── KnotDef ──────────────────────────────────────────────────────────

impl KnotDef {
    pub fn header(&self) -> Option<KnotHeader> {
        support::child(&self.syntax)
    }

    pub fn body(&self) -> Option<KnotBody> {
        support::child(&self.syntax)
    }
}

// ── KnotHeader ───────────────────────────────────────────────────────

impl KnotHeader {
    pub fn function_kw(&self) -> Option<SyntaxToken> {
        support::token(&self.syntax, KW_FUNCTION)
    }

    pub fn is_function(&self) -> bool {
        self.function_kw().is_some()
    }

    pub fn identifier(&self) -> Option<Identifier> {
        support::child(&self.syntax)
    }

    pub fn name(&self) -> Option<String> {
        self.identifier().and_then(|id| id.name())
    }

    pub fn params(&self) -> Option<KnotParams> {
        support::child(&self.syntax)
    }
}

// ── KnotBody ─────────────────────────────────────────────────────────

impl KnotBody {
    pub fn stitches(&self) -> impl Iterator<Item = StitchDef> {
        support::children(&self.syntax)
    }

    pub fn content_lines(&self) -> impl Iterator<Item = ContentLine> {
        support::children(&self.syntax)
    }

    pub fn logic_lines(&self) -> impl Iterator<Item = LogicLine> {
        support::children(&self.syntax)
    }

    pub fn choices(&self) -> impl Iterator<Item = Choice> {
        support::children(&self.syntax)
    }

    pub fn gathers(&self) -> impl Iterator<Item = Gather> {
        support::children(&self.syntax)
    }
}

// ── KnotParams ───────────────────────────────────────────────────────

impl KnotParams {
    pub fn params(&self) -> impl Iterator<Item = KnotParamDecl> {
        support::children(&self.syntax)
    }
}

// ── KnotParamDecl ────────────────────────────────────────────────────

impl KnotParamDecl {
    pub fn divert_token(&self) -> Option<SyntaxToken> {
        support::token(&self.syntax, DIVERT)
    }

    pub fn is_divert(&self) -> bool {
        self.divert_token().is_some()
    }

    pub fn ref_kw(&self) -> Option<SyntaxToken> {
        support::token(&self.syntax, KW_REF)
    }

    pub fn is_ref(&self) -> bool {
        self.ref_kw().is_some()
    }

    pub fn identifier(&self) -> Option<Identifier> {
        support::child(&self.syntax)
    }

    pub fn name(&self) -> Option<String> {
        self.identifier().and_then(|id| id.name())
    }
}

// ── StitchDef ────────────────────────────────────────────────────────

impl StitchDef {
    pub fn header(&self) -> Option<StitchHeader> {
        support::child(&self.syntax)
    }

    pub fn body(&self) -> Option<StitchBody> {
        support::child(&self.syntax)
    }
}

// ── StitchHeader ─────────────────────────────────────────────────────

impl StitchHeader {
    pub fn identifier(&self) -> Option<Identifier> {
        support::child(&self.syntax)
    }

    pub fn name(&self) -> Option<String> {
        self.identifier().and_then(|id| id.name())
    }

    pub fn params(&self) -> Option<KnotParams> {
        support::child(&self.syntax)
    }
}

// ── StitchBody ───────────────────────────────────────────────────────

impl StitchBody {
    pub fn content_lines(&self) -> impl Iterator<Item = ContentLine> {
        support::children(&self.syntax)
    }

    pub fn logic_lines(&self) -> impl Iterator<Item = LogicLine> {
        support::children(&self.syntax)
    }

    pub fn choices(&self) -> impl Iterator<Item = Choice> {
        support::children(&self.syntax)
    }

    pub fn gathers(&self) -> impl Iterator<Item = Gather> {
        support::children(&self.syntax)
    }
}

// ── ContentLine ──────────────────────────────────────────────────────

impl ContentLine {
    pub fn mixed_content(&self) -> Option<MixedContent> {
        support::child(&self.syntax)
    }

    pub fn divert(&self) -> Option<DivertNode> {
        support::child(&self.syntax)
    }

    pub fn tags(&self) -> Option<Tags> {
        support::child(&self.syntax)
    }
}

// ── LogicLine ────────────────────────────────────────────────────────

impl LogicLine {
    pub fn return_stmt(&self) -> Option<ReturnStmt> {
        support::child(&self.syntax)
    }

    pub fn temp_decl(&self) -> Option<TempDecl> {
        support::child(&self.syntax)
    }

    pub fn assignment(&self) -> Option<Assignment> {
        support::child(&self.syntax)
    }
}

// ── TagLine ──────────────────────────────────────────────────────────

impl TagLine {
    pub fn tags(&self) -> Option<Tags> {
        support::child(&self.syntax)
    }
}

// ── ReturnStmt ───────────────────────────────────────────────────────

impl ReturnStmt {
    /// Returns the value expression, if any.
    ///
    /// A bare `return` has no child expression node; `return expr` always
    /// wraps the expression in a typed node (the parser calls `expression()`).
    pub fn value(&self) -> Option<Expr> {
        support::child(&self.syntax)
    }

    /// Returns `true` if the return has a value expression.
    pub fn has_value(&self) -> bool {
        self.value().is_some()
    }
}

// ── TempDecl ─────────────────────────────────────────────────────────

impl TempDecl {
    pub fn identifier(&self) -> Option<Identifier> {
        support::child(&self.syntax)
    }

    pub fn name(&self) -> Option<String> {
        self.identifier().and_then(|id| id.name())
    }

    pub fn eq_token(&self) -> Option<SyntaxToken> {
        support::token(&self.syntax, EQ)
    }

    /// Returns the initializer expression after `=`.
    pub fn value(&self) -> Option<Expr> {
        support::child(&self.syntax)
    }
}

// ── Assignment ───────────────────────────────────────────────────────

impl Assignment {
    pub fn target(&self) -> Option<Expr> {
        self.syntax.children().find_map(Expr::cast)
    }

    /// The assignment operator token (`=`, `+=`, or `-=`).
    pub fn op_token(&self) -> Option<SyntaxToken> {
        self.syntax
            .children_with_tokens()
            .filter_map(rowan::NodeOrToken::into_token)
            .find(|tok| matches!(tok.kind(), EQ | PLUS_EQ | MINUS_EQ))
    }

    /// Returns the right-hand side value expression (the second `Expr` child).
    pub fn value(&self) -> Option<Expr> {
        self.syntax.children().filter_map(Expr::cast).nth(1)
    }
}

// ── MixedContent ─────────────────────────────────────────────────────

impl MixedContent {
    pub fn texts(&self) -> impl Iterator<Item = Text> {
        support::children(&self.syntax)
    }

    pub fn glue_nodes(&self) -> impl Iterator<Item = GlueNode> {
        support::children(&self.syntax)
    }

    pub fn inline_logics(&self) -> impl Iterator<Item = InlineLogic> {
        support::children(&self.syntax)
    }

    pub fn escapes(&self) -> impl Iterator<Item = Escape> {
        support::children(&self.syntax)
    }
}

// ── Choice ───────────────────────────────────────────────────────────

impl Choice {
    pub fn bullets(&self) -> Option<ChoiceBullets> {
        support::child(&self.syntax)
    }

    pub fn label(&self) -> Option<Label> {
        support::child(&self.syntax)
    }

    pub fn conditions(&self) -> impl Iterator<Item = ChoiceCondition> {
        support::children(&self.syntax)
    }

    pub fn start_content(&self) -> Option<ChoiceStartContent> {
        support::child(&self.syntax)
    }

    pub fn bracket_content(&self) -> Option<ChoiceBracketContent> {
        support::child(&self.syntax)
    }

    pub fn inner_content(&self) -> Option<ChoiceInnerContent> {
        support::child(&self.syntax)
    }

    pub fn divert(&self) -> Option<DivertNode> {
        support::child(&self.syntax)
    }

    pub fn tags(&self) -> Option<Tags> {
        support::child(&self.syntax)
    }

    /// Returns an iterator over all TAGS children (tags can appear on
    /// each content region within a choice line).
    pub fn all_tags(&self) -> impl Iterator<Item = Tags> {
        support::children(&self.syntax)
    }
}

// ── ChoiceBullets ────────────────────────────────────────────────────

impl ChoiceBullets {
    /// Number of bullet characters (nesting depth).
    pub fn depth(&self) -> usize {
        self.syntax
            .children_with_tokens()
            .filter_map(rowan::NodeOrToken::into_token)
            .filter(|tok| matches!(tok.kind(), STAR | PLUS))
            .count()
    }

    /// Returns `true` if using `+` (sticky), `false` if using `*`.
    ///
    /// Determined by the first bullet token, matching the reference ink
    /// compiler's behavior for degenerate mixed-bullet cases.
    pub fn is_sticky(&self) -> bool {
        self.syntax
            .children_with_tokens()
            .filter_map(rowan::NodeOrToken::into_token)
            .find(|tok| matches!(tok.kind(), STAR | PLUS))
            .is_some_and(|tok| tok.kind() == PLUS)
    }

    /// Returns `true` if bullets mix `*` and `+` (e.g. `*+`, `+*`).
    ///
    /// Mixed bullets are degenerate input — a diagnostic pass should flag them.
    pub fn is_mixed(&self) -> bool {
        let mut has_star = false;
        let mut has_plus = false;
        for tok in self
            .syntax
            .children_with_tokens()
            .filter_map(rowan::NodeOrToken::into_token)
        {
            match tok.kind() {
                STAR => has_star = true,
                PLUS => has_plus = true,
                _ => {}
            }
        }
        has_star && has_plus
    }
}

// ── Label ────────────────────────────────────────────────────────────

impl Label {
    pub fn identifier(&self) -> Option<Identifier> {
        support::child(&self.syntax)
    }

    pub fn name(&self) -> Option<String> {
        self.identifier().and_then(|id| id.name())
    }
}

// ── Gather ───────────────────────────────────────────────────────────

impl Gather {
    pub fn dashes(&self) -> Option<GatherDashes> {
        support::child(&self.syntax)
    }

    pub fn label(&self) -> Option<Label> {
        support::child(&self.syntax)
    }

    pub fn mixed_content(&self) -> Option<MixedContent> {
        support::child(&self.syntax)
    }

    /// Inline choice on the same line as the gather (e.g. `- * hello`).
    pub fn choice(&self) -> Option<Choice> {
        support::child(&self.syntax)
    }

    pub fn divert(&self) -> Option<DivertNode> {
        support::child(&self.syntax)
    }

    pub fn tags(&self) -> Option<Tags> {
        support::child(&self.syntax)
    }
}

// ── GatherDashes ─────────────────────────────────────────────────────

impl GatherDashes {
    /// Number of dashes (nesting depth).
    pub fn depth(&self) -> usize {
        support::tokens(&self.syntax, MINUS).count()
    }
}

// ── Tags ─────────────────────────────────────────────────────────────

impl Tags {
    pub fn tags(&self) -> impl Iterator<Item = Tag> {
        support::children(&self.syntax)
    }
}

// ── Tag ──────────────────────────────────────────────────────────────

impl Tag {
    /// Returns the tag value with the leading `#` stripped.
    ///
    /// Walks tokens directly rather than string-manipulating the full node text.
    /// The parser guarantees a `HASH` token is always present.
    pub fn text(&self) -> String {
        self.syntax
            .children_with_tokens()
            .filter_map(rowan::NodeOrToken::into_token)
            .filter(|tok| tok.kind() != HASH)
            .map(|tok| tok.text().to_string())
            .collect::<String>()
            .trim()
            .to_string()
    }
}

// ── InlineLogic ──────────────────────────────────────────────────────

impl InlineLogic {
    pub fn inner_expression(&self) -> Option<InnerExpression> {
        support::child(&self.syntax)
    }

    pub fn conditional(&self) -> Option<ConditionalWithExpr> {
        support::child(&self.syntax)
    }

    pub fn sequence(&self) -> Option<SequenceWithAnnotation> {
        support::child(&self.syntax)
    }

    pub fn implicit_sequence(&self) -> Option<ImplicitSequence> {
        support::child(&self.syntax)
    }

    pub fn multiline_conditional(&self) -> Option<MultilineConditional> {
        support::child(&self.syntax)
    }
}

// ── MultilineBlock ───────────────────────────────────────────────────

impl MultilineBlock {
    pub fn conditional(&self) -> Option<ConditionalWithExpr> {
        support::child(&self.syntax)
    }

    pub fn sequence(&self) -> Option<SequenceWithAnnotation> {
        support::child(&self.syntax)
    }

    pub fn branches_cond(&self) -> Option<MultilineBranchesCond> {
        support::child(&self.syntax)
    }
}

// ── SequenceWithAnnotation ───────────────────────────────────────────

impl SequenceWithAnnotation {
    pub fn symbol_annotation(&self) -> Option<SequenceSymbolAnnotation> {
        support::child(&self.syntax)
    }

    pub fn word_annotation(&self) -> Option<SequenceWordAnnotation> {
        support::child(&self.syntax)
    }

    pub fn inline_branches(&self) -> Option<InlineBranchesSeq> {
        support::child(&self.syntax)
    }

    pub fn multiline_branches(&self) -> Option<MultilineBranchesSeq> {
        support::child(&self.syntax)
    }
}

// ── SequenceSymbolAnnotation ──────────────────────────────────────────

impl SequenceSymbolAnnotation {
    pub fn amp_token(&self) -> Option<SyntaxToken> {
        support::token(&self.syntax, AMP)
    }

    pub fn bang_token(&self) -> Option<SyntaxToken> {
        support::token(&self.syntax, BANG)
    }

    pub fn tilde_token(&self) -> Option<SyntaxToken> {
        support::token(&self.syntax, TILDE)
    }

    pub fn dollar_token(&self) -> Option<SyntaxToken> {
        support::token(&self.syntax, DOLLAR)
    }
}

// ── SequenceWordAnnotation ───────────────────────────────────────────

impl SequenceWordAnnotation {
    pub fn stopping_kw(&self) -> Option<SyntaxToken> {
        support::token(&self.syntax, KW_STOPPING)
    }

    pub fn cycle_kw(&self) -> Option<SyntaxToken> {
        support::token(&self.syntax, KW_CYCLE)
    }

    pub fn shuffle_kw(&self) -> Option<SyntaxToken> {
        support::token(&self.syntax, KW_SHUFFLE)
    }

    pub fn once_kw(&self) -> Option<SyntaxToken> {
        support::token(&self.syntax, KW_ONCE)
    }
}

// ── InlineBranchesSeq ────────────────────────────────────────────────

impl InlineBranchesSeq {
    pub fn branches(&self) -> impl Iterator<Item = BranchContent> {
        support::children(&self.syntax)
    }
}

// ── InlineBranchesCond ───────────────────────────────────────────────

impl InlineBranchesCond {
    pub fn branches(&self) -> impl Iterator<Item = BranchContent> {
        support::children(&self.syntax)
    }
}

// ── MultilineBranchesSeq ─────────────────────────────────────────────

impl MultilineBranchesSeq {
    pub fn branches(&self) -> impl Iterator<Item = MultilineBranchSeq> {
        support::children(&self.syntax)
    }
}

// ── MultilineBranchesCond ────────────────────────────────────────────

impl MultilineBranchesCond {
    pub fn branches(&self) -> impl Iterator<Item = MultilineBranchCond> {
        support::children(&self.syntax)
    }
}

// ── MultilineBranchSeq ───────────────────────────────────────────────

impl MultilineBranchSeq {
    pub fn body(&self) -> Option<MultilineBranchBody> {
        support::child(&self.syntax)
    }
}

// ── MultilineBranchCond ──────────────────────────────────────────────

impl MultilineBranchCond {
    /// Returns the branch condition expression (if not an else branch).
    pub fn condition(&self) -> Option<Expr> {
        support::child(&self.syntax)
    }

    pub fn body(&self) -> Option<MultilineBranchBody> {
        support::child(&self.syntax)
    }

    pub fn else_kw(&self) -> Option<SyntaxToken> {
        support::token(&self.syntax, KW_ELSE)
    }

    pub fn is_else(&self) -> bool {
        self.else_kw().is_some()
    }
}

// ── ConditionalWithExpr ──────────────────────────────────────────────

impl ConditionalWithExpr {
    /// Returns the condition expression.
    pub fn condition(&self) -> Option<Expr> {
        support::child(&self.syntax)
    }

    pub fn inline_branches(&self) -> Option<InlineBranchesCond> {
        support::child(&self.syntax)
    }

    pub fn multiline_branches(&self) -> Option<MultilineBranchesCond> {
        support::child(&self.syntax)
    }

    pub fn branchless_body(&self) -> Option<BranchlessCondBody> {
        support::child(&self.syntax)
    }
}

// ── BranchlessCondBody ───────────────────────────────────────────────

impl BranchlessCondBody {
    pub fn texts(&self) -> impl Iterator<Item = Text> {
        support::children(&self.syntax)
    }

    pub fn inline_logics(&self) -> impl Iterator<Item = InlineLogic> {
        support::children(&self.syntax)
    }

    pub fn glue_nodes(&self) -> impl Iterator<Item = GlueNode> {
        support::children(&self.syntax)
    }

    pub fn escapes(&self) -> impl Iterator<Item = Escape> {
        support::children(&self.syntax)
    }

    pub fn logic_lines(&self) -> impl Iterator<Item = LogicLine> {
        support::children(&self.syntax)
    }

    pub fn divert(&self) -> Option<DivertNode> {
        support::child(&self.syntax)
    }

    pub fn content_lines(&self) -> impl Iterator<Item = ContentLine> {
        support::children(&self.syntax)
    }

    pub fn else_branch(&self) -> Option<ElseBranch> {
        support::child(&self.syntax)
    }
}

// ── ElseBranch ───────────────────────────────────────────────────────

impl ElseBranch {
    pub fn branch(&self) -> Option<MultilineBranchCond> {
        support::child(&self.syntax)
    }
}

// ── MultilineConditional ─────────────────────────────────────────────

impl MultilineConditional {
    pub fn branches(&self) -> impl Iterator<Item = MultilineBranchCond> {
        support::children(&self.syntax)
    }
}

// ── ImplicitSequence ─────────────────────────────────────────────────

impl ImplicitSequence {
    pub fn branches(&self) -> impl Iterator<Item = BranchContent> {
        support::children(&self.syntax)
    }
}

// ── PrefixExpr ───────────────────────────────────────────────────────

impl PrefixExpr {
    pub fn op_token(&self) -> Option<SyntaxToken> {
        self.syntax
            .children_with_tokens()
            .filter_map(rowan::NodeOrToken::into_token)
            .find(|tok| matches!(tok.kind(), MINUS | BANG | KW_NOT))
    }

    /// Returns the operand expression.
    pub fn operand(&self) -> Option<Expr> {
        support::child(&self.syntax)
    }
}

// ── PostfixExpr ──────────────────────────────────────────────────────

impl PostfixExpr {
    /// Returns the first operator token (`PLUS` for `++`, `MINUS` for `--`).
    /// Both operators are two adjacent tokens inside this node.
    pub fn op_token(&self) -> Option<SyntaxToken> {
        self.syntax
            .children_with_tokens()
            .filter_map(rowan::NodeOrToken::into_token)
            .find(|tok| matches!(tok.kind(), PLUS | MINUS))
    }

    /// Returns the operand expression.
    pub fn operand(&self) -> Option<Expr> {
        support::child(&self.syntax)
    }
}

// ── InfixExpr ────────────────────────────────────────────────────────

impl InfixExpr {
    pub fn op_token(&self) -> Option<SyntaxToken> {
        self.syntax
            .children_with_tokens()
            .filter_map(rowan::NodeOrToken::into_token)
            .find(|tok| {
                matches!(
                    tok.kind(),
                    PLUS | MINUS
                        | STAR
                        | SLASH
                        | PERCENT
                        | CARET
                        | EQ_EQ
                        | BANG_EQ
                        | LT
                        | GT
                        | LT_EQ
                        | GT_EQ
                        | KW_AND
                        | AMP_AMP
                        | KW_OR
                        | PIPE
                        | KW_MOD
                        | KW_HAS
                        | KW_HASNT
                        | QUESTION
                        | BANG_QUESTION
                        | PLUS_EQ
                        | MINUS_EQ
                )
            })
    }

    pub fn lhs(&self) -> Option<Expr> {
        self.syntax.children().find_map(Expr::cast)
    }

    pub fn rhs(&self) -> Option<Expr> {
        self.syntax.children().filter_map(Expr::cast).nth(1)
    }
}

// ── FunctionCall ─────────────────────────────────────────────────────

impl FunctionCall {
    pub fn identifier(&self) -> Option<Identifier> {
        support::child(&self.syntax)
    }

    pub fn name(&self) -> Option<String> {
        self.identifier().and_then(|id| id.name())
    }

    pub fn arg_list(&self) -> Option<ArgList> {
        support::child(&self.syntax)
    }
}

// ── ArgList ──────────────────────────────────────────────────────────

impl ArgList {
    /// Number of arguments (child expression nodes).
    pub fn arg_count(&self) -> usize {
        self.syntax
            .children()
            .filter(|child| child.kind() != SyntaxKind::ERROR)
            .count()
    }

    /// Iterator over the argument expressions.
    pub fn args(&self) -> impl Iterator<Item = Expr> {
        support::children(&self.syntax)
    }
}

// ── DivertTargetExpr ─────────────────────────────────────────────────

impl DivertTargetExpr {
    pub fn target(&self) -> Option<Path> {
        support::child(&self.syntax)
    }
}

// ── ListExpr ─────────────────────────────────────────────────────────

impl ListExpr {
    pub fn items(&self) -> impl Iterator<Item = Path> {
        support::children(&self.syntax)
    }
}

// ── DivertNode ───────────────────────────────────────────────────────

impl DivertNode {
    pub fn thread_start(&self) -> Option<ThreadStart> {
        support::child(&self.syntax)
    }

    pub fn tunnel_onwards(&self) -> Option<TunnelOnwardsNode> {
        support::child(&self.syntax)
    }

    pub fn tunnel_call(&self) -> Option<TunnelCallNode> {
        support::child(&self.syntax)
    }

    pub fn simple_divert(&self) -> Option<SimpleDivert> {
        support::child(&self.syntax)
    }
}

// ── SimpleDivert ─────────────────────────────────────────────────────

impl SimpleDivert {
    pub fn targets(&self) -> impl Iterator<Item = DivertTargetWithArgs> {
        support::children(&self.syntax)
    }
}

// ── DivertTargetWithArgs ─────────────────────────────────────────────

impl DivertTargetWithArgs {
    pub fn path(&self) -> Option<Path> {
        support::child(&self.syntax)
    }

    pub fn done_kw(&self) -> Option<SyntaxToken> {
        support::token(&self.syntax, KW_DONE)
    }

    pub fn end_kw(&self) -> Option<SyntaxToken> {
        support::token(&self.syntax, KW_END)
    }

    pub fn arg_list(&self) -> Option<ArgList> {
        support::child(&self.syntax)
    }
}

// ── ThreadStart ──────────────────────────────────────────────────────

impl ThreadStart {
    /// Returns the target path.
    ///
    /// The parser produces a `PATH` child directly (not wrapped in
    /// `DivertTargetWithArgs`), so this returns `Option<Path>`.
    pub fn target(&self) -> Option<Path> {
        support::child(&self.syntax)
    }

    pub fn arg_list(&self) -> Option<ArgList> {
        support::child(&self.syntax)
    }
}

// ── TunnelOnwardsNode ────────────────────────────────────────────────

impl TunnelOnwardsNode {
    pub fn targets(&self) -> impl Iterator<Item = DivertTargetWithArgs> {
        support::children(&self.syntax)
    }

    pub fn tunnel_call(&self) -> Option<TunnelCallNode> {
        support::child(&self.syntax)
    }
}

// ── TunnelCallNode ──────────────────────────────────────────────────

impl TunnelCallNode {
    pub fn targets(&self) -> impl Iterator<Item = DivertTargetWithArgs> {
        support::children(&self.syntax)
    }
}

// ── Identifier ───────────────────────────────────────────────────────

impl Identifier {
    pub fn ident_token(&self) -> Option<SyntaxToken> {
        support::token(&self.syntax, IDENT)
    }

    /// Returns the name text, accepting either `IDENT` or keyword tokens
    /// (ink keywords are contextual and may appear as identifiers).
    pub fn name(&self) -> Option<String> {
        self.ident_token()
            .or_else(|| {
                self.syntax
                    .children_with_tokens()
                    .filter_map(rowan::NodeOrToken::into_token)
                    .find(|t| t.kind().is_keyword())
            })
            .map(|t| t.text().to_string())
    }
}

// ── Path ─────────────────────────────────────────────────────────────

impl Path {
    /// Iterator over the segment tokens (`IDENT` or keyword tokens between dots).
    pub fn segments(&self) -> impl Iterator<Item = SyntaxToken> {
        self.syntax
            .children_with_tokens()
            .filter_map(rowan::NodeOrToken::into_token)
            .filter(|t| t.kind() == IDENT || t.kind().is_keyword())
    }

    /// Full dotted name (e.g. `"knot.stitch"`).
    pub fn full_name(&self) -> String {
        self.segments()
            .map(|t| t.text().to_string())
            .collect::<Vec<_>>()
            .join(".")
    }
}

// ── VarDecl ──────────────────────────────────────────────────────────

impl VarDecl {
    pub fn identifier(&self) -> Option<Identifier> {
        support::child(&self.syntax)
    }

    pub fn name(&self) -> Option<String> {
        self.identifier().and_then(|id| id.name())
    }

    /// Returns the initializer expression after `=`.
    pub fn value(&self) -> Option<Expr> {
        support::child(&self.syntax)
    }
}

// ── ConstDecl ────────────────────────────────────────────────────────

impl ConstDecl {
    pub fn identifier(&self) -> Option<Identifier> {
        support::child(&self.syntax)
    }

    pub fn name(&self) -> Option<String> {
        self.identifier().and_then(|id| id.name())
    }

    /// Returns the initializer expression after `=`.
    pub fn value(&self) -> Option<Expr> {
        support::child(&self.syntax)
    }
}

// ── ListDecl ─────────────────────────────────────────────────────────

impl ListDecl {
    pub fn identifier(&self) -> Option<Identifier> {
        support::child(&self.syntax)
    }

    pub fn name(&self) -> Option<String> {
        self.identifier().and_then(|id| id.name())
    }

    pub fn definition(&self) -> Option<ListDef> {
        support::child(&self.syntax)
    }
}

// ── ListDef ──────────────────────────────────────────────────────────

impl ListDef {
    pub fn members(&self) -> impl Iterator<Item = ListMember> {
        support::children(&self.syntax)
    }
}

// ── ListMember ───────────────────────────────────────────────────────

impl ListMember {
    pub fn on_member(&self) -> Option<ListMemberOn> {
        support::child(&self.syntax)
    }

    pub fn off_member(&self) -> Option<ListMemberOff> {
        support::child(&self.syntax)
    }
}

// ── ListMemberOn ─────────────────────────────────────────────────────

impl ListMemberOn {
    pub fn name_token(&self) -> Option<SyntaxToken> {
        // Ink keywords are contextual — accept IDENT or keywords as member names.
        support::ident_or_keyword_token(&self.syntax)
    }

    pub fn name(&self) -> Option<String> {
        self.name_token().map(|t| t.text().to_string())
    }

    pub fn value_token(&self) -> Option<SyntaxToken> {
        support::token(&self.syntax, INTEGER)
    }

    /// Returns the explicit integer value assigned to this member, if any.
    pub fn value(&self) -> Option<i64> {
        self.value_token()
            .and_then(|t| t.text().parse::<i64>().ok())
    }
}

// ── ListMemberOff ────────────────────────────────────────────────────

impl ListMemberOff {
    pub fn name_token(&self) -> Option<SyntaxToken> {
        // Ink keywords are contextual — accept IDENT or keywords as member names.
        support::ident_or_keyword_token(&self.syntax)
    }

    pub fn name(&self) -> Option<String> {
        self.name_token().map(|t| t.text().to_string())
    }

    pub fn value_token(&self) -> Option<SyntaxToken> {
        support::token(&self.syntax, INTEGER)
    }

    /// Returns the explicit integer value assigned to this member, if any.
    pub fn value(&self) -> Option<i64> {
        self.value_token()
            .and_then(|t| t.text().parse::<i64>().ok())
    }
}

// ── FunctionParamList ────────────────────────────────────────────────

impl FunctionParamList {
    /// Iterator over the `Identifier` nodes in the param list.
    pub fn params(&self) -> impl Iterator<Item = Identifier> {
        support::children(&self.syntax)
    }
}

// ── IntegerLit ───────────────────────────────────────────────────────

impl IntegerLit {
    pub fn value_token(&self) -> Option<SyntaxToken> {
        support::token(&self.syntax, INTEGER)
    }

    pub fn value(&self) -> Option<i64> {
        self.value_token()
            .and_then(|t| t.text().parse::<i64>().ok())
    }
}

// ── FloatLit ─────────────────────────────────────────────────────────

impl FloatLit {
    pub fn value_token(&self) -> Option<SyntaxToken> {
        support::token(&self.syntax, FLOAT)
    }

    pub fn value(&self) -> Option<f64> {
        self.value_token()
            .and_then(|t| t.text().parse::<f64>().ok())
    }
}

// ── StringLit ────────────────────────────────────────────────────────

impl StringLit {
    /// Returns the raw content between the quotes (excluding the quotes themselves).
    ///
    /// The opening quote is always present (the parser enters `string_literal`
    /// only on a `QUOTE` token). The closing quote may be absent if the string
    /// is unterminated — the parser emits an error and closes the node without
    /// consuming a trailing `QUOTE`. The `strip_suffix` fallback handles that
    /// error-recovery case.
    pub fn raw_text(&self) -> String {
        let full = self.syntax.text().to_string();
        let trimmed = full.strip_prefix('"').unwrap_or(&full);
        trimmed.strip_suffix('"').unwrap_or(trimmed).to_string()
    }
}

// ── BooleanLit ───────────────────────────────────────────────────────

impl BooleanLit {
    pub fn value(&self) -> Option<bool> {
        let tok = self
            .syntax
            .children_with_tokens()
            .filter_map(rowan::NodeOrToken::into_token)
            .find(|tok| matches!(tok.kind(), KW_TRUE | KW_FALSE))?;
        match tok.kind() {
            KW_TRUE => Some(true),
            KW_FALSE => Some(false),
            _ => None,
        }
    }
}

// ── AuthorWarning ────────────────────────────────────────────────────

impl AuthorWarning {
    /// Returns the warning text with the `TODO:` prefix stripped.
    ///
    /// Walks tokens directly — skips the `KW_TODO` token and the optional
    /// `COLON`, then collects remaining content until `NEWLINE`.
    pub fn text(&self) -> String {
        self.syntax
            .children_with_tokens()
            .filter_map(rowan::NodeOrToken::into_token)
            .skip_while(|tok| matches!(tok.kind(), KW_TODO | COLON) || tok.kind().is_trivia())
            .take_while(|tok| tok.kind() != NEWLINE)
            .map(|tok| tok.text().to_string())
            .collect::<String>()
            .trim()
            .to_string()
    }
}

// ── ChoiceCondition ──────────────────────────────────────────────────

impl ChoiceCondition {
    /// Returns the condition expression inside `{ expr }`.
    pub fn expr(&self) -> Option<Expr> {
        support::child(&self.syntax)
    }
}

// ── InnerExpression ──────────────────────────────────────────────────

impl InnerExpression {
    /// Returns the wrapped expression.
    pub fn expr(&self) -> Option<Expr> {
        support::child(&self.syntax)
    }
}

// ── ParenExpr ────────────────────────────────────────────────────────

impl ParenExpr {
    /// Returns the inner expression inside `( expr )`.
    pub fn inner(&self) -> Option<Expr> {
        support::child(&self.syntax)
    }
}

// ── BranchContent (extra) ────────────────────────────────────────────

impl BranchContent {
    pub fn divert(&self) -> Option<DivertNode> {
        support::child(&self.syntax)
    }
}

// ── MultilineBranchBody ──────────────────────────────────────────────

impl MultilineBranchBody {
    pub fn texts(&self) -> impl Iterator<Item = Text> {
        support::children(&self.syntax)
    }

    pub fn inline_logics(&self) -> impl Iterator<Item = InlineLogic> {
        support::children(&self.syntax)
    }

    pub fn glue_nodes(&self) -> impl Iterator<Item = GlueNode> {
        support::children(&self.syntax)
    }

    pub fn escapes(&self) -> impl Iterator<Item = Escape> {
        support::children(&self.syntax)
    }

    pub fn logic_lines(&self) -> impl Iterator<Item = LogicLine> {
        support::children(&self.syntax)
    }

    pub fn divert(&self) -> Option<DivertNode> {
        support::child(&self.syntax)
    }

    pub fn content_lines(&self) -> impl Iterator<Item = ContentLine> {
        support::children(&self.syntax)
    }

    pub fn choices(&self) -> impl Iterator<Item = Choice> {
        support::children(&self.syntax)
    }
}
