use crate::SyntaxKind::{
    AMP, BACKSLASH, BANG, BLOCK_COMMENT, BRANCH_CONTENT, BRANCHLESS_COND_BODY, COLON,
    CONDITIONAL_WITH_EXPR, DIVERT, DOLLAR, ELSE_BRANCH, EOF, ESCAPE, GLUE, GLUE_NODE, HASH,
    IMPLICIT_SEQUENCE, INLINE_BRANCHES_COND, INLINE_BRANCHES_SEQ, INLINE_LOGIC, INNER_EXPRESSION,
    KW_CYCLE, KW_ELSE, KW_ONCE, KW_SHUFFLE, KW_STOPPING, L_BRACE, L_PAREN, LINE_COMMENT, MINUS,
    MULTILINE_BLOCK, MULTILINE_BRANCH_BODY, MULTILINE_BRANCH_COND, MULTILINE_BRANCH_SEQ,
    MULTILINE_BRANCHES_COND, MULTILINE_BRANCHES_SEQ, MULTILINE_CONDITIONAL, NEWLINE, PIPE, PLUS,
    R_BRACE, R_PAREN, SEQUENCE_SYMBOL_ANNOTATION, SEQUENCE_WITH_ANNOTATION,
    SEQUENCE_WORD_ANNOTATION, STAR, TAG, TAGS, TEXT, THREAD, TILDE, TUNNEL_ONWARDS, WHITESPACE,
};

use super::Parser;

/// Parse inline logic: `{ inner_logic }`.
///
/// This is the primary entry point called from content and choice parsers.
///
/// ```text
/// inline_logic = { "{" ~ inner_logic ~ "}" }
/// ```
pub(crate) fn inline_logic(p: &mut Parser<'_, '_>) {
    p.skip_ws(); // flush trivia to the parent before starting the node
    p.start_node(INLINE_LOGIC);
    let brace_pos = p.pos(); // raw position of the `{` for scan lookup
    p.bump(); // L_BRACE

    if p.at_depth_limit() {
        p.error("nesting depth limit exceeded".into());
        // Skip tokens until the matching `}` or EOF to recover.
        let mut depth = 1u32;
        while !p.at_eof() && depth > 0 {
            match p.current() {
                L_BRACE => {
                    depth += 1;
                    p.bump();
                }
                R_BRACE => {
                    depth -= 1;
                    if depth > 0 {
                        p.bump();
                    }
                }
                _ => p.bump(),
            }
        }
        if p.current() == R_BRACE {
            p.bump();
        }
        p.finish_node();
        return;
    }

    p.depth += 1;
    p.skip_ws();

    inner_logic(p, brace_pos);

    p.skip_ws();
    if p.current() == R_BRACE {
        p.bump();
    } else if !p.at_eof() {
        p.error("expected `}`".into());
    }
    p.depth -= 1;
    p.finish_node();
}

/// Parse a multiline block: `{ NEWLINE ... }`.
///
/// This is the statement-level form that spans multiple lines.
///
/// ```text
/// multiline_block = { "{" ~ NEWLINE ~ inner_logic_multiline ~ "}" }
/// ```
pub(crate) fn multiline_block(p: &mut Parser<'_, '_>) {
    p.skip_ws(); // flush trivia to the parent before starting the node
    p.start_node(MULTILINE_BLOCK);
    p.bump(); // L_BRACE
    p.skip_ws();

    // Consume the newline that makes this a multiline block
    if p.at(NEWLINE) {
        p.bump();
    }
    skip_blank_lines(p);

    // inner_logic_multiline: sequence_with_annotation | multiline_conditional_bare | conditional_with_expr
    if at_sequence_annotation(p) {
        sequence_with_annotation(p);
    } else if at_multiline_branch_start(p) {
        // multiline_conditional_bare (branches without a leading expression)
        multiline_branches_cond(p);
    } else {
        // conditional_with_expr (expression followed by `:`)
        conditional_with_expr_standalone(p);
    }

    p.skip_ws();
    skip_blank_lines(p);
    if p.current() == R_BRACE {
        p.bump();
    } else if !p.at_eof() {
        p.error("expected `}` to close multiline block".into());
    }
    p.finish_node();
}

/// 5-way dispatch for inner logic inside `{...}`.
///
/// `brace_pos` is the raw token position of the opening `{`, used to look up
/// the pre-computed scan result (whether `PIPE` or `COLON` appears first at
/// depth-0 inside this brace pair).
///
/// ```text
/// inner_logic = {
///     sequence_with_annotation
///   | conditional_with_expr
///   | multiline_conditional
///   | implicit_sequence
///   | inner_expression
/// }
/// ```
fn inner_logic(p: &mut Parser<'_, '_>, brace_pos: usize) {
    // 1. sequence_with_annotation: starts with annotation symbol or keyword
    if at_sequence_annotation(p) {
        sequence_with_annotation(p);
        return;
    }

    // 2. multiline_conditional: starts with NEWLINE
    if p.current() == NEWLINE {
        multiline_conditional(p);
        return;
    }

    // O(1) lookup of pre-computed scan result for this brace pair.
    let lookahead = p.brace_scan_at(brace_pos);

    if lookahead == PIPE {
        // Implicit sequence: {branch|branch|...}
        // Parse first branch as content (not expression) so sentences work.
        p.start_node(IMPLICIT_SEQUENCE);
        branch_content(p); // first branch
        while p.current() == PIPE {
            p.skip_ws();
            p.bump(); // PIPE
            branch_content(p);
        }
        p.finish_node();
        return;
    }

    // COLON, R_BRACE, or other: try expression, then dispatch
    let checkpoint = p.checkpoint();
    super::expression::expression(p);
    p.skip_ws();

    if p.current() == COLON {
        // conditional_with_expr: we already parsed the expression, now wrap it
        p.start_node_at(checkpoint, CONDITIONAL_WITH_EXPR);
        p.bump(); // COLON
        conditional_body(p);
        p.finish_node();
    } else {
        // Bare expression -- wrap it
        p.start_node_at(checkpoint, INNER_EXPRESSION);
        p.finish_node();
    }
}

/// Parse the body after `expr :` in a conditional.
fn conditional_body(p: &mut Parser<'_, '_>) {
    match p.current() {
        NEWLINE => {
            p.skip_ws();
            if at_multiline_branch_start(p) {
                multiline_branches_cond(p);
            } else {
                branchless_cond_body(p);
            }
        }
        R_BRACE => {
            p.skip_ws();
            inline_branches_cond(p);
        }
        _ => {
            inline_branches_cond(p);
        }
    }
}

// ── Sequence with annotation ────────────────────────────────────────

fn at_sequence_annotation(p: &Parser<'_, '_>) -> bool {
    matches!(
        p.current(),
        AMP | BANG | TILDE | DOLLAR | KW_STOPPING | KW_CYCLE | KW_SHUFFLE | KW_ONCE
    )
}

/// Parse a sequence with annotation.
///
/// ```text
/// sequence_with_annotation = { sequence_annotation ~ (multiline_branches_seq | inline_branches_seq) }
/// ```
fn sequence_with_annotation(p: &mut Parser<'_, '_>) {
    p.start_node(SEQUENCE_WITH_ANNOTATION);

    // Parse annotation
    match p.current() {
        AMP | BANG | TILDE | DOLLAR => {
            p.start_node(SEQUENCE_SYMBOL_ANNOTATION);
            while matches!(p.current(), AMP | BANG | TILDE | DOLLAR) {
                p.bump();
            }
            p.finish_node();
        }
        KW_STOPPING | KW_CYCLE | KW_SHUFFLE | KW_ONCE => {
            p.start_node(SEQUENCE_WORD_ANNOTATION);
            p.bump(); // first keyword
            loop {
                p.skip_ws();
                if matches!(p.current(), KW_STOPPING | KW_CYCLE | KW_SHUFFLE | KW_ONCE) {
                    p.bump();
                } else {
                    break;
                }
            }
            p.skip_ws();
            p.expect(COLON);
            p.finish_node();
        }
        _ => {
            p.error("expected sequence annotation".into());
        }
    }

    p.skip_ws();

    // Branches: multiline or inline
    if p.current() == R_BRACE {
        p.error("expected sequence branches".into());
    } else if p.current() == NEWLINE {
        multiline_branches_seq(p);
    } else {
        inline_branches_seq(p);
    }

    p.finish_node();
}

/// Parse inline sequence branches: `content | content | ...`
fn inline_branches_seq(p: &mut Parser<'_, '_>) {
    p.start_node(INLINE_BRANCHES_SEQ);
    branch_content(p);
    while p.current() == PIPE {
        p.skip_ws();
        p.bump(); // PIPE
        branch_content(p);
    }
    p.finish_node();
}

/// Parse multiline sequence branches.
fn multiline_branches_seq(p: &mut Parser<'_, '_>) {
    p.start_node(MULTILINE_BRANCHES_SEQ);
    // Consume leading newlines and whitespace
    while matches!(p.current(), NEWLINE) {
        p.bump();
        p.skip_ws();
    }
    skip_blank_lines(p);

    while at_multiline_branch_start(p) {
        multiline_branch_seq(p);
    }
    p.finish_node();
}

/// Parse a single multiline sequence branch: `- content`.
fn multiline_branch_seq(p: &mut Parser<'_, '_>) {
    p.start_node(MULTILINE_BRANCH_SEQ);
    // Skip newlines and whitespace before the branch marker `-`.
    while matches!(p.nth_raw(0), NEWLINE | WHITESPACE) {
        p.bump();
    }
    p.bump(); // MINUS (the dash)
    p.skip_ws();
    multiline_branch_body(p);
    p.finish_node();
}

// ── Conditional ─────────────────────────────────────────────────────

/// Parse `expr : ...` as a standalone `conditional_with_expr`
/// (used in multiline block context).
fn conditional_with_expr_standalone(p: &mut Parser<'_, '_>) {
    p.start_node(CONDITIONAL_WITH_EXPR);
    super::expression::expression(p);
    p.skip_ws();
    if p.current() == COLON {
        p.bump();
        p.skip_ws();
        conditional_body(p);
    } else {
        p.error("expected `:` in conditional".into());
    }
    p.finish_node();
}

/// Parse inline conditional branches: `true_content | false_content?`
fn inline_branches_cond(p: &mut Parser<'_, '_>) {
    p.start_node(INLINE_BRANCHES_COND);
    branch_content(p);
    if p.current() == PIPE {
        p.skip_ws();
        p.bump(); // PIPE
        branch_content(p);
    }
    p.finish_node();
}

/// Parse multiline conditional branches.
fn multiline_branches_cond(p: &mut Parser<'_, '_>) {
    p.start_node(MULTILINE_BRANCHES_COND);
    while matches!(p.current(), NEWLINE) {
        p.bump();
        p.skip_ws();
    }
    skip_blank_lines(p);

    while at_multiline_branch_start(p) {
        multiline_branch_cond(p);
    }
    p.finish_node();
}

/// Parse a multiline conditional: just branches after a NEWLINE.
fn multiline_conditional(p: &mut Parser<'_, '_>) {
    p.start_node(MULTILINE_CONDITIONAL);
    while matches!(p.current(), NEWLINE) {
        p.bump();
        p.skip_ws();
    }
    skip_blank_lines(p);

    while at_multiline_branch_start(p) {
        multiline_branch_cond(p);
    }
    p.finish_node();
}

/// Parse a multiline conditional branch.
///
/// ```text
/// multiline_branch_cond = {
///     NEWLINE? ~ WS* ~ "-" ~ !">" ~ WS*
///   ~ ("else" ~ ":"? | expression ~ ":")? ~ WS*
///   ~ multiline_branch_body
/// }
/// ```
fn multiline_branch_cond(p: &mut Parser<'_, '_>) {
    p.start_node(MULTILINE_BRANCH_COND);
    // Skip newlines and whitespace before the branch marker `-`.
    // The body parser breaks on the NEWLINE before the next branch,
    // so there may be multiple NEWLINEs (blank lines) to consume here.
    while matches!(p.nth_raw(0), NEWLINE | WHITESPACE) {
        p.bump();
    }
    p.bump(); // MINUS (the dash)
    p.skip_ws();

    // Optional condition: "else" or expression ":"
    if p.current() == KW_ELSE {
        p.bump(); // else
        p.skip_ws();
        if p.current() == COLON {
            p.bump();
        }
        p.skip_ws();
    } else if !matches!(p.current(), NEWLINE | EOF | R_BRACE) && p.current() != MINUS {
        // Try to parse as expression : condition
        let has_condition = looks_like_condition(p);
        if has_condition {
            super::expression::expression(p);
            p.skip_ws();
            if p.current() == COLON {
                p.bump();
                p.skip_ws();
            }
        }
    }

    multiline_branch_body(p);
    p.finish_node();
}

/// Heuristic: does the current position start a condition (expression followed by `:`)?
/// We scan ahead looking for `:` before NEWLINE or `}`.
fn looks_like_condition(p: &Parser<'_, '_>) -> bool {
    let mut i = 0;
    let mut depth: u32 = 0;
    loop {
        let kind = p.nth(i);
        match kind {
            COLON if depth == 0 => return true,
            EOF => return false,
            NEWLINE | R_BRACE if depth == 0 => return false,
            L_BRACE | L_PAREN => depth += 1,
            R_BRACE | R_PAREN => depth = depth.saturating_sub(1),
            _ => {}
        }
        i += 1;
    }
}

/// Parse branchless conditional body (content after `:` with no `-` branch markers).
fn branchless_cond_body(p: &mut Parser<'_, '_>) {
    p.start_node(BRANCHLESS_COND_BODY);

    // Consume the leading NEWLINE and strip indentation
    if p.current() == NEWLINE {
        p.bump();
        p.skip_ws();
    }

    // Track whether we're at the logical start of a line.  MINUS is only
    // a branch/else marker at line start; mid-content MINUS (e.g. `<>-<>`)
    // is literal text.
    let mut at_line_start = true;

    // Content until we hit a branch start, closing brace
    loop {
        match p.current() {
            R_BRACE | EOF => break,
            NEWLINE => {
                // Check if next line starts a branch
                if at_multiline_branch_start(p) {
                    // This is an else branch
                    else_branch(p);
                    break;
                }
                p.bump();
                // Strip leading indentation on the next line so it doesn't
                // leak into text content (inklecate strips it too).
                p.skip_ws();
                at_line_start = true;
            }
            MINUS if at_line_start => {
                // `- else:` branch (the NEWLINE before was consumed by
                // logic_line or skip_ws, so we land here directly).
                else_branch(p);
                break;
            }
            STAR | PLUS => {
                super::choice::choice(p);
                // choice() always consumes its trailing newline, leaving
                // us at the start of the next line — strip indentation.
                p.skip_ws();
                at_line_start = true;
            }
            HASH if at_line_start => {
                super::tag::tag_line(p);
                p.skip_ws();
                at_line_start = true;
            }
            TILDE => {
                p.skip_ws();
                super::logic::logic_line(p);
                // Logic lines may consume their trailing newline, leaving
                // us at the start of the next line — strip indentation.
                p.skip_ws();
                at_line_start = true;
            }
            L_BRACE => {
                inline_logic(p);
                at_line_start = false;
            }
            DIVERT | TUNNEL_ONWARDS | THREAD => {
                p.skip_ws();
                super::divert::divert(p);
                at_line_start = false;
            }
            GLUE => {
                p.skip_ws();
                p.start_node(GLUE_NODE);
                p.bump();
                p.finish_node();
                at_line_start = false;
            }
            BACKSLASH => {
                if matches!(p.nth(1), NEWLINE | EOF) {
                    p.start_node(TEXT);
                    p.bump();
                    p.finish_node();
                } else {
                    p.start_node(ESCAPE);
                    p.bump(); // backslash
                    p.bump(); // escaped char
                    p.finish_node();
                }
                at_line_start = false;
            }
            _ => {
                let before = p.pos();
                multiline_branch_text(p);
                if p.pos() == before {
                    break;
                }
                at_line_start = false;
            }
        }
    }

    p.finish_node();
}

/// Parse an else branch at the end of a branchless conditional body.
fn else_branch(p: &mut Parser<'_, '_>) {
    p.start_node(ELSE_BRANCH);
    multiline_branch_cond(p);
    p.finish_node();
}

// ── Shared branch helpers ───────────────────────────────────────────

/// Returns `true` if we're at a multiline branch start.
/// Peeks past optional NEWLINE and whitespace to check for `-` not followed by `>`.
fn at_multiline_branch_start(p: &Parser<'_, '_>) -> bool {
    let mut i = 0;
    loop {
        match p.nth(i) {
            NEWLINE => {
                i += 1;
            }
            MINUS => return true,
            _ => return false,
        }
    }
}

/// Parse a multiline branch body.
///
/// ```text
/// multiline_branch_body = { (multiline_branch_body_item | body_newline)* }
/// ```
///
/// NOTE: Gathers (`-`) inside inner blocks are forbidden by the ink spec
/// but we don't yet emit a diagnostic for them — the MINUS arm just breaks
/// out of the body loop (same as a branch separator).
fn multiline_branch_body(p: &mut Parser<'_, '_>) {
    p.start_node(MULTILINE_BRANCH_BODY);
    loop {
        // Strip leading indentation at the start of each line so it doesn't
        // leak into text content (inklecate strips it at parse time too).
        // This runs on every iteration because sub-parsers (choice, logic_line)
        // may consume their trailing NEWLINE, leaving us at the start of a
        // new indented line without the NEWLINE handler below firing.
        p.skip_ws();
        match p.current() {
            EOF | R_BRACE | MINUS => break,
            NEWLINE => {
                // body_newline: NEWLINE not followed by branch start
                if next_line_is_branch(p) {
                    break;
                }
                p.bump();
            }
            STAR | PLUS => {
                // Choices participate in the outer weave structure.
                super::choice::choice(p);
            }
            HASH => {
                super::tag::tag_line(p);
            }
            TILDE => {
                p.skip_ws();
                super::logic::logic_line(p);
            }
            L_BRACE => {
                inline_logic(p);
            }
            GLUE => {
                p.skip_ws();
                p.start_node(GLUE_NODE);
                p.bump();
                p.finish_node();
            }
            BACKSLASH => {
                if matches!(p.nth(1), NEWLINE | EOF) {
                    // Backslash before newline/EOF — consume as text to avoid stall
                    p.start_node(TEXT);
                    p.bump();
                    p.finish_node();
                } else {
                    p.start_node(ESCAPE);
                    p.bump(); // backslash
                    p.bump(); // escaped char
                    p.finish_node();
                }
            }
            DIVERT | TUNNEL_ONWARDS | THREAD => {
                super::divert::divert(p);
            }
            _ => {
                let before = p.pos();
                multiline_branch_text(p);
                if p.pos() == before {
                    break;
                }
            }
        }
    }
    p.finish_node();
}

/// Check if the line after the current NEWLINE starts a branch (WS* - !>).
fn next_line_is_branch(p: &Parser<'_, '_>) -> bool {
    let mut offset = 1; // skip past the NEWLINE
    loop {
        match p.nth(offset) {
            NEWLINE => offset += 1,
            MINUS => return true,
            _ => return false,
        }
    }
}

/// Parse multiline branch text.
fn multiline_branch_text(p: &mut Parser<'_, '_>) {
    p.start_node(TEXT);
    loop {
        if p.at_eof() {
            break;
        }
        match p.nth_raw(0) {
            NEWLINE | L_BRACE | R_BRACE | GLUE | DIVERT | TUNNEL_ONWARDS | LINE_COMMENT
            | BLOCK_COMMENT | BACKSLASH | EOF | TILDE | THREAD => break,
            _ => p.bump(),
        }
    }
    p.finish_node();
}

// ── Shared inline branch content ────────────────────────────────────

/// Parse branch content (inline): text, `inline_logic`, glue, escapes until `|` or `}`.
fn branch_content(p: &mut Parser<'_, '_>) {
    p.start_node(BRANCH_CONTENT);
    loop {
        match p.current() {
            PIPE | R_BRACE | NEWLINE | EOF => break,
            HASH => {
                branch_tags(p);
                break;
            }
            L_BRACE => {
                p.skip_ws();
                inline_logic(p);
            }
            GLUE => {
                p.skip_ws();
                p.start_node(GLUE_NODE);
                p.bump();
                p.finish_node();
            }
            DIVERT | TUNNEL_ONWARDS | THREAD => {
                p.skip_ws();
                super::divert::divert(p);
            }
            // A `~` logic statement at the start of an inline branch is invalid
            // ink — logic only lives in a multiline conditional block. (A literal
            // mid-text tilde is consumed inside `branch_text`, so it never reaches
            // here.) Reject it with a diagnostic and recover.
            TILDE => {
                p.error_recover(
                    "logic statements aren't allowed in an inline conditional; \
                     use a multiline block, e.g. `{ cond:` then `~ ...` on its own line",
                );
            }
            BACKSLASH => {
                if matches!(p.nth(1), NEWLINE | EOF) {
                    // Backslash before newline/EOF — consume as text to avoid stall
                    p.start_node(TEXT);
                    p.bump();
                    p.finish_node();
                } else {
                    p.start_node(ESCAPE);
                    p.bump(); // backslash
                    p.bump(); // escaped char
                    p.finish_node();
                }
            }
            _ => {
                let before = p.pos();
                branch_text(p);
                if p.pos() == before {
                    break;
                }
            }
        }
    }
    p.finish_node();
}

/// Parse tags inside a sequence/conditional branch.
/// Stops at `|`, `}`, newline, or EOF (unlike regular tags which only stop
/// at `#` and newline).
fn branch_tags(p: &mut Parser<'_, '_>) {
    p.start_node(TAGS);
    while p.current() == HASH {
        branch_tag(p);
    }
    p.finish_node();
}

/// Parse a single tag inside a branch: stops at `#`, `|`, `}`, newline, EOF.
fn branch_tag(p: &mut Parser<'_, '_>) {
    p.start_node(TAG);
    p.skip_ws();
    p.bump_assert(HASH);
    loop {
        if p.at_eof() {
            break;
        }
        match p.nth_raw(0) {
            HASH | NEWLINE | EOF | PIPE | R_BRACE => break,
            _ => p.bump(),
        }
    }
    p.finish_node();
}

/// Parse a run of branch text characters.
fn branch_text(p: &mut Parser<'_, '_>) {
    p.start_node(TEXT);
    loop {
        if p.at_eof() {
            break;
        }
        match p.nth_raw(0) {
            PIPE | L_BRACE | R_BRACE | GLUE | DIVERT | TUNNEL_ONWARDS | LINE_COMMENT
            | BLOCK_COMMENT | NEWLINE | BACKSLASH | EOF | THREAD | HASH => break,
            _ => p.bump(),
        }
    }
    p.finish_node();
}

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

fn skip_blank_lines(p: &mut Parser<'_, '_>) {
    while p.current() == NEWLINE {
        p.bump();
        p.skip_ws();
    }
}
