//! Declaration extraction helpers for `ModuleInfoExtractor`.
//!
//! These are inherent methods that extract export information from
//! declaration AST nodes, binding patterns, and require/import patterns.

use oxc_ast::ast::{
    Argument, BindingPattern, CallExpression, Declaration, Expression, ImportExpression,
    TSEnumMemberName, TSModuleDeclarationName, VariableDeclarator,
};

use crate::{
    DynamicImportInfo, ExportInfo, ExportName, MemberInfo, MemberKind, RequireCallInfo,
    VisibilityTag,
};
use fallow_types::extract::ClassHeritageInfo;

use super::helpers::{
    extract_class_instance_bindings, extract_class_members, extract_implemented_interface_names,
    extract_super_class_name, has_angular_class_decorator,
};
use super::{MemberAccess, ModuleInfoExtractor, extract_destructured_names};

impl ModuleInfoExtractor {
    pub(crate) fn extract_declaration_exports(
        &mut self,
        decl: &Declaration<'_>,
        is_type_only: bool,
    ) {
        match decl {
            Declaration::VariableDeclaration(var) => {
                for declarator in &var.declarations {
                    self.extract_binding_pattern_names(&declarator.id, is_type_only);
                    if !is_type_only {
                        self.record_inline_server_action_const(declarator);
                    }
                }
            }
            Declaration::FunctionDeclaration(func) => {
                if let Some(id) = func.id.as_ref() {
                    self.upsert_function_declaration_export(
                        id.name.as_str(),
                        id.span,
                        is_type_only,
                    );
                    // An exported `async function f() { "use server" }` in a
                    // non-`"use server"` file is an inline Server Action; record
                    // its export name so the `unused-server-action` reclassifier
                    // can move a dead one out of `unused-export`. Only exported
                    // declarations reach this path, so capture is exported-only.
                    if !is_type_only
                        && super::visit_impl::function_body_has_use_server(func.body.as_deref())
                    {
                        self.inline_server_action_exports.push(id.name.to_string());
                    }
                }
            }
            Declaration::ClassDeclaration(class) => {
                self.extract_class_declaration_export(class, is_type_only);
            }
            Declaration::TSTypeAliasDeclaration(alias) => {
                self.push_type_export(&alias.id.name, alias.id.span);
            }
            Declaration::TSInterfaceDeclaration(iface) => {
                self.push_type_export(&iface.id.name, iface.id.span);
            }
            Declaration::TSEnumDeclaration(enumd) => {
                self.extract_enum_declaration_export(enumd, is_type_only);
            }
            Declaration::TSModuleDeclaration(module) => {
                self.extract_module_declaration_export(module, is_type_only);
            }
            _ => {}
        }
    }

    fn upsert_function_declaration_export(
        &mut self,
        name: &str,
        span: oxc_span::Span,
        is_type_only: bool,
    ) {
        let export_name = ExportName::Named(name.to_string());
        if let Some(existing) = self.exports.iter_mut().find(|e| e.name == export_name) {
            existing.span = span;
            existing.is_type_only = is_type_only;
        } else {
            self.exports.push(ExportInfo {
                name: export_name,
                local_name: Some(name.to_string()),
                is_type_only,
                visibility: VisibilityTag::None,
                expected_unused_reason: None,
                span,
                members: vec![],
                is_side_effect_used: false,
                super_class: None,
            });
        }
    }

    fn extract_class_declaration_export(
        &mut self,
        class: &oxc_ast::ast::Class<'_>,
        is_type_only: bool,
    ) {
        let Some(id) = class.id.as_ref() else {
            return;
        };
        let is_angular = has_angular_class_decorator(class);
        self.record_angular_inputs_outputs(class, is_angular);
        let members = extract_class_members(class, is_angular);
        let super_class = extract_super_class_name(class);
        let implemented_interfaces = extract_implemented_interface_names(class);
        let instance_bindings =
            extract_class_instance_bindings(class, |local_name, source, imported_name| {
                self.is_named_import_from(local_name, source, imported_name)
            });
        if super_class.is_some()
            || !implemented_interfaces.is_empty()
            || !instance_bindings.is_empty()
        {
            self.class_heritage.push(ClassHeritageInfo {
                export_name: id.name.to_string(),
                super_class: super_class.clone(),
                implements: implemented_interfaces,
                instance_bindings,
            });
        }
        self.exports.push(ExportInfo {
            name: ExportName::Named(id.name.to_string()),
            local_name: Some(id.name.to_string()),
            is_type_only,
            is_side_effect_used: false,
            visibility: VisibilityTag::None,
            expected_unused_reason: None,
            span: id.span,
            members,
            super_class,
        });
    }

    fn extract_enum_declaration_export(
        &mut self,
        enumd: &oxc_ast::ast::TSEnumDeclaration<'_>,
        is_type_only: bool,
    ) {
        let members: Vec<MemberInfo> = enumd
            .body
            .members
            .iter()
            .filter_map(|member| {
                let name = match &member.id {
                    TSEnumMemberName::Identifier(id) => id.name.to_string(),
                    TSEnumMemberName::String(s) | TSEnumMemberName::ComputedString(s) => {
                        s.value.to_string()
                    }
                    TSEnumMemberName::ComputedTemplateString(_) => return None,
                };
                Some(MemberInfo {
                    name,
                    kind: MemberKind::EnumMember,
                    span: member.span,
                    has_decorator: false,
                    decorator_names: Vec::new(),
                    is_instance_returning_static: false,
                    is_self_returning: false,
                })
            })
            .collect();
        self.exports.push(ExportInfo {
            name: ExportName::Named(enumd.id.name.to_string()),
            local_name: Some(enumd.id.name.to_string()),
            is_type_only,
            visibility: VisibilityTag::None,
            expected_unused_reason: None,
            span: enumd.id.span,
            members,
            is_side_effect_used: false,
            super_class: None,
        });
    }

    fn extract_module_declaration_export(
        &mut self,
        module: &oxc_ast::ast::TSModuleDeclaration<'_>,
        is_type_only: bool,
    ) {
        let ns_type_only = module.declare || is_type_only;
        let (name, span) = match &module.id {
            TSModuleDeclarationName::Identifier(id) => (id.name.to_string(), id.span),
            TSModuleDeclarationName::StringLiteral(lit) => (lit.value.to_string(), lit.span),
        };
        self.exports.push(ExportInfo {
            name: ExportName::Named(name.clone()),
            local_name: Some(name),
            is_type_only: ns_type_only,
            visibility: VisibilityTag::None,
            expected_unused_reason: None,
            span,
            members: vec![],
            is_side_effect_used: false,
            super_class: None,
        });
    }

    pub(crate) fn extract_binding_pattern_names(
        &mut self,
        pattern: &BindingPattern<'_>,
        is_type_only: bool,
    ) {
        for id in pattern.get_binding_identifiers() {
            self.exports.push(ExportInfo {
                name: ExportName::Named(id.name.to_string()),
                local_name: Some(id.name.to_string()),
                is_type_only,
                visibility: VisibilityTag::None,
                expected_unused_reason: None,
                span: id.span,
                members: vec![],
                is_side_effect_used: false,
                super_class: None,
            });
        }
    }

    /// Record an exported `const f = () => { "use server" }` /
    /// `const f = async () => {...}` / `const f = function() {...}` inline Server
    /// Action by its binding name, so the `unused-server-action` reclassifier can
    /// move a dead one out of `unused-export`. Only fires for a plain identifier
    /// binding whose initializer is an arrow / function expression with a body
    /// carrying an inline `"use server"` directive.
    fn record_inline_server_action_const(&mut self, declarator: &VariableDeclarator<'_>) {
        let Some(init) = declarator.init.as_ref() else {
            return;
        };
        let body_has_use_server = match init {
            Expression::ArrowFunctionExpression(arrow) => {
                super::visit_impl::function_body_has_use_server(Some(&arrow.body))
            }
            Expression::FunctionExpression(func) => {
                super::visit_impl::function_body_has_use_server(func.body.as_deref())
            }
            _ => false,
        };
        if !body_has_use_server {
            return;
        }
        if let BindingPattern::BindingIdentifier(id) = &declarator.id {
            self.inline_server_action_exports.push(id.name.to_string());
        }
    }

    /// Extract namespace member names from a declaration inside a namespace body.
    ///
    /// Called when `namespace_depth > 0` to collect inner exported declarations
    /// as `MemberInfo` entries instead of top-level module exports.
    pub(crate) fn extract_namespace_members(&mut self, decl: &Declaration<'_>) {
        match decl {
            Declaration::FunctionDeclaration(func) => {
                if let Some(id) = func.id.as_ref() {
                    self.push_namespace_member(id.name.to_string(), id.span);
                }
            }
            Declaration::VariableDeclaration(var) => {
                for declarator in &var.declarations {
                    for id in declarator.id.get_binding_identifiers() {
                        self.push_namespace_member(id.name.to_string(), id.span);
                    }
                }
            }
            Declaration::ClassDeclaration(class) => {
                if let Some(id) = class.id.as_ref() {
                    self.push_namespace_member(id.name.to_string(), id.span);
                }
            }
            Declaration::TSEnumDeclaration(enumd) => {
                self.push_namespace_member(enumd.id.name.to_string(), enumd.id.span);
            }
            Declaration::TSInterfaceDeclaration(iface) => {
                self.push_namespace_member(iface.id.name.to_string(), iface.id.span);
            }
            Declaration::TSTypeAliasDeclaration(alias) => {
                self.push_namespace_member(alias.id.name.to_string(), alias.id.span);
            }
            Declaration::TSModuleDeclaration(module) => match &module.id {
                TSModuleDeclarationName::Identifier(id) => {
                    self.push_namespace_member(id.name.to_string(), id.span);
                }
                TSModuleDeclarationName::StringLiteral(lit) => {
                    self.push_namespace_member(lit.value.to_string(), lit.span);
                }
            },
            _ => {}
        }
    }

    /// Push a single namespace-member entry with the shared `NamespaceMember`
    /// defaults (no decorator / static / self-return signals).
    fn push_namespace_member(&mut self, name: String, span: oxc_span::Span) {
        self.pending_namespace_members.push(MemberInfo {
            name,
            kind: MemberKind::NamespaceMember,
            span,
            has_decorator: false,
            decorator_names: Vec::new(),
            is_instance_returning_static: false,
            is_self_returning: false,
        });
    }

    /// Handle `const x = require('./y')` patterns, recording the require call
    /// and tracking namespace bindings for later member access narrowing.
    pub(super) fn handle_require_declaration(
        &mut self,
        declarator: &VariableDeclarator<'_>,
        call: &CallExpression<'_>,
        source: &str,
    ) {
        // Span of the specifier string literal (with quotes) for the diagnostic
        // squiggly; falls back to the whole call when the argument is not a
        // plain string literal.
        let source_span = match call.arguments.first() {
            Some(Argument::StringLiteral(lit)) => lit.span,
            _ => call.span,
        };
        match &declarator.id {
            BindingPattern::ObjectPattern(obj_pat) => {
                let names = extract_destructured_names(obj_pat);
                self.require_calls.push(RequireCallInfo {
                    source: source.to_string(),
                    span: call.span,
                    source_span,
                    destructured_names: names,
                    local_name: None,
                });
                self.handled_require_spans.insert(call.span);
            }
            BindingPattern::BindingIdentifier(id) => {
                let local = id.name.to_string();
                self.namespace_binding_names.push(local.clone());
                self.require_calls.push(RequireCallInfo {
                    source: source.to_string(),
                    span: call.span,
                    source_span,
                    destructured_names: Vec::new(),
                    local_name: Some(local),
                });
                self.handled_require_spans.insert(call.span);
            }
            _ => {}
        }
    }

    /// Handle namespace destructuring: `const { a, b } = ns` where `ns` is a namespace
    /// import, dynamic import namespace, or require namespace.
    /// Records member accesses so the graph can narrow which exports are used.
    pub(super) fn handle_namespace_destructuring(
        &mut self,
        declarator: &VariableDeclarator<'_>,
        ident_name: &str,
    ) {
        if let BindingPattern::ObjectPattern(obj_pat) = &declarator.id {
            if obj_pat.rest.is_some() {
                self.whole_object_uses.push(ident_name.to_string());
            } else {
                for prop in &obj_pat.properties {
                    if let Some(name) = prop.key.static_name() {
                        self.member_accesses.push(MemberAccess {
                            object: ident_name.to_string(),
                            member: name.to_string(),
                        });
                    }
                }
            }
        }
    }

    /// Handle `const x = await import('./y')` and `const x = import('./y')` patterns,
    /// recording the dynamic import and tracking namespace bindings.
    pub(super) fn handle_dynamic_import_declaration(
        &mut self,
        declarator: &VariableDeclarator<'_>,
        import_expr: &ImportExpression<'_>,
        source: &str,
    ) {
        match &declarator.id {
            BindingPattern::ObjectPattern(obj_pat) => {
                let names = extract_destructured_names(obj_pat);
                self.dynamic_imports.push(DynamicImportInfo {
                    source: source.to_string(),
                    span: import_expr.span,
                    destructured_names: names,
                    local_name: None,
                    is_speculative: false,
                });
                self.handled_import_spans.insert(import_expr.span);
            }
            BindingPattern::BindingIdentifier(id) => {
                let local = id.name.to_string();
                self.namespace_binding_names.push(local.clone());
                self.dynamic_imports.push(DynamicImportInfo {
                    source: source.to_string(),
                    span: import_expr.span,
                    destructured_names: Vec::new(),
                    local_name: Some(local),
                    is_speculative: false,
                });
                self.handled_import_spans.insert(import_expr.span);
            }
            _ => {}
        }
    }
}
