use crate::ast::{
    Attribute, AttributeValue, BlockHead, BlockMode, CodeHead, CodeKind, CommentKind, CommentNode,
    ControlFlowHead, ControlFlowKind, DoctypeHead, Document, ExtendsHead, FilterHead, IncludeHead,
    InlineText, InlineTextKind, MixinCallHead, MixinHead, Node, QuoteStyle, RawTextNode,
    StatementHead, StatementNode, TagHead, TextBlockKind, TextLineKind, TextLineNode,
};
use crate::lexer::LexedLine;

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum DiagnosticSeverity {
    Warning,
    Error,
    Fatal,
}

#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Diagnostic {
    pub severity: DiagnosticSeverity,
    pub line: usize,
    pub message: String,
}

#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ParseReport {
    pub document: Document,
    pub diagnostics: Vec<Diagnostic>,
}

pub fn parse(lines: &[LexedLine]) -> Document {
    parse_with_diagnostics(lines).document
}

pub fn parse_with_diagnostics(lines: &[LexedLine]) -> ParseReport {
    let mut diagnostics = Vec::new();
    let (children, _) = parse_block(lines, 0, 0, ParseMode::Normal, &mut diagnostics);

    ParseReport {
        document: Document { children },
        diagnostics,
    }
}

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum ParseMode {
    Normal,
    RawText,
}

fn parse_block(
    lines: &[LexedLine],
    mut index: usize,
    current_indent: usize,
    mode: ParseMode,
    diagnostics: &mut Vec<Diagnostic>,
) -> (Vec<Node>, usize) {
    let mut nodes = Vec::new();

    while index < lines.len() {
        let line = &lines[index];

        if line.is_blank {
            if mode == ParseMode::RawText {
                nodes.push(Node::RawText(RawTextNode {
                    preserve_base_indent: line.indent >= current_indent,
                    extra_indent: line.indent.saturating_sub(current_indent),
                    content: String::new(),
                }));
            }
            index += 1;
            continue;
        }

        if line.indent < current_indent {
            break;
        }

        if mode == ParseMode::RawText {
            nodes.push(Node::RawText(RawTextNode {
                preserve_base_indent: true,
                extra_indent: line.indent.saturating_sub(current_indent),
                content: line.content.clone(),
            }));
            index += 1;
            continue;
        }

        let recovered_indent = line.indent > current_indent;
        if recovered_indent {
            diagnostics.push(Diagnostic {
                severity: DiagnosticSeverity::Warning,
                line: index + 1,
                message: format!(
                    "Recovered inconsistent indentation: expected {} spaces in this block, found {}",
                    current_indent, line.indent
                ),
            });
        }

        let content = line.content.trim_start();
        let nesting_parent_indent = if recovered_indent {
            line.indent
        } else {
            current_indent
        };

        if let Some((kind, value)) = parse_comment_head(content) {
            let mut children = Vec::new();
            let mut next_index = index + 1;

            if block_has_children(lines, next_index, nesting_parent_indent) {
                let child_indent = determine_child_indent(lines, next_index, nesting_parent_indent);
                let (parsed_children, consumed_index) =
                    parse_raw_text_block(lines, next_index, child_indent, diagnostics);
                children = parsed_children;
                next_index = consumed_index;
            }

            nodes.push(Node::Comment(CommentNode {
                kind,
                value,
                children,
            }));
            index = next_index;
            continue;
        }

        if let Some(text) = content.strip_prefix('|') {
            nodes.push(Node::Text(TextLineNode {
                kind: TextLineKind::Piped,
                content: text.to_string(),
            }));
            index += 1;
            continue;
        }

        let (statement_content, next_index) = collect_statement_lines(lines, index, current_indent);
        let (statement_content, has_text_block_suffix) =
            split_text_block_suffix(&statement_content);
        let head = parse_statement_head(statement_content, index + 1, diagnostics);
        validate_statement_context(&head, &nodes, index + 1, diagnostics);
        let text_block_kind = determine_text_block_kind(&head, has_text_block_suffix);

        let mut node = Node::Statement(StatementNode {
            head,
            text_block_kind,
            children: Vec::new(),
        });

        if block_has_children(lines, next_index, nesting_parent_indent) {
            if let Node::Statement(statement) = &mut node {
                let next_mode = if statement.text_block_kind.is_some() {
                    ParseMode::RawText
                } else {
                    ParseMode::Normal
                };
                let child_indent = determine_child_indent(lines, next_index, nesting_parent_indent);
                let (children, consumed_index) =
                    parse_block(lines, next_index, child_indent, next_mode, diagnostics);
                statement.children = children;
                index = consumed_index;
            } else {
                index = next_index;
            }
        } else {
            index = next_index;
        }

        nodes.push(node);
    }

    (nodes, index)
}

fn block_has_children(lines: &[LexedLine], start_index: usize, parent_indent: usize) -> bool {
    let mut index = start_index;

    while index < lines.len() {
        let line = &lines[index];
        if line.is_blank {
            index += 1;
            continue;
        }

        return line.indent > parent_indent;
    }

    false
}

fn determine_child_indent(lines: &[LexedLine], start_index: usize, parent_indent: usize) -> usize {
    let mut minimum_indent: Option<usize> = None;
    let mut index = start_index;

    while index < lines.len() {
        let line = &lines[index];

        if !line.is_blank && line.indent <= parent_indent {
            break;
        }

        if !line.is_blank && line.indent > parent_indent {
            minimum_indent = Some(match minimum_indent {
                Some(current_minimum) => current_minimum.min(line.indent),
                None => line.indent,
            });
        }

        index += 1;
    }

    minimum_indent.unwrap_or(lines[start_index].indent)
}

fn collect_statement_lines(
    lines: &[LexedLine],
    start_index: usize,
    current_indent: usize,
) -> (String, usize) {
    let mut content = lines[start_index].content.trim_start().to_string();
    let Some(collection_mode) = multiline_statement_mode(&content) else {
        return (content, start_index + 1);
    };

    let mut index = start_index + 1;
    while index < lines.len() && has_unclosed_parenthesis(&content) {
        let line = &lines[index];
        if line.indent < current_indent {
            break;
        }

        content.push('\n');
        match collection_mode {
            MultilineStatementMode::Normalized => content.push_str(line.content.trim()),
            MultilineStatementMode::PreserveLayout => {
                content.push_str(&" ".repeat(line.indent));
                content.push_str(&line.content);
            }
        }
        index += 1;
    }

    (content, index)
}

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum MultilineStatementMode {
    Normalized,
    PreserveLayout,
}

fn multiline_statement_mode(content: &str) -> Option<MultilineStatementMode> {
    if !has_unclosed_parenthesis(content) {
        return None;
    }

    if starts_attribute_list_in_head(content) {
        return Some(MultilineStatementMode::Normalized);
    }

    if content.starts_with('+') {
        return Some(MultilineStatementMode::PreserveLayout);
    }

    None
}

fn has_unclosed_parenthesis(content: &str) -> bool {
    let mut in_quote = None;
    let mut escaped = false;
    let mut depth = 0isize;

    for ch in content.chars() {
        if let Some(quote) = in_quote {
            if escaped {
                escaped = false;
                continue;
            }

            if ch == '\\' {
                escaped = true;
                continue;
            }

            if ch == quote {
                in_quote = None;
            }
            continue;
        }

        match ch {
            '\'' | '"' => in_quote = Some(ch),
            '(' => depth += 1,
            ')' => depth -= 1,
            _ => {}
        }
    }

    depth > 0
}

fn starts_attribute_list_in_head(content: &str) -> bool {
    let mut cursor = 0;

    if let Some((_, next_cursor)) = parse_tag_name(content, cursor) {
        cursor = next_cursor;
    }

    while let Some(marker) = content[cursor..].chars().next() {
        if marker != '#' && marker != '.' {
            break;
        }

        let segment_start = cursor + marker.len_utf8();
        let Some((_, next_cursor)) = parse_shorthand_value(content, segment_start) else {
            return false;
        };
        cursor = next_cursor;
    }

    content[cursor..].starts_with('(')
}

fn split_text_block_suffix(content: &str) -> (&str, bool) {
    let trimmed_end = content.trim_end_matches(char::is_whitespace);

    if trimmed_end == "." {
        return ("", true);
    }

    if matches!(trimmed_end, "" | "..") {
        return (content, false);
    }

    if let Some(without_dot) = trimmed_end.strip_suffix('.')
        && (without_dot.is_empty()
            || !without_dot
                .chars()
                .last()
                .is_some_and(|ch| ch.is_whitespace()))
    {
        return (without_dot, true);
    }

    (content, false)
}

fn parse_statement_head(
    content: &str,
    line: usize,
    diagnostics: &mut Vec<Diagnostic>,
) -> StatementHead {
    if let Some(head) = parse_doctype_head(content) {
        return StatementHead::Doctype(head);
    }

    if let Some(head) = parse_code_head(content) {
        return StatementHead::Code(head);
    }

    if let Some(head) = parse_control_flow_head(content) {
        validate_control_flow_head(&head, line, diagnostics);
        return StatementHead::ControlFlow(head);
    }

    if let Some(head) = parse_filter_head(content) {
        return StatementHead::Filter(head);
    }

    if let Some(head) = parse_include_head(content) {
        validate_include_head(&head, line, diagnostics);
        return StatementHead::Include(head);
    }

    if let Some(head) = parse_extends_head(content) {
        validate_extends_head(&head, line, diagnostics);
        return StatementHead::Extends(head);
    }

    if let Some(head) = parse_block_head(content) {
        validate_block_head(&head, line, diagnostics);
        return StatementHead::Block(head);
    }

    if let Some(head) = parse_mixin_head(content) {
        return StatementHead::Mixin(head);
    }

    if let Some(head) = parse_mixin_call_head(content) {
        return StatementHead::MixinCall(head);
    }

    if let Some(head) = parse_tag_head(content) {
        return StatementHead::Tag(head);
    }

    StatementHead::Raw(content.to_string())
}

fn validate_include_head(head: &IncludeHead, line: usize, diagnostics: &mut Vec<Diagnostic>) {
    if head.suffix.trim().is_empty() {
        diagnostics.push(Diagnostic {
            severity: DiagnosticSeverity::Warning,
            line,
            message: String::from("Recovered `include` without a path"),
        });
    }
}

fn validate_extends_head(head: &ExtendsHead, line: usize, diagnostics: &mut Vec<Diagnostic>) {
    if head.suffix.trim().is_empty() {
        diagnostics.push(Diagnostic {
            severity: DiagnosticSeverity::Warning,
            line,
            message: String::from("Recovered `extends` without a path"),
        });
    }
}

fn validate_block_head(head: &BlockHead, line: usize, diagnostics: &mut Vec<Diagnostic>) {
    if head.mode.is_some() && head.target.is_none() {
        diagnostics.push(Diagnostic {
            severity: DiagnosticSeverity::Warning,
            line,
            message: String::from("Recovered `block append`/`block prepend` without a target"),
        });
    }
}

fn validate_control_flow_head(
    head: &ControlFlowHead,
    line: usize,
    diagnostics: &mut Vec<Diagnostic>,
) {
    let missing_payload = match head.kind {
        ControlFlowKind::If
        | ControlFlowKind::ElseIf
        | ControlFlowKind::Case
        | ControlFlowKind::When
        | ControlFlowKind::Each
        | ControlFlowKind::While => head.suffix.trim().is_empty(),
        ControlFlowKind::Else | ControlFlowKind::Default => false,
    };

    if missing_payload {
        diagnostics.push(Diagnostic {
            severity: DiagnosticSeverity::Warning,
            line,
            message: format!(
                "Recovered `{}` without the required expression",
                control_flow_keyword(head.kind)
            ),
        });
    }

    let unexpected_payload = match head.kind {
        ControlFlowKind::Else => !head.suffix.trim().is_empty(),
        ControlFlowKind::Default => {
            let trimmed = head.suffix.trim_start();
            !trimmed.is_empty() && !trimmed.starts_with(':')
        }
        ControlFlowKind::If
        | ControlFlowKind::ElseIf
        | ControlFlowKind::Case
        | ControlFlowKind::When
        | ControlFlowKind::Each
        | ControlFlowKind::While => false,
    };

    if unexpected_payload {
        diagnostics.push(Diagnostic {
            severity: DiagnosticSeverity::Warning,
            line,
            message: format!(
                "Recovered `{}` with unexpected trailing content",
                control_flow_keyword(head.kind)
            ),
        });
    }
}

fn validate_statement_context(
    head: &StatementHead,
    prior_nodes: &[Node],
    line: usize,
    diagnostics: &mut Vec<Diagnostic>,
) {
    let StatementHead::ControlFlow(head) = head else {
        return;
    };

    match head.kind {
        ControlFlowKind::Else => {
            if !previous_statement_head(prior_nodes).is_some_and(is_valid_else_predecessor) {
                diagnostics.push(Diagnostic {
                    severity: DiagnosticSeverity::Warning,
                    line,
                    message: String::from("Recovered orphaned `else` without a matching `if`"),
                });
            }
        }
        ControlFlowKind::Default
            if !matches!(
                previous_statement_head(prior_nodes),
                Some(StatementHead::ControlFlow(ControlFlowHead {
                    kind: ControlFlowKind::When,
                    ..
                }))
            ) =>
        {
            diagnostics.push(Diagnostic {
                severity: DiagnosticSeverity::Warning,
                line,
                message: String::from("Recovered orphaned `default` without a preceding `when`"),
            });
        }
        _ => {}
    }
}

fn previous_statement_head(nodes: &[Node]) -> Option<&StatementHead> {
    nodes.iter().rev().find_map(|node| match node {
        Node::Statement(statement) => Some(&statement.head),
        Node::Comment(_) | Node::Text(_) | Node::RawText(_) => None,
    })
}

fn is_valid_else_predecessor(head: &StatementHead) -> bool {
    matches!(
        head,
        StatementHead::ControlFlow(ControlFlowHead {
            kind: ControlFlowKind::If | ControlFlowKind::ElseIf | ControlFlowKind::Each,
            ..
        })
    ) || is_for_loop_head(head)
}

fn is_for_loop_head(head: &StatementHead) -> bool {
    let StatementHead::Tag(head) = head else {
        return false;
    };

    head.tag_name.as_deref() == Some("for")
}

fn control_flow_keyword(kind: ControlFlowKind) -> &'static str {
    match kind {
        ControlFlowKind::If => "if",
        ControlFlowKind::ElseIf => "else if",
        ControlFlowKind::Else => "else",
        ControlFlowKind::Case => "case",
        ControlFlowKind::When => "when",
        ControlFlowKind::Default => "default",
        ControlFlowKind::Each => "each",
        ControlFlowKind::While => "while",
    }
}

fn determine_text_block_kind(
    head: &StatementHead,
    has_text_block_suffix: bool,
) -> Option<TextBlockKind> {
    if matches!(head, StatementHead::Filter(_)) {
        return Some(TextBlockKind::Raw);
    }

    has_text_block_suffix.then(|| classify_text_block_kind(head))
}

fn parse_comment_head(content: &str) -> Option<(CommentKind, Option<String>)> {
    if let Some(comment) = content.strip_prefix("//-") {
        return Some((CommentKind::Unbuffered, parse_optional_payload(comment)));
    }

    let comment = content.strip_prefix("//")?;
    Some((CommentKind::Buffered, parse_optional_payload(comment)))
}

fn parse_filter_head(content: &str) -> Option<FilterHead> {
    let name = content.strip_prefix(':')?;
    if name.is_empty() || name.chars().any(char::is_whitespace) {
        return None;
    }

    Some(FilterHead {
        name: name.to_string(),
    })
}

fn parse_code_head(content: &str) -> Option<CodeHead> {
    if let Some(suffix) = content.strip_prefix("!=") {
        return Some(CodeHead {
            kind: CodeKind::UnescapedBuffered,
            suffix: suffix.to_string(),
        });
    }

    if let Some(suffix) = content.strip_prefix('=') {
        return Some(CodeHead {
            kind: CodeKind::EscapedBuffered,
            suffix: suffix.to_string(),
        });
    }

    if let Some(suffix) = content.strip_prefix('-') {
        return Some(CodeHead {
            kind: CodeKind::Unbuffered,
            suffix: suffix.to_string(),
        });
    }

    None
}

fn parse_control_flow_head(content: &str) -> Option<ControlFlowHead> {
    const KEYWORDS: &[(ControlFlowKind, &str)] = &[
        (ControlFlowKind::ElseIf, "else if"),
        (ControlFlowKind::Else, "else"),
        (ControlFlowKind::If, "if"),
        (ControlFlowKind::Case, "case"),
        (ControlFlowKind::When, "when"),
        (ControlFlowKind::Default, "default"),
        (ControlFlowKind::Each, "each"),
        (ControlFlowKind::While, "while"),
    ];

    for (kind, keyword) in KEYWORDS {
        let Some(suffix) = content.strip_prefix(keyword) else {
            continue;
        };

        if !starts_control_flow_suffix(suffix) {
            continue;
        }

        return Some(ControlFlowHead {
            kind: *kind,
            suffix: suffix.to_string(),
        });
    }

    None
}

fn parse_include_head(content: &str) -> Option<IncludeHead> {
    let suffix = content.strip_prefix("include")?;
    if !starts_keyword_suffix(suffix) {
        return None;
    }

    Some(IncludeHead {
        suffix: suffix.to_string(),
    })
}

fn parse_extends_head(content: &str) -> Option<ExtendsHead> {
    let suffix = content.strip_prefix("extends")?;
    if !starts_keyword_suffix(suffix) {
        return None;
    }

    Some(ExtendsHead {
        suffix: suffix.to_string(),
    })
}

fn parse_block_head(content: &str) -> Option<BlockHead> {
    let suffix = content.strip_prefix("block")?;
    if !starts_keyword_suffix(suffix) {
        return None;
    }

    let trimmed = suffix.trim_start();
    let (mode, target) = if let Some(rest) = trimmed.strip_prefix("append")
        && starts_keyword_suffix(rest)
    {
        (Some(BlockMode::Append), parse_optional_payload(rest))
    } else if let Some(rest) = trimmed.strip_prefix("prepend")
        && starts_keyword_suffix(rest)
    {
        (Some(BlockMode::Prepend), parse_optional_payload(rest))
    } else {
        (None, parse_optional_payload(trimmed))
    };

    Some(BlockHead {
        mode,
        target,
        suffix: suffix.to_string(),
    })
}

fn parse_mixin_head(content: &str) -> Option<MixinHead> {
    let suffix = content.strip_prefix("mixin")?;
    if !starts_keyword_suffix(suffix) {
        return None;
    }

    Some(MixinHead {
        suffix: suffix.to_string(),
    })
}

fn parse_mixin_call_head(content: &str) -> Option<MixinCallHead> {
    let suffix = content.strip_prefix('+')?;
    if suffix.is_empty() {
        return None;
    }

    Some(MixinCallHead {
        suffix: suffix.to_string(),
    })
}

fn starts_keyword_suffix(suffix: &str) -> bool {
    suffix.is_empty() || suffix.chars().next().is_some_and(|ch| ch.is_whitespace())
}

fn parse_optional_payload(content: &str) -> Option<String> {
    let trimmed = content.trim();
    (!trimmed.is_empty()).then(|| trimmed.to_string())
}

fn parse_raw_text_block(
    lines: &[LexedLine],
    index: usize,
    current_indent: usize,
    diagnostics: &mut Vec<Diagnostic>,
) -> (Vec<RawTextNode>, usize) {
    let (children, consumed_index) = parse_block(
        lines,
        index,
        current_indent,
        ParseMode::RawText,
        diagnostics,
    );
    let raw_text = children
        .into_iter()
        .map(|node| match node {
            Node::RawText(text) => text,
            _ => unreachable!("raw text parse mode should only produce raw text nodes"),
        })
        .collect();

    (raw_text, consumed_index)
}

fn starts_control_flow_suffix(suffix: &str) -> bool {
    suffix.is_empty()
        || suffix
            .chars()
            .next()
            .is_some_and(|ch| ch.is_whitespace() || !is_identifier_continue(ch))
}

fn is_identifier_continue(ch: char) -> bool {
    ch.is_ascii_alphanumeric() || ch == '_' || ch == '-'
}

fn parse_doctype_head(content: &str) -> Option<DoctypeHead> {
    if content == "doctype" {
        return Some(DoctypeHead {
            spacing: None,
            value: None,
        });
    }

    let suffix = content.strip_prefix("doctype")?;
    if suffix.is_empty() || !suffix.chars().next().is_some_and(|ch| ch.is_whitespace()) {
        return None;
    }

    let spacing_len = suffix
        .chars()
        .take_while(|ch| ch.is_whitespace())
        .map(char::len_utf8)
        .sum();

    let spacing = &suffix[..spacing_len];
    let value = &suffix[spacing_len..];

    Some(DoctypeHead {
        spacing: Some(spacing.to_string()),
        value: Some(value.to_string()),
    })
}

fn parse_tag_head(content: &str) -> Option<TagHead> {
    let mut cursor = 0;
    let mut tag_name = None;
    let mut shorthand_id = None;
    let mut shorthand_classes = Vec::new();

    if let Some((name, next_cursor)) = parse_tag_name(content, cursor) {
        tag_name = Some(name.to_string());
        cursor = next_cursor;
    }

    while let Some(marker) = content[cursor..].chars().next() {
        if marker != '#' && marker != '.' {
            break;
        }

        let segment_start = cursor + marker.len_utf8();
        let (value, next_cursor) = parse_shorthand_value(content, segment_start)?;

        if marker == '#' {
            if shorthand_id.is_some() {
                return None;
            }
            shorthand_id = Some(value.to_string());
        } else {
            shorthand_classes.push(value.to_string());
        }

        cursor = next_cursor;
    }

    if tag_name.is_none() && shorthand_id.is_none() && shorthand_classes.is_empty() {
        return None;
    }

    let mut attributes = None;
    if content[cursor..].starts_with('(') {
        let end = find_matching_paren(content, cursor)?;
        attributes = Some(parse_attributes(&content[cursor + 1..end])?);
        cursor = end + 1;
    }

    let mut inline_space = None;
    let mut inline_text = None;
    if cursor < content.len() {
        let remainder = &content[cursor..];
        if !remainder
            .chars()
            .next()
            .is_some_and(|ch| ch.is_whitespace())
        {
            return None;
        }

        let spacing_len = remainder
            .chars()
            .take_while(|ch| ch.is_whitespace())
            .map(char::len_utf8)
            .sum();
        let spacing = &remainder[..spacing_len];
        let text = &remainder[spacing_len..];

        if !text.is_empty() {
            inline_space = Some(spacing.to_string());
            inline_text = Some(InlineText {
                kind: classify_inline_text(text),
                content: text.to_string(),
            });
        }
    }

    Some(TagHead {
        tag_name,
        shorthand_id,
        shorthand_classes,
        attributes,
        inline_space,
        inline_text,
    })
}

fn classify_text_block_kind(head: &StatementHead) -> TextBlockKind {
    match head {
        StatementHead::Filter(_) => TextBlockKind::Raw,
        StatementHead::Tag(head)
            if head
                .tag_name
                .as_deref()
                .is_some_and(is_code_like_raw_text_tag) =>
        {
            TextBlockKind::Raw
        }
        _ => TextBlockKind::Prose,
    }
}

fn is_code_like_raw_text_tag(tag: &str) -> bool {
    matches!(tag, "pre" | "script" | "style" | "textarea")
}

fn classify_inline_text(text: &str) -> InlineTextKind {
    if text.trim_start().starts_with('<') {
        return InlineTextKind::LiteralHtml;
    }

    if text.contains("#[") || text.contains("#{") || text.contains("!{") {
        return InlineTextKind::Interpolated;
    }

    InlineTextKind::Plain
}

fn parse_attributes(content: &str) -> Option<Vec<Attribute>> {
    let trimmed = content.trim();
    if trimmed.is_empty() {
        return Some(Vec::new());
    }

    let mut attributes = Vec::new();
    for entry in split_top_level_attributes(trimmed) {
        let entry = entry.trim();
        if entry.is_empty() {
            continue;
        }
        attributes.push(parse_attribute(entry)?);
    }

    Some(attributes)
}

fn parse_attribute(content: &str) -> Option<Attribute> {
    let trimmed = content.trim();
    let Some(split_index) = find_top_level_equals(trimmed) else {
        return Some(Attribute {
            name: trimmed.to_string(),
            value: None,
        });
    };

    let name = trimmed[..split_index].trim();
    let value = trimmed[split_index + 1..].trim();

    if name.is_empty() || value.is_empty() {
        return None;
    }

    Some(Attribute {
        name: name.to_string(),
        value: Some(parse_attribute_value(value)),
    })
}

fn parse_attribute_value(content: &str) -> AttributeValue {
    if let Some((quote_style, value)) = parse_quoted_value(content) {
        return AttributeValue::Quoted { value, quote_style };
    }

    AttributeValue::Expression(content.to_string())
}

fn parse_quoted_value(content: &str) -> Option<(QuoteStyle, String)> {
    if content.len() < 2 {
        return None;
    }

    let mut chars = content.chars();
    let first = chars.next()?;
    let last = content.chars().last()?;

    let quote_style = match first {
        '"' if last == '"' => QuoteStyle::Double,
        '\'' if last == '\'' => QuoteStyle::Single,
        _ => return None,
    };

    if !is_wrapped_in_single_top_level_quote(content, first) {
        return None;
    }

    let inner = &content[first.len_utf8()..content.len() - last.len_utf8()];
    Some((quote_style, inner.to_string()))
}

fn is_wrapped_in_single_top_level_quote(content: &str, quote: char) -> bool {
    let mut escaped = false;
    let mut close_index = None;

    for (index, ch) in content.char_indices().skip(1) {
        if escaped {
            escaped = false;
            continue;
        }

        if ch == '\\' {
            escaped = true;
            continue;
        }

        if ch == quote {
            close_index = Some(index);
            break;
        }
    }

    close_index == Some(content.len() - quote.len_utf8())
}

fn find_top_level_equals(content: &str) -> Option<usize> {
    let mut in_quote = None;
    let mut escaped = false;
    let mut paren_depth = 0isize;
    let mut bracket_depth = 0isize;
    let mut brace_depth = 0isize;

    for (index, ch) in content.char_indices() {
        if let Some(quote) = in_quote {
            if escaped {
                escaped = false;
                continue;
            }

            if ch == '\\' {
                escaped = true;
                continue;
            }

            if ch == quote {
                in_quote = None;
            }
            continue;
        }

        match ch {
            '\'' | '"' => in_quote = Some(ch),
            '(' => paren_depth += 1,
            ')' => paren_depth -= 1,
            '[' => bracket_depth += 1,
            ']' => bracket_depth -= 1,
            '{' => brace_depth += 1,
            '}' => brace_depth -= 1,
            '=' if paren_depth == 0 && bracket_depth == 0 && brace_depth == 0 => {
                return Some(index);
            }
            _ => {}
        }
    }

    None
}

fn split_top_level_attributes(content: &str) -> Vec<&str> {
    let mut parts = Vec::new();
    let mut start = 0;
    let mut in_quote = None;
    let mut escaped = false;
    let mut paren_depth = 0isize;
    let mut bracket_depth = 0isize;
    let mut brace_depth = 0isize;

    for (index, ch) in content.char_indices() {
        if let Some(quote) = in_quote {
            if escaped {
                escaped = false;
                continue;
            }

            if ch == '\\' {
                escaped = true;
                continue;
            }

            if ch == quote {
                in_quote = None;
            }
            continue;
        }

        match ch {
            '\'' | '"' => in_quote = Some(ch),
            '(' => paren_depth += 1,
            ')' => paren_depth -= 1,
            '[' => bracket_depth += 1,
            ']' => bracket_depth -= 1,
            '{' => brace_depth += 1,
            '}' => brace_depth -= 1,
            ',' if paren_depth == 0 && bracket_depth == 0 && brace_depth == 0 => {
                parts.push(&content[start..index]);
                start = index + ch.len_utf8();
            }
            '\n' if paren_depth == 0 && bracket_depth == 0 && brace_depth == 0 => {
                parts.push(&content[start..index]);
                start = index + ch.len_utf8();
            }
            _ => {}
        }
    }

    parts.push(&content[start..]);
    parts
}

fn parse_tag_name(content: &str, start: usize) -> Option<(&str, usize)> {
    let mut chars = content[start..].char_indices();
    let (_, first) = chars.next()?;
    if !is_tag_name_start(first) {
        return None;
    }

    let mut end = start + first.len_utf8();
    for (offset, ch) in chars {
        if !is_tag_name_continue(ch) {
            break;
        }
        end = start + offset + ch.len_utf8();
    }

    Some((&content[start..end], end))
}

fn parse_shorthand_value(content: &str, start: usize) -> Option<(&str, usize)> {
    let mut chars = content[start..].char_indices();
    let (_, first) = chars.next()?;
    if !is_shorthand_char(first) {
        return None;
    }

    let mut end = start + first.len_utf8();
    for (offset, ch) in chars {
        if !is_shorthand_char(ch) {
            break;
        }
        end = start + offset + ch.len_utf8();
    }

    Some((&content[start..end], end))
}

fn find_matching_paren(content: &str, open_index: usize) -> Option<usize> {
    let mut depth = 0;
    let mut in_quote = None;
    let mut escaped = false;

    for (offset, ch) in content[open_index..].char_indices() {
        if let Some(quote) = in_quote {
            if escaped {
                escaped = false;
                continue;
            }

            if ch == '\\' {
                escaped = true;
                continue;
            }

            if ch == quote {
                in_quote = None;
            }
            continue;
        }

        match ch {
            '\'' | '"' => in_quote = Some(ch),
            '(' => depth += 1,
            ')' => {
                depth -= 1;
                if depth == 0 {
                    return Some(open_index + offset);
                }
            }
            _ => {}
        }
    }

    None
}

fn is_tag_name_start(ch: char) -> bool {
    ch.is_ascii_alphabetic()
}

fn is_tag_name_continue(ch: char) -> bool {
    ch.is_ascii_alphanumeric() || matches!(ch, '-' | '_')
}

fn is_shorthand_char(ch: char) -> bool {
    ch.is_ascii_alphanumeric() || matches!(ch, '-' | '_')
}
