use super::super::*;

impl<'a> Parser<'a> {
    pub(in crate::parser) fn word_syntax_is_source_backed(&self, word: &Word) -> bool {
        word.span.end.offset() <= self.input.len()
            && word
                .parts
                .first()
                .is_none_or(|part| part.span.start == word.span.start)
            && word
                .parts
                .last()
                .is_none_or(|part| part.span.end == word.span.end)
            && word
                .parts
                .iter()
                .all(|part| self.word_part_syntax_is_source_backed(&part.kind, part.span))
    }

    pub(in crate::parser) fn word_part_syntax_is_source_backed(
        &self,
        part: &WordPart,
        span: Span,
    ) -> bool {
        span.end.offset() <= self.input.len()
            && match part {
                WordPart::Literal(text) => text.is_source_backed(),
                WordPart::ZshQualifiedGlob(glob) => {
                    glob.segments
                        .iter()
                        .all(Self::zsh_glob_segment_is_source_backed)
                        && glob.qualifiers.as_ref().is_none_or(|group| {
                            self.zsh_glob_qualifier_group_is_source_backed(group)
                        })
                }
                WordPart::SingleQuoted { value, .. } => value.is_source_backed(),
                WordPart::DoubleQuoted { parts, .. } => parts
                    .iter()
                    .all(|part| self.word_part_syntax_is_source_backed(&part.kind, part.span)),
                WordPart::Variable(_)
                | WordPart::CommandSubstitution { .. }
                | WordPart::ProcessSubstitution { .. }
                | WordPart::PrefixMatch { .. } => true,
                WordPart::ArithmeticExpansion { expression, .. } => expression.is_source_backed(),
                WordPart::Parameter(parameter) => parameter.raw_body.is_source_backed(),
                WordPart::ParameterExpansion {
                    reference,
                    operator,
                    operand,
                    ..
                } => {
                    reference.is_source_backed()
                        && self.parameter_operator_is_source_backed(operator)
                        && operand.as_deref().is_none_or(SourceText::is_source_backed)
                }
                WordPart::Length(reference)
                | WordPart::ArrayAccess(reference)
                | WordPart::ArrayLength(reference)
                | WordPart::ArrayIndices(reference)
                | WordPart::Transformation { reference, .. } => reference.is_source_backed(),
                WordPart::Substring {
                    reference,
                    offset,
                    length,
                    ..
                }
                | WordPart::ArraySlice {
                    reference,
                    offset,
                    length,
                    ..
                } => {
                    reference.is_source_backed()
                        && offset.is_source_backed()
                        && length.as_deref().is_none_or(SourceText::is_source_backed)
                }
                WordPart::IndirectExpansion {
                    reference,
                    operator,
                    operand,
                    ..
                } => {
                    reference.is_source_backed()
                        && operator.is_none()
                        && operand.as_deref().is_none_or(SourceText::is_source_backed)
                }
            }
    }

    pub(in crate::parser) fn parameter_operator_is_source_backed(
        &self,
        operator: &ParameterOp,
    ) -> bool {
        match operator {
            ParameterOp::RemovePrefixShort { pattern }
            | ParameterOp::RemovePrefixLong { pattern }
            | ParameterOp::RemoveSuffixShort { pattern }
            | ParameterOp::RemoveSuffixLong { pattern } => pattern.is_source_backed(),
            ParameterOp::ReplaceFirst {
                pattern,
                replacement,
                ..
            }
            | ParameterOp::ReplaceAll {
                pattern,
                replacement,
                ..
            } => pattern.is_source_backed() && replacement.is_source_backed(),
            _ => true,
        }
    }

    pub(in crate::parser) fn zsh_glob_qualifier_group_is_source_backed(
        &self,
        group: &ZshGlobQualifierGroup,
    ) -> bool {
        group
            .fragments
            .iter()
            .all(Self::zsh_glob_qualifier_is_source_backed)
    }

    pub(in crate::parser) fn zsh_glob_segment_is_source_backed(segment: &ZshGlobSegment) -> bool {
        match segment {
            ZshGlobSegment::Pattern(pattern) => pattern.is_source_backed(),
            ZshGlobSegment::InlineControl(control) => {
                Self::zsh_inline_glob_control_is_source_backed(control)
            }
        }
    }

    pub(in crate::parser) fn zsh_inline_glob_control_is_source_backed(
        _control: &ZshInlineGlobControl,
    ) -> bool {
        true
    }

    pub(in crate::parser) fn zsh_glob_qualifier_is_source_backed(
        fragment: &ZshGlobQualifier,
    ) -> bool {
        match fragment {
            ZshGlobQualifier::Negation { .. } | ZshGlobQualifier::Flag { .. } => true,
            ZshGlobQualifier::LetterSequence { text, .. } => text.is_source_backed(),
            ZshGlobQualifier::NumericArgument { start, end, .. } => {
                start.is_source_backed() && end.as_ref().is_none_or(SourceText::is_source_backed)
            }
        }
    }

    pub(in crate::parser) fn word_part_syntax_text<'b>(
        &'b self,
        part: &'b WordPartNode,
    ) -> Cow<'b, str> {
        if self.word_part_syntax_is_source_backed(&part.kind, part.span) {
            Cow::Borrowed(part.span.slice(self.input))
        } else {
            let mut syntax = String::new();
            self.push_word_part_syntax(&mut syntax, &part.kind, part.span);
            Cow::Owned(syntax)
        }
    }

    pub(in crate::parser) fn compound_array_inner_text<'b>(
        &'b self,
        word: &'b Word,
    ) -> Option<(Cow<'b, str>, Position)> {
        let first = word.parts.first()?;
        let last = word.parts.last()?;
        let first_syntax = self.word_part_syntax_text(first);
        let last_syntax = self.word_part_syntax_text(last);

        if !first_syntax.starts_with('(') || !last_syntax.ends_with(')') {
            return None;
        }

        let inner_start = word.span.start.advanced_by("(");
        if self.word_syntax_is_source_backed(word) {
            let syntax = word.span.slice(self.input);
            return Some((
                Cow::Borrowed(&syntax[1..syntax.len().saturating_sub(1)]),
                inner_start,
            ));
        }

        let mut inner = String::new();
        for (index, part) in word.parts.iter().enumerate() {
            let syntax = self.word_part_syntax_text(part);
            let start = if index == 0 { 1 } else { 0 };
            let end = syntax.len() - usize::from(index + 1 == word.parts.len());
            if start < end {
                inner.push_str(&syntax[start..end]);
            }
        }

        Some((Cow::Owned(inner), inner_start))
    }

    pub(in crate::parser) fn push_word_part_syntax(
        &self,
        out: &mut String,
        part: &WordPart,
        span: Span,
    ) {
        if self.word_part_syntax_is_source_backed(part, span) {
            out.push_str(span.slice(self.input));
            return;
        }

        match part {
            WordPart::Literal(text) => out.push_str(text.as_str(self.input, span)),
            WordPart::ZshQualifiedGlob(glob) => {
                for segment in &glob.segments {
                    self.push_zsh_glob_segment_syntax(out, segment);
                }
                if let Some(qualifiers) = &glob.qualifiers {
                    self.push_zsh_glob_qualifier_group_syntax(out, qualifiers);
                }
            }
            WordPart::SingleQuoted { value, dollar } => {
                if *dollar {
                    out.push('$');
                }
                out.push('\'');
                out.push_str(value.slice(self.input));
                out.push('\'');
            }
            WordPart::DoubleQuoted { parts, dollar } => {
                if *dollar {
                    out.push('$');
                }
                out.push('"');
                for part in parts {
                    self.push_word_part_syntax(out, &part.kind, part.span);
                }
                out.push('"');
            }
            WordPart::Variable(name) => {
                out.push('$');
                out.push_str(name.as_str());
            }
            WordPart::CommandSubstitution { syntax, .. } => match syntax {
                CommandSubstitutionSyntax::DollarParen => out.push_str("$()"),
                CommandSubstitutionSyntax::Backtick => out.push_str("``"),
            },
            WordPart::ArithmeticExpansion {
                expression, syntax, ..
            } => match syntax {
                ArithmeticExpansionSyntax::DollarParenParen => {
                    out.push_str("$((");
                    out.push_str(expression.slice(self.input));
                    out.push_str("))");
                }
                ArithmeticExpansionSyntax::LegacyBracket => {
                    out.push_str("$[");
                    out.push_str(expression.slice(self.input));
                    out.push(']');
                }
            },
            WordPart::Parameter(parameter) => {
                out.push_str("${");
                out.push_str(parameter.raw_body.slice(self.input));
                out.push('}');
            }
            WordPart::ParameterExpansion {
                reference,
                operator,
                operand,
                colon_variant,
                ..
            } => {
                out.push_str("${");
                self.push_var_ref_syntax(out, reference);
                self.push_parameter_operator_syntax(
                    out,
                    operator,
                    operand.as_ref().map(|v| &**v),
                    *colon_variant,
                );
                out.push('}');
            }
            WordPart::Length(reference) => {
                out.push_str("${#");
                self.push_var_ref_syntax(out, reference);
                out.push('}');
            }
            WordPart::ArrayAccess(reference) => {
                out.push_str("${");
                self.push_var_ref_syntax(out, reference);
                out.push('}');
            }
            WordPart::ArrayLength(reference) => {
                out.push_str("${#");
                self.push_var_ref_syntax(out, reference);
                out.push('}');
            }
            WordPart::ArrayIndices(reference) => {
                out.push_str("${!");
                self.push_var_ref_syntax(out, reference);
                out.push('}');
            }
            WordPart::Substring {
                reference,
                offset,
                length,
                ..
            }
            | WordPart::ArraySlice {
                reference,
                offset,
                length,
                ..
            } => {
                out.push_str("${");
                self.push_var_ref_syntax(out, reference);
                out.push(':');
                out.push_str(offset.slice(self.input));
                if let Some(length) = length {
                    out.push(':');
                    out.push_str(length.slice(self.input));
                }
                out.push('}');
            }
            WordPart::IndirectExpansion {
                reference,
                operator,
                operand,
                colon_variant,
                ..
            } => {
                out.push_str("${!");
                self.push_var_ref_syntax(out, reference);
                if let Some(operator) = operator {
                    self.push_parameter_operator_syntax(
                        out,
                        operator,
                        operand.as_ref().map(|v| &**v),
                        *colon_variant,
                    );
                }
                out.push('}');
            }
            WordPart::PrefixMatch { prefix, kind } => {
                out.push_str("${!");
                out.push_str(prefix.as_str());
                out.push(kind.as_char());
                out.push('}');
            }
            WordPart::ProcessSubstitution { is_input, .. } => {
                out.push(if *is_input { '<' } else { '>' });
                out.push_str("()");
            }
            WordPart::Transformation {
                reference,
                operator,
            } => {
                out.push_str("${");
                self.push_var_ref_syntax(out, reference);
                out.push('@');
                out.push(*operator);
                out.push('}');
            }
        }
    }

    pub(in crate::parser) fn push_zsh_glob_qualifier_group_syntax(
        &self,
        out: &mut String,
        group: &ZshGlobQualifierGroup,
    ) {
        match group.kind {
            ZshGlobQualifierKind::Classic => out.push('('),
            ZshGlobQualifierKind::HashQ => out.push_str("(#q"),
        }
        for fragment in &group.fragments {
            match fragment {
                ZshGlobQualifier::Negation { .. } => out.push('^'),
                ZshGlobQualifier::Flag { name, .. } => out.push(*name),
                ZshGlobQualifier::LetterSequence { text, .. } => {
                    out.push_str(text.slice(self.input));
                }
                ZshGlobQualifier::NumericArgument { start, end, .. } => {
                    out.push('[');
                    out.push_str(start.slice(self.input));
                    if let Some(end) = end {
                        out.push(',');
                        out.push_str(end.slice(self.input));
                    }
                    out.push(']');
                }
            }
        }
        out.push(')');
    }

    pub(in crate::parser) fn push_zsh_glob_segment_syntax(
        &self,
        out: &mut String,
        segment: &ZshGlobSegment,
    ) {
        match segment {
            ZshGlobSegment::Pattern(pattern) => self.push_pattern_syntax(out, pattern),
            ZshGlobSegment::InlineControl(control) => match control {
                ZshInlineGlobControl::CaseInsensitive { .. } => out.push_str("(#i)"),
                ZshInlineGlobControl::Backreferences { .. } => out.push_str("(#b)"),
                ZshInlineGlobControl::StartAnchor { .. } => out.push_str("(#s)"),
                ZshInlineGlobControl::EndAnchor { .. } => out.push_str("(#e)"),
            },
        }
    }

    pub(in crate::parser) fn push_var_ref_syntax(&self, out: &mut String, reference: &VarRef) {
        out.push_str(reference.name.as_str());
        if let Some(subscript) = &reference.subscript {
            out.push('[');
            out.push_str(subscript.syntax_text(self.input));
            out.push(']');
        }
    }

    pub(in crate::parser) fn push_parameter_operator_syntax(
        &self,
        out: &mut String,
        operator: &ParameterOp,
        operand: Option<&SourceText>,
        colon_variant: bool,
    ) {
        let colon = if colon_variant { ":" } else { "" };
        match operator {
            ParameterOp::UseDefault => {
                out.push_str(colon);
                out.push('-');
                if let Some(operand) = operand {
                    out.push_str(operand.slice(self.input));
                }
            }
            ParameterOp::AssignDefault => {
                out.push_str(colon);
                out.push('=');
                if let Some(operand) = operand {
                    out.push_str(operand.slice(self.input));
                }
            }
            ParameterOp::UseReplacement => {
                out.push_str(colon);
                out.push('+');
                if let Some(operand) = operand {
                    out.push_str(operand.slice(self.input));
                }
            }
            ParameterOp::Error => {
                out.push_str(colon);
                out.push('?');
                if let Some(operand) = operand {
                    out.push_str(operand.slice(self.input));
                }
            }
            ParameterOp::RemovePrefixShort { pattern } => {
                out.push('#');
                self.push_pattern_syntax(out, pattern);
            }
            ParameterOp::RemovePrefixLong { pattern } => {
                out.push_str("##");
                self.push_pattern_syntax(out, pattern);
            }
            ParameterOp::RemoveSuffixShort { pattern } => {
                out.push('%');
                self.push_pattern_syntax(out, pattern);
            }
            ParameterOp::RemoveSuffixLong { pattern } => {
                out.push_str("%%");
                self.push_pattern_syntax(out, pattern);
            }
            ParameterOp::ReplaceFirst {
                pattern,
                replacement,
                ..
            } => {
                out.push('/');
                self.push_pattern_syntax(out, pattern);
                out.push('/');
                out.push_str(replacement.slice(self.input));
            }
            ParameterOp::ReplaceAll {
                pattern,
                replacement,
                ..
            } => {
                out.push_str("//");
                self.push_pattern_syntax(out, pattern);
                out.push('/');
                out.push_str(replacement.slice(self.input));
            }
            ParameterOp::UpperFirst => out.push('^'),
            ParameterOp::UpperAll => out.push_str("^^"),
            ParameterOp::LowerFirst => out.push(','),
            ParameterOp::LowerAll => out.push_str(",,"),
        }
    }

    pub(in crate::parser) fn push_pattern_syntax(&self, out: &mut String, pattern: &Pattern) {
        if pattern.is_source_backed() && pattern.span.end.offset() <= self.input.len() {
            out.push_str(pattern.span.slice(self.input));
            return;
        }

        for part in &pattern.parts {
            self.push_pattern_part_syntax(out, &part.kind, part.span);
        }
    }

    pub(in crate::parser) fn push_pattern_part_syntax(
        &self,
        out: &mut String,
        part: &PatternPart,
        span: Span,
    ) {
        match part {
            PatternPart::Literal(text) => out.push_str(text.as_str(self.input, span)),
            PatternPart::AnyString => out.push('*'),
            PatternPart::AnyChar => out.push('?'),
            PatternPart::CharClass(text) => out.push_str(text.slice(self.input)),
            PatternPart::Group { kind, patterns } => {
                out.push(kind.prefix());
                out.push('(');
                for (index, pattern) in patterns.iter().enumerate() {
                    if index > 0 {
                        out.push('|');
                    }
                    self.push_pattern_syntax(out, pattern);
                }
                out.push(')');
            }
            PatternPart::Word(word) => {
                for part in &word.parts {
                    self.push_word_part_syntax(out, &part.kind, part.span);
                }
            }
        }
    }
}
