use super::*;

impl<'a> Parser<'a> {
    pub(super) fn brace_syntax_from_parts(
        &self,
        parts: &[WordPartNode],
        offset: usize,
    ) -> Vec<BraceSyntax> {
        if !self.brace_syntax_enabled_at(offset) {
            return Vec::new();
        }
        let brace_ccl_enabled = self.brace_ccl_enabled_at(offset);
        let mut brace_syntax = Vec::new();
        self.collect_brace_syntax_from_parts(
            parts,
            BraceQuoteContext::Unquoted,
            brace_ccl_enabled,
            &mut brace_syntax,
        );
        brace_syntax.sort_by_key(|brace| (brace.span.start.offset(), brace.span.end.offset()));
        brace_syntax.dedup_by_key(|brace| {
            (
                brace.span.start.offset(),
                brace.span.end.offset(),
                brace.quote_context,
                brace.kind,
            )
        });
        brace_syntax
    }

    pub(super) fn collect_brace_syntax_from_parts(
        &self,
        parts: &[WordPartNode],
        quote_context: BraceQuoteContext,
        brace_ccl_enabled: bool,
        out: &mut Vec<BraceSyntax>,
    ) {
        for part in parts {
            match &part.kind {
                WordPart::Literal(text) => Self::scan_brace_syntax_text(
                    text.syntax_str(self.input, part.span),
                    part.span.start,
                    quote_context,
                    brace_ccl_enabled,
                    out,
                ),
                WordPart::SingleQuoted { .. } => Self::scan_brace_syntax_text(
                    part.span.slice(self.input),
                    part.span.start,
                    BraceQuoteContext::SingleQuoted,
                    brace_ccl_enabled,
                    out,
                ),
                WordPart::DoubleQuoted { parts, .. } => self.collect_brace_syntax_from_parts(
                    parts,
                    BraceQuoteContext::DoubleQuoted,
                    brace_ccl_enabled,
                    out,
                ),
                WordPart::ZshQualifiedGlob(glob) => self
                    .collect_brace_syntax_from_zsh_qualified_glob(
                        glob,
                        quote_context,
                        brace_ccl_enabled,
                        out,
                    ),
                WordPart::Variable(_)
                | WordPart::CommandSubstitution { .. }
                | WordPart::ArithmeticExpansion { .. }
                | WordPart::Parameter(_)
                | WordPart::ParameterExpansion { .. }
                | WordPart::Length(_)
                | WordPart::ArrayAccess(_)
                | WordPart::ArrayLength(_)
                | WordPart::ArrayIndices(_)
                | WordPart::Substring { .. }
                | WordPart::ArraySlice { .. }
                | WordPart::IndirectExpansion { .. }
                | WordPart::PrefixMatch { .. }
                | WordPart::ProcessSubstitution { .. }
                | WordPart::Transformation { .. } => {}
            }
        }

        if self.needs_cross_part_brace_scan(parts) {
            let mut chars = self.brace_scan_chars.take();
            chars.clear();
            self.collect_brace_scan_chars_from_parts(parts, &mut chars);
            Self::scan_brace_syntax_chars(&chars, quote_context, brace_ccl_enabled, out);
            self.brace_scan_chars.replace(chars);
        }
    }

    pub(super) fn needs_cross_part_brace_scan(&self, parts: &[WordPartNode]) -> bool {
        if parts.len() < 2 {
            return false;
        }

        let mut cursor = parts[0].span.start.offset();
        for part in parts {
            if part.span.start.offset() > cursor
                && self
                    .input
                    .get(cursor..part.span.start.offset())
                    .is_some_and(|raw| raw.contains(['{', '}']))
            {
                return true;
            }

            let has_brace_text = match &part.kind {
                WordPart::Literal(text) => {
                    text.syntax_str(self.input, part.span).contains(['{', '}'])
                }
                WordPart::SingleQuoted { .. }
                | WordPart::DoubleQuoted { .. }
                | WordPart::ZshQualifiedGlob(_) => self
                    .input
                    .get(part.span.start.offset()..part.span.end.offset())
                    .is_some_and(|raw| raw.contains(['{', '}'])),
                WordPart::Variable(_)
                | WordPart::CommandSubstitution { .. }
                | WordPart::ArithmeticExpansion { .. }
                | WordPart::Parameter(_)
                | WordPart::ParameterExpansion { .. }
                | WordPart::Length(_)
                | WordPart::ArrayAccess(_)
                | WordPart::ArrayLength(_)
                | WordPart::ArrayIndices(_)
                | WordPart::Substring { .. }
                | WordPart::ArraySlice { .. }
                | WordPart::IndirectExpansion { .. }
                | WordPart::PrefixMatch { .. }
                | WordPart::ProcessSubstitution { .. }
                | WordPart::Transformation { .. } => false,
            };
            if has_brace_text {
                return true;
            }

            cursor = part.span.end.offset();
        }

        false
    }

    pub(super) fn collect_brace_scan_chars_from_parts(
        &self,
        parts: &[WordPartNode],
        out: &mut Vec<(char, Position)>,
    ) {
        for part in parts {
            match &part.kind {
                WordPart::Literal(text) => {
                    Self::push_brace_scan_text(
                        text.syntax_str(self.input, part.span),
                        part.span.start,
                        out,
                    );
                }
                WordPart::SingleQuoted { .. } => {
                    if let Some(raw) = self
                        .input
                        .get(part.span.start.offset()..part.span.end.offset())
                    {
                        Self::push_brace_scan_text(raw, part.span.start, out);
                    }
                }
                WordPart::DoubleQuoted { parts, .. } => {
                    self.collect_brace_scan_chars_from_double_quoted_part(part.span, parts, out);
                }
                WordPart::ZshQualifiedGlob(_) => {}
                WordPart::Variable(_)
                | WordPart::CommandSubstitution { .. }
                | WordPart::ArithmeticExpansion { .. }
                | WordPart::Parameter(_)
                | WordPart::ParameterExpansion { .. }
                | WordPart::Length(_)
                | WordPart::ArrayAccess(_)
                | WordPart::ArrayLength(_)
                | WordPart::ArrayIndices(_)
                | WordPart::Substring { .. }
                | WordPart::ArraySlice { .. }
                | WordPart::IndirectExpansion { .. }
                | WordPart::PrefixMatch { .. }
                | WordPart::ProcessSubstitution { .. }
                | WordPart::Transformation { .. } => {
                    Self::push_brace_scan_boundary(part.span.start, out);
                }
            }
        }
    }

    pub(super) fn collect_brace_scan_chars_from_double_quoted_part(
        &self,
        span: Span,
        parts: &[WordPartNode],
        out: &mut Vec<(char, Position)>,
    ) {
        let mut cursor_offset = span.start.offset();
        let mut cursor_position = span.start;

        for part in parts {
            if part.span.start.offset() > cursor_offset
                && let Some(raw) = self.input.get(cursor_offset..part.span.start.offset())
            {
                Self::push_brace_scan_text(raw, cursor_position, out);
            }

            match &part.kind {
                WordPart::Literal(text) => {
                    Self::push_brace_scan_text(
                        text.syntax_str(self.input, part.span),
                        part.span.start,
                        out,
                    );
                }
                WordPart::SingleQuoted { .. } => {
                    if let Some(raw) = self
                        .input
                        .get(part.span.start.offset()..part.span.end.offset())
                    {
                        Self::push_brace_scan_text(raw, part.span.start, out);
                    }
                }
                WordPart::DoubleQuoted { parts, .. } => {
                    self.collect_brace_scan_chars_from_double_quoted_part(part.span, parts, out);
                }
                WordPart::ZshQualifiedGlob(_) => {}
                WordPart::Variable(_)
                | WordPart::CommandSubstitution { .. }
                | WordPart::ArithmeticExpansion { .. }
                | WordPart::Parameter(_)
                | WordPart::ParameterExpansion { .. }
                | WordPart::Length(_)
                | WordPart::ArrayAccess(_)
                | WordPart::ArrayLength(_)
                | WordPart::ArrayIndices(_)
                | WordPart::Substring { .. }
                | WordPart::ArraySlice { .. }
                | WordPart::IndirectExpansion { .. }
                | WordPart::PrefixMatch { .. }
                | WordPart::ProcessSubstitution { .. }
                | WordPart::Transformation { .. } => {
                    Self::push_brace_scan_boundary(part.span.start, out);
                }
            }

            cursor_offset = part.span.end.offset();
            cursor_position = part.span.end;
        }

        if cursor_offset < span.end.offset()
            && let Some(raw) = self.input.get(cursor_offset..span.end.offset())
        {
            Self::push_brace_scan_text(raw, cursor_position, out);
        }
    }

    pub(super) fn push_brace_scan_text(
        text: &str,
        start: Position,
        out: &mut Vec<(char, Position)>,
    ) {
        let mut position = start;
        for ch in text.chars() {
            out.push((ch, position));
            position.advance(ch);
        }
    }

    pub(super) fn push_brace_scan_boundary(position: Position, out: &mut Vec<(char, Position)>) {
        out.push(('\0', position));
    }

    pub(super) fn scan_brace_syntax_chars(
        chars: &[(char, Position)],
        initial_quote_context: BraceQuoteContext,
        brace_ccl_enabled: bool,
        out: &mut Vec<BraceSyntax>,
    ) {
        #[derive(Clone, Copy, PartialEq, Eq)]
        enum QuoteState {
            Single,
            AnsiSingle,
            Double,
        }

        #[derive(Clone, Copy)]
        struct Candidate {
            start: Position,
            quote_context: BraceQuoteContext,
            has_comma: bool,
            has_dot_dot: bool,
            saw_unquoted_whitespace: bool,
            has_brace_ccl_content: bool,
            saw_quote_boundary: bool,
            prev_char: Option<char>,
        }

        fn quote_state(context: BraceQuoteContext) -> Option<QuoteState> {
            match context {
                BraceQuoteContext::Unquoted => None,
                BraceQuoteContext::SingleQuoted => Some(QuoteState::Single),
                BraceQuoteContext::DoubleQuoted => Some(QuoteState::Double),
            }
        }

        fn quote_context(state: Option<QuoteState>) -> BraceQuoteContext {
            match state {
                None => BraceQuoteContext::Unquoted,
                Some(QuoteState::Single | QuoteState::AnsiSingle) => {
                    BraceQuoteContext::SingleQuoted
                }
                Some(QuoteState::Double) => BraceQuoteContext::DoubleQuoted,
            }
        }

        fn quote_context_is_active(context: BraceQuoteContext, state: Option<QuoteState>) -> bool {
            match context {
                BraceQuoteContext::Unquoted => state.is_none(),
                BraceQuoteContext::SingleQuoted => {
                    matches!(state, Some(QuoteState::Single | QuoteState::AnsiSingle))
                }
                BraceQuoteContext::DoubleQuoted => matches!(state, Some(QuoteState::Double)),
            }
        }

        fn last_active_candidate_index(
            stack: &[Candidate],
            state: Option<QuoteState>,
        ) -> Option<usize> {
            stack
                .iter()
                .rposition(|candidate| quote_context_is_active(candidate.quote_context, state))
        }

        fn mark_brace_ccl_content(stack: &mut [Candidate], state: Option<QuoteState>) {
            if let Some(candidate_index) = last_active_candidate_index(stack, state) {
                stack[candidate_index].has_brace_ccl_content = true;
            }
        }

        fn template_placeholder_end(
            chars: &[(char, Position)],
            start: usize,
            quote_context: BraceQuoteContext,
        ) -> Option<usize> {
            if chars.get(start)?.0 != '{' || chars.get(start + 1)?.0 != '{' {
                return None;
            }

            let mut index = start + 2;
            let mut depth = 1usize;

            while index < chars.len() {
                if matches!(quote_context, BraceQuoteContext::Unquoted) && chars[index].0 == '\\' {
                    index += 1;
                    if index < chars.len() {
                        index += 1;
                    }
                    continue;
                }

                if chars.get(index).is_some_and(|(ch, _)| *ch == '{')
                    && chars.get(index + 1).is_some_and(|(ch, _)| *ch == '{')
                {
                    depth += 1;
                    index += 2;
                    continue;
                }

                if chars.get(index).is_some_and(|(ch, _)| *ch == '}')
                    && chars.get(index + 1).is_some_and(|(ch, _)| *ch == '}')
                {
                    depth -= 1;
                    index += 2;
                    if depth == 0 {
                        return Some(index);
                    }
                    continue;
                }

                index += 1;
            }

            None
        }

        fn brace_span(start: Position, end: (char, Position)) -> Span {
            let mut end_position = end.1;
            end_position.advance(end.0);
            Span::from_positions(start, end_position)
        }

        let mut index = 0usize;
        let mut quote_state = quote_state(initial_quote_context);
        let mut stack = Vec::<Candidate>::new();

        while index < chars.len() {
            let ch = chars[index].0;

            if matches!(
                quote_state,
                None | Some(QuoteState::AnsiSingle) | Some(QuoteState::Double)
            ) && ch == '\\'
            {
                if let Some(candidate_index) = last_active_candidate_index(&stack, quote_state) {
                    if index + 1 < chars.len() {
                        stack[candidate_index].has_brace_ccl_content = true;
                    }
                    stack[candidate_index].prev_char = None;
                }
                index += 1;
                if index < chars.len() {
                    index += 1;
                }
                continue;
            }

            let current_quote_context = quote_context(quote_state);
            if ch == '{'
                && let Some(end_index) =
                    template_placeholder_end(chars, index, current_quote_context)
            {
                out.push(BraceSyntax {
                    kind: BraceSyntaxKind::TemplatePlaceholder,
                    span: brace_span(chars[index].1, chars[end_index - 1]),
                    quote_context: current_quote_context,
                });
                if let Some(candidate_index) = last_active_candidate_index(&stack, quote_state) {
                    stack[candidate_index].prev_char = None;
                }
                index = end_index;
                continue;
            }

            match quote_state {
                None => match ch {
                    '\'' => {
                        quote_state = if index > 0 && chars[index - 1].0 == '$' {
                            Some(QuoteState::AnsiSingle)
                        } else {
                            Some(QuoteState::Single)
                        };
                        for candidate in &mut stack {
                            if matches!(candidate.quote_context, BraceQuoteContext::Unquoted) {
                                candidate.saw_quote_boundary = true;
                            }
                        }
                    }
                    '"' => {
                        quote_state = Some(QuoteState::Double);
                        for candidate in &mut stack {
                            if matches!(candidate.quote_context, BraceQuoteContext::Unquoted) {
                                candidate.saw_quote_boundary = true;
                            }
                        }
                    }
                    _ => {}
                },
                Some(QuoteState::Single) => {
                    if ch == '\'' {
                        quote_state = None;
                        for candidate in &mut stack {
                            if matches!(candidate.quote_context, BraceQuoteContext::Unquoted) {
                                candidate.saw_quote_boundary = true;
                            }
                        }
                    }
                }
                Some(QuoteState::AnsiSingle) => {
                    if ch == '\'' {
                        quote_state = None;
                        for candidate in &mut stack {
                            if matches!(candidate.quote_context, BraceQuoteContext::Unquoted) {
                                candidate.saw_quote_boundary = true;
                            }
                        }
                    }
                }
                Some(QuoteState::Double) => {
                    if ch == '"' {
                        quote_state = None;
                        for candidate in &mut stack {
                            if matches!(candidate.quote_context, BraceQuoteContext::Unquoted) {
                                candidate.saw_quote_boundary = true;
                            }
                        }
                    }
                }
            }

            match ch {
                '{' => {
                    mark_brace_ccl_content(&mut stack, quote_state);
                    stack.push(Candidate {
                        start: chars[index].1,
                        quote_context: current_quote_context,
                        has_comma: false,
                        has_dot_dot: false,
                        saw_unquoted_whitespace: false,
                        has_brace_ccl_content: false,
                        saw_quote_boundary: false,
                        prev_char: Some(ch),
                    });
                    index += 1;
                    continue;
                }
                '}' => {
                    if let Some(candidate_index) = last_active_candidate_index(&stack, quote_state)
                    {
                        let candidate = stack.remove(candidate_index);
                        let span = brace_span(candidate.start, chars[index]);
                        let kind = Self::classify_brace_construct_kind(
                            candidate.quote_context,
                            brace_ccl_enabled,
                            candidate.has_comma,
                            candidate.has_dot_dot,
                            candidate.saw_unquoted_whitespace,
                            candidate.has_brace_ccl_content,
                        );
                        if !matches!(kind, BraceSyntaxKind::Literal)
                            || !candidate.saw_quote_boundary
                        {
                            out.push(BraceSyntax {
                                kind,
                                span,
                                quote_context: candidate.quote_context,
                            });
                        }
                    }
                }
                _ => {
                    let is_quote_syntax = match quote_state {
                        None => {
                            matches!(ch, '\'' | '"')
                                || (ch == '$'
                                    && chars.get(index + 1).is_some_and(|(next, _)| *next == '\''))
                        }
                        Some(QuoteState::Single | QuoteState::AnsiSingle) => ch == '\'',
                        Some(QuoteState::Double) => ch == '"',
                    };
                    if ch != '\0' && !is_quote_syntax {
                        mark_brace_ccl_content(&mut stack, quote_state);
                    }
                    if let Some(candidate_index) = last_active_candidate_index(&stack, quote_state)
                    {
                        let candidate = &mut stack[candidate_index];
                        let counts_as_top_level =
                            quote_context_is_active(candidate.quote_context, quote_state);

                        if counts_as_top_level {
                            match ch {
                                ',' => candidate.has_comma = true,
                                '.' if candidate.prev_char == Some('.') => {
                                    candidate.has_dot_dot = true;
                                }
                                c if matches!(
                                    candidate.quote_context,
                                    BraceQuoteContext::Unquoted
                                ) && quote_state.is_none()
                                    && c.is_whitespace() =>
                                {
                                    candidate.saw_unquoted_whitespace = true;
                                }
                                _ => {}
                            }
                        }
                    }
                }
            }

            if let Some(candidate_index) = last_active_candidate_index(&stack, quote_state) {
                stack[candidate_index].prev_char = Some(ch);
            }

            index += 1;
        }
    }

    pub(super) fn collect_brace_syntax_from_pattern(
        &self,
        pattern: &Pattern,
        quote_context: BraceQuoteContext,
        brace_ccl_enabled: bool,
        out: &mut Vec<BraceSyntax>,
    ) {
        for (part, span) in pattern.parts_with_spans() {
            match part {
                PatternPart::Literal(text) => Self::scan_brace_syntax_text(
                    text.as_str(self.input, span),
                    span.start,
                    quote_context,
                    brace_ccl_enabled,
                    out,
                ),
                PatternPart::CharClass(text) => Self::scan_brace_syntax_text(
                    text.slice(self.input),
                    text.span().start,
                    quote_context,
                    brace_ccl_enabled,
                    out,
                ),
                PatternPart::Group { patterns, .. } => {
                    for pattern in patterns {
                        self.collect_brace_syntax_from_pattern(
                            pattern,
                            quote_context,
                            brace_ccl_enabled,
                            out,
                        );
                    }
                }
                PatternPart::Word(word) => self.collect_brace_syntax_from_parts(
                    &word.parts,
                    quote_context,
                    brace_ccl_enabled,
                    out,
                ),
                PatternPart::AnyString | PatternPart::AnyChar => {}
            }
        }
    }

    pub(super) fn collect_brace_syntax_from_zsh_qualified_glob(
        &self,
        glob: &ZshQualifiedGlob,
        quote_context: BraceQuoteContext,
        brace_ccl_enabled: bool,
        out: &mut Vec<BraceSyntax>,
    ) {
        for segment in &glob.segments {
            if let ZshGlobSegment::Pattern(pattern) = segment {
                self.collect_brace_syntax_from_pattern(
                    pattern,
                    quote_context,
                    brace_ccl_enabled,
                    out,
                );
            }
        }
    }

    pub(super) fn scan_brace_syntax_text(
        text: &str,
        base: Position,
        quote_context: BraceQuoteContext,
        brace_ccl_enabled: bool,
        out: &mut Vec<BraceSyntax>,
    ) {
        if memchr(b'{', text.as_bytes()).is_none() {
            return;
        }

        #[derive(Clone, Copy)]
        struct ScanFrame<'a> {
            text: &'a str,
            index: usize,
            position: Position,
        }

        let mut work = SmallVec::<[ScanFrame<'_>; 2]>::new();
        work.push(ScanFrame {
            text,
            index: 0,
            position: base,
        });

        while let Some(mut frame) = work.pop() {
            let bytes = frame.text.as_bytes();

            while frame.index < bytes.len() {
                let next_special = if matches!(quote_context, BraceQuoteContext::Unquoted) {
                    memchr2(b'{', b'\\', &bytes[frame.index..])
                        .map(|relative| frame.index + relative)
                } else {
                    memchr(b'{', &bytes[frame.index..]).map(|relative| frame.index + relative)
                };

                let Some(next_index) = next_special else {
                    break;
                };

                if next_index > frame.index {
                    frame.position = frame
                        .position
                        .advanced_by(&frame.text[frame.index..next_index]);
                    frame.index = next_index;
                }

                if matches!(quote_context, BraceQuoteContext::Unquoted)
                    && bytes[frame.index] == b'\\'
                {
                    let escaped_start = frame.index;
                    frame.index += 1;
                    if let Some(next) = frame.text[frame.index..].chars().next() {
                        frame.index += next.len_utf8();
                    }
                    frame.position = frame
                        .position
                        .advanced_by(&frame.text[escaped_start..frame.index]);
                    continue;
                }

                let brace_start = frame.position;
                if let Some(len) =
                    Self::template_placeholder_len(frame.text, frame.index, quote_context)
                {
                    let brace_end =
                        brace_start.advanced_by(&frame.text[frame.index..frame.index + len]);
                    out.push(BraceSyntax {
                        kind: BraceSyntaxKind::TemplatePlaceholder,
                        span: Span::from_positions(brace_start, brace_end),
                        quote_context,
                    });
                    frame.position = brace_end;
                    frame.index += len;
                    continue;
                }

                if let Some((len, kind)) = Self::brace_construct_len(
                    frame.text,
                    frame.index,
                    quote_context,
                    brace_ccl_enabled,
                ) {
                    let brace_end =
                        brace_start.advanced_by(&frame.text[frame.index..frame.index + len]);
                    out.push(BraceSyntax {
                        kind,
                        span: Span::from_positions(brace_start, brace_end),
                        quote_context,
                    });

                    frame.position = brace_end;
                    frame.index += len;

                    if len > 2 {
                        let inner_start = frame.index - len + '{'.len_utf8();
                        let inner_end = frame.index - '}'.len_utf8();
                        if inner_start < inner_end {
                            let inner_base = brace_start.advanced_by("{");
                            work.push(frame);
                            work.push(ScanFrame {
                                text: &frame.text[inner_start..inner_end],
                                index: 0,
                                position: inner_base,
                            });
                            break;
                        }
                    }

                    continue;
                }

                frame.position.advance('{');
                frame.index += '{'.len_utf8();
            }
        }
    }

    pub(super) fn text_position(base: Position, text: &str, offset: usize) -> Position {
        base.advanced_by(&text[..offset])
    }

    pub(super) fn template_placeholder_len(
        text: &str,
        start: usize,
        quote_context: BraceQuoteContext,
    ) -> Option<usize> {
        text.get(start..).filter(|rest| rest.starts_with("{{"))?;

        let mut index = start + "{{".len();
        let mut depth = 1usize;

        while index < text.len() {
            if matches!(quote_context, BraceQuoteContext::Unquoted)
                && text[index..].starts_with('\\')
            {
                index += 1;
                if let Some(next) = text[index..].chars().next() {
                    index += next.len_utf8();
                }
                continue;
            }

            if text[index..].starts_with("{{") {
                depth += 1;
                index += "{{".len();
                continue;
            }

            if text[index..].starts_with("}}") {
                depth -= 1;
                index += "}}".len();
                if depth == 0 {
                    return Some(index - start);
                }
                continue;
            }

            index += text[index..].chars().next()?.len_utf8();
        }

        None
    }

    pub(super) fn brace_construct_len(
        text: &str,
        start: usize,
        quote_context: BraceQuoteContext,
        brace_ccl_enabled: bool,
    ) -> Option<(usize, BraceSyntaxKind)> {
        text.get(start..).filter(|rest| rest.starts_with('{'))?;

        #[derive(Clone, Copy, PartialEq, Eq)]
        enum QuoteState {
            Single,
            Double,
        }

        let mut index = start + '{'.len_utf8();
        let mut depth = 1usize;
        let mut has_comma = false;
        let mut has_dot_dot = false;
        let mut saw_unquoted_whitespace = false;
        let mut has_brace_ccl_content = false;
        let mut prev_char = None;
        let mut quote_state = None;

        while index < text.len() {
            if matches!(quote_context, BraceQuoteContext::Unquoted)
                && quote_state.is_none()
                && text[index..].starts_with('\\')
            {
                index += 1;
                if let Some(next) = text[index..].chars().next() {
                    if depth == 1 {
                        has_brace_ccl_content = true;
                    }
                    index += next.len_utf8();
                }
                prev_char = None;
                continue;
            }

            let ch = text[index..].chars().next()?;
            index += ch.len_utf8();

            if matches!(quote_context, BraceQuoteContext::Unquoted) {
                match quote_state {
                    None => match ch {
                        '\'' => {
                            quote_state = Some(QuoteState::Single);
                            prev_char = None;
                            continue;
                        }
                        '"' => {
                            quote_state = Some(QuoteState::Double);
                            prev_char = None;
                            continue;
                        }
                        '$' if text[index..].starts_with('\'') => {}
                        '{' => {
                            has_brace_ccl_content = true;
                            depth += 1;
                        }
                        '}' => {
                            depth -= 1;
                            if depth == 0 {
                                let kind = Self::classify_brace_construct_kind(
                                    quote_context,
                                    brace_ccl_enabled,
                                    has_comma,
                                    has_dot_dot,
                                    saw_unquoted_whitespace,
                                    has_brace_ccl_content,
                                );
                                return Some((index - start, kind));
                            }
                        }
                        ',' if depth == 1 => has_comma = true,
                        '.' if depth == 1 && prev_char == Some('.') => has_dot_dot = true,
                        c if c.is_whitespace() => saw_unquoted_whitespace = true,
                        _ => has_brace_ccl_content = true,
                    },
                    Some(QuoteState::Single) => {
                        if ch == '\'' {
                            quote_state = None;
                        } else {
                            has_brace_ccl_content = true;
                        }
                    }
                    Some(QuoteState::Double) => match ch {
                        '\\' => {
                            if let Some(next) = text[index..].chars().next() {
                                has_brace_ccl_content = true;
                                index += next.len_utf8();
                            }
                            prev_char = None;
                            continue;
                        }
                        '"' => quote_state = None,
                        _ => has_brace_ccl_content = true,
                    },
                }
            } else {
                match ch {
                    '{' => {
                        has_brace_ccl_content = true;
                        depth += 1;
                    }
                    '}' => {
                        depth -= 1;
                        if depth == 0 {
                            let kind = Self::classify_brace_construct_kind(
                                quote_context,
                                brace_ccl_enabled,
                                has_comma,
                                has_dot_dot,
                                false,
                                has_brace_ccl_content,
                            );
                            return Some((index - start, kind));
                        }
                    }
                    ',' if depth == 1 => has_comma = true,
                    '.' if depth == 1 && prev_char == Some('.') => has_dot_dot = true,
                    _ => has_brace_ccl_content = true,
                }
            }

            prev_char = Some(ch);
        }

        None
    }

    pub(super) fn classify_brace_construct_kind(
        quote_context: BraceQuoteContext,
        brace_ccl_enabled: bool,
        has_comma: bool,
        has_dot_dot: bool,
        saw_unquoted_whitespace: bool,
        has_brace_ccl_content: bool,
    ) -> BraceSyntaxKind {
        if matches!(quote_context, BraceQuoteContext::Unquoted) && saw_unquoted_whitespace {
            BraceSyntaxKind::Literal
        } else if has_comma {
            BraceSyntaxKind::Expansion(BraceExpansionKind::CommaList)
        } else if has_dot_dot {
            BraceSyntaxKind::Expansion(BraceExpansionKind::Sequence)
        } else if brace_ccl_enabled && has_brace_ccl_content {
            BraceSyntaxKind::Expansion(BraceExpansionKind::CharacterClass)
        } else {
            BraceSyntaxKind::Literal
        }
    }

    pub(super) fn maybe_parse_zsh_qualified_glob_word(
        &mut self,
        text: &str,
        span: Span,
        source_backed: bool,
    ) -> Option<Word> {
        let features = self.zsh_glob_parse_features_at(span.start.offset());
        if !self.zsh_glob_word_parsing_enabled_at(span.start.offset())
            || text.is_empty()
            || text.contains('=')
            || text.contains(['\x00', '\\', '\'', '"', '$', '`'])
            || text.chars().any(char::is_whitespace)
        {
            return None;
        }

        let (segments, qualifiers, saw_glob_syntax) =
            self.parse_zsh_qualified_glob_segments(text, span, source_backed, features)?;
        if !saw_glob_syntax {
            return None;
        }

        Some(self.word_with_parts(
            vec![WordPartNode::new(
                WordPart::ZshQualifiedGlob(Box::new(ZshQualifiedGlob {
                    span,
                    segments,
                    qualifiers,
                })),
                span,
            )],
            span,
        ))
    }

    pub(super) fn parse_zsh_qualified_glob_segments(
        &mut self,
        text: &str,
        span: Span,
        source_backed: bool,
        features: ZshGlobParseFeatures,
    ) -> Option<(Vec<ZshGlobSegment>, Option<ZshGlobQualifierGroup>, bool)> {
        let mut segments = Vec::new();
        let mut qualifiers = None;
        let mut saw_glob_syntax = false;
        let mut pattern_start = 0usize;
        let mut index = 0usize;

        while index < text.len() {
            if features.extended_glob && text[index..].starts_with("(#") {
                if let Some((len, control)) =
                    self.parse_zsh_inline_glob_control(text, span.start, index)
                {
                    self.push_zsh_pattern_segment(
                        &mut segments,
                        text,
                        span.start,
                        (pattern_start, index),
                        source_backed,
                        features,
                    );
                    segments.push(ZshGlobSegment::InlineControl(control));
                    saw_glob_syntax = true;
                    index += len;
                    pattern_start = index;
                    continue;
                }

                let suffix_start = Self::text_position(span.start, text, index);
                if let Some(group) = self.parse_zsh_terminal_glob_qualifier_group(
                    &text[index..],
                    suffix_start,
                    source_backed,
                ) {
                    self.push_zsh_pattern_segment(
                        &mut segments,
                        text,
                        span.start,
                        (pattern_start, index),
                        source_backed,
                        features,
                    );
                    qualifiers = Some(group);
                    saw_glob_syntax = true;
                    index = text.len();
                    pattern_start = index;
                    break;
                }
            }

            if features.classic_qualifiers && text[index..].starts_with('(') {
                let suffix_start = Self::text_position(span.start, text, index);
                if let Some(group) = self.parse_zsh_terminal_glob_qualifier_group(
                    &text[index..],
                    suffix_start,
                    source_backed,
                ) && matches!(group.kind, ZshGlobQualifierKind::Classic)
                {
                    self.push_zsh_pattern_segment(
                        &mut segments,
                        text,
                        span.start,
                        (pattern_start, index),
                        source_backed,
                        features,
                    );
                    qualifiers = Some(group);
                    saw_glob_syntax = true;
                    index = text.len();
                    pattern_start = index;
                    break;
                }
            }

            index += text[index..].chars().next()?.len_utf8();
        }

        saw_glob_syntax |= self.push_zsh_pattern_segment(
            &mut segments,
            text,
            span.start,
            (pattern_start, text.len()),
            source_backed,
            features,
        );

        segments
            .iter()
            .any(|segment| matches!(segment, ZshGlobSegment::Pattern(_)))
            .then_some((segments, qualifiers, saw_glob_syntax))
    }

    pub(super) fn push_zsh_pattern_segment(
        &mut self,
        segments: &mut Vec<ZshGlobSegment>,
        text: &str,
        base: Position,
        bounds: (usize, usize),
        source_backed: bool,
        features: ZshGlobParseFeatures,
    ) -> bool {
        let (start, end) = bounds;
        if start >= end {
            return false;
        }

        let span = Span::from_positions(
            Self::text_position(base, text, start),
            Self::text_position(base, text, end),
        );
        let pattern_word =
            self.decode_word_text(&text[start..end], span, span.start, source_backed);
        let pattern = self.pattern_from_word(&pattern_word);
        let saw_glob_syntax = self.pattern_has_glob_syntax_with_features(&pattern, features);
        segments.push(ZshGlobSegment::Pattern(pattern));
        saw_glob_syntax
    }

    pub(super) fn parse_zsh_inline_glob_control(
        &self,
        text: &str,
        base: Position,
        start: usize,
    ) -> Option<(usize, ZshInlineGlobControl)> {
        let (len, control) = if text[start..].starts_with("(#i)") {
            (
                "(#i)".len(),
                ZshInlineGlobControl::CaseInsensitive {
                    span: Span::from_positions(
                        Self::text_position(base, text, start),
                        Self::text_position(base, text, start + "(#i)".len()),
                    ),
                },
            )
        } else if text[start..].starts_with("(#b)") {
            (
                "(#b)".len(),
                ZshInlineGlobControl::Backreferences {
                    span: Span::from_positions(
                        Self::text_position(base, text, start),
                        Self::text_position(base, text, start + "(#b)".len()),
                    ),
                },
            )
        } else if text[start..].starts_with("(#s)") {
            (
                "(#s)".len(),
                ZshInlineGlobControl::StartAnchor {
                    span: Span::from_positions(
                        Self::text_position(base, text, start),
                        Self::text_position(base, text, start + "(#s)".len()),
                    ),
                },
            )
        } else if text[start..].starts_with("(#e)") {
            (
                "(#e)".len(),
                ZshInlineGlobControl::EndAnchor {
                    span: Span::from_positions(
                        Self::text_position(base, text, start),
                        Self::text_position(base, text, start + "(#e)".len()),
                    ),
                },
            )
        } else {
            return None;
        };

        Some((len, control))
    }

    pub(super) fn parse_zsh_terminal_glob_qualifier_group(
        &self,
        text: &str,
        base: Position,
        source_backed: bool,
    ) -> Option<ZshGlobQualifierGroup> {
        let (kind, prefix_len, inner) = if let Some(inner) = text
            .strip_prefix("(#q")
            .and_then(|rest| rest.strip_suffix(')'))
        {
            (ZshGlobQualifierKind::HashQ, "(#q".len(), inner)
        } else {
            let inner = text.strip_prefix('(')?.strip_suffix(')')?;
            (ZshGlobQualifierKind::Classic, "(".len(), inner)
        };

        let fragments = self.parse_zsh_glob_qualifier_fragments(
            inner,
            Self::text_position(base, text, prefix_len),
            source_backed,
        )?;

        Some(ZshGlobQualifierGroup {
            span: Span::from_positions(base, Self::text_position(base, text, text.len())),
            kind,
            fragments,
        })
    }

    pub(super) fn parse_zsh_glob_qualifier_fragments(
        &self,
        text: &str,
        base: Position,
        source_backed: bool,
    ) -> Option<Vec<ZshGlobQualifier>> {
        let mut fragments = Vec::new();
        let mut index = 0;
        let mut saw_non_letter_fragment = false;

        while index < text.len() {
            let start = index;
            let ch = text[index..].chars().next()?;

            match ch {
                '^' => {
                    index += ch.len_utf8();
                    fragments.push(ZshGlobQualifier::Negation {
                        span: Span::from_positions(
                            Self::text_position(base, text, start),
                            Self::text_position(base, text, index),
                        ),
                    });
                    saw_non_letter_fragment = true;
                }
                '.' | '/' | '-' | 'A'..='Z' => {
                    index += ch.len_utf8();
                    fragments.push(ZshGlobQualifier::Flag {
                        name: ch,
                        span: Span::from_positions(
                            Self::text_position(base, text, start),
                            Self::text_position(base, text, index),
                        ),
                    });
                    saw_non_letter_fragment = true;
                }
                '[' => {
                    index += ch.len_utf8();
                    let number_start = index;
                    while matches!(text[index..].chars().next(), Some('0'..='9')) {
                        index += 1;
                    }
                    if number_start == index {
                        return None;
                    }

                    let start_text = self.zsh_glob_qualifier_source_text(
                        text,
                        base,
                        number_start,
                        index,
                        source_backed,
                    );
                    let end_text = if text[index..].starts_with(',') {
                        index += 1;
                        let range_start = index;
                        while matches!(text[index..].chars().next(), Some('0'..='9')) {
                            index += 1;
                        }
                        if range_start == index {
                            return None;
                        }
                        Some(self.zsh_glob_qualifier_source_text(
                            text,
                            base,
                            range_start,
                            index,
                            source_backed,
                        ))
                    } else {
                        None
                    };

                    if !text[index..].starts_with(']') {
                        return None;
                    }
                    index += "]".len();
                    fragments.push(ZshGlobQualifier::NumericArgument {
                        span: Span::from_positions(
                            Self::text_position(base, text, start),
                            Self::text_position(base, text, index),
                        ),
                        start: start_text,
                        end: end_text,
                    });
                    saw_non_letter_fragment = true;
                }
                'a'..='z' => {
                    index += ch.len_utf8();
                    while matches!(text[index..].chars().next(), Some('a'..='z' | 'A'..='Z')) {
                        index += 1;
                    }
                    if index - start <= 1 {
                        return None;
                    }
                    fragments.push(ZshGlobQualifier::LetterSequence {
                        text: self.zsh_glob_qualifier_source_text(
                            text,
                            base,
                            start,
                            index,
                            source_backed,
                        ),
                        span: Span::from_positions(
                            Self::text_position(base, text, start),
                            Self::text_position(base, text, index),
                        ),
                    });
                }
                _ => return None,
            }
        }

        (!fragments.is_empty() && saw_non_letter_fragment).then_some(fragments)
    }

    pub(super) fn zsh_glob_qualifier_source_text(
        &self,
        text: &str,
        base: Position,
        start: usize,
        end: usize,
        source_backed: bool,
    ) -> SourceText {
        let span = Span::from_positions(
            Self::text_position(base, text, start),
            Self::text_position(base, text, end),
        );
        if source_backed {
            SourceText::source(span)
        } else {
            SourceText::cooked(span, text[start..end].to_string())
        }
    }

    pub(super) fn pattern_has_glob_syntax_with_features(
        &self,
        pattern: &Pattern,
        features: ZshGlobParseFeatures,
    ) -> bool {
        pattern.parts.iter().any(|part| match &part.kind {
            PatternPart::AnyString | PatternPart::AnyChar | PatternPart::CharClass(_) => true,
            PatternPart::Group { .. } => true,
            PatternPart::Word(word) => Self::pattern_has_glob_word(word),
            PatternPart::Literal(text) => {
                Self::literal_text_has_zsh_glob_syntax(text.as_str(self.input, part.span), features)
            }
        })
    }

    pub(super) fn pattern_has_glob_word(word: &Word) -> bool {
        word.parts
            .iter()
            .any(|part| !matches!(part.kind, WordPart::Literal(_)))
    }

    pub(super) fn literal_text_has_zsh_glob_syntax(
        text: &str,
        features: ZshGlobParseFeatures,
    ) -> bool {
        if text.is_empty() {
            return false;
        }

        if features.extended_glob && text.contains("(#") {
            return true;
        }

        let bytes = text.as_bytes();
        let mut escaped = false;
        let mut bracket_depth = 0usize;
        let mut previous_char = None;
        let mut index = 0usize;

        while index < bytes.len() {
            let Some(ch) = text[index..].chars().next() else {
                break;
            };

            if escaped {
                escaped = false;
                previous_char = Some(ch);
                index += ch.len_utf8();
                continue;
            }

            if ch == '\\' {
                escaped = true;
                previous_char = Some(ch);
                index += ch.len_utf8();
                continue;
            }

            if bracket_depth == 0 {
                if features.extended_glob
                    && (ch == '~'
                        || ch == '#'
                        || (ch == '^'
                            && previous_char.is_none_or(|prev| prev == '(' || prev == '|')))
                {
                    return true;
                }

                if features.bare_groups
                    && ch == '<'
                    && Self::literal_text_has_numeric_range_suffix(&text[index..])
                {
                    return true;
                }
            }

            match ch {
                '[' => bracket_depth += 1,
                ']' if bracket_depth > 0 => bracket_depth -= 1,
                _ => {}
            }

            previous_char = Some(ch);
            index += ch.len_utf8();
        }

        false
    }

    pub(super) fn literal_text_has_numeric_range_suffix(text: &str) -> bool {
        let Some(rest) = text.strip_prefix('<') else {
            return false;
        };

        let mut saw_body = false;
        let mut saw_hyphen = false;
        for ch in rest.chars() {
            if ch == '>' {
                return saw_body && saw_hyphen;
            }
            if !matches!(ch, '0'..='9' | '-') {
                return false;
            }
            saw_hyphen |= ch == '-';
            saw_body = true;
        }

        false
    }
}
