//! Main AST to HIR lowering pass.
//!
//! This module contains the core lowering logic that transforms surface AST
//! into HIR by:
//!
//! 1. Resolving names (identifiers -> `DefIds`)
//! 2. Desugaring syntactic constructs
//! 3. Building HIR nodes

use bhc_ast as ast;
use bhc_hir as hir;
use bhc_intern::{Ident, Symbol};
use bhc_span::Span;
use camino::Utf8PathBuf;
use rustc_hash::FxHashMap;

use crate::context::{DefKind, LowerContext};
use crate::desugar;
use crate::loader::{self, LoadError, ModuleCache};
use crate::resolve::{bind_pattern, collect_module_definitions, resolve_constructor, resolve_var};
use crate::{LowerError, LowerResult};

/// Configuration for the lowering pass.
#[derive(Clone, Debug, Default)]
pub struct LowerConfig {
    /// Whether to include builtins in the context.
    pub include_builtins: bool,
    /// Whether to report warnings for unused bindings.
    pub warn_unused: bool,
    /// Search paths for module imports.
    pub search_paths: Vec<Utf8PathBuf>,
}

/// Lower an AST module to HIR.
///
/// # Arguments
///
/// * `ctx` - The lowering context containing scope and definition information
/// * `module` - The AST module to lower
/// * `config` - Configuration including search paths for module imports
pub fn lower_module(
    ctx: &mut LowerContext,
    module: &ast::Module,
    config: &LowerConfig,
) -> LowerResult<hir::Module> {
    lower_module_with_cache(ctx, module, config, ModuleCache::new())
}

/// Lower an AST module to HIR with a pre-seeded module cache.
///
/// This is used during multi-module compilation: modules compiled earlier
/// have their exports inserted into the cache so that later modules can
/// resolve imports without loading from disk.
pub fn lower_module_with_cache(
    ctx: &mut LowerContext,
    module: &ast::Module,
    config: &LowerConfig,
    cache: ModuleCache,
) -> LowerResult<hir::Module> {
    let mut cache = cache;

    // Determine if we should inject an implicit Prelude import.
    // Skip if:
    // 1. The module is the Prelude itself
    // 2. The module has {-# LANGUAGE NoImplicitPrelude #-}
    // 3. The module already explicitly imports Prelude
    let module_name_str = module.name.as_ref().map(bhc_ast::ModuleName::to_string);
    let is_prelude_module = module_name_str.as_deref() == Some("Prelude")
        || module_name_str.as_deref() == Some("BHC.Prelude");

    let has_no_implicit_prelude = module.pragmas.iter().any(|p| {
        if let ast::PragmaKind::Language(exts) = &p.kind {
            exts.iter().any(|e| e.as_str() == "NoImplicitPrelude")
        } else {
            false
        }
    });

    let has_overloaded_strings = module.pragmas.iter().any(|p| {
        if let ast::PragmaKind::Language(exts) = &p.kind {
            exts.iter().any(|e| e.as_str() == "OverloadedStrings")
        } else {
            false
        }
    });

    let has_scoped_type_variables = module.pragmas.iter().any(|p| {
        if let ast::PragmaKind::Language(exts) = &p.kind {
            exts.iter().any(|e| e.as_str() == "ScopedTypeVariables")
        } else {
            false
        }
    });

    let has_generalized_newtype_deriving = module.pragmas.iter().any(|p| {
        if let ast::PragmaKind::Language(exts) = &p.kind {
            exts.iter()
                .any(|e| e.as_str() == "GeneralizedNewtypeDeriving")
        } else {
            false
        }
    });

    let has_flexible_instances = module.pragmas.iter().any(|p| {
        if let ast::PragmaKind::Language(exts) = &p.kind {
            exts.iter().any(|e| e.as_str() == "FlexibleInstances")
        } else {
            false
        }
    });

    let has_flexible_contexts = module.pragmas.iter().any(|p| {
        if let ast::PragmaKind::Language(exts) = &p.kind {
            exts.iter().any(|e| e.as_str() == "FlexibleContexts")
        } else {
            false
        }
    });

    let has_gadts = module.pragmas.iter().any(|p| {
        if let ast::PragmaKind::Language(exts) = &p.kind {
            exts.iter().any(|e| e.as_str() == "GADTs")
        } else {
            false
        }
    });

    let has_strict_data = module.pragmas.iter().any(|p| {
        if let ast::PragmaKind::Language(exts) = &p.kind {
            exts.iter().any(|e| e.as_str() == "StrictData")
        } else {
            false
        }
    });

    let has_overloaded_lists = module.pragmas.iter().any(|p| {
        if let ast::PragmaKind::Language(exts) = &p.kind {
            exts.iter().any(|e| e.as_str() == "OverloadedLists")
        } else {
            false
        }
    });

    // Validate LANGUAGE pragmas — warn about unknown or unimplemented extensions
    for pragma in &module.pragmas {
        if let ast::PragmaKind::Language(exts) = &pragma.kind {
            for ext_sym in exts {
                let ext = ast::Extension::from_name(ext_sym.as_str());
                match ext.status() {
                    ast::ExtensionStatus::Supported => {}
                    ast::ExtensionStatus::Unimplemented => {
                        ctx.warnings
                            .push(crate::LowerWarning::UnimplementedExtension {
                                name: ext_sym.as_str().to_string(),
                                span: pragma.span,
                            });
                    }
                    ast::ExtensionStatus::Unknown => {
                        ctx.warnings.push(crate::LowerWarning::UnknownExtension {
                            name: ext_sym.as_str().to_string(),
                            span: pragma.span,
                        });
                    }
                }
            }
        }
    }

    let already_imports_prelude = module.imports.iter().any(|imp| {
        let name = imp
            .module
            .parts
            .iter()
            .map(|s| s.as_str())
            .collect::<Vec<_>>()
            .join(".");
        name == "Prelude"
    });

    let should_inject_prelude =
        !is_prelude_module && !has_no_implicit_prelude && !already_imports_prelude;

    // Build the effective imports list, possibly prepending an implicit Prelude import
    let mut effective_imports = Vec::new();
    if should_inject_prelude {
        effective_imports.push(ast::ImportDecl {
            module: ast::ModuleName {
                parts: vec![Symbol::intern("Prelude")],
                span: Span::default(),
            },
            qualified: false,
            alias: None,
            spec: None,
            span: Span::default(),
        });
    }
    effective_imports.extend(module.imports.iter().cloned());

    // Process imports first to register aliases
    process_imports(ctx, &effective_imports, &config.search_paths, &mut cache);

    // First pass: collect all top-level definitions
    collect_module_definitions(ctx, module);

    // Second pass: lower all declarations
    let mut items = Vec::new();
    for decl in &module.decls {
        let new_items = lower_decl(ctx, decl)?;
        items.extend(new_items);
    }

    // Lower imports
    let imports = module.imports.iter().map(lower_import).collect();

    // Lower exports
    let exports = module
        .exports
        .as_ref()
        .map(|exps| exps.iter().map(lower_export).collect());

    // Check for errors
    if ctx.has_errors() {
        let errors = ctx.take_errors();
        return Err(LowerError::Multiple(errors));
    }

    Ok(hir::Module {
        name: module.name.as_ref().map_or_else(
            || Symbol::intern("Main"),
            |n| {
                // Combine module name parts into a single symbol
                let full_name = n
                    .parts
                    .iter()
                    .map(|s| s.as_str())
                    .collect::<Vec<_>>()
                    .join(".");
                Symbol::intern(&full_name)
            },
        ),
        exports,
        imports,
        items,
        span: module.span,
        overloaded_strings: has_overloaded_strings,
        scoped_type_variables: has_scoped_type_variables,
        generalized_newtype_deriving: has_generalized_newtype_deriving,
        flexible_instances: has_flexible_instances,
        flexible_contexts: has_flexible_contexts,
        gadts: has_gadts,
        strict_data: has_strict_data,
        overloaded_lists: has_overloaded_lists,
    })
}

/// Process imports and register aliases.
///
/// This function attempts to load modules from the file system first. If a module
/// is not found in the search paths, it falls back to the hardcoded standard
/// library exports.
fn process_imports(
    ctx: &mut LowerContext,
    imports: &[ast::ImportDecl],
    search_paths: &[Utf8PathBuf],
    cache: &mut ModuleCache,
) {
    for import in imports {
        // Get the full module name
        let module_name = import
            .module
            .parts
            .iter()
            .map(|s| s.as_str())
            .collect::<Vec<_>>()
            .join(".");
        let module_sym = Symbol::intern(&module_name);

        // Determine the alias to use
        let alias = if let Some(alias_name) = &import.alias {
            // Explicit alias: import qualified Data.Map as M
            let alias_str = alias_name
                .parts
                .iter()
                .map(|s| s.as_str())
                .collect::<Vec<_>>()
                .join(".");
            Symbol::intern(&alias_str)
        } else if import.qualified {
            // No explicit alias for qualified import: use full module name
            // import qualified Data.Map means M.lookup is Data.Map.lookup
            module_sym
        } else {
            // Non-qualified import: no alias needed for qualified access
            // but we might still want to track it
            module_sym
        };

        // Register the alias
        ctx.register_import_alias(alias, module_sym);

        // For non-qualified imports, also register the last component as an alias
        // This handles `import Data.Map` allowing both `Data.Map.lookup` and `Map.lookup`
        if !import.qualified && import.module.parts.len() > 1 {
            if let Some(last_part) = import.module.parts.last() {
                let short_alias = Symbol::intern(last_part.as_str());
                ctx.register_import_alias(short_alias, module_sym);
            }
        }

        // Try to load the module from the file system
        match loader::load_module(&module_name, search_paths, cache, ctx) {
            Ok(exports) => {
                // Apply import specification (Only/Hiding)
                let filtered = loader::apply_import_spec(&exports, &import.spec);
                // Register the imported names
                loader::register_imported_names(ctx, import, &filtered);
                tracing::debug!(
                    module = %module_name,
                    values = filtered.values.len(),
                    types = filtered.types.len(),
                    constructors = filtered.constructors.len(),
                    "loaded module exports"
                );
            }
            Err(LoadError::ModuleNotFound(_)) => {
                // Module not found in search paths - fall back to standard library
                tracing::debug!(
                    module = %module_name,
                    "module not found in search paths, using standard library fallback"
                );
                register_standard_module_exports(ctx, &module_name, Some(import));
            }
            Err(LoadError::CircularImport(ref cycle_module)) => {
                // Log circular import but don't fail - allow compilation to continue
                tracing::warn!(
                    module = %module_name,
                    cycle = %cycle_module,
                    "circular import detected"
                );
                // Still register standard exports as fallback
                register_standard_module_exports(ctx, &module_name, Some(import));
            }
            Err(e) => {
                // Other errors (IO, parse) - log and fall back
                tracing::warn!(
                    module = %module_name,
                    error = %e,
                    "failed to load module, using standard library fallback"
                );
                register_standard_module_exports(ctx, &module_name, Some(import));
            }
        }
    }
}

/// Register standard module exports.
///
/// This is a temporary solution until we have proper module loading.
/// It registers common functions from well-known modules.
/// When `import_decl` is provided, qualified names are also registered under the
/// import alias (e.g., `T.pack` for `import qualified Data.Text as T`).
fn register_standard_module_exports(
    ctx: &mut LowerContext,
    module_name: &str,
    import_decl: Option<&ast::ImportDecl>,
) {
    let exports: &[&str] = match module_name {
        "Prelude" => &[
            // Numeric
            "+",
            "-",
            "*",
            "negate",
            "abs",
            "signum",
            "fromInteger",
            "fromIntegral",
            "div",
            "mod",
            "even",
            "odd",
            "sum",
            "product",
            // Comparison
            "==",
            "/=",
            "<",
            "<=",
            ">",
            ">=",
            "compare",
            "min",
            "max",
            // Boolean
            "&&",
            "||",
            "not",
            "otherwise",
            // List operations
            "map",
            "filter",
            "head",
            "tail",
            "last",
            "init",
            "null",
            "length",
            "reverse",
            "foldl",
            "foldl'",
            "foldr",
            "concat",
            "concatMap",
            "zip",
            "zipWith",
            "unzip",
            "take",
            "drop",
            "splitAt",
            "takeWhile",
            "dropWhile",
            "span",
            "break",
            "elem",
            "notElem",
            "lookup",
            "replicate",
            "iterate",
            "repeat",
            "cycle",
            "any",
            "all",
            "and",
            "or",
            "lines",
            "unlines",
            "words",
            "unwords",
            "++",
            // Tuple
            "fst",
            "snd",
            "curry",
            "uncurry",
            "swap",
            // Function
            "id",
            "const",
            "flip",
            "$",
            ".",
            "seq",
            "error",
            "undefined",
            // Maybe
            "maybe",
            "fromMaybe",
            "isJust",
            "isNothing",
            // Either
            "either",
            // IO
            "putStrLn",
            "putStr",
            "print",
            "getLine",
            "readLn",
            ">>",
            ">>=",
            "return",
            // Show
            "show",
            "showString",
            "showChar",
            "showParen",
            // Enum
            "enumFromTo",
            "succ",
            "pred",
            "toEnum",
            "fromEnum",
            // Integral
            "gcd",
            "lcm",
            "quot",
            "rem",
            "quotRem",
            "divMod",
            "subtract",
            "realToFrac",
            // List extras
            "maximum",
            "minimum",
            "scanl",
            "scanr",
            "scanl1",
            "scanr1",
            "zip3",
            "zipWith3",
            "unzip3",
            // Function
            "until",
            "asTypeOf",
            // IO
            "getChar",
            "getContents",
            "readFile",
            "writeFile",
            "appendFile",
            "interact",
            // Read/lex
            "lex",
            "read",
            "reads",
            "readParen",
            // MonadFail
            "fail",
            // Error
            "userError",
            "ioError",
        ],
        "Data.Map"
        | "Data.Map.Strict"
        | "Data.Map.Lazy"
        | "Data.Map.Strict.Internal"
        | "Data.Map.Internal" => &[
            "Map",
            "empty",
            "singleton",
            "insert",
            "insertWith",
            "delete",
            "lookup",
            "member",
            "notMember",
            "null",
            "size",
            "union",
            "unionWith",
            "unionWithKey",
            "unions",
            "intersection",
            "intersectionWith",
            "difference",
            "differenceWith",
            "map",
            "mapWithKey",
            "mapKeys",
            "filter",
            "filterWithKey",
            "foldr",
            "foldl",
            "foldrWithKey",
            "foldlWithKey",
            "keys",
            "elems",
            "assocs",
            "toList",
            "fromList",
            "fromListWith",
            "toAscList",
            "toDescList",
            "findWithDefault",
            "adjust",
            "update",
            "alter",
            "keysSet",
            "isSubmapOf",
            "!",
        ],
        "Data.Set" | "Data.Set.Internal" => &[
            "Set",
            "empty",
            "singleton",
            "insert",
            "delete",
            "member",
            "notMember",
            "null",
            "size",
            "union",
            "unions",
            "intersection",
            "difference",
            "isSubsetOf",
            "isProperSubsetOf",
            "map",
            "filter",
            "partition",
            "foldr",
            "foldl",
            "toList",
            "fromList",
            "toAscList",
            "toDescList",
            "elems",
            "findMin",
            "findMax",
            "lookupMin",
            "lookupMax",
            "deleteMin",
            "deleteMax",
        ],
        "Data.IntMap" | "Data.IntMap.Strict" => &[
            "empty",
            "singleton",
            "insert",
            "insertWith",
            "delete",
            "lookup",
            "member",
            "null",
            "size",
            "union",
            "unionWith",
            "intersection",
            "difference",
            "map",
            "mapWithKey",
            "filter",
            "foldr",
            "foldlWithKey",
            "keys",
            "elems",
            "toList",
            "toAscList",
            "fromList",
            "findWithDefault",
            "adjust",
            "lookupMax",
            "lookupMin",
            "lookupGT",
            "lookupLT",
            "lookupGE",
            "lookupLE",
        ],
        "Data.IntSet" => &[
            "empty",
            "singleton",
            "insert",
            "delete",
            "member",
            "null",
            "size",
            "union",
            "intersection",
            "difference",
            "isSubsetOf",
            "filter",
            "foldr",
            "toList",
            "fromList",
        ],
        "Data.Sequence" => &[
            "Seq",
            "empty",
            "singleton",
            "null",
            "length",
            "index",
            "lookup",
            "<|",
            "|>",
            "><",
            "take",
            "drop",
            "reverse",
            "update",
            "insertAt",
            "deleteAt",
            "fromList",
            "toList",
            "replicate",
            "viewl",
            "viewr",
            "filter",
            "spanl",
            "spanr",
            "ViewL",
            "ViewR",
            "EmptyL",
            "EmptyR",
            ":<",
            ":>",
            "sort",
            "sortBy",
            "sortOn",
            "zip",
            "zipWith",
            "unzip",
            "foldlWithIndex",
            "foldrWithIndex",
            "mapWithIndex",
        ],
        "Data.List" => &[
            "sort",
            "sortBy",
            "sortOn",
            "nub",
            "nubBy",
            "delete",
            "deleteBy",
            "union",
            "unionBy",
            "intersect",
            "intersectBy",
            "group",
            "groupBy",
            "intersperse",
            "intercalate",
            "transpose",
            "subsequences",
            "permutations",
            "foldl'",
            "find",
            "partition",
            "span",
            "break",
            "stripPrefix",
            "isPrefixOf",
            "isSuffixOf",
            "isInfixOf",
            "tails",
            "inits",
            "mapAccumL",
            "mapAccumR",
            "unfoldr",
            "genericLength",
            "genericTake",
            "genericDrop",
            "\\\\",
        ],
        "Data.Maybe" => &[
            "maybe",
            "isJust",
            "isNothing",
            "fromJust",
            "fromMaybe",
            "listToMaybe",
            "maybeToList",
            "catMaybes",
            "mapMaybe",
        ],
        "Data.Either" => &[
            "either",
            "isLeft",
            "isRight",
            "fromLeft",
            "fromRight",
            "lefts",
            "rights",
            "partitionEithers",
        ],
        "Data.Semigroup" => &[
            "(<>)", "sconcat", "stimes", // Min/Max wrappers
            "Min", "getMin", "Max", "getMax", // Arg
            "Arg", "First", "Last",
        ],
        "Control.Monad" => &[
            "when",
            "unless",
            "guard",
            "void",
            "join",
            "filterM",
            "mapM",
            "forM",
            "sequence",
            "replicateM",
            "replicateM_",
            "forever",
            "liftM",
            "liftM2",
            "liftM3",
            "liftM4",
            "liftM5",
            "ap",
            "mzero",
            "mplus",
            "msum",
            "mfilter",
            "mapAndUnzipM",
            "zipWithM",
            "zipWithM_",
            "foldM",
            "foldM_",
            ">=>",
            "<=<",
        ],
        "Control.Applicative" => &[
            "pure", "(<*>)", "(<$>)", "(*>)", "(<*)", "empty", "(<|>)", "some", "many", "optional",
            "liftA", "liftA2", "liftA3",
        ],
        "Control.Exception" => &[
            "Exception",
            "SomeException",
            "IOException",
            "ErrorCall",
            "catch",
            "try",
            "throw",
            "throwIO",
            "bracket",
            "bracket_",
            "bracketOnError",
            "finally",
            "onException",
            "handle",
            "handleJust",
            "catchJust",
            "tryJust",
            "evaluate",
            "mask",
            "mask_",
            "uninterruptibleMask",
            "uninterruptibleMask_",
            "getMaskingState",
            "throwTo",
            "toException",
            "fromException",
            "displayException",
            "userError",
            "ioError",
        ],
        "Control.Concurrent" => &[
            "forkIO",
            "forkOS",
            "killThread",
            "threadDelay",
            "myThreadId",
            "throwTo",
            "ThreadId",
            // Re-exports from Control.Concurrent.MVar
            "MVar",
            "newMVar",
            "newEmptyMVar",
            "readMVar",
            "putMVar",
            "takeMVar",
            "tryPutMVar",
            "tryTakeMVar",
            "modifyMVar",
            "modifyMVar_",
            "swapMVar",
            "withMVar",
            // Re-exports from Control.Concurrent.Chan
            "Chan",
            "newChan",
            "readChan",
            "writeChan",
        ],
        "Control.Concurrent.MVar" => &[
            "MVar",
            "newMVar",
            "newEmptyMVar",
            "takeMVar",
            "putMVar",
            "readMVar",
            "modifyMVar",
            "modifyMVar_",
            "withMVar",
        ],
        "Data.Foldable" => &[
            "fold",
            "foldMap",
            "foldMap'",
            "foldr",
            "foldr'",
            "foldl",
            "foldl'",
            "foldr1",
            "foldl1",
            "toList",
            "null",
            "length",
            "elem",
            "notElem",
            "maximum",
            "minimum",
            "maximumBy",
            "minimumBy",
            "sum",
            "product",
            "any",
            "all",
            "and",
            "or",
            "find",
            "concat",
            "concatMap",
            "asum",
            "msum",
            "traverse_",
            "for_",
            "sequenceA_",
            "mapM_",
            "forM_",
        ],
        "Data.Traversable" => &[
            "traverse",
            "sequenceA",
            "mapM",
            "sequence",
            "for",
            "forM",
            "mapAccumL",
            "mapAccumR",
        ],
        "Data.Monoid" => &[
            "mempty",
            "mappend",
            "mconcat",
            "(<>)",
            "Sum",
            "Product",
            "Any",
            "All",
            "First",
            "Last",
            "Endo",
            "Dual",
            // Accessor/unwrapper functions for newtypes
            "getSum",
            "getProduct",
            "getAny",
            "getAll",
            "getFirst",
            "getLast",
            "getDual",
            "appEndo",
        ],
        "Data.Bits" => &[
            "(.&.)",
            "(.|.)",
            "xor",
            "complement",
            "shift",
            "rotate",
            "bit",
            "setBit",
            "clearBit",
            "complementBit",
            "testBit",
            "shiftL",
            "shiftR",
            "rotateL",
            "rotateR",
            "popCount",
            "zeroBits",
            "countLeadingZeros",
            "countTrailingZeros",
        ],
        "Data.Char" => &[
            "ord",
            "chr",
            "isAlpha",
            "isAlphaNum",
            "isAscii",
            "isControl",
            "isDigit",
            "isHexDigit",
            "isLetter",
            "isLower",
            "isNumber",
            "isPrint",
            "isPunctuation",
            "isSpace",
            "isSymbol",
            "isUpper",
            "toLower",
            "toUpper",
            "toTitle",
            "digitToInt",
            "intToDigit",
            "isLatin1",
            "isAsciiLower",
            "isAsciiUpper",
            "readLitChar",
            // Unicode category predicates
            "isMark",
            "isSeparator",
            "isOtherLetter",
            "isTitleCase",
            "isModifierLetter",
            "isNonSpacingMark",
            "isSpacingCombiningMark",
            "isEnclosingMark",
            "isDecimalNumber",
            "isLetterNumber",
            "isOtherNumber",
            "isConnectorPunctuation",
            "isDashPunctuation",
            "isOpenPunctuation",
            "isClosePunctuation",
            "isInitialQuote",
            "isFinalQuote",
            "isOtherPunctuation",
            "isMathSymbol",
            "isCurrencySymbol",
            "isModifierSymbol",
            "isOtherSymbol",
            // Conversion
            "generalCategory",
            "GeneralCategory",
            "UppercaseLetter",
            "LowercaseLetter",
            "TitlecaseLetter",
            "ModifierLetter",
            "OtherLetter",
            "NonSpacingMark",
            "SpacingCombiningMark",
            "EnclosingMark",
            "DecimalNumber",
            "LetterNumber",
            "OtherNumber",
            "ConnectorPunctuation",
            "DashPunctuation",
            "OpenPunctuation",
            "ClosePunctuation",
            "InitialQuote",
            "FinalQuote",
            "OtherPunctuation",
            "MathSymbol",
            "CurrencySymbol",
            "ModifierSymbol",
            "OtherSymbol",
            "Space",
            "LineSeparator",
            "ParagraphSeparator",
            "Control",
            "Format",
            "Surrogate",
            "PrivateUse",
            "NotAssigned",
        ],
        "Data.Function" => &["id", "const", "flip", "($)", "(&)", "on", "fix"],
        "Data.Tuple" => &["fst", "snd", "curry", "uncurry", "swap"],
        "Data.Ord" => &[
            "compare",
            "(<)",
            "(<=)",
            "(>)",
            "(>=)",
            "max",
            "min",
            "comparing",
            "clamp",
            "Down",
        ],
        "Data.Eq" => &["(==)", "(/=)"],
        "Text.Read" => &[
            "read",
            "reads",
            "readMaybe",
            "readEither",
            "readPrec",
            "lex",
        ],
        "Text.Show" => &["show", "shows", "showString", "showChar", "showParen"],
        "System.IO" => &[
            "IO",
            "FilePath",
            "Handle",
            "IOMode",
            "BufferMode",
            "ReadMode",
            "WriteMode",
            "AppendMode",
            "ReadWriteMode",
            "NewlineMode",
            "Newline",
            "nativeNewline",
            "noNewlineTranslation",
            "universalNewlineMode",
            "stdin",
            "stdout",
            "stderr",
            "openFile",
            "hClose",
            "hGetChar",
            "hGetLine",
            "hGetContents",
            "hPutChar",
            "hPutStr",
            "hPutStrLn",
            "hPrint",
            "hFlush",
            "hIsEOF",
            "hSetBuffering",
            "hGetBuffering",
            "hSetNewlineMode",
            "hSetEncoding",
            "utf8",
            "hSeek",
            "hTell",
            "hFileSize",
            "withFile",
            "readFile",
            "writeFile",
            "appendFile",
            "getLine",
            "readLn",
            "getContents",
            "putStr",
            "putStrLn",
            "print",
            "isEOF",
            "hSetBinaryMode",
            "withBinaryFile",
            "openBinaryFile",
            "SeekMode",
            "AbsoluteSeek",
            "RelativeSeek",
            "SeekFromEnd",
            "LineBuffering",
            "BlockBuffering",
            "NoBuffering",
            "hIsOpen",
            "hIsClosed",
            "hIsReadable",
            "hIsWritable",
            "hIsSeekable",
            "hReady",
            "hWaitForInput",
            "hLookAhead",
            "CRLF",
            "LF",
        ],
        "Data.IORef" => &[
            "IORef",
            "newIORef",
            "readIORef",
            "writeIORef",
            "modifyIORef",
            "modifyIORef'",
            "atomicModifyIORef",
            "atomicModifyIORef'",
        ],
        "System.Exit" => &[
            "ExitCode",
            "ExitSuccess",
            "ExitFailure",
            "exitSuccess",
            "exitFailure",
            "exitWith",
        ],
        "System.Environment" => &[
            "getArgs",
            "getProgName",
            "getEnv",
            "lookupEnv",
            "setEnv",
            "getEnvironment",
        ],
        "System.Directory" => &[
            "doesFileExist",
            "doesDirectoryExist",
            "createDirectory",
            "createDirectoryIfMissing",
            "removeFile",
            "removeDirectory",
            "getCurrentDirectory",
            "setCurrentDirectory",
            "findExecutable",
            "getPermissions",
            "executable",
            "getDirectoryContents",
            "listDirectory",
            "renameFile",
            "copyFile",
            "getTemporaryDirectory",
            "getHomeDirectory",
            "getModificationTime",
        ],
        "Data.String" => &[
            "IsString",
            "fromString",
            "lines",
            "words",
            "unlines",
            "unwords",
        ],
        "Data.Proxy" => &["Proxy", "asProxyTypeOf"],
        "Data.Void" => &["Void", "absurd", "vacuous"],
        "Data.Word" => &[
            "Word",
            "Word8",
            "Word16",
            "Word32",
            "Word64",
            "bitReverse32",
        ],
        "Data.Text.IO" => &[
            "readFile",
            "writeFile",
            "appendFile",
            "hGetContents",
            "hGetLine",
            "hPutStr",
            "hPutStrLn",
            "putStr",
            "putStrLn",
            "getLine",
            "readLn",
            "getContents",
        ],
        "GHC.Generics" => &[
            "Generic", "Rep", "from", "to", "V1", "U1", "K1", "Rec0", "M1", "D1", "C1", "S1", "L1",
            "R1", ":+:", ":*:",
        ],
        "Control.DeepSeq" => &["NFData", "rnf", "deepseq", "force"],
        "Data.Text.Lazy" => &[
            "Text",
            "empty",
            "fromStrict",
            "toStrict",
            "pack",
            "unpack",
            "null",
            "length",
            "append",
            "fromChunks",
            "toChunks",
            "head",
            "tail",
            "take",
            "drop",
        ],
        "Data.ByteString.Lazy" => &[
            "ByteString",
            "empty",
            "fromStrict",
            "toStrict",
            "fromChunks",
            "toChunks",
            "null",
            "length",
            "pack",
            "unpack",
            "append",
            "head",
            "tail",
            "take",
            "drop",
            "filter",
            "isPrefixOf",
            "readFile",
            "writeFile",
            "putStr",
            "hPutStr",
            "hGetContents",
        ],
        "Data.ByteString.Lazy.Char8" => &[
            "ByteString",
            "empty",
            "pack",
            "unpack",
            "singleton",
            "null",
            "length",
            "head",
            "tail",
            "take",
            "drop",
            "dropWhile",
            "takeWhile",
            "cons",
            "snoc",
            "append",
            "filter",
            "map",
            "reverse",
            "concat",
            "concatMap",
            "intercalate",
            "lines",
            "unlines",
            "words",
            "unwords",
            "isPrefixOf",
            "isSuffixOf",
            "readFile",
            "writeFile",
            "putStr",
            "putStrLn",
            "hGetContents",
            "hGet",
            "hPut",
            "hPutStr",
            "hPutStrLn",
            "fromStrict",
            "toStrict",
            "fromChunks",
            "toChunks",
            "foldr",
            "foldl",
            "foldl'",
            "any",
            "all",
            "elem",
            "index",
            "splitAt",
            "break",
            "span",
            "zip",
            "zipWith",
        ],
        "Data.ByteString.Builder" => &[
            "empty",
            "singleton",
            "word8",
            "byteString",
            "shortByteString",
            "lazyByteString",
            "toLazyByteString",
            "toStrictByteString",
            "append",
            "hPutBuilder",
            "charUtf8",
            "stringUtf8",
            "intDec",
            "int8Dec",
            "int16Dec",
            "int32Dec",
            "int64Dec",
            "integerDec",
            "wordDec",
            "word8Dec",
            "word16Dec",
            "word32Dec",
            "word64Dec",
            "char7",
            "char8",
            "string7",
            "string8",
            "word16BE",
            "word32BE",
            "word64BE",
            "int16BE",
            "int32BE",
            "int64BE",
            "word16LE",
            "word32LE",
            "word64LE",
            "int16LE",
            "int32LE",
            "int64LE",
            "word16Host",
            "word32Host",
            "word64Host",
            "int16Host",
            "int32Host",
            "int64Host",
            "wordHex",
            "word8Hex",
            "word16Hex",
            "word32Hex",
            "word64Hex",
            "word8HexFixed",
            "word16HexFixed",
            "word32HexFixed",
            "word64HexFixed",
            "floatBE",
            "doubleBE",
            "floatLE",
            "doubleLE",
            "floatHost",
            "doubleHost",
        ],
        // === Data.Text family ===
        "Data.Text" => &[
            "Text",
            "pack",
            "unpack",
            "singleton",
            "null",
            "length",
            "head",
            "last",
            "tail",
            "init",
            "cons",
            "snoc",
            "append",
            "uncons",
            "unsnoc",
            "map",
            "intercalate",
            "intersperse",
            "foldl",
            "foldl'",
            "foldr",
            "concat",
            "concatMap",
            "any",
            "all",
            "filter",
            "take",
            "takeWhile",
            "takeWhileEnd",
            "drop",
            "dropWhile",
            "dropWhileEnd",
            "dropAround",
            "strip",
            "stripStart",
            "stripEnd",
            "stripPrefix",
            "stripSuffix",
            "split",
            "splitAt",
            "splitOn",
            "breakOn",
            "breakOnEnd",
            "break",
            "span",
            "words",
            "unwords",
            "lines",
            "unlines",
            "isPrefixOf",
            "isSuffixOf",
            "isInfixOf",
            "replace",
            "toLower",
            "toUpper",
            "toTitle",
            "toCaseFold",
            "justifyLeft",
            "justifyRight",
            "center",
            "replicate",
            "empty",
            "index",
            "findIndex",
            "count",
            "zip",
            "zipWith",
            "reverse",
            "transpose",
            "chunksOf",
            "compareLength",
            "groupBy",
            "group",
            "breakOnAll",
            "tails",
            "inits",
            "scanl",
            "scanr",
        ],
        "Data.Text.Encoding" => &[
            "encodeUtf8",
            "decodeUtf8",
            "decodeUtf8'",
            "decodeUtf8With",
            "decodeLatin1",
            "encodeUtf16BE",
            "encodeUtf16LE",
            "decodeUtf16BE",
            "decodeUtf16LE",
        ],
        "Data.Text.Encoding.Error" => &[
            "lenientDecode",
            "strictDecode",
            "ignore",
            "replace",
            "UnicodeException",
            "OnDecodeError",
            "OnError",
            "DecodeError",
        ],
        "Data.Text.Read" => &[
            "decimal",
            "hexadecimal",
            "signed",
            "rational",
            "double",
            "Reader",
        ],
        // === Monad transformers ===
        "Control.Monad.Except" | "Control.Monad.Trans.Except" => &[
            "ExceptT",
            "Except",
            "runExceptT",
            "runExcept",
            "mapExceptT",
            "withExceptT",
            "throwError",
            "catchError",
            "MonadError",
            "liftEither",
        ],
        "Control.Monad.State"
        | "Control.Monad.State.Strict"
        | "Control.Monad.State.Lazy"
        | "Control.Monad.State.Class"
        | "Control.Monad.Trans.State"
        | "Control.Monad.Trans.State.Strict"
        | "Control.Monad.Trans.State.Lazy" => &[
            "StateT",
            "State",
            "runStateT",
            "runState",
            "evalStateT",
            "evalState",
            "execStateT",
            "execState",
            "get",
            "put",
            "modify",
            "modify'",
            "gets",
            "MonadState",
            "state",
        ],
        "Control.Monad.Reader" | "Control.Monad.Reader.Class" | "Control.Monad.Trans.Reader" => &[
            "ReaderT",
            "Reader",
            "runReaderT",
            "runReader",
            "mapReaderT",
            "withReaderT",
            "ask",
            "asks",
            "local",
            "MonadReader",
            "reader",
        ],
        "Control.Monad.Writer"
        | "Control.Monad.Writer.Strict"
        | "Control.Monad.Writer.Lazy"
        | "Control.Monad.Writer.Class"
        | "Control.Monad.Trans.Writer"
        | "Control.Monad.Trans.Writer.Strict"
        | "Control.Monad.Trans.Writer.Lazy" => &[
            "WriterT",
            "Writer",
            "runWriterT",
            "runWriter",
            "execWriterT",
            "execWriter",
            "tell",
            "listen",
            "pass",
            "censor",
            "MonadWriter",
            "writer",
        ],
        "Control.Monad.Trans" | "Control.Monad.Trans.Class" => &["lift", "MonadTrans"],
        "Control.Monad.IO.Class" => &["liftIO", "MonadIO"],
        "Control.Monad.Identity" | "Data.Functor.Identity" => &["Identity", "runIdentity"],
        "Control.Monad.Fail" => &["MonadFail", "fail"],
        // === ByteString ===
        "Data.ByteString" => &[
            "ByteString",
            "empty",
            "singleton",
            "pack",
            "unpack",
            "null",
            "length",
            "cons",
            "snoc",
            "append",
            "head",
            "tail",
            "last",
            "init",
            "map",
            "reverse",
            "foldl",
            "foldl'",
            "foldr",
            "concat",
            "concatMap",
            "any",
            "all",
            "filter",
            "take",
            "drop",
            "splitAt",
            "isPrefixOf",
            "isSuffixOf",
            "isInfixOf",
            "intercalate",
            "breakSubstring",
            "stripPrefix",
            "readFile",
            "writeFile",
            "hPut",
            "hPutStr",
            "hGet",
            "hGetContents",
            "hGetLine",
            "putStr",
            "getContents",
            "elemIndex",
            "findIndex",
            "elem",
            "find",
            "index",
            "breakByte",
            "span",
            "break",
            "dropWhile",
            "takeWhile",
            "strip",
            "intersperse",
            "zip",
            "unzip",
            "copy",
            "replicate",
            "unfoldr",
            "unfoldrN",
            "sort",
            "group",
            "groupBy",
            "tails",
            "inits",
            "transpose",
            "interact",
            "hGetSome",
            "hPutNonBlocking",
        ],
        "Data.ByteString.Char8" => &[
            "ByteString",
            "pack",
            "unpack",
            "lines",
            "unlines",
            "words",
            "readFile",
            "writeFile",
            "hPutStrLn",
            "hPutStr",
            "hGetContents",
            "hGet",
            "hPut",
            "break",
            "span",
            "drop",
            "take",
            "length",
            "null",
            "empty",
            "singleton",
            "append",
            "concat",
            "map",
            "filter",
            "elem",
            "find",
            "intercalate",
            "intersperse",
            "dropWhile",
            "takeWhile",
            "strip",
            "cons",
            "snoc",
            "head",
            "tail",
            "init",
            "last",
            "index",
            "isPrefixOf",
            "isSuffixOf",
            "isInfixOf",
            "any",
            "all",
            "foldr",
            "foldl",
            "foldl'",
        ],
        // === System modules ===
        "System.FilePath" | "System.FilePath.Posix" => &[
            "takeExtension",
            "takeBaseName",
            "takeDirectory",
            "takeFileName",
            "replaceExtension",
            "dropExtension",
            "addExtension",
            "hasExtension",
            "splitExtension",
            "splitFileName",
            "splitPath",
            "joinPath",
            "combine",
            "</>",
            "<.>",
            "splitDirectories",
            "normalise",
            "makeRelative",
            "isRelative",
            "isAbsolute",
            "isValid",
            "pathSeparator",
            "searchPathSeparator",
            "extSeparator",
            "isPathSeparator",
            "isExtSeparator",
            "dropTrailingPathSeparator",
        ],
        "System.IO.Error" => &[
            "isDoesNotExistError",
            "isPermissionError",
            "isAlreadyExistsError",
            "ioError",
            "userError",
            "catchIOError",
            "tryIOError",
            "mkIOError",
            "IOErrorType",
        ],
        "System.IO.Unsafe" => &[
            "unsafePerformIO",
            "unsafeDupablePerformIO",
            "unsafeInterleaveIO",
        ],
        "System.Process" => &[
            "readProcess",
            "readProcessWithExitCode",
            "readCreateProcess",
            "readCreateProcessWithExitCode",
            "callProcess",
            "callCommand",
            "createProcess",
            "createProcess_",
            "proc",
            "shell",
            "CreateProcess",
            "ProcessHandle",
            // CreateProcess record fields
            "cmdspec",
            "cwd",
            "env",
            "std_in",
            "std_out",
            "std_err",
            "close_fds",
            "create_group",
            "delegate_ctlc",
            "detach_console",
            "create_new_console",
            "new_session",
            "child_group",
            "child_user",
            "use_process_jobs",
            // StdStream type and constructors
            "StdStream",
            "Inherit",
            "UseHandle",
            "CreatePipe",
            "NoStream",
            // CmdSpec type and constructors
            "CmdSpec",
            "ShellCommand",
            "RawCommand",
            "waitForProcess",
            "terminateProcess",
            "interruptProcessGroupOf",
            "withCreateProcess",
            "cleanupProcess",
            "getProcessExitCode",
            "getPid",
        ],
        "System.Info" => &["os", "arch", "compilerName", "compilerVersion"],
        "System.Timeout" => &["timeout"],
        // === Data modules ===
        "Data.Functor" => &["<$>", "<$!>", "$>", "<&>", "void", "fmap"],
        "Data.Default" | "Data.Default.Class" => &["Default", "def"],
        "Data.Typeable" => &["Typeable", "typeOf", "typeRep", "cast", "Proxy"],
        "Data.Data" => &[
            "Data",
            "toConstr",
            "showConstr",
            "dataTypeOf",
            "dataTypeName",
        ],
        "Data.Coerce" => &["coerce", "Coercible"],
        "Data.STRef" => &[
            "STRef",
            "newSTRef",
            "readSTRef",
            "writeSTRef",
            "modifySTRef",
            "modifySTRef'",
        ],
        "Data.List.NonEmpty" => &[
            "NonEmpty", "head", "tail", "last", "init", "toList", "nonEmpty", "fromList", "(<>)",
            "sconcat", "stimes", "map", "cons", "uncons", "unfoldr", "sort", "reverse", "length",
            "take", "drop", "filter", "group", "groupBy", "zip", "zipWith",
        ],
        "Data.Complex" => &[
            "Complex",
            "realPart",
            "imagPart",
            "mkPolar",
            "cis",
            "polar",
            "magnitude",
            "phase",
            "conjugate",
        ],
        "Data.Ratio" => &[
            "Ratio",
            "Rational",
            "(%)",
            "numerator",
            "denominator",
            "approxRational",
        ],
        "Data.Int" => &["Int", "Int8", "Int16", "Int32", "Int64"],
        "Data.Fixed" => &[
            "Fixed",
            "HasResolution",
            "resolution",
            "Pico",
            "Nano",
            "Micro",
            "Milli",
            "Centi",
            "Deci",
            "Uni",
        ],
        "Data.Dynamic" => &[
            "Dynamic",
            "toDyn",
            "fromDyn",
            "fromDynamic",
            "dynApply",
            "dynTypeRep",
        ],
        "Data.Unique" => &["Unique", "newUnique", "hashUnique"],
        "Data.Version" => &[
            "Version",
            "showVersion",
            "parseVersion",
            "makeVersion",
            "versionBranch",
        ],
        // === Aeson ===
        "Data.Aeson" | "Data.Aeson.Types" => &[
            "encode",
            "decode",
            "decode'",
            "eitherDecode",
            "eitherDecode'",
            "decodeStrict",
            "decodeStrict'",
            "eitherDecodeStrict",
            "eitherDecodeStrict'",
            "object",
            ".=",
            ".:",
            ".:?",
            ".!=",
            "Value",
            "Object",
            "Array",
            "String",
            "Number",
            "Bool",
            "Null",
            "toJSON",
            "fromJSON",
            "parseJSON",
            "ToJSON",
            "FromJSON",
            "Result",
            "Success",
            "Error",
            "Parser",
            "parse",
            "parseEither",
            "parseMaybe",
            "pairs",
            "withObject",
            "withArray",
            "withText",
            "withScientific",
            "withBool",
            "typeMismatch",
            "prependFailure",
            "modifyFailure",
            // Re-exports from Data.Aeson.TH
            "deriveJSON",
            "deriveToJSON",
            "deriveFromJSON",
            "defaultOptions",
            "fieldLabelModifier",
            "constructorTagModifier",
            "omitNothingFields",
            "allNullaryToStringTag",
            "Options",
            "SumEncoding",
            "camelTo2",
        ],
        // === Network ===
        "Network.URI" => &[
            "URI",
            "parseURI",
            "parseURIReference",
            "escapeURIString",
            "unEscapeString",
            "isAllowedInURI",
            "isUnescapedInURI",
            "uriScheme",
            "uriAuthority",
            "uriPath",
            "uriQuery",
            "uriFragment",
            "nullURI",
            "relativeTo",
            "nonStrictRelativeTo",
            "isURI",
            "isAbsoluteURI",
            "isRelativeReference",
            "isURIReference",
            "uriRegName",
            "URIAuth",
            "uriUserInfo",
            "uriPort",
        ],
        // === Text modules ===
        "Text.Printf" => &[
            "printf",
            "hPrintf",
            "sprintf",
            "formatString",
            "PrintfArg",
            "HPrintfType",
            "PrintfType",
        ],
        "Text.ParserCombinators.ReadP" => &[
            "ReadP",
            "get",
            "look",
            "pfail",
            "eof",
            "satisfy",
            "char",
            "string",
            "munch",
            "munch1",
            "skipSpaces",
            "choice",
            "between",
            "option",
            "optional",
            "many",
            "many1",
            "sepBy",
            "sepBy1",
            "readP_to_S",
            "readS_to_P",
        ],
        // === Control modules ===
        "Control.Monad.ST" => &["ST", "runST", "stToIO"],
        "Control.Arrow" => &[
            "Arrow",
            "ArrowChoice",
            "ArrowApply",
            "ArrowZero",
            "ArrowPlus",
            "arr",
            "first",
            "second",
            "left",
            "right",
            "(***)",
            "(&&&)",
            "(>>>)",
            "(<<<)",
            "|||",
            "+++",
            ">>^",
            "^>>",
            "<<^",
            "^<<",
            "returnA",
        ],
        "Control.Category" => &["Category", "id", "(.)", "<<<", ">>>"],
        "Control.Concurrent.STM" | "Control.Monad.STM" => &[
            "STM",
            "atomically",
            "retry",
            "orElse",
            "throwSTM",
            "catchSTM",
            "TVar",
            "newTVar",
            "newTVarIO",
            "readTVar",
            "readTVarIO",
            "writeTVar",
            "modifyTVar",
            "modifyTVar'",
            "TMVar",
            "newTMVar",
            "newEmptyTMVar",
            "takeTMVar",
            "putTMVar",
            "readTMVar",
            "TQueue",
            "newTQueue",
            "readTQueue",
            "writeTQueue",
            "TChan",
            "newTChan",
            "readTChan",
            "writeTChan",
        ],
        // === GHC modules ===
        "GHC.Stack" => &[
            "HasCallStack",
            "CallStack",
            "callStack",
            "prettyCallStack",
            "getCallStack",
            "withFrozenCallStack",
        ],
        "GHC.Exts" | "GHC.Prim" => &[
            "IsList",
            "Item",
            "fromList",
            "toList",
            "fromListN",
            "the",
            "sortWith",
            "groupWith",
            "inline",
            "lazy",
            "oneShot",
            "coerce",
            "IsString",
            "fromString",
        ],
        "GHC.TypeLits" | "GHC.TypeNats" => &[
            "Nat",
            "Symbol",
            "KnownNat",
            "KnownSymbol",
            "natVal",
            "symbolVal",
            "SomeNat",
            "SomeSymbol",
            "someNatVal",
            "someSymbolVal",
            "TypeError",
            "ErrorMessage",
        ],
        "GHC.IO" | "GHC.IO.Handle" => &["IO", "Handle", "hFlush", "hClose"],
        "GHC.Base" => &[
            "id",
            "const",
            "flip",
            "($)",
            "(.)",
            "seq",
            "map",
            "(++)",
            "foldr",
            "otherwise",
        ],
        "GHC.Show" => &[
            "Show",
            "show",
            "showString",
            "showChar",
            "showParen",
            "showsPrec",
        ],
        "GHC.Read" => &[
            "Read",
            "read",
            "reads",
            "readPrec",
            "readMaybe",
            "readEither",
        ],
        "GHC.Num" => &[
            "Num",
            "+",
            "-",
            "*",
            "negate",
            "abs",
            "signum",
            "fromInteger",
        ],
        "GHC.Real" => &[
            "Integral",
            "Fractional",
            "Real",
            "RealFrac",
            "div",
            "mod",
            "quot",
            "rem",
            "divMod",
            "quotRem",
            "toInteger",
            "toRational",
            "fromIntegral",
            "realToFrac",
            "(%)",
            "numerator",
            "denominator",
        ],
        "GHC.Enum" => &[
            "Enum",
            "Bounded",
            "succ",
            "pred",
            "toEnum",
            "fromEnum",
            "enumFrom",
            "enumFromThen",
            "enumFromTo",
            "enumFromThenTo",
            "minBound",
            "maxBound",
        ],
        "GHC.Float" => &[
            "Float",
            "Double",
            "Floating",
            "RealFloat",
            "pi",
            "exp",
            "log",
            "sqrt",
            "sin",
            "cos",
            "tan",
            "asin",
            "acos",
            "atan",
            "sinh",
            "cosh",
            "tanh",
            "float2Double",
            "double2Float",
        ],
        "GHC.Err" => &["error", "undefined", "errorWithoutStackTrace"],
        "GHC.List" => &[
            "map",
            "filter",
            "head",
            "tail",
            "last",
            "init",
            "null",
            "length",
            "reverse",
            "foldl",
            "foldr",
            "zip",
            "zipWith",
            "take",
            "drop",
            "splitAt",
            "iterate",
            "repeat",
            "replicate",
            "cycle",
        ],
        "GHC.Word" | "GHC.Int" => &[
            "Word", "Word8", "Word16", "Word32", "Word64", "Int", "Int8", "Int16", "Int32", "Int64",
        ],
        "GHC.Ptr" | "GHC.ForeignPtr" => &[
            "Ptr",
            "nullPtr",
            "castPtr",
            "plusPtr",
            "minusPtr",
            "ForeignPtr",
            "newForeignPtr",
            "withForeignPtr",
        ],
        "GHC.Conc" => &[
            "forkIO",
            "killThread",
            "threadDelay",
            "myThreadId",
            "throwTo",
        ],
        "GHC.IORef" | "GHC.STRef" => &[
            "IORef",
            "newIORef",
            "readIORef",
            "writeIORef",
            "modifyIORef",
            "modifyIORef'",
            "STRef",
            "newSTRef",
            "readSTRef",
            "writeSTRef",
        ],
        // === Foreign modules ===
        "Foreign" | "Foreign.Ptr" => &[
            "Ptr",
            "nullPtr",
            "castPtr",
            "plusPtr",
            "minusPtr",
            "FunPtr",
            "nullFunPtr",
            "castFunPtr",
        ],
        "Foreign.C" | "Foreign.C.Types" => &[
            "CInt",
            "CUInt",
            "CLong",
            "CULong",
            "CLLong",
            "CULLong",
            "CFloat",
            "CDouble",
            "CSize",
            "CChar",
            "CUChar",
            "CSChar",
            "CShort",
            "CUShort",
            "CWchar",
            "CIntPtr",
            "CUIntPtr",
            "CBool",
            // Re-exports from Foreign.C.String
            "CString",
            "CStringLen",
            "withCString",
            "peekCString",
            "newCString",
            // Re-exports from Foreign.C.Error
            "Errno",
            "getErrno",
            "resetErrno",
            "throwErrno",
            "throwErrnoIf",
            "throwErrnoIfMinus1",
            "throwErrnoIfNull",
            "throwErrnoPath",
            "errnoToIOError",
            "isValidErrno",
            "eOK",
            "e2BIG",
            "eACCES",
            "eADDRINUSE",
            "eAGAIN",
            "eALREADY",
            "eBADF",
            "eBUSY",
            "eCHILD",
            "eCONNABORTED",
            "eCONNREFUSED",
            "eCONNRESET",
            "eDEADLK",
            "eEXIST",
            "eFAULT",
            "eFBIG",
            "eHOSTUNREACH",
            "eINPROGRESS",
            "eINTR",
            "eINVAL",
            "eIO",
            "eISCONN",
            "eISDIR",
            "eMFILE",
            "eMLINK",
            "eMSGSIZE",
            "eNAMETOOLONG",
            "eNETDOWN",
            "eNETUNREACH",
            "eNFILE",
            "eNOBUFS",
            "eNODEV",
            "eNOENT",
            "eNOEXEC",
            "eNOLCK",
            "eNOMEM",
            "eNOMSG",
            "eNOSPC",
            "eNOSYS",
            "eNOTCONN",
            "eNOTDIR",
            "eNOTEMPTY",
            "eNOTSOCK",
            "eNOTTY",
            "eNXIO",
            "ePERM",
            "ePIPE",
            "eRANGE",
            "eROFS",
            "eSPIPE",
            "eSRCH",
            "eTIMEDOUT",
            "eWOULDBLOCK",
            "eXDEV",
        ],
        "Foreign.C.String" => &[
            "CString",
            "CStringLen",
            "withCString",
            "peekCString",
            "newCString",
            "withCStringLen",
            "peekCStringLen",
        ],
        "Foreign.C.Error" => &[
            "Errno",
            "getErrno",
            "resetErrno",
            "throwErrno",
            "throwErrnoIf",
            "throwErrnoIf_",
            "throwErrnoIfRetry",
            "throwErrnoIfMinus1",
            "throwErrnoIfMinus1_",
            "throwErrnoIfNull",
            "throwErrnoIfRetryMayBlock",
            "throwErrnoPath",
            "throwErrnoPathIf",
            "throwErrnoPathIfNull",
            "throwErrnoPathIfMinus1",
            "errnoToIOError",
            "eOK",
            "e2BIG",
            "eACCES",
            "eADDRINUSE",
            "eADDRNOTAVAIL",
            "eADV",
            "eAFNOSUPPORT",
            "eAGAIN",
            "eALREADY",
            "eBADF",
            "eBADMSG",
            "eBADRPC",
            "eBUSY",
            "eCHILD",
            "eCOMM",
            "eCONNABORTED",
            "eCONNREFUSED",
            "eCONNRESET",
            "eDEADLK",
            "eDESTADDRREQ",
            "eDIRTY",
            "eDOM",
            "eDQUOT",
            "eEXIST",
            "eFAULT",
            "eFBIG",
            "eFTYPE",
            "eHOSTDOWN",
            "eHOSTUNREACH",
            "eIDRM",
            "eILSEQ",
            "eINPROGRESS",
            "eINTR",
            "eINVAL",
            "eIO",
            "eISCONN",
            "eISDIR",
            "eLOOP",
            "eMFILE",
            "eMLINK",
            "eMSGSIZE",
            "eMULTIHOP",
            "eNAMETOOLONG",
            "eNETDOWN",
            "eNETRESET",
            "eNETUNREACH",
            "eNFILE",
            "eNOBUFS",
            "eNODATA",
            "eNODEV",
            "eNOENT",
            "eNOEXEC",
            "eNOLCK",
            "eNOLINK",
            "eNOMEM",
            "eNOMSG",
            "eNONET",
            "eNOPROTOOPT",
            "eNOSPC",
            "eNOSR",
            "eNOSTR",
            "eNOSYS",
            "eNOTBLK",
            "eNOTCONN",
            "eNOTDIR",
            "eNOTEMPTY",
            "eNOTSOCK",
            "eNOTSUP",
            "eNOTTY",
            "eNXIO",
            "eOPNOTSUPP",
            "ePERM",
            "ePFNOSUPPORT",
            "ePIPE",
            "ePROCLIM",
            "ePROCUNAVAIL",
            "ePROGMISMATCH",
            "ePROGUNAVAIL",
            "ePROTO",
            "ePROTONOSUPPORT",
            "ePROTOTYPE",
            "eRANGE",
            "eREMCHG",
            "eREMOTE",
            "eROFS",
            "eRPCMISMATCH",
            "eRRPCERR",
            "eSHUTDOWN",
            "eSOCKTNOSUPPORT",
            "eSPIPE",
            "eSRCH",
            "eSTALE",
            "eTIME",
            "eTIMEDOUT",
            "eTOOMANYREFS",
            "eTXTBSY",
            "eUSERS",
            "eWOULDBLOCK",
            "eXDEV",
            "isValidErrno",
        ],
        "Foreign.Storable" => &[
            "Storable",
            "sizeOf",
            "alignment",
            "peek",
            "poke",
            "peekByteOff",
            "pokeByteOff",
            "peekElemOff",
            "pokeElemOff",
        ],
        "Foreign.Marshal" | "Foreign.Marshal.Alloc" => &[
            "malloc",
            "free",
            "alloca",
            "allocaBytes",
            "mallocBytes",
            "reallocBytes",
            "finalizerFree",
        ],
        "Foreign.ForeignPtr" => &[
            "ForeignPtr",
            "newForeignPtr",
            "newForeignPtr_",
            "withForeignPtr",
            "mallocForeignPtr",
            "finalizeForeignPtr",
        ],
        // === Numeric/Debug/Unsafe ===
        "Numeric" => &[
            "showInt",
            "showHex",
            "showOct",
            "showFloat",
            "showFFloat",
            "showEFloat",
            "showGFloat",
            "readInt",
            "readHex",
            "readOct",
            "readFloat",
            "readDec",
            "fromRat",
        ],
        "Debug.Trace" => &[
            "trace",
            "traceShow",
            "traceShowId",
            "traceIO",
            "traceM",
            "traceShowM",
        ],
        "Unsafe.Coerce" => &["unsafeCoerce"],
        "Data.Text.Lazy.Encoding" => &["encodeUtf8", "decodeUtf8", "decodeUtf8'", "decodeUtf8With"],
        // =====================================================================
        // Pandoc ecosystem stubs
        // =====================================================================
        "Text.DocLayout" => &[
            "Doc",
            "HasChars",
            "empty",
            "blankline",
            "space",
            "cr",
            "text",
            "char",
            "nest",
            "hang",
            "vcat",
            "hcat",
            "hsep",
            "vsep",
            "chomp",
            "render",
            "literal",
            "height",
            "minOffset",
            "offset",
            "realLength",
            "prefixed",
            "flush",
            "afterBreak",
            "beforeNonBlank",
            "nowrap",
            "lblock",
            "cblock",
            "rblock",
            "isEmpty",
            "braces",
            "brackets",
            "parens",
            "quotes",
            "doubleQuotes",
            "$$",
            "$+$",
            "<+>",
            "<>",
            "</>",
            "inside",
            "charWidth",
            "updateColumn",
            "blanklines",
            // Constructors (for pattern matching)
            "AfterBreak",
            "BreakingSpace",
            "NewLine",
            "Concat",
            "Text",
            "vfill",
            "hfill",
            // ANSI styling and color (rendered as escape sequences)
            "bold",
            "italic",
            "underlined",
            "strikeout",
            "fg",
            "bg",
            "link",
            "red",
            "green",
            "blue",
            "yellow",
            "cyan",
            "magenta",
            "white",
            "black",
            "renderANSI",
            "replicateChar",
        ],
        "Text.Pandoc.Definition" => &[
            // Core types
            "Pandoc",
            "Meta",
            "MetaValue",
            "Block",
            "Inline",
            "Alignment",
            "ListAttributes",
            "ListNumberStyle",
            "ListNumberDelim",
            "Format",
            "Attr",
            "Target",
            "TableHead",
            "TableBody",
            "TableFoot",
            "Caption",
            "Cell",
            "Row",
            "ColSpec",
            "ColWidth",
            "RowSpan",
            "ColSpan",
            "RowHeadColumns",
            "Citation",
            "CitationMode",
            "MathType",
            "QuoteType",
            // Block constructors
            "Plain",
            "Para",
            "LineBlock",
            "CodeBlock",
            "RawBlock",
            "BlockQuote",
            "OrderedList",
            "BulletList",
            "DefinitionList",
            "Header",
            "HorizontalRule",
            "Table",
            "Figure",
            "Div",
            "Null",
            // Inline constructors
            "Str",
            "Emph",
            "Underline",
            "Strong",
            "Strikeout",
            "Superscript",
            "Subscript",
            "SmallCaps",
            "Quoted",
            "Cite",
            "Code",
            "Space",
            "SoftBreak",
            "LineBreak",
            "Math",
            "RawInline",
            "Link",
            "Image",
            "Note",
            "Span",
            // MetaValue constructors
            "MetaMap",
            "MetaList",
            "MetaString",
            "MetaBool",
            "MetaInlines",
            "MetaBlocks",
            // Enum constructors
            "SingleQuote",
            "DoubleQuote",
            "DisplayMath",
            "InlineMath",
            "AuthorInText",
            "SuppressAuthor",
            "NormalCitation",
            "AlignLeft",
            "AlignRight",
            "AlignCenter",
            "AlignDefault",
            "DefaultStyle",
            "Example",
            "Decimal",
            "LowerRoman",
            "UpperRoman",
            "LowerAlpha",
            "UpperAlpha",
            "DefaultDelim",
            "Period",
            "OneParen",
            "TwoParens",
            "ColWidthDefault",
            // Pattern synonyms
            "SimpleFigure",
            // Record fields
            "citationId",
            "citationPrefix",
            "citationSuffix",
            "citationMode",
            "citationHash",
            "citationNoteNum",
            // Functions
            "nullAttr",
            "nullMeta",
            "pandocTypesVersion",
            "lookupMeta",
            "docTitle",
            "docAuthors",
            "docDate",
            "lookupContext",
        ],
        "Text.Pandoc.Builder" => &[
            "doc",
            "setTitle",
            "setAuthors",
            "setDate",
            "setMeta",
            "deleteMeta",
            "HasMeta",
            "text",
            "str",
            "emph",
            "underline",
            "strong",
            "strikeout",
            "superscript",
            "subscript",
            "smallcaps",
            "singleQuoted",
            "doubleQuoted",
            "cite",
            "codeWith",
            "code",
            "space",
            "softbreak",
            "linebreak",
            "math",
            "displayMath",
            "rawInline",
            "link",
            "linkWith",
            "image",
            "imageWith",
            "note",
            "spanWith",
            "trimInlines",
            "para",
            "plain",
            "lineBlock",
            "codeBlockWith",
            "codeBlock",
            "rawBlock",
            "blockQuote",
            "orderedList",
            "orderedListWith",
            "bulletList",
            "definitionList",
            "header",
            "headerWith",
            "horizontalRule",
            "table",
            "tableWith",
            "simpleTable",
            "figure",
            "figureWith",
            "divWith",
            "Blocks",
            "Inlines",
            "Many",
            "toList",
            "fromList",
            "singleton",
            "isNull",
            "<>",
            "unMany",
            // Table utilities (from pandoc-types Text.Pandoc.Builder)
            "emptyCell",
            "simpleCell",
            "emptyCaption",
            "simpleFigureWith",
            "clipRows",
            "clipColumns",
            "cell",
            "cellWith",
            "simpleCaption",
            "simpleRow",
            "toRow",
            "toColSpec",
            "normalizeTableHead",
            "normalizeTableBody",
            "normalizeTableFoot",
            // Re-exports from Text.Pandoc.Definition
            "Pandoc",
            "Meta",
            "MetaValue",
            "Block",
            "Inline",
            "Alignment",
            "ListAttributes",
            "ListNumberStyle",
            "ListNumberDelim",
            "Format",
            "Attr",
            "Target",
            "TableHead",
            "TableBody",
            "TableFoot",
            "Caption",
            "Cell",
            "Row",
            "ColSpec",
            "ColWidth",
            "RowSpan",
            "ColSpan",
            "RowHeadColumns",
            "Citation",
            "CitationMode",
            "MathType",
            "QuoteType",
            "Plain",
            "Para",
            "LineBlock",
            "CodeBlock",
            "RawBlock",
            "BlockQuote",
            "OrderedList",
            "BulletList",
            "DefinitionList",
            "Header",
            "HorizontalRule",
            "Table",
            "Figure",
            "Div",
            "Null",
            "Str",
            "Emph",
            "Underline",
            "Strong",
            "Strikeout",
            "Superscript",
            "Subscript",
            "SmallCaps",
            "Quoted",
            "Cite",
            "Code",
            "Space",
            "SoftBreak",
            "LineBreak",
            "Math",
            "RawInline",
            "Link",
            "Image",
            "Note",
            "Span",
            "MetaMap",
            "MetaList",
            "MetaString",
            "MetaBool",
            "MetaInlines",
            "MetaBlocks",
            "SingleQuote",
            "DoubleQuote",
            "DisplayMath",
            "InlineMath",
            "AuthorInText",
            "SuppressAuthor",
            "NormalCitation",
            "AlignLeft",
            "AlignRight",
            "AlignCenter",
            "AlignDefault",
            "DefaultStyle",
            "Example",
            "Decimal",
            "LowerRoman",
            "UpperRoman",
            "LowerAlpha",
            "UpperAlpha",
            "DefaultDelim",
            "Period",
            "OneParen",
            "TwoParens",
            "ColWidthDefault",
            "nullAttr",
            "nullMeta",
            "lookupMeta",
            "docTitle",
            "docAuthors",
            "docDate",
            // Pattern synonyms
            "SimpleFigure",
            // Citation record fields
            "citationId",
            "citationPrefix",
            "citationSuffix",
            "citationMode",
            "citationHash",
            "citationNoteNum",
            // ToMetaValue class
            "ToMetaValue",
            "toMetaValue",
        ],
        "Text.Pandoc.Walk" => &["Walkable", "walk", "walkM", "query", "queryM"],
        "Text.Pandoc.MediaBag" => &[
            "MediaBag",
            "emptyMediaBag",
            "insertMedia",
            "lookupMedia",
            "mediaDirectory",
            "mediaItems",
        ],
        "Text.Pandoc.Translations" => &["Term", "Translations", "lookupTerm", "readTranslations"],
        // =====================================================================
        // Parsec stubs
        // =====================================================================
        "Text.Parsec" => &[
            // Re-exports from sub-modules
            "Parsec",
            "ParsecT",
            "Stream",
            "parse",
            "runParser",
            "runParserT",
            "try",
            "char",
            "string",
            "anyChar",
            "letter",
            "digit",
            "space",
            "spaces",
            "newline",
            "satisfy",
            "oneOf",
            "noneOf",
            "many1",
            "manyTill",
            "lookAhead",
            "notFollowedBy",
            "skipMany",
            "skipMany1",
            "option",
            "optionMaybe",
            "optional",
            "choice",
            "count",
            "between",
            "sepBy",
            "sepBy1",
            "endBy",
            "endBy1",
            "eof",
            "sepEndBy",
            "sepEndBy1",
            "getState",
            "putState",
            "setState",
            "updateState",
            "modifyState",
            "getPosition",
            "setPosition",
            "getInput",
            "setInput",
            "SourcePos",
            "sourceLine",
            "sourceColumn",
            "sourceName",
            "<?>",
            "label",
            "unexpected",
            "many",
            "skipMany",
            "ParseError",
            "updatePosChar",
            "tokenPrim",
            "errorPos",
            "errorMessages",
        ],
        "Text.Parsec.Char" => &[
            "char",
            "string",
            "anyChar",
            "letter",
            "digit",
            "space",
            "spaces",
            "newline",
            "satisfy",
            "oneOf",
            "noneOf",
            "upper",
            "lower",
            "alphaNum",
            "hexDigit",
            "octDigit",
            "tab",
            "crlf",
            "endOfLine",
        ],
        "Text.Parsec.Combinator" => &[
            "many1",
            "manyTill",
            "lookAhead",
            "notFollowedBy",
            "skipMany",
            "skipMany1",
            "option",
            "optionMaybe",
            "optional",
            "try",
            "choice",
            "count",
            "between",
            "sepBy",
            "sepBy1",
            "endBy",
            "endBy1",
            "chainl1",
            "chainr1",
            "eof",
            "anyToken",
            "sepEndBy",
            "sepEndBy1",
        ],
        "Text.Parsec.Prim" => &[
            "Parsec",
            "ParsecT",
            "Stream",
            "runParser",
            "runParserT",
            "parse",
            "getState",
            "putState",
            "updateState",
            "modifyState",
            "getPosition",
            "setPosition",
            "getInput",
            "setInput",
            "try",
            "unexpected",
            "label",
            "<?>",
            "token",
            "tokens",
            "tokenPrim",
            "many",
            "skipMany",
            "parserFail",
        ],
        "Text.Parsec.Pos" => &[
            "SourcePos",
            "sourceLine",
            "sourceColumn",
            "sourceName",
            "newPos",
            "initialPos",
            "incSourceLine",
            "incSourceColumn",
            "setSourceLine",
            "setSourceColumn",
            "setSourceName",
            "SourceName",
            "updatePosChar",
            "updatePosString",
        ],
        "Text.Parsec.Error" => &[
            "ParseError",
            "Message",
            "errorPos",
            "errorMessages",
            "showErrorMessages",
            "messageString",
            "newErrorMessage",
            "newErrorUnknown",
        ],
        "Text.Parsec.String" | "Text.Parsec.Text" => &["Parser", "GenParser"],
        // =====================================================================
        // HTML TagSoup stubs
        // =====================================================================
        "Text.HTML.TagSoup" => &[
            "Tag",
            "TagOpen",
            "TagClose",
            "TagText",
            "TagComment",
            "TagWarning",
            "TagPosition",
            "parseTags",
            "parseTagsOptions",
            "renderTags",
            "renderTagsOptions",
            "isTagOpen",
            "isTagClose",
            "isTagText",
            "isTagComment",
            "isTagWarning",
            "isTagPosition",
            "fromAttrib",
            "fromTagText",
            "innerText",
            "sections",
            "partitions",
            "~==",
            "~/=",
            // RenderOptions
            "RenderOptions",
            "renderOptions",
            "optMinimize",
            "optRawTag",
            "optEscape",
        ],
        "Text.HTML.TagSoup.Tree" => &[
            "TagTree",
            "tagTree",
            "flattenTree",
            "universeTree",
            "TagBranch",
            "TagLeaf",
        ],
        // =====================================================================
        // TagSoup Entity stubs
        // =====================================================================
        "Text.HTML.TagSoup.Entity" => &[
            "lookupEntity",
            "lookupNamedEntity",
            "lookupNumericEntity",
            "escapeXML",
            "xmlEntities",
            "htmlEntities",
        ],
        // =====================================================================
        // Commonmark stubs
        // =====================================================================
        "Commonmark" | "Commonmark.Entity" | "Commonmark.Extensions" => &[
            "lookupEntity",
            "pEntity",
            "unEntity",
            "commonmark",
            "defaultSyntaxSpec",
            "SyntaxSpec",
            "gfmExtensions",
            // HasAttributes class
            "HasAttributes",
            "addAttributes",
        ],
        // =====================================================================
        // Pandoc XML.Light stubs
        // =====================================================================
        "Text.Pandoc.XML.Light"
        | "Text.Pandoc.XML.Light.Types"
        | "Text.Pandoc.XML.Light.Proc"
        | "Text.Pandoc.XML.Light.Output" => &[
            "Element",
            "Content",
            "CData",
            "CDataKind",
            "QName",
            "Attr",
            "Line",
            "Node",
            "node",
            "elName",
            "elAttribs",
            "elContent",
            "elChildren",
            "elLine",
            "qName",
            "qURI",
            "qPrefix",
            "cdVerbatim",
            "cdData",
            "cdLine",
            "attrKey",
            "attrVal",
            "unqual",
            "blank_element",
            "blank_cdata",
            "add_attr",
            "add_attrs",
            "findAttr",
            "lookupAttr",
            "lookupAttrBy",
            "findElement",
            "findElements",
            "findChild",
            "findChildren",
            "filterChildren",
            "filterChildrenName",
            "filterChild",
            "filterChildName",
            "filterElement",
            "filterElements",
            "unode",
            "filterElementName",
            "filterElementsName",
            "findAttrBy",
            "strContent",
            "showElement",
            "showContent",
            "ppElement",
            "ppTopElement",
            "ppContent",
            "showTopElement",
            "showQName",
            "parseXML",
            "parseXMLElement",
            "parseXMLContents",
            "onlyElems",
            "elsByTag",
            "Elem",
            "Text",
            "CRef",
            "CDataText",
            "CDataVerbatim",
            "CDataRaw",
        ],
        // =====================================================================
        // Data.Bifunctor, Data.Tree, Data.Containers.ListUtils
        // =====================================================================
        "Data.Bifunctor" => &["Bifunctor", "bimap", "first", "second"],
        "Data.Tree" => &[
            "Tree",
            "Node",
            "Forest",
            "rootLabel",
            "subForest",
            "foldTree",
            "flatten",
            "levels",
            "unfoldTree",
            "unfoldForest",
            "drawTree",
            "drawForest",
        ],
        "Data.Containers.ListUtils" => &["nubOrd", "nubOrdOn", "nubInt", "nubIntOn"],
        // =====================================================================
        // Safe / Safe.Foldable stubs
        // =====================================================================
        "Safe" => &[
            "headMay",
            "headDef",
            "headNote",
            "tailMay",
            "tailDef",
            "tailNote",
            "tailSafe",
            "initMay",
            "initDef",
            "initNote",
            "initSafe",
            "lastMay",
            "lastDef",
            "lastNote",
            "minimumMay",
            "maximumMay",
            "readMay",
            "readDef",
            "readNote",
            "atMay",
            "atDef",
            "atNote",
            "fromJustDef",
            "fromJustNote",
            "toEnumMay",
            "succMay",
            "predMay",
            "lookupJustDef",
            "lookupJustNote",
            "findJustDef",
            "findJustNote",
            "abort",
        ],
        "Safe.Foldable" => &[
            "foldl1May",
            "foldr1May",
            "minimumMay",
            "maximumMay",
            "minimumByMay",
            "maximumByMay",
            "findJust",
            "findJustDef",
            "findJustNote",
        ],
        // =====================================================================
        // DocTemplates stubs
        // =====================================================================
        "Text.DocTemplates" => &[
            "Template",
            "Context",
            "Val",
            // Val constructors
            "SimpleVal",
            "MapVal",
            "ListVal",
            "BoolVal",
            "NullVal",
            "compileTemplate",
            "renderTemplate",
            "applyTemplate",
            "compileTemplateFile",
            "toVal",
            "fromVal",
            "toContext",
            "WithDefaultPartials",
            "WithPartials",
            "runWithDefaultPartials",
            "runWithPartials",
            "TemplateMonad",
            "TemplateTarget",
            "ToContext",
            "FromContext",
            "lookupContext",
        ],
        // =====================================================================
        // Skylighting stubs
        // =====================================================================
        "Skylighting" | "Skylighting.Types" => &[
            "Syntax",
            "SyntaxMap",
            "Token",
            "TokenType",
            "SourceLine",
            "TokenStyle",
            "Style",
            "Color",
            "defaultSyntaxMap",
            "lookupSyntax",
            "parseTheme",
            // Syntax record fields
            "sName",
            "sShortname",
            "sAuthor",
            "sVersion",
            "sLicense",
            "sExtensions",
            "sContexts",
            "defaultStyle",
            "pygments",
            "kate",
            "espresso",
            "tango",
            "haddock",
            "monochrome",
            "breezeDark",
            "zenburn",
            "formatHtmlBlock",
            "formatHtmlInline",
            "formatLaTeXBlock",
            "formatLaTeXInline",
            "formatConTeXtBlock",
            "formatConTeXtInline",
            "formatANSI",
            "styleToCss",
            "styleToLaTeX",
            "styleToConTeXt",
            "tokenize",
            "TokenizerConfig",
            "defaultTokenizerConfig",
            "syntaxesByExtension",
            "syntaxesByFilename",
            // FormatOptions and record fields
            "FormatOptions",
            "defaultFormatOpts",
            "startNumber",
            "lineAnchors",
            "numberLines",
            "lineIdPrefix",
            "codeClasses",
            "containerClasses",
            // Token type constructors
            "NormalTok",
            "KeywordTok",
            "DataTypeTok",
            "DecValTok",
            "BaseNTok",
            "FloatTok",
            "CharTok",
            "StringTok",
            "CommentTok",
            "OtherTok",
            "AlertTok",
            "FunctionTok",
            "RegionMarkerTok",
            "ErrorTok",
            "AnnotationTok",
            "AttributeTok",
            "InformationTok",
            "WarningTok",
            "ConstantTok",
            "SpecialCharTok",
            "VerbatimStringTok",
            "SpecialStringTok",
            "ImportTok",
            "DocumentationTok",
            "PreprocessorTok",
            "ExtensionTok",
            "OperatorTok",
            "BuiltInTok",
            "VariableTok",
            "ControlFlowTok",
            "CommentVarTok",
            // Style fields
            "backgroundColor",
            "defaultColor",
            "lineNumberColor",
            "lineNumberBackgroundColor",
            "tokenStyles",
        ],
        "Skylighting.Parser" => &[
            "addSyntaxDefinition",
            "parseSyntaxDefinition",
            "parseSyntaxDefinitionFromString",
        ],
        // =====================================================================
        // Data.Time stubs
        // =====================================================================
        "Data.Time" | "Data.Time.Clock" => &[
            "UTCTime",
            "NominalDiffTime",
            "DiffTime",
            "getCurrentTime",
            "addUTCTime",
            "diffUTCTime",
            "secondsToNominalDiffTime",
            "nominalDiffTimeToSeconds",
            // Re-exports from Data.Time.Calendar
            "Day",
            "fromGregorian",
            "toGregorian",
            "addDays",
            "diffDays",
            "showGregorian",
            "addGregorianMonthsClip",
            "addGregorianYearsClip",
            // Re-exports from Data.Time.Format
            "formatTime",
            "parseTimeM",
            "defaultTimeLocale",
            "iso8601DateFormat",
            "rfc822DateFormat",
            "FormatTime",
            "ParseTime",
            "TimeLocale",
            // Re-exports from Data.Time.LocalTime
            "LocalTime",
            "ZonedTime",
            "TimeZone",
            "TimeOfDay",
            "utcToLocalTime",
            "localTimeToUTC",
            "getCurrentTimeZone",
            "utc",
            "zonedTimeToUTC",
            "utcToZonedTime",
            // Record field accessors
            "zonedTimeToLocalTime",
            "zonedTimeZone",
            "localDay",
            "localTimeOfDay",
            "todHour",
            "todMin",
            "todSec",
            "getZonedTime",
        ],
        "Data.Time.Clock.POSIX" => &[
            "POSIXTime",
            "posixSecondsToUTCTime",
            "utcTimeToPOSIXSeconds",
            "getPOSIXTime",
        ],
        "Data.Time.Format" => &[
            "formatTime",
            "parseTimeM",
            "defaultTimeLocale",
            "iso8601DateFormat",
            "rfc822DateFormat",
            "FormatTime",
            "ParseTime",
            "TimeLocale",
        ],
        "Data.Time.Calendar" => &[
            "Day",
            "fromGregorian",
            "toGregorian",
            "addDays",
            "diffDays",
            "showGregorian",
            "addGregorianMonthsClip",
            "addGregorianYearsClip",
        ],
        "Data.Time.LocalTime" => &[
            "LocalTime",
            "ZonedTime",
            "TimeZone",
            "TimeOfDay",
            "utcToLocalTime",
            "localTimeToUTC",
            "getCurrentTimeZone",
            "utc",
            "zonedTimeToUTC",
            "utcToZonedTime",
            "zonedTimeToLocalTime",
            "localDay",
            "localTimeOfDay",
            "todHour",
            "todMin",
            "todSec",
        ],
        // =====================================================================
        // Codec.Archive.Zip stubs
        // =====================================================================
        "Codec.Archive.Zip" => &[
            "Archive",
            "Entry",
            "CompressionMethod",
            "toArchive",
            "fromArchive",
            "toEntry",
            "fromEntry",
            "findEntryByPath",
            "filesInArchive",
            "addEntryToArchive",
            "deleteEntryFromArchive",
            "emptyArchive",
            "eRelativePath",
            "eCompressedData",
            "eUncompressedSize",
            "Deflate",
            "NoCompression",
        ],
        // =====================================================================
        // Text.TeXMath stubs
        // =====================================================================
        "Text.TeXMath" | "Text.TeXMath.Types" => &[
            "Exp",
            "TextType",
            "Alignment",
            "DisplayType",
            "readTeX",
            "writeMathML",
            "writeTeX",
            "writeLaTeX",
            "writeOMML",
            "readMathML",
            "readOMML",
            "writePandoc",
            "DisplayBlock",
            "DisplayInline",
            "Grouped",
            "ESymbol",
            "ENumber",
            "EIdentifier",
            "EMathOperator",
            "EText",
            "EDelimited",
            "EStyled",
            "ESub",
            "ESuper",
            "ESubsup",
            "EOver",
            "EUnder",
            "EUnderover",
            "EPhantom",
            "EBoxed",
            "EFraction",
            "ERoot",
            "ESqrt",
            "EScaled",
            "EArray",
            "ESpace",
            "TextNormal",
            "TextBold",
            "TextItalic",
            "TextMonospace",
            "TextSansSerif",
            "TextDoubleStruck",
            "TextScript",
            "TextFraktur",
            "TextBoldItalic",
            "TextSansSerifBold",
            "TextSansSerifBoldItalic",
            "TextBoldScript",
            "TextBoldFraktur",
            "TextSansSerifItalic",
        ],
        // =====================================================================
        // Data.Attoparsec stubs
        // =====================================================================
        "Data.Attoparsec.ByteString" => &[
            "Parser",
            "Result",
            "IResult",
            "parse",
            "parseOnly",
            "feed",
            "string",
            "word8",
            "anyWord8",
            "satisfy",
            "satisfyWith",
            "take",
            "takeWhile",
            "takeWhile1",
            "takeTill",
            "skipWhile",
            "takeByteString",
            "takeLazyByteString",
            "notWord8",
            "inClass",
            "notInClass",
            "try",
            "choice",
            "count",
            "option",
            "many'",
            "many1'",
            "manyTill'",
            "sepBy",
            "sepBy'",
            "sepBy1",
            "sepBy1'",
            "skipMany",
            "skipMany1",
            "eitherP",
            "match",
            "endOfInput",
            "atEnd",
            "Partial",
            "Done",
            "Fail",
        ],
        "Data.Attoparsec.ByteString.Char8" => &[
            "Parser",
            "Result",
            "IResult",
            "parse",
            "parseOnly",
            "char8",
            "anyChar",
            "satisfy",
            "satisfyWith",
            "char",
            "notChar",
            "digit",
            "letter_iso8859_15",
            "letter_ascii",
            "space",
            "isDigit",
            "isAlpha_iso8859_15",
            "isAlpha_ascii",
            "isSpace",
            "decimal",
            "hexadecimal",
            "double",
            "number",
            "rational",
            "signed",
            "scientific",
            "string",
            "stringCI",
            "take",
            "takeWhile",
            "takeWhile1",
            "takeTill",
            "skipWhile",
            "skipSpace",
            "endOfLine",
            "endOfInput",
            "atEnd",
            "try",
            "choice",
            "count",
            "option",
            "many'",
            "many1'",
            "manyTill'",
            "many1",
            "manyTill",
            "sepBy",
            "sepBy'",
            "sepBy1",
            "sepBy1'",
            "Partial",
            "Done",
            "Fail",
            "inClass",
            "notInClass",
        ],
        "Data.Attoparsec.Text" => &[
            "Parser",
            "Result",
            "IResult",
            "parse",
            "parseOnly",
            "feed",
            "char",
            "anyChar",
            "satisfy",
            "satisfyWith",
            "notChar",
            "string",
            "stringCI",
            "take",
            "takeWhile",
            "takeWhile1",
            "takeTill",
            "takeText",
            "skipWhile",
            "skipSpace",
            "decimal",
            "hexadecimal",
            "double",
            "number",
            "rational",
            "signed",
            "scientific",
            "endOfLine",
            "endOfInput",
            "atEnd",
            "try",
            "choice",
            "count",
            "option",
            "many'",
            "many1'",
            "manyTill'",
            "many1",
            "manyTill",
            "sepBy",
            "sepBy'",
            "sepBy1",
            "sepBy1'",
            "Partial",
            "Done",
            "Fail",
            "inClass",
            "notInClass",
        ],
        // =====================================================================
        // Text.Collate stubs
        // =====================================================================
        "Text.Collate" | "Text.Collate.Lang" => &[
            "Lang",
            "parseLang",
            "renderLang",
            // Lang record fields
            "langLanguage",
            "langScript",
            "langRegion",
            "langVariants",
            "langExtensions",
            "langPrivateUse",
            "Collator",
            "collate",
            "sortBy",
            "rootCollator",
            "collatorFor",
        ],
        // =====================================================================
        // Data.Generics (SYB) stubs
        // =====================================================================
        "Data.Generics" => &[
            "Data",
            "Typeable",
            "Generic",
            "everywhere",
            "everything",
            "mkT",
            "mkQ",
            "mkM",
            "extQ",
            "extT",
            "gmapQ",
            "gmapT",
            "gmapM",
            "toConstr",
            "gunfold",
            "dataTypeOf",
            "listify",
            "everywhereM",
        ],
        // =====================================================================
        // Commonmark.Pandoc stubs
        // =====================================================================
        "Commonmark.Pandoc" => &[
            "commonmarkToNode",
            "nodeToBlocks",
            "nodeToInlines",
            "Cm",
            "unCm",
        ],
        // =====================================================================
        // Text.Pandoc.Generic stubs (internal module)
        // =====================================================================
        "Text.Pandoc.Generic" => &["bottomUp", "topDown", "queryWith"],
        // =====================================================================
        // System extras stubs
        // =====================================================================
        "System.Random" => &[
            "StdGen",
            "Random",
            "RandomGen",
            "randomRIO",
            "randomIO",
            "randomR",
            "random",
            "mkStdGen",
            "newStdGen",
            "getStdGen",
            "setStdGen",
            "randoms",
            "randomRs",
            "split",
            "next",
            "genRange",
        ],
        "System.Console.GetOpt" => &[
            "OptDescr",
            "ArgDescr",
            "ArgOrder",
            "Option",
            "NoArg",
            "ReqArg",
            "OptArg",
            "Permute",
            "RequireOrder",
            "ReturnInOrder",
            "getOpt",
            "getOpt'",
            "usageInfo",
        ],
        "System.CPUTime" => &["getCPUTime", "cpuTimePrecision"],
        "System.FilePath.Windows" => &[
            "takeExtension",
            "takeBaseName",
            "takeDirectory",
            "takeFileName",
            "replaceExtension",
            "dropExtension",
            "addExtension",
            "hasExtension",
            "splitExtension",
            "splitFileName",
            "splitPath",
            "joinPath",
            "combine",
            "</>",
            "<.>",
            "splitDirectories",
            "normalise",
            "makeRelative",
            "isRelative",
            "isAbsolute",
            "pathSeparator",
            "searchPathSeparator",
            "extSeparator",
        ],
        "System.Posix.IO" => &[
            "openFd",
            "closeFd",
            "fdRead",
            "fdWrite",
            "dupTo",
            "dup",
            "setFdOption",
            "queryFdOption",
            "stdInput",
            "stdOutput",
            "stdError",
        ],
        "System.Posix.Terminal" => &[
            "queryTerminal",
            "getTerminalName",
            "TerminalAttributes",
            "getTerminalAttributes",
            "setTerminalAttributes",
        ],
        // =====================================================================
        // Data extras stubs
        // =====================================================================
        "Data.Vector" => &[
            "Vector",
            "empty",
            "singleton",
            "null",
            "length",
            "head",
            "last",
            "tail",
            "init",
            "fromList",
            "toList",
            "map",
            "filter",
            "foldl",
            "foldl'",
            "foldr",
            "zip",
            "zipWith",
            "unzip",
            "concat",
            "cons",
            "snoc",
            "take",
            "drop",
            "splitAt",
            "replicate",
            "generate",
            "(!)",
            "(!?)",
            "freeze",
            "thaw",
            "imap",
            "imapM_",
            "forM_",
            "mapM_",
            "uncons",
        ],
        "Data.List.Split" => &[
            "splitOn",
            "splitWhen",
            "splitOneOf",
            "chunksOf",
            "splitPlaces",
            "endBy",
            "wordsBy",
            "linesBy",
            "split",
            "Splitter",
            "onSublist",
            "whenElt",
            "keepDelimsL",
            "keepDelimsR",
            "condense",
            "dropInitBlank",
            "dropFinalBlank",
            "dropInnerBlanks",
            "dropBlanks",
        ],
        "Data.Aeson.Encode.Pretty" => &[
            "encodePretty",
            "encodePretty'",
            "encodePrettyToTextBuilder",
            "Config",
            "defConfig",
            "confIndent",
            "confCompare",
            "keyOrder",
            "Indent",
            "Spaces",
            "Tab",
        ],
        "Data.Aeson.TH" => &[
            "deriveJSON",
            "deriveToJSON",
            "deriveFromJSON",
            "defaultOptions",
            "fieldLabelModifier",
            "constructorTagModifier",
            "omitNothingFields",
            "allNullaryToStringTag",
            "Options",
            "SumEncoding",
            "TaggedObject",
            "UntaggedValue",
            "ObjectWithSingleField",
            "TwoElemArray",
            "camelTo2",
        ],
        "Data.Yaml" => &[
            "encode",
            "decode",
            "decodeEither",
            "decodeFileEither",
            "decodeThrow",
            "decodeFileThrow",
            "decodeEither'",
            "decodeAllEither'",
            "ParseException",
            "prettyPrintParseException",
            "ToJSON",
            "FromJSON",
        ],
        "Data.Ipynb" => &[
            "Notebook",
            "Cell",
            "CellType",
            "Output",
            "Source",
            "decodeNotebook",
            "encodeNotebook",
            "CodeCell",
            "MarkdownCell",
            "RawCell",
        ],
        "Data.ByteString.Base64" => &["encode", "decode", "decodeLenient"],
        "Data.ByteString.Base64.Lazy" => &["encode", "decode", "decodeLenient"],
        "Data.Text.Normalize" => &[
            "normalize",
            "NormalizationMode",
            "NFC",
            "NFD",
            "NFKC",
            "NFKD",
        ],
        "Data.FileEmbed" => &[
            "embedFile",
            "embedDir",
            "embedStringFile",
            "makeRelativeToProject",
        ],
        // =====================================================================
        // Control extras stubs
        // =====================================================================
        "Control.Monad.Catch" => &[
            "MonadThrow",
            "MonadCatch",
            "MonadMask",
            "throwM",
            "catch",
            "catchAll",
            "try",
            "tryJust",
            "handle",
            "handleAll",
            "bracket",
            "bracket_",
            "finally",
            "onException",
            "mask",
            "uninterruptibleMask",
            "SomeException",
            "Exception",
        ],
        "Control.Monad.RWS" => &[
            "RWS", "RWST", "runRWS", "evalRWS", "execRWS", "runRWST", "evalRWST", "execRWST",
            "ask", "asks", "tell", "get", "put", "modify", "reader", "writer", "state",
        ],
        // =====================================================================
        // Crypto.Hash stubs
        // =====================================================================
        "Crypto.Hash" => &[
            "Digest",
            "SHA1",
            "SHA256",
            "MD5",
            "hash",
            "hashlazy",
            "hashWith",
            "digestToHexByteString",
            "HashAlgorithm",
            "Context",
            "hashInit",
            "hashUpdate",
            "hashFinalize",
        ],
        // =====================================================================
        // Citeproc stubs
        // =====================================================================
        "Citeproc" => &[
            "citeproc",
            "evalStyle",
            "processStyle",
            "Citation",
            "CitationItem",
            "Reference",
            "Style",
            "Locale",
            "Result",
            "resultBibliography",
            "resultCitations",
            "CiteprocError",
            "prettyCiteprocError",
        ],
        "Citeproc.Types" => &[
            "Reference",
            "Val",
            "ItemId",
            "CiteprocOutput",
            "Name",
            "Date",
            "DateParts",
            "Variable",
            "Lang",
            "Locale",
            "Style",
            "Citation",
            "CitationItem",
            "NormalCitation",
            "AuthorOnly",
            "SuppressAuthor",
            "Formatted",
            "unFormatted",
            // Reference field accessors
            "referenceId",
            "referenceType",
            "referenceVariables",
            // Name field accessors
            "nameFamily",
            "nameGiven",
            "nameDroppingParticle",
            "nameNonDroppingParticle",
            "nameSuffix",
            "nameLiteral",
            "nameCommaSuffix",
            "nameStaticOrdering",
            // Date field accessors
            "dateLiteral",
            "dateParts",
            "dateCirca",
            // Val constructors
            "TextVal",
            "FancyVal",
            "NumVal",
            "NamesVal",
            "DateVal",
            // Val/Reference functions
            "lookupVariable",
            "valToText",
            "valToNumber",
            "unItemId",
            // Conversion functions
            "toVariable",
            "fromVariable",
            // Date utilities
            "rawDateEDTF",
            "emptyDate",
            // Variable sets
            "dateVariables",
            "nameVariables",
            // extractParticles helper
            "extractParticles",
        ],
        "Citeproc.Pandoc" => &["citeproc", "getReferences", "getStyle", "CiteprocOutput"],
        "Citeproc.Locale" => &["Locale", "getLocale", "mergeLocales", "lookupTerm"],
        "Citeproc.CslJson" => &[
            "readCslJson",
            "writeCslJson",
            "parseCslJson",
            "renderCslJson",
            "CslJson",
            "CslText",
            "CslEmpty",
            "CslConcat",
            "CslQuoted",
            "CslItalic",
            "CslNormal",
            "CslBold",
            "CslUnderline",
            "CslNoDecoration",
            "CslSmallCaps",
            "CslBaseline",
            "CslSub",
            "CslSup",
            "CslNoCase",
            "CslDiv",
            "CslLink",
        ],
        // =====================================================================
        // XML / Emoji stubs
        // =====================================================================
        "Text.XML.Light" => &[
            "Element",
            "Content",
            "CData",
            "CDataKind",
            "QName",
            "Attr",
            "elName",
            "elAttribs",
            "elContent",
            "elChildren",
            "qName",
            "qURI",
            "qPrefix",
            "cdVerbatim",
            "cdData",
            "attrKey",
            "attrVal",
            "unqual",
            "blank_element",
            "blank_cdata",
            "add_attr",
            "add_attrs",
            "findAttr",
            "lookupAttr",
            "findChild",
            "findChildren",
            "filterChildren",
            "strContent",
            "showElement",
            "showContent",
            "ppElement",
            "ppTopElement",
            "parseXML",
            "onlyElems",
            "Elem",
            "Text",
            "CRef",
            "CDataText",
            "CDataVerbatim",
            "CDataRaw",
        ],
        "Text.Emoji" => &[
            "emojis",
            "emojiFromAlias",
            "aliasesFromEmoji",
            "replaceEmojis",
            "emojisToAliases",
        ],
        // =====================================================================
        // Data.Array stubs
        // =====================================================================
        "Data.Array" | "Data.Array.IArray" => &[
            "Array",
            "Ix",
            "IArray",
            "array",
            "listArray",
            "accumArray",
            "(!)",
            "bounds",
            "indices",
            "elems",
            "assocs",
            "accum",
            "ixmap",
            "amap",
            "range",
            "index",
            "inRange",
            "rangeSize",
        ],
        "Data.Array.MArray" => &[
            "MArray",
            "IOArray",
            "newArray",
            "newArray_",
            "newListArray",
            "readArray",
            "writeArray",
            "getBounds",
            "getElems",
            "getAssocs",
            "freeze",
            "thaw",
            "mapArray",
            "mapIndices",
        ],
        "Data.Array.ST" => &[
            "STArray",
            "runSTArray",
            "newSTArray",
            "readSTArray",
            "writeSTArray",
            "freezeSTArray",
            "thawSTArray",
        ],
        "Data.Array.IO" => &[
            "IOArray",
            "IOUArray",
            "newArray",
            "newArray_",
            "readArray",
            "writeArray",
            "hGetArray",
            "hPutArray",
        ],
        // =====================================================================
        // Control.Monad.RWS.Strict
        // =====================================================================
        "Control.Monad.RWS.Strict" => &[
            "RWS", "RWST", "rws", "runRWS", "evalRWS", "execRWS", "runRWST", "evalRWST",
            "execRWST", "ask", "local", "asks", "tell", "listen", "pass", "censor", "get", "put",
            "modify", "gets",
        ],
        // =====================================================================
        // Network stubs
        // =====================================================================
        "Network.HTTP.Client" | "Network.HTTP.Client.Internal" => &[
            "Manager",
            "newManager",
            "defaultManagerSettings",
            "Request",
            "Response",
            "HttpException",
            "httpLbs",
            "httpNoBody",
            "httpBS",
            "parseRequest",
            "parseRequest_",
            "parseUrlThrow",
            "responseBody",
            "responseStatus",
            "responseHeaders",
            "method",
            "requestBody",
            "requestHeaders",
            "setRequestBodyLBS",
            "setRequestBodyBS",
            "responseTimeoutNone",
            "responseTimeoutMicro",
            "managerResponseTimeout",
            "managerConnCount",
            // Request record field accessors
            "host",
            "port",
            "secure",
            "path",
            "queryString",
            "proxy",
            "redirectCount",
            "checkResponse",
            // Internal functions
            "addProxy",
        ],
        "Network.HTTP.Client.TLS" => &[
            "tlsManagerSettings",
            "newTlsManager",
            "newTlsManagerWith",
            "mkManagerSettings",
        ],
        "Network.HTTP.Types" | "Network.HTTP.Types.Header" | "Network.HTTP.Types.Status" => &[
            "Status",
            "status200",
            "status301",
            "status302",
            "status404",
            "status500",
            "statusCode",
            "statusMessage",
            "Header",
            "HeaderName",
            "RequestHeaders",
            "ResponseHeaders",
            "hContentType",
            "hAccept",
            "hAuthorization",
            "methodGet",
            "methodPost",
            "methodPut",
            "methodDelete",
            "renderQuery",
            "parseQuery",
            "urlEncode",
            "urlDecode",
        ],
        "Network.Mime" => &[
            "defaultMimeLookup",
            "defaultMimeMap",
            "defaultMimeType",
            "MimeType",
            "MimeMap",
            "FileName",
        ],
        "Network.Socket" => &[
            "Socket",
            "SockAddr",
            "SockAddrUnix",
            "SocketType",
            "Family",
            "AF_UNIX",
            "AF_INET",
            "AF_INET6",
            "Stream",
            "Datagram",
            "socket",
            "connect",
            "bind",
            "listen",
            "accept",
            "close",
            "withSocketsDo",
            "gracefulClose",
            "defaultProtocol",
            "setSocketOption",
            "getSocketName",
        ],
        "Network.Connection" => &["TLSSettings", "initConnectionContext", "ConnectionParams"],
        "Network.TLS" | "Network.TLS.Extra" => &[
            "ClientParams",
            "Supported",
            "Shared",
            "defaultParamsClient",
            "supportedCiphers",
            "ciphersuite_default",
            // Record field accessors
            "clientSupported",
            "clientShared",
            "supportedExtendedMainSecret",
            "sharedCAStore",
            "sharedValidationCache",
            // Data constructors
            "AllowEMS",
            "RequireEMS",
            "NoEMS",
            "ValidationCache",
            "ValidationCachePass",
            "ValidationCacheDeny",
        ],
        // =====================================================================
        // Text.Blaze stubs
        // =====================================================================
        "Text.Blaze" | "Text.Blaze.Internal" => &[
            "Markup",
            "MarkupM",
            "Tag",
            "Attribute",
            "AttributeValue",
            "ChoiceString",
            "StaticString",
            // MarkupM constructors
            "Parent",
            "CustomParent",
            "Leaf",
            "CustomLeaf",
            "AddAttribute",
            "AddCustomAttribute",
            "Content",
            "Comment",
            "Append",
            "Empty",
            // ChoiceString constructors
            "Static",
            "String",
            "PreEscaped",
            "External",
            "AppendChoiceString",
            "EmptyChoiceString",
            // Functions
            "toMarkup",
            "toValue",
            "preEscapedToMarkup",
            "string",
            "text",
            "lazyText",
            "textTag",
            "textValue",
            "customAttribute",
            "customParent",
            "customLeaf",
            "(!)",
            "(!?)",
            "contents",
            "getText",
            "ToMarkup",
            "ToValue",
        ],
        "Text.Blaze.Html" => &[
            "Html",
            "toHtml",
            "preEscapedToHtml",
            "preEscapedString",
            "preEscapedText",
        ],
        "Text.Blaze.Html.Renderer.Text" => &["renderHtml"],
        "Text.Blaze.XHtml5" | "Text.Blaze.XHtml1.Transitional" => &[
            "docType",
            "docTypeHtml",
            "html",
            "head",
            "title",
            "body",
            "div",
            "span",
            "p",
            "a",
            "img",
            "br",
            "hr",
            "h1",
            "h2",
            "h3",
            "h4",
            "h5",
            "h6",
            "ul",
            "ol",
            "li",
            "dl",
            "dt",
            "dd",
            "table",
            "thead",
            "tbody",
            "tr",
            "th",
            "td",
            "form",
            "input",
            "button",
            "select",
            "option",
            "textarea",
            "pre",
            "code",
            "blockquote",
            "em",
            "strong",
            "i",
            "b",
            "link",
            "meta",
            "script",
            "style",
            "noscript",
            "sup",
            "sub",
            "small",
            "abbr",
            "cite",
            "q",
            "section",
            "article",
            "nav",
            "aside",
            "header",
            "footer",
            "figure",
            "figcaption",
            "video",
            "audio",
            "source",
        ],
        "Text.Blaze.XHtml5.Attributes" | "Text.Blaze.XHtml1.Transitional.Attributes" => &[
            "class_",
            "id",
            "href",
            "src",
            "alt",
            "title",
            "style",
            "type_",
            "name",
            "value",
            "action",
            "method",
            "width",
            "height",
            "colspan",
            "rowspan",
            "rel",
            "target",
            "placeholder",
            "role",
            "lang",
            "dir",
            "tabindex",
            "accesskey",
        ],
        // =====================================================================
        // Djot stubs
        // =====================================================================
        "Djot" => &[
            "parseDjot",
            "renderDjot",
            "renderHtml",
            "parseDoc",
            "ParseOptions",
            "RenderOptions",
            "defaultParseOptions",
            "defaultRenderOptions",
            // SourcePosOption + constructors (Readers/Djot pattern-matches these)
            "SourcePosOption",
            "AllSourcePos",
            "NoSourcePos",
            // Pos + constructors
            "Pos",
            "NoPos",
            "sourcePositions",
            // Re-exported utilities
            "toIdentifier",
        ],
        "Djot.AST" => &[
            // Core types
            "Doc",
            "Block",
            "Blocks",
            "Inline",
            "Inlines",
            "Attr",
            "Node",
            "Section",
            "Caption",
            "Format",
            "Reference",
            "ReferenceMap",
            "NoteMap",
            "OrderedListAttributes",
            "Cell",
            // Block constructors (used in pattern matching by Readers/Djot.hs)
            "Para",
            "Heading",
            "CodeBlock",
            "BlockQuote",
            "BulletList",
            "OrderedList",
            "DefinitionList",
            "TaskList",
            "Table",
            "Div",
            "ThematicBreak",
            "RawBlock",
            // Inline constructors
            "Str",
            "Emph",
            "Strong",
            "Link",
            "Image",
            "Verbatim",
            "RawInline",
            "SoftBreak",
            "HardBreak",
            "Span",
            "Superscript",
            "Subscript",
            "Delete",
            "Insert",
            "Highlight",
            "Quoted",
            "Math",
            "Symbol",
            "FootnoteReference",
            "NonBreakingSpace",
            "UrlLink",
            "EmailLink",
            // Quote / math / alignment / list-style / link-type enums
            "SingleQuotes",
            "DoubleQuotes",
            "DisplayMath",
            "InlineMath",
            "AlignLeft",
            "AlignRight",
            "AlignCenter",
            "AlignDefault",
            "Direct",
            "Section",
            "Decimal",
            "LetterLower",
            "LetterUpper",
            "RomanLower",
            "RomanUpper",
            "RightPeriod",
            "RightParen",
            "LeftRightParen",
            "Loose",
            "Tight",
            "Complete",
            "HeadCell",
            "BodyCell",
            "TaskStatus",
            // Doc record fields (Readers/Djot reads these)
            "docBlocks",
            "docReferences",
            "docFootnotes",
            "docAutoReferences",
            "docAutoIdentifiers",
            // OrderedListAttributes record fields
            "orderedListStart",
            "orderedListStyle",
            "orderedListDelim",
            // Smart constructors / functions (Writers/Djot)
            "para",
            "heading",
            "codeBlock",
            "blockQuote",
            "bulletList",
            "orderedList",
            "definitionList",
            "table",
            "div",
            "thematicBreak",
            "rawBlock",
            "str",
            "emph",
            "strong",
            "link",
            "image",
            "verbatim",
            "rawInline",
            "softBreak",
            "hardBreak",
            "span_",
            "superscript",
            "subscript",
            "delete",
            "highlight",
            "singleQuoted",
            "doubleQuoted",
            "inlineMath",
            "displayMath",
            "section",
            "footnoteReference",
            "urlLink",
            "emailLink",
            "addAttr",
            // Utility/lookup functions
            "lookupReference",
            "lookupNote",
            "insertReference",
            "insertNote",
            "normalizeLabel",
            "inlinesToByteString",
            "unMany",
        ],
        // =====================================================================
        // Data.Scientific
        // =====================================================================
        "Data.Scientific" => &[
            "Scientific",
            "scientific",
            "coefficient",
            "base10Exponent",
            "toRealFloat",
            "fromRealFloat",
            "toDecimalDigits",
            "normalize",
            "toBoundedInteger",
            "toBoundedRealFloat",
            "fromFloatDigits",
            "isFloating",
            "isInteger",
        ],
        // =====================================================================
        // Text.HTML.TagSoup.Match
        // =====================================================================
        "Text.HTML.TagSoup.Match" => &[
            "tagOpen",
            "tagClose",
            "tagText",
            "tagComment",
            "tagOpenLit",
            "tagCloseLit",
            "tagOpenAttr",
            "tagOpenAttrLit",
            "tagOpenAttrNameLit",
            "(~==)",
            "(/~==)",
        ],
        // =====================================================================
        // Data.Text.Lazy.Builder
        // =====================================================================
        "Data.Text.Lazy.Builder" => &[
            "Builder",
            "toLazyText",
            "fromText",
            "fromLazyText",
            "fromString",
            "singleton",
            "flush",
        ],
        // =====================================================================
        // Codec.Compression stubs
        // =====================================================================
        "Codec.Compression.GZip" | "Codec.Compression.Zlib" => &[
            "compress",
            "decompress",
            "compressWith",
            "decompressWith",
            "defaultCompressParams",
            "defaultDecompressParams",
            "CompressParams",
            "DecompressParams",
            "compressLevel",
            "bestCompression",
            "bestSpeed",
            "noCompression",
        ],
        // =====================================================================
        // Codec.Picture stubs
        // =====================================================================
        "Codec.Picture" | "Codec.Picture.Metadata" => &[
            "DynamicImage",
            "Image",
            "PixelRGBA8",
            "PixelRGB8",
            "decodePng",
            "decodeJpeg",
            "decodeGif",
            "decodeImage",
            "decodeImageWithMetadata",
            "readImage",
            "encodePng",
            "encodeJpeg",
            "savePngImage",
            "dynamicMap",
            "imageWidth",
            "imageHeight",
            "Metadatas",
            "Keys",
            "lookup",
            "Width",
            "Height",
            "DpiX",
            "DpiY",
        ],
        // =====================================================================
        // Data.Binary.Get
        // =====================================================================
        "Data.Binary.Get" => &[
            "Get",
            "runGet",
            "runGetOrFail",
            "getWord8",
            "getWord16be",
            "getWord16le",
            "getWord32be",
            "getWord32le",
            "getWord64be",
            "getWord64le",
            "getByteString",
            "getLazyByteString",
            "getRemainingLazyByteString",
            "skip",
            "isEmpty",
            "bytesRead",
            "lookAhead",
        ],
        // =====================================================================
        // Data.CaseInsensitive
        // =====================================================================
        "Data.CaseInsensitive" => &[
            "CI",
            "mk",
            "original",
            "foldedCase",
            "map",
            "FoldCase",
            "foldCase",
        ],
        // =====================================================================
        // System extras
        // =====================================================================
        "System.IO.Temp" => &[
            "withSystemTempFile",
            "withSystemTempDirectory",
            "withTempFile",
            "withTempDirectory",
            "openTempFile",
            "openBinaryTempFile",
            "createTempDirectory",
            "getCanonicalTemporaryDirectory",
        ],
        "System.X509" => &["getSystemCertificateStore", "CertificateStore"],
        "System.FilePath.Glob" => &["glob", "globDir", "globDir1", "compile", "match", "Pattern"],
        "GHC.IO.Exception" => &[
            "IOException",
            "IOError",
            "IOErrorType",
            // IOException record fields
            "ioe_handle",
            "ioe_type",
            "ioe_location",
            "ioe_description",
            "ioe_errno",
            "ioe_filename",
            // IOErrorType constructors
            "AlreadyExists",
            "NoSuchThing",
            "ResourceBusy",
            "ResourceExhausted",
            "EOF",
            "IllegalOperation",
            "PermissionDenied",
            "UserError",
            "UnsatisfiedConstraints",
            "SystemError",
            "ProtocolError",
            "OtherError",
            "InvalidArgument",
            "InappropriateType",
            "HardwareFault",
            "UnsupportedOperation",
            "TimeExpired",
            "ResourceVanished",
            "Interrupted",
        ],
        // =====================================================================
        // Text extras
        // =====================================================================
        "Text.Pandoc.JSON" => &[
            "toJSONFilter",
            "toJSONFilter'",
            "Block",
            "Inline",
            "Pandoc",
            "Format",
        ],
        "Text.TeXMath.Readers.OMML" => &["readOMML"],
        "Text.TeXMath.Readers.MathML.EntityMap" => &["getEntity", "entityMap"],
        "Text.TeXMath.Unicode.ToTeX" => &["toTeX", "toUnicode", "getTeXMath", "lookupChar"],
        "Text.TeXMath.Shared" => &["getSpaceChars", "getLaTeXTextCommand"],
        "Text.DocTemplates.Internal" => &[
            "Context",
            "Val",
            "ToContext",
            "FromContext",
            "renderTemplate",
            "compileTemplate",
        ],
        "Text.XML.Light.Output" => &[
            "showElement",
            "showContent",
            "ppElement",
            "ppContent",
            "showTopElement",
            "ppTopElement",
        ],
        "Text.Show.Pretty" => &["ppShow", "ppDoc", "ppValue", "Value", "parseValue"],
        "Text.PrettyPrint" => &[
            "Doc",
            "text",
            "char",
            "int",
            "integer",
            "nest",
            "hang",
            "punctuate",
            "(<>)",
            "(<+>)",
            "($$)",
            "($+$)",
            "hcat",
            "hsep",
            "vcat",
            "sep",
            "cat",
            "fsep",
            "fcat",
            "parens",
            "brackets",
            "braces",
            "quotes",
            "doubleQuotes",
            "empty",
            "render",
            "renderStyle",
            "Style",
            "style",
            "lineLength",
            "ribbonsPerLine",
            "mode",
            "Mode",
            "PageMode",
            "ZigZagMode",
            "LeftMode",
            "OneLineMode",
        ],
        "Text.GridTable" => &[
            "gridTable",
            "parseGridTable",
            "GridTable",
            "Cell",
            "Row",
            "arrayTableFoot",
            "arrayTableHead",
            "arrayTableBody",
            "fromRowIndex",
            "toRowIndex",
            "RowIndex",
            "ColIndex",
            "fromColIndex",
            "toColIndex",
            "arrayTable",
            "lookupCell",
            "arrayTableColSpecs",
            "RowSpan",
            "ColSpan",
            "rows",
            "mapCells",
            "AlignLeft",
            "AlignRight",
            "AlignCenter",
            "AlignDefault",
            "Alignment",
        ],
        // =====================================================================
        // Typst stubs
        // =====================================================================
        "Typst" => &["parseTypst", "evaluateTypst"],
        "Typst.Types" => &[
            "Val",
            "Content",
            "Selector",
            "Function",
            "VString",
            "VInteger",
            "VFloat",
            "VBool",
            "VContent",
            "VArray",
            "VDict",
            "VNone",
            "VAuto",
            "Identifier",
            "fromVal",
            "toVal",
            // Content constructors
            "Elt",
            "Txt",
            "Lab",
            "Seq",
            // FromVal typeclass
            "FromVal",
        ],
        "Typst.Methods" => &["applyPureFunction", "lookupMethod"],
        // =====================================================================
        // Jira stubs
        // =====================================================================
        "Text.Jira.Parser" => &["parse", "doc"],
        "Text.Jira.Printer" => &[
            "prettyBlocks",
            "prettyBlock",
            "prettyInlines",
            "prettyInline",
        ],
        "Text.Jira.Markup" => &[
            "Doc",
            "Block",
            "Inline",
            "Para",
            "Header",
            "List",
            "Table",
            "Code",
            "Panel",
            "Str",
            "Emph",
            "Strong",
            "Monospaced",
            "Link",
            "Image",
            "Linebreak",
            "Anchor",
            "Citation",
        ],
        // =====================================================================
        // Haddock stubs
        // =====================================================================
        "Documentation.Haddock.Parser" => &["parseParas", "parseString"],
        "Documentation.Haddock.Types" => &[
            "DocH",
            "Doc",
            "DocEmpty",
            "DocAppend",
            "DocString",
            "DocParagraph",
            "DocIdentifier",
            "DocModule",
            "DocWarning",
            "DocEmphasis",
            "DocMonospaced",
            "DocBold",
            "DocHeader",
            "DocUnorderedList",
            "DocOrderedList",
            "DocDefList",
            "DocCodeBlock",
            "DocHyperlink",
            "DocAName",
            "DocProperty",
            "Hyperlink",
            "hyperlinkUrl",
            "hyperlinkLabel",
            "Header",
            "headerLevel",
            "headerTitle",
        ],
        // =====================================================================
        // Yaml stubs
        // =====================================================================
        "Data.Yaml.Internal" => &["rawParser", "parseRawDoc"],
        "Text.Libyaml" => &[
            "Event",
            "Style",
            "Tag",
            "EventStreamStart",
            "EventStreamEnd",
            "EventDocumentStart",
            "EventDocumentEnd",
            "EventMappingStart",
            "EventMappingEnd",
            "EventSequenceStart",
            "EventSequenceEnd",
            "EventScalar",
            "EventAlias",
        ],
        // =====================================================================
        // Paths_pandoc (auto-generated by Cabal)
        // =====================================================================
        "Paths_pandoc" => &["version", "getDataDir", "getDataFileName"],
        // =====================================================================
        // Unicode
        // =====================================================================
        "Unicode.Char" => &[
            "isLetter",
            "isNumber",
            "isSpace",
            "isPunctuation",
            "isSymbol",
            "isSeparator",
            "isMark",
            "generalCategory",
            "GeneralCategory",
        ],
        // =====================================================================
        // Data.Text.Conversions
        // =====================================================================
        "Data.Text.Conversions" => &[
            "ToText",
            "FromText",
            "toText",
            "fromText",
            "convertText",
            "decodeConvertText",
            "UTF8",
            "Base16",
            "Base64",
        ],
        // =====================================================================
        // AsciiDoc
        // =====================================================================
        "AsciiDoc" => &[
            "parseAsciiDoc",
            "defaultOptions",
            "AsciiDoc",
            "Block",
            "Inline",
        ],
        // =====================================================================
        // Powerpoint output
        // =====================================================================
        "Text.Pandoc.Writers.Powerpoint.Output" => &["presentationToArchive"],
        // =====================================================================
        // aeson (extended)
        // =====================================================================
        "Data.Aeson.Key" => &["Key", "fromText", "toText", "fromString"],
        "Data.Aeson.KeyMap" => &[
            "KeyMap",
            "empty",
            "singleton",
            "null",
            "size",
            "member",
            "lookup",
            "insert",
            "delete",
            "union",
            "intersection",
            "difference",
            "map",
            "mapWithKey",
            "filter",
            "filterWithKey",
            "foldMapWithKey",
            "foldrWithKey",
            "toList",
            "fromList",
            "toHashMap",
            "fromHashMap",
            "toMap",
            "fromMap",
            "keys",
            "elems",
        ],

        // =====================================================================
        // time (extended)
        // =====================================================================
        "Data.Time.Clock.System" => &[
            "SystemTime",
            "getSystemTime",
            "systemToUTCTime",
            "utcToSystemTime",
            "systemSeconds",
            "systemNanoseconds",
        ],

        // =====================================================================
        // network (extended)
        // =====================================================================
        "Network.Socket.ByteString" | "Network.Socket.ByteString.Lazy" => {
            &["recv", "recvFrom", "send", "sendTo", "sendAll", "sendAllTo"]
        }

        // =====================================================================
        // hashable
        // =====================================================================
        "Data.Hashable" | "Data.Hashable.Class" => {
            &["Hashable", "hashWithSalt", "hash", "hashUsing"]
        }

        // =====================================================================
        // unordered-containers
        // =====================================================================
        "Data.HashMap.Strict" | "Data.HashMap.Lazy" | "Data.HashMap.Internal" => &[
            "HashMap",
            "empty",
            "singleton",
            "null",
            "size",
            "member",
            "lookup",
            "lookupDefault",
            "findWithDefault",
            "insert",
            "insertWith",
            "delete",
            "adjust",
            "alter",
            "union",
            "unionWith",
            "unionWithKey",
            "intersection",
            "intersectionWith",
            "difference",
            "differenceWith",
            "map",
            "mapWithKey",
            "mapKeys",
            "filter",
            "filterWithKey",
            "foldlWithKey'",
            "foldrWithKey",
            "keys",
            "elems",
            "toList",
            "fromList",
            "fromListWith",
        ],
        "Data.HashSet" | "Data.HashSet.Internal" => &[
            "HashSet",
            "empty",
            "singleton",
            "null",
            "size",
            "member",
            "insert",
            "delete",
            "union",
            "intersection",
            "difference",
            "map",
            "filter",
            "foldl'",
            "foldr",
            "toList",
            "fromList",
            "toMap",
            "fromMap",
        ],

        // =====================================================================
        // optparse-applicative
        // =====================================================================
        "Options.Applicative" | "Options.Applicative.Extra" => &[
            "Parser",
            "ParserInfo",
            "ParserPrefs",
            "execParser",
            "customExecParser",
            "info",
            "infoOption",
            "helper",
            "fullDesc",
            "progDesc",
            "header",
            "footer",
            "strOption",
            "option",
            "strArgument",
            "argument",
            "switch",
            "flag",
            "flag'",
            "long",
            "short",
            "metavar",
            "value",
            "showDefault",
            "help",
            "many",
            "some",
            "optional",
            "command",
            "subparser",
            "hsubparser",
            "auto",
            "str",
            "eitherReader",
            "maybeReader",
            "(<>)",
            "(<**>)",
            "(<|>)",
            "<**>",
            "helper",
        ],
        "Options.Applicative.Builder" => &[
            "strOption",
            "option",
            "strArgument",
            "argument",
            "switch",
            "flag",
            "flag'",
            "long",
            "short",
            "metavar",
            "value",
            "showDefault",
            "help",
            "command",
            "subparser",
            "hsubparser",
            "auto",
            "str",
            "eitherReader",
            "maybeReader",
            "info",
            "fullDesc",
            "progDesc",
            "header",
            "footer",
        ],
        "Options.Applicative.Common" => &["runParser", "evalParser"],
        "Options.Applicative.Help" => &["renderHelp"],
        "Options.Applicative.Types" => &[
            "Parser",
            "ParserInfo",
            "ParserPrefs",
            "ParserResult",
            "ReadM",
            "readerAsk",
            "readerAbort",
            "readerError",
        ],

        _ => &[],
    };

    // Determine the qualifier to use for qualified access
    let qualifier = if let Some(import) = import_decl {
        if let Some(alias) = &import.alias {
            alias
                .parts
                .iter()
                .map(|s| s.as_str())
                .collect::<Vec<_>>()
                .join(".")
        } else {
            module_name.to_string()
        }
    } else {
        module_name.to_string()
    };

    // For non-qualified imports, we should override existing bindings
    // (e.g., `import Data.Set (fromList)` should override OverloadedLists' fromList).
    let is_unqualified_import = import_decl.is_some_and(|imp| !imp.qualified);

    // Build a set of explicitly imported names, if the import has an explicit list.
    // This prevents `import Data.Text (Text)` from overriding Prelude's `foldr` etc.
    let explicit_imports: Option<rustc_hash::FxHashSet<&str>> = import_decl.and_then(|imp| {
        if let Some(ast::ImportSpec::Only(items)) = &imp.spec {
            let mut names = rustc_hash::FxHashSet::default();
            let mut has_dotdot_import = false;
            for item in items {
                match item {
                    ast::Import::Var(ident, _) => {
                        names.insert(ident.name.as_str());
                    }
                    ast::Import::Type(ident, ctors, _) => {
                        names.insert(ident.name.as_str());
                        match ctors {
                            Some(cs) if !cs.is_empty() => {
                                for c in cs {
                                    names.insert(c.name.as_str());
                                }
                            }
                            Some(_) => {
                                // Type(..) — all constructors of this type are imported.
                                // The parser returns Some(vec![]) for (..).
                                // We don't know which constructors belong to this type,
                                // so mark that we have a wildcard constructor import.
                                has_dotdot_import = true;
                            }
                            None => {
                                // Just the type name, no constructors (e.g., `import Foo (Bar)`)
                            }
                        }
                    }
                    ast::Import::Pattern(ident, _) => {
                        names.insert(ident.name.as_str());
                    }
                }
            }
            // If any type/class uses (..) import, allow all exports through the
            // filter. We can't distinguish Type(..) (constructors) from Class(..)
            // (methods) at import time, and class methods are lowercase (e.g.,
            // `walkM`, `query`), so we must allow all names, not just uppercase.
            if has_dotdot_import {
                for export in exports {
                    names.insert(export);
                }
            }
            Some(names)
        } else {
            None // No explicit list (import everything) or hiding list
        }
    });

    for &raw_export in exports {
        // Operator names may appear in the export list wrapped in parens
        // (e.g., "(<>)", "(!)"). Normalize to the bare operator name so
        // lookups at infix use sites match — `arr ! x` resolves `!`, not `(!)`.
        let export = raw_export
            .strip_prefix('(')
            .and_then(|s| s.strip_suffix(')'))
            .unwrap_or(raw_export);
        let unqualified = Symbol::intern(export);

        // Detect if this export is a constructor (starts with uppercase or ':')
        let is_constructor = export
            .chars()
            .next()
            .is_some_and(|c| c.is_uppercase() || c == ':');

        // Only use fully-qualified names for modules that have explicit type
        // signatures in the type checker's register_lowered_builtins.
        // Other modules get unqualified names (→ fresh type variables).
        let has_typed_sigs = matches!(
            module_name,
            "Data.Map"
                | "Data.Map.Strict"
                | "Data.Map.Lazy"
                | "Data.IntMap"
                | "Data.IntMap.Strict"
                | "Data.IntMap.Lazy"
                | "Data.IntSet"
                | "Data.Set"
                | "Data.Text"
                | "Data.Text.IO"
                | "Data.Text.Encoding"
                | "Data.Text.Lazy"
                | "Data.Text.Lazy.Encoding"
                | "Data.ByteString"
                | "Data.ByteString.Char8"
                | "Data.ByteString.Lazy"
                | "Data.ByteString.Lazy.Char8"
                | "Data.Sequence"
                | "Data.Foldable"
                | "Control.Category"
                | "Text.DocLayout"
                | "Text.Emoji"
                | "Network.Mime"
                | "Network.URI"
                | "Network.HTTP.Types"
                | "Data.Attoparsec.Text"
                | "Data.Attoparsec.ByteString"
                | "Data.Attoparsec.ByteString.Char8"
                | "Data.ByteString.Base64"
                | "Text.Parsec"
                | "Text.Parsec.Prim"
                | "Text.Parsec.Pos"
                | "Text.Parsec.Char"
                | "Text.Parsec.Combinator"
        );

        // Register qualified name under the alias: e.g. T.pack -> pack
        let aliased_qualified = Symbol::intern(&format!("{qualifier}.{export}"));
        if has_typed_sigs {
            // For modules with typed sigs, create a direct value binding for
            // qualified names (e.g., T.groupBy -> DefId named "Data.Text.groupBy").
            // This prevents T.groupBy from resolving to the Prelude's groupBy.
            if ctx.lookup_value(aliased_qualified).is_none() {
                let qual_def_id = ctx.fresh_def_id();
                let qual_def_name = Symbol::intern(&format!("{module_name}.{export}"));
                if is_constructor {
                    ctx.define(
                        qual_def_id,
                        qual_def_name,
                        DefKind::StubConstructor,
                        Span::default(),
                    );
                } else {
                    ctx.define(
                        qual_def_id,
                        qual_def_name,
                        DefKind::StubValue,
                        Span::default(),
                    );
                }
                ctx.bind_value(aliased_qualified, qual_def_id);
            }
            // Also register under the full module name if different from alias
            if qualifier != module_name {
                let full_qualified = Symbol::intern(&format!("{module_name}.{export}"));
                if ctx.lookup_value(full_qualified).is_none() {
                    // Re-use the aliased DefId
                    if let Some(def_id) = ctx.lookup_value(aliased_qualified) {
                        ctx.bind_value(full_qualified, def_id);
                    }
                }
            }
        } else {
            // For modules without typed sigs, prefer an existing binding of the
            // fully-qualified name (e.g. fixed-DefId prelude bindings like
            // Data.ByteString.Builder.toLazyByteString) — codegen dispatches on
            // that name. Only fall back to the unqualified indirect mapping
            // when no such binding exists; otherwise the alias would resolve to
            // an unqualified stub that panics at runtime.
            let full_qualified = Symbol::intern(&format!("{module_name}.{export}"));
            let prelude_bound = ctx.lookup_value(full_qualified);
            if ctx.lookup_value(aliased_qualified).is_none() {
                if let Some(def_id) = prelude_bound {
                    ctx.bind_value(aliased_qualified, def_id);
                } else {
                    ctx.register_qualified_name(aliased_qualified, unqualified);
                }
            }
            if qualifier != module_name && prelude_bound.is_none() {
                ctx.register_qualified_name(full_qualified, unqualified);
            }
        }

        // Ensure the unqualified name is defined.
        // Non-qualified imports override existing bindings (e.g., `import Data.Set
        // (fromList)` overrides OverloadedLists). Exception: GHC.Generics, whose
        // `from`/`to` exports would clash with well-typed builtins.
        //
        // IMPORTANT: If the import has an explicit import list (e.g., `import Data.Text (Text)`),
        // only register unqualified names that are actually imported. Otherwise, importing
        // Data.Text (Text) would override Prelude's foldr/map/etc. with Data.Text's versions.
        let is_explicitly_imported = explicit_imports
            .as_ref()
            .is_none_or(|names| names.contains(export));
        if !is_explicitly_imported {
            // Skip unqualified registration — this name wasn't in the explicit import list.
            // Qualified names (e.g., T.foldr) are still registered above.
        } else if ctx.lookup_value(unqualified).is_none() {
            // No existing binding — create a stub for this imported name.
            let def_id = ctx.fresh_def_id();
            let def_name = if has_typed_sigs {
                Symbol::intern(&format!("{module_name}.{export}"))
            } else {
                unqualified
            };
            if is_constructor {
                ctx.define(def_id, def_name, DefKind::StubConstructor, Span::default());
            } else {
                ctx.define(def_id, def_name, DefKind::StubValue, Span::default());
            }
            ctx.bind_value(unqualified, def_id);
        } else if is_unqualified_import && module_name != "GHC.Generics" {
            // There's already a binding. Create a fresh DefId to avoid sharing
            // DefIds across function boundaries in multi-module compilation
            // (which causes LLVM "referring to argument in another function" errors).
            // Preserve the existing DefKind so builtins keep their codegen support.
            let existing_def_id = ctx.lookup_value(unqualified).unwrap();
            let existing_kind = ctx.defs.get(&existing_def_id).map(|info| info.kind);
            let def_id = ctx.fresh_def_id();
            let def_name = if has_typed_sigs {
                Symbol::intern(&format!("{module_name}.{export}"))
            } else {
                unqualified
            };
            let kind = match existing_kind {
                Some(kind) if !kind.is_stub() => {
                    // Preserve real builtin kind (DefKind::Value) — codegen
                    // uses name-based matching, so a new DefId with the same
                    // name and kind will still be recognized as a builtin.
                    kind
                }
                _ => {
                    // Existing is a stub or unknown; create appropriate stub.
                    if is_constructor {
                        DefKind::StubConstructor
                    } else {
                        DefKind::StubValue
                    }
                }
            };
            ctx.define(def_id, def_name, kind, Span::default());
            ctx.bind_value(unqualified, def_id);
        }

        // Also register in constructor namespace for pattern matching
        if is_constructor && ctx.lookup_constructor(unqualified).is_none() {
            // Re-use the same def_id from the value namespace
            if let Some(def_id) = ctx.lookup_value(unqualified) {
                ctx.bind_constructor(unqualified, def_id);

                // Also register qualified constructor names
                let aliased_qual_sym = Symbol::intern(&format!("{qualifier}.{export}"));
                if ctx.lookup_constructor(aliased_qual_sym).is_none() {
                    ctx.bind_constructor(aliased_qual_sym, def_id);
                }
                if qualifier != module_name {
                    let full_qual_sym = Symbol::intern(&format!("{module_name}.{export}"));
                    if ctx.lookup_constructor(full_qual_sym).is_none() {
                        ctx.bind_constructor(full_qual_sym, def_id);
                    }
                }
            }
        }
    }
}

/// Lower a top-level declaration.
fn lower_decl(ctx: &mut LowerContext, decl: &ast::Decl) -> LowerResult<Vec<hir::Item>> {
    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: (x, y) = expr
                // Generate a value definition for each variable in the pattern
                let items = lower_pattern_binding(ctx, fun_bind)?;
                Ok(items)
            } else {
                let item = lower_fun_bind(ctx, fun_bind)?;
                Ok(vec![hir::Item::Value(item)])
            }
        }

        ast::Decl::DataDecl(data_decl) => {
            let (data_def, accessors) = lower_data_decl_with_accessors(ctx, data_decl)?;
            let mut items = vec![hir::Item::Data(data_def)];
            for accessor in accessors {
                items.push(hir::Item::Value(accessor));
            }
            Ok(items)
        }

        ast::Decl::Newtype(newtype_decl) => {
            let (newtype_def, accessors) = lower_newtype_decl_with_accessors(ctx, newtype_decl)?;
            let mut items = Vec::with_capacity(1 + accessors.len());
            items.push(hir::Item::Newtype(newtype_def));
            for accessor in accessors {
                items.push(hir::Item::Value(accessor));
            }
            Ok(items)
        }

        ast::Decl::TypeAlias(type_alias) => {
            let item = lower_type_alias(ctx, type_alias)?;
            Ok(vec![hir::Item::TypeAlias(item)])
        }

        ast::Decl::ClassDecl(class_decl) => {
            let item = lower_class_decl(ctx, class_decl)?;
            Ok(vec![hir::Item::Class(item)])
        }

        ast::Decl::InstanceDecl(instance_decl) => {
            let item = lower_instance_decl(ctx, instance_decl)?;
            Ok(vec![hir::Item::Instance(item)])
        }

        ast::Decl::Fixity(fixity_decl) => {
            let item = lower_fixity_decl(fixity_decl);
            Ok(vec![hir::Item::Fixity(item)])
        }

        ast::Decl::Foreign(foreign) => {
            let item = lower_foreign_decl(ctx, foreign)?;
            Ok(vec![hir::Item::Foreign(item)])
        }

        // Type signatures are associated with their definitions
        ast::Decl::TypeSig(_) => Ok(vec![]),

        // Pragmas in declarations are handled at parse time or ignored
        ast::Decl::PragmaDecl(_) => Ok(vec![]),

        ast::Decl::StandaloneDeriving(sd) => {
            let class = sd.class.name;
            let type_name = extract_type_name(&sd.ty);
            Ok(vec![hir::Item::StandaloneDeriving(
                hir::StandaloneDeriving {
                    class,
                    type_name,
                    span: sd.span,
                },
            )])
        }

        ast::Decl::PatternSynonym(_) => {
            // Pattern synonyms are fully handled during name collection
            // (collect_module_definitions) and expanded inline during
            // lower_pat / lower_expr. No HIR item needed.
            Ok(vec![])
        }

        ast::Decl::TypeFamilyDecl(tf_decl) => {
            let item = lower_type_family_decl(ctx, tf_decl)?;
            Ok(vec![hir::Item::TypeFamily(item)])
        }

        ast::Decl::TypeInstanceDecl(ti_decl) => {
            let item = lower_type_instance_decl(ctx, ti_decl);
            Ok(vec![hir::Item::TypeFamilyInst(item)])
        }

        ast::Decl::DataFamilyDecl(df_decl) => {
            let item = lower_data_family_decl(ctx, df_decl)?;
            Ok(vec![hir::Item::DataFamily(item)])
        }

        ast::Decl::DataInstanceDecl(di_decl) => {
            let item = lower_data_instance_decl(ctx, di_decl);
            Ok(vec![hir::Item::DataFamilyInst(item)])
        }
    }
}

/// Extract the type constructor name from a type (e.g., `Maybe Int` -> `Maybe`, `Foo` -> `Foo`).
fn extract_type_name(ty: &ast::Type) -> Symbol {
    match ty {
        ast::Type::Con(ident, _) => ident.name,
        ast::Type::App(f, _, _) => extract_type_name(f),
        ast::Type::Paren(inner, _) => extract_type_name(inner),
        _ => Symbol::intern("<unknown>"),
    }
}

/// Substitute pattern synonym arguments into the RHS pattern.
///
/// For `pattern Succ n = Add n One` used as `Succ x`:
/// - `syn_args` = ["n"], `syn_pat` = `Add n One`, `actual_args` = [`Pat::Var("x`")]
/// - Result: `Add x One` (substitute `n` → `x` in the pattern)
fn substitute_pattern_synonym(
    syn_pat: &ast::Pat,
    syn_args: &[Symbol],
    actual_args: &[ast::Pat],
    span: Span,
) -> ast::Pat {
    // Build substitution map: syn_arg_name -> actual_arg_pattern
    let subst: FxHashMap<Symbol, &ast::Pat> = syn_args
        .iter()
        .zip(actual_args.iter())
        .map(|(name, pat)| (*name, pat))
        .collect();

    subst_pat(syn_pat, &subst, span)
}

/// Convert a pattern to an expression (for bidirectional pattern synonyms).
///
/// `Con A B` → `App(App(Con(A), ...), ...)`, `Lit 42` → `Lit(42)`, `Var x` → `Var(x)`
fn pat_to_expr(pat: &ast::Pat, span: Span) -> ast::Expr {
    match pat {
        ast::Pat::Con(ident, args, _) => {
            let mut result = ast::Expr::Con(*ident, span);
            for arg in args {
                let arg_expr = pat_to_expr(arg, span);
                result = ast::Expr::App(Box::new(result), Box::new(arg_expr), span);
            }
            result
        }
        ast::Pat::Lit(lit, _) => ast::Expr::Lit(lit.clone(), span),
        ast::Pat::Var(ident, _) => ast::Expr::Var(*ident, span),
        ast::Pat::Tuple(pats, _) => {
            let exprs: Vec<ast::Expr> = pats.iter().map(|p| pat_to_expr(p, span)).collect();
            ast::Expr::Tuple(exprs, span)
        }
        ast::Pat::List(pats, _) => {
            let exprs: Vec<ast::Expr> = pats.iter().map(|p| pat_to_expr(p, span)).collect();
            ast::Expr::List(exprs, span)
        }
        ast::Pat::Paren(inner, _) => {
            let inner_expr = pat_to_expr(inner, span);
            ast::Expr::Paren(Box::new(inner_expr), span)
        }
        ast::Pat::Infix(lhs, op, rhs, _) => {
            let l = pat_to_expr(lhs, span);
            let r = pat_to_expr(rhs, span);
            ast::Expr::Infix(Box::new(l), *op, Box::new(r), span)
        }
        ast::Pat::Wildcard(_) => {
            // Wildcards in expression position shouldn't happen for bidirectional synonyms
            ast::Expr::Var(Ident::from_str("_"), span)
        }
        _ => ast::Expr::Var(Ident::from_str("_"), span),
    }
}

/// Recursively substitute variables in a pattern.
// `span` is threaded through for spans on rebuilt patterns (WIP: not all arms use it yet).
#[allow(clippy::only_used_in_recursion)]
fn subst_pat(pat: &ast::Pat, subst: &FxHashMap<Symbol, &ast::Pat>, span: Span) -> ast::Pat {
    match pat {
        ast::Pat::Var(ident, _) => {
            if let Some(replacement) = subst.get(&ident.name) {
                (*replacement).clone()
            } else {
                // Not a synonym arg — keep as variable
                pat.clone()
            }
        }
        ast::Pat::Con(ident, pats, s) => {
            let new_pats = pats.iter().map(|p| subst_pat(p, subst, span)).collect();
            ast::Pat::Con(*ident, new_pats, *s)
        }
        ast::Pat::Infix(lhs, op, rhs, s) => {
            let l = subst_pat(lhs, subst, span);
            let r = subst_pat(rhs, subst, span);
            ast::Pat::Infix(Box::new(l), *op, Box::new(r), *s)
        }
        ast::Pat::Tuple(pats, s) => {
            let new_pats = pats.iter().map(|p| subst_pat(p, subst, span)).collect();
            ast::Pat::Tuple(new_pats, *s)
        }
        ast::Pat::List(pats, s) => {
            let new_pats = pats.iter().map(|p| subst_pat(p, subst, span)).collect();
            ast::Pat::List(new_pats, *s)
        }
        ast::Pat::As(name, inner, s) => {
            let new_inner = subst_pat(inner, subst, span);
            ast::Pat::As(*name, Box::new(new_inner), *s)
        }
        ast::Pat::Paren(inner, s) => {
            let new_inner = subst_pat(inner, subst, span);
            ast::Pat::Paren(Box::new(new_inner), *s)
        }
        // Literals, wildcards, etc. pass through unchanged
        _ => pat.clone(),
    }
}

/// Lower a top-level pattern binding to multiple value definitions.
///
/// Look up (or dynamically register) a tuple constructor of the given arity.
fn ensure_tuple_constructor(ctx: &mut LowerContext, arity: usize, span: Span) -> hir::DefId {
    let tuple_sym = Symbol::intern(&format!("({})", ",".repeat(arity.saturating_sub(1))));
    if let Some(id) = ctx.lookup_constructor(tuple_sym) {
        return id;
    }
    // Register on-the-fly for large tuples beyond the builtins
    let type_name = Symbol::intern(&format!("Tuple{arity}"));
    let id = ctx.fresh_def_id();
    ctx.define_constructor_with_type(id, tuple_sym, span, arity, type_name, arity, None);
    id
}

/// For `(x, y) = (1, 2)`, generates:
/// - `x = let (x, y) = (1, 2) in x`
/// - `y = let (x, y) = (1, 2) in y`
fn lower_pattern_binding(
    ctx: &mut LowerContext,
    fun_bind: &ast::FunBind,
) -> LowerResult<Vec<hir::Item>> {
    let clause = &fun_bind.clauses[0];
    let pat = &clause.pats[0];

    // Collect all variables bound by the pattern
    let bound_vars = collect_pattern_vars(pat);
    if bound_vars.is_empty() {
        return Ok(vec![]);
    }

    // For each variable, generate a value definition
    let mut items = Vec::new();
    for (var_name, var_span) in bound_vars {
        // Look up the DefId for this variable (should have been bound in collect_module_definitions)
        let def_id = ctx
            .lookup_value(var_name)
            .expect("pattern variable should be pre-bound");

        // Create a let expression: let pat = rhs in var
        let rhs_expr = ctx.in_scope(|ctx| {
            // Bind pattern variables in this scope
            bind_pattern(ctx, pat);

            // Lower the pattern
            let hir_pat = lower_pat(ctx, pat);

            // Lower the RHS
            let hir_rhs = lower_rhs(ctx, &clause.rhs);

            // Create the binding
            let binding = hir::Binding {
                pat: hir_pat,
                sig: None,
                rhs: hir_rhs,
                span: fun_bind.span,
            };

            // Look up the variable's def_id in this scope
            let var_def_id = ctx
                .lookup_value(var_name)
                .expect("pattern variable should be bound");

            // Create the body: just reference the variable
            let body = hir::Expr::Var(ctx.def_ref(var_def_id, var_span));

            // Create the let expression
            hir::Expr::Let(vec![binding], Box::new(body), fun_bind.span)
        });

        // Create the value definition
        let value_def = hir::ValueDef {
            id: def_id,
            name: var_name,
            sig: None,
            equations: vec![hir::Equation {
                pats: vec![],
                guards: vec![],
                rhs: rhs_expr,
                span: fun_bind.span,
            }],
            span: fun_bind.span,
        };

        items.push(hir::Item::Value(value_def));
    }

    Ok(items)
}

/// Collect all variable names bound by a pattern.
fn collect_pattern_vars(pat: &ast::Pat) -> Vec<(Symbol, Span)> {
    let mut vars = Vec::new();
    collect_pattern_vars_impl(pat, &mut vars);
    vars
}

fn collect_pattern_vars_impl(pat: &ast::Pat, vars: &mut Vec<(Symbol, Span)>) {
    match pat {
        ast::Pat::Var(ident, span) => {
            vars.push((ident.name, *span));
        }
        ast::Pat::As(ident, inner, span) => {
            vars.push((ident.name, *span));
            collect_pattern_vars_impl(inner, vars);
        }
        ast::Pat::Con(_, pats, _)
        | ast::Pat::QualCon(_, _, pats, _)
        | ast::Pat::Tuple(pats, _)
        | ast::Pat::List(pats, _) => {
            for p in pats {
                collect_pattern_vars_impl(p, vars);
            }
        }
        ast::Pat::Infix(left, _, right, _) => {
            collect_pattern_vars_impl(left, vars);
            collect_pattern_vars_impl(right, vars);
        }
        ast::Pat::Record(_, fields, _, _) | ast::Pat::QualRecord(_, _, fields, _, _) => {
            for field in fields {
                if let Some(p) = &field.pat {
                    collect_pattern_vars_impl(p, vars);
                } else {
                    // Punning: Foo { x } binds x
                    vars.push((field.name.name, field.span));
                }
            }
        }
        ast::Pat::Paren(inner, _) | ast::Pat::Ann(inner, _, _) => {
            collect_pattern_vars_impl(inner, vars);
        }
        ast::Pat::Lazy(inner, _) | ast::Pat::Bang(inner, _) => {
            collect_pattern_vars_impl(inner, vars);
        }
        ast::Pat::View(_, result_pat, _) => {
            collect_pattern_vars_impl(result_pat, vars);
        }
        ast::Pat::Wildcard(_) | ast::Pat::Lit(_, _) => {}
    }
}

/// Lower a function binding.
fn lower_fun_bind(ctx: &mut LowerContext, fun_bind: &ast::FunBind) -> LowerResult<hir::ValueDef> {
    let name = fun_bind.name.name;
    let def_id = ctx
        .lookup_value(name)
        .expect("function should be pre-bound");

    let mut equations = Vec::new();
    for clause in &fun_bind.clauses {
        let eq = lower_clause(ctx, clause)?;
        equations.push(eq);
    }

    // Look up the type signature if one was declared
    let sig = ctx
        .lookup_type_signature(name)
        .cloned()
        .map(|ty| lower_type_to_scheme(ctx, &ty));

    Ok(hir::ValueDef {
        id: def_id,
        name,
        sig,
        equations,
        span: fun_bind.span,
    })
}

/// Lower an instance method binding with a specific `DefId`.
/// Unlike `lower_fun_bind`, this takes a pre-assigned `DefId` rather than looking
/// it up by name, since instance methods should NOT overwrite the builtin
/// method binding (e.g. `show` should still resolve to the polymorphic builtin
/// inside the method body).
fn lower_instance_method(
    ctx: &mut LowerContext,
    fun_bind: &ast::FunBind,
    def_id: hir::DefId,
) -> LowerResult<hir::ValueDef> {
    let name = fun_bind.name.name;

    let mut equations = Vec::new();
    for clause in &fun_bind.clauses {
        let eq = lower_clause(ctx, clause)?;
        equations.push(eq);
    }

    // Look up the type signature if one was declared
    let sig = ctx
        .lookup_type_signature(name)
        .cloned()
        .map(|ty| lower_type_to_scheme(ctx, &ty));

    Ok(hir::ValueDef {
        id: def_id,
        name,
        sig,
        equations,
        span: fun_bind.span,
    })
}

/// Lower a function clause.
fn lower_clause(ctx: &mut LowerContext, clause: &ast::Clause) -> LowerResult<hir::Equation> {
    ctx.in_scope(|ctx| {
        // Bind pattern variables
        let mut pats = Vec::new();
        for ast_pat in &clause.pats {
            bind_pattern(ctx, ast_pat);
            let pat = lower_pat(ctx, ast_pat);
            pats.push(pat);
        }

        // Lower where bindings first (they're in scope for RHS)
        if !clause.wheres.is_empty() {
            // Enter a scope for where bindings
            ctx.enter_scope();
            for where_decl in &clause.wheres {
                if let ast::Decl::FunBind(fb) = where_decl {
                    // Check for pattern binding (special name $patbind)
                    if fb.name.name.as_str() == "$patbind"
                        && fb.clauses.len() == 1
                        && fb.clauses[0].pats.len() == 1
                    {
                        // Pattern binding: bind all variables in the pattern
                        bind_pattern(ctx, &fb.clauses[0].pats[0]);
                    } else {
                        // Regular function binding
                        let def_id = ctx.fresh_def_id();
                        ctx.define(def_id, fb.name.name, DefKind::Value, fb.span);
                        ctx.bind_value(fb.name.name, def_id);
                    }
                }
            }
        }

        // Lower RHS
        let (rhs, guards) = match &clause.rhs {
            ast::Rhs::Simple(expr, _) => (lower_expr(ctx, expr), Vec::new()),
            ast::Rhs::Guarded(guarded_rhss, _) => {
                // For guarded RHS, we desugar to nested if expressions
                let rhs = desugar::desugar_guarded_rhs(
                    ctx,
                    guarded_rhss,
                    clause.span,
                    &|ctx, e| lower_expr(ctx, e),
                    &|ctx, p| lower_pat(ctx, p),
                );

                // We don't need guards in HIR for this; they're already desugared
                (rhs, Vec::new())
            }
        };

        // Wrap in let if there are where bindings
        let final_rhs = if clause.wheres.is_empty() {
            rhs
        } else {
            let bindings: Vec<hir::Binding> = clause
                .wheres
                .iter()
                .filter_map(|d| {
                    if let ast::Decl::FunBind(fb) = d {
                        // Check for pattern binding (special name $patbind)
                        if fb.name.name.as_str() == "$patbind"
                            && fb.clauses.len() == 1
                            && fb.clauses[0].pats.len() == 1
                        {
                            // Pattern binding: (x, y) = expr
                            let pat = lower_pat(ctx, &fb.clauses[0].pats[0]);
                            let rhs_expr = lower_rhs(ctx, &fb.clauses[0].rhs);
                            return Some(hir::Binding {
                                pat,
                                sig: None,
                                rhs: rhs_expr,
                                span: fb.span,
                            });
                        }

                        // Look up the DefId that was bound for this where binding
                        let def_id = ctx
                            .lookup_value(fb.name.name)
                            .expect("where binding should be bound");

                        // For simple bindings (no parameters)
                        if fb.clauses.len() == 1 && fb.clauses[0].pats.is_empty() {
                            let inner_clause = &fb.clauses[0];
                            let rhs_expr = if inner_clause.wheres.is_empty() {
                                lower_rhs(ctx, &inner_clause.rhs)
                            } else {
                                // Handle nested where bindings
                                ctx.enter_scope();
                                for nested_decl in &inner_clause.wheres {
                                    if let ast::Decl::FunBind(nested_fb) = nested_decl {
                                        // Check for pattern binding ($patbind)
                                        if nested_fb.name.name.as_str() == "$patbind"
                                            && nested_fb.clauses.len() == 1
                                            && nested_fb.clauses[0].pats.len() == 1
                                        {
                                            bind_pattern(ctx, &nested_fb.clauses[0].pats[0]);
                                        } else {
                                            let nested_def_id = ctx.fresh_def_id();
                                            ctx.define(
                                                nested_def_id,
                                                nested_fb.name.name,
                                                DefKind::Value,
                                                nested_fb.span,
                                            );
                                            ctx.bind_value(nested_fb.name.name, nested_def_id);
                                        }
                                    }
                                }
                                let body = lower_rhs(ctx, &inner_clause.rhs);
                                let nested_bindings: Vec<hir::Binding> = inner_clause
                                    .wheres
                                    .iter()
                                    .filter_map(|d| {
                                        if let ast::Decl::FunBind(nested_fb) = d {
                                            // Handle pattern bindings ($patbind)
                                            if nested_fb.name.name.as_str() == "$patbind"
                                                && nested_fb.clauses.len() == 1
                                                && nested_fb.clauses[0].pats.len() == 1
                                            {
                                                let pat =
                                                    lower_pat(ctx, &nested_fb.clauses[0].pats[0]);
                                                let rhs_expr =
                                                    lower_rhs(ctx, &nested_fb.clauses[0].rhs);
                                                return Some(hir::Binding {
                                                    pat,
                                                    sig: None,
                                                    rhs: rhs_expr,
                                                    span: nested_fb.span,
                                                });
                                            }
                                            let nested_def_id = ctx
                                                .lookup_value(nested_fb.name.name)
                                                .expect("nested where binding should be bound");
                                            if nested_fb.clauses.len() == 1
                                                && nested_fb.clauses[0].pats.is_empty()
                                            {
                                                let nested_rhs =
                                                    lower_rhs(ctx, &nested_fb.clauses[0].rhs);
                                                return Some(hir::Binding {
                                                    pat: hir::Pat::Var(
                                                        nested_fb.name.name,
                                                        nested_def_id,
                                                        nested_fb.span,
                                                    ),
                                                    sig: None,
                                                    rhs: nested_rhs,
                                                    span: nested_fb.span,
                                                });
                                            }
                                        }
                                        None
                                    })
                                    .collect();
                                ctx.exit_scope();
                                hir::Expr::Let(nested_bindings, Box::new(body), fb.span)
                            };
                            return Some(hir::Binding {
                                pat: hir::Pat::Var(fb.name.name, def_id, fb.span),
                                sig: None,
                                rhs: rhs_expr,
                                span: fb.span,
                            });
                        }

                        // For function bindings with parameters, lower to a lambda
                        // f x y = expr  =>  f = \x -> \y -> expr
                        if fb.clauses.len() == 1 {
                            let clause = &fb.clauses[0];
                            // Enter scope and bind pattern variables
                            ctx.enter_scope();
                            for p in &clause.pats {
                                bind_pattern(ctx, p);
                            }
                            let mut pats: Vec<hir::Pat> = Vec::new();
                            for p in &clause.pats {
                                pats.push(lower_pat(ctx, p));
                            }

                            // Handle nested where bindings
                            let body = if clause.wheres.is_empty() {
                                lower_rhs(ctx, &clause.rhs)
                            } else {
                                ctx.enter_scope();
                                // Pre-bind nested where names
                                for nested_decl in &clause.wheres {
                                    if let ast::Decl::FunBind(nested_fb) = nested_decl {
                                        // Check for pattern binding ($patbind)
                                        if nested_fb.name.name.as_str() == "$patbind"
                                            && nested_fb.clauses.len() == 1
                                            && nested_fb.clauses[0].pats.len() == 1
                                        {
                                            bind_pattern(ctx, &nested_fb.clauses[0].pats[0]);
                                        } else {
                                            let nested_def_id = ctx.fresh_def_id();
                                            ctx.define(
                                                nested_def_id,
                                                nested_fb.name.name,
                                                DefKind::Value,
                                                nested_fb.span,
                                            );
                                            ctx.bind_value(nested_fb.name.name, nested_def_id);
                                        }
                                    }
                                }
                                let rhs_expr = lower_rhs(ctx, &clause.rhs);
                                // Lower nested where bindings into a Let
                                let nested_bindings: Vec<hir::Binding> = clause
                                    .wheres
                                    .iter()
                                    .filter_map(|d| {
                                        if let ast::Decl::FunBind(nested_fb) = d {
                                            // Handle pattern bindings ($patbind)
                                            if nested_fb.name.name.as_str() == "$patbind"
                                                && nested_fb.clauses.len() == 1
                                                && nested_fb.clauses[0].pats.len() == 1
                                            {
                                                let pat =
                                                    lower_pat(ctx, &nested_fb.clauses[0].pats[0]);
                                                let rhs_expr =
                                                    lower_rhs(ctx, &nested_fb.clauses[0].rhs);
                                                return Some(hir::Binding {
                                                    pat,
                                                    sig: None,
                                                    rhs: rhs_expr,
                                                    span: nested_fb.span,
                                                });
                                            }
                                            let nested_def_id = ctx
                                                .lookup_value(nested_fb.name.name)
                                                .expect("nested where binding should be bound");
                                            if nested_fb.clauses.len() == 1
                                                && nested_fb.clauses[0].pats.is_empty()
                                            {
                                                let nested_rhs =
                                                    lower_rhs(ctx, &nested_fb.clauses[0].rhs);
                                                return Some(hir::Binding {
                                                    pat: hir::Pat::Var(
                                                        nested_fb.name.name,
                                                        nested_def_id,
                                                        nested_fb.span,
                                                    ),
                                                    sig: None,
                                                    rhs: nested_rhs,
                                                    span: nested_fb.span,
                                                });
                                            }
                                        }
                                        None
                                    })
                                    .collect();
                                ctx.exit_scope();
                                hir::Expr::Let(nested_bindings, Box::new(rhs_expr), fb.span)
                            };

                            ctx.exit_scope();
                            // Create a lambda expression
                            let lam = hir::Expr::Lam(pats, Box::new(body), fb.span);
                            return Some(hir::Binding {
                                pat: hir::Pat::Var(fb.name.name, def_id, fb.span),
                                sig: None,
                                rhs: lam,
                                span: fb.span,
                            });
                        }

                        // For multi-clause functions, create a lambda with case
                        // f 0 = a; f n = b  =>  f = \x -> case x of { 0 -> a; n -> b }
                        if !fb.clauses.is_empty() && !fb.clauses[0].pats.is_empty() {
                            let arity = fb.clauses[0].pats.len();
                            // Create fresh variables for the lambda parameters
                            let mut param_names = Vec::new();
                            let mut param_pats = Vec::new();
                            for i in 0..arity {
                                let name = Symbol::intern(&format!("_arg{i}"));
                                let param_def_id = ctx.fresh_def_id();
                                ctx.define(param_def_id, name, DefKind::Value, fb.span);
                                ctx.bind_value(name, param_def_id);
                                param_names.push((name, param_def_id));
                                param_pats.push(hir::Pat::Var(name, param_def_id, fb.span));
                            }

                            // Build case alternatives from clauses
                            let mut alts = Vec::new();
                            for clause in &fb.clauses {
                                // Enter a scope for this alternative's pattern bindings
                                ctx.enter_scope();

                                // First bind all pattern variables
                                for p in &clause.pats {
                                    bind_pattern(ctx, p);
                                }

                                // Create a tuple pattern from clause patterns
                                let pat = if clause.pats.len() == 1 {
                                    lower_pat(ctx, &clause.pats[0])
                                } else {
                                    // Create a tuple pattern for multi-argument case
                                    let tuple_def_id =
                                        ensure_tuple_constructor(ctx, clause.pats.len(), fb.span);
                                    let tuple_ref = ctx.def_ref(tuple_def_id, fb.span);
                                    hir::Pat::Con(
                                        tuple_ref,
                                        clause.pats.iter().map(|p| lower_pat(ctx, p)).collect(),
                                        fb.span,
                                    )
                                };

                                // Handle per-clause where bindings
                                let body = if clause.wheres.is_empty() {
                                    lower_rhs(ctx, &clause.rhs)
                                } else {
                                    ctx.enter_scope();
                                    // Pre-bind where names
                                    for wd in &clause.wheres {
                                        if let ast::Decl::FunBind(wfb) = wd {
                                            if wfb.name.name.as_str() == "$patbind"
                                                && wfb.clauses.len() == 1
                                                && wfb.clauses[0].pats.len() == 1
                                            {
                                                bind_pattern(ctx, &wfb.clauses[0].pats[0]);
                                            } else {
                                                let wd_id = ctx.fresh_def_id();
                                                ctx.define(
                                                    wd_id,
                                                    wfb.name.name,
                                                    DefKind::Value,
                                                    wfb.span,
                                                );
                                                ctx.bind_value(wfb.name.name, wd_id);
                                            }
                                        }
                                    }
                                    let rhs_expr = lower_rhs(ctx, &clause.rhs);
                                    // Lower where bindings
                                    let wbs: Vec<hir::Binding> = clause
                                        .wheres
                                        .iter()
                                        .filter_map(|wd| {
                                            if let ast::Decl::FunBind(wfb) = wd {
                                                if wfb.name.name.as_str() == "$patbind"
                                                    && wfb.clauses.len() == 1
                                                    && wfb.clauses[0].pats.len() == 1
                                                {
                                                    let wpat =
                                                        lower_pat(ctx, &wfb.clauses[0].pats[0]);
                                                    let wrhs = lower_rhs(ctx, &wfb.clauses[0].rhs);
                                                    return Some(hir::Binding {
                                                        pat: wpat,
                                                        sig: None,
                                                        rhs: wrhs,
                                                        span: wfb.span,
                                                    });
                                                }
                                                let wd_id = ctx
                                                    .lookup_value(wfb.name.name)
                                                    .expect("where binding should be bound");
                                                if wfb.clauses.len() == 1
                                                    && wfb.clauses[0].pats.is_empty()
                                                {
                                                    let wrhs = lower_rhs(ctx, &wfb.clauses[0].rhs);
                                                    return Some(hir::Binding {
                                                        pat: hir::Pat::Var(
                                                            wfb.name.name,
                                                            wd_id,
                                                            wfb.span,
                                                        ),
                                                        sig: None,
                                                        rhs: wrhs,
                                                        span: wfb.span,
                                                    });
                                                }
                                                // For where-bindings with parameters, lower to lambda
                                                if wfb.clauses.len() == 1 {
                                                    let wc = &wfb.clauses[0];
                                                    ctx.enter_scope();
                                                    for wp in &wc.pats {
                                                        bind_pattern(ctx, wp);
                                                    }
                                                    let wpats: Vec<hir::Pat> = wc
                                                        .pats
                                                        .iter()
                                                        .map(|wp| lower_pat(ctx, wp))
                                                        .collect();
                                                    let wbody = lower_rhs(ctx, &wc.rhs);
                                                    ctx.exit_scope();
                                                    let wlam = hir::Expr::Lam(
                                                        wpats,
                                                        Box::new(wbody),
                                                        wfb.span,
                                                    );
                                                    return Some(hir::Binding {
                                                        pat: hir::Pat::Var(
                                                            wfb.name.name,
                                                            wd_id,
                                                            wfb.span,
                                                        ),
                                                        sig: None,
                                                        rhs: wlam,
                                                        span: wfb.span,
                                                    });
                                                }
                                            }
                                            None
                                        })
                                        .collect();
                                    ctx.exit_scope();
                                    hir::Expr::Let(wbs, Box::new(rhs_expr), clause.span)
                                };
                                ctx.exit_scope();
                                alts.push(hir::CaseAlt {
                                    pat,
                                    guards: Vec::new(),
                                    rhs: body,
                                    span: clause.span,
                                });
                            }

                            // Create the scrutinee (tuple of params or single param)
                            let scrutinee = if param_names.len() == 1 {
                                hir::Expr::Var(hir::DefRef {
                                    def_id: param_names[0].1,
                                    span: fb.span,
                                })
                            } else {
                                hir::Expr::Tuple(
                                    param_names
                                        .iter()
                                        .map(|(_, def_id)| {
                                            hir::Expr::Var(hir::DefRef {
                                                def_id: *def_id,
                                                span: fb.span,
                                            })
                                        })
                                        .collect(),
                                    fb.span,
                                )
                            };

                            let case_expr = hir::Expr::Case(Box::new(scrutinee), alts, fb.span);

                            let lam = hir::Expr::Lam(param_pats, Box::new(case_expr), fb.span);
                            return Some(hir::Binding {
                                pat: hir::Pat::Var(fb.name.name, def_id, fb.span),
                                sig: None,
                                rhs: lam,
                                span: fb.span,
                            });
                        }
                    }
                    None
                })
                .collect();

            ctx.exit_scope();

            if bindings.is_empty() {
                rhs
            } else {
                hir::Expr::Let(bindings, Box::new(rhs), clause.span)
            }
        };

        Ok(hir::Equation {
            pats,
            guards,
            rhs: final_rhs,
            span: clause.span,
        })
    })
}

/// Lower a right-hand side.
/// Lower a function body that has where-clause bindings.
/// Enters a scope, pre-binds where names, lowers the body, wraps in Let.
fn lower_body_with_wheres(
    ctx: &mut LowerContext,
    rhs: &ast::Rhs,
    wheres: &[ast::Decl],
    span: Span,
) -> hir::Expr {
    ctx.enter_scope();
    // Pre-bind nested where names
    for nested_decl in wheres {
        if let ast::Decl::FunBind(nested_fb) = nested_decl {
            if nested_fb.name.name.as_str() == "$patbind"
                && nested_fb.clauses.len() == 1
                && nested_fb.clauses[0].pats.len() == 1
            {
                bind_pattern(ctx, &nested_fb.clauses[0].pats[0]);
            } else {
                let nested_def_id = ctx.fresh_def_id();
                ctx.define(
                    nested_def_id,
                    nested_fb.name.name,
                    DefKind::Value,
                    nested_fb.span,
                );
                ctx.bind_value(nested_fb.name.name, nested_def_id);
            }
        }
    }
    let rhs_expr = lower_rhs(ctx, rhs);
    // Lower nested where bindings
    let nested_bindings: Vec<hir::Binding> = wheres
        .iter()
        .filter_map(|d| {
            if let ast::Decl::FunBind(nested_fb) = d {
                // Handle pattern bindings ($patbind)
                if nested_fb.name.name.as_str() == "$patbind"
                    && nested_fb.clauses.len() == 1
                    && nested_fb.clauses[0].pats.len() == 1
                {
                    let pat = lower_pat(ctx, &nested_fb.clauses[0].pats[0]);
                    let nested_rhs = lower_rhs(ctx, &nested_fb.clauses[0].rhs);
                    return Some(hir::Binding {
                        pat,
                        sig: None,
                        rhs: nested_rhs,
                        span: nested_fb.span,
                    });
                }
                let nested_def_id = ctx
                    .lookup_value(nested_fb.name.name)
                    .expect("nested where binding should be bound");
                if nested_fb.clauses.len() == 1 && nested_fb.clauses[0].pats.is_empty() {
                    let nested_rhs = lower_rhs(ctx, &nested_fb.clauses[0].rhs);
                    return Some(hir::Binding {
                        pat: hir::Pat::Var(nested_fb.name.name, nested_def_id, nested_fb.span),
                        sig: None,
                        rhs: nested_rhs,
                        span: nested_fb.span,
                    });
                }
                // Single-clause with parameters: lower to lambda
                if nested_fb.clauses.len() == 1 {
                    let clause = &nested_fb.clauses[0];
                    ctx.enter_scope();
                    for p in &clause.pats {
                        bind_pattern(ctx, p);
                    }
                    let pats: Vec<hir::Pat> =
                        clause.pats.iter().map(|p| lower_pat(ctx, p)).collect();
                    let body = lower_rhs(ctx, &clause.rhs);
                    ctx.exit_scope();
                    let lam = hir::Expr::Lam(pats, Box::new(body), nested_fb.span);
                    return Some(hir::Binding {
                        pat: hir::Pat::Var(nested_fb.name.name, nested_def_id, nested_fb.span),
                        sig: None,
                        rhs: lam,
                        span: nested_fb.span,
                    });
                }
            }
            None
        })
        .collect();
    ctx.exit_scope();
    hir::Expr::Let(nested_bindings, Box::new(rhs_expr), span)
}

fn lower_rhs(ctx: &mut LowerContext, rhs: &ast::Rhs) -> hir::Expr {
    match rhs {
        ast::Rhs::Simple(expr, _) => lower_expr(ctx, expr),
        ast::Rhs::Guarded(guards, span) => desugar::desugar_guarded_rhs(
            ctx,
            guards,
            *span,
            &|ctx, e| lower_expr(ctx, e),
            &|ctx, p| lower_pat(ctx, p),
        ),
    }
}

/// Lower an expression.
fn lower_expr(ctx: &mut LowerContext, expr: &ast::Expr) -> hir::Expr {
    match expr {
        ast::Expr::Var(ident, span) => {
            let name = ident.name;
            if let Some(def_id) = resolve_var(ctx, name, *span) {
                hir::Expr::Var(ctx.def_ref(def_id, *span))
            } else {
                // Create placeholder for error recovery
                let def_id = ctx.fresh_def_id();
                ctx.define(def_id, name, DefKind::Value, *span);
                hir::Expr::Var(ctx.def_ref(def_id, *span))
            }
        }

        ast::Expr::QualVar(module_name, ident, span) => {
            // Qualified variable like M.foo or Data.Map.lookup
            let qualifier = Symbol::intern(&module_name.to_string());
            let name = ident.name;

            // Use the qualified name resolution which handles aliases
            if let Some(def_id) = ctx.resolve_qualified_var(qualifier, name) {
                // Warn if this is a stub
                let qual_name = format!("{}.{}", module_name, name.as_str());
                ctx.warn_if_stub(def_id, &qual_name, *span);
                hir::Expr::Var(ctx.def_ref(def_id, *span))
            } else {
                // Fall back to creating a placeholder with the full qualified name
                let qual_name = format!("{}.{}", module_name, name.as_str());
                let qual_sym = Symbol::intern(&qual_name);
                ctx.error(crate::LowerError::UnboundVar {
                    name: qual_name,
                    span: *span,
                });
                let def_id = ctx.fresh_def_id();
                ctx.define(def_id, qual_sym, DefKind::Value, *span);
                hir::Expr::Var(ctx.def_ref(def_id, *span))
            }
        }

        ast::Expr::Con(ident, span) => {
            let name = ident.name;
            // Check if this is a bidirectional pattern synonym in expression position
            if let Some((syn_args, syn_pat)) = ctx.lookup_pattern_synonym(name).cloned() {
                if syn_args.is_empty() {
                    // 0-arg synonym: convert RHS pattern to expression
                    let expr = pat_to_expr(&syn_pat, *span);
                    return lower_expr(ctx, &expr);
                } else {
                    // N-arg synonym: create a lambda
                    // pattern Succ n = Add n One  →  \n -> Add n One
                    let body = pat_to_expr(&syn_pat, *span);
                    let mut result = body;
                    for arg in syn_args.iter().rev() {
                        result = ast::Expr::Lam(
                            vec![ast::Pat::Var(Ident::new(*arg), *span)],
                            Box::new(result),
                            *span,
                        );
                    }
                    return lower_expr(ctx, &result);
                }
            }
            if let Some(def_id) = resolve_constructor(ctx, name, *span) {
                hir::Expr::Con(ctx.def_ref(def_id, *span))
            } else {
                let def_id = ctx.fresh_def_id();
                ctx.define(def_id, name, DefKind::Constructor, *span);
                hir::Expr::Con(ctx.def_ref(def_id, *span))
            }
        }

        ast::Expr::QualCon(module_name, ident, span) => {
            // Qualified constructor like M.Just or Data.Maybe.Just
            let qualifier = Symbol::intern(&module_name.to_string());
            let name = ident.name;

            // Use the qualified constructor resolution which handles aliases
            if let Some(def_id) = ctx.resolve_qualified_constructor(qualifier, name) {
                // Warn if this is a stub
                let qual_name = format!("{}.{}", module_name, name.as_str());
                ctx.warn_if_stub(def_id, &qual_name, *span);
                hir::Expr::Con(ctx.def_ref(def_id, *span))
            } else {
                // Fall back to creating a placeholder with the full qualified name
                let qual_name = format!("{}.{}", module_name, name.as_str());
                let qual_sym = Symbol::intern(&qual_name);
                ctx.error(crate::LowerError::UnboundCon {
                    name: qual_name,
                    span: *span,
                });
                let def_id = ctx.fresh_def_id();
                ctx.define(def_id, qual_sym, DefKind::Constructor, *span);
                hir::Expr::Con(ctx.def_ref(def_id, *span))
            }
        }

        ast::Expr::Lit(lit, span) => {
            let hir_lit = lower_lit(lit);
            hir::Expr::Lit(hir_lit, *span)
        }

        ast::Expr::App(fun, arg, span) => {
            let f = lower_expr(ctx, fun);
            let a = lower_expr(ctx, arg);
            hir::Expr::App(Box::new(f), Box::new(a), *span)
        }

        ast::Expr::Infix(lhs, op, rhs, span) => {
            // Desugar infix to prefix: a `op` b -> op a b
            // For qualified names like "E.catch", check the last part (after the last dot)
            let op_name_str = op.name.as_str();
            let is_con = if let Some(dot_pos) = op_name_str.rfind('.') {
                // Only treat as qualified if the part before the dot looks like a module
                // (starts with uppercase). Otherwise `.:` would incorrectly split into
                // qualifier="" and operator ":" which starts with ':'.
                let before_dot = &op_name_str[..dot_pos];
                let is_qualified = before_dot.chars().next().is_some_and(char::is_uppercase);
                if is_qualified {
                    // Qualified name - check the part after the last dot
                    op_name_str[dot_pos + 1..]
                        .chars()
                        .next()
                        .is_some_and(|c| c.is_uppercase() || c == ':')
                } else {
                    // Not a real qualifier (e.g. `.:` operator) - check first char
                    op_name_str
                        .chars()
                        .next()
                        .is_some_and(|c| c.is_uppercase() || c == ':')
                }
            } else {
                // Unqualified name - check the first character
                op_name_str
                    .chars()
                    .next()
                    .is_some_and(|c| c.is_uppercase() || c == ':')
            };
            let op_expr = if is_con {
                // Constructor
                if let Some(def_id) = resolve_constructor(ctx, op.name, *span) {
                    hir::Expr::Con(ctx.def_ref(def_id, *span))
                } else {
                    hir::Expr::Error(*span)
                }
            } else if let Some(dot_pos) = op_name_str.rfind('.') {
                // Qualified variable/operator in backtick position, e.g. `B.isPrefixOf`
                let qualifier = Symbol::intern(&op_name_str[..dot_pos]);
                let base_name = Symbol::intern(&op_name_str[dot_pos + 1..]);
                if let Some(def_id) = ctx.resolve_qualified_var(qualifier, base_name) {
                    ctx.warn_if_stub(def_id, op_name_str, *span);
                    hir::Expr::Var(ctx.def_ref(def_id, *span))
                } else {
                    ctx.error(crate::LowerError::UnboundVar {
                        name: op_name_str.to_string(),
                        span: *span,
                    });
                    let def_id = ctx.fresh_def_id();
                    ctx.define(def_id, op.name, DefKind::Value, *span);
                    hir::Expr::Var(ctx.def_ref(def_id, *span))
                }
            } else {
                // Unqualified variable/operator
                if let Some(def_id) = resolve_var(ctx, op.name, *span) {
                    hir::Expr::Var(ctx.def_ref(def_id, *span))
                } else {
                    // Create placeholder
                    let def_id = ctx.fresh_def_id();
                    ctx.define(def_id, op.name, DefKind::Value, *span);
                    hir::Expr::Var(ctx.def_ref(def_id, *span))
                }
            };
            let l = lower_expr(ctx, lhs);
            let r = lower_expr(ctx, rhs);
            let app1 = hir::Expr::App(Box::new(op_expr), Box::new(l), *span);
            hir::Expr::App(Box::new(app1), Box::new(r), *span)
        }

        ast::Expr::Neg(inner, span) => {
            // Desugar negation: -e -> negate e
            let negate_sym = Symbol::intern("negate");
            if let Some(def_id) = ctx.lookup_value(negate_sym) {
                let negate = hir::Expr::Var(ctx.def_ref(def_id, *span));
                let e = lower_expr(ctx, inner);
                hir::Expr::App(Box::new(negate), Box::new(e), *span)
            } else {
                hir::Expr::Error(*span)
            }
        }

        ast::Expr::Lam(pats, body, span) => ctx.in_scope(|ctx| {
            let mut hir_pats = Vec::new();
            for p in pats {
                bind_pattern(ctx, p);
                hir_pats.push(lower_pat(ctx, p));
            }
            let e = lower_expr(ctx, body);
            hir::Expr::Lam(hir_pats, Box::new(e), *span)
        }),

        ast::Expr::Let(decls, body, span) => ctx.in_scope(|ctx| {
            // Bind all declarations first
            for decl in decls {
                if let ast::Decl::FunBind(fb) = decl {
                    // Check for pattern binding (special name $patbind)
                    if fb.name.name.as_str() == "$patbind"
                        && fb.clauses.len() == 1
                        && fb.clauses[0].pats.len() == 1
                    {
                        // Pattern binding: (x, y) = expr
                        // Bind variables from the pattern
                        bind_pattern(ctx, &fb.clauses[0].pats[0]);
                    } else {
                        // Regular function binding
                        let def_id = ctx.fresh_def_id();
                        ctx.define(def_id, fb.name.name, DefKind::Value, fb.span);
                        ctx.bind_value(fb.name.name, def_id);
                    }
                }
            }

            // Lower bindings
            let bindings: Vec<hir::Binding> = decls
                .iter()
                .filter_map(|d| {
                    if let ast::Decl::FunBind(fb) = d {
                        // Check for pattern binding (special name $patbind)
                        if fb.name.name.as_str() == "$patbind"
                            && fb.clauses.len() == 1
                            && fb.clauses[0].pats.len() == 1
                        {
                            // Pattern binding: (x, y) = expr
                            let pat = lower_pat(ctx, &fb.clauses[0].pats[0]);
                            let rhs_expr = lower_rhs(ctx, &fb.clauses[0].rhs);
                            return Some(hir::Binding {
                                pat,
                                sig: None,
                                rhs: rhs_expr,
                                span: fb.span,
                            });
                        }

                        let def_id = ctx
                            .lookup_value(fb.name.name)
                            .expect("let binding should be bound");

                        // Simple binding (no parameters): let x = expr
                        if fb.clauses.len() == 1 && fb.clauses[0].pats.is_empty() {
                            let clause = &fb.clauses[0];
                            let rhs_expr = if clause.wheres.is_empty() {
                                lower_rhs(ctx, &clause.rhs)
                            } else {
                                lower_body_with_wheres(ctx, &clause.rhs, &clause.wheres, fb.span)
                            };
                            return Some(hir::Binding {
                                pat: hir::Pat::Var(fb.name.name, def_id, fb.span),
                                sig: None,
                                rhs: rhs_expr,
                                span: fb.span,
                            });
                        }

                        // Single-clause function: let f x y = expr => f = \x -> \y -> expr
                        if fb.clauses.len() == 1 {
                            let clause = &fb.clauses[0];
                            ctx.enter_scope();
                            for p in &clause.pats {
                                bind_pattern(ctx, p);
                            }
                            let mut pats: Vec<hir::Pat> = Vec::new();
                            for p in &clause.pats {
                                pats.push(lower_pat(ctx, p));
                            }
                            let body_expr = if clause.wheres.is_empty() {
                                lower_rhs(ctx, &clause.rhs)
                            } else {
                                lower_body_with_wheres(ctx, &clause.rhs, &clause.wheres, fb.span)
                            };
                            ctx.exit_scope();
                            let lam = hir::Expr::Lam(pats, Box::new(body_expr), fb.span);
                            return Some(hir::Binding {
                                pat: hir::Pat::Var(fb.name.name, def_id, fb.span),
                                sig: None,
                                rhs: lam,
                                span: fb.span,
                            });
                        }

                        // Multi-clause function: let f 0 = a; f n = b => f = \x -> case x of ...
                        if !fb.clauses.is_empty() && !fb.clauses[0].pats.is_empty() {
                            let arity = fb.clauses[0].pats.len();
                            // Create fresh variables for the lambda parameters
                            let mut param_names = Vec::new();
                            let mut param_pats = Vec::new();
                            for i in 0..arity {
                                let name = Symbol::intern(&format!("_arg{i}"));
                                let param_def_id = ctx.fresh_def_id();
                                ctx.define(param_def_id, name, DefKind::Value, fb.span);
                                ctx.bind_value(name, param_def_id);
                                param_names.push((name, param_def_id));
                                param_pats.push(hir::Pat::Var(name, param_def_id, fb.span));
                            }

                            // Build case alternatives from clauses
                            let mut alts = Vec::new();
                            for clause in &fb.clauses {
                                ctx.enter_scope();
                                for p in &clause.pats {
                                    bind_pattern(ctx, p);
                                }
                                let pat = if clause.pats.len() == 1 {
                                    lower_pat(ctx, &clause.pats[0])
                                } else {
                                    let tuple_def_id =
                                        ensure_tuple_constructor(ctx, clause.pats.len(), fb.span);
                                    let tuple_ref = ctx.def_ref(tuple_def_id, fb.span);
                                    hir::Pat::Con(
                                        tuple_ref,
                                        clause.pats.iter().map(|p| lower_pat(ctx, p)).collect(),
                                        fb.span,
                                    )
                                };
                                let clause_body = if clause.wheres.is_empty() {
                                    lower_rhs(ctx, &clause.rhs)
                                } else {
                                    lower_body_with_wheres(
                                        ctx,
                                        &clause.rhs,
                                        &clause.wheres,
                                        fb.span,
                                    )
                                };
                                ctx.exit_scope();
                                alts.push(hir::CaseAlt {
                                    pat,
                                    guards: Vec::new(),
                                    rhs: clause_body,
                                    span: clause.span,
                                });
                            }

                            // Create the scrutinee (tuple of params or single param)
                            let scrutinee = if param_names.len() == 1 {
                                hir::Expr::Var(hir::DefRef {
                                    def_id: param_names[0].1,
                                    span: fb.span,
                                })
                            } else {
                                let tuple_elems: Vec<hir::Expr> = param_names
                                    .iter()
                                    .map(|(_n, id)| {
                                        hir::Expr::Var(hir::DefRef {
                                            def_id: *id,
                                            span: fb.span,
                                        })
                                    })
                                    .collect();
                                hir::Expr::Tuple(tuple_elems, fb.span)
                            };

                            let case_expr = hir::Expr::Case(Box::new(scrutinee), alts, fb.span);
                            let lam = hir::Expr::Lam(param_pats, Box::new(case_expr), fb.span);
                            return Some(hir::Binding {
                                pat: hir::Pat::Var(fb.name.name, def_id, fb.span),
                                sig: None,
                                rhs: lam,
                                span: fb.span,
                            });
                        }
                    }
                    None
                })
                .collect();

            let e = lower_expr(ctx, body);
            hir::Expr::Let(bindings, Box::new(e), *span)
        }),

        ast::Expr::If(cond, then_branch, else_branch, span) => {
            let c = lower_expr(ctx, cond);
            let t = lower_expr(ctx, then_branch);
            let e = lower_expr(ctx, else_branch);
            hir::Expr::If(Box::new(c), Box::new(t), Box::new(e), *span)
        }

        ast::Expr::Case(scrutinee, alts, span) => {
            let s = lower_expr(ctx, scrutinee);
            let hir_alts: Vec<hir::CaseAlt> = alts.iter().map(|alt| lower_alt(ctx, alt)).collect();
            hir::Expr::Case(Box::new(s), hir_alts, *span)
        }

        ast::Expr::Do(stmts, span) => desugar::desugar_do(ctx, stmts, *span, lower_expr, lower_pat),

        ast::Expr::ListComp(expr, stmts, span) => {
            desugar::desugar_list_comp(ctx, expr, stmts, *span, lower_expr, lower_pat)
        }

        ast::Expr::Tuple(exprs, span) => {
            let es: Vec<hir::Expr> = exprs.iter().map(|e| lower_expr(ctx, e)).collect();
            hir::Expr::Tuple(es, *span)
        }

        ast::Expr::List(exprs, span) => {
            let es: Vec<hir::Expr> = exprs.iter().map(|e| lower_expr(ctx, e)).collect();
            hir::Expr::List(es, *span)
        }

        ast::Expr::ArithSeq(seq, span) => {
            // Desugar arithmetic sequences
            lower_arith_seq(ctx, seq, *span)
        }

        ast::Expr::RecordCon(con, fields, has_wildcard, span) => {
            let con_name = con.name;
            if let Some(def_id) = resolve_constructor(ctx, con_name, *span) {
                let con_ref = ctx.def_ref(def_id, *span);
                let mut hir_fields = Vec::with_capacity(fields.len());
                for f in fields {
                    let value = if let Some(e) = &f.value {
                        lower_expr(ctx, e)
                    } else {
                        // Punning: Foo { x } means Foo { x = x }
                        let name = f.name.name;
                        if let Some(def_id) = ctx.lookup_value(name) {
                            hir::Expr::Var(ctx.def_ref(def_id, f.span))
                        } else {
                            hir::Expr::Error(f.span)
                        }
                    };
                    hir_fields.push(hir::FieldExpr {
                        name: f.name.name,
                        value,
                        span: f.span,
                    });
                }
                // RecordWildCards: expand `..` to include remaining fields from scope
                if *has_wildcard {
                    if let Some(field_names) = ctx.get_constructor_field_names(def_id) {
                        let existing: rustc_hash::FxHashSet<_> =
                            hir_fields.iter().map(|f| f.name).collect();
                        for field_name in field_names {
                            if !existing.contains(&field_name) {
                                if let Some(val_id) = ctx.lookup_value(field_name) {
                                    hir_fields.push(hir::FieldExpr {
                                        name: field_name,
                                        value: hir::Expr::Var(ctx.def_ref(val_id, *span)),
                                        span: *span,
                                    });
                                }
                            }
                        }
                    }
                }
                hir::Expr::Record(con_ref, hir_fields, *span)
            } else {
                hir::Expr::Error(*span)
            }
        }

        ast::Expr::QualRecordCon(module_name, con, fields, has_wildcard, span) => {
            let qualifier = Symbol::intern(&module_name.to_string());
            let con_name = con.name;
            if let Some(def_id) = ctx.resolve_qualified_constructor(qualifier, con_name) {
                let con_ref = ctx.def_ref(def_id, *span);
                let mut hir_fields = Vec::with_capacity(fields.len());
                for f in fields {
                    let value = if let Some(e) = &f.value {
                        lower_expr(ctx, e)
                    } else {
                        // Punning: Foo { x } means Foo { x = x }
                        let name = f.name.name;
                        if let Some(def_id) = ctx.lookup_value(name) {
                            hir::Expr::Var(ctx.def_ref(def_id, f.span))
                        } else {
                            hir::Expr::Error(f.span)
                        }
                    };
                    hir_fields.push(hir::FieldExpr {
                        name: f.name.name,
                        value,
                        span: f.span,
                    });
                }
                if *has_wildcard {
                    if let Some(field_names) = ctx.get_constructor_field_names(def_id) {
                        let existing: rustc_hash::FxHashSet<_> =
                            hir_fields.iter().map(|f| f.name).collect();
                        for field_name in field_names {
                            if !existing.contains(&field_name) {
                                if let Some(val_id) = ctx.lookup_value(field_name) {
                                    hir_fields.push(hir::FieldExpr {
                                        name: field_name,
                                        value: hir::Expr::Var(ctx.def_ref(val_id, *span)),
                                        span: *span,
                                    });
                                }
                            }
                        }
                    }
                }
                hir::Expr::Record(con_ref, hir_fields, *span)
            } else {
                let qual_name = format!("{}.{}", module_name, con_name.as_str());
                ctx.error(crate::LowerError::UnboundCon {
                    name: qual_name,
                    span: *span,
                });
                hir::Expr::Error(*span)
            }
        }

        ast::Expr::RecordUpd(base, fields, span) => {
            let b = lower_expr(ctx, base);
            let mut hir_fields = Vec::with_capacity(fields.len());
            for f in fields {
                let value = match &f.value {
                    Some(e) => lower_expr(ctx, e),
                    None => hir::Expr::Error(f.span),
                };
                hir_fields.push(hir::FieldExpr {
                    name: f.name.name,
                    value,
                    span: f.span,
                });
            }
            hir::Expr::RecordUpdate(Box::new(b), hir_fields, *span)
        }

        ast::Expr::Ann(expr, ty, span) => {
            let e = lower_expr(ctx, expr);
            let t = lower_type(ctx, ty);
            hir::Expr::Ann(Box::new(e), t, *span)
        }

        ast::Expr::TypeApp(expr, ty, span) => {
            let e = lower_expr(ctx, expr);
            let t = lower_type(ctx, ty);
            hir::Expr::TypeApp(Box::new(e), t, *span)
        }

        ast::Expr::Paren(inner, _) => lower_expr(ctx, inner),

        ast::Expr::Lazy(inner, _span) => {
            // For now, just lower the inner expression
            // TODO: Handle lazy block semantics
            lower_expr(ctx, inner)
        }

        // Wildcard in expression context is a typed hole
        ast::Expr::Wildcard(span) => hir::Expr::Error(*span),
    }
}

/// Lower a case alternative.
fn lower_alt(ctx: &mut LowerContext, alt: &ast::Alt) -> hir::CaseAlt {
    ctx.in_scope(|ctx| {
        // Bind pattern variables first
        bind_pattern(ctx, &alt.pat);
        let pat = lower_pat(ctx, &alt.pat);

        // Handle where bindings (they scope over the RHS)
        if !alt.wheres.is_empty() {
            ctx.enter_scope();
            // Pre-bind all where clause names
            for where_decl in &alt.wheres {
                if let ast::Decl::FunBind(fb) = where_decl {
                    if fb.name.name.as_str() == "$patbind"
                        && fb.clauses.len() == 1
                        && fb.clauses[0].pats.len() == 1
                    {
                        // Pattern binding: bind all variables in the pattern
                        bind_pattern(ctx, &fb.clauses[0].pats[0]);
                    } else {
                        // Regular function binding
                        let def_id = ctx.fresh_def_id();
                        ctx.define(def_id, fb.name.name, DefKind::Value, fb.span);
                        ctx.bind_value(fb.name.name, def_id);
                    }
                }
            }
        }

        // Lower the RHS (where clause bindings are now in scope)
        let rhs_expr = lower_rhs(ctx, &alt.rhs);

        // Wrap in let if there are where bindings
        let final_rhs = if alt.wheres.is_empty() {
            rhs_expr
        } else {
            let bindings: Vec<hir::Binding> = alt
                .wheres
                .iter()
                .filter_map(|d| {
                    if let ast::Decl::FunBind(fb) = d {
                        // Check for pattern binding (special name $patbind)
                        if fb.name.name.as_str() == "$patbind"
                            && fb.clauses.len() == 1
                            && fb.clauses[0].pats.len() == 1
                        {
                            // Pattern binding: (x, y) = expr or (x :| xs) = expr
                            let pat = lower_pat(ctx, &fb.clauses[0].pats[0]);
                            let rhs = lower_rhs(ctx, &fb.clauses[0].rhs);
                            return Some(hir::Binding {
                                pat,
                                sig: None,
                                rhs,
                                span: fb.span,
                            });
                        }

                        // Look up the DefId that was bound for this where binding
                        let def_id = ctx
                            .lookup_value(fb.name.name)
                            .expect("where binding should be bound");

                        // For simple bindings (no parameters)
                        if fb.clauses.len() == 1 && fb.clauses[0].pats.is_empty() {
                            let rhs = lower_rhs(ctx, &fb.clauses[0].rhs);
                            return Some(hir::Binding {
                                pat: hir::Pat::Var(fb.name.name, def_id, fb.span),
                                sig: None,
                                rhs,
                                span: fb.span,
                            });
                        }

                        // For function bindings with parameters, lower to a lambda
                        if fb.clauses.len() == 1 {
                            let clause = &fb.clauses[0];
                            ctx.enter_scope();
                            for p in &clause.pats {
                                bind_pattern(ctx, p);
                            }
                            let mut pats: Vec<hir::Pat> = Vec::new();
                            for p in &clause.pats {
                                pats.push(lower_pat(ctx, p));
                            }
                            let body = lower_rhs(ctx, &clause.rhs);
                            ctx.exit_scope();

                            let lam = hir::Expr::Lam(pats, Box::new(body), fb.span);
                            return Some(hir::Binding {
                                pat: hir::Pat::Var(fb.name.name, def_id, fb.span),
                                sig: None,
                                rhs: lam,
                                span: fb.span,
                            });
                        }

                        // Multi-clause where bindings would need more work
                        None
                    } else {
                        None
                    }
                })
                .collect();

            ctx.exit_scope();
            hir::Expr::Let(bindings, Box::new(rhs_expr), alt.span)
        };

        hir::CaseAlt {
            pat,
            guards: vec![],
            rhs: final_rhs,
            span: alt.span,
        }
    })
}

/// Lower a pattern.
fn lower_pat(ctx: &mut LowerContext, pat: &ast::Pat) -> hir::Pat {
    match pat {
        ast::Pat::Var(ident, span) => {
            // Look up the DefId that was bound by bind_pattern
            let def_id = ctx
                .lookup_value(ident.name)
                .expect("pattern variable should be bound");
            hir::Pat::Var(ident.name, def_id, *span)
        }

        ast::Pat::Wildcard(span) => hir::Pat::Wild(*span),

        ast::Pat::Lit(lit, span) => {
            let hir_lit = lower_lit(lit);
            hir::Pat::Lit(hir_lit, *span)
        }

        ast::Pat::Con(ident, pats, span) => {
            let con_name = ident.name;
            // Check if this is a pattern synonym and expand it
            if let Some((syn_args, syn_pat)) = ctx.lookup_pattern_synonym(con_name).cloned() {
                let expanded = substitute_pattern_synonym(&syn_pat, &syn_args, pats, *span);
                return lower_pat(ctx, &expanded);
            }
            if let Some(def_id) = resolve_constructor(ctx, con_name, *span) {
                let con_ref = ctx.def_ref(def_id, *span);
                let hir_pats: Vec<hir::Pat> = pats.iter().map(|p| lower_pat(ctx, p)).collect();
                hir::Pat::Con(con_ref, hir_pats, *span)
            } else {
                hir::Pat::Error(*span)
            }
        }

        ast::Pat::Infix(lhs, op, rhs, span) => {
            // Desugar infix pattern: x : xs -> (:) x xs
            let con_name = op.name;
            if let Some(def_id) = resolve_constructor(ctx, con_name, *span) {
                let con_ref = ctx.def_ref(def_id, *span);
                let l = lower_pat(ctx, lhs);
                let r = lower_pat(ctx, rhs);
                hir::Pat::Con(con_ref, vec![l, r], *span)
            } else {
                hir::Pat::Error(*span)
            }
        }

        ast::Pat::Tuple(pats, span) => {
            // Tuple pattern is sugar for tuple constructor
            let tuple_sym =
                Symbol::intern(&format!("({})", ",".repeat(pats.len().saturating_sub(1))));
            // Use existing constructor if available, otherwise create one
            let def_id = ctx.lookup_constructor(tuple_sym).unwrap_or_else(|| {
                let id = ctx.fresh_def_id();
                ctx.define(id, tuple_sym, DefKind::Constructor, *span);
                id
            });
            let tuple_ref = ctx.def_ref(def_id, *span);

            let hir_pats: Vec<hir::Pat> = pats.iter().map(|p| lower_pat(ctx, p)).collect();
            hir::Pat::Con(tuple_ref, hir_pats, *span)
        }

        ast::Pat::List(pats, span) => {
            // Desugar list pattern to cons chain
            desugar_list_pat(ctx, pats, *span)
        }

        ast::Pat::As(ident, inner, span) => {
            // Look up the DefId that was bound by bind_pattern
            let def_id = ctx
                .lookup_value(ident.name)
                .expect("as-pattern should be bound");
            let p = lower_pat(ctx, inner);
            hir::Pat::As(ident.name, def_id, Box::new(p), *span)
        }

        ast::Pat::Lazy(inner, _span) => {
            // For now, just lower the inner pattern
            lower_pat(ctx, inner)
        }

        ast::Pat::Bang(inner, _span) => {
            // For now, just lower the inner pattern
            lower_pat(ctx, inner)
        }

        ast::Pat::Record(con, fields, has_wildcard, span) => {
            let con_name = con.name;
            if let Some(def_id) = resolve_constructor(ctx, con_name, *span) {
                let con_ref = ctx.def_ref(def_id, *span);
                let mut hir_field_pats: Vec<hir::FieldPat> = Vec::with_capacity(fields.len());
                for f in fields {
                    let pat = if let Some(p) = &f.pat {
                        lower_pat(ctx, p)
                    } else {
                        // Punned field: Foo { x } binds x
                        let field_def_id = ctx
                            .lookup_value(f.name.name)
                            .expect("punned field should be bound");
                        hir::Pat::Var(f.name.name, field_def_id, f.span)
                    };
                    hir_field_pats.push(hir::FieldPat {
                        name: f.name.name,
                        pat,
                        span: f.span,
                    });
                }
                // RecordWildCards: expand `..` to bind remaining fields as variables
                if *has_wildcard {
                    if let Some(field_names) = ctx.get_constructor_field_names(def_id) {
                        let existing: rustc_hash::FxHashSet<_> =
                            hir_field_pats.iter().map(|f| f.name).collect();
                        for field_name in field_names {
                            if !existing.contains(&field_name) {
                                // Reuse the DefId already bound by collect_pattern_bindings
                                let field_def_id = ctx
                                    .lookup_value(field_name)
                                    .expect("wildcard field should be bound");
                                hir_field_pats.push(hir::FieldPat {
                                    name: field_name,
                                    pat: hir::Pat::Var(field_name, field_def_id, *span),
                                    span: *span,
                                });
                            }
                        }
                    }
                }
                hir::Pat::RecordCon(con_ref, hir_field_pats, *span)
            } else {
                hir::Pat::Error(*span)
            }
        }

        ast::Pat::QualCon(module_name, ident, pats, span) => {
            // Qualified constructor like W.StackSet x y
            let qualifier = Symbol::intern(&module_name.to_string());
            let con_name = ident.name;
            if let Some(def_id) = ctx.resolve_qualified_constructor(qualifier, con_name) {
                let con_ref = ctx.def_ref(def_id, *span);
                let hir_pats: Vec<hir::Pat> = pats.iter().map(|p| lower_pat(ctx, p)).collect();
                hir::Pat::Con(con_ref, hir_pats, *span)
            } else {
                // Fall back to creating a placeholder with the full qualified name
                let qual_name = format!("{}.{}", module_name, con_name.as_str());
                let qual_sym = Symbol::intern(&qual_name);
                ctx.error(crate::LowerError::UnboundCon {
                    name: qual_name,
                    span: *span,
                });
                let def_id = ctx.fresh_def_id();
                ctx.define(def_id, qual_sym, DefKind::Constructor, *span);
                let con_ref = ctx.def_ref(def_id, *span);
                let hir_pats: Vec<hir::Pat> = pats.iter().map(|p| lower_pat(ctx, p)).collect();
                hir::Pat::Con(con_ref, hir_pats, *span)
            }
        }

        ast::Pat::QualRecord(module_name, con, fields, _has_wildcard, span) => {
            // Qualified record pattern like XMonad.XConfig { modMask = m }
            let qualifier = Symbol::intern(&module_name.to_string());
            let con_name = con.name;
            if let Some(def_id) = ctx.resolve_qualified_constructor(qualifier, con_name) {
                let con_ref = ctx.def_ref(def_id, *span);
                let mut hir_field_pats: Vec<hir::FieldPat> = Vec::with_capacity(fields.len());
                for f in fields {
                    let pat = if let Some(p) = &f.pat {
                        lower_pat(ctx, p)
                    } else {
                        // Punned field: Foo { x } binds x
                        let field_def_id = ctx
                            .lookup_value(f.name.name)
                            .expect("punned field should be bound");
                        hir::Pat::Var(f.name.name, field_def_id, f.span)
                    };
                    hir_field_pats.push(hir::FieldPat {
                        name: f.name.name,
                        pat,
                        span: f.span,
                    });
                }
                hir::Pat::RecordCon(con_ref, hir_field_pats, *span)
            } else {
                // Fall back to creating a placeholder with the full qualified name
                let qual_name = format!("{}.{}", module_name, con_name.as_str());
                let qual_sym = Symbol::intern(&qual_name);
                ctx.error(crate::LowerError::UnboundCon {
                    name: qual_name,
                    span: *span,
                });
                let def_id = ctx.fresh_def_id();
                ctx.define(def_id, qual_sym, DefKind::Constructor, *span);
                let con_ref = ctx.def_ref(def_id, *span);
                let mut hir_field_pats: Vec<hir::FieldPat> = Vec::with_capacity(fields.len());
                for f in fields {
                    let pat = if let Some(p) = &f.pat {
                        lower_pat(ctx, p)
                    } else {
                        let field_def_id = ctx
                            .lookup_value(f.name.name)
                            .expect("punned field should be bound");
                        hir::Pat::Var(f.name.name, field_def_id, f.span)
                    };
                    hir_field_pats.push(hir::FieldPat {
                        name: f.name.name,
                        pat,
                        span: f.span,
                    });
                }
                hir::Pat::RecordCon(con_ref, hir_field_pats, *span)
            }
        }

        ast::Pat::Paren(inner, _) => lower_pat(ctx, inner),

        ast::Pat::Ann(inner, ty, span) => {
            let p = lower_pat(ctx, inner);
            let t = lower_type(ctx, ty);
            hir::Pat::Ann(Box::new(p), t, *span)
        }

        // View patterns: (expr -> pat)
        // Lower both the view expression and result pattern into HIR.
        // The actual desugaring happens in HIR→Core pattern compilation.
        ast::Pat::View(view_expr, result_pat, span) => {
            let hir_view_expr = lower_expr(ctx, view_expr);
            let hir_result_pat = lower_pat(ctx, result_pat);
            hir::Pat::View(Box::new(hir_view_expr), Box::new(hir_result_pat), *span)
        }
    }
}

/// Desugar a list pattern [p1, p2, ...] to p1 : p2 : ... : []
fn desugar_list_pat(ctx: &mut LowerContext, pats: &[ast::Pat], span: Span) -> hir::Pat {
    let nil_sym = Symbol::intern("[]");
    let cons_sym = Symbol::intern(":");

    // Start with nil pattern
    let nil_def = ctx.lookup_constructor(nil_sym).unwrap_or_else(|| {
        let id = ctx.fresh_def_id();
        ctx.define(id, nil_sym, DefKind::Constructor, span);
        id
    });
    let nil_ref = ctx.def_ref(nil_def, span);

    pats.iter()
        .rev()
        .fold(hir::Pat::Con(nil_ref, vec![], span), |acc, p| {
            let cons_def = ctx.lookup_constructor(cons_sym).unwrap_or_else(|| {
                let id = ctx.fresh_def_id();
                ctx.define(id, cons_sym, DefKind::Constructor, span);
                id
            });
            let cons_ref = ctx.def_ref(cons_def, span);
            let hir_p = lower_pat(ctx, p);
            hir::Pat::Con(cons_ref, vec![hir_p, acc], span)
        })
}

/// Lower a literal.
fn lower_lit(lit: &ast::Lit) -> hir::Lit {
    match lit {
        ast::Lit::Int(n) => hir::Lit::Int(i128::from(*n)),
        ast::Lit::Float(f) => hir::Lit::Float(*f),
        ast::Lit::Char(c) => hir::Lit::Char(*c),
        ast::Lit::String(s) => hir::Lit::String(Symbol::intern(s)),
    }
}

/// Lower a type.
/// Lower an AST type into a type scheme, preserving constraints.
///
/// If the type has a `Constrained` wrapper (`Eq a => a -> Bool`), the
/// constraints are extracted and placed in the scheme. Free type variables
/// in the type are quantified over.
fn lower_type_to_scheme(ctx: &mut LowerContext, ty: &ast::Type) -> bhc_types::Scheme {
    match ty {
        ast::Type::Constrained(constraints, inner, _span) => {
            let inner_ty = lower_type(ctx, inner);

            // Lower AST constraints to bhc_types::Constraint
            let type_constraints: Vec<bhc_types::Constraint> = constraints
                .iter()
                .map(|c| {
                    let args: Vec<bhc_types::Ty> =
                        c.args.iter().map(|a| lower_type(ctx, a)).collect();
                    bhc_types::Constraint::new_multi(c.class.name, args, c.span)
                })
                .collect();

            // Collect free type variables for quantification
            let mut fvs = inner_ty.free_vars();
            for c in &type_constraints {
                for arg in &c.args {
                    for v in arg.free_vars() {
                        if !fvs.iter().any(|fv| fv.id == v.id) {
                            fvs.push(v);
                        }
                    }
                }
            }

            if fvs.is_empty() && type_constraints.is_empty() {
                bhc_types::Scheme::mono(inner_ty)
            } else {
                bhc_types::Scheme::qualified(fvs, type_constraints, inner_ty)
            }
        }
        ast::Type::Forall(vars, inner, _) => {
            // Handle explicit forall: `forall a. C a => a -> a`
            let ty_vars: Vec<bhc_types::TyVar> = vars
                .iter()
                .map(|v| bhc_types::TyVar::new_star(v.name.name.as_u32()))
                .collect();
            let inner_scheme = lower_type_to_scheme(ctx, inner);
            bhc_types::Scheme {
                vars: ty_vars,
                constraints: inner_scheme.constraints,
                ty: inner_scheme.ty,
            }
        }
        _ => {
            // No constraints — quantify over free type variables
            let raw_ty = lower_type(ctx, ty);
            let free_vars = raw_ty.free_vars();
            if free_vars.is_empty() {
                bhc_types::Scheme::mono(raw_ty)
            } else {
                bhc_types::Scheme::poly(free_vars, raw_ty)
            }
        }
    }
}

// `ctx` is threaded through for resolving named types (WIP: most arms are structural for now).
#[allow(clippy::only_used_in_recursion)]
pub(crate) fn lower_type(ctx: &mut LowerContext, ty: &ast::Type) -> bhc_types::Ty {
    match ty {
        ast::Type::Var(tyvar, _) => {
            bhc_types::Ty::Var(bhc_types::TyVar::new_star(tyvar.name.name.as_u32()))
        }

        ast::Type::Con(ident, _) => {
            bhc_types::Ty::Con(bhc_types::TyCon::new(ident.name, bhc_types::Kind::Star))
        }

        ast::Type::App(f, a, _) => {
            let fun_ty = lower_type(ctx, f);
            let arg_ty = lower_type(ctx, a);
            bhc_types::Ty::App(Box::new(fun_ty), Box::new(arg_ty))
        }

        ast::Type::Fun(from, to, _) => {
            let from_ty = lower_type(ctx, from);
            let to_ty = lower_type(ctx, to);
            bhc_types::Ty::Fun(Box::new(from_ty), Box::new(to_ty))
        }

        ast::Type::Tuple(tys, _) => {
            let hir_tys: Vec<bhc_types::Ty> = tys.iter().map(|t| lower_type(ctx, t)).collect();
            bhc_types::Ty::Tuple(hir_tys)
        }

        ast::Type::List(elem, _) => {
            let elem_ty = lower_type(ctx, elem);
            bhc_types::Ty::List(Box::new(elem_ty))
        }

        ast::Type::Paren(inner, _) => lower_type(ctx, inner),

        ast::Type::Forall(vars, inner, _) => {
            let ty_vars: Vec<bhc_types::TyVar> = vars
                .iter()
                .map(|v| bhc_types::TyVar::new_star(v.name.name.as_u32()))
                .collect();
            let inner_ty = lower_type(ctx, inner);
            bhc_types::Ty::Forall(ty_vars, Box::new(inner_ty))
        }

        ast::Type::Constrained(_, inner, _) => {
            // TODO: handle constraints properly
            lower_type(ctx, inner)
        }

        ast::Type::QualCon(module_name, ident, _) => {
            // Qualified type constructor like M.Map
            // Create a qualified name symbol by combining module and name
            let qual_name = format!("{}.{}", module_name, ident.name.as_str());
            let symbol = Symbol::intern(&qual_name);
            bhc_types::Ty::Con(bhc_types::TyCon::new(symbol, bhc_types::Kind::Star))
        }

        ast::Type::NatLit(n, _) => bhc_types::Ty::nat_lit(*n),

        ast::Type::Bang(inner, _) | ast::Type::Lazy(inner, _) => {
            // Strip strictness/laziness annotations and lower the inner type
            lower_type(ctx, inner)
        }

        ast::Type::PromotedList(_, _) => {
            // TODO: handle promoted lists
            bhc_types::Ty::Error
        }

        ast::Type::InfixOp(lhs, op, rhs, _) => {
            let op_ty = bhc_types::Ty::Con(bhc_types::TyCon::new(op.name, bhc_types::Kind::Star));
            let lhs_ty = lower_type(ctx, lhs);
            let rhs_ty = lower_type(ctx, rhs);
            bhc_types::Ty::App(
                Box::new(bhc_types::Ty::App(Box::new(op_ty), Box::new(lhs_ty))),
                Box::new(rhs_ty),
            )
        }
    }
}

/// Lower an arithmetic sequence.
fn lower_arith_seq(ctx: &mut LowerContext, seq: &ast::ArithSeq, span: Span) -> hir::Expr {
    // Desugar arithmetic sequences to enumFrom* calls
    let (func_name, args) = match seq {
        ast::ArithSeq::From(start) => ("enumFrom", vec![lower_expr(ctx, start)]),
        ast::ArithSeq::FromThen(start, next) => (
            "enumFromThen",
            vec![lower_expr(ctx, start), lower_expr(ctx, next)],
        ),
        ast::ArithSeq::FromTo(start, end) => (
            "enumFromTo",
            vec![lower_expr(ctx, start), lower_expr(ctx, end)],
        ),
        ast::ArithSeq::FromThenTo(start, next, end) => (
            "enumFromThenTo",
            vec![
                lower_expr(ctx, start),
                lower_expr(ctx, next),
                lower_expr(ctx, end),
            ],
        ),
    };

    let func_sym = Symbol::intern(func_name);
    let func = if let Some(def_id) = ctx.lookup_value(func_sym) {
        hir::Expr::Var(ctx.def_ref(def_id, span))
    } else {
        let def_id = ctx.fresh_def_id();
        ctx.define(def_id, func_sym, DefKind::Value, span);
        hir::Expr::Var(ctx.def_ref(def_id, span))
    };

    args.into_iter()
        .fold(func, |f, a| hir::Expr::App(Box::new(f), Box::new(a), span))
}

/// Lower an import declaration.
fn lower_import(imp: &ast::ImportDecl) -> hir::Import {
    let module_name = imp
        .module
        .parts
        .iter()
        .map(|s| s.as_str())
        .collect::<Vec<_>>()
        .join(".");

    hir::Import {
        module: Symbol::intern(&module_name),
        qualified: imp.qualified,
        alias: imp.alias.as_ref().map(|a| {
            let alias_name = a
                .parts
                .iter()
                .map(|s| s.as_str())
                .collect::<Vec<_>>()
                .join(".");
            Symbol::intern(&alias_name)
        }),
        items: imp.spec.as_ref().map(|spec| match spec {
            ast::ImportSpec::Only(items) | ast::ImportSpec::Hiding(items) => items
                .iter()
                .map(|item| match item {
                    ast::Import::Var(ident, span) => hir::ImportItem {
                        name: ident.name,
                        children: hir::ExportChildren::None,
                        span: *span,
                    },
                    ast::Import::Type(ident, children, span) => hir::ImportItem {
                        name: ident.name,
                        children: children.as_ref().map_or(hir::ExportChildren::None, |cs| {
                            if cs.is_empty() {
                                hir::ExportChildren::All
                            } else {
                                hir::ExportChildren::Some(cs.iter().map(|c| c.name).collect())
                            }
                        }),
                        span: *span,
                    },
                    ast::Import::Pattern(ident, span) => hir::ImportItem {
                        name: ident.name,
                        children: hir::ExportChildren::None,
                        span: *span,
                    },
                })
                .collect(),
        }),
        hiding: matches!(imp.spec, Some(ast::ImportSpec::Hiding(_))),
        span: imp.span,
    }
}

/// Lower an export specification.
fn lower_export(exp: &ast::Export) -> hir::Export {
    match exp {
        ast::Export::Var(ident, span) => hir::Export {
            name: ident.name,
            children: hir::ExportChildren::None,
            span: *span,
        },
        ast::Export::Type(ident, children, span) => hir::Export {
            name: ident.name,
            children: children.as_ref().map_or(hir::ExportChildren::None, |cs| {
                if cs.is_empty() {
                    // Type(..) — export all constructors/methods
                    hir::ExportChildren::All
                } else {
                    hir::ExportChildren::Some(cs.iter().map(|c| c.name).collect())
                }
            }),
            span: *span,
        },
        ast::Export::Module(module_name, span) => {
            let name = module_name
                .parts
                .iter()
                .map(|s| s.as_str())
                .collect::<Vec<_>>()
                .join(".");
            hir::Export {
                name: Symbol::intern(&name),
                children: hir::ExportChildren::All,
                span: *span,
            }
        }
        ast::Export::Pattern(ident, span) => hir::Export {
            name: ident.name,
            children: hir::ExportChildren::None,
            span: *span,
        },
    }
}

/// Lower a data declaration and generate field accessor functions for records.
fn lower_data_decl_with_accessors(
    ctx: &mut LowerContext,
    data: &ast::DataDecl,
) -> LowerResult<(hir::DataDef, Vec<hir::ValueDef>)> {
    let data_def = lower_data_decl(ctx, data)?;

    // Generate field accessor functions for record constructors
    let mut accessors = Vec::new();
    for con in &data_def.cons {
        if let hir::ConFields::Named(fields) = &con.fields {
            for (field_idx, field) in fields.iter().enumerate() {
                let accessor = generate_field_accessor(ctx, con, fields.len(), field_idx, field);
                accessors.push(accessor);
            }
        }
    }

    Ok((data_def, accessors))
}

/// Generate a field accessor function for a record field.
///
/// For `data Point = Point { px :: Int, py :: Int }`, generates:
/// - `px (Point x _) = x`
/// - `py (Point _ y) = y`
fn generate_field_accessor(
    ctx: &mut LowerContext,
    con: &hir::ConDef,
    num_fields: usize,
    field_idx: usize,
    field: &hir::FieldDef,
) -> hir::ValueDef {
    // Create a pattern for matching the constructor
    // e.g., Point x _ for accessing the first field
    let mut sub_pats = Vec::with_capacity(num_fields);
    let mut result_var_def_id = None;
    let result_var_name = Symbol::intern("_field_val");

    for i in 0..num_fields {
        if i == field_idx {
            // This is the field we're extracting
            let var_def_id = ctx.fresh_def_id();
            result_var_def_id = Some(var_def_id);
            ctx.define(var_def_id, result_var_name, DefKind::Value, field.span);
            sub_pats.push(hir::Pat::Var(result_var_name, var_def_id, field.span));
        } else {
            // Other fields are wildcards
            sub_pats.push(hir::Pat::Wild(field.span));
        }
    }

    let result_var_def_id = result_var_def_id.expect("field_idx should be valid");

    // Build the constructor pattern
    let con_pat = hir::Pat::Con(ctx.def_ref(con.id, con.span), sub_pats, field.span);

    // The RHS is just the variable we bound
    let rhs = hir::Expr::Var(ctx.def_ref(result_var_def_id, field.span));

    // Create the equation: accessor (Con ... x ...) = x
    let equation = hir::Equation {
        pats: vec![con_pat],
        guards: vec![],
        rhs,
        span: field.span,
    };

    hir::ValueDef {
        id: field.id,
        name: field.name,
        sig: None, // Type is inferred from the registered type
        equations: vec![equation],
        span: field.span,
    }
}

/// Infer the kind of a type parameter from how it's used in constructor field types.
/// Returns the number of arguments the parameter takes (0 = kind *, 1 = kind * -> *, etc.)
fn infer_param_arity(param_name: Symbol, tys: &[&ast::Type]) -> usize {
    let mut max_arity: usize = 0;
    for ty in tys {
        max_arity = max_arity.max(infer_param_arity_in_type(param_name, ty));
    }
    max_arity
}

/// Recursively find the maximum arity for a type parameter in a type expression.
fn infer_param_arity_in_type(param_name: Symbol, ty: &ast::Type) -> usize {
    match ty {
        ast::Type::Var(_, _) => {
            // A bare type variable has arity 0 (it appears as itself), whether or
            // not it is the parameter we are looking for.
            0
        }
        ast::Type::App(f, a, _) => {
            // Check if this application chain has our parameter at the base
            // e.g., for `f a b`, if f is our param, it takes 2 args
            let chain_arity = count_app_chain_arity(param_name, ty);
            // Also check recursively in both parts for nested uses
            let f_recursive = infer_param_arity_in_type(param_name, f);
            let a_recursive = infer_param_arity_in_type(param_name, a);
            chain_arity.max(f_recursive).max(a_recursive)
        }
        ast::Type::Fun(from, to, _) => infer_param_arity_in_type(param_name, from)
            .max(infer_param_arity_in_type(param_name, to)),
        ast::Type::Tuple(tys, _) => tys
            .iter()
            .map(|t| infer_param_arity_in_type(param_name, t))
            .max()
            .unwrap_or(0),
        ast::Type::List(elem, _) => infer_param_arity_in_type(param_name, elem),
        ast::Type::Paren(inner, _) => infer_param_arity_in_type(param_name, inner),
        ast::Type::Forall(_, inner, _) => infer_param_arity_in_type(param_name, inner),
        // Type constructors, qualified constructors, constrained types, promoted lists,
        // type-level literals, etc. don't contain our type parameter in a way that matters.
        ast::Type::InfixOp(lhs, _, rhs, _) => infer_param_arity_in_type(param_name, lhs)
            .max(infer_param_arity_in_type(param_name, rhs)),
        ast::Type::Con(_, _)
        | ast::Type::QualCon(_, _, _)
        | ast::Type::Constrained(_, _, _)
        | ast::Type::PromotedList(_, _)
        | ast::Type::NatLit(_, _)
        | ast::Type::Bang(_, _)
        | ast::Type::Lazy(_, _) => 0,
    }
}

/// Count how many arguments a type parameter takes in a chain of applications.
/// For example, in `f a b`, if f is the param, it takes 2 arguments.
fn count_app_chain_arity(param_name: Symbol, ty: &ast::Type) -> usize {
    // Walk up the application spine to count args
    fn count_args(param_name: Symbol, ty: &ast::Type, depth: usize) -> usize {
        match ty {
            ast::Type::Var(v, _) if v.name.name == param_name => depth,
            ast::Type::App(f, _, _) => count_args(param_name, f, depth + 1),
            ast::Type::Paren(inner, _) => count_args(param_name, inner, depth),
            _ => 0,
        }
    }
    count_args(param_name, ty, 0)
}

/// Build a kind from an arity (0 = *, 1 = * -> *, 2 = * -> * -> *, etc.)
fn kind_from_arity(arity: usize) -> bhc_types::Kind {
    let mut kind = bhc_types::Kind::Star;
    for _ in 0..arity {
        kind = bhc_types::Kind::Arrow(Box::new(bhc_types::Kind::Star), Box::new(kind));
    }
    kind
}

/// Collect all field types from constructor declarations.
fn collect_field_types(constrs: &[ast::ConDecl]) -> Vec<&ast::Type> {
    let mut types = Vec::new();
    for con in constrs {
        match &con.fields {
            ast::ConFields::Positional(tys) => {
                for ty in tys {
                    types.push(ty);
                }
            }
            ast::ConFields::Record(fields) => {
                for field in fields {
                    types.push(&field.ty);
                }
            }
        }
    }
    types
}

/// Lower a data declaration.
fn lower_data_decl(ctx: &mut LowerContext, data: &ast::DataDecl) -> LowerResult<hir::DataDef> {
    let type_def_id = ctx
        .lookup_type(data.name.name)
        .expect("type should be pre-bound");

    let is_gadt = !data.gadt_constrs.is_empty();

    // Collect all field types from constructors to infer parameter kinds
    let field_types = collect_field_types(&data.constrs);

    // Infer kinds for each type parameter based on usage
    let params: Vec<bhc_types::TyVar> = data
        .params
        .iter()
        .map(|p| {
            let arity = infer_param_arity(p.name.name, &field_types);
            let kind = kind_from_arity(arity);
            bhc_types::TyVar::new(p.name.name.as_u32(), kind)
        })
        .collect();

    let cons: Vec<hir::ConDef> = if is_gadt {
        data.gadt_constrs
            .iter()
            .map(|c| lower_gadt_con_def(ctx, c))
            .collect()
    } else {
        data.constrs.iter().map(|c| lower_con_def(ctx, c)).collect()
    };

    let deriving: Vec<hir::DerivingClause> = data
        .deriving
        .iter()
        .map(|clause| hir::DerivingClause {
            strategy: lower_deriving_strategy(ctx, &clause.strategy),
            class: clause.class.name,
        })
        .collect();

    Ok(hir::DataDef {
        id: type_def_id,
        name: data.name.name,
        params,
        cons,
        is_gadt,
        deriving,
        span: data.span,
    })
}

/// Lower a constructor definition.
fn lower_con_def(ctx: &mut LowerContext, con: &ast::ConDecl) -> hir::ConDef {
    let con_def_id = ctx
        .lookup_constructor(con.name.name)
        .expect("constructor should be pre-bound");

    let fields = match &con.fields {
        ast::ConFields::Positional(tys) => {
            let hir_tys: Vec<bhc_types::Ty> = tys.iter().map(|t| lower_type(ctx, t)).collect();
            hir::ConFields::Positional(hir_tys)
        }
        ast::ConFields::Record(fields) => {
            let hir_fields: Vec<hir::FieldDef> = fields
                .iter()
                .map(|f| {
                    // Look up the DefId for the field accessor that was bound during
                    // collect_module_definitions
                    let field_def_id = ctx
                        .lookup_value(f.name.name)
                        .expect("field accessor should be pre-bound");
                    hir::FieldDef {
                        id: field_def_id,
                        name: f.name.name,
                        ty: lower_type(ctx, &f.ty),
                        span: f.span,
                    }
                })
                .collect();
            hir::ConFields::Named(hir_fields)
        }
    };

    // Lower existential type variables
    let existential_vars: Vec<bhc_types::TyVar> = con
        .existential_vars
        .iter()
        .map(|tv| bhc_types::TyVar::new_star(tv.name.name.as_u32()))
        .collect();

    // Lower existential context constraints
    let existential_context: Vec<bhc_types::Constraint> = con
        .existential_context
        .iter()
        .map(|c| {
            let args: Vec<bhc_types::Ty> = c.args.iter().map(|t| lower_type(ctx, t)).collect();
            bhc_types::Constraint::new_multi(c.class.name, args, c.span)
        })
        .collect();

    hir::ConDef {
        id: con_def_id,
        name: con.name.name,
        fields,
        gadt_return_ty: None,
        existential_vars,
        existential_context,
        span: con.span,
    }
}

/// Lower a GADT constructor definition.
///
/// Decomposes the full constructor type `A -> B -> ... -> RetType` into
/// field types `[A, B, ...]` and a return type `RetType`.
fn lower_gadt_con_def(ctx: &mut LowerContext, con: &ast::GadtConDecl) -> hir::ConDef {
    let con_def_id = ctx
        .lookup_constructor(con.name.name)
        .expect("GADT constructor should be pre-bound");

    // Decompose the constructor type: strip forall, then peel function arrows
    let full_ty = &con.ty;

    // Strip forall if present
    let inner_ty = match full_ty {
        ast::Type::Forall(_, inner, _) => inner.as_ref(),
        _ => full_ty,
    };

    // Walk the function arrow chain to separate argument types from return type
    let mut arg_types = Vec::new();
    let mut current = inner_ty;
    while let ast::Type::Fun(from, to, _) = current {
        arg_types.push(lower_type(ctx, from));
        current = to.as_ref();
    }
    let return_ty = lower_type(ctx, current);

    hir::ConDef {
        id: con_def_id,
        name: con.name.name,
        fields: hir::ConFields::Positional(arg_types),
        gadt_return_ty: Some(return_ty),
        existential_vars: Vec::new(),
        existential_context: Vec::new(),
        span: con.span,
    }
}

/// Lower a deriving strategy from AST to HIR.
fn lower_deriving_strategy(
    ctx: &mut LowerContext,
    strategy: &ast::DerivingStrategy,
) -> hir::DerivingStrategy {
    match strategy {
        ast::DerivingStrategy::Default => hir::DerivingStrategy::Default,
        ast::DerivingStrategy::Stock => hir::DerivingStrategy::Stock,
        ast::DerivingStrategy::Newtype => hir::DerivingStrategy::Newtype,
        ast::DerivingStrategy::Anyclass => hir::DerivingStrategy::Anyclass,
        ast::DerivingStrategy::Via(ty) => hir::DerivingStrategy::Via(lower_type(ctx, ty)),
    }
}

/// Lower a newtype declaration.
/// Lower a newtype declaration and generate field accessor functions for records.
fn lower_newtype_decl_with_accessors(
    ctx: &mut LowerContext,
    newtype: &ast::NewtypeDecl,
) -> LowerResult<(hir::NewtypeDef, Vec<hir::ValueDef>)> {
    let type_def_id = ctx
        .lookup_type(newtype.name.name)
        .expect("type should be pre-bound");

    let params: Vec<bhc_types::TyVar> = newtype
        .params
        .iter()
        .map(|p| bhc_types::TyVar::new_star(p.name.name.as_u32()))
        .collect();

    let con = lower_con_def(ctx, &newtype.constr);

    let deriving: Vec<hir::DerivingClause> = newtype
        .deriving
        .iter()
        .map(|clause| hir::DerivingClause {
            strategy: lower_deriving_strategy(ctx, &clause.strategy),
            class: clause.class.name,
        })
        .collect();

    // Generate field accessor functions for record constructors
    let mut accessors = Vec::new();
    if let hir::ConFields::Named(fields) = &con.fields {
        for (field_idx, field) in fields.iter().enumerate() {
            let accessor = generate_field_accessor(ctx, &con, fields.len(), field_idx, field);
            accessors.push(accessor);
        }
    }

    let newtype_def = hir::NewtypeDef {
        id: type_def_id,
        name: newtype.name.name,
        params,
        con,
        deriving,
        span: newtype.span,
    };

    Ok((newtype_def, accessors))
}

/// Lower a type alias declaration.
fn lower_type_alias(
    ctx: &mut LowerContext,
    type_alias: &ast::TypeAlias,
) -> LowerResult<hir::TypeAlias> {
    let def_id = ctx
        .lookup_type(type_alias.name.name)
        .expect("type should be pre-bound");

    let params: Vec<bhc_types::TyVar> = type_alias
        .params
        .iter()
        .map(|p| bhc_types::TyVar::new_star(p.name.name.as_u32()))
        .collect();

    let ty = lower_type(ctx, &type_alias.ty);

    Ok(hir::TypeAlias {
        id: def_id,
        name: type_alias.name.name,
        params,
        ty,
        span: type_alias.span,
    })
}

/// Lower a standalone type family declaration to HIR.
fn lower_type_family_decl(
    ctx: &mut LowerContext,
    tf: &ast::TypeFamilyDecl,
) -> LowerResult<hir::TypeFamilyDef> {
    let def_id = ctx
        .lookup_type(tf.name.name)
        .expect("type family should be pre-bound");

    let params: Vec<bhc_types::TyVar> = tf
        .params
        .iter()
        .map(|p| bhc_types::TyVar::new_star(p.name.name.as_u32()))
        .collect();

    let kind = tf.kind.as_ref().map_or(bhc_types::Kind::Star, lower_kind);

    let family_kind = match tf.family_kind {
        ast::TypeFamilyKind::Open => hir::TypeFamilyKind::Open,
        ast::TypeFamilyKind::Closed => hir::TypeFamilyKind::Closed,
    };

    let equations: Vec<hir::TypeFamilyEqn> = tf
        .equations
        .iter()
        .map(|eq| hir::TypeFamilyEqn {
            args: eq.args.iter().map(|a| lower_type(ctx, a)).collect(),
            rhs: lower_type(ctx, &eq.rhs),
            span: eq.span,
        })
        .collect();

    Ok(hir::TypeFamilyDef {
        id: def_id,
        name: tf.name.name,
        params,
        kind,
        family_kind,
        equations,
        span: tf.span,
    })
}

/// Lower a standalone type instance declaration to HIR.
fn lower_type_instance_decl(
    ctx: &mut LowerContext,
    ti: &ast::TypeInstanceDecl,
) -> hir::TypeFamilyInstance {
    hir::TypeFamilyInstance {
        name: ti.name.name,
        args: ti.args.iter().map(|a| lower_type(ctx, a)).collect(),
        rhs: lower_type(ctx, &ti.rhs),
        span: ti.span,
    }
}

/// Lower a standalone data family declaration to HIR.
fn lower_data_family_decl(
    ctx: &mut LowerContext,
    df: &ast::DataFamilyDecl,
) -> LowerResult<hir::DataFamilyDef> {
    let def_id = ctx
        .lookup_type(df.name.name)
        .expect("data family should be pre-bound");

    let params: Vec<bhc_types::TyVar> = df
        .params
        .iter()
        .map(|p| bhc_types::TyVar::new_star(p.name.name.as_u32()))
        .collect();

    let kind = df.kind.as_ref().map_or(bhc_types::Kind::Star, lower_kind);

    Ok(hir::DataFamilyDef {
        id: def_id,
        name: df.name.name,
        params,
        kind,
        span: df.span,
    })
}

/// Lower a data family instance declaration to HIR.
fn lower_data_instance_decl(
    ctx: &mut LowerContext,
    di: &ast::DataInstanceDecl,
) -> hir::DataFamilyInstance {
    let cons: Vec<hir::ConDef> = if di.gadt_constrs.is_empty() {
        di.constrs.iter().map(|c| lower_con_def(ctx, c)).collect()
    } else {
        di.gadt_constrs
            .iter()
            .map(|c| lower_gadt_con_def(ctx, c))
            .collect()
    };

    let deriving: Vec<hir::DerivingClause> = di
        .deriving
        .iter()
        .map(|clause| hir::DerivingClause {
            strategy: lower_deriving_strategy(ctx, &clause.strategy),
            class: clause.class.name,
        })
        .collect();

    hir::DataFamilyInstance {
        family_name: di.family_name.name,
        args: di.args.iter().map(|a| lower_type(ctx, a)).collect(),
        cons,
        deriving,
        span: di.span,
    }
}

/// Lower an AST kind to a `bhc_types` kind.
fn lower_kind(kind: &ast::Kind) -> bhc_types::Kind {
    match kind {
        ast::Kind::Star => bhc_types::Kind::Star,
        ast::Kind::Arrow(left, right) => {
            bhc_types::Kind::Arrow(Box::new(lower_kind(left)), Box::new(lower_kind(right)))
        }
        ast::Kind::Var(_ident) => {
            // For now, treat kind variables as Star
            // A more sophisticated implementation would do kind inference
            bhc_types::Kind::Star
        }
    }
}

/// Lower an AST associated type declaration to HIR.
fn lower_assoc_type(ctx: &mut LowerContext, assoc: &ast::AssocType) -> hir::AssocTypeSig {
    let id = ctx.fresh_def_id();

    // Convert type parameters
    let params: Vec<bhc_types::TyVar> = assoc
        .params
        .iter()
        .map(|p| bhc_types::TyVar::new_star(p.name.name.as_u32()))
        .collect();

    // Convert kind, defaulting to Star
    let kind = assoc
        .kind
        .as_ref()
        .map_or(bhc_types::Kind::Star, lower_kind);

    // Convert optional default type
    let default = assoc.default.as_ref().map(|ty| lower_type(ctx, ty));

    hir::AssocTypeSig {
        id,
        name: assoc.name.name,
        params,
        kind,
        default,
        span: assoc.span,
    }
}

/// Lower an AST associated type definition to HIR.
fn lower_assoc_type_def(ctx: &mut LowerContext, def: &ast::AssocTypeDef) -> hir::AssocTypeImpl {
    let args: Vec<bhc_types::Ty> = def.args.iter().map(|ty| lower_type(ctx, ty)).collect();

    let rhs = lower_type(ctx, &def.rhs);

    hir::AssocTypeImpl {
        name: def.name.name,
        args,
        rhs,
        span: def.span,
    }
}

/// Lower a class declaration.
fn lower_class_decl(ctx: &mut LowerContext, class: &ast::ClassDecl) -> LowerResult<hir::ClassDef> {
    let def_id = ctx
        .lookup_type(class.name.name)
        .expect("class should be pre-bound");

    let params: Vec<bhc_types::TyVar> = class
        .params
        .iter()
        .map(|p| bhc_types::TyVar::new_star(p.name.name.as_u32()))
        .collect();

    // Build a map from param name to index for fundep conversion
    let param_indices: std::collections::HashMap<Symbol, usize> = class
        .params
        .iter()
        .enumerate()
        .map(|(i, p)| (p.name.name, i))
        .collect();

    // Convert AST fundeps to HIR fundeps (names -> indices)
    let fundeps: Vec<hir::FunDep> = class
        .fundeps
        .iter()
        .map(|fd| {
            let from: Vec<usize> = fd
                .from
                .iter()
                .filter_map(|ident| param_indices.get(&ident.name).copied())
                .collect();
            let to: Vec<usize> = fd
                .to
                .iter()
                .filter_map(|ident| param_indices.get(&ident.name).copied())
                .collect();
            hir::FunDep {
                from,
                to,
                span: fd.span,
            }
        })
        .collect();

    let supers: Vec<Symbol> = class.context.iter().map(|c| c.class.name).collect();

    // Extract method signatures
    let methods: Vec<hir::MethodSig> = class
        .methods
        .iter()
        .filter_map(|m| {
            if let ast::Decl::TypeSig(sig) = m {
                Some(
                    sig.names
                        .iter()
                        .map(|n| hir::MethodSig {
                            name: n.name,
                            id: ctx
                                .lookup_value(n.name)
                                .unwrap_or_else(|| ctx.fresh_def_id()),
                            ty: lower_type_to_scheme(ctx, &sig.ty),
                            span: sig.span,
                        })
                        .collect::<Vec<_>>(),
                )
            } else {
                None
            }
        })
        .flatten()
        .collect();

    // Pre-bind default method implementations before lowering them
    // (TypeSig names are already bound in collect_module_definitions, but
    // default implementations may have names not in TypeSig or may appear first)
    for method in &class.methods {
        if let ast::Decl::FunBind(fb) = method {
            let name = fb.name.name;
            if ctx.lookup_value(name).is_none() {
                let def_id = ctx.fresh_def_id();
                ctx.define(def_id, name, DefKind::Value, fb.span);
                ctx.bind_value(name, def_id);
            }
        }
    }

    // Extract default implementations
    let defaults: Vec<hir::ValueDef> = class
        .methods
        .iter()
        .filter_map(|m| {
            if let ast::Decl::FunBind(fb) = m {
                lower_fun_bind(ctx, fb).ok()
            } else {
                None
            }
        })
        .collect();

    // Lower associated type declarations
    let assoc_types: Vec<hir::AssocTypeSig> = class
        .assoc_types
        .iter()
        .map(|at| lower_assoc_type(ctx, at))
        .collect();

    Ok(hir::ClassDef {
        id: def_id,
        name: class.name.name,
        params,
        fundeps,
        supers,
        methods,
        defaults,
        assoc_types,
        span: class.span,
    })
}

/// Lower an instance declaration.
/// Flatten an AST type application spine into individual types.
///
/// For multi-param type classes, the parser produces a single type like
/// `App(Con("Int"), Con("String"))` for `instance C Int String`.
/// This function decomposes the spine into `[Int, String]` based on the
/// expected parameter count.
///
/// For single-param classes (`param_count=1`), returns the type as-is in a vec.
fn flatten_instance_type(ty: &ast::Type, param_count: usize) -> Vec<&ast::Type> {
    if param_count <= 1 {
        return vec![ty];
    }

    // Walk the App spine leftward to collect all types
    let mut spine = Vec::new();
    let mut current = ty;
    loop {
        if let ast::Type::App(f, x, _) = current {
            spine.push(x.as_ref());
            current = f.as_ref();
        } else {
            spine.push(current);
            break;
        }
    }
    // spine is in reverse order (rightmost arg first), so reverse it
    spine.reverse();

    // If we have more types than params, take only the last param_count
    // (the excess would be a partially applied type constructor)
    if spine.len() > param_count {
        spine.split_off(spine.len() - param_count)
    } else {
        spine
    }
}

fn lower_instance_decl(
    ctx: &mut LowerContext,
    instance: &ast::InstanceDecl,
) -> LowerResult<hir::InstanceDef> {
    // Decompose the instance type based on class parameter count.
    // For multi-param classes like `instance Convertible Int String`,
    // the parser produces a single type App(Con("Int"), Con("String"))
    // which we flatten into [Int, String].
    let param_count = ctx
        .lookup_class_param_count(instance.class.name)
        .unwrap_or(1);
    let flattened = flatten_instance_type(&instance.ty, param_count);
    let types: Vec<bhc_types::Ty> = flattened.iter().map(|t| lower_type(ctx, t)).collect();

    let constraints: Vec<bhc_types::Constraint> = instance
        .context
        .iter()
        .map(|c| {
            let args: Vec<bhc_types::Ty> = c.args.iter().map(|a| lower_type(ctx, a)).collect();
            bhc_types::Constraint {
                class: c.class.name,
                args,
                span: c.span,
            }
        })
        .collect();

    // Lower instance methods with FRESH DefIds. We do NOT bind the method name
    // to the fresh DefId — this ensures that references to `show` in the method
    // body resolve to the builtin polymorphic `show`, not the instance method
    // itself (which would cause infinite recursion).
    let methods: Vec<hir::ValueDef> = instance
        .methods
        .iter()
        .filter_map(|m| {
            if let ast::Decl::FunBind(fb) = m {
                let def_id = ctx.fresh_def_id();
                ctx.define(def_id, fb.name.name, DefKind::Value, fb.span);
                lower_instance_method(ctx, fb, def_id).ok()
            } else {
                None
            }
        })
        .collect();

    // Lower associated type definitions
    let assoc_type_impls: Vec<hir::AssocTypeImpl> = instance
        .assoc_type_defs
        .iter()
        .map(|def| lower_assoc_type_def(ctx, def))
        .collect();

    Ok(hir::InstanceDef {
        class: instance.class.name,
        types,
        constraints,
        methods,
        assoc_type_impls,
        span: instance.span,
    })
}

/// Lower a fixity declaration.
fn lower_fixity_decl(fixity: &ast::FixityDecl) -> hir::FixityDecl {
    let hir_fixity = match fixity.fixity {
        ast::Fixity::Left => hir::Fixity::Left,
        ast::Fixity::Right => hir::Fixity::Right,
        ast::Fixity::None => hir::Fixity::None,
    };

    hir::FixityDecl {
        fixity: hir_fixity,
        precedence: fixity.prec,
        ops: fixity.ops.iter().map(|o| o.name).collect(),
        span: fixity.span,
    }
}

/// Lower a foreign declaration.
fn lower_foreign_decl(
    ctx: &mut LowerContext,
    foreign: &ast::ForeignDecl,
) -> LowerResult<hir::ForeignDecl> {
    let def_id = ctx
        .lookup_value(foreign.name.name)
        .expect("foreign import should be pre-bound");

    // Map convention string to ForeignConvention
    let convention = match foreign.convention.as_str() {
        "ccall" | "capi" => hir::ForeignConvention::CCall,
        "stdcall" => hir::ForeignConvention::StdCall,
        "javascript" => hir::ForeignConvention::JavaScript,
        _ => hir::ForeignConvention::CCall, // Default
    };

    // Lower the type and quantify free type variables so the scheme
    // is properly polymorphic (e.g. `a -> IO (TVar a)` becomes
    // `forall a. a -> IO (TVar a)`). Without this, multiple uses of
    // the same foreign import would share type variables.
    let raw_ty = lower_type(ctx, &foreign.ty);
    let free_vars = raw_ty.free_vars();
    let ty = if free_vars.is_empty() {
        bhc_types::Scheme::mono(raw_ty)
    } else {
        bhc_types::Scheme::poly(free_vars, raw_ty)
    };

    // Update the DefInfo so the type checker can find the proper type scheme
    // for this foreign import (needed for imported stub modules).
    if let Some(def_info) = ctx.defs.get_mut(&def_id) {
        def_info.type_scheme = Some(ty.clone());
    }

    // Map safety annotation from AST to HIR
    let safety = match foreign.safety {
        ast::ForeignSafety::Safe => hir::ForeignSafety::Safe,
        ast::ForeignSafety::Unsafe => hir::ForeignSafety::Unsafe,
        ast::ForeignSafety::Interruptible => hir::ForeignSafety::Interruptible,
    };

    Ok(hir::ForeignDecl {
        id: def_id,
        name: foreign.name.name,
        foreign_name: foreign
            .external_name
            .as_ref()
            .map_or_else(|| foreign.name.name, |s| Symbol::intern(s)),
        convention,
        safety,
        ty,
        span: foreign.span,
    })
}

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

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

        // Create a variable reference to a builtin
        let ident = bhc_intern::Ident::from_str("map");
        let expr = ast::Expr::Var(ident, Span::default());

        let result = lower_expr(&mut ctx, &expr);

        assert!(matches!(result, hir::Expr::Var(_)));
        assert!(!ctx.has_errors());
    }

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

        let expr = ast::Expr::Lit(ast::Lit::Int(42), Span::default());

        let result = lower_expr(&mut ctx, &expr);

        match result {
            hir::Expr::Lit(hir::Lit::Int(n), _) => assert_eq!(n, 42),
            _ => panic!("expected integer literal"),
        }
    }

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

        // map id
        let map_ident = bhc_intern::Ident::from_str("map");
        let id_ident = bhc_intern::Ident::from_str("id");

        let expr = ast::Expr::App(
            Box::new(ast::Expr::Var(map_ident, Span::default())),
            Box::new(ast::Expr::Var(id_ident, Span::default())),
            Span::default(),
        );

        let result = lower_expr(&mut ctx, &expr);

        assert!(matches!(result, hir::Expr::App(_, _, _)));
    }
}
