/// DreamStack Parser — recursive descent parser producing AST from tokens.
use crate::ast::*;
use crate::lexer::{Token, TokenKind};

/// Result of resilient parsing — contains both successfully parsed declarations and collected errors.
#[derive(Debug)]
pub struct ParseResult {
    pub program: Program,
    pub errors: Vec<ParseError>,
}

pub struct Parser {
    tokens: Vec<Token>,
    pos: usize,
    source_lines: Vec<String>,
    pending_decls: Vec<Declaration>,
}

impl Parser {
    pub fn new(tokens: Vec<Token>) -> Self {
        Self { tokens, pos: 0, source_lines: Vec::new(), pending_decls: Vec::new() }
    }

    /// Create a parser with source text for enriched error messages.
    pub fn with_source(tokens: Vec<Token>, source: &str) -> Self {
        Self {
            tokens,
            pos: 0,
            source_lines: source.lines().map(String::from).collect(),
            pending_decls: Vec::new(),
        }
    }

    pub fn parse_program(&mut self) -> Result<Program, ParseError> {
        let mut declarations = Vec::new();
        self.skip_newlines();

        while !self.is_at_end() {
            let decl = self.parse_declaration()?;
            declarations.push(decl);
            // Drain any pending declarations from destructuring desugaring
            declarations.extend(self.pending_decls.drain(..));
            self.skip_newlines();
        }

        Ok(Program { declarations })
    }

    /// Parse with error recovery — collects multiple errors instead of stopping at the first.
    /// Returns a `ParseResult` with both the (possibly partial) program and all errors.
    pub fn parse_program_resilient(&mut self) -> ParseResult {
        let mut declarations = Vec::new();
        let mut errors = Vec::new();
        self.skip_newlines();

        while !self.is_at_end() {
            match self.parse_declaration() {
                Ok(decl) => {
                    declarations.push(decl);
                    declarations.extend(self.pending_decls.drain(..));
                }
                Err(err) => {
                    errors.push(err);
                    self.synchronize();
                }
            }
            self.skip_newlines();
        }

        ParseResult {
            program: Program { declarations },
            errors,
        }
    }

    /// Skip tokens until we reach a declaration keyword (error recovery).
    fn synchronize(&mut self) {
        while !self.is_at_end() {
            // If we're at a newline, check if the next non-newline token starts a declaration
            if *self.peek() == TokenKind::Newline {
                self.advance();
                self.skip_newlines();
                if self.is_at_declaration_start() || self.is_at_end() {
                    return;
                }
                continue;
            }
            self.advance();
        }
    }

    /// Check if the current token starts a declaration.
    fn is_at_declaration_start(&self) -> bool {
        matches!(self.peek(),
            TokenKind::Let | TokenKind::View | TokenKind::Component |
            TokenKind::On | TokenKind::Effect | TokenKind::Type |
            TokenKind::Route | TokenKind::Stream | TokenKind::Layout |
            TokenKind::Constrain | TokenKind::Import | TokenKind::Export |
            TokenKind::Every | TokenKind::Enum | TokenKind::Pub |
            TokenKind::DocComment(_)
        )
    }

    // ── Helpers ──────────────────────────────────────────

    fn peek(&self) -> &TokenKind {
        self.tokens
            .get(self.pos)
            .map(|t| &t.kind)
            .unwrap_or(&TokenKind::Eof)
    }

    fn current_token(&self) -> &Token {
        &self.tokens[self.pos.min(self.tokens.len() - 1)]
    }

    fn advance(&mut self) -> &Token {
        let tok = &self.tokens[self.pos.min(self.tokens.len() - 1)];
        self.pos += 1;
        tok
    }

    fn expect(&mut self, expected: &TokenKind) -> Result<&Token, ParseError> {
        if std::mem::discriminant(self.peek()) == std::mem::discriminant(expected) {
            Ok(self.advance())
        } else {
            Err(self.error(format!("expected {expected:?}, got {:?}", self.peek())))
        }
    }

    fn expect_ident(&mut self) -> Result<String, ParseError> {
        match self.peek().clone() {
            TokenKind::Ident(name) => {
                self.advance();
                Ok(name)
            }
            // Also accept keywords that can be used as identifiers in some contexts
            _ => Err(self.error(format!("expected identifier, got {:?}", self.peek()))),
        }
    }

    fn check(&self, kind: &TokenKind) -> bool {
        std::mem::discriminant(self.peek()) == std::mem::discriminant(kind)
    }

    fn is_at_end(&self) -> bool {
        matches!(self.peek(), TokenKind::Eof)
    }

    fn skip_newlines(&mut self) {
        while matches!(self.peek(), TokenKind::Newline) {
            self.advance();
        }
    }

    /// Check if the current token could start a match pattern.
    /// Patterns are: string literals, integer literals, identifiers (as wildcard/binding).
    /// For identifiers, we look ahead to disambiguate: a pattern ident is followed by `->`.
    /// A view element like `text "..."` or `button "..." {}` is NOT a pattern.
    fn can_be_pattern(&self) -> bool {
        match self.peek() {
            // String and integer literals are always valid patterns
            TokenKind::StringFragment(_) | TokenKind::Int(_) => {
                true
            }
            // Boolean literals: `true ->` / `false ->`
            TokenKind::True | TokenKind::False => true,
            // Tuple pattern: `(a, b) ->`
            TokenKind::LParen => true,
            // Identifiers: could be a pattern binding OR a view element
            TokenKind::Ident(name) => {
                // Special case: _ is always a wildcard pattern  
                if name == "_" {
                    return true;
                }
                // Look ahead past the ident to see what follows
                let next_pos = self.pos + 1;
                if next_pos < self.tokens.len() {
                    match &self.tokens[next_pos].kind {
                        TokenKind::Arrow => true,
                        TokenKind::LParen => true,
                        TokenKind::Newline => {
                            let mut peek_pos = next_pos + 1;
                            while peek_pos < self.tokens.len() && self.tokens[peek_pos].kind == TokenKind::Newline {
                                peek_pos += 1;
                            }
                            if peek_pos < self.tokens.len() {
                                matches!(self.tokens[peek_pos].kind, TokenKind::Arrow)
                            } else {
                                false
                            }
                        }
                        _ => false,
                    }
                } else {
                    false
                }
            }
            _ => false,
        }
    }

    fn error(&self, msg: String) -> ParseError {
        let tok = self.current_token();
        let source_line = if tok.line > 0 && tok.line <= self.source_lines.len() {
            Some(self.source_lines[tok.line - 1].clone())
        } else {
            None
        };
        ParseError {
            message: msg,
            line: tok.line,
            col: tok.col,
            source_line,
        }
    }

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

    fn parse_declaration(&mut self) -> Result<Declaration, ParseError> {
        // Collect doc comments before declaration
        let mut doc = None;
        while matches!(self.peek(), TokenKind::DocComment(_)) {
            if let TokenKind::DocComment(text) = self.peek().clone() {
                doc = Some(match doc {
                    Some(existing) => format!("{}\n{}", existing, text),
                    None => text,
                });
            }
            self.advance();
            self.skip_newlines();
        }

        let mut decl = match self.peek() {
            TokenKind::Pub => self.parse_pub_decl()?,
            TokenKind::Let => self.parse_let_decl()?,
            TokenKind::View => self.parse_view_decl()?,
            TokenKind::Effect => self.parse_effect_decl()?,
            TokenKind::On => self.parse_on_handler()?,
            TokenKind::Component => self.parse_component_decl()?,
            TokenKind::Route => self.parse_route_decl()?,
            TokenKind::Constrain => self.parse_constrain_decl()?,
            TokenKind::Stream => self.parse_stream_decl()?,
            TokenKind::Every => self.parse_every_decl()?,
            TokenKind::Import => self.parse_import_decl()?,
            TokenKind::Export => self.parse_export_decl()?,
            TokenKind::Type => self.parse_type_alias_decl()?,
            TokenKind::Layout => self.parse_layout_decl()?,
            TokenKind::Enum => self.parse_enum_decl()?,
            // Expression statement: `log("hello")`, `push(items, x)`
            TokenKind::Ident(_) => {
                let expr = self.parse_expr()?;
                Declaration::ExprStatement(expr)
            }
            _ => return Err(self.error(format!(
                "expected declaration (let, view, effect, on, component, route, constrain, stream, every, type, layout, enum, pub), got {:?}",
                self.peek()
            ))),
        };

        // Attach doc comment to the parsed declaration
        if let Some(doc_text) = doc {
            match &mut decl {
                Declaration::Let(d) => d.doc = Some(doc_text),
                Declaration::View(d) => d.doc = Some(doc_text),
                Declaration::Component(d) => d.doc = Some(doc_text),
                Declaration::Effect(d) => d.doc = Some(doc_text),
                Declaration::TypeAlias(d) => d.doc = Some(doc_text),
                Declaration::Enum(d) => d.doc = Some(doc_text),
                _ => {} // doc comments on other decls are silently ignored
            }
        }

        Ok(decl)
    }

    /// Parse `pub let ...`, `pub component ...`, `pub view ...`, `pub enum ...`
    fn parse_pub_decl(&mut self) -> Result<Declaration, ParseError> {
        self.advance(); // consume 'pub'
        self.skip_newlines();
        let mut decl = match self.peek() {
            TokenKind::Let => self.parse_let_decl()?,
            TokenKind::View => self.parse_view_decl()?,
            TokenKind::Component => self.parse_component_decl()?,
            TokenKind::Type => self.parse_type_alias_decl()?,
            TokenKind::Enum => self.parse_enum_decl()?,
            _ => return Err(self.error(format!(
                "expected let, view, component, type, or enum after 'pub', got {:?}",
                self.peek()
            ))),
        };
        // Set visibility to Public
        match &mut decl {
            Declaration::Let(d) => d.visibility = Visibility::Public,
            Declaration::View(d) => d.visibility = Visibility::Public,
            Declaration::Component(d) => d.visibility = Visibility::Public,
            Declaration::TypeAlias(d) => d.visibility = Visibility::Public,
            Declaration::Enum(d) => d.visibility = Visibility::Public,
            _ => {}
        }
        Ok(decl)
    }

    /// Parse `enum Status { Loading, Ok(data), Error(msg) }`
    fn parse_enum_decl(&mut self) -> Result<Declaration, ParseError> {
        let line = self.current_token().line;
        self.advance(); // consume 'enum'
        let name = self.expect_ident()?;
        self.expect(&TokenKind::LBrace)?;
        self.skip_newlines();

        let mut variants = Vec::new();
        while !self.check(&TokenKind::RBrace) && !self.is_at_end() {
            let var_line = self.current_token().line;
            let var_name = self.expect_ident()?;

            // Optional fields: `Ok(value: T)` or `Ok(value)`
            let fields = if self.check(&TokenKind::LParen) {
                self.parse_params()?
            } else {
                Vec::new()
            };

            variants.push(EnumVariant {
                name: var_name,
                fields,
                span: Span { start: 0, end: 0, line: var_line, col: 0 },
            });

            self.skip_newlines();
            if self.check(&TokenKind::Comma) {
                self.advance();
            }
            self.skip_newlines();
        }

        self.expect(&TokenKind::RBrace)?;
        Ok(Declaration::Enum(EnumDecl {
            name,
            variants,
            visibility: Visibility::Private,
            doc: None,
            span: Span { start: 0, end: 0, line, col: 0 },
        }))
    }

    fn parse_every_decl(&mut self) -> Result<Declaration, ParseError> {
        let line = self.current_token().line;
        self.advance(); // consume 'every'
        let interval = self.parse_expr()?;
        self.expect(&TokenKind::Arrow)?;
        self.skip_newlines();
        let body = self.parse_expr()?;
        Ok(Declaration::Every(EveryDecl {
            interval_ms: interval,
            body,
            span: Span { start: 0, end: 0, line, col: 0 },
        }))
    }

    // import { Card, Button } from "./components"
    fn parse_import_decl(&mut self) -> Result<Declaration, ParseError> {
        let line = self.current_token().line;
        self.advance(); // consume 'import'

        // Parse the import names: { name1, name2 }
        self.expect(&TokenKind::LBrace)?;
        let mut names = Vec::new();
        loop {
            self.skip_newlines();
            if self.check(&TokenKind::RBrace) {
                break;
            }
            match self.peek().clone() {
                TokenKind::Ident(name) => {
                    names.push(name);
                    self.advance();
                }
                // Allow keywords to be imported as names (e.g., component names)
                _ => {
                    let tok = self.peek().clone();
                    return Err(self.error(format!("expected identifier in import, got {:?}", tok)));
                }
            }
            self.skip_newlines();
            if self.check(&TokenKind::Comma) {
                self.advance(); // consume ','
            }
        }
        self.expect(&TokenKind::RBrace)?;

        // Parse "from"
        self.expect(&TokenKind::From)?;

        // Parse the source path
        let source = match self.peek().clone() {
            TokenKind::StringFragment(s) => {
                self.advance();
                // Consume the StringEnd if present
                if self.check(&TokenKind::StringEnd) {
                    self.advance();
                }
                s
            }
            TokenKind::StringEnd => {
                self.advance();
                String::new()
            }
            _ => return Err(self.error("expected string after 'from'".to_string())),
        };

        Ok(Declaration::Import(ImportDecl {
            names,
            source,
            span: Span { start: 0, end: 0, line, col: 0 },
        }))
    }

    // export let count = 0 / export component Card(...) = ...
    fn parse_export_decl(&mut self) -> Result<Declaration, ParseError> {
        self.advance(); // consume 'export'

        // Parse the inner declaration
        let inner = self.parse_declaration()?;

        // Extract the name being exported
        let name = match &inner {
            Declaration::Let(d) => d.name.clone(),
            Declaration::View(d) => d.name.clone(),
            Declaration::Component(d) => d.name.clone(),
            Declaration::Effect(d) => d.name.clone(),
            _ => return Err(self.error("can only export let, view, component, or effect".to_string())),
        };

        Ok(Declaration::Export(name, Box::new(inner)))
    }

    fn parse_let_decl(&mut self) -> Result<Declaration, ParseError> {
        let line = self.current_token().line;
        self.advance(); // consume 'let'

        // Object destructuring: `let { a, b } = expr`
        if self.check(&TokenKind::LBrace) {
            self.advance(); // consume '{'
            let mut names = Vec::new();
            loop {
                self.skip_newlines();
                if self.check(&TokenKind::RBrace) { break; }
                names.push(self.expect_ident()?);
                self.skip_newlines();
                if self.check(&TokenKind::Comma) { self.advance(); }
            }
            self.expect(&TokenKind::RBrace)?;
            self.expect(&TokenKind::Eq)?;
            let value = self.parse_expr()?;

            if names.is_empty() {
                return Err(self.error("empty destructuring pattern".to_string()));
            }

            // Desugar: let { a, b } = expr → let _tmp = expr; let a = _tmp.a; let b = _tmp.b;
            let temp = format!("_ds_{}", names.join("_"));
            let mut all_decls = vec![Declaration::Let(LetDecl {
                name: temp.clone(),
                type_annotation: None,
                value,
                    visibility: Visibility::Private,
                    doc: None,
                span: Span { start: 0, end: 0, line, col: 0 },
            })];
            for field in &names {
                all_decls.push(Declaration::Let(LetDecl {
                    name: field.clone(),
                    type_annotation: None,
                    value: Expr::DotAccess(Box::new(Expr::Ident(temp.clone())), field.clone()),
                    visibility: Visibility::Private,
                    doc: None,
                    span: Span { start: 0, end: 0, line, col: 0 },
                }));
            }
            // Return first decl, queue rest for injection by parse_program
            self.pending_decls.extend(all_decls.drain(1..));
            return Ok(all_decls.into_iter().next().unwrap());
        }

        // Array destructuring: `let [a, b] = expr`
        if self.check(&TokenKind::LBracket) {
            self.advance(); // consume '['
            let mut names = Vec::new();
            loop {
                self.skip_newlines();
                if self.check(&TokenKind::RBracket) { break; }
                names.push(self.expect_ident()?);
                self.skip_newlines();
                if self.check(&TokenKind::Comma) { self.advance(); }
            }
            self.expect(&TokenKind::RBracket)?;
            self.expect(&TokenKind::Eq)?;
            let value = self.parse_expr()?;

            if names.is_empty() {
                return Err(self.error("empty array destructuring pattern".to_string()));
            }

            // Desugar: let [a, b] = expr → let _tmp = expr; let a = _tmp[0]; let b = _tmp[1];
            let temp = format!("_ds_{}", names.join("_"));
            let mut all_decls = vec![Declaration::Let(LetDecl {
                name: temp.clone(),
                type_annotation: None,
                value,
                    visibility: Visibility::Private,
                    doc: None,
                span: Span { start: 0, end: 0, line, col: 0 },
            })];
            for (i, name) in names.iter().enumerate() {
                all_decls.push(Declaration::Let(LetDecl {
                    name: name.clone(),
                    type_annotation: None,
                    value: Expr::Index(
                        Box::new(Expr::Ident(temp.clone())),
                        Box::new(Expr::IntLit(i as i64)),
                    ),
                    visibility: Visibility::Private,
                    doc: None,
                    span: Span { start: 0, end: 0, line, col: 0 },
                }));
            }
            self.pending_decls.extend(all_decls.drain(1..));
            return Ok(all_decls.into_iter().next().unwrap());
        }

        let name = self.expect_ident()?;

        // Optional type annotation: `let name: Type = value`
        let type_annotation = if self.check(&TokenKind::Colon) {
            self.advance(); // consume ':'
            Some(self.parse_type_expr()?)
        } else {
            None
        };

        self.expect(&TokenKind::Eq)?;
        let value = self.parse_expr()?;

        Ok(Declaration::Let(LetDecl {
            name,
            type_annotation,
            value,
                    visibility: Visibility::Private,
                    doc: None,
            span: Span { start: 0, end: 0, line, col: 0 },
        }))
    }

    /// Parse a type alias: `type PositiveInt = Int where value > 0`
    fn parse_type_alias_decl(&mut self) -> Result<Declaration, ParseError> {
        let line = self.current_token().line;
        self.advance(); // consume 'type'
        let name = self.expect_ident()?;
        self.expect(&TokenKind::Eq)?;
        let definition = self.parse_type_expr()?;

        Ok(Declaration::TypeAlias(TypeAliasDecl {
            name,
            definition,
            visibility: Visibility::Private,
            doc: None,
            span: Span { start: 0, end: 0, line, col: 0 },
        }))
    }

    /// Parse a type expression: `Int`, `Array<String>`, `Int where value > 0`
    fn parse_type_expr(&mut self) -> Result<TypeExpr, ParseError> {
        // Parse base type name
        let name = match self.peek().clone() {
            TokenKind::Ident(n) => { self.advance(); n }
            _ => return Err(self.error(format!("expected type name, got {:?}", self.peek()))),
        };

        // Optional generic params: `<Type1, Type2>`
        let base = if self.check(&TokenKind::Lt) {
            self.advance(); // <
            let mut params = Vec::new();
            loop {
                params.push(self.parse_type_expr()?);
                if self.check(&TokenKind::Comma) {
                    self.advance();
                } else {
                    break;
                }
            }
            self.expect(&TokenKind::Gt)?;
            TypeExpr::Generic(name, params)
        } else {
            TypeExpr::Named(name)
        };

        // Optional `where` predicate
        if self.check(&TokenKind::Where) {
            self.advance(); // consume 'where'
            let predicate = self.parse_expr()?;
            Ok(TypeExpr::Refined {
                base: Box::new(base),
                predicate: Box::new(predicate),
            })
        } else {
            Ok(base)
        }
    }

    /// Parse a layout declaration: `layout name { constraints }`
    fn parse_layout_decl(&mut self) -> Result<Declaration, ParseError> {
        let line = self.current_token().line;
        self.advance(); // consume 'layout'
        let name = self.expect_ident()?;
        self.expect(&TokenKind::LBrace)?;
        self.skip_newlines();

        let mut constraints = Vec::new();
        while !self.check(&TokenKind::RBrace) && !matches!(self.peek(), TokenKind::Eof) {
            let constraint = self.parse_layout_constraint()?;
            constraints.push(constraint);
            self.skip_newlines();
        }

        self.expect(&TokenKind::RBrace)?;
        Ok(Declaration::Layout(LayoutDecl {
            name,
            constraints,
            span: Span { start: 0, end: 0, line, col: 0 },
        }))
    }

    /// Parse a single constraint: `left_expr op right_expr [strength]`
    fn parse_layout_constraint(&mut self) -> Result<LayoutConstraint, ParseError> {
        let left = self.parse_layout_additive()?;

        let op = match self.peek() {
            TokenKind::EqEq => ConstraintOp::Eq,
            TokenKind::Gte => ConstraintOp::Gte,
            TokenKind::Lte => ConstraintOp::Lte,
            other => return Err(self.error(format!("expected ==, >=, or <= in layout constraint, got {:?}", other))),
        };
        self.advance();

        let right = self.parse_layout_additive()?;

        // Optional strength: [required] [strong] [weak]
        let strength = if self.check(&TokenKind::LBracket) {
            self.advance();
            let s = match self.peek() {
                TokenKind::Ident(n) if n == "required" => ConstraintStrength::Required,
                TokenKind::Ident(n) if n == "strong" => ConstraintStrength::Strong,
                TokenKind::Ident(n) if n == "medium" => ConstraintStrength::Medium,
                TokenKind::Ident(n) if n == "weak" => ConstraintStrength::Weak,
                _ => ConstraintStrength::Required,
            };
            self.advance();
            self.expect(&TokenKind::RBracket)?;
            s
        } else {
            ConstraintStrength::Required
        };

        Ok(LayoutConstraint { left, op, right, strength })
    }

    /// Parse layout additive: `term (+ | - term)*`
    fn parse_layout_additive(&mut self) -> Result<LayoutExpr, ParseError> {
        let mut left = self.parse_layout_multiplicative()?;
        loop {
            if self.check(&TokenKind::Plus) {
                self.advance();
                let right = self.parse_layout_multiplicative()?;
                left = LayoutExpr::Add(Box::new(left), Box::new(right));
            } else if self.check(&TokenKind::Minus) {
                self.advance();
                let right = self.parse_layout_multiplicative()?;
                left = LayoutExpr::Sub(Box::new(left), Box::new(right));
            } else {
                break;
            }
        }
        Ok(left)
    }

    /// Parse layout multiplicative: `atom (* atom)*`
    fn parse_layout_multiplicative(&mut self) -> Result<LayoutExpr, ParseError> {
        let mut left = self.parse_layout_atom()?;
        while self.check(&TokenKind::Star) {
            self.advance();
            let right = self.parse_layout_atom()?;
            left = LayoutExpr::Mul(Box::new(left), Box::new(right));
        }
        Ok(left)
    }

    /// Parse layout atom: `element.prop` | `number` | `(expr)`
    fn parse_layout_atom(&mut self) -> Result<LayoutExpr, ParseError> {
        match self.peek().clone() {
            TokenKind::Int(n) => {
                self.advance();
                Ok(LayoutExpr::Const(n as f64))
            }
            TokenKind::Float(f) => {
                self.advance();
                Ok(LayoutExpr::Const(f))
            }
            TokenKind::Ident(name) => {
                self.advance();
                if self.check(&TokenKind::Dot) {
                    self.advance();
                    let prop = self.expect_ident()?;
                    Ok(LayoutExpr::Prop(name, prop))
                } else {
                    // Bare identifier treated as element.value
                    Ok(LayoutExpr::Prop(name, "value".to_string()))
                }
            }
            TokenKind::LParen => {
                self.advance();
                let expr = self.parse_layout_additive()?;
                self.expect(&TokenKind::RParen)?;
                Ok(expr)
            }
            other => Err(self.error(format!("expected layout expression, got {:?}", other))),
        }
    }

    fn parse_view_decl(&mut self) -> Result<Declaration, ParseError> {
        let line = self.current_token().line;
        self.advance(); // consume 'view'
        let name = self.expect_ident()?;

        // Optional parameters
        let params = if self.check(&TokenKind::LParen) {
            self.parse_params()?
        } else {
            Vec::new()
        };

        self.expect(&TokenKind::Eq)?;
        self.skip_newlines();
        let body = self.parse_expr()?;

        Ok(Declaration::View(ViewDecl {
            name,
            params,
            body,
            visibility: Visibility::Private,
            doc: None,
            span: Span { start: 0, end: 0, line, col: 0 },
        }))
    }

    fn parse_effect_decl(&mut self) -> Result<Declaration, ParseError> {
        let line = self.current_token().line;
        self.advance(); // consume 'effect'
        let name = self.expect_ident()?;
        let params = self.parse_params()?;
        self.expect(&TokenKind::Colon)?;
        let return_type = self.parse_type_expr()?;

        Ok(Declaration::Effect(EffectDecl {
            name,
            params,
            return_type,
            doc: None,
            span: Span { start: 0, end: 0, line, col: 0 },
        }))
    }

    fn parse_on_handler(&mut self) -> Result<Declaration, ParseError> {
        let line = self.current_token().line;
        self.advance(); // consume 'on'
        let event = self.expect_ident()?;

        // Optional parameter: `on drag(event) ->`
        let param = if self.check(&TokenKind::LParen) {
            self.advance();
            let p = self.expect_ident()?;
            self.expect(&TokenKind::RParen)?;
            Some(p)
        } else {
            None
        };

        self.expect(&TokenKind::Arrow)?;
        self.skip_newlines();
        let body = self.parse_expr()?;

        Ok(Declaration::OnHandler(OnHandler {
            event,
            param,
            body,
            span: Span { start: 0, end: 0, line, col: 0 },
        }))
    }

    /// `component Card(title: String) = column [ ... ]`
    fn parse_component_decl(&mut self) -> Result<Declaration, ParseError> {
        let line = self.current_token().line;
        self.advance(); // consume 'component'
        let name = self.expect_ident()?;

        // Props
        let props = if self.check(&TokenKind::LParen) {
            self.parse_params()?
        } else {
            Vec::new()
        };

        self.expect(&TokenKind::Eq)?;
        self.skip_newlines();
        let body = self.parse_expr()?;

        Ok(Declaration::Component(ComponentDecl {
            name,
            props,
            body,
            visibility: Visibility::Private,
            doc: None,
            span: Span { start: 0, end: 0, line, col: 0 },
        }))
    }

    /// `route "/users/:id" -> column [ ... ]`
    fn parse_route_decl(&mut self) -> Result<Declaration, ParseError> {
        let line = self.current_token().line;
        self.advance(); // consume 'route'

        // Path string: consume "path" as StringStart + StringFragment + StringEnd
        let path = if matches!(self.peek(), TokenKind::StringFragment(_)) {
            // Simple string token (no interpolation)
            if let TokenKind::StringFragment(s) = self.peek() {
                let p = s.clone();
                self.advance(); // consume fragment
                // Consume trailing StringEnd if present
                if matches!(self.peek(), TokenKind::StringEnd) {
                    self.advance();
                }
                p
            } else {
                return Err(self.error("expected path string after 'route'".to_string()));
            }
        } else {
            return Err(self.error("expected path string after 'route'".to_string()));
        };

        self.expect(&TokenKind::Arrow)?;
        self.skip_newlines();
        let body = self.parse_expr()?;

        Ok(Declaration::Route(RouteDecl {
            path,
            body,
            span: Span { start: 0, end: 0, line, col: 0 },
        }))
    }

    /// `constrain sidebar.width = clamp(200, viewport.width * 0.2, 350)`
    fn parse_constrain_decl(&mut self) -> Result<Declaration, ParseError> {
        let line = self.current_token().line;
        self.advance(); // consume 'constrain'

        let element = self.expect_ident()?;
        self.expect(&TokenKind::Dot)?;
        let prop = self.expect_ident()?;

        self.expect(&TokenKind::Eq)?;
        self.skip_newlines();
        let expr = self.parse_expr()?;

        Ok(Declaration::Constrain(ConstrainDecl {
            element,
            prop,
            expr,
            span: Span { start: 0, end: 0, line, col: 0 },
        }))
    }

    /// Parse: `stream <view_name> on <url_expr> { mode: signal, output: a, b }`
    fn parse_stream_decl(&mut self) -> Result<Declaration, ParseError> {
        let line = self.current_token().line;
        self.advance(); // consume `stream`

        let view_name = self.expect_ident()?;

        // Expect `on`
        match self.peek() {
            TokenKind::On => { self.advance(); }
            _ => return Err(self.error("Expected 'on' after stream view name".into())),
        }

        let relay_url = self.parse_expr()?;

        // Optional config block: `{ mode: signal, transport: webrtc, output: a, b }`
        let mut mode = StreamMode::Signal;
        let mut transport = StreamTransport::WebSocket;
        let mut output: Vec<String> = Vec::new();

        if self.check(&TokenKind::LBrace) {
            self.advance(); // {
            while !self.check(&TokenKind::RBrace) && !self.is_at_end() {
                self.skip_newlines();
                let key = self.expect_ident()?;
                self.expect(&TokenKind::Colon)?;
                if key == "mode" {
                    match self.peek() {
                        TokenKind::Pixel => { mode = StreamMode::Pixel; self.advance(); }
                        TokenKind::Delta => { mode = StreamMode::Delta; self.advance(); }
                        TokenKind::Signals => { mode = StreamMode::Signal; self.advance(); }
                        TokenKind::Ident(s) if s == "signal" => { mode = StreamMode::Signal; self.advance(); }
                        _ => return Err(self.error("Expected pixel, delta, or signal".into())),
                    }
                } else if key == "transport" {
                    match self.peek() {
                        TokenKind::Ident(s) if s == "webrtc" || s == "WebRTC" => {
                            transport = StreamTransport::WebRTC;
                            self.advance();
                        }
                        TokenKind::Ident(s) if s == "websocket" || s == "ws" => {
                            transport = StreamTransport::WebSocket;
                            self.advance();
                        }
                        _ => return Err(self.error("Expected webrtc or websocket".into())),
                    }
                } else if key == "output" {
                    // Parse comma-separated identifiers: output: count, doubled
                    loop {
                        let name = self.expect_ident()?;
                        output.push(name);
                        if self.check(&TokenKind::Comma) {
                            self.advance();
                            // Peek ahead: if next is a known key or RBrace, stop
                            // (the comma was a field separator, not an output separator)
                            match self.peek() {
                                TokenKind::RBrace => break,
                                TokenKind::Ident(s) if s == "mode" || s == "transport" || s == "output" => break,
                                _ => {} // continue parsing output names
                            }
                        } else {
                            break;
                        }
                    }
                }
                if self.check(&TokenKind::Comma) { self.advance(); }
                self.skip_newlines();
            }
            self.expect(&TokenKind::RBrace)?;
        }

        Ok(Declaration::Stream(StreamDecl {
            view_name,
            relay_url,
            mode,
            transport,
            output,
            span: Span { start: 0, end: 0, line, col: 0 },
        }))
    }

    fn parse_params(&mut self) -> Result<Vec<Param>, ParseError> {
        self.expect(&TokenKind::LParen)?;
        let mut params = Vec::new();

        while !self.check(&TokenKind::RParen) && !self.is_at_end() {
            let name = self.expect_ident()?;
            let type_annotation = if self.check(&TokenKind::Colon) {
                self.advance();
                Some(self.parse_type_expr()?)
            } else {
                None
            };
            params.push(Param { name, type_annotation });
            if self.check(&TokenKind::Comma) {
                self.advance();
            }
        }

        self.expect(&TokenKind::RParen)?;
        Ok(params)
    }




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

    fn parse_expr(&mut self) -> Result<Expr, ParseError> {
        self.parse_pipe_expr()
    }

    /// Pipe: `expr | operator`
    fn parse_pipe_expr(&mut self) -> Result<Expr, ParseError> {
        let mut left = self.parse_assignment()?;

        while self.check(&TokenKind::Pipe) {
            self.advance();
            self.skip_newlines();
            let right = self.parse_assignment()?;
            left = Expr::Pipe(Box::new(left), Box::new(right));
        }

        Ok(left)
    }

    /// Assignment: `x = value`, `x += 1`
    fn parse_assignment(&mut self) -> Result<Expr, ParseError> {
        let expr = self.parse_or()?;

        match self.peek() {
            TokenKind::Eq => {
                self.advance();
                self.skip_newlines();
                let value = self.parse_expr()?;
                Ok(Expr::Assign(Box::new(expr), AssignOp::Set, Box::new(value)))
            }
            TokenKind::PlusEq => {
                self.advance();
                let value = self.parse_expr()?;
                Ok(Expr::Assign(Box::new(expr), AssignOp::AddAssign, Box::new(value)))
            }
            TokenKind::MinusEq => {
                self.advance();
                let value = self.parse_expr()?;
                Ok(Expr::Assign(Box::new(expr), AssignOp::SubAssign, Box::new(value)))
            }
            TokenKind::StarEq => {
                self.advance();
                let value = self.parse_expr()?;
                Ok(Expr::Assign(Box::new(expr), AssignOp::MulAssign, Box::new(value)))
            }
            TokenKind::SlashEq => {
                self.advance();
                let value = self.parse_expr()?;
                Ok(Expr::Assign(Box::new(expr), AssignOp::DivAssign, Box::new(value)))
            }
            _ => Ok(expr),
        }
    }

    /// `||`
    fn parse_or(&mut self) -> Result<Expr, ParseError> {
        let mut left = self.parse_and()?;
        while self.check(&TokenKind::Or) {
            self.advance();
            let right = self.parse_and()?;
            left = Expr::BinOp(Box::new(left), BinOp::Or, Box::new(right));
        }
        Ok(left)
    }

    /// `&&`
    fn parse_and(&mut self) -> Result<Expr, ParseError> {
        let mut left = self.parse_comparison()?;
        while self.check(&TokenKind::And) {
            self.advance();
            let right = self.parse_comparison()?;
            left = Expr::BinOp(Box::new(left), BinOp::And, Box::new(right));
        }
        Ok(left)
    }

    /// `==`, `!=`, `<`, `>`, `<=`, `>=`
    fn parse_comparison(&mut self) -> Result<Expr, ParseError> {
        let mut left = self.parse_additive()?;
        loop {
            let op = match self.peek() {
                TokenKind::EqEq => BinOp::Eq,
                TokenKind::Neq => BinOp::Neq,
                TokenKind::Lt => BinOp::Lt,
                TokenKind::Gt => BinOp::Gt,
                TokenKind::Lte => BinOp::Lte,
                TokenKind::Gte => BinOp::Gte,
                _ => break,
            };
            self.advance();
            let right = self.parse_additive()?;
            left = Expr::BinOp(Box::new(left), op, Box::new(right));
        }
        Ok(left)
    }

    /// `+`, `-`
    fn parse_additive(&mut self) -> Result<Expr, ParseError> {
        let mut left = self.parse_multiplicative()?;
        loop {
            let op = match self.peek() {
                TokenKind::Plus => BinOp::Add,
                TokenKind::Minus => BinOp::Sub,
                _ => break,
            };
            self.advance();
            let right = self.parse_multiplicative()?;
            left = Expr::BinOp(Box::new(left), op, Box::new(right));
        }
        Ok(left)
    }

    /// `*`, `/`, `%`
    fn parse_multiplicative(&mut self) -> Result<Expr, ParseError> {
        let mut left = self.parse_unary()?;
        loop {
            let op = match self.peek() {
                TokenKind::Star => BinOp::Mul,
                TokenKind::Slash => BinOp::Div,
                TokenKind::Percent => BinOp::Mod,
                _ => break,
            };
            self.advance();
            let right = self.parse_unary()?;
            left = Expr::BinOp(Box::new(left), op, Box::new(right));
        }
        Ok(left)
    }

    /// `-x`, `!flag`
    fn parse_unary(&mut self) -> Result<Expr, ParseError> {
        match self.peek() {
            TokenKind::Minus => {
                self.advance();
                let expr = self.parse_unary()?;
                Ok(Expr::UnaryOp(UnaryOp::Neg, Box::new(expr)))
            }
            TokenKind::Not => {
                self.advance();
                let expr = self.parse_unary()?;
                Ok(Expr::UnaryOp(UnaryOp::Not, Box::new(expr)))
            }
            _ => self.parse_postfix(),
        }
    }

    /// Dot access: `user.name`, function calls: `clamp(a, b)`
    fn parse_postfix(&mut self) -> Result<Expr, ParseError> {
        let mut expr = self.parse_primary()?;

        loop {
            match self.peek() {
                TokenKind::Dot => {
                    self.advance();
                    let field = self.expect_ident()?;
                    // Check if this is a method call: obj.method(args)
                    if self.check(&TokenKind::LParen) {
                        let args = self.parse_call_args()?;
                        expr = Expr::MethodCall(Box::new(expr), field, args);
                    } else {
                        expr = Expr::DotAccess(Box::new(expr), field);
                    }
                }
                TokenKind::LBracket => {
                    self.advance(); // consume [
                    let index = self.parse_expr()?;
                    self.expect(&TokenKind::RBracket)?;
                    expr = Expr::Index(Box::new(expr), Box::new(index));
                }
                _ => break,
            }
        }

        Ok(expr)
    }

    /// Primary expressions: literals, identifiers, containers, etc.
    /// Parse a string literal, handling interpolation: `"hello {name}, you are {age}"`
    /// Lexer emits: StringFragment("hello ") → StringInterp → [name tokens] → RBrace → StringFragment(", you are ") → StringInterp → [age tokens] → RBrace → StringFragment/StringEnd
    fn parse_string_lit(&mut self) -> Result<Expr, ParseError> {
        let mut segments = Vec::new();

        loop {
            match self.peek().clone() {
                TokenKind::StringFragment(text) => {
                    self.advance();
                    segments.push(StringSegment::Literal(text));
                }
                TokenKind::StringEnd => {
                    self.advance();
                    // Empty string or end after interpolation
                    break;
                }
                TokenKind::StringInterp => {
                    self.advance(); // consume the { marker
                    let expr = self.parse_expr()?;
                    segments.push(StringSegment::Interpolation(Box::new(expr)));
                    // Consume the closing } — lexer emits RBrace and decrements interp_depth,
                    // then next_token transitions back to string mode automatically.
                    if self.check(&TokenKind::RBrace) {
                        self.advance();
                    }
                }
                _ => break,
            }
        }

        if segments.is_empty() {
            segments.push(StringSegment::Literal(String::new()));
        }

        Ok(Expr::StringLit(StringLit { segments }))
    }

    fn parse_primary(&mut self) -> Result<Expr, ParseError> {
        match self.peek().clone() {
            TokenKind::Int(n) => {
                self.advance();
                Ok(Expr::IntLit(n))
            }
            TokenKind::Float(n) => {
                self.advance();
                Ok(Expr::FloatLit(n))
            }
            TokenKind::True => {
                self.advance();
                Ok(Expr::BoolLit(true))
            }
            TokenKind::False => {
                self.advance();
                Ok(Expr::BoolLit(false))
            }
            TokenKind::StringFragment(_) | TokenKind::StringEnd | TokenKind::StringInterp => {
                self.parse_string_lit()
            }
            TokenKind::TripleStringFragment(s) => {
                let text = s.clone();
                self.advance();
                Ok(Expr::RawString(text))
            }
            TokenKind::Await => {
                self.advance(); // consume 'await' keyword
                let inner = self.parse_primary()?;
                Ok(Expr::Await(Box::new(inner)))
            }

            // Containers
            TokenKind::Column => self.parse_container(ContainerKind::Column),
            TokenKind::Row => self.parse_container(ContainerKind::Row),
            TokenKind::Stack => self.parse_container(ContainerKind::Stack),
            TokenKind::Panel => self.parse_container_with_props(ContainerKind::Panel),
            TokenKind::Scene => self.parse_container_with_props(ContainerKind::Scene),

            // When conditional
            TokenKind::When => {
                self.advance();
                let cond = self.parse_comparison()?;
                self.expect(&TokenKind::Arrow)?;
                self.skip_newlines();
                let body = self.parse_expr()?;
                // Optional else branch
                self.skip_newlines();
                let else_body = if self.check(&TokenKind::Else) {
                    self.advance();
                    self.expect(&TokenKind::Arrow)?;
                    self.skip_newlines();
                    Some(Box::new(self.parse_expr()?))
                } else {
                    None
                };
                Ok(Expr::When(Box::new(cond), Box::new(body), else_body))
            }

            // Each loop: `each item in list => template`
            TokenKind::Each => {
                self.advance();
                let item_name = self.expect_ident()?;
                self.expect(&TokenKind::InKw)?;
                let list_expr = self.parse_primary()?;
                self.expect(&TokenKind::Arrow)?;
                self.skip_newlines();
                let body = self.parse_expr()?;
                Ok(Expr::Each(item_name, Box::new(list_expr), Box::new(body)))
            }
            TokenKind::Match => {
                self.advance();
                let scrutinee = self.parse_primary()?;
                self.skip_newlines();
                let mut arms = Vec::new();
                // Match arms: continue while the next token could be a pattern
                // (StringFragment, Int, Ident that starts with lowercase, or _ wildcard).
                // Stop on anything that can't be a pattern: keywords, brackets, EOF.
                while !self.is_at_end() && self.can_be_pattern() {
                    self.skip_newlines();
                    if self.is_at_end() || !self.can_be_pattern() {
                        break;
                    }
                    let pattern = self.parse_pattern()?;
                    self.expect(&TokenKind::Arrow)?;
                    self.skip_newlines();
                    let body = self.parse_expr()?;
                    arms.push(MatchArm { pattern, body });
                    self.skip_newlines();
                }
                Ok(Expr::Match(Box::new(scrutinee), arms))
            }

            // If-then-else
            TokenKind::If => {
                self.advance();
                let cond = self.parse_expr()?;
                self.expect(&TokenKind::Then)?;
                let then_branch = self.parse_expr()?;
                self.expect(&TokenKind::Else)?;
                let else_branch = self.parse_expr()?;
                Ok(Expr::If(Box::new(cond), Box::new(then_branch), Box::new(else_branch)))
            }

            // Perform effect
            TokenKind::Perform => {
                self.advance();
                let name = self.expect_ident()?;
                let args = if self.check(&TokenKind::LParen) {
                    self.parse_call_args()?
                } else {
                    Vec::new()
                };
                Ok(Expr::Perform(name, args))
            }

            // For-in: `for item in items -> body` or `for item, idx in items -> body`
            TokenKind::For => {
                self.advance();
                let item = self.expect_ident()?;

                // Optional index: `for item, idx in ...`
                let index = if self.check(&TokenKind::Comma) {
                    self.advance();
                    Some(self.expect_ident()?)
                } else {
                    None
                };

                self.expect(&TokenKind::InKw)?;
                let iter_expr = self.parse_comparison()?;
                self.expect(&TokenKind::Arrow)?;
                self.skip_newlines();
                let body = self.parse_expr()?;

                Ok(Expr::ForIn {
                    item,
                    index,
                    iter: Box::new(iter_expr),
                    body: Box::new(body),
                })
            }

            // Navigate expression: `navigate "/path"`
            TokenKind::Navigate => {
                self.advance();
                let path_expr = self.parse_primary()?;
                Ok(Expr::Call("navigate".to_string(), vec![path_expr]))
            }

            // Spring expression: `spring(value, stiffness, damping)`
            TokenKind::Spring => {
                self.advance();
                if self.check(&TokenKind::LParen) {
                    let args = self.parse_call_args()?;
                    Ok(Expr::Call("spring".to_string(), args))
                } else {
                    // Just `spring` as an identifier
                    Ok(Expr::Ident("spring".to_string()))
                }
            }

            // Stream from — accept string URL or dotted identifier
            TokenKind::Stream => {
                self.advance();
                self.expect(&TokenKind::From)?;
                // Accept string URL: `stream from "ws://localhost:9100"`
                // or dotted ident: `stream from button.click`
                let full_source = if matches!(self.peek(), TokenKind::StringFragment(_) | TokenKind::StringEnd | TokenKind::StringInterp) {
                    // Parse string literal and extract the raw text
                    let expr = self.parse_string_lit()?;
                    match &expr {
                        Expr::StringLit(s) if s.segments.len() == 1 => {
                            if let StringSegment::Literal(text) = &s.segments[0] {
                                text.clone()
                            } else {
                                return Err(self.error("stream from requires a plain string URL".into()));
                            }
                        }
                        _ => return Err(self.error("stream from requires a plain string URL".into())),
                    }
                } else {
                    let source = self.expect_ident()?;
                    let mut full = source;
                    while self.check(&TokenKind::Dot) {
                        self.advance();
                        let next = self.expect_ident()?;
                        full = format!("{full}.{next}");
                    }
                    full
                };
                // Parse optional { select: field1, field2 } block
                let mut select: Vec<String> = Vec::new();
                if self.check(&TokenKind::LBrace) {
                    self.advance();
                    self.skip_newlines();
                    while !self.check(&TokenKind::RBrace) && !self.is_at_end() {
                        let key = self.expect_ident()?;
                        self.expect(&TokenKind::Colon)?;
                        if key == "select" {
                            // Parse comma-separated identifiers
                            loop {
                                let name = self.expect_ident()?;
                                select.push(name);
                                if self.check(&TokenKind::Comma) {
                                    self.advance();
                                    // Peek: if next is RBrace or a known key, stop
                                    match self.peek() {
                                        TokenKind::RBrace => break,
                                        TokenKind::Ident(s) if s == "select" || s == "mode" => break,
                                        _ => {}
                                    }
                                } else {
                                    break;
                                }
                            }
                        }
                        // Skip trailing comma between block entries
                        if self.check(&TokenKind::Comma) { self.advance(); }
                        self.skip_newlines();
                    }
                    self.expect(&TokenKind::RBrace)?;
                }
                Ok(Expr::StreamFrom { source: full_source, mode: None, select })
            }

            // Record: `{ key: value }`
            TokenKind::LBrace => {
                self.advance();
                self.skip_newlines();
                let mut fields = Vec::new();
                while !self.check(&TokenKind::RBrace) && !self.is_at_end() {
                    self.skip_newlines();
                    let key = self.expect_ident()?;
                    self.expect(&TokenKind::Colon)?;
                    self.skip_newlines();
                    let val = self.parse_expr()?;
                    fields.push((key, val));
                    self.skip_newlines();
                    if self.check(&TokenKind::Comma) {
                        self.advance();
                    }
                    self.skip_newlines();
                }
                self.expect(&TokenKind::RBrace)?;
                Ok(Expr::Record(fields))
            }

            // List: `[a, b, c]`
            TokenKind::LBracket => {
                self.advance();
                self.skip_newlines();
                let mut items = Vec::new();
                while !self.check(&TokenKind::RBracket) && !self.is_at_end() {
                    self.skip_newlines();
                    items.push(self.parse_expr()?);
                    self.skip_newlines();
                    if self.check(&TokenKind::Comma) {
                        self.advance();
                    }
                    self.skip_newlines();
                }
                self.expect(&TokenKind::RBracket)?;
                Ok(Expr::List(items))
            }

            // Parenthesized expression or lambda
            TokenKind::LParen => {
                self.advance();
                // Check for lambda: `(x -> x * 2)` or `(x, y -> x + y)`
                let expr = self.parse_expr()?;
                if self.check(&TokenKind::Arrow) {
                    // This is a lambda
                    let params = vec![self.expr_to_ident(&expr)?];
                    self.advance(); // consume ->
                    let body = self.parse_expr()?;
                    self.expect(&TokenKind::RParen)?;
                    Ok(Expr::Lambda(params, Box::new(body)))
                } else if self.check(&TokenKind::Comma) {
                    // Could be multi-param lambda or tuple — check ahead
                    let mut items = vec![expr];
                    while self.check(&TokenKind::Comma) {
                        self.advance();
                        items.push(self.parse_expr()?);
                    }
                    if self.check(&TokenKind::Arrow) {
                        // Multi-param lambda
                        let mut params = Vec::new();
                        for item in &items {
                            params.push(self.expr_to_ident(item)?);
                        }
                        self.advance(); // ->
                        let body = self.parse_expr()?;
                        self.expect(&TokenKind::RParen)?;
                        Ok(Expr::Lambda(params, Box::new(body)))
                    } else {
                        // Just a parenthesized expression (take first)
                        self.expect(&TokenKind::RParen)?;
                        Ok(items.into_iter().next().unwrap())
                    }
                } else {
                    self.expect(&TokenKind::RParen)?;
                    Ok(expr)
                }
            }

            // Animate modifier (used in pipe context)
            TokenKind::Animate => {
                self.advance();
                let name = self.expect_ident()?;
                // Parse optional duration: `200ms`
                let mut args = Vec::new();
                if let TokenKind::Int(n) = self.peek().clone() {
                    self.advance();
                    // Check for unit suffix (we treat `ms` as part of value for now)
                    args.push(Expr::IntLit(n));
                }
                Ok(Expr::Call(format!("animate_{name}"), args))
            }

            // Identifier — variable reference, element, component use, or function call
            TokenKind::Ident(name) => {
                let name = name.clone();
                self.advance();

                // Slot keyword: renders children inside a component
                if name == "slot" {
                    return Ok(Expr::Slot);
                }

                // Await: `await fetchJSON("/api")` (string form, kept for back-compat)
                if name == "await" {
                    let inner = self.parse_primary()?;
                    return Ok(Expr::Await(Box::new(inner)));
                }

                // Merge: `merge(stream1, stream2, ...)`
                if name == "merge" && self.check(&TokenKind::LParen) {
                    let args = self.parse_call_args()?;
                    return Ok(Expr::Merge(args));
                }

                // Component use: `Button { label: "hello" }` — capitalized name + `{`
                if name.chars().next().map_or(false, |c| c.is_uppercase())
                    && self.check(&TokenKind::LBrace)
                {
                    self.advance(); // consume `{`
                    self.skip_newlines();
                    let mut props = Vec::new();
                    let mut children = Vec::new();
                    while !self.check(&TokenKind::RBrace) && !self.is_at_end() {
                        self.skip_newlines();
                        // Check if this looks like a key: value prop or a child expression
                        let key = self.expect_ident()?;
                        if self.check(&TokenKind::Colon) {
                            self.advance(); // consume ':'
                            self.skip_newlines();
                            let val = self.parse_expr()?;
                            props.push((key, val));
                        } else {
                            // Bare ident — treat as child expression
                            children.push(Expr::Ident(key));
                        }
                        self.skip_newlines();
                        if self.check(&TokenKind::Comma) {
                            self.advance();
                        }
                        self.skip_newlines();
                    }
                    self.expect(&TokenKind::RBrace)?;
                    // Check for [...] children block after props
                    self.skip_newlines();
                    if self.check(&TokenKind::LBracket) {
                        self.advance(); // consume [
                        self.skip_newlines();
                        while !self.check(&TokenKind::RBracket) && !self.is_at_end() {
                            self.skip_newlines();
                            children.push(self.parse_expr()?);
                            self.skip_newlines();
                        }
                        self.expect(&TokenKind::RBracket)?;
                    }
                    return Ok(Expr::ComponentUse { name, props, children });
                }

                // UI element with any arg (string, ident, parenthesized expr, or props-only)
                if is_ui_element(&name) {
                    let next = self.peek().clone();
                    let looks_like_element = matches!(
                        next,
                        TokenKind::StringFragment(_) | TokenKind::StringEnd | TokenKind::StringInterp
                        | TokenKind::LBrace | TokenKind::LParen
                    ) || matches!(next, TokenKind::Ident(ref n) if !is_declaration_keyword(n));

                    if looks_like_element {
                        let fallback = name.clone();
                        match self.parse_element(name)? {
                            Some(el) => Ok(el),
                            None => Ok(Expr::Ident(fallback)),
                        }
                    } else {
                        Ok(Expr::Ident(name))
                    }
                }
                // Function call: `name(args)` — only for non-element idents
                else if self.check(&TokenKind::LParen) {
                    let args = self.parse_call_args()?;
                    Ok(Expr::Call(name, args))
                }
                // Element with string arg: `text "hello"` — fallback for non-is_ui_element tags
                else if matches!(self.peek(), TokenKind::StringFragment(_)) {
                    let fallback = name.clone();
                    match self.parse_element(name)? {
                        Some(el) => Ok(el),
                        None => Ok(Expr::Ident(fallback)),
                    }
                }
                else {
                    Ok(Expr::Ident(name))
                }
            }

            _ => Err(self.error(format!("unexpected token: {:?}", self.peek()))),
        }
    }

    fn parse_element(&mut self, tag: String) -> Result<Option<Expr>, ParseError> {
        let mut args = Vec::new();
        let mut props = Vec::new();
        let mut modifiers = Vec::new();

        // Parse string, ident, or parenthesized expression args
        loop {
            match self.peek().clone() {
                TokenKind::StringFragment(_) | TokenKind::StringEnd | TokenKind::StringInterp => {
                    args.push(self.parse_string_lit()?);
                }
                TokenKind::Ident(name) if !is_declaration_keyword(&name) => {
                    // Only consume if it looks like an element argument
                    self.advance();
                    args.push(Expr::Ident(name));
                }
                TokenKind::LParen => {
                    // Parenthesized expression arg: button (if cond then "A" else "B")
                    self.advance(); // consume (
                    let expr = self.parse_expr()?;
                    self.expect(&TokenKind::RParen)?;
                    args.push(expr);
                }
                _ => break,
            }
        }

        // Parse props: `{ click: handler, class: "foo" }`
        if self.check(&TokenKind::LBrace) {
            self.advance();
            self.skip_newlines();
            while !self.check(&TokenKind::RBrace) && !self.is_at_end() {
                self.skip_newlines();
                let key = self.expect_ident()?;
                self.expect(&TokenKind::Colon)?;
                self.skip_newlines();
                let first = self.parse_expr()?;
                // Multi-action: `click: action1; action2; action3`
                // Collects semicolon-separated expressions into a Block
                let val = if self.check(&TokenKind::Semicolon) {
                    let mut exprs = vec![first];
                    while self.check(&TokenKind::Semicolon) {
                        self.advance(); // consume ';'
                        self.skip_newlines();
                        // Stop if we hit } (end of props) or , (next prop)
                        if self.check(&TokenKind::RBrace) || self.check(&TokenKind::Comma) {
                            break;
                        }
                        exprs.push(self.parse_expr()?);
                    }
                    Expr::Block(exprs)
                } else {
                    first
                };
                // Merge duplicate keys: `click: a` then `click: b` → Block([a, b])
                if let Some(existing) = props.iter_mut().find(|(k, _)| *k == key) {
                    // Merge with existing value
                    match &existing.1 {
                        Expr::Block(exprs) => {
                            let mut merged = exprs.clone();
                            match val {
                                Expr::Block(new_exprs) => merged.extend(new_exprs),
                                other => merged.push(other),
                            }
                            existing.1 = Expr::Block(merged);
                        }
                        _ => {
                            let prev = existing.1.clone();
                            match val {
                                Expr::Block(mut new_exprs) => {
                                    new_exprs.insert(0, prev);
                                    existing.1 = Expr::Block(new_exprs);
                                }
                                other => {
                                    existing.1 = Expr::Block(vec![prev, other]);
                                }
                            }
                        }
                    }
                } else {
                    props.push((key, val));
                }
                self.skip_newlines();
                if self.check(&TokenKind::Comma) {
                    self.advance();
                }
                self.skip_newlines();
            }
            self.expect(&TokenKind::RBrace)?;
        }

        // Parse modifiers: `| animate fade-in 200ms`
        while self.check(&TokenKind::Pipe) {
            self.advance();
            let name = self.expect_ident()?;
            let mut mod_args = Vec::new();
            while matches!(self.peek(), TokenKind::Ident(_) | TokenKind::Int(_) | TokenKind::Float(_)) {
                mod_args.push(self.parse_primary()?);
            }
            modifiers.push(Modifier { name, args: mod_args });
        }

        if args.is_empty() && props.is_empty() && modifiers.is_empty() {
            return Ok(None);
        }

        Ok(Some(Expr::Element(Element {
            tag,
            args,
            props,
            modifiers,
        })))
    }

    fn parse_container(&mut self, kind: ContainerKind) -> Result<Expr, ParseError> {
        self.advance(); // consume container keyword

        // Optional LEADING props: `column { style: "..." } [children]`
        let mut props = Vec::new();
        if self.check(&TokenKind::LBrace) {
            self.advance();
            self.skip_newlines();
            while !self.check(&TokenKind::RBrace) && !self.is_at_end() {
                self.skip_newlines();
                let key = self.expect_ident()?;
                self.expect(&TokenKind::Colon)?;
                let val = self.parse_expr()?;
                props.push((key, val));
                self.skip_newlines();
                if self.check(&TokenKind::Comma) {
                    self.advance();
                }
                self.skip_newlines();
            }
            self.expect(&TokenKind::RBrace)?;
        }

        self.expect(&TokenKind::LBracket)?;
        self.skip_newlines();

        let mut children = Vec::new();
        while !self.check(&TokenKind::RBracket) && !self.is_at_end() {
            self.skip_newlines();
            if self.check(&TokenKind::RBracket) { break; }
            children.push(self.parse_expr()?);
            self.skip_newlines();
            if self.check(&TokenKind::Comma) {
                self.advance();
            }
            self.skip_newlines();
        }

        self.expect(&TokenKind::RBracket)?;

        // Optional TRAILING props: `column [ ... ] { variant: "card" }`
        if self.check(&TokenKind::LBrace) {
            self.advance();
            self.skip_newlines();
            while !self.check(&TokenKind::RBrace) && !self.is_at_end() {
                self.skip_newlines();
                let key = self.expect_ident()?;
                self.expect(&TokenKind::Colon)?;
                let val = self.parse_expr()?;
                props.push((key, val));
                self.skip_newlines();
                if self.check(&TokenKind::Comma) {
                    self.advance();
                }
                self.skip_newlines();
            }
            self.expect(&TokenKind::RBrace)?;
        }

        Ok(Expr::Container(Container {
            kind,
            children,
            props,
        }))
    }

    fn parse_container_with_props(&mut self, kind: ContainerKind) -> Result<Expr, ParseError> {
        self.advance(); // consume container keyword

        // Optional props: `panel { x: panel_x }`
        let mut props = Vec::new();
        if self.check(&TokenKind::LBrace) {
            self.advance();
            self.skip_newlines();
            while !self.check(&TokenKind::RBrace) && !self.is_at_end() {
                self.skip_newlines();
                let key = self.expect_ident()?;
                self.expect(&TokenKind::Colon)?;
                let val = self.parse_expr()?;
                props.push((key, val));
                self.skip_newlines();
                if self.check(&TokenKind::Comma) {
                    self.advance();
                }
                self.skip_newlines();
            }
            self.expect(&TokenKind::RBrace)?;
        }

        self.expect(&TokenKind::LBracket)?;
        self.skip_newlines();

        let mut children = Vec::new();
        while !self.check(&TokenKind::RBracket) && !self.is_at_end() {
            self.skip_newlines();
            if self.check(&TokenKind::RBracket) { break; }
            children.push(self.parse_expr()?);
            self.skip_newlines();
            if self.check(&TokenKind::Comma) {
                self.advance();
            }
            self.skip_newlines();
        }

        self.expect(&TokenKind::RBracket)?;
        Ok(Expr::Container(Container { kind, children, props }))
    }

    fn parse_call_args(&mut self) -> Result<Vec<Expr>, ParseError> {
        self.expect(&TokenKind::LParen)?;
        let mut args = Vec::new();
        while !self.check(&TokenKind::RParen) && !self.is_at_end() {
            args.push(self.parse_expr()?);
            if self.check(&TokenKind::Comma) {
                self.advance();
            }
        }
        self.expect(&TokenKind::RParen)?;
        Ok(args)
    }

    fn parse_pattern(&mut self) -> Result<Pattern, ParseError> {
        match self.peek().clone() {
            TokenKind::Ident(name) => {
                self.advance();
                if name == "_" {
                    Ok(Pattern::Wildcard)
                } else if self.check(&TokenKind::LParen) {
                    // Constructor pattern: `Ok(value)` or `Some(Ok(x))`
                    self.advance();
                    let mut fields = Vec::new();
                    while !self.check(&TokenKind::RParen) && !self.is_at_end() {
                        fields.push(self.parse_pattern()?);
                        if self.check(&TokenKind::Comma) {
                            self.advance();
                        }
                    }
                    self.expect(&TokenKind::RParen)?;
                    Ok(Pattern::Constructor(name, fields))
                } else {
                    Ok(Pattern::Ident(name))
                }
            }
            TokenKind::Int(n) => {
                self.advance();
                Ok(Pattern::IntLiteral(n))
            }
            TokenKind::True => {
                self.advance();
                Ok(Pattern::BoolLiteral(true))
            }
            TokenKind::False => {
                self.advance();
                Ok(Pattern::BoolLiteral(false))
            }
            TokenKind::LParen => {
                // Tuple pattern: `(a, b, c)`
                self.advance();
                let mut elements = Vec::new();
                while !self.check(&TokenKind::RParen) && !self.is_at_end() {
                    elements.push(self.parse_pattern()?);
                    if self.check(&TokenKind::Comma) {
                        self.advance();
                    }
                }
                self.expect(&TokenKind::RParen)?;
                Ok(Pattern::Tuple(elements))
            }
            TokenKind::StringFragment(s) => {
                self.advance();
                Ok(Pattern::Literal(Expr::StringLit(StringLit {
                    segments: vec![StringSegment::Literal(s)],
                })))
            }
            _ => Err(self.error(format!("expected pattern, got {:?}", self.peek()))),
        }
    }

    fn expr_to_ident(&self, expr: &Expr) -> Result<String, ParseError> {
        match expr {
            Expr::Ident(name) => Ok(name.clone()),
            _ => Err(self.error("expected identifier in lambda parameter".into())),
        }
    }
}

fn is_ui_element(name: &str) -> bool {
    matches!(
        name,
        "text" | "button" | "input" | "image" | "avatar" | "icon"
            | "link" | "label" | "badge" | "chip" | "card"
            | "header" | "footer" | "nav" | "section" | "div"
            | "spinner" | "skeleton"
            | "circle" | "rect" | "line"  // Physics scene elements
    )
}

fn is_declaration_keyword(name: &str) -> bool {
    matches!(name, "let" | "view" | "effect" | "on" | "handle")
}

#[derive(Debug, Clone)]
pub struct ParseError {
    pub message: String,
    pub line: usize,
    pub col: usize,
    pub source_line: Option<String>,
}

impl std::fmt::Display for ParseError {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        writeln!(f, "Parse error at line {}:{}: {}", self.line, self.col, self.message)?;
        if let Some(ref src) = self.source_line {
            let line_num = format!("{}", self.line);
            let pad = " ".repeat(line_num.len());
            writeln!(f, "{pad} |")?;
            writeln!(f, "{line_num} | {src}")?;
            let caret_offset = if self.col > 0 { self.col - 1 } else { 0 };
            writeln!(f, "{pad} | {}^", " ".repeat(caret_offset))?;
        }
        Ok(())
    }
}

impl std::error::Error for ParseError {}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::lexer::Lexer;

    fn parse(src: &str) -> Program {
        let mut lexer = Lexer::new(src);
        let tokens = lexer.tokenize();
        let mut parser = Parser::new(tokens);
        parser.parse_program().expect("parse failed")
    }

    #[test]
    fn test_let_int() {
        let prog = parse("let count = 0");
        assert_eq!(prog.declarations.len(), 1);
        match &prog.declarations[0] {
            Declaration::Let(decl) => {
                assert_eq!(decl.name, "count");
                assert!(matches!(decl.value, Expr::IntLit(0)));
            }
            _ => panic!("expected let"),
        }
    }

    #[test]
    fn test_let_binop() {
        let prog = parse("let doubled = count * 2");
        match &prog.declarations[0] {
            Declaration::Let(decl) => {
                assert_eq!(decl.name, "doubled");
                match &decl.value {
                    Expr::BinOp(_, BinOp::Mul, _) => {}
                    other => panic!("expected BinOp(Mul), got {other:?}"),
                }
            }
            _ => panic!("expected let"),
        }
    }

    #[test]
    fn test_view_simple() {
        let prog = parse(
            r#"view counter =
  column [
    text "hello"
  ]"#
        );
        match &prog.declarations[0] {
            Declaration::View(v) => {
                assert_eq!(v.name, "counter");
                match &v.body {
                    Expr::Container(c) => {
                        assert!(matches!(c.kind, ContainerKind::Column));
                        assert_eq!(c.children.len(), 1);
                    }
                    other => panic!("expected Container, got {other:?}"),
                }
            }
            _ => panic!("expected view"),
        }
    }

    #[test]
    fn test_when_expr() {
        let prog = parse(
            r#"view test =
  column [
    when count > 10 ->
      text "big"
  ]"#
        );
        match &prog.declarations[0] {
            Declaration::View(v) => {
                match &v.body {
                    Expr::Container(c) => {
                        assert!(matches!(&c.children[0], Expr::When(_, _, _)));
                    }
                    other => panic!("expected Container, got {other:?}"),
                }
            }
            _ => panic!("expected view"),
        }
    }

    #[test]
    fn test_on_handler() {
        let prog = parse("on toggle_sidebar ->\n  count += 1");
        match &prog.declarations[0] {
            Declaration::OnHandler(h) => {
                assert_eq!(h.event, "toggle_sidebar");
                assert!(matches!(&h.body, Expr::Assign(_, AssignOp::AddAssign, _)));
            }
            _ => panic!("expected on handler"),
        }
    }

    #[test]
    fn test_full_counter() {
        let prog = parse(
            r#"let count = 0
let doubled = count * 2
let label = "hello"

view counter =
  column [
    text label
    button "+" { click: count += 1 }
    when count > 10 ->
      text "big"
  ]"#
        );
        assert_eq!(prog.declarations.len(), 4); // 3 lets + 1 view
    }

    #[test]
    fn test_stream_decl() {
        let prog = parse(r#"stream main on "ws://localhost:9100" { mode: signal }"#);
        match &prog.declarations[0] {
            Declaration::Stream(s) => {
                assert_eq!(s.view_name, "main");
                assert_eq!(s.mode, StreamMode::Signal);
            }
            other => panic!("expected Stream, got {other:?}"),
        }
    }

    #[test]
    fn test_stream_decl_pixel_mode() {
        let prog = parse(r#"stream main on "ws://localhost:9100" { mode: pixel }"#);
        match &prog.declarations[0] {
            Declaration::Stream(s) => {
                assert_eq!(s.mode, StreamMode::Pixel);
            }
            other => panic!("expected Stream, got {other:?}"),
        }
    }

    #[test]
    fn test_stream_decl_default_mode() {
        let prog = parse(r#"stream main on "ws://localhost:9100""#);
        match &prog.declarations[0] {
            Declaration::Stream(s) => {
                assert_eq!(s.mode, StreamMode::Signal);
            }
            other => panic!("expected Stream, got {other:?}"),
        }
    }
    #[test]
    fn test_stream_from_string_url() {
        let prog = parse(r#"let remote = stream from "ws://localhost:9100""#);
        match &prog.declarations[0] {
            Declaration::Let(decl) => {
                assert_eq!(decl.name, "remote");
                match &decl.value {
                    Expr::StreamFrom { source, .. } => {
                        assert_eq!(source, "ws://localhost:9100");
                    }
                    other => panic!("expected StreamFrom, got {other:?}"),
                }
            }
            other => panic!("expected Let, got {other:?}"),
        }
    }

    #[test]
    fn test_stream_from_dotted_ident() {
        let prog = parse("let remote = stream from button.click");
        match &prog.declarations[0] {
            Declaration::Let(decl) => {
                assert_eq!(decl.name, "remote");
                match &decl.value {
                    Expr::StreamFrom { source, .. } => {
                        assert_eq!(source, "button.click");
                    }
                    other => panic!("expected StreamFrom, got {other:?}"),
                }
            }
            other => panic!("expected Let, got {other:?}"),
        }
    }

    #[test]
    fn test_multiple_stream_from() {
        let prog = parse(r#"let a = stream from "ws://localhost:9100"
let b = stream from "ws://localhost:9101""#);
        assert_eq!(prog.declarations.len(), 2);
        for decl in &prog.declarations {
            match decl {
                Declaration::Let(d) => {
                    assert!(matches!(d.value, Expr::StreamFrom { .. }));
                }
                other => panic!("expected Let, got {other:?}"),
            }
        }
    }

    // ── v0.2/v0.3 Feature Tests ─────────────────────────

    fn parse_resilient(src: &str) -> ParseResult {
        let mut lexer = Lexer::new(src);
        let tokens = lexer.tokenize();
        let mut parser = Parser::new(tokens);
        parser.parse_program_resilient()
    }

    #[test]
    fn test_enum_decl() {
        let prog = parse("enum Status { Loading, Ok, Error }");
        match &prog.declarations[0] {
            Declaration::Enum(e) => {
                assert_eq!(e.name, "Status");
                assert_eq!(e.variants.len(), 3);
                assert_eq!(e.variants[0].name, "Loading");
                assert_eq!(e.variants[1].name, "Ok");
                assert_eq!(e.variants[2].name, "Error");
            }
            other => panic!("expected Enum, got {other:?}"),
        }
    }

    #[test]
    fn test_enum_with_fields() {
        let prog = parse("enum Result { Ok(value), Error(msg) }");
        match &prog.declarations[0] {
            Declaration::Enum(e) => {
                assert_eq!(e.name, "Result");
                assert_eq!(e.variants.len(), 2);
                assert_eq!(e.variants[0].name, "Ok");
                assert_eq!(e.variants[0].fields.len(), 1);
                assert_eq!(e.variants[0].fields[0].name, "value");
                assert_eq!(e.variants[1].name, "Error");
                assert_eq!(e.variants[1].fields.len(), 1);
            }
            other => panic!("expected Enum, got {other:?}"),
        }
    }

    #[test]
    fn test_pub_let() {
        let prog = parse("pub let count = 0");
        match &prog.declarations[0] {
            Declaration::Let(d) => {
                assert_eq!(d.name, "count");
                assert!(matches!(d.visibility, Visibility::Public));
            }
            other => panic!("expected Let, got {other:?}"),
        }
    }

    #[test]
    fn test_doc_comment() {
        let prog = parse("/// This is a counter\nlet count = 0");
        match &prog.declarations[0] {
            Declaration::Let(d) => {
                assert_eq!(d.name, "count");
                assert_eq!(d.doc.as_deref(), Some("This is a counter"));
            }
            other => panic!("expected Let, got {other:?}"),
        }
    }

    #[test]
    fn test_error_recovery_two_errors() {
        // Two broken declarations with a valid one in between
        let result = parse_resilient("!@#$\nlet count = 0\n!@#$\n");
        assert!(!result.errors.is_empty(), "expected errors");
        // The valid declaration should still be parsed
        let has_count = result.program.declarations.iter().any(|d| {
            matches!(d, Declaration::Let(d) if d.name == "count")
        });
        assert!(has_count, "expected 'count' declaration to be recovered");
    }

    #[test]
    fn test_error_recovery_valid_after_error() {
        // Garbage followed by a valid declaration
        let result = parse_resilient("??? bad stuff\nlet count = 42");
        assert!(!result.errors.is_empty(), "expected errors");
        let has_count = result.program.declarations.iter().any(|d| {
            matches!(d, Declaration::Let(d) if d.name == "count")
        });
        assert!(has_count, "expected 'count' declaration to be recovered");
    }

    #[test]
    fn test_empty_program() {
        let prog = parse("");
        assert_eq!(prog.declarations.len(), 0);
    }

    #[test]
    fn test_multiple_declarations() {
        let prog = parse("let a = 1\nlet b = 2\nlet c = 3");
        assert_eq!(prog.declarations.len(), 3);
    }

    // ── v0.6 Parser Hardening Tests ─────────────────────────

    #[test]
    fn test_import_decl() {
        let prog = parse("import { Button, Card } from \"./components\"");
        match &prog.declarations[0] {
            Declaration::Import(imp) => {
                assert_eq!(imp.source, "./components");
                assert_eq!(imp.names, vec!["Button", "Card"]);
            }
            other => panic!("expected Import, got {other:?}"),
        }
    }

    #[test]
    fn test_export_decl() {
        let prog = parse("export let theme = \"dark\"");
        match &prog.declarations[0] {
            Declaration::Export(name, inner) => {
                assert_eq!(name, "theme");
                match inner.as_ref() {
                    Declaration::Let(d) => assert_eq!(d.name, "theme"),
                    other => panic!("expected inner Let, got {other:?}"),
                }
            }
            other => panic!("expected Export, got {other:?}"),
        }
    }

    #[test]
    fn test_component_decl() {
        let prog = parse("component Card(title, subtitle) =\n  column [\n    text title\n    text subtitle\n  ]");
        match &prog.declarations[0] {
            Declaration::Component(c) => {
                assert_eq!(c.name, "Card");
                assert_eq!(c.props.len(), 2);
            }
            other => panic!("expected Component, got {other:?}"),
        }
    }

    #[test]
    fn test_route_decl() {
        let prog = parse("route \"/settings\" -> settings_view");
        match &prog.declarations[0] {
            Declaration::Route(r) => {
                assert_eq!(r.path, "/settings");
            }
            other => panic!("expected Route, got {other:?}"),
        }
    }

    #[test]
    fn test_every_decl() {
        let prog = parse("let t = 0\nevery 16 -> t = t + 1");
        let has_every = prog.declarations.iter().any(|d| matches!(d, Declaration::Every(_)));
        assert!(has_every, "expected Every declaration");
    }

    #[test]
    fn test_type_alias() {
        let prog = parse("type Color = String");
        match &prog.declarations[0] {
            Declaration::TypeAlias(ta) => {
                assert_eq!(ta.name, "Color");
            }
            other => panic!("expected TypeAlias, got {other:?}"),
        }
    }

    #[test]
    fn test_layout_decl() {
        let prog = parse("layout dashboard {\n  sidebar.width == 250\n}");
        match &prog.declarations[0] {
            Declaration::Layout(l) => {
                assert_eq!(l.name, "dashboard");
                assert!(!l.constraints.is_empty());
            }
            other => panic!("expected Layout, got {other:?}"),
        }
    }

    #[test]
    fn test_effect_decl() {
        let prog = parse("effect fetchUser(id): Result");
        match &prog.declarations[0] {
            Declaration::Effect(e) => {
                assert_eq!(e.name, "fetchUser");
                assert!(!e.params.is_empty());
            }
            other => panic!("expected Effect, got {other:?}"),
        }
    }

    #[test]
    fn test_match_expr() {
        let prog = parse("let mood = \"happy\"\nview main = column [\n  match mood\n    \"happy\" -> text \"great\"\n    _ -> text \"ok\"\n]");
        match &prog.declarations[1] {
            Declaration::View(v) => {
                match &v.body {
                    Expr::Container(c) => {
                        assert!(matches!(&c.children[0], Expr::Match(_, _)));
                    }
                    other => panic!("expected Container, got {other:?}"),
                }
            }
            other => panic!("expected View, got {other:?}"),
        }
    }

    #[test]
    fn test_for_in_expr() {
        let prog = parse("view main = column [\n  for item in items ->\n    text item\n]");
        match &prog.declarations[0] {
            Declaration::View(v) => {
                match &v.body {
                    Expr::Container(c) => {
                        assert!(matches!(&c.children[0], Expr::ForIn { .. }));
                    }
                    other => panic!("expected Container, got {other:?}"),
                }
            }
            other => panic!("expected View, got {other:?}"),
        }
    }

    #[test]
    fn test_string_interpolation() {
        let prog = parse("let greeting = \"hello {name}\"");
        match &prog.declarations[0] {
            Declaration::Let(d) => {
                match &d.value {
                    Expr::StringLit(s) => {
                        assert!(!s.segments.is_empty(), "should have interpolation parts");
                    }
                    other => panic!("expected StringLit, got {other:?}"),
                }
            }
            other => panic!("expected Let, got {other:?}"),
        }
    }

    #[test]
    fn test_constrain_decl() {
        let prog = parse("constrain sidebar.width = 250");
        match &prog.declarations[0] {
            Declaration::Constrain(_) => {}
            other => panic!("expected Constrain, got {other:?}"),
        }
    }

    // ── v0.9 Rust-Like Match Pattern Tests ──────────────────

    #[test]
    fn test_match_tuple_pattern() {
        let prog = parse("let pair = (1, 2)\nview main = match pair\n  (a, b) -> text \"pair\"");
        // Should parse without error — view-level match with tuple pattern
        assert!(!prog.declarations.is_empty());
    }

    #[test]
    fn test_match_int_literal_pattern() {
        let prog = parse("let n = 42\nview main = match n\n  0 -> text \"zero\"\n  1 -> text \"one\"\n  _ -> text \"other\"");
        assert!(prog.declarations.len() >= 2);
    }

    #[test]
    fn test_match_bool_literal_pattern() {
        let prog = parse("let flag = true\nview main = match flag\n  true -> text \"yes\"\n  false -> text \"no\"");
        assert!(prog.declarations.len() >= 2);
    }

    #[test]
    fn test_match_nested_constructor() {
        let prog = parse("view main = match result\n  Some(Ok(x)) -> text \"good\"\n  Some(Error(e)) -> text \"bad\"\n  None -> text \"nothing\"");
        assert!(!prog.declarations.is_empty());
    }

    #[test]
    fn test_match_mixed_arms() {
        let prog = parse("let status = \"ok\"\nlet label = match status\n  \"loading\" -> \"wait\"\n  \"ok\" -> \"done\"\n  _ -> \"unknown\"");
        // Expression-level match with string literals and wildcard
        match &prog.declarations[1] {
            Declaration::Let(d) => {
                assert!(matches!(&d.value, Expr::Match(_, arms) if arms.len() == 3));
            }
            other => panic!("expected Let with Match, got {other:?}"),
        }
    }
}


