//! Custom visitors for [`Utf8ToUtf16Converter`].
//!
//! Mostly the visitors are generated by `oxc_ast_tools`, but some types need custom logic
//! to ensure that offsets are converted in ascending order.
//!
//! The generated visitor in `generated/utf8_to_utf16_converter.rs` delegates to the methods
//! defined below for these types.

use oxc_ast::ast::*;
use oxc_span::{GetSpan, GetSpanMut};

use crate::{VisitMut, walk_mut};

use super::Utf8ToUtf16Converter;

impl Utf8ToUtf16Converter<'_> {
    pub(crate) fn convert_formal_parameters(&mut self, params: &mut FormalParameters<'_>) {
        // Span of `FormalParameters` itself does not appear in ESTree AST,
        // and its span includes `TSThisParameter` in types like `Function`,
        // which is converted before `FormalParameters`. So skip converting span.
        walk_mut::walk_formal_parameters(self, params);
    }

    pub(crate) fn convert_object_property(&mut self, prop: &mut ObjectProperty<'_>) {
        self.convert_offset(&mut prop.span.start);

        // If shorthand, span of `key` and `value` are the same
        match (prop.shorthand, &mut prop.key, &mut prop.value) {
            (true, PropertyKey::StaticIdentifier(key), Expression::Identifier(value)) => {
                self.visit_identifier_name(key);
                value.span = key.span;
            }
            (_, key, value) => {
                self.visit_property_key(key);
                self.visit_expression(value);
            }
        }

        self.convert_offset(&mut prop.span.end);
    }

    pub(crate) fn convert_binding_property(&mut self, prop: &mut BindingProperty<'_>) {
        self.convert_offset(&mut prop.span.start);

        // If shorthand, span of `key` and `value` are the same
        match (prop.shorthand, &mut prop.key, &mut prop.value) {
            (
                true,
                PropertyKey::StaticIdentifier(key),
                BindingPattern::BindingIdentifier(value),
            ) => {
                self.visit_identifier_name(key);
                value.span = key.span;
            }
            (
                true,
                PropertyKey::StaticIdentifier(key),
                BindingPattern::AssignmentPattern(pattern),
            ) => {
                self.visit_assignment_pattern(pattern);
                key.span = pattern.left.span();
            }
            (_, key, value) => {
                self.visit_property_key(key);
                match value {
                    BindingPattern::BindingIdentifier(ident) => {
                        self.visit_binding_identifier(ident);
                    }
                    BindingPattern::ObjectPattern(pattern) => self.visit_object_pattern(pattern),
                    BindingPattern::ArrayPattern(pattern) => self.visit_array_pattern(pattern),
                    BindingPattern::AssignmentPattern(pattern) => {
                        self.visit_assignment_pattern(pattern);
                    }
                }
            }
        }

        self.convert_offset(&mut prop.span.end);
    }

    pub(crate) fn convert_export_named_declaration(
        &mut self,
        decl: &mut ExportNamedDeclaration<'_>,
    ) {
        // Special case logic for `@dec export class C {}`
        if let Some(Declaration::ClassDeclaration(class)) = &mut decl.declaration {
            self.convert_exported_class(class, &mut decl.span);
        } else {
            self.convert_offset(&mut decl.span.start);
            walk_mut::walk_export_named_declaration(self, decl);
            self.convert_offset(&mut decl.span.end);
        }
    }

    pub(crate) fn convert_export_default_declaration(
        &mut self,
        decl: &mut ExportDefaultDeclaration<'_>,
    ) {
        // Special case logic for `@dec export default class {}`
        if let ExportDefaultDeclarationKind::ClassDeclaration(class) = &mut decl.declaration {
            self.convert_exported_class(class, &mut decl.span);
        } else {
            self.convert_offset(&mut decl.span.start);
            walk_mut::walk_export_default_declaration(self, decl);
            self.convert_offset(&mut decl.span.end);
        }
    }

    /// Visit `ExportNamedDeclaration` or `ExportDefaultDeclaration` containing a `Class`.
    /// e.g. `export class C {}`, `export default class {}`
    ///
    /// These need special handing because decorators before the `export` keyword
    /// have `Span`s which are before the start of the export statement.
    /// e.g. `@dec export class C {}`, `@dec export default class {}`.
    /// So they need to be processed first.
    fn convert_exported_class(&mut self, class: &mut Class<'_>, export_decl_span: &mut Span) {
        // Process decorators.
        // Process decorators before the `export` keyword first.
        // These have spans which are before the export statement span start.
        // Then process export statement and `Class` start, then remaining decorators,
        // which have spans within the span of `Class`.
        let mut decl_start = export_decl_span.start;
        for decorator in &mut class.decorators {
            if decorator.span.start > decl_start {
                // Process span start of export statement and `Class`
                self.convert_offset(&mut export_decl_span.start);
                self.convert_offset(&mut class.span.start);
                // Prevent this branch being taken again
                decl_start = u32::MAX;
            }
            self.visit_decorator(decorator);
        }

        // If didn't already, process span start of export statement and `Class`
        if decl_start < u32::MAX {
            self.convert_offset(&mut export_decl_span.start);
            self.convert_offset(&mut class.span.start);
        }

        // Process rest of the class
        if let Some(id) = &mut class.id {
            self.visit_binding_identifier(id);
        }
        if let Some(type_parameters) = &mut class.type_parameters {
            self.visit_ts_type_parameter_declaration(type_parameters);
        }
        if let Some(super_class) = &mut class.super_class {
            self.visit_expression(super_class);
        }
        if let Some(super_type_arguments) = &mut class.super_type_arguments {
            self.visit_ts_type_parameter_instantiation(super_type_arguments);
        }
        self.visit_ts_class_implements_list(&mut class.implements);
        self.visit_class_body(&mut class.body);

        // Process span end of `Class` and export statement
        self.convert_offset(&mut class.span.end);
        self.convert_offset(&mut export_decl_span.end);
    }

    pub(crate) fn convert_export_specifier(&mut self, specifier: &mut ExportSpecifier<'_>) {
        self.convert_offset(&mut specifier.span.start);

        // `local` and `exported` have same span if e.g.:
        // * `export {x}`
        // * `export {x} from 'foo.js;`
        // * `export {"a-b"} from 'foo.js';`
        let same_spans = specifier.local.span() == specifier.exported.span();

        self.visit_module_export_name(&mut specifier.local);

        if same_spans {
            *specifier.exported.span_mut() = specifier.local.span();
        } else {
            self.visit_module_export_name(&mut specifier.exported);
        }

        self.convert_offset(&mut specifier.span.end);
    }

    pub(crate) fn convert_import_specifier(&mut self, specifier: &mut ImportSpecifier<'_>) {
        self.convert_offset(&mut specifier.span.start);

        // `imported` and `local` have same span if e.g. `import {x} from 'foo';`
        let same_spans = specifier.imported.span() == specifier.local.span;

        self.visit_module_export_name(&mut specifier.imported);

        if same_spans {
            specifier.local.span = specifier.imported.span();
        } else {
            self.visit_binding_identifier(&mut specifier.local);
        }

        self.convert_offset(&mut specifier.span.end);
    }

    pub(crate) fn convert_template_literal(&mut self, lit: &mut TemplateLiteral<'_>) {
        self.convert_offset(&mut lit.span.start);

        // Visit `quasis` and `expressions` in source order. The two `Vec`s are interleaved.
        for (quasi, expression) in lit.quasis.iter_mut().zip(&mut lit.expressions) {
            self.visit_template_element(quasi);
            self.visit_expression(expression);
        }
        self.visit_template_element(lit.quasis.last_mut().unwrap());

        self.convert_offset(&mut lit.span.end);
    }

    pub(crate) fn convert_ts_template_literal_type(&mut self, lit: &mut TSTemplateLiteralType<'_>) {
        self.convert_offset(&mut lit.span.start);

        // Visit `quasis` and `types` in source order. The two `Vec`s are interleaved.
        for (quasi, ts_type) in lit.quasis.iter_mut().zip(&mut lit.types) {
            self.visit_template_element(quasi);
            self.visit_ts_type(ts_type);
        }
        self.visit_template_element(lit.quasis.last_mut().unwrap());

        self.convert_offset(&mut lit.span.end);
    }

    /// Convert extra span for `global` keyword.
    pub(crate) fn convert_ts_global_declaration(&mut self, decl: &mut TSGlobalDeclaration<'_>) {
        self.convert_offset(&mut decl.span.start);
        self.convert_offset(&mut decl.global_span.start);
        self.convert_offset(&mut decl.global_span.end);
        self.visit_ts_module_block(&mut decl.body);
        self.convert_offset(&mut decl.span.end);
    }
}
