//! Name resolution for AST to HIR lowering.
//!
//! This module handles resolving identifiers in the AST to their definitions.
//! It tracks:
//!
//! - Variable bindings
//! - Type definitions
//! - Data constructors
//! - Type class methods
//!
//! The resolution is performed during the lowering pass, building up scopes
//! as we descend into the AST.

use bhc_ast as ast;
use bhc_hir::DefId;
use bhc_intern::Symbol;
use bhc_span::Span;

use crate::context::{DefKind, LowerContext};
use crate::LowerError;

/// Resolve a variable reference.
///
/// Returns the `DefId` if found, or records an error and returns `None`.
/// If the resolved definition is a stub, emits a warning.
pub fn resolve_var(ctx: &mut LowerContext, name: Symbol, span: Span) -> Option<DefId> {
    if let Some(def_id) = ctx.lookup_value(name) {
        // Warn if this is a stub (external package placeholder)
        ctx.warn_if_stub(def_id, name.as_str(), span);
        Some(def_id)
    } else {
        ctx.error(LowerError::UnboundVar {
            name: name.as_str().to_string(),
            span,
        });
        None
    }
}

/// Resolve a type reference.
///
/// If the resolved definition is a stub, emits a warning.
// Not yet wired into the lowering pass (WIP type-name resolution).
#[allow(dead_code)]
pub fn resolve_type(ctx: &mut LowerContext, name: Symbol, span: Span) -> Option<DefId> {
    if let Some(def_id) = ctx.lookup_type(name) {
        // Warn if this is a stub (external package placeholder)
        ctx.warn_if_stub(def_id, name.as_str(), span);
        Some(def_id)
    } else {
        ctx.error(LowerError::UnboundType {
            name: name.as_str().to_string(),
            span,
        });
        None
    }
}

/// Resolve a constructor reference.
///
/// If the resolved definition is a stub, emits a warning.
pub fn resolve_constructor(ctx: &mut LowerContext, name: Symbol, span: Span) -> Option<DefId> {
    if let Some(def_id) = ctx.lookup_constructor(name) {
        // Warn if this is a stub (external package placeholder)
        ctx.warn_if_stub(def_id, name.as_str(), span);
        Some(def_id)
    } else {
        ctx.error(LowerError::UnboundCon {
            name: name.as_str().to_string(),
            span,
        });
        None
    }
}

/// Bind a pattern, adding all bound variables to the current scope.
///
/// Returns the list of newly bound (name, `DefId`) pairs.
pub fn bind_pattern(ctx: &mut LowerContext, pat: &ast::Pat) -> Vec<(Symbol, DefId)> {
    let mut bindings = Vec::new();
    collect_pattern_bindings(ctx, pat, &mut bindings);
    bindings
}

fn collect_pattern_bindings(
    ctx: &mut LowerContext,
    pat: &ast::Pat,
    bindings: &mut Vec<(Symbol, DefId)>,
) {
    match pat {
        ast::Pat::Var(ident, span) => {
            let name = ident.name;
            let def_id = ctx.fresh_def_id();
            ctx.define(def_id, name, DefKind::PatVar, *span);

            // Check for duplicate binding in this pattern
            if ctx.current_scope().lookup_value_local(name).is_some() {
                ctx.error(LowerError::DuplicateDefinition {
                    name: name.as_str().to_string(),
                    new_span: *span,
                    existing_span: *span, // TODO: track original span
                });
            } else {
                ctx.bind_value(name, def_id);
                bindings.push((name, def_id));
            }
        }

        ast::Pat::As(ident, inner, span) => {
            let name = ident.name;
            let def_id = ctx.fresh_def_id();
            ctx.define(def_id, name, DefKind::PatVar, *span);

            if ctx.current_scope().lookup_value_local(name).is_some() {
                ctx.error(LowerError::DuplicateDefinition {
                    name: name.as_str().to_string(),
                    new_span: *span,
                    existing_span: *span,
                });
            } else {
                ctx.bind_value(name, def_id);
                bindings.push((name, def_id));
            }

            collect_pattern_bindings(ctx, inner, bindings);
        }

        ast::Pat::Con(_, pats, _) | ast::Pat::QualCon(_, _, pats, _) => {
            for p in pats {
                collect_pattern_bindings(ctx, p, bindings);
            }
        }

        ast::Pat::Tuple(pats, _) => {
            for p in pats {
                collect_pattern_bindings(ctx, p, bindings);
            }
        }

        ast::Pat::List(pats, _) => {
            for p in pats {
                collect_pattern_bindings(ctx, p, bindings);
            }
        }

        ast::Pat::Infix(left, _op, right, _) => {
            // Infix constructor pattern like `x : xs`
            collect_pattern_bindings(ctx, left, bindings);
            collect_pattern_bindings(ctx, right, bindings);
        }

        ast::Pat::Record(con, fields, has_wildcard, span)
        | ast::Pat::QualRecord(_, con, fields, has_wildcard, span) => {
            for field in fields {
                if let Some(p) = &field.pat {
                    collect_pattern_bindings(ctx, p, bindings);
                } else {
                    // Punning: `Foo { x }` binds `x`
                    let name = field.name.name;
                    let def_id = ctx.fresh_def_id();
                    ctx.define(def_id, name, DefKind::PatVar, field.span);
                    ctx.bind_value(name, def_id);
                    bindings.push((name, def_id));
                }
            }
            // RecordWildCards: bind remaining constructor fields as variables
            if *has_wildcard {
                let con_name = con.name;
                if let Some(con_def_id) = ctx.lookup_constructor(con_name) {
                    if let Some(field_names) = ctx.get_constructor_field_names(con_def_id) {
                        let existing: rustc_hash::FxHashSet<_> =
                            fields.iter().map(|f| f.name.name).collect();
                        for field_name in field_names {
                            if !existing.contains(&field_name) {
                                let def_id = ctx.fresh_def_id();
                                ctx.define(def_id, field_name, DefKind::PatVar, *span);
                                ctx.bind_value(field_name, def_id);
                                bindings.push((field_name, def_id));
                            }
                        }
                    }
                }
            }
        }

        ast::Pat::Paren(inner, _) | ast::Pat::Ann(inner, _, _) => {
            collect_pattern_bindings(ctx, inner, bindings);
        }

        ast::Pat::Lazy(inner, _) | ast::Pat::Bang(inner, _) => {
            collect_pattern_bindings(ctx, inner, bindings);
        }

        ast::Pat::Wildcard(_) | ast::Pat::Lit(_, _) => {
            // No bindings
        }

        ast::Pat::View(_view_expr, result_pat, _) => {
            // View pattern binds the result pattern
            collect_pattern_bindings(ctx, result_pat, bindings);
        }
    }
}

/// Pre-collect all top-level definitions in a module.
///
/// This allows forward references within the module.
pub fn collect_module_definitions(ctx: &mut LowerContext, module: &ast::Module) {
    for decl in &module.decls {
        match decl {
            ast::Decl::FunBind(fun_bind) => {
                // Check for pattern binding (special name $patbind)
                if fun_bind.name.name.as_str() == "$patbind"
                    && fun_bind.clauses.len() == 1
                    && fun_bind.clauses[0].pats.len() == 1
                {
                    // Pattern binding: bind all variables in the pattern
                    bind_pattern(ctx, &fun_bind.clauses[0].pats[0]);
                } else {
                    // Regular function binding
                    let name = fun_bind.name.name;
                    let def_id = ctx.fresh_def_id();
                    ctx.define(def_id, name, DefKind::Value, fun_bind.span);
                    ctx.bind_value(name, def_id);
                }
            }

            ast::Decl::DataDecl(data_decl) => {
                // Bind the type name
                let type_name = data_decl.name.name;
                let type_def_id = ctx.fresh_def_id();
                ctx.define(type_def_id, type_name, DefKind::Type, data_decl.span);
                ctx.bind_type(type_name, type_def_id);

                let type_param_count = data_decl.params.len();

                // Bind constructors (H98 syntax)
                for con in &data_decl.constrs {
                    let con_name = con.name.name;
                    let con_def_id = ctx.fresh_def_id();

                    // Extract field info for RecordWildCards support
                    let (arity, field_names) = match &con.fields {
                        ast::ConFields::Positional(fields) => (fields.len(), None),
                        ast::ConFields::Record(fields) => {
                            let names: Vec<Symbol> = fields.iter().map(|f| f.name.name).collect();
                            (fields.len(), Some(names))
                        }
                    };
                    ctx.define_constructor_with_type(
                        con_def_id,
                        con_name,
                        con.span,
                        arity,
                        type_name,
                        type_param_count,
                        field_names,
                    );
                    ctx.bind_constructor(con_name, con_def_id);

                    // Record fields also become functions
                    if let ast::ConFields::Record(fields) = &con.fields {
                        for field in fields {
                            let field_name = field.name.name;
                            let field_def_id = ctx.fresh_def_id();
                            ctx.define(field_def_id, field_name, DefKind::Value, field.span);
                            ctx.bind_value(field_name, field_def_id);
                        }
                    }
                }

                // Bind GADT constructors
                for con in &data_decl.gadt_constrs {
                    let con_name = con.name.name;
                    let con_def_id = ctx.fresh_def_id();

                    // Count arity by counting function arrows in the type
                    let arity = count_type_arrows(&con.ty);
                    ctx.define_constructor_with_type(
                        con_def_id,
                        con_name,
                        con.span,
                        arity,
                        type_name,
                        type_param_count,
                        None,
                    );
                    ctx.bind_constructor(con_name, con_def_id);
                }
            }

            ast::Decl::Newtype(newtype_decl) => {
                let type_name = newtype_decl.name.name;
                let type_def_id = ctx.fresh_def_id();
                ctx.define(type_def_id, type_name, DefKind::Type, newtype_decl.span);
                ctx.bind_type(type_name, type_def_id);

                let type_param_count = newtype_decl.params.len();
                let con_name = newtype_decl.constr.name.name;
                let con_def_id = ctx.fresh_def_id();

                // Extract field info for RecordWildCards support
                let (arity, field_names) = match &newtype_decl.constr.fields {
                    ast::ConFields::Positional(fields) => (fields.len(), None),
                    ast::ConFields::Record(fields) => {
                        let names: Vec<Symbol> = fields.iter().map(|f| f.name.name).collect();
                        (fields.len(), Some(names))
                    }
                };
                ctx.define_constructor_with_type(
                    con_def_id,
                    con_name,
                    newtype_decl.constr.span,
                    arity,
                    type_name,
                    type_param_count,
                    field_names,
                );
                ctx.bind_constructor(con_name, con_def_id);

                // Record fields also become functions (same as data declarations)
                if let ast::ConFields::Record(fields) = &newtype_decl.constr.fields {
                    for field in fields {
                        let field_name = field.name.name;
                        let field_def_id = ctx.fresh_def_id();
                        ctx.define(field_def_id, field_name, DefKind::Value, field.span);
                        ctx.bind_value(field_name, field_def_id);
                    }
                }
            }

            ast::Decl::TypeAlias(type_alias) => {
                let name = type_alias.name.name;
                let def_id = ctx.fresh_def_id();
                ctx.define(def_id, name, DefKind::Type, type_alias.span);
                ctx.bind_type(name, def_id);
            }

            ast::Decl::ClassDecl(class_decl) => {
                let name = class_decl.name.name;
                let def_id = ctx.fresh_def_id();
                ctx.define(def_id, name, DefKind::Class, class_decl.span);
                ctx.bind_type(name, def_id);

                // Register the number of type parameters for multi-param class support
                ctx.register_class_param_count(name, class_decl.params.len());

                // Bind method names
                for method in &class_decl.methods {
                    if let ast::Decl::TypeSig(sig) = method {
                        for method_name in &sig.names {
                            let name = method_name.name;
                            let method_def_id = ctx.fresh_def_id();
                            ctx.define(method_def_id, name, DefKind::Value, sig.span);
                            ctx.bind_value(name, method_def_id);
                        }
                    }
                }
            }

            ast::Decl::Foreign(foreign) => {
                let name = foreign.name.name;
                let def_id = ctx.fresh_def_id();
                ctx.define(def_id, name, DefKind::Value, foreign.span);
                ctx.bind_value(name, def_id);
            }

            ast::Decl::TypeSig(sig) => {
                // Register the type signature for each name
                for name in &sig.names {
                    ctx.register_type_signature(name.name, sig.ty.clone());
                }
            }

            ast::Decl::TypeFamilyDecl(tf) => {
                let name = tf.name.name;
                let def_id = ctx.fresh_def_id();
                ctx.define(def_id, name, DefKind::Type, tf.span);
                ctx.bind_type(name, def_id);
            }

            ast::Decl::TypeInstanceDecl(_) => {
                // Type instances don't introduce new names
            }

            ast::Decl::DataFamilyDecl(df) => {
                let name = df.name.name;
                let def_id = ctx.fresh_def_id();
                ctx.define(def_id, name, DefKind::Type, df.span);
                ctx.bind_type(name, def_id);
            }

            ast::Decl::DataInstanceDecl(di) => {
                // Data family instances define constructors but not a new type name
                let type_name = di.family_name.name;
                let type_param_count = di.args.len();

                // Bind H98 constructors
                for con in &di.constrs {
                    let con_name = con.name.name;
                    let con_def_id = ctx.fresh_def_id();
                    let (arity, field_names) = match &con.fields {
                        ast::ConFields::Positional(fields) => (fields.len(), None),
                        ast::ConFields::Record(fields) => {
                            let names: Vec<Symbol> = fields.iter().map(|f| f.name.name).collect();
                            (fields.len(), Some(names))
                        }
                    };
                    ctx.define_constructor_with_type(
                        con_def_id,
                        con_name,
                        con.span,
                        arity,
                        type_name,
                        type_param_count,
                        field_names,
                    );
                    ctx.bind_constructor(con_name, con_def_id);

                    if let ast::ConFields::Record(fields) = &con.fields {
                        for field in fields {
                            let field_def_id = ctx.fresh_def_id();
                            ctx.define(field_def_id, field.name.name, DefKind::Value, field.span);
                            ctx.bind_value(field.name.name, field_def_id);
                        }
                    }
                }

                // Bind GADT constructors
                for con in &di.gadt_constrs {
                    let con_name = con.name.name;
                    let con_def_id = ctx.fresh_def_id();
                    let arity = count_type_arrows(&con.ty);
                    ctx.define_constructor_with_type(
                        con_def_id,
                        con_name,
                        con.span,
                        arity,
                        type_name,
                        type_param_count,
                        None,
                    );
                    ctx.bind_constructor(con_name, con_def_id);
                }
            }

            ast::Decl::Fixity(_)
            | ast::Decl::InstanceDecl(_)
            | ast::Decl::PragmaDecl(_)
            | ast::Decl::StandaloneDeriving(_) => {
                // These don't introduce new names
            }

            ast::Decl::PatternSynonym(ps) => {
                // Register the pattern synonym name as a constructor-like name
                let name = ps.name.name;
                let con_def_id = ctx.fresh_def_id();
                ctx.define(con_def_id, name, DefKind::Constructor, ps.span);
                ctx.bind_constructor(name, con_def_id);
                // Store the pattern synonym definition for expansion during lower_pat
                let args: Vec<Symbol> = ps.args.iter().map(|a| a.name).collect();
                ctx.register_pattern_synonym(name, args, ps.pattern.clone());
            }
        }
    }
}

/// Collect type variables from a type, binding them in the current scope.
// Not yet wired into the lowering pass (WIP type-variable binding).
#[allow(dead_code)]
pub fn bind_type_vars(ctx: &mut LowerContext, ty: &ast::Type) -> Vec<(Symbol, DefId)> {
    let mut bindings = Vec::new();
    collect_type_vars(ctx, ty, &mut bindings);
    bindings
}

// Helper for the WIP `bind_type_vars`; unused until that pass is wired in.
#[allow(dead_code)]
fn collect_type_vars(ctx: &mut LowerContext, ty: &ast::Type, bindings: &mut Vec<(Symbol, DefId)>) {
    match ty {
        ast::Type::Var(tyvar, span) => {
            let name = tyvar.name.name;
            // Only bind if not already bound
            if ctx.lookup_type(name).is_none() && !bindings.iter().any(|(n, _)| *n == name) {
                let def_id = ctx.fresh_def_id();
                ctx.define(def_id, name, DefKind::TyVar, *span);
                ctx.bind_type(name, def_id);
                bindings.push((name, def_id));
            }
        }

        ast::Type::App(f, a, _) => {
            collect_type_vars(ctx, f, bindings);
            collect_type_vars(ctx, a, bindings);
        }

        ast::Type::Fun(from, to, _) => {
            collect_type_vars(ctx, from, bindings);
            collect_type_vars(ctx, to, bindings);
        }

        ast::Type::Tuple(tys, _) => {
            for t in tys {
                collect_type_vars(ctx, t, bindings);
            }
        }

        ast::Type::List(elem, _) => {
            collect_type_vars(ctx, elem, bindings);
        }

        ast::Type::Paren(inner, _) => {
            collect_type_vars(ctx, inner, bindings);
        }

        ast::Type::Constrained(constraints, inner, _) => {
            for c in constraints {
                for arg in &c.args {
                    collect_type_vars(ctx, arg, bindings);
                }
            }
            collect_type_vars(ctx, inner, bindings);
        }

        ast::Type::Forall(vars, inner, _) => {
            // Forall binds its own variables
            for var in vars {
                let name = var.name.name;
                if !bindings.iter().any(|(n, _)| *n == name) {
                    let def_id = ctx.fresh_def_id();
                    ctx.define(def_id, name, DefKind::TyVar, var.span);
                    bindings.push((name, def_id));
                }
            }
            collect_type_vars(ctx, inner, bindings);
        }

        ast::Type::InfixOp(lhs, _, rhs, _) => {
            // Recurse into both operands; the operator itself is a constructor
            collect_type_vars(ctx, lhs, bindings);
            collect_type_vars(ctx, rhs, bindings);
        }

        ast::Type::Con(_, _)
        | ast::Type::QualCon(_, _, _)
        | ast::Type::NatLit(_, _)
        | ast::Type::PromotedList(_, _)
        | ast::Type::Bang(_, _)
        | ast::Type::Lazy(_, _) => {
            // No type variables to collect in these
        }
    }
}

/// Count the number of function arrows in an AST type (for GADT arity).
///
/// Strips forall and then counts `->` occurrences.
fn count_type_arrows(ty: &ast::Type) -> usize {
    let inner = match ty {
        ast::Type::Forall(_, inner, _) => inner.as_ref(),
        _ => ty,
    };
    let mut count = 0;
    let mut current = inner;
    while let ast::Type::Fun(_, to, _) = current {
        count += 1;
        current = to.as_ref();
    }
    count
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_resolve_builtin() {
        let mut ctx = LowerContext::with_builtins();

        let map = Symbol::intern("map");
        let result = resolve_var(&mut ctx, map, Span::default());
        assert!(result.is_some());
        assert!(!ctx.has_errors());
    }

    #[test]
    fn test_resolve_unbound() {
        let mut ctx = LowerContext::with_builtins();

        let unknown = Symbol::intern("unknownVariable");
        let result = resolve_var(&mut ctx, unknown, Span::default());
        assert!(result.is_none());
        assert!(ctx.has_errors());
    }

    #[test]
    fn test_resolve_type() {
        let mut ctx = LowerContext::with_builtins();

        let int = Symbol::intern("Int");
        let result = resolve_type(&mut ctx, int, Span::default());
        assert!(result.is_some());
    }

    #[test]
    fn test_resolve_constructor() {
        let mut ctx = LowerContext::with_builtins();

        let just = Symbol::intern("Just");
        let result = resolve_constructor(&mut ctx, just, Span::default());
        assert!(result.is_some());
    }
}
