//! Lowering from Core IR to LLVM IR.
//!
//! This module implements the translation from BHC's Core IR to LLVM IR.
//! The lowering handles:
//!
//! - Literals: Int, Double, Char, String
//! - Variables: Mapped to LLVM SSA values
//! - Function applications: Compiled to calls
//! - Lambdas: Compiled to closures or direct functions
//! - Let bindings: Compiled to LLVM allocas/phis
//! - Case expressions: Compiled to switch/branch

use crate::{CodegenError, CodegenResult};
use bhc_core::{Alt, AltCon, Bind, CoreModule, DataCon, Expr, ForeignImport, Literal, Var, VarId};
use bhc_intern::Symbol;
use rustc_hash::FxHashSet;

/// Primitive operations that compile directly to LLVM instructions.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum PrimOp {
    // Arithmetic
    Add,
    Sub,
    Mul,
    Div,
    Mod,
    Rem,
    Quot,

    // Comparison
    Eq,
    Ne,
    Lt,
    Le,
    Gt,
    Ge,

    // Boolean
    And,
    Or,
    Not,

    // Unary numeric
    Negate,
    Abs,
    Signum,

    // Bitwise
    BitAnd,
    BitOr,
    BitXor,
    ShiftL,
    ShiftR,
    Complement,
}
use bhc_index::Idx;
use bhc_types::Ty;
use inkwell::builder::Builder;
use inkwell::context::Context;
use inkwell::types::{BasicType, BasicTypeEnum};
use inkwell::values::{BasicValueEnum, FloatValue, FunctionValue, IntValue, PointerValue};
use rustc_hash::FxHashMap;

use super::context::LlvmContext;
use super::module::LlvmModule;
use super::types::TypeMapper;

/// FFI type representation for foreign import marshalling.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum FfiType {
    /// 8-bit integer (CChar, Int8, Word8).
    Int8,
    /// 32-bit integer (CInt on some platforms, Int32).
    Int32,
    /// 64-bit integer (Int, CSize, Word64, etc.).
    Int64,
    /// 32-bit float (CFloat, Float).
    Float,
    /// 64-bit float (CDouble, Double).
    Double,
    /// Opaque pointer (Ptr a, FunPtr a, CString).
    Ptr,
    /// Void (unit return).
    Void,
}

/// Metadata about a data constructor, used for code generation.
#[derive(Clone, Debug)]
pub struct ConstructorMeta {
    /// The constructor's tag (0-based index within the data type).
    pub tag: u32,
    /// The number of fields this constructor has.
    pub arity: u32,
    /// The data type this constructor belongs to (if known).
    pub type_name: Option<String>,
    /// Whether this constructor is a newtype constructor (identity at runtime).
    pub is_newtype: bool,
}

/// A symbol exported by an already-compiled module, used for cross-module linking.
#[derive(Clone, Debug)]
pub struct CompiledSymbol {
    /// The source-level name (interned).
    pub name: Symbol,
    /// The LLVM symbol name (module-qualified).
    pub llvm_name: String,
    /// Number of parameters the function takes.
    pub param_count: usize,
}

/// A single layer in a transformer stack.
///
/// Used to track which transformer's bind/return semantics to use during lowering,
/// and to enable automatic lift insertion for cross-transformer operations.
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
enum TransformerLayer {
    IO,
    ReaderT,
    StateT,
    ExceptT,
    WriterT,
}

/// Coercion mode for type-specialized show functions.
#[derive(Clone, Copy, Debug)]
enum ShowCoerce {
    /// Coerce to i64 for show_int
    Int,
    /// Coerce to f64 (bitcast from i64) for show_double
    Double,
    /// Coerce to f32 for show_float
    Float,
    /// Coerce to i32 (truncate from i64) for show_char
    Char,
    /// Extract ADT tag (i64) for show_bool
    Bool,
    /// Pass pointer directly for show_string ([Char])
    StringList,
    /// Pass pointer + elem type tag for show_list
    List,
    /// Pass pointer + elem type tag for show_maybe
    MaybeOf,
    /// Pass pointer + left/right type tags for show_either
    EitherOf,
    /// Pass pointer + fst/snd type tags for show_tuple2
    Tuple2Of,
    /// Pass pointer directly for show_unit
    Unit,
    /// Extract ADT tag (i64) for show_ordering
    Ordering,
    /// Pass pointer directly for show_integer (arbitrary precision)
    Integer,
}

/// A stack of transformer layers, tracking the full transformer stack.
///
/// The stack is ordered [outermost, ..., IO], so the current (outermost) layer
/// is at index 0. This enables automatic lift insertion when an operation's
/// native layer doesn't match the stack top.
#[derive(Clone, Debug)]
struct TransformerStack {
    layers: Vec<TransformerLayer>,
}

impl TransformerStack {
    /// Create a new stack with just IO at the base.
    fn new() -> Self {
        Self {
            layers: vec![TransformerLayer::IO],
        }
    }

    /// Push a new transformer layer onto the stack (becomes the new outermost).
    fn push(&mut self, layer: TransformerLayer) {
        self.layers.insert(0, layer);
    }

    /// Pop the outermost transformer layer. Returns None if only IO remains.
    fn pop(&mut self) -> Option<TransformerLayer> {
        if self.layers.len() > 1 {
            Some(self.layers.remove(0))
        } else {
            None
        }
    }

    /// Get the current (outermost) transformer layer.
    fn current(&self) -> TransformerLayer {
        self.layers.first().copied().unwrap_or(TransformerLayer::IO)
    }

    /// Calculate how many lifts are needed to reach the target layer.
    /// Returns None if the target layer is not in the stack.
    fn lifts_to_reach(&self, target: TransformerLayer) -> Option<usize> {
        self.layers.iter().position(|&l| l == target)
    }

    /// Check if the stack contains the given layer.
    fn contains(&self, layer: TransformerLayer) -> bool {
        self.layers.contains(&layer)
    }

    /// Check if this is a nested StateT over ReaderT context.
    ///
    /// Returns true if the current layer is StateT and ReaderT is in the stack below it.
    fn is_state_t_over_reader_t(&self) -> bool {
        self.layers.len() >= 2
            && self.layers[0] == TransformerLayer::StateT
            && self
                .layers
                .iter()
                .skip(1)
                .any(|&l| l == TransformerLayer::ReaderT)
    }

    /// Check if this is a genuine nested ReaderT over StateT context.
    ///
    /// Returns true if the top two layers are [ReaderT, StateT] with no additional
    /// transformer layers below (only IO or empty). This distinguishes genuine RtOvSt
    /// from auto-lift artifacts in StOvRt, where the stack looks like
    /// [ReaderT, StateT, StateT, ReaderT, IO].
    fn is_reader_t_over_state_t(&self) -> bool {
        self.layers.len() >= 2
            && self.layers[0] == TransformerLayer::ReaderT
            && self.layers[1] == TransformerLayer::StateT
            && !self
                .layers
                .iter()
                .skip(2)
                .any(|&l| l == TransformerLayer::StateT || l == TransformerLayer::ReaderT)
    }

    /// Check if this is a nested ExceptT over StateT context.
    ///
    /// Returns true if the top two layers are [ExceptT, StateT] with no additional
    /// ExceptT/StateT/ReaderT below. Excludes auto-lift transient stacks.
    fn is_except_t_over_state_t(&self) -> bool {
        self.layers.len() >= 2
            && self.layers[0] == TransformerLayer::ExceptT
            && self.layers[1] == TransformerLayer::StateT
            && !self.layers.iter().skip(2).any(|&l| {
                l == TransformerLayer::ExceptT
                    || l == TransformerLayer::StateT
                    || l == TransformerLayer::ReaderT
            })
    }

    /// Check if this is a nested ExceptT over ReaderT context.
    ///
    /// Returns true if the top two layers are [ExceptT, ReaderT] with no additional
    /// ExceptT/StateT/ReaderT below. Excludes auto-lift transient stacks.
    fn is_except_t_over_reader_t(&self) -> bool {
        self.layers.len() >= 2
            && self.layers[0] == TransformerLayer::ExceptT
            && self.layers[1] == TransformerLayer::ReaderT
            && !self.layers.iter().skip(2).any(|&l| {
                l == TransformerLayer::ExceptT
                    || l == TransformerLayer::StateT
                    || l == TransformerLayer::ReaderT
            })
    }

    /// Check if this is a nested WriterT over StateT context.
    ///
    /// Returns true if the top two layers are [WriterT, StateT] with no additional
    /// WriterT/StateT/ReaderT below. Excludes auto-lift transient stacks.
    fn is_writer_t_over_state_t(&self) -> bool {
        self.layers.len() >= 2
            && self.layers[0] == TransformerLayer::WriterT
            && self.layers[1] == TransformerLayer::StateT
            && !self.layers.iter().skip(2).any(|&l| {
                l == TransformerLayer::WriterT
                    || l == TransformerLayer::StateT
                    || l == TransformerLayer::ReaderT
            })
    }

    /// Check if this is a nested WriterT over ReaderT context.
    ///
    /// Returns true if the top two layers are [WriterT, ReaderT] with no additional
    /// WriterT/StateT/ReaderT below. Excludes auto-lift transient stacks.
    fn is_writer_t_over_reader_t(&self) -> bool {
        self.layers.len() >= 2
            && self.layers[0] == TransformerLayer::WriterT
            && self.layers[1] == TransformerLayer::ReaderT
            && !self.layers.iter().skip(2).any(|&l| {
                l == TransformerLayer::WriterT
                    || l == TransformerLayer::StateT
                    || l == TransformerLayer::ReaderT
            })
    }
}

/// State for lowering Core IR to LLVM IR.
///
/// The struct has two lifetimes:
/// - `'ctx`: The LLVM context lifetime (for types and values)
/// - `'m`: The module reference lifetime (can be shorter than 'ctx)
pub struct Lowering<'ctx, 'm> {
    /// The underlying LLVM context (borrowed from LlvmContext).
    llvm_ctx: &'ctx Context,
    /// The LLVM module being generated.
    module: &'m LlvmModule<'ctx>,
    /// Mapping from Core variables to LLVM values.
    env: FxHashMap<VarId, BasicValueEnum<'ctx>>,
    /// Mapping from Core variables to LLVM functions (for top-level bindings).
    functions: FxHashMap<VarId, FunctionValue<'ctx>>,
    /// Counter for generating unique closure names.
    closure_counter: u32,
    /// Mapping from constructor names to metadata (tag, arity).
    /// This is populated from DataCon entries in case alternatives.
    constructor_metadata: FxHashMap<String, ConstructorMeta>,
    /// Whether we're currently lowering an expression in tail position.
    /// Used for tail call optimization.
    in_tail_position: bool,
    /// Optional module name for symbol mangling in multi-module compilation.
    module_name: Option<String>,
    /// External functions imported from other compiled modules.
    external_functions: FxHashMap<Symbol, FunctionValue<'ctx>>,
    /// Stack tracking the full transformer stack for automatic lift insertion.
    /// Pushed when entering transformer runners (runReaderT, evalStateT, etc.).
    transformer_stack: TransformerStack,
    /// Set of function names whose bodies use StateT operations.
    /// Used to detect monad context when these functions appear in bind chains.
    state_t_functions: FxHashSet<String>,
    /// Set of function names whose bodies use ReaderT operations.
    reader_t_functions: FxHashSet<String>,
    /// Set of function names whose bodies use ExceptT operations.
    except_t_functions: FxHashSet<String>,
    /// Set of function names whose bodies use WriterT operations.
    writer_t_functions: FxHashSet<String>,
    /// Map from type name → VarId of the $derived_show_TypeName function.
    derived_show_fns: FxHashMap<String, VarId>,
    /// Map from type name → VarId of the $derived_read_TypeName function.
    derived_read_fns: FxHashMap<String, VarId>,
    /// Map from type name → VarId of the $derived_eq_TypeName function.
    derived_eq_fns: FxHashMap<String, VarId>,
    /// Map from type name → VarId of the $derived_compare_TypeName function.
    derived_compare_fns: FxHashMap<String, VarId>,
    /// Map from type name → VarId of the $derived_fmap_TypeName function.
    derived_functor_fns: FxHashMap<String, VarId>,
    /// Map from type name → VarId of the $derived_foldr_TypeName function.
    derived_foldable_fns: FxHashMap<String, VarId>,
    /// Map from type name → VarId of the $derived_traverse_TypeName function.
    derived_traversable_fns: FxHashMap<String, VarId>,
    /// Map from type name → VarId of the $derived_fromEnum_TypeName function.
    derived_from_enum_fns: FxHashMap<String, VarId>,
    /// Map from type name → VarId of the $derived_toEnum_TypeName function.
    derived_to_enum_fns: FxHashMap<String, VarId>,
    /// Map from type name → VarId of the $derived_minBound_TypeName function.
    derived_min_bound_fns: FxHashMap<String, VarId>,
    /// Map from type name → VarId of the $derived_maxBound_TypeName function.
    derived_max_bound_fns: FxHashMap<String, VarId>,
    /// Map from type name → VarId of the $derived_from_TypeName function (GHC.Generics).
    derived_from_fns: FxHashMap<String, VarId>,
    /// Map from type name → VarId of the $derived_to_TypeName function (GHC.Generics).
    derived_to_fns: FxHashMap<String, VarId>,
    /// Counter for generating unique show descriptor global names.
    show_desc_counter: usize,
    /// Whether {-# LANGUAGE OverloadedStrings #-} is enabled.
    overloaded_strings: bool,
    /// E.44: Set of variable IDs that were thunked (lazy let-bindings).
    /// When these variables are looked up, they must be forced via bhc_force.
    thunked_vars: FxHashSet<VarId>,
    /// E.45: Set of variable IDs that hold Integer (arbitrary precision) values.
    /// Used by show dispatch to route to bhc_integer_show instead of show_int.
    integer_vars: FxHashSet<VarId>,
    /// Cache of generated stub functions for unimplemented external package functions.
    /// When a stub variable is referenced, we lazily generate an LLVM function that
    /// calls bhc_error with the stub name and cache it here.
    stub_functions: FxHashMap<String, FunctionValue<'ctx>>,
}

impl<'ctx, 'm> Lowering<'ctx, 'm> {
    /// Create a new lowering context.
    pub fn new(ctx: &'ctx LlvmContext, module: &'m LlvmModule<'ctx>) -> Self {
        let mut lowering = Self {
            llvm_ctx: ctx.llvm_context(),
            module,
            env: FxHashMap::default(),
            functions: FxHashMap::default(),
            closure_counter: 0,
            constructor_metadata: FxHashMap::default(),
            in_tail_position: false,
            module_name: None,
            external_functions: FxHashMap::default(),
            transformer_stack: TransformerStack::new(),
            state_t_functions: FxHashSet::default(),
            reader_t_functions: FxHashSet::default(),
            except_t_functions: FxHashSet::default(),
            writer_t_functions: FxHashSet::default(),
            derived_show_fns: FxHashMap::default(),
            derived_read_fns: FxHashMap::default(),
            derived_eq_fns: FxHashMap::default(),
            derived_compare_fns: FxHashMap::default(),
            derived_functor_fns: FxHashMap::default(),
            derived_foldable_fns: FxHashMap::default(),
            derived_traversable_fns: FxHashMap::default(),
            derived_from_enum_fns: FxHashMap::default(),
            derived_to_enum_fns: FxHashMap::default(),
            derived_min_bound_fns: FxHashMap::default(),
            derived_max_bound_fns: FxHashMap::default(),
            derived_from_fns: FxHashMap::default(),
            derived_to_fns: FxHashMap::default(),
            show_desc_counter: 0,
            overloaded_strings: false,
            thunked_vars: FxHashSet::default(),
            integer_vars: FxHashSet::default(),
            stub_functions: FxHashMap::default(),
        };
        lowering.declare_rts_functions();
        lowering
    }

    /// Create a new lowering context for multi-module compilation.
    ///
    /// Accepts a module name for symbol mangling and a list of imported symbols
    /// from already-compiled modules to declare as external.
    pub fn new_multimodule(
        ctx: &'ctx LlvmContext,
        module: &'m LlvmModule<'ctx>,
        module_name: &str,
        imported_symbols: &[CompiledSymbol],
    ) -> CodegenResult<Self> {
        let mut lowering = Self {
            llvm_ctx: ctx.llvm_context(),
            module,
            env: FxHashMap::default(),
            functions: FxHashMap::default(),
            closure_counter: 0,
            constructor_metadata: FxHashMap::default(),
            in_tail_position: false,
            module_name: Some(module_name.to_string()),
            external_functions: FxHashMap::default(),
            transformer_stack: TransformerStack::new(),
            state_t_functions: FxHashSet::default(),
            reader_t_functions: FxHashSet::default(),
            except_t_functions: FxHashSet::default(),
            writer_t_functions: FxHashSet::default(),
            derived_show_fns: FxHashMap::default(),
            derived_read_fns: FxHashMap::default(),
            derived_eq_fns: FxHashMap::default(),
            derived_compare_fns: FxHashMap::default(),
            derived_functor_fns: FxHashMap::default(),
            derived_foldable_fns: FxHashMap::default(),
            derived_traversable_fns: FxHashMap::default(),
            derived_from_enum_fns: FxHashMap::default(),
            derived_to_enum_fns: FxHashMap::default(),
            derived_min_bound_fns: FxHashMap::default(),
            derived_max_bound_fns: FxHashMap::default(),
            derived_from_fns: FxHashMap::default(),
            derived_to_fns: FxHashMap::default(),
            show_desc_counter: 0,
            overloaded_strings: false,
            thunked_vars: FxHashSet::default(),
            integer_vars: FxHashSet::default(),
            stub_functions: FxHashMap::default(),
        };
        lowering.declare_rts_functions();
        lowering.declare_external_symbols(imported_symbols)?;
        Ok(lowering)
    }

    /// Push a transformer layer onto the stack (e.g. when entering a transformer runner).
    fn push_transformer_layer(&mut self, layer: TransformerLayer) {
        self.transformer_stack.push(layer);
    }

    /// Pop the transformer stack (e.g. when leaving a transformer runner).
    fn pop_transformer_layer(&mut self) {
        self.transformer_stack.pop();
    }

    /// Return the current transformer layer (outermost, defaults to IO).
    fn current_transformer_layer(&self) -> TransformerLayer {
        self.transformer_stack.current()
    }

    /// Detect the native transformer layer of an operation by inspecting the head symbol.
    /// This determines which transformer the operation belongs to natively
    /// (e.g. `ask` belongs to ReaderT, `get` belongs to StateT).
    fn detect_operation_layer(&self, expr: &Expr) -> TransformerLayer {
        match expr {
            Expr::Var(v, _) => {
                let name = v.name.as_str();
                match name {
                    "ask" | "asks" | "local" | "ReaderT.>>=" | "ReaderT.>>" | "ReaderT.pure"
                    | "ReaderT.fmap" | "ReaderT.<*>" | "ReaderT.lift" | "ReaderT.liftIO" => {
                        TransformerLayer::ReaderT
                    }
                    "get" | "put" | "modify" | "gets" | "StateT.>>=" | "StateT.>>"
                    | "StateT.pure" | "StateT.fmap" | "StateT.<*>" | "StateT.lift"
                    | "StateT.liftIO" => TransformerLayer::StateT,
                    "throwE" | "catchE" | "throwError" | "catchError" | "ExceptT.>>="
                    | "ExceptT.>>" | "ExceptT.pure" | "ExceptT.fmap" | "ExceptT.<*>"
                    | "ExceptT.lift" | "ExceptT.liftIO" => TransformerLayer::ExceptT,
                    "tell" | "WriterT.>>=" | "WriterT.>>" | "WriterT.pure" | "WriterT.fmap"
                    | "WriterT.<*>" | "WriterT.lift" | "WriterT.liftIO" => {
                        TransformerLayer::WriterT
                    }
                    _ => {
                        // Check if this is a user-defined function that was detected
                        // as using StateT/ReaderT/ExceptT/WriterT operations in its body.
                        if self.state_t_functions.contains(name) {
                            TransformerLayer::StateT
                        } else if self.reader_t_functions.contains(name) {
                            TransformerLayer::ReaderT
                        } else if self.except_t_functions.contains(name) {
                            TransformerLayer::ExceptT
                        } else if self.writer_t_functions.contains(name) {
                            TransformerLayer::WriterT
                        } else {
                            self.current_transformer_layer()
                        }
                    }
                }
            }
            Expr::App(func, _, _) => self.detect_operation_layer(func),
            _ => self.current_transformer_layer(),
        }
    }

    /// Extract the transformer stack from a type signature.
    ///
    /// For a type like `StateT Int (ReaderT String IO) Int`, this returns
    /// `[StateT, ReaderT, IO]` from outermost to innermost.
    ///
    /// For nested transformers, we examine the monad parameter (the second-to-last
    /// type argument) recursively.
    ///
    /// Used for nested transformer support to set up the full transformer stack.
    fn extract_transformer_stack_from_type(&self, ty: &Ty) -> Vec<TransformerLayer> {
        let mut stack = Vec::new();
        self.extract_transformer_stack_recursive(ty, &mut stack);
        stack
    }

    /// Helper for recursively building the transformer stack from a type.
    fn extract_transformer_stack_recursive(&self, ty: &Ty, stack: &mut Vec<TransformerLayer>) {
        match ty {
            // Check for IO at the base
            Ty::Con(tycon) if tycon.name.as_str() == "IO" => {
                stack.push(TransformerLayer::IO);
            }

            // Check for transformer application. Can be either:
            // - T x m a (fully applied): App(App(App(Con(T), x), m), a)
            // - T x m (partial application): App(App(Con(T), x), m)
            Ty::App(inner, arg) => {
                // Case 1: Full application T x m a - go inside to get App(App(Con(T), x), m)
                if let Ty::App(inner2, monad_arg) = inner.as_ref() {
                    // inner2 might be App(Con(T), x) or Con(T) or App(App(Con(T), x), m)
                    if let Ty::App(con_or_app, _param) = inner2.as_ref() {
                        // con_or_app should be Con(T)
                        if let Ty::Con(tycon) = con_or_app.as_ref() {
                            let layer = match tycon.name.as_str() {
                                "StateT" => Some(TransformerLayer::StateT),
                                "ReaderT" => Some(TransformerLayer::ReaderT),
                                "ExceptT" => Some(TransformerLayer::ExceptT),
                                "WriterT" => Some(TransformerLayer::WriterT),
                                _ => None,
                            };
                            if let Some(l) = layer {
                                stack.push(l);
                                // Recursively extract from the monad argument
                                self.extract_transformer_stack_recursive(monad_arg, stack);
                                return;
                            }
                        }
                    }

                    // Case 2: Partial application T x m - inner2 is Con(T)
                    // Structure is App(App(Con(T), x), m) where we're at the outer App
                    // Here, inner2 = Con(T), monad_arg = m (but we need to check inner/arg differently)
                    if let Ty::Con(tycon) = inner2.as_ref() {
                        let layer = match tycon.name.as_str() {
                            "StateT" => Some(TransformerLayer::StateT),
                            "ReaderT" => Some(TransformerLayer::ReaderT),
                            "ExceptT" => Some(TransformerLayer::ExceptT),
                            "WriterT" => Some(TransformerLayer::WriterT),
                            _ => None,
                        };
                        if let Some(l) = layer {
                            stack.push(l);
                            // The arg is the monad 'm' for partial applications
                            self.extract_transformer_stack_recursive(arg, stack);
                            return;
                        }
                    }
                }

                // Case 3: Partial application - inner is Con(T)
                // Structure is App(Con(T), x) - not enough args, recurse on arg
                if let Ty::Con(tycon) = inner.as_ref() {
                    // Check if it's IO
                    if tycon.name.as_str() == "IO" {
                        stack.push(TransformerLayer::IO);
                        return;
                    }
                    // Otherwise, single-param application, continue
                }

                // Fallback: recurse on arg to find inner monads
                self.extract_transformer_stack_recursive(arg, stack);
            }
            _ => {}
        }
    }

    /// Check whether an expression tree contains StateT/ReaderT/ExceptT/WriterT operations.
    /// Used to determine the transformer layer for top-level function definitions
    /// that are used as transformer computations.
    fn detect_transformer_for_body(&self, expr: &Expr) -> Option<TransformerLayer> {
        match expr {
            Expr::Var(v, _) => {
                let name = v.name.as_str();
                match name {
                    "get" | "put" | "modify" | "gets" | "StateT.>>=" | "StateT.>>"
                    | "StateT.pure" => Some(TransformerLayer::StateT),
                    "ask" | "asks" | "local" | "ReaderT.>>=" | "ReaderT.>>" | "ReaderT.pure" => {
                        Some(TransformerLayer::ReaderT)
                    }
                    "throwE" | "catchE" | "throwError" | "catchError" | "ExceptT.>>="
                    | "ExceptT.>>" | "ExceptT.pure" => Some(TransformerLayer::ExceptT),
                    "tell" | "WriterT.>>=" | "WriterT.>>" | "WriterT.pure" => {
                        Some(TransformerLayer::WriterT)
                    }
                    _ => None,
                }
            }
            Expr::App(func, arg, _) => self
                .detect_transformer_for_body(func)
                .or_else(|| self.detect_transformer_for_body(arg)),
            Expr::Lam(_, body, _) => self.detect_transformer_for_body(body),
            Expr::Let(bind, body, _) => {
                let in_bind = match bind.as_ref() {
                    Bind::NonRec(_, e) => self.detect_transformer_for_body(e),
                    Bind::Rec(bindings) => bindings
                        .iter()
                        .find_map(|(_, e)| self.detect_transformer_for_body(e)),
                };
                in_bind.or_else(|| self.detect_transformer_for_body(body))
            }
            Expr::Case(scrut, alts, _, _) => {
                self.detect_transformer_for_body(scrut).or_else(|| {
                    alts.iter()
                        .find_map(|alt| self.detect_transformer_for_body(&alt.rhs))
                })
            }
            Expr::TyApp(e, _, _) => self.detect_transformer_for_body(e),
            _ => None,
        }
    }

    // ========================================================================
    // Automatic Lift Insertion for Cross-Transformer Operations
    // ========================================================================

    /// Lower a bind operation with automatic lifting for cross-transformer use.
    ///
    /// When the LHS operation's native layer differs from the current stack top,
    /// we wrap the operation result with appropriate lifts before binding.
    fn lower_bind_with_auto_lift(
        &mut self,
        lhs_expr: &Expr,
        rhs_expr: &Expr,
        native_layer: TransformerLayer,
        current_layer: TransformerLayer,
    ) -> CodegenResult<Option<BasicValueEnum<'ctx>>> {
        // Lower the LHS with its native context to get the inner action
        self.push_transformer_layer(native_layer);
        let inner_val = self
            .lower_expr(lhs_expr)?
            .ok_or_else(|| CodegenError::Internal("auto-lift: LHS has no value".to_string()))?;
        self.pop_transformer_layer();

        // Apply the lift to wrap from native layer to current layer
        let lifted_val = self.apply_lift_for_layer(inner_val, native_layer, current_layer)?;

        // Now dispatch bind to the current layer with the lifted value
        match current_layer {
            TransformerLayer::StateT => self.lower_state_t_bind_with_value(lifted_val, rhs_expr),
            TransformerLayer::ReaderT => self.lower_reader_t_bind_with_value(lifted_val, rhs_expr),
            TransformerLayer::ExceptT => self.lower_except_t_bind_with_value(lifted_val, rhs_expr),
            TransformerLayer::WriterT => self.lower_writer_t_bind_with_value(lifted_val, rhs_expr),
            TransformerLayer::IO => self.lower_io_bind_with_value(lifted_val, rhs_expr),
        }
    }

    /// Lower a then operation with automatic lifting for cross-transformer use.
    fn lower_then_with_auto_lift(
        &mut self,
        lhs_expr: &Expr,
        rhs_expr: &Expr,
        native_layer: TransformerLayer,
        current_layer: TransformerLayer,
    ) -> CodegenResult<Option<BasicValueEnum<'ctx>>> {
        // Lower the LHS with its native context to get the inner action
        self.push_transformer_layer(native_layer);
        let inner_val = self.lower_expr(lhs_expr)?.ok_or_else(|| {
            CodegenError::Internal("auto-lift then: LHS has no value".to_string())
        })?;
        self.pop_transformer_layer();

        // Apply the lift to wrap from native layer to current layer
        let lifted_val = self.apply_lift_for_layer(inner_val, native_layer, current_layer)?;

        // Now dispatch then to the current layer with the lifted value
        match current_layer {
            TransformerLayer::StateT => self.lower_state_t_then_with_value(lifted_val, rhs_expr),
            TransformerLayer::ReaderT => self.lower_reader_t_then_with_value(lifted_val, rhs_expr),
            TransformerLayer::ExceptT => self.lower_except_t_then_with_value(lifted_val, rhs_expr),
            TransformerLayer::WriterT => self.lower_writer_t_then_with_value(lifted_val, rhs_expr),
            TransformerLayer::IO => self.lower_io_then_with_value(lifted_val, rhs_expr),
        }
    }

    /// Apply a lift to wrap an inner transformer's action in the outer transformer.
    ///
    /// For example, lifting a ReaderT action into StateT produces:
    /// `StateT.lift (runReaderT action env)` which becomes `\s -> (runReaderT action env, s)`
    fn apply_lift_for_layer(
        &mut self,
        inner_val: BasicValueEnum<'ctx>,
        _native_layer: TransformerLayer,
        outer_layer: TransformerLayer,
    ) -> CodegenResult<BasicValueEnum<'ctx>> {
        // The inner_val is already a closure representing the inner transformer's action.
        // We need to wrap it with the outer transformer's lift operation.
        match outer_layer {
            TransformerLayer::StateT => {
                // StateT.lift action = \s -> (action, s)
                // The action from native_layer returns a value when run,
                // and we pair it with the unchanged state.
                self.apply_state_t_lift_to_value(inner_val)
            }
            TransformerLayer::ReaderT => {
                // ReaderT.lift action = \r -> action
                // The action from native_layer is returned ignoring the environment.
                self.apply_reader_t_lift_to_value(inner_val)
            }
            TransformerLayer::ExceptT => {
                // ExceptT.lift action = wrap action in Right
                self.apply_except_t_lift_to_value(inner_val)
            }
            TransformerLayer::WriterT => {
                // WriterT.lift action = (action, mempty)
                self.apply_writer_t_lift_to_value(inner_val)
            }
            TransformerLayer::IO => {
                // IO is the base, no lifting needed
                Ok(inner_val)
            }
        }
    }

    /// Apply StateT.lift to a value (creates closure \s -> (action, s))
    ///
    /// For nested transformers (StateT over ReaderT), creates a 3-arg closure:
    /// \(env, s, reader_env) -> (run_reader(action, reader_env), s)
    fn apply_state_t_lift_to_value(
        &mut self,
        action_val: BasicValueEnum<'ctx>,
    ) -> CodegenResult<BasicValueEnum<'ctx>> {
        let ptr_type = self.type_mapper().ptr_type();

        // Check if we're lifting a ReaderT action in a nested StateT-over-ReaderT context
        if self.transformer_stack.is_state_t_over_reader_t() {
            // Nested case: create a 3-arg closure that runs the inner ReaderT action
            let fn_name = "bhc_state_t_lift_reader_t_nested";
            let func = self.get_or_create_nested_transformer_fn(fn_name);
            if func.count_basic_blocks() == 0 {
                let entry = self.llvm_ctx.append_basic_block(func, "entry");
                let saved_bb = self.builder().get_insert_block();
                self.builder().position_at_end(entry);
                // \(env, s, reader_env) -> (call(inner_closure, inner_closure, reader_env), s)
                // where inner_closure = env[0]
                let env = func.get_nth_param(0).unwrap().into_pointer_value();
                let s = func.get_nth_param(1).unwrap();
                let reader_env = func.get_nth_param(2).unwrap();

                // Extract the inner ReaderT closure from our closure's environment
                let inner_closure = self.extract_closure_env_elem(env, 1, 0)?;

                // Call the inner ReaderT closure with (inner_closure_as_env, reader_env)
                // ReaderT closures take (closure_env, reader_env) and return the result
                let inner_fn = self.extract_closure_fn_ptr(inner_closure)?;
                let fn_type = ptr_type.fn_type(&[ptr_type.into(), ptr_type.into()], false);
                let result = self
                    .builder()
                    .build_indirect_call(
                        fn_type,
                        inner_fn,
                        &[inner_closure.into(), reader_env.into()],
                        "lift_reader_result",
                    )
                    .map_err(|e| {
                        CodegenError::Internal(format!("state_t_lift_nested call inner: {:?}", e))
                    })?
                    .try_as_basic_value()
                    .basic()
                    .ok_or_else(|| {
                        CodegenError::Internal(
                            "state_t_lift_nested: inner returned void".to_string(),
                        )
                    })?;

                // Return (result, s)
                let pair = self.alloc_pair(result, s)?;
                self.builder().build_return(Some(&pair)).map_err(|e| {
                    CodegenError::Internal(format!("state_t_lift_nested return: {:?}", e))
                })?;
                if let Some(bb) = saved_bb {
                    self.builder().position_at_end(bb);
                }
            }

            let fn_ptr = func.as_global_value().as_pointer_value();
            let action_ptr = self.value_to_ptr(action_val)?;
            let closure_ptr =
                self.alloc_closure(fn_ptr, &[(VarId::new(900100), action_ptr.into())])?;
            return Ok(closure_ptr.into());
        }

        // Non-nested case: simple 2-arg closure
        let fn_name = "bhc_state_t_lift_auto";

        let func = self.get_or_create_transformer_fn(fn_name);
        if func.count_basic_blocks() == 0 {
            let entry = self.llvm_ctx.append_basic_block(func, "entry");
            let saved_bb = self.builder().get_insert_block();
            self.builder().position_at_end(entry);
            // \(env, s) -> (action, s) where action = env[0]
            let env = func.get_nth_param(0).unwrap().into_pointer_value();
            let s = func.get_nth_param(1).unwrap();
            let action = self.extract_closure_env_elem(env, 1, 0)?;
            let pair = self.alloc_pair(action.into(), s)?;
            self.builder().build_return(Some(&pair)).map_err(|e| {
                CodegenError::Internal(format!("state_t_lift_auto return: {:?}", e))
            })?;
            if let Some(bb) = saved_bb {
                self.builder().position_at_end(bb);
            }
        }

        let fn_ptr = func.as_global_value().as_pointer_value();
        let action_ptr = self.value_to_ptr(action_val)?;
        let closure_ptr = self.alloc_closure(fn_ptr, &[(VarId::new(900100), action_ptr.into())])?;
        Ok(closure_ptr.into())
    }

    /// Apply ReaderT.lift to a value (creates closure \r -> action)
    fn apply_reader_t_lift_to_value(
        &mut self,
        action_val: BasicValueEnum<'ctx>,
    ) -> CodegenResult<BasicValueEnum<'ctx>> {
        let fn_name = "bhc_reader_t_lift_auto";

        let func = self.get_or_create_transformer_fn(fn_name);
        if func.count_basic_blocks() == 0 {
            let entry = self.llvm_ctx.append_basic_block(func, "entry");
            let saved_bb = self.builder().get_insert_block();
            self.builder().position_at_end(entry);
            // \(env, _r) -> action where action = env[0]
            let env = func.get_nth_param(0).unwrap().into_pointer_value();
            let action = self.extract_closure_env_elem(env, 1, 0)?;
            self.builder().build_return(Some(&action)).map_err(|e| {
                CodegenError::Internal(format!("reader_t_lift_auto return: {:?}", e))
            })?;
            if let Some(bb) = saved_bb {
                self.builder().position_at_end(bb);
            }
        }

        let fn_ptr = func.as_global_value().as_pointer_value();
        let action_ptr = self.value_to_ptr(action_val)?;
        let closure_ptr = self.alloc_closure(fn_ptr, &[(VarId::new(900101), action_ptr.into())])?;
        Ok(closure_ptr.into())
    }

    /// Apply ExceptT.lift to a value (wraps in Right)
    fn apply_except_t_lift_to_value(
        &mut self,
        action_val: BasicValueEnum<'ctx>,
    ) -> CodegenResult<BasicValueEnum<'ctx>> {
        // ExceptT over StateT: \(env, s) -> (Right(inner(s).fst), inner(s).snd)
        if self.transformer_stack.is_except_t_over_state_t() {
            return self.apply_except_t_lift_to_value_over_st(action_val);
        }
        // ExceptT over ReaderT: \(env, r) -> Right(inner(r))
        if self.transformer_stack.is_except_t_over_reader_t() {
            return self.apply_except_t_lift_to_value_over_rt(action_val);
        }

        let fn_name = "bhc_except_t_lift_auto";

        let func = self.get_or_create_transformer_fn(fn_name);
        if func.count_basic_blocks() == 0 {
            let entry = self.llvm_ctx.append_basic_block(func, "entry");
            let saved_bb = self.builder().get_insert_block();
            self.builder().position_at_end(entry);
            // \(env, _) -> Right action where action = env[0]
            let env = func.get_nth_param(0).unwrap().into_pointer_value();
            let action = self.extract_closure_env_elem(env, 1, 0)?;
            // Allocate Right ADT: tag=1, arity=1
            let right_adt = self.alloc_adt(1, 1)?;
            self.store_adt_field(right_adt, 1, 0, action.into())?;
            self.builder().build_return(Some(&right_adt)).map_err(|e| {
                CodegenError::Internal(format!("except_t_lift_auto return: {:?}", e))
            })?;
            if let Some(bb) = saved_bb {
                self.builder().position_at_end(bb);
            }
        }

        let fn_ptr = func.as_global_value().as_pointer_value();
        let action_ptr = self.value_to_ptr(action_val)?;
        let closure_ptr = self.alloc_closure(fn_ptr, &[(VarId::new(900102), action_ptr.into())])?;
        Ok(closure_ptr.into())
    }

    /// Apply WriterT.lift to a value (pairs with mempty)
    fn apply_writer_t_lift_to_value(
        &mut self,
        action_val: BasicValueEnum<'ctx>,
    ) -> CodegenResult<BasicValueEnum<'ctx>> {
        // WriterT over StateT: \(env, s) -> ((action(action, s).fst, []), action(action, s).snd)
        if self.transformer_stack.is_writer_t_over_state_t() {
            return self.apply_writer_t_lift_to_value_over_st(action_val);
        }
        // WriterT over ReaderT: \(env, r) -> (action(action, r), [])
        if self.transformer_stack.is_writer_t_over_reader_t() {
            return self.apply_writer_t_lift_to_value_over_rt(action_val);
        }

        let fn_name = "bhc_writer_t_lift_auto";

        let func = self.get_or_create_transformer_fn(fn_name);
        if func.count_basic_blocks() == 0 {
            let entry = self.llvm_ctx.append_basic_block(func, "entry");
            let saved_bb = self.builder().get_insert_block();
            self.builder().position_at_end(entry);
            // \(env, _) -> (action, []) where action = env[0]
            let env = func.get_nth_param(0).unwrap().into_pointer_value();
            let action = self.extract_closure_env_elem(env, 1, 0)?;
            // Build empty list as mempty for the writer log
            let empty_list = self.build_nil()?;
            let pair = self.alloc_pair(action.into(), empty_list.into())?;
            self.builder().build_return(Some(&pair)).map_err(|e| {
                CodegenError::Internal(format!("writer_t_lift_auto return: {:?}", e))
            })?;
            if let Some(bb) = saved_bb {
                self.builder().position_at_end(bb);
            }
        }

        let fn_ptr = func.as_global_value().as_pointer_value();
        let action_ptr = self.value_to_ptr(action_val)?;
        let closure_ptr = self.alloc_closure(fn_ptr, &[(VarId::new(900103), action_ptr.into())])?;
        Ok(closure_ptr.into())
    }

    // ========================================================================
    // Bind/Then with Pre-Evaluated LHS Value
    // ========================================================================

    /// StateT bind with a pre-evaluated LHS value
    fn lower_state_t_bind_with_value(
        &mut self,
        lhs_val: BasicValueEnum<'ctx>,
        rhs_expr: &Expr,
    ) -> CodegenResult<Option<BasicValueEnum<'ctx>>> {
        // The LHS is already a StateT closure (function s -> (a, s'))
        // We need to create a new closure that runs LHS, then runs RHS with the result
        let rhs_val = self.lower_expr(rhs_expr)?.ok_or_else(|| {
            CodegenError::Internal("state_t_bind_val: RHS has no value".to_string())
        })?;

        let ptr_type = self.type_mapper().ptr_type();
        let fn_name = "bhc_state_t_bind_val";

        let func = self.get_or_create_transformer_fn(fn_name);
        if func.count_basic_blocks() == 0 {
            let entry = self.llvm_ctx.append_basic_block(func, "entry");
            let saved_bb = self.builder().get_insert_block();
            self.builder().position_at_end(entry);

            // \(env, s) -> let (a, s') = m(s) in k(a)(s')
            let env = func.get_nth_param(0).unwrap().into_pointer_value();
            let s = func.get_nth_param(1).unwrap();
            let m = self.extract_closure_env_elem(env, 2, 0)?;
            let k = self.extract_closure_env_elem(env, 2, 1)?;

            let fn_type = ptr_type.fn_type(&[ptr_type.into(), ptr_type.into()], false);

            // Run m(s) to get (a, s')
            let m_fn = self.extract_closure_fn_ptr(m)?;
            let s_ptr = self.value_to_ptr(s)?;
            let pair = self
                .builder()
                .build_indirect_call(fn_type, m_fn, &[m.into(), s_ptr.into()], "state_bind_m")
                .map_err(|e| CodegenError::Internal(format!("state_t_bind m: {:?}", e)))?
                .try_as_basic_value()
                .basic()
                .ok_or_else(|| CodegenError::Internal("state_t_bind m: void".to_string()))?
                .into_pointer_value();

            let a = self.extract_pair_fst(pair)?;
            let s_prime = self.extract_pair_snd(pair)?;

            // Run k(a) to get a new StateT closure
            let k_fn = self.extract_closure_fn_ptr(k)?;
            let a_ptr = self.value_to_ptr(a.into())?;
            let k_result = self
                .builder()
                .build_indirect_call(fn_type, k_fn, &[k.into(), a_ptr.into()], "state_bind_k")
                .map_err(|e| CodegenError::Internal(format!("state_t_bind k: {:?}", e)))?
                .try_as_basic_value()
                .basic()
                .ok_or_else(|| CodegenError::Internal("state_t_bind k: void".to_string()))?
                .into_pointer_value();

            // Run the resulting closure with s'
            let k_result_fn = self.extract_closure_fn_ptr(k_result)?;
            let s_prime_ptr = self.value_to_ptr(s_prime.into())?;
            let final_result = self
                .builder()
                .build_indirect_call(
                    fn_type,
                    k_result_fn,
                    &[k_result.into(), s_prime_ptr.into()],
                    "state_bind_final",
                )
                .map_err(|e| CodegenError::Internal(format!("state_t_bind final: {:?}", e)))?
                .try_as_basic_value()
                .basic()
                .ok_or_else(|| CodegenError::Internal("state_t_bind final: void".to_string()))?;

            self.builder()
                .build_return(Some(&final_result))
                .map_err(|e| CodegenError::Internal(format!("state_t_bind return: {:?}", e)))?;
            if let Some(bb) = saved_bb {
                self.builder().position_at_end(bb);
            }
        }

        let fn_ptr = func.as_global_value().as_pointer_value();
        let m_ptr = self.value_to_ptr(lhs_val)?;
        let k_ptr = self.value_to_ptr(rhs_val)?;
        let closure_ptr = self.alloc_closure(
            fn_ptr,
            &[
                (VarId::new(900200), m_ptr.into()),
                (VarId::new(900201), k_ptr.into()),
            ],
        )?;
        Ok(Some(closure_ptr.into()))
    }

    /// ReaderT bind with a pre-evaluated LHS value
    fn lower_reader_t_bind_with_value(
        &mut self,
        lhs_val: BasicValueEnum<'ctx>,
        rhs_expr: &Expr,
    ) -> CodegenResult<Option<BasicValueEnum<'ctx>>> {
        let rhs_val = self.lower_expr(rhs_expr)?.ok_or_else(|| {
            CodegenError::Internal("reader_t_bind_val: RHS has no value".to_string())
        })?;

        let ptr_type = self.type_mapper().ptr_type();
        let fn_name = "bhc_reader_t_bind_val";

        let func = self.get_or_create_transformer_fn(fn_name);
        if func.count_basic_blocks() == 0 {
            let entry = self.llvm_ctx.append_basic_block(func, "entry");
            let saved_bb = self.builder().get_insert_block();
            self.builder().position_at_end(entry);

            // \(env, r) -> let a = m(r) in k(a)(r)
            let env = func.get_nth_param(0).unwrap().into_pointer_value();
            let r = func.get_nth_param(1).unwrap();
            let m = self.extract_closure_env_elem(env, 2, 0)?;
            let k = self.extract_closure_env_elem(env, 2, 1)?;

            let fn_type = ptr_type.fn_type(&[ptr_type.into(), ptr_type.into()], false);

            // Run m(r) to get a
            let m_fn = self.extract_closure_fn_ptr(m)?;
            let r_ptr = self.value_to_ptr(r)?;
            let a = self
                .builder()
                .build_indirect_call(fn_type, m_fn, &[m.into(), r_ptr.into()], "reader_bind_m")
                .map_err(|e| CodegenError::Internal(format!("reader_t_bind m: {:?}", e)))?
                .try_as_basic_value()
                .basic()
                .ok_or_else(|| CodegenError::Internal("reader_t_bind m: void".to_string()))?;

            // Run k(a) to get new ReaderT closure
            let k_fn = self.extract_closure_fn_ptr(k)?;
            let a_ptr = self.value_to_ptr(a)?;
            let k_result = self
                .builder()
                .build_indirect_call(fn_type, k_fn, &[k.into(), a_ptr.into()], "reader_bind_k")
                .map_err(|e| CodegenError::Internal(format!("reader_t_bind k: {:?}", e)))?
                .try_as_basic_value()
                .basic()
                .ok_or_else(|| CodegenError::Internal("reader_t_bind k: void".to_string()))?
                .into_pointer_value();

            // Run k_result(r)
            let k_result_fn = self.extract_closure_fn_ptr(k_result)?;
            let final_result = self
                .builder()
                .build_indirect_call(
                    fn_type,
                    k_result_fn,
                    &[k_result.into(), r_ptr.into()],
                    "reader_bind_final",
                )
                .map_err(|e| CodegenError::Internal(format!("reader_t_bind final: {:?}", e)))?
                .try_as_basic_value()
                .basic()
                .ok_or_else(|| CodegenError::Internal("reader_t_bind final: void".to_string()))?;

            self.builder()
                .build_return(Some(&final_result))
                .map_err(|e| CodegenError::Internal(format!("reader_t_bind return: {:?}", e)))?;
            if let Some(bb) = saved_bb {
                self.builder().position_at_end(bb);
            }
        }

        let fn_ptr = func.as_global_value().as_pointer_value();
        let m_ptr = self.value_to_ptr(lhs_val)?;
        let k_ptr = self.value_to_ptr(rhs_val)?;
        let closure_ptr = self.alloc_closure(
            fn_ptr,
            &[
                (VarId::new(900202), m_ptr.into()),
                (VarId::new(900203), k_ptr.into()),
            ],
        )?;
        Ok(Some(closure_ptr.into()))
    }

    /// ExceptT bind with pre-evaluated LHS - uses standard implementation
    fn lower_except_t_bind_with_value(
        &mut self,
        lhs_val: BasicValueEnum<'ctx>,
        rhs_expr: &Expr,
    ) -> CodegenResult<Option<BasicValueEnum<'ctx>>> {
        // For ExceptT, we can use the standard bind since the lifted value is already wrapped
        let rhs_val = self.lower_expr(rhs_expr)?.ok_or_else(|| {
            CodegenError::Internal("except_t_bind_val: RHS has no value".to_string())
        })?;
        self.lower_except_t_bind_impl(lhs_val, rhs_val)
    }

    /// WriterT bind with pre-evaluated LHS - uses standard implementation
    fn lower_writer_t_bind_with_value(
        &mut self,
        lhs_val: BasicValueEnum<'ctx>,
        rhs_expr: &Expr,
    ) -> CodegenResult<Option<BasicValueEnum<'ctx>>> {
        let rhs_val = self.lower_expr(rhs_expr)?.ok_or_else(|| {
            CodegenError::Internal("writer_t_bind_val: RHS has no value".to_string())
        })?;
        self.lower_writer_t_bind_impl(lhs_val, rhs_val)
    }

    /// IO bind with pre-evaluated LHS
    fn lower_io_bind_with_value(
        &mut self,
        lhs_val: BasicValueEnum<'ctx>,
        rhs_expr: &Expr,
    ) -> CodegenResult<Option<BasicValueEnum<'ctx>>> {
        // For IO, we just run the LHS and pass its result to RHS
        let rhs_val = self
            .lower_expr(rhs_expr)?
            .ok_or_else(|| CodegenError::Internal("io_bind_val: RHS has no value".to_string()))?;

        // Call RHS with LHS result
        let rhs_ptr = rhs_val.into_pointer_value();
        let fn_ptr = self.extract_closure_fn_ptr(rhs_ptr)?;
        let ptr_type = self.type_mapper().ptr_type();
        let fn_type = ptr_type.fn_type(&[ptr_type.into(), ptr_type.into()], false);
        let lhs_ptr = self.value_to_ptr(lhs_val)?;
        let result = self
            .builder()
            .build_indirect_call(
                fn_type,
                fn_ptr,
                &[rhs_ptr.into(), lhs_ptr.into()],
                "io_bind_val",
            )
            .map_err(|e| CodegenError::Internal(format!("io_bind_val: {:?}", e)))?
            .try_as_basic_value()
            .basic()
            .ok_or_else(|| CodegenError::Internal("io_bind_val: void".to_string()))?;
        Ok(Some(result))
    }

    /// StateT then with pre-evaluated LHS
    fn lower_state_t_then_with_value(
        &mut self,
        lhs_val: BasicValueEnum<'ctx>,
        rhs_expr: &Expr,
    ) -> CodegenResult<Option<BasicValueEnum<'ctx>>> {
        // For then, we ignore the result of LHS and just run RHS
        let rhs_val = self.lower_expr(rhs_expr)?.ok_or_else(|| {
            CodegenError::Internal("state_t_then_val: RHS has no value".to_string())
        })?;

        let ptr_type = self.type_mapper().ptr_type();
        let fn_name = "bhc_state_t_then_val";

        let func = self.get_or_create_transformer_fn(fn_name);
        if func.count_basic_blocks() == 0 {
            let entry = self.llvm_ctx.append_basic_block(func, "entry");
            let saved_bb = self.builder().get_insert_block();
            self.builder().position_at_end(entry);

            // \(env, s) -> let (_, s') = m(s) in n(s')
            let env = func.get_nth_param(0).unwrap().into_pointer_value();
            let s = func.get_nth_param(1).unwrap();
            let m = self.extract_closure_env_elem(env, 2, 0)?;
            let n = self.extract_closure_env_elem(env, 2, 1)?;

            let fn_type = ptr_type.fn_type(&[ptr_type.into(), ptr_type.into()], false);

            // Run m(s) to get (_, s')
            let m_fn = self.extract_closure_fn_ptr(m)?;
            let s_ptr = self.value_to_ptr(s)?;
            let pair = self
                .builder()
                .build_indirect_call(fn_type, m_fn, &[m.into(), s_ptr.into()], "state_then_m")
                .map_err(|e| CodegenError::Internal(format!("state_t_then m: {:?}", e)))?
                .try_as_basic_value()
                .basic()
                .ok_or_else(|| CodegenError::Internal("state_t_then m: void".to_string()))?
                .into_pointer_value();

            let s_prime = self.extract_pair_snd(pair)?;

            // Run n(s')
            let n_fn = self.extract_closure_fn_ptr(n)?;
            let s_prime_ptr = self.value_to_ptr(s_prime.into())?;
            let result = self
                .builder()
                .build_indirect_call(
                    fn_type,
                    n_fn,
                    &[n.into(), s_prime_ptr.into()],
                    "state_then_n",
                )
                .map_err(|e| CodegenError::Internal(format!("state_t_then n: {:?}", e)))?
                .try_as_basic_value()
                .basic()
                .ok_or_else(|| CodegenError::Internal("state_t_then n: void".to_string()))?;

            self.builder()
                .build_return(Some(&result))
                .map_err(|e| CodegenError::Internal(format!("state_t_then return: {:?}", e)))?;
            if let Some(bb) = saved_bb {
                self.builder().position_at_end(bb);
            }
        }

        let fn_ptr = func.as_global_value().as_pointer_value();
        let m_ptr = self.value_to_ptr(lhs_val)?;
        let n_ptr = self.value_to_ptr(rhs_val)?;
        let closure_ptr = self.alloc_closure(
            fn_ptr,
            &[
                (VarId::new(900210), m_ptr.into()),
                (VarId::new(900211), n_ptr.into()),
            ],
        )?;
        Ok(Some(closure_ptr.into()))
    }

    /// ReaderT then with pre-evaluated LHS
    fn lower_reader_t_then_with_value(
        &mut self,
        lhs_val: BasicValueEnum<'ctx>,
        rhs_expr: &Expr,
    ) -> CodegenResult<Option<BasicValueEnum<'ctx>>> {
        let rhs_val = self.lower_expr(rhs_expr)?.ok_or_else(|| {
            CodegenError::Internal("reader_t_then_val: RHS has no value".to_string())
        })?;

        let ptr_type = self.type_mapper().ptr_type();
        let fn_name = "bhc_reader_t_then_val";

        let func = self.get_or_create_transformer_fn(fn_name);
        if func.count_basic_blocks() == 0 {
            let entry = self.llvm_ctx.append_basic_block(func, "entry");
            let saved_bb = self.builder().get_insert_block();
            self.builder().position_at_end(entry);

            // \(env, r) -> let _ = m(r) in n(r)
            let env = func.get_nth_param(0).unwrap().into_pointer_value();
            let r = func.get_nth_param(1).unwrap();
            let m = self.extract_closure_env_elem(env, 2, 0)?;
            let n = self.extract_closure_env_elem(env, 2, 1)?;

            let fn_type = ptr_type.fn_type(&[ptr_type.into(), ptr_type.into()], false);

            // Run m(r) and ignore result
            let m_fn = self.extract_closure_fn_ptr(m)?;
            let r_ptr = self.value_to_ptr(r)?;
            let _m_result = self
                .builder()
                .build_indirect_call(fn_type, m_fn, &[m.into(), r_ptr.into()], "reader_then_m")
                .map_err(|e| CodegenError::Internal(format!("reader_t_then m: {:?}", e)))?;

            // Run n(r)
            let n_fn = self.extract_closure_fn_ptr(n)?;
            let result = self
                .builder()
                .build_indirect_call(fn_type, n_fn, &[n.into(), r_ptr.into()], "reader_then_n")
                .map_err(|e| CodegenError::Internal(format!("reader_t_then n: {:?}", e)))?
                .try_as_basic_value()
                .basic()
                .ok_or_else(|| CodegenError::Internal("reader_t_then n: void".to_string()))?;

            self.builder()
                .build_return(Some(&result))
                .map_err(|e| CodegenError::Internal(format!("reader_t_then return: {:?}", e)))?;
            if let Some(bb) = saved_bb {
                self.builder().position_at_end(bb);
            }
        }

        let fn_ptr = func.as_global_value().as_pointer_value();
        let m_ptr = self.value_to_ptr(lhs_val)?;
        let n_ptr = self.value_to_ptr(rhs_val)?;
        let closure_ptr = self.alloc_closure(
            fn_ptr,
            &[
                (VarId::new(900212), m_ptr.into()),
                (VarId::new(900213), n_ptr.into()),
            ],
        )?;
        Ok(Some(closure_ptr.into()))
    }

    /// ExceptT then with pre-evaluated LHS - uses standard implementation
    fn lower_except_t_then_with_value(
        &mut self,
        lhs_val: BasicValueEnum<'ctx>,
        rhs_expr: &Expr,
    ) -> CodegenResult<Option<BasicValueEnum<'ctx>>> {
        let rhs_val = self.lower_expr(rhs_expr)?.ok_or_else(|| {
            CodegenError::Internal("except_t_then_val: RHS has no value".to_string())
        })?;
        self.lower_except_t_then_impl(lhs_val, rhs_val)
    }

    /// WriterT then with pre-evaluated LHS - uses standard implementation
    fn lower_writer_t_then_with_value(
        &mut self,
        lhs_val: BasicValueEnum<'ctx>,
        rhs_expr: &Expr,
    ) -> CodegenResult<Option<BasicValueEnum<'ctx>>> {
        let rhs_val = self.lower_expr(rhs_expr)?.ok_or_else(|| {
            CodegenError::Internal("writer_t_then_val: RHS has no value".to_string())
        })?;
        self.lower_writer_t_then_impl(lhs_val, rhs_val)
    }

    /// IO then with pre-evaluated LHS
    fn lower_io_then_with_value(
        &mut self,
        _lhs_val: BasicValueEnum<'ctx>,
        rhs_expr: &Expr,
    ) -> CodegenResult<Option<BasicValueEnum<'ctx>>> {
        // For IO then, LHS is already evaluated (side effect happened)
        // Just evaluate and return RHS
        self.lower_expr(rhs_expr)
    }

    /// Mangle a function name with the module name for multi-module compilation.
    ///
    /// In multi-module mode, function names are qualified as `Module.name` to avoid
    /// collisions. The entry point wrapper (C `main`) is created separately by the
    /// driver, so the Haskell `main` gets mangled to `Main.main` like any other function.
    fn mangle_name(&self, name: &str) -> String {
        if let Some(ref mod_name) = self.module_name {
            format!("{}.{}", mod_name, name)
        } else {
            name.to_string()
        }
    }

    /// Declare external symbols from already-compiled modules.
    ///
    /// For each imported symbol, we add a function declaration (extern) to the
    /// current LLVM module so that references to cross-module functions can be
    /// resolved at link time.
    fn declare_external_symbols(&mut self, symbols: &[CompiledSymbol]) -> CodegenResult<()> {
        let tm = self.type_mapper();
        let ptr_type = tm.ptr_type();
        for sym in symbols {
            // All BHC functions use uniform calling convention with an env/closure
            // pointer as the first parameter. param_count from CompiledSymbol counts
            // only the Haskell-level params (lambdas), so we add 1 for the env pointer.
            let total_params = sym.param_count + 1;
            let param_types: Vec<inkwell::types::BasicMetadataTypeEnum> =
                (0..total_params).map(|_| ptr_type.into()).collect();
            let fn_type = ptr_type.fn_type(&param_types, false);
            let fn_val = self.module.add_function(&sym.llvm_name, fn_type);
            self.external_functions.insert(sym.name, fn_val);
        }
        Ok(())
    }

    /// Register a constructor's metadata for later use.
    /// Preserves existing type_name if already set (e.g., by detect_derived_instance_methods).
    pub fn register_constructor(&mut self, name: &str, tag: u32, arity: u32) {
        let key = name.to_string();
        if let Some(existing) = self.constructor_metadata.get_mut(&key) {
            existing.tag = tag;
            existing.arity = arity;
            // Preserve type_name and is_newtype if already set
        } else {
            self.constructor_metadata.insert(
                key,
                ConstructorMeta {
                    tag,
                    arity,
                    type_name: None,
                    is_newtype: false,
                },
            );
        }
    }

    /// Check if a constructor is a newtype constructor (identity at runtime).
    pub fn is_newtype_constructor(&self, name: &str) -> bool {
        self.constructor_metadata
            .get(name)
            .is_some_and(|meta| meta.is_newtype)
    }

    /// Declare external RTS functions.
    fn declare_rts_functions(&mut self) {
        // Get all types upfront to avoid borrow conflicts
        let void_type = self.llvm_ctx.void_type();
        let i64_type = self.type_mapper().i64_type();
        let f64_type = self.type_mapper().f64_type();
        let i32_type = self.type_mapper().i32_type();
        let i8_ptr_type = self.llvm_ctx.ptr_type(inkwell::AddressSpace::default());

        // bhc_print_int_ln(i64) -> void
        let print_int_ln_type = void_type.fn_type(&[i64_type.into()], false);
        let print_int_ln =
            self.module
                .llvm_module()
                .add_function("bhc_print_int_ln", print_int_ln_type, None);
        self.functions.insert(VarId::new(1000000), print_int_ln); // Base 1_000_000 to avoid collisions with user DefIds

        // bhc_print_double_ln(f64) -> void
        let print_double_ln_type = void_type.fn_type(&[f64_type.into()], false);
        let print_double_ln = self.module.llvm_module().add_function(
            "bhc_print_double_ln",
            print_double_ln_type,
            None,
        );
        self.functions.insert(VarId::new(1000001), print_double_ln);

        // bhc_print_string_ln(*i8) -> void
        let print_string_ln_type = void_type.fn_type(&[i8_ptr_type.into()], false);
        let print_string_ln = self.module.llvm_module().add_function(
            "bhc_print_string_ln",
            print_string_ln_type,
            None,
        );
        self.functions.insert(VarId::new(1000002), print_string_ln);

        // bhc_print_int(i64) -> void
        let print_int_type = void_type.fn_type(&[i64_type.into()], false);
        let print_int =
            self.module
                .llvm_module()
                .add_function("bhc_print_int", print_int_type, None);
        self.functions.insert(VarId::new(1000003), print_int);

        // bhc_print_string(*i8) -> void
        let print_string_type = void_type.fn_type(&[i8_ptr_type.into()], false);
        let print_string =
            self.module
                .llvm_module()
                .add_function("bhc_print_string", print_string_type, None);
        self.functions.insert(VarId::new(1000004), print_string);

        // bhc_alloc(size: i64) -> ptr - allocate heap memory
        let alloc_type = i8_ptr_type.fn_type(&[i64_type.into()], false);
        let alloc_fn = self
            .module
            .llvm_module()
            .add_function("bhc_alloc", alloc_type, None);
        self.functions.insert(VarId::new(1000005), alloc_fn);

        // bhc_error(*i8) -> void - runtime error (does not return)
        let error_type = void_type.fn_type(&[i8_ptr_type.into()], false);
        let error_fn = self
            .module
            .llvm_module()
            .add_function("bhc_error", error_type, None);
        self.functions.insert(VarId::new(1000006), error_fn);

        // bhc_print_bool(i64) -> void - print True/False
        let print_bool_type = void_type.fn_type(&[i64_type.into()], false);
        let print_bool =
            self.module
                .llvm_module()
                .add_function("bhc_print_bool", print_bool_type, None);
        self.functions.insert(VarId::new(1000007), print_bool);

        // bhc_print_bool_ln(i64) -> void - print True/False with newline
        let print_bool_ln_type = void_type.fn_type(&[i64_type.into()], false);
        let print_bool_ln =
            self.module
                .llvm_module()
                .add_function("bhc_print_bool_ln", print_bool_ln_type, None);
        self.functions.insert(VarId::new(1000008), print_bool_ln);

        // bhc_print_char(i32) -> void - print a character
        let print_char_type = void_type.fn_type(&[i32_type.into()], false);
        let print_char =
            self.module
                .llvm_module()
                .add_function("bhc_print_char", print_char_type, None);
        self.functions.insert(VarId::new(1000009), print_char);

        // bhc_print_newline() -> void
        let print_newline_type = void_type.fn_type(&[], false);
        let print_newline =
            self.module
                .llvm_module()
                .add_function("bhc_print_newline", print_newline_type, None);
        self.functions.insert(VarId::new(1000010), print_newline);

        // bhc_force(ptr) -> ptr - Force thunk evaluation to WHNF
        let force_type = i8_ptr_type.fn_type(&[i8_ptr_type.into()], false);
        let force_fn = self
            .module
            .llvm_module()
            .add_function("bhc_force", force_type, None);
        self.functions.insert(VarId::new(1000011), force_fn);

        // bhc_is_thunk(ptr) -> i32 - Check if object is an unevaluated thunk
        let is_thunk_type = i32_type.fn_type(&[i8_ptr_type.into()], false);
        let is_thunk_fn =
            self.module
                .llvm_module()
                .add_function("bhc_is_thunk", is_thunk_type, None);
        self.functions.insert(VarId::new(1000012), is_thunk_fn);

        // === Math RTS functions ===
        let f64_to_f64 = f64_type.fn_type(&[f64_type.into()], false);
        let f32_type = self.llvm_ctx.f32_type();
        let f64_to_i64 = i64_type.fn_type(&[f64_type.into()], false);
        let f64_f64_to_f64 = f64_type.fn_type(&[f64_type.into(), f64_type.into()], false);
        let i64_to_i64 = i64_type.fn_type(&[i64_type.into()], false);

        // bhc_sqrt_double(f64) -> f64
        let sqrt_double =
            self.module
                .llvm_module()
                .add_function("bhc_sqrt_double", f64_to_f64, None);
        self.functions.insert(VarId::new(1000013), sqrt_double);

        // bhc_exp_double(f64) -> f64
        let exp_double = self
            .module
            .llvm_module()
            .add_function("bhc_exp_double", f64_to_f64, None);
        self.functions.insert(VarId::new(1000014), exp_double);

        // bhc_log_double(f64) -> f64
        let log_double = self
            .module
            .llvm_module()
            .add_function("bhc_log_double", f64_to_f64, None);
        self.functions.insert(VarId::new(1000015), log_double);

        // bhc_sin_double(f64) -> f64
        let sin_double = self
            .module
            .llvm_module()
            .add_function("bhc_sin_double", f64_to_f64, None);
        self.functions.insert(VarId::new(1000016), sin_double);

        // bhc_cos_double(f64) -> f64
        let cos_double = self
            .module
            .llvm_module()
            .add_function("bhc_cos_double", f64_to_f64, None);
        self.functions.insert(VarId::new(1000017), cos_double);

        // bhc_tan_double(f64) -> f64
        let tan_double = self
            .module
            .llvm_module()
            .add_function("bhc_tan_double", f64_to_f64, None);
        self.functions.insert(VarId::new(1000018), tan_double);

        // bhc_asin_double(f64) -> f64
        let asin_double =
            self.module
                .llvm_module()
                .add_function("bhc_asin_double", f64_to_f64, None);
        self.functions.insert(VarId::new(1000019), asin_double);

        // bhc_acos_double(f64) -> f64
        let acos_double =
            self.module
                .llvm_module()
                .add_function("bhc_acos_double", f64_to_f64, None);
        self.functions.insert(VarId::new(1000020), acos_double);

        // bhc_atan_double(f64) -> f64
        let atan_double =
            self.module
                .llvm_module()
                .add_function("bhc_atan_double", f64_to_f64, None);
        self.functions.insert(VarId::new(1000021), atan_double);

        // bhc_atan2_double(f64, f64) -> f64
        let atan2_double =
            self.module
                .llvm_module()
                .add_function("bhc_atan2_double", f64_f64_to_f64, None);
        self.functions.insert(VarId::new(1000022), atan2_double);

        // bhc_ceiling_double(f64) -> i64
        let ceiling_double =
            self.module
                .llvm_module()
                .add_function("bhc_ceiling_double", f64_to_i64, None);
        self.functions.insert(VarId::new(1000023), ceiling_double);

        // bhc_floor_double(f64) -> i64
        let floor_double =
            self.module
                .llvm_module()
                .add_function("bhc_floor_double", f64_to_i64, None);
        self.functions.insert(VarId::new(1000024), floor_double);

        // bhc_round_double(f64) -> i64
        let round_double =
            self.module
                .llvm_module()
                .add_function("bhc_round_double", f64_to_i64, None);
        self.functions.insert(VarId::new(1000025), round_double);

        // bhc_truncate_double(f64) -> i64
        let truncate_double =
            self.module
                .llvm_module()
                .add_function("bhc_truncate_double", f64_to_i64, None);
        self.functions.insert(VarId::new(1000026), truncate_double);

        // bhc_negate_int(i64) -> i64
        let negate_int = self
            .module
            .llvm_module()
            .add_function("bhc_negate_int", i64_to_i64, None);
        self.functions.insert(VarId::new(1000027), negate_int);

        // bhc_abs_int(i64) -> i64
        let abs_int = self
            .module
            .llvm_module()
            .add_function("bhc_abs_int", i64_to_i64, None);
        self.functions.insert(VarId::new(1000028), abs_int);

        // bhc_signum_int(i64) -> i64
        let signum_int = self
            .module
            .llvm_module()
            .add_function("bhc_signum_int", i64_to_i64, None);
        self.functions.insert(VarId::new(1000029), signum_int);

        // === Character RTS functions ===
        let u32_type = self.llvm_ctx.i32_type();
        let u32_to_bool = self.llvm_ctx.bool_type().fn_type(&[u32_type.into()], false);
        let u32_to_u32 = u32_type.fn_type(&[u32_type.into()], false);
        let u32_to_i32 = i32_type.fn_type(&[u32_type.into()], false);
        let i32_to_u32 = u32_type.fn_type(&[i32_type.into()], false);

        // bhc_char_is_alpha(u32) -> bool
        let char_is_alpha =
            self.module
                .llvm_module()
                .add_function("bhc_char_is_alpha", u32_to_bool, None);
        self.functions.insert(VarId::new(1000030), char_is_alpha);

        // bhc_char_is_digit(u32) -> bool
        let char_is_digit =
            self.module
                .llvm_module()
                .add_function("bhc_char_is_digit", u32_to_bool, None);
        self.functions.insert(VarId::new(1000031), char_is_digit);

        // bhc_char_is_alphanumeric(u32) -> bool
        let char_is_alnum =
            self.module
                .llvm_module()
                .add_function("bhc_char_is_alphanumeric", u32_to_bool, None);
        self.functions.insert(VarId::new(1000032), char_is_alnum);

        // bhc_char_is_space(u32) -> bool
        let char_is_space =
            self.module
                .llvm_module()
                .add_function("bhc_char_is_space", u32_to_bool, None);
        self.functions.insert(VarId::new(1000033), char_is_space);

        // bhc_char_is_upper(u32) -> bool
        let char_is_upper =
            self.module
                .llvm_module()
                .add_function("bhc_char_is_upper", u32_to_bool, None);
        self.functions.insert(VarId::new(1000034), char_is_upper);

        // bhc_char_is_lower(u32) -> bool
        let char_is_lower =
            self.module
                .llvm_module()
                .add_function("bhc_char_is_lower", u32_to_bool, None);
        self.functions.insert(VarId::new(1000035), char_is_lower);

        // bhc_char_to_upper(u32) -> u32
        let char_to_upper =
            self.module
                .llvm_module()
                .add_function("bhc_char_to_upper", u32_to_u32, None);
        self.functions.insert(VarId::new(1000036), char_to_upper);

        // bhc_char_to_lower(u32) -> u32
        let char_to_lower =
            self.module
                .llvm_module()
                .add_function("bhc_char_to_lower", u32_to_u32, None);
        self.functions.insert(VarId::new(1000037), char_to_lower);

        // bhc_char_is_print(u32) -> bool
        let char_is_print =
            self.module
                .llvm_module()
                .add_function("bhc_char_is_print", u32_to_bool, None);
        self.functions.insert(VarId::new(1000038), char_is_print);

        // bhc_char_is_ascii(u32) -> bool
        let char_is_ascii =
            self.module
                .llvm_module()
                .add_function("bhc_char_is_ascii", u32_to_bool, None);
        self.functions.insert(VarId::new(1000039), char_is_ascii);

        // bhc_char_is_control(u32) -> bool
        let char_is_control =
            self.module
                .llvm_module()
                .add_function("bhc_char_is_control", u32_to_bool, None);
        self.functions.insert(VarId::new(1000040), char_is_control);

        // bhc_char_is_hex_digit(u32) -> bool
        let char_is_hex =
            self.module
                .llvm_module()
                .add_function("bhc_char_is_hex_digit", u32_to_bool, None);
        self.functions.insert(VarId::new(1000041), char_is_hex);

        // bhc_char_is_letter(u32) -> bool
        let char_is_letter =
            self.module
                .llvm_module()
                .add_function("bhc_char_is_letter", u32_to_bool, None);
        self.functions.insert(VarId::new(1000042), char_is_letter);

        // bhc_char_is_number(u32) -> bool
        let char_is_number =
            self.module
                .llvm_module()
                .add_function("bhc_char_is_number", u32_to_bool, None);
        self.functions.insert(VarId::new(1000043), char_is_number);

        // bhc_char_is_punctuation(u32) -> bool
        let char_is_punct =
            self.module
                .llvm_module()
                .add_function("bhc_char_is_punctuation", u32_to_bool, None);
        self.functions.insert(VarId::new(1000044), char_is_punct);

        // bhc_char_is_symbol(u32) -> bool
        let char_is_symbol =
            self.module
                .llvm_module()
                .add_function("bhc_char_is_symbol", u32_to_bool, None);
        self.functions.insert(VarId::new(1000045), char_is_symbol);

        // bhc_char_digit_to_int(u32) -> i32
        let char_digit_to_int =
            self.module
                .llvm_module()
                .add_function("bhc_char_digit_to_int", u32_to_i32, None);
        self.functions
            .insert(VarId::new(1000046), char_digit_to_int);

        // bhc_char_int_to_digit(i32) -> u32
        let char_int_to_digit =
            self.module
                .llvm_module()
                .add_function("bhc_char_int_to_digit", i32_to_u32, None);
        self.functions
            .insert(VarId::new(1000047), char_int_to_digit);

        // bhc_char_ord(u32) -> u32
        let char_ord = self
            .module
            .llvm_module()
            .add_function("bhc_char_ord", u32_to_u32, None);
        self.functions.insert(VarId::new(1000048), char_ord);

        // bhc_char_chr(u32) -> u32
        let char_chr = self
            .module
            .llvm_module()
            .add_function("bhc_char_chr", u32_to_u32, None);
        self.functions.insert(VarId::new(1000049), char_chr);

        // === IO/System RTS functions ===
        // bhc_readFile(*i8) -> *i8
        let ptr_to_ptr = i8_ptr_type.fn_type(&[i8_ptr_type.into()], false);
        let read_file = self
            .module
            .llvm_module()
            .add_function("bhc_readFile", ptr_to_ptr, None);
        self.functions.insert(VarId::new(1000050), read_file);

        // bhc_writeFile(*i8, *i8) -> void
        let ptr_ptr_to_void = void_type.fn_type(&[i8_ptr_type.into(), i8_ptr_type.into()], false);
        let write_file =
            self.module
                .llvm_module()
                .add_function("bhc_writeFile", ptr_ptr_to_void, None);
        self.functions.insert(VarId::new(1000051), write_file);

        // bhc_appendFile(*i8, *i8) -> void
        let append_file =
            self.module
                .llvm_module()
                .add_function("bhc_appendFile", ptr_ptr_to_void, None);
        self.functions.insert(VarId::new(1000052), append_file);

        // bhc_open_file(*i8, i32) -> *i8 (handle)
        let ptr_i32_to_ptr = i8_ptr_type.fn_type(&[i8_ptr_type.into(), i32_type.into()], false);
        let open_file =
            self.module
                .llvm_module()
                .add_function("bhc_open_file", ptr_i32_to_ptr, None);
        self.functions.insert(VarId::new(1000053), open_file);

        // bhc_close_handle(*i8) -> void
        let ptr_to_void = void_type.fn_type(&[i8_ptr_type.into()], false);
        let close_handle =
            self.module
                .llvm_module()
                .add_function("bhc_close_handle", ptr_to_void, None);
        self.functions.insert(VarId::new(1000054), close_handle);

        // bhc_hGetChar(*i8) -> i32
        let ptr_to_i32 = i32_type.fn_type(&[i8_ptr_type.into()], false);
        let h_get_char = self
            .module
            .llvm_module()
            .add_function("bhc_hGetChar", ptr_to_i32, None);
        self.functions.insert(VarId::new(1000055), h_get_char);

        // bhc_hGetLine(*i8) -> *i8
        let h_get_line = self
            .module
            .llvm_module()
            .add_function("bhc_hGetLine", ptr_to_ptr, None);
        self.functions.insert(VarId::new(1000056), h_get_line);

        // bhc_hPutStr(*i8, *i8) -> void
        let h_put_str =
            self.module
                .llvm_module()
                .add_function("bhc_hPutStr", ptr_ptr_to_void, None);
        self.functions.insert(VarId::new(1000057), h_put_str);

        // bhc_hFlush(*i8) -> void
        let h_flush = self
            .module
            .llvm_module()
            .add_function("bhc_hFlush", ptr_to_void, None);
        self.functions.insert(VarId::new(1000058), h_flush);

        // bhc_hIsEOF(*i8) -> i32 (bool)
        let h_is_eof = self
            .module
            .llvm_module()
            .add_function("bhc_hIsEOF", ptr_to_i32, None);
        self.functions.insert(VarId::new(1000059), h_is_eof);

        // bhc_stdin() -> *i8
        let void_to_ptr = i8_ptr_type.fn_type(&[], false);
        let stdin_fn = self
            .module
            .llvm_module()
            .add_function("bhc_stdin", void_to_ptr, None);
        self.functions.insert(VarId::new(1000060), stdin_fn);

        // bhc_stdout() -> *i8
        let stdout_fn = self
            .module
            .llvm_module()
            .add_function("bhc_stdout", void_to_ptr, None);
        self.functions.insert(VarId::new(1000061), stdout_fn);

        // bhc_stderr() -> *i8
        let stderr_fn = self
            .module
            .llvm_module()
            .add_function("bhc_stderr", void_to_ptr, None);
        self.functions.insert(VarId::new(1000062), stderr_fn);

        // bhc_exists(*i8) -> i32 (bool) - doesFileExist
        let does_file_exist =
            self.module
                .llvm_module()
                .add_function("bhc_exists", ptr_to_i32, None);
        self.functions.insert(VarId::new(1000063), does_file_exist);

        // bhc_is_directory(*i8) -> i32 (bool) - doesDirectoryExist
        let does_dir_exist =
            self.module
                .llvm_module()
                .add_function("bhc_is_directory", ptr_to_i32, None);
        self.functions.insert(VarId::new(1000064), does_dir_exist);

        // bhc_exit(i32) -> void
        let i32_to_void = void_type.fn_type(&[i32_type.into()], false);
        let exit_fn = self
            .module
            .llvm_module()
            .add_function("bhc_exit", i32_to_void, None);
        self.functions.insert(VarId::new(1000065), exit_fn);

        // bhc_exit_success() -> void
        let void_to_void = void_type.fn_type(&[], false);
        let exit_success =
            self.module
                .llvm_module()
                .add_function("bhc_exit_success", void_to_void, None);
        self.functions.insert(VarId::new(1000066), exit_success);

        // bhc_exit_failure() -> void
        let exit_failure =
            self.module
                .llvm_module()
                .add_function("bhc_exit_failure", void_to_void, None);
        self.functions.insert(VarId::new(1000067), exit_failure);

        // bhc_get_env(*i8) -> *i8
        let get_env = self
            .module
            .llvm_module()
            .add_function("bhc_get_env", ptr_to_ptr, None);
        self.functions.insert(VarId::new(1000068), get_env);

        // bhc_argc() -> i32
        let void_to_i32 = i32_type.fn_type(&[], false);
        let argc_fn = self
            .module
            .llvm_module()
            .add_function("bhc_argc", void_to_i32, None);
        self.functions.insert(VarId::new(1000069), argc_fn);

        // bhc_argv() -> *i8 (returns ptr to argv array)
        let argv_fn = self
            .module
            .llvm_module()
            .add_function("bhc_argv", void_to_ptr, None);
        self.functions.insert(VarId::new(1000070), argv_fn);

        // bhc_getLine() -> *i8
        let get_line = self
            .module
            .llvm_module()
            .add_function("bhc_getLine", void_to_ptr, None);
        self.functions.insert(VarId::new(1000071), get_line);

        // bhc_show_int(i64) -> *i8
        let i64_to_ptr = i8_ptr_type.fn_type(&[i64_type.into()], false);
        let show_int = self
            .module
            .llvm_module()
            .add_function("bhc_show_int", i64_to_ptr, None);
        self.functions.insert(VarId::new(1000072), show_int);

        // bhc_show_double(f64) -> *i8
        let f64_to_ptr = i8_ptr_type.fn_type(&[f64_type.into()], false);
        let show_double =
            self.module
                .llvm_module()
                .add_function("bhc_show_double", f64_to_ptr, None);
        self.functions.insert(VarId::new(1000073), show_double);

        // bhc_show_bool(i64) -> *i8
        let show_bool = self
            .module
            .llvm_module()
            .add_function("bhc_show_bool", i64_to_ptr, None);
        self.functions.insert(VarId::new(1000091), show_bool);

        // bhc_show_ordering(i64) -> *i8
        let show_ordering =
            self.module
                .llvm_module()
                .add_function("bhc_show_ordering", i64_to_ptr, None);
        self.functions.insert(VarId::new(1000098), show_ordering);

        // bhc_show_char(i32) -> *i8
        let i32_to_ptr = i8_ptr_type.fn_type(&[i32_type.into()], false);
        let show_char = self
            .module
            .llvm_module()
            .add_function("bhc_show_char", i32_to_ptr, None);
        self.functions.insert(VarId::new(1000074), show_char);

        // bhc_show_float(f32) -> *i8
        let f32_to_ptr = i8_ptr_type.fn_type(&[f32_type.into()], false);
        let show_float = self
            .module
            .llvm_module()
            .add_function("bhc_show_float", f32_to_ptr, None);
        self.functions.insert(VarId::new(1000090), show_float);

        // bhc_show_string(ptr) -> *i8
        let ptr_to_ptr = i8_ptr_type.fn_type(&[i8_ptr_type.into()], false);
        let show_string =
            self.module
                .llvm_module()
                .add_function("bhc_show_string", ptr_to_ptr, None);
        self.functions.insert(VarId::new(1000092), show_string);

        // bhc_show_list(ptr, i64) -> *i8
        let ptr_i64_to_ptr = i8_ptr_type.fn_type(&[i8_ptr_type.into(), i64_type.into()], false);
        let show_list =
            self.module
                .llvm_module()
                .add_function("bhc_show_list", ptr_i64_to_ptr, None);
        self.functions.insert(VarId::new(1000093), show_list);

        // bhc_show_maybe(ptr, i64) -> *i8
        let show_maybe =
            self.module
                .llvm_module()
                .add_function("bhc_show_maybe", ptr_i64_to_ptr, None);
        self.functions.insert(VarId::new(1000094), show_maybe);

        // bhc_show_either(ptr, i64, i64) -> *i8
        let ptr_i64_i64_to_ptr = i8_ptr_type.fn_type(
            &[i8_ptr_type.into(), i64_type.into(), i64_type.into()],
            false,
        );
        let show_either =
            self.module
                .llvm_module()
                .add_function("bhc_show_either", ptr_i64_i64_to_ptr, None);
        self.functions.insert(VarId::new(1000095), show_either);

        // bhc_show_tuple2(ptr, i64, i64) -> *i8
        let show_tuple2 =
            self.module
                .llvm_module()
                .add_function("bhc_show_tuple2", ptr_i64_i64_to_ptr, None);
        self.functions.insert(VarId::new(1000096), show_tuple2);

        // bhc_show_unit(ptr) -> *i8
        let show_unit = self
            .module
            .llvm_module()
            .add_function("bhc_show_unit", ptr_to_ptr, None);
        self.functions.insert(VarId::new(1000097), show_unit);

        // bhc_show_with_desc(ptr, ptr) -> *i8
        let ptr_ptr_to_ptr = i8_ptr_type.fn_type(&[i8_ptr_type.into(), i8_ptr_type.into()], false);
        let show_with_desc =
            self.module
                .llvm_module()
                .add_function("bhc_show_with_desc", ptr_ptr_to_ptr, None);
        self.functions.insert(VarId::new(1000099), show_with_desc);

        // bhc_gcd(i64, i64) -> i64
        let i64_i64_to_i64 = i64_type.fn_type(&[i64_type.into(), i64_type.into()], false);
        let gcd_fn = self
            .module
            .llvm_module()
            .add_function("bhc_gcd", i64_i64_to_i64, None);
        self.functions.insert(VarId::new(1000500), gcd_fn);

        // bhc_lcm(i64, i64) -> i64
        let lcm_fn = self
            .module
            .llvm_module()
            .add_function("bhc_lcm", i64_i64_to_i64, None);
        self.functions.insert(VarId::new(1000501), lcm_fn);

        // bhc_new_ioref(ptr) -> ptr
        let new_ioref = self
            .module
            .llvm_module()
            .add_function("bhc_new_ioref", ptr_to_ptr, None);
        self.functions.insert(VarId::new(1000502), new_ioref);

        // bhc_read_ioref(ptr) -> ptr
        let read_ioref = self
            .module
            .llvm_module()
            .add_function("bhc_read_ioref", ptr_to_ptr, None);
        self.functions.insert(VarId::new(1000503), read_ioref);

        // bhc_write_ioref(ptr, ptr) -> void
        let ptr_ptr_to_void = self
            .llvm_context()
            .void_type()
            .fn_type(&[i8_ptr_type.into(), i8_ptr_type.into()], false);
        let write_ioref =
            self.module
                .llvm_module()
                .add_function("bhc_write_ioref", ptr_ptr_to_void, None);
        self.functions.insert(VarId::new(1000504), write_ioref);

        // bhc_char_to_int(u32) -> i64 (ord)
        let u32_to_i64 = i64_type.fn_type(&[u32_type.into()], false);
        let char_to_int =
            self.module
                .llvm_module()
                .add_function("bhc_char_to_int", u32_to_i64, None);
        self.functions.insert(VarId::new(1000075), char_to_int);

        // bhc_int_to_char(i64) -> u32 (chr)
        let i64_to_u32 = u32_type.fn_type(&[i64_type.into()], false);
        let int_to_char =
            self.module
                .llvm_module()
                .add_function("bhc_int_to_char", i64_to_u32, None);
        self.functions.insert(VarId::new(1000076), int_to_char);

        // bhc_negate_double(f64) -> f64
        let negate_double =
            self.module
                .llvm_module()
                .add_function("bhc_negate_double", f64_to_f64, None);
        self.functions.insert(VarId::new(1000077), negate_double);

        // bhc_abs_double(f64) -> f64
        let abs_double = self
            .module
            .llvm_module()
            .add_function("bhc_abs_double", f64_to_f64, None);
        self.functions.insert(VarId::new(1000078), abs_double);

        // bhc_signum_double(f64) -> f64
        let signum_double =
            self.module
                .llvm_module()
                .add_function("bhc_signum_double", f64_to_f64, None);
        self.functions.insert(VarId::new(1000079), signum_double);

        // Container RTS functions (Map, Set, IntMap, IntSet)
        let ptr_type = i8_ptr_type;

        // Map operations
        let map_empty = self.module.llvm_module().add_function(
            "bhc_map_empty",
            ptr_type.fn_type(&[], false),
            None,
        );
        self.functions.insert(VarId::new(1000100), map_empty);

        let map_singleton = self.module.llvm_module().add_function(
            "bhc_map_singleton",
            ptr_type.fn_type(&[i64_type.into(), ptr_type.into()], false),
            None,
        );
        self.functions.insert(VarId::new(1000101), map_singleton);

        let map_null = self.module.llvm_module().add_function(
            "bhc_map_null",
            i64_type.fn_type(&[ptr_type.into()], false),
            None,
        );
        self.functions.insert(VarId::new(1000102), map_null);

        let map_size = self.module.llvm_module().add_function(
            "bhc_map_size",
            i64_type.fn_type(&[ptr_type.into()], false),
            None,
        );
        self.functions.insert(VarId::new(1000103), map_size);

        let map_member = self.module.llvm_module().add_function(
            "bhc_map_member",
            i64_type.fn_type(&[i64_type.into(), ptr_type.into()], false),
            None,
        );
        self.functions.insert(VarId::new(1000104), map_member);

        let map_lookup = self.module.llvm_module().add_function(
            "bhc_map_lookup",
            ptr_type.fn_type(&[i64_type.into(), ptr_type.into()], false),
            None,
        );
        self.functions.insert(VarId::new(1000105), map_lookup);

        let map_fwd = self.module.llvm_module().add_function(
            "bhc_map_find_with_default",
            ptr_type.fn_type(&[ptr_type.into(), i64_type.into(), ptr_type.into()], false),
            None,
        );
        self.functions.insert(VarId::new(1000106), map_fwd);

        let map_insert = self.module.llvm_module().add_function(
            "bhc_map_insert",
            ptr_type.fn_type(&[i64_type.into(), ptr_type.into(), ptr_type.into()], false),
            None,
        );
        self.functions.insert(VarId::new(1000107), map_insert);

        let map_delete = self.module.llvm_module().add_function(
            "bhc_map_delete",
            ptr_type.fn_type(&[i64_type.into(), ptr_type.into()], false),
            None,
        );
        self.functions.insert(VarId::new(1000108), map_delete);

        let map_union = self.module.llvm_module().add_function(
            "bhc_map_union",
            ptr_type.fn_type(&[ptr_type.into(), ptr_type.into()], false),
            None,
        );
        self.functions.insert(VarId::new(1000109), map_union);

        let map_intersection = self.module.llvm_module().add_function(
            "bhc_map_intersection",
            ptr_type.fn_type(&[ptr_type.into(), ptr_type.into()], false),
            None,
        );
        self.functions.insert(VarId::new(1000110), map_intersection);

        let map_difference = self.module.llvm_module().add_function(
            "bhc_map_difference",
            ptr_type.fn_type(&[ptr_type.into(), ptr_type.into()], false),
            None,
        );
        self.functions.insert(VarId::new(1000111), map_difference);

        let map_is_submap = self.module.llvm_module().add_function(
            "bhc_map_is_submap_of",
            i64_type.fn_type(&[ptr_type.into(), ptr_type.into()], false),
            None,
        );
        self.functions.insert(VarId::new(1000112), map_is_submap);

        // Set operations
        let set_empty = self.module.llvm_module().add_function(
            "bhc_set_empty",
            ptr_type.fn_type(&[], false),
            None,
        );
        self.functions.insert(VarId::new(1000120), set_empty);

        let set_singleton = self.module.llvm_module().add_function(
            "bhc_set_singleton",
            ptr_type.fn_type(&[i64_type.into()], false),
            None,
        );
        self.functions.insert(VarId::new(1000121), set_singleton);

        let set_null = self.module.llvm_module().add_function(
            "bhc_set_null",
            i64_type.fn_type(&[ptr_type.into()], false),
            None,
        );
        self.functions.insert(VarId::new(1000122), set_null);

        let set_size = self.module.llvm_module().add_function(
            "bhc_set_size",
            i64_type.fn_type(&[ptr_type.into()], false),
            None,
        );
        self.functions.insert(VarId::new(1000123), set_size);

        let set_member = self.module.llvm_module().add_function(
            "bhc_set_member",
            i64_type.fn_type(&[i64_type.into(), ptr_type.into()], false),
            None,
        );
        self.functions.insert(VarId::new(1000124), set_member);

        let set_insert = self.module.llvm_module().add_function(
            "bhc_set_insert",
            ptr_type.fn_type(&[i64_type.into(), ptr_type.into()], false),
            None,
        );
        self.functions.insert(VarId::new(1000125), set_insert);

        let set_delete = self.module.llvm_module().add_function(
            "bhc_set_delete",
            ptr_type.fn_type(&[i64_type.into(), ptr_type.into()], false),
            None,
        );
        self.functions.insert(VarId::new(1000126), set_delete);

        let set_union = self.module.llvm_module().add_function(
            "bhc_set_union",
            ptr_type.fn_type(&[ptr_type.into(), ptr_type.into()], false),
            None,
        );
        self.functions.insert(VarId::new(1000127), set_union);

        let set_intersection = self.module.llvm_module().add_function(
            "bhc_set_intersection",
            ptr_type.fn_type(&[ptr_type.into(), ptr_type.into()], false),
            None,
        );
        self.functions.insert(VarId::new(1000128), set_intersection);

        let set_difference = self.module.llvm_module().add_function(
            "bhc_set_difference",
            ptr_type.fn_type(&[ptr_type.into(), ptr_type.into()], false),
            None,
        );
        self.functions.insert(VarId::new(1000129), set_difference);

        let set_is_subset = self.module.llvm_module().add_function(
            "bhc_set_is_subset_of",
            i64_type.fn_type(&[ptr_type.into(), ptr_type.into()], false),
            None,
        );
        self.functions.insert(VarId::new(1000130), set_is_subset);

        let set_is_proper_subset = self.module.llvm_module().add_function(
            "bhc_set_is_proper_subset_of",
            i64_type.fn_type(&[ptr_type.into(), ptr_type.into()], false),
            None,
        );
        self.functions
            .insert(VarId::new(1000131), set_is_proper_subset);

        let set_find_min = self.module.llvm_module().add_function(
            "bhc_set_find_min",
            i64_type.fn_type(&[ptr_type.into()], false),
            None,
        );
        self.functions.insert(VarId::new(1000132), set_find_min);

        let set_find_max = self.module.llvm_module().add_function(
            "bhc_set_find_max",
            i64_type.fn_type(&[ptr_type.into()], false),
            None,
        );
        self.functions.insert(VarId::new(1000133), set_find_max);

        let set_delete_min = self.module.llvm_module().add_function(
            "bhc_set_delete_min",
            ptr_type.fn_type(&[ptr_type.into()], false),
            None,
        );
        self.functions.insert(VarId::new(1000134), set_delete_min);

        let set_delete_max = self.module.llvm_module().add_function(
            "bhc_set_delete_max",
            ptr_type.fn_type(&[ptr_type.into()], false),
            None,
        );
        self.functions.insert(VarId::new(1000135), set_delete_max);

        // IntMap operations
        let intmap_empty = self.module.llvm_module().add_function(
            "bhc_intmap_empty",
            ptr_type.fn_type(&[], false),
            None,
        );
        self.functions.insert(VarId::new(1000140), intmap_empty);

        let intmap_singleton = self.module.llvm_module().add_function(
            "bhc_intmap_singleton",
            ptr_type.fn_type(&[i64_type.into(), ptr_type.into()], false),
            None,
        );
        self.functions.insert(VarId::new(1000141), intmap_singleton);

        let intmap_null = self.module.llvm_module().add_function(
            "bhc_intmap_null",
            i64_type.fn_type(&[ptr_type.into()], false),
            None,
        );
        self.functions.insert(VarId::new(1000142), intmap_null);

        let intmap_insert = self.module.llvm_module().add_function(
            "bhc_intmap_insert",
            ptr_type.fn_type(&[i64_type.into(), ptr_type.into(), ptr_type.into()], false),
            None,
        );
        self.functions.insert(VarId::new(1000143), intmap_insert);

        let intmap_delete = self.module.llvm_module().add_function(
            "bhc_intmap_delete",
            ptr_type.fn_type(&[i64_type.into(), ptr_type.into()], false),
            None,
        );
        self.functions.insert(VarId::new(1000144), intmap_delete);

        let intmap_lookup = self.module.llvm_module().add_function(
            "bhc_intmap_lookup",
            ptr_type.fn_type(&[i64_type.into(), ptr_type.into()], false),
            None,
        );
        self.functions.insert(VarId::new(1000145), intmap_lookup);

        let intmap_union = self.module.llvm_module().add_function(
            "bhc_intmap_union",
            ptr_type.fn_type(&[ptr_type.into(), ptr_type.into()], false),
            None,
        );
        self.functions.insert(VarId::new(1000146), intmap_union);

        // IntSet operations
        let intset_empty = self.module.llvm_module().add_function(
            "bhc_intset_empty",
            ptr_type.fn_type(&[], false),
            None,
        );
        self.functions.insert(VarId::new(1000150), intset_empty);

        let intset_singleton = self.module.llvm_module().add_function(
            "bhc_intset_singleton",
            ptr_type.fn_type(&[i64_type.into()], false),
            None,
        );
        self.functions.insert(VarId::new(1000151), intset_singleton);

        let intset_null = self.module.llvm_module().add_function(
            "bhc_intset_null",
            i64_type.fn_type(&[ptr_type.into()], false),
            None,
        );
        self.functions.insert(VarId::new(1000152), intset_null);

        let intset_insert = self.module.llvm_module().add_function(
            "bhc_intset_insert",
            ptr_type.fn_type(&[i64_type.into(), ptr_type.into()], false),
            None,
        );
        self.functions.insert(VarId::new(1000153), intset_insert);

        let intset_delete = self.module.llvm_module().add_function(
            "bhc_intset_delete",
            ptr_type.fn_type(&[i64_type.into(), ptr_type.into()], false),
            None,
        );
        self.functions.insert(VarId::new(1000154), intset_delete);

        let intset_union = self.module.llvm_module().add_function(
            "bhc_intset_union",
            ptr_type.fn_type(&[ptr_type.into(), ptr_type.into()], false),
            None,
        );
        self.functions.insert(VarId::new(1000155), intset_union);

        let intset_member = self.module.llvm_module().add_function(
            "bhc_intset_member",
            i64_type.fn_type(&[i64_type.into(), ptr_type.into()], false),
            None,
        );
        self.functions.insert(VarId::new(1000156), intset_member);

        let intset_intersection = self.module.llvm_module().add_function(
            "bhc_intset_intersection",
            ptr_type.fn_type(&[ptr_type.into(), ptr_type.into()], false),
            None,
        );
        self.functions
            .insert(VarId::new(1000157), intset_intersection);

        let intset_difference = self.module.llvm_module().add_function(
            "bhc_intset_difference",
            ptr_type.fn_type(&[ptr_type.into(), ptr_type.into()], false),
            None,
        );
        self.functions
            .insert(VarId::new(1000158), intset_difference);

        let intset_is_subset = self.module.llvm_module().add_function(
            "bhc_intset_is_subset_of",
            i64_type.fn_type(&[ptr_type.into(), ptr_type.into()], false),
            None,
        );
        self.functions.insert(VarId::new(1000159), intset_is_subset);

        // Sequence operations (VarId 1000800-1000829)
        let seq_empty = self.module.llvm_module().add_function(
            "bhc_seq_empty",
            ptr_type.fn_type(&[], false),
            None,
        );
        self.functions.insert(VarId::new(1000800), seq_empty);
        let seq_singleton = self.module.llvm_module().add_function(
            "bhc_seq_singleton",
            ptr_type.fn_type(&[ptr_type.into()], false),
            None,
        );
        self.functions.insert(VarId::new(1000801), seq_singleton);
        let seq_null = self.module.llvm_module().add_function(
            "bhc_seq_null",
            i64_type.fn_type(&[ptr_type.into()], false),
            None,
        );
        self.functions.insert(VarId::new(1000802), seq_null);
        let seq_length = self.module.llvm_module().add_function(
            "bhc_seq_length",
            i64_type.fn_type(&[ptr_type.into()], false),
            None,
        );
        self.functions.insert(VarId::new(1000803), seq_length);
        let seq_index = self.module.llvm_module().add_function(
            "bhc_seq_index",
            ptr_type.fn_type(&[ptr_type.into(), i64_type.into()], false),
            None,
        );
        self.functions.insert(VarId::new(1000804), seq_index);
        let seq_lookup = self.module.llvm_module().add_function(
            "bhc_seq_lookup",
            ptr_type.fn_type(&[i64_type.into(), ptr_type.into()], false),
            None,
        );
        self.functions.insert(VarId::new(1000805), seq_lookup);
        let seq_cons = self.module.llvm_module().add_function(
            "bhc_seq_cons",
            ptr_type.fn_type(&[ptr_type.into(), ptr_type.into()], false),
            None,
        );
        self.functions.insert(VarId::new(1000806), seq_cons);
        let seq_snoc = self.module.llvm_module().add_function(
            "bhc_seq_snoc",
            ptr_type.fn_type(&[ptr_type.into(), ptr_type.into()], false),
            None,
        );
        self.functions.insert(VarId::new(1000807), seq_snoc);
        let seq_append = self.module.llvm_module().add_function(
            "bhc_seq_append",
            ptr_type.fn_type(&[ptr_type.into(), ptr_type.into()], false),
            None,
        );
        self.functions.insert(VarId::new(1000808), seq_append);
        let seq_take = self.module.llvm_module().add_function(
            "bhc_seq_take",
            ptr_type.fn_type(&[i64_type.into(), ptr_type.into()], false),
            None,
        );
        self.functions.insert(VarId::new(1000809), seq_take);
        let seq_drop = self.module.llvm_module().add_function(
            "bhc_seq_drop",
            ptr_type.fn_type(&[i64_type.into(), ptr_type.into()], false),
            None,
        );
        self.functions.insert(VarId::new(1000810), seq_drop);
        let seq_reverse = self.module.llvm_module().add_function(
            "bhc_seq_reverse",
            ptr_type.fn_type(&[ptr_type.into()], false),
            None,
        );
        self.functions.insert(VarId::new(1000811), seq_reverse);
        let seq_update = self.module.llvm_module().add_function(
            "bhc_seq_update",
            ptr_type.fn_type(&[i64_type.into(), ptr_type.into(), ptr_type.into()], false),
            None,
        );
        self.functions.insert(VarId::new(1000812), seq_update);
        let seq_insert_at = self.module.llvm_module().add_function(
            "bhc_seq_insert_at",
            ptr_type.fn_type(&[i64_type.into(), ptr_type.into(), ptr_type.into()], false),
            None,
        );
        self.functions.insert(VarId::new(1000813), seq_insert_at);
        let seq_delete_at = self.module.llvm_module().add_function(
            "bhc_seq_delete_at",
            ptr_type.fn_type(&[i64_type.into(), ptr_type.into()], false),
            None,
        );
        self.functions.insert(VarId::new(1000814), seq_delete_at);
        let seq_elem_count = self.module.llvm_module().add_function(
            "bhc_seq_elem_count",
            i64_type.fn_type(&[ptr_type.into()], false),
            None,
        );
        self.functions.insert(VarId::new(1000815), seq_elem_count);
        let seq_elem_at = self.module.llvm_module().add_function(
            "bhc_seq_elem_at",
            ptr_type.fn_type(&[ptr_type.into(), i64_type.into()], false),
            None,
        );
        self.functions.insert(VarId::new(1000816), seq_elem_at);
        let seq_replicate = self.module.llvm_module().add_function(
            "bhc_seq_replicate",
            ptr_type.fn_type(&[i64_type.into(), ptr_type.into()], false),
            None,
        );
        self.functions.insert(VarId::new(1000817), seq_replicate);
        let seq_viewl_tag = self.module.llvm_module().add_function(
            "bhc_seq_viewl_tag",
            i64_type.fn_type(&[ptr_type.into()], false),
            None,
        );
        self.functions.insert(VarId::new(1000818), seq_viewl_tag);
        let seq_viewl_head = self.module.llvm_module().add_function(
            "bhc_seq_viewl_head",
            ptr_type.fn_type(&[ptr_type.into()], false),
            None,
        );
        self.functions.insert(VarId::new(1000819), seq_viewl_head);
        let seq_viewl_tail = self.module.llvm_module().add_function(
            "bhc_seq_viewl_tail",
            ptr_type.fn_type(&[ptr_type.into()], false),
            None,
        );
        self.functions.insert(VarId::new(1000820), seq_viewl_tail);
        let seq_viewr_tag = self.module.llvm_module().add_function(
            "bhc_seq_viewr_tag",
            i64_type.fn_type(&[ptr_type.into()], false),
            None,
        );
        self.functions.insert(VarId::new(1000821), seq_viewr_tag);
        let seq_viewr_last = self.module.llvm_module().add_function(
            "bhc_seq_viewr_last",
            ptr_type.fn_type(&[ptr_type.into()], false),
            None,
        );
        self.functions.insert(VarId::new(1000822), seq_viewr_last);
        let seq_viewr_init = self.module.llvm_module().add_function(
            "bhc_seq_viewr_init",
            ptr_type.fn_type(&[ptr_type.into()], false),
            None,
        );
        self.functions.insert(VarId::new(1000823), seq_viewr_init);

        // Container iteration helpers (for toList/fromList)
        // bhc_map_keys_count(map_ptr) -> i64
        let map_keys_count = self.module.llvm_module().add_function(
            "bhc_map_keys_count",
            i64_type.fn_type(&[ptr_type.into()], false),
            None,
        );
        self.functions.insert(VarId::new(1000160), map_keys_count);
        // bhc_map_key_at(map_ptr, index) -> i64
        let map_key_at = self.module.llvm_module().add_function(
            "bhc_map_key_at",
            i64_type.fn_type(&[ptr_type.into(), i64_type.into()], false),
            None,
        );
        self.functions.insert(VarId::new(1000161), map_key_at);
        // bhc_map_value_at(map_ptr, index) -> ptr
        let map_value_at = self.module.llvm_module().add_function(
            "bhc_map_value_at",
            ptr_type.fn_type(&[ptr_type.into(), i64_type.into()], false),
            None,
        );
        self.functions.insert(VarId::new(1000162), map_value_at);
        // bhc_set_elem_count(set_ptr) -> i64
        let set_elem_count = self.module.llvm_module().add_function(
            "bhc_set_elem_count",
            i64_type.fn_type(&[ptr_type.into()], false),
            None,
        );
        self.functions.insert(VarId::new(1000163), set_elem_count);
        // bhc_set_elem_at(set_ptr, index) -> i64
        let set_elem_at = self.module.llvm_module().add_function(
            "bhc_set_elem_at",
            i64_type.fn_type(&[ptr_type.into(), i64_type.into()], false),
            None,
        );
        self.functions.insert(VarId::new(1000164), set_elem_at);

        // ---- List RTS functions (VarId 1170-1175) ----
        // bhc_list_sort(list_ptr) -> ptr
        let list_sort = self.module.llvm_module().add_function(
            "bhc_list_sort",
            ptr_type.fn_type(&[ptr_type.into()], false),
            None,
        );
        self.functions.insert(VarId::new(1000170), list_sort);
        // bhc_list_sort_by(closure_ptr, list_ptr) -> ptr
        let list_sort_by = self.module.llvm_module().add_function(
            "bhc_list_sort_by",
            ptr_type.fn_type(&[ptr_type.into(), ptr_type.into()], false),
            None,
        );
        self.functions.insert(VarId::new(1000171), list_sort_by);
        // bhc_list_nub(list_ptr) -> ptr
        let list_nub = self.module.llvm_module().add_function(
            "bhc_list_nub",
            ptr_type.fn_type(&[ptr_type.into()], false),
            None,
        );
        self.functions.insert(VarId::new(1000172), list_nub);
        // bhc_list_group(list_ptr) -> ptr
        let list_group = self.module.llvm_module().add_function(
            "bhc_list_group",
            ptr_type.fn_type(&[ptr_type.into()], false),
            None,
        );
        self.functions.insert(VarId::new(1000173), list_group);
        // bhc_list_intercalate(sep_ptr, list_ptr) -> ptr
        let list_intercalate = self.module.llvm_module().add_function(
            "bhc_list_intercalate",
            ptr_type.fn_type(&[ptr_type.into(), ptr_type.into()], false),
            None,
        );
        self.functions.insert(VarId::new(1000174), list_intercalate);
        // bhc_list_transpose(list_ptr) -> ptr
        let list_transpose = self.module.llvm_module().add_function(
            "bhc_list_transpose",
            ptr_type.fn_type(&[ptr_type.into()], false),
            None,
        );
        self.functions.insert(VarId::new(1000175), list_transpose);

        // ---- E.26 List RTS functions (VarId 1000550-1000559) ----
        // bhc_list_sort_on(key_fn, list) -> ptr
        let list_sort_on = self.module.llvm_module().add_function(
            "bhc_list_sort_on",
            ptr_type.fn_type(&[ptr_type.into(), ptr_type.into()], false),
            None,
        );
        self.functions.insert(VarId::new(1000550), list_sort_on);
        // bhc_list_nub_by(eq_fn, list) -> ptr
        let list_nub_by = self.module.llvm_module().add_function(
            "bhc_list_nub_by",
            ptr_type.fn_type(&[ptr_type.into(), ptr_type.into()], false),
            None,
        );
        self.functions.insert(VarId::new(1000551), list_nub_by);
        // bhc_list_group_by(eq_fn, list) -> ptr
        let list_group_by = self.module.llvm_module().add_function(
            "bhc_list_group_by",
            ptr_type.fn_type(&[ptr_type.into(), ptr_type.into()], false),
            None,
        );
        self.functions.insert(VarId::new(1000552), list_group_by);
        // bhc_list_delete_by(eq_fn, val, list) -> ptr
        let list_delete_by = self.module.llvm_module().add_function(
            "bhc_list_delete_by",
            ptr_type.fn_type(&[ptr_type.into(), ptr_type.into(), ptr_type.into()], false),
            None,
        );
        self.functions.insert(VarId::new(1000553), list_delete_by);
        // bhc_list_union_by(eq_fn, xs, ys) -> ptr
        let list_union_by = self.module.llvm_module().add_function(
            "bhc_list_union_by",
            ptr_type.fn_type(&[ptr_type.into(), ptr_type.into(), ptr_type.into()], false),
            None,
        );
        self.functions.insert(VarId::new(1000554), list_union_by);
        // bhc_list_intersect_by(eq_fn, xs, ys) -> ptr
        let list_intersect_by = self.module.llvm_module().add_function(
            "bhc_list_intersect_by",
            ptr_type.fn_type(&[ptr_type.into(), ptr_type.into(), ptr_type.into()], false),
            None,
        );
        self.functions
            .insert(VarId::new(1000555), list_intersect_by);
        // bhc_list_strip_prefix(prefix, list) -> ptr
        let list_strip_prefix = self.module.llvm_module().add_function(
            "bhc_list_strip_prefix",
            ptr_type.fn_type(&[ptr_type.into(), ptr_type.into()], false),
            None,
        );
        self.functions
            .insert(VarId::new(1000556), list_strip_prefix);
        // bhc_list_insert(val, list) -> ptr
        let list_insert = self.module.llvm_module().add_function(
            "bhc_list_insert",
            ptr_type.fn_type(&[ptr_type.into(), ptr_type.into()], false),
            None,
        );
        self.functions.insert(VarId::new(1000557), list_insert);
        // bhc_list_map_accum_l(f, acc, list) -> ptr
        let list_map_accum_l = self.module.llvm_module().add_function(
            "bhc_list_map_accum_l",
            ptr_type.fn_type(&[ptr_type.into(), ptr_type.into(), ptr_type.into()], false),
            None,
        );
        self.functions.insert(VarId::new(1000558), list_map_accum_l);
        // bhc_list_map_accum_r(f, acc, list) -> ptr
        let list_map_accum_r = self.module.llvm_module().add_function(
            "bhc_list_map_accum_r",
            ptr_type.fn_type(&[ptr_type.into(), ptr_type.into(), ptr_type.into()], false),
            None,
        );
        self.functions.insert(VarId::new(1000559), list_map_accum_r);

        // ---- E.28 IO input RTS functions (VarId 1000560-1000562) ----
        let get_char_fn = self.module.llvm_module().add_function(
            "bhc_getChar",
            i64_type.fn_type(&[], false),
            None,
        );
        self.functions.insert(VarId::new(1000560), get_char_fn);
        let is_eof_fn =
            self.module
                .llvm_module()
                .add_function("bhc_isEOF", i64_type.fn_type(&[], false), None);
        self.functions.insert(VarId::new(1000561), is_eof_fn);
        let get_contents_fn = self.module.llvm_module().add_function(
            "bhc_getContents",
            ptr_type.fn_type(&[], false),
            None,
        );
        self.functions.insert(VarId::new(1000562), get_contents_fn);

        // ---- E.45: Integer (arbitrary precision) RTS functions (VarId 1000600-1000619) ----
        // bhc_integer_from_i64(i64) -> ptr
        let int_from_i64 = self.module.llvm_module().add_function(
            "bhc_integer_from_i64",
            ptr_type.fn_type(&[i64_type.into()], false),
            None,
        );
        self.functions.insert(VarId::new(1000600), int_from_i64);
        // bhc_integer_from_str(ptr) -> ptr
        let int_from_str = self.module.llvm_module().add_function(
            "bhc_integer_from_str",
            ptr_type.fn_type(&[ptr_type.into()], false),
            None,
        );
        self.functions.insert(VarId::new(1000601), int_from_str);
        // bhc_integer_to_i64(ptr) -> i64
        let int_to_i64 = self.module.llvm_module().add_function(
            "bhc_integer_to_i64",
            i64_type.fn_type(&[ptr_type.into()], false),
            None,
        );
        self.functions.insert(VarId::new(1000602), int_to_i64);
        // bhc_integer_add(ptr, ptr) -> ptr
        let int_add = self.module.llvm_module().add_function(
            "bhc_integer_add",
            ptr_type.fn_type(&[ptr_type.into(), ptr_type.into()], false),
            None,
        );
        self.functions.insert(VarId::new(1000603), int_add);
        // bhc_integer_sub(ptr, ptr) -> ptr
        let int_sub = self.module.llvm_module().add_function(
            "bhc_integer_sub",
            ptr_type.fn_type(&[ptr_type.into(), ptr_type.into()], false),
            None,
        );
        self.functions.insert(VarId::new(1000604), int_sub);
        // bhc_integer_mul(ptr, ptr) -> ptr
        let int_mul = self.module.llvm_module().add_function(
            "bhc_integer_mul",
            ptr_type.fn_type(&[ptr_type.into(), ptr_type.into()], false),
            None,
        );
        self.functions.insert(VarId::new(1000605), int_mul);
        // bhc_integer_div(ptr, ptr) -> ptr
        let int_div = self.module.llvm_module().add_function(
            "bhc_integer_div",
            ptr_type.fn_type(&[ptr_type.into(), ptr_type.into()], false),
            None,
        );
        self.functions.insert(VarId::new(1000606), int_div);
        // bhc_integer_mod(ptr, ptr) -> ptr
        let int_mod = self.module.llvm_module().add_function(
            "bhc_integer_mod",
            ptr_type.fn_type(&[ptr_type.into(), ptr_type.into()], false),
            None,
        );
        self.functions.insert(VarId::new(1000607), int_mod);
        // bhc_integer_negate(ptr) -> ptr
        let int_negate = self.module.llvm_module().add_function(
            "bhc_integer_negate",
            ptr_type.fn_type(&[ptr_type.into()], false),
            None,
        );
        self.functions.insert(VarId::new(1000608), int_negate);
        // bhc_integer_abs(ptr) -> ptr
        let int_abs = self.module.llvm_module().add_function(
            "bhc_integer_abs",
            ptr_type.fn_type(&[ptr_type.into()], false),
            None,
        );
        self.functions.insert(VarId::new(1000609), int_abs);
        // bhc_integer_signum(ptr) -> ptr
        let int_signum = self.module.llvm_module().add_function(
            "bhc_integer_signum",
            ptr_type.fn_type(&[ptr_type.into()], false),
            None,
        );
        self.functions.insert(VarId::new(1000610), int_signum);
        // bhc_integer_gcd(ptr, ptr) -> ptr
        let int_gcd = self.module.llvm_module().add_function(
            "bhc_integer_gcd",
            ptr_type.fn_type(&[ptr_type.into(), ptr_type.into()], false),
            None,
        );
        self.functions.insert(VarId::new(1000611), int_gcd);
        // bhc_integer_eq(ptr, ptr) -> i64
        let int_eq = self.module.llvm_module().add_function(
            "bhc_integer_eq",
            i64_type.fn_type(&[ptr_type.into(), ptr_type.into()], false),
            None,
        );
        self.functions.insert(VarId::new(1000612), int_eq);
        // bhc_integer_lt(ptr, ptr) -> i64
        let int_lt = self.module.llvm_module().add_function(
            "bhc_integer_lt",
            i64_type.fn_type(&[ptr_type.into(), ptr_type.into()], false),
            None,
        );
        self.functions.insert(VarId::new(1000613), int_lt);
        // bhc_integer_le(ptr, ptr) -> i64
        let int_le = self.module.llvm_module().add_function(
            "bhc_integer_le",
            i64_type.fn_type(&[ptr_type.into(), ptr_type.into()], false),
            None,
        );
        self.functions.insert(VarId::new(1000614), int_le);
        // bhc_integer_gt(ptr, ptr) -> i64
        let int_gt = self.module.llvm_module().add_function(
            "bhc_integer_gt",
            i64_type.fn_type(&[ptr_type.into(), ptr_type.into()], false),
            None,
        );
        self.functions.insert(VarId::new(1000615), int_gt);
        // bhc_integer_ge(ptr, ptr) -> i64
        let int_ge = self.module.llvm_module().add_function(
            "bhc_integer_ge",
            i64_type.fn_type(&[ptr_type.into(), ptr_type.into()], false),
            None,
        );
        self.functions.insert(VarId::new(1000616), int_ge);
        // bhc_integer_compare(ptr, ptr) -> i64
        let int_compare = self.module.llvm_module().add_function(
            "bhc_integer_compare",
            i64_type.fn_type(&[ptr_type.into(), ptr_type.into()], false),
            None,
        );
        self.functions.insert(VarId::new(1000617), int_compare);
        // bhc_integer_show(ptr) -> ptr
        let int_show = self.module.llvm_module().add_function(
            "bhc_integer_show",
            ptr_type.fn_type(&[ptr_type.into()], false),
            None,
        );
        self.functions.insert(VarId::new(1000618), int_show);

        // ---- String RTS functions (VarId 1176-1179) ----
        // bhc_string_lines(str_ptr) -> ptr
        let string_lines = self.module.llvm_module().add_function(
            "bhc_string_lines",
            ptr_type.fn_type(&[ptr_type.into()], false),
            None,
        );
        self.functions.insert(VarId::new(1000176), string_lines);
        // bhc_string_unlines(list_ptr) -> ptr
        let string_unlines = self.module.llvm_module().add_function(
            "bhc_string_unlines",
            ptr_type.fn_type(&[ptr_type.into()], false),
            None,
        );
        self.functions.insert(VarId::new(1000177), string_unlines);
        // bhc_string_words(str_ptr) -> ptr
        let string_words = self.module.llvm_module().add_function(
            "bhc_string_words",
            ptr_type.fn_type(&[ptr_type.into()], false),
            None,
        );
        self.functions.insert(VarId::new(1000178), string_words);
        // bhc_string_unwords(list_ptr) -> ptr
        let string_unwords = self.module.llvm_module().add_function(
            "bhc_string_unwords",
            ptr_type.fn_type(&[ptr_type.into()], false),
            None,
        );
        self.functions.insert(VarId::new(1000179), string_unwords);
        // bhc_string_length(str_ptr) -> i64  (strlen wrapper for C strings)
        let string_length = self.module.llvm_module().add_function(
            "bhc_string_length",
            i64_type.fn_type(&[ptr_type.into()], false),
            None,
        );
        self.functions.insert(VarId::new(1000180), string_length);

        // ---- Text RTS functions (VarId 1200-1227) ----
        // bhc_text_empty() -> ptr
        let text_empty = self.module.llvm_module().add_function(
            "bhc_text_empty",
            ptr_type.fn_type(&[], false),
            None,
        );
        self.functions.insert(VarId::new(1000200), text_empty);
        // bhc_text_singleton(i64) -> ptr
        let text_singleton = self.module.llvm_module().add_function(
            "bhc_text_singleton",
            ptr_type.fn_type(&[i64_type.into()], false),
            None,
        );
        self.functions.insert(VarId::new(1000201), text_singleton);
        // bhc_text_null(ptr) -> i64
        let text_null = self.module.llvm_module().add_function(
            "bhc_text_null",
            i64_type.fn_type(&[ptr_type.into()], false),
            None,
        );
        self.functions.insert(VarId::new(1000202), text_null);
        // bhc_text_length(ptr) -> i64
        let text_length = self.module.llvm_module().add_function(
            "bhc_text_length",
            i64_type.fn_type(&[ptr_type.into()], false),
            None,
        );
        self.functions.insert(VarId::new(1000203), text_length);
        // bhc_text_eq(ptr, ptr) -> i64
        let text_eq = self.module.llvm_module().add_function(
            "bhc_text_eq",
            i64_type.fn_type(&[ptr_type.into(), ptr_type.into()], false),
            None,
        );
        self.functions.insert(VarId::new(1000204), text_eq);
        // bhc_text_compare(ptr, ptr) -> i64
        let text_compare = self.module.llvm_module().add_function(
            "bhc_text_compare",
            i64_type.fn_type(&[ptr_type.into(), ptr_type.into()], false),
            None,
        );
        self.functions.insert(VarId::new(1000205), text_compare);
        // bhc_text_head(ptr) -> i64
        let text_head = self.module.llvm_module().add_function(
            "bhc_text_head",
            i64_type.fn_type(&[ptr_type.into()], false),
            None,
        );
        self.functions.insert(VarId::new(1000206), text_head);
        // bhc_text_last(ptr) -> i64
        let text_last = self.module.llvm_module().add_function(
            "bhc_text_last",
            i64_type.fn_type(&[ptr_type.into()], false),
            None,
        );
        self.functions.insert(VarId::new(1000207), text_last);
        // bhc_text_tail(ptr) -> ptr
        let text_tail = self.module.llvm_module().add_function(
            "bhc_text_tail",
            ptr_type.fn_type(&[ptr_type.into()], false),
            None,
        );
        self.functions.insert(VarId::new(1000208), text_tail);
        // bhc_text_init(ptr) -> ptr
        let text_init = self.module.llvm_module().add_function(
            "bhc_text_init",
            ptr_type.fn_type(&[ptr_type.into()], false),
            None,
        );
        self.functions.insert(VarId::new(1000209), text_init);
        // bhc_text_append(ptr, ptr) -> ptr
        let text_append = self.module.llvm_module().add_function(
            "bhc_text_append",
            ptr_type.fn_type(&[ptr_type.into(), ptr_type.into()], false),
            None,
        );
        self.functions.insert(VarId::new(1000210), text_append);
        // bhc_text_reverse(ptr) -> ptr
        let text_reverse = self.module.llvm_module().add_function(
            "bhc_text_reverse",
            ptr_type.fn_type(&[ptr_type.into()], false),
            None,
        );
        self.functions.insert(VarId::new(1000211), text_reverse);
        // bhc_text_take(i64, ptr) -> ptr
        let text_take = self.module.llvm_module().add_function(
            "bhc_text_take",
            ptr_type.fn_type(&[i64_type.into(), ptr_type.into()], false),
            None,
        );
        self.functions.insert(VarId::new(1000212), text_take);
        // bhc_text_take_end(i64, ptr) -> ptr
        let text_take_end = self.module.llvm_module().add_function(
            "bhc_text_take_end",
            ptr_type.fn_type(&[i64_type.into(), ptr_type.into()], false),
            None,
        );
        self.functions.insert(VarId::new(1000213), text_take_end);
        // bhc_text_drop(i64, ptr) -> ptr
        let text_drop = self.module.llvm_module().add_function(
            "bhc_text_drop",
            ptr_type.fn_type(&[i64_type.into(), ptr_type.into()], false),
            None,
        );
        self.functions.insert(VarId::new(1000214), text_drop);
        // bhc_text_drop_end(i64, ptr) -> ptr
        let text_drop_end = self.module.llvm_module().add_function(
            "bhc_text_drop_end",
            ptr_type.fn_type(&[i64_type.into(), ptr_type.into()], false),
            None,
        );
        self.functions.insert(VarId::new(1000215), text_drop_end);
        // bhc_text_is_prefix_of(ptr, ptr) -> i64
        let text_is_prefix = self.module.llvm_module().add_function(
            "bhc_text_is_prefix_of",
            i64_type.fn_type(&[ptr_type.into(), ptr_type.into()], false),
            None,
        );
        self.functions.insert(VarId::new(1000216), text_is_prefix);
        // bhc_text_is_suffix_of(ptr, ptr) -> i64
        let text_is_suffix = self.module.llvm_module().add_function(
            "bhc_text_is_suffix_of",
            i64_type.fn_type(&[ptr_type.into(), ptr_type.into()], false),
            None,
        );
        self.functions.insert(VarId::new(1000217), text_is_suffix);
        // bhc_text_is_infix_of(ptr, ptr) -> i64
        let text_is_infix = self.module.llvm_module().add_function(
            "bhc_text_is_infix_of",
            i64_type.fn_type(&[ptr_type.into(), ptr_type.into()], false),
            None,
        );
        self.functions.insert(VarId::new(1000218), text_is_infix);
        // bhc_text_to_lower(ptr) -> ptr
        let text_to_lower = self.module.llvm_module().add_function(
            "bhc_text_to_lower",
            ptr_type.fn_type(&[ptr_type.into()], false),
            None,
        );
        self.functions.insert(VarId::new(1000219), text_to_lower);
        // bhc_text_to_upper(ptr) -> ptr
        let text_to_upper = self.module.llvm_module().add_function(
            "bhc_text_to_upper",
            ptr_type.fn_type(&[ptr_type.into()], false),
            None,
        );
        self.functions.insert(VarId::new(1000220), text_to_upper);
        // bhc_text_to_case_fold(ptr) -> ptr
        let text_case_fold = self.module.llvm_module().add_function(
            "bhc_text_to_case_fold",
            ptr_type.fn_type(&[ptr_type.into()], false),
            None,
        );
        self.functions.insert(VarId::new(1000221), text_case_fold);
        // bhc_text_to_title(ptr) -> ptr
        let text_to_title = self.module.llvm_module().add_function(
            "bhc_text_to_title",
            ptr_type.fn_type(&[ptr_type.into()], false),
            None,
        );
        self.functions.insert(VarId::new(1000222), text_to_title);
        // bhc_text_pack(ptr) -> ptr  (list -> Text)
        let text_pack = self.module.llvm_module().add_function(
            "bhc_text_pack",
            ptr_type.fn_type(&[ptr_type.into()], false),
            None,
        );
        self.functions.insert(VarId::new(1000223), text_pack);
        // bhc_text_char_count(ptr) -> i64  (for unpack iteration)
        let text_char_count = self.module.llvm_module().add_function(
            "bhc_text_char_count",
            i64_type.fn_type(&[ptr_type.into()], false),
            None,
        );
        self.functions.insert(VarId::new(1000224), text_char_count);
        // bhc_text_char_at(ptr, i64) -> i64  (for unpack iteration)
        let text_char_at = self.module.llvm_module().add_function(
            "bhc_text_char_at",
            i64_type.fn_type(&[ptr_type.into(), i64_type.into()], false),
            None,
        );
        self.functions.insert(VarId::new(1000225), text_char_at);
        // bhc_text_map(fn_ptr, env_ptr, text_ptr) -> ptr
        let text_map = self.module.llvm_module().add_function(
            "bhc_text_map",
            ptr_type.fn_type(&[ptr_type.into(), ptr_type.into(), ptr_type.into()], false),
            None,
        );
        self.functions.insert(VarId::new(1000226), text_map);

        // ---- Additional Text RTS functions (VarId 1000227-1000236) ----
        // bhc_text_filter(fn_ptr, env_ptr, text_ptr) -> ptr
        let text_filter = self.module.llvm_module().add_function(
            "bhc_text_filter",
            ptr_type.fn_type(&[ptr_type.into(), ptr_type.into(), ptr_type.into()], false),
            None,
        );
        self.functions.insert(VarId::new(1000227), text_filter);
        // bhc_text_foldl(fn_ptr, env_ptr, i64, text_ptr) -> i64
        let text_foldl = self.module.llvm_module().add_function(
            "bhc_text_foldl",
            i64_type.fn_type(
                &[
                    ptr_type.into(),
                    ptr_type.into(),
                    i64_type.into(),
                    ptr_type.into(),
                ],
                false,
            ),
            None,
        );
        self.functions.insert(VarId::new(1000228), text_foldl);
        // bhc_text_concat(list_ptr) -> ptr
        let text_concat = self.module.llvm_module().add_function(
            "bhc_text_concat",
            ptr_type.fn_type(&[ptr_type.into()], false),
            None,
        );
        self.functions.insert(VarId::new(1000229), text_concat);
        // bhc_text_intercalate(sep_ptr, list_ptr) -> ptr
        let text_intercalate = self.module.llvm_module().add_function(
            "bhc_text_intercalate",
            ptr_type.fn_type(&[ptr_type.into(), ptr_type.into()], false),
            None,
        );
        self.functions.insert(VarId::new(1000230), text_intercalate);
        // bhc_text_strip(text_ptr) -> ptr
        let text_strip = self.module.llvm_module().add_function(
            "bhc_text_strip",
            ptr_type.fn_type(&[ptr_type.into()], false),
            None,
        );
        self.functions.insert(VarId::new(1000231), text_strip);
        // bhc_text_words(text_ptr) -> ptr (returns cons-list of Text)
        let text_words = self.module.llvm_module().add_function(
            "bhc_text_words",
            ptr_type.fn_type(&[ptr_type.into()], false),
            None,
        );
        self.functions.insert(VarId::new(1000232), text_words);
        // bhc_text_lines(text_ptr) -> ptr (returns cons-list of Text)
        let text_lines = self.module.llvm_module().add_function(
            "bhc_text_lines",
            ptr_type.fn_type(&[ptr_type.into()], false),
            None,
        );
        self.functions.insert(VarId::new(1000233), text_lines);
        // bhc_text_split_on(needle_ptr, haystack_ptr) -> ptr (returns cons-list of Text)
        let text_split_on = self.module.llvm_module().add_function(
            "bhc_text_split_on",
            ptr_type.fn_type(&[ptr_type.into(), ptr_type.into()], false),
            None,
        );
        self.functions.insert(VarId::new(1000234), text_split_on);
        // bhc_text_replace(needle_ptr, replacement_ptr, haystack_ptr) -> ptr
        let text_replace = self.module.llvm_module().add_function(
            "bhc_text_replace",
            ptr_type.fn_type(&[ptr_type.into(), ptr_type.into(), ptr_type.into()], false),
            None,
        );
        self.functions.insert(VarId::new(1000235), text_replace);
        // bhc_text_encode_utf8(text_ptr) -> ptr (Text -> ByteString)
        let text_encode_utf8 = self.module.llvm_module().add_function(
            "bhc_text_encode_utf8",
            ptr_type.fn_type(&[ptr_type.into()], false),
            None,
        );
        self.functions.insert(VarId::new(1000236), text_encode_utf8);
        // bhc_text_decode_utf8(bs_ptr) -> ptr (ByteString -> Text)
        let text_decode_utf8 = self.module.llvm_module().add_function(
            "bhc_text_decode_utf8",
            ptr_type.fn_type(&[ptr_type.into()], false),
            None,
        );
        self.functions.insert(VarId::new(1000237), text_decode_utf8);

        // ---- Data.Text.IO RTS functions (VarId 1000240-1000246) ----
        // bhc_text_read_file(path_cstr) -> ptr (Text)
        let text_read_file = self.module.llvm_module().add_function(
            "bhc_text_read_file",
            ptr_type.fn_type(&[ptr_type.into()], false),
            None,
        );
        self.functions.insert(VarId::new(1000240), text_read_file);
        // bhc_text_write_file(path_cstr, text_ptr) -> void
        let text_write_file = self.module.llvm_module().add_function(
            "bhc_text_write_file",
            void_type.fn_type(&[ptr_type.into(), ptr_type.into()], false),
            None,
        );
        self.functions.insert(VarId::new(1000241), text_write_file);
        // bhc_text_append_file(path_cstr, text_ptr) -> void
        let text_append_file = self.module.llvm_module().add_function(
            "bhc_text_append_file",
            void_type.fn_type(&[ptr_type.into(), ptr_type.into()], false),
            None,
        );
        self.functions.insert(VarId::new(1000242), text_append_file);
        // bhc_text_h_get_contents(handle_ptr) -> ptr (Text)
        let text_h_get_contents = self.module.llvm_module().add_function(
            "bhc_text_h_get_contents",
            ptr_type.fn_type(&[ptr_type.into()], false),
            None,
        );
        self.functions
            .insert(VarId::new(1000243), text_h_get_contents);
        // bhc_text_h_get_line(handle_ptr) -> ptr (Text)
        let text_h_get_line = self.module.llvm_module().add_function(
            "bhc_text_h_get_line",
            ptr_type.fn_type(&[ptr_type.into()], false),
            None,
        );
        self.functions.insert(VarId::new(1000244), text_h_get_line);
        // bhc_text_h_put_str(handle_ptr, text_ptr) -> void
        let text_h_put_str = self.module.llvm_module().add_function(
            "bhc_text_h_put_str",
            void_type.fn_type(&[ptr_type.into(), ptr_type.into()], false),
            None,
        );
        self.functions.insert(VarId::new(1000245), text_h_put_str);
        // bhc_text_h_put_str_ln(handle_ptr, text_ptr) -> void
        let text_h_put_str_ln = self.module.llvm_module().add_function(
            "bhc_text_h_put_str_ln",
            void_type.fn_type(&[ptr_type.into(), ptr_type.into()], false),
            None,
        );
        self.functions
            .insert(VarId::new(1000246), text_h_put_str_ln);

        // ---- ByteString RTS functions (VarId 1000400-1000425) ----
        // bhc_bs_empty() -> ptr
        let bs_empty = self.module.llvm_module().add_function(
            "bhc_bs_empty",
            ptr_type.fn_type(&[], false),
            None,
        );
        self.functions.insert(VarId::new(1000400), bs_empty);
        // bhc_bs_singleton(i64) -> ptr
        let bs_singleton = self.module.llvm_module().add_function(
            "bhc_bs_singleton",
            ptr_type.fn_type(&[i64_type.into()], false),
            None,
        );
        self.functions.insert(VarId::new(1000401), bs_singleton);
        // bhc_bs_pack(ptr) -> ptr  (list -> ByteString)
        let bs_pack = self.module.llvm_module().add_function(
            "bhc_bs_pack",
            ptr_type.fn_type(&[ptr_type.into()], false),
            None,
        );
        self.functions.insert(VarId::new(1000402), bs_pack);
        // bhc_bs_length(ptr) -> i64
        let bs_length = self.module.llvm_module().add_function(
            "bhc_bs_length",
            i64_type.fn_type(&[ptr_type.into()], false),
            None,
        );
        self.functions.insert(VarId::new(1000403), bs_length);
        // bhc_bs_null(ptr) -> i64
        let bs_null = self.module.llvm_module().add_function(
            "bhc_bs_null",
            i64_type.fn_type(&[ptr_type.into()], false),
            None,
        );
        self.functions.insert(VarId::new(1000404), bs_null);
        // bhc_bs_head(ptr) -> i64
        let bs_head = self.module.llvm_module().add_function(
            "bhc_bs_head",
            i64_type.fn_type(&[ptr_type.into()], false),
            None,
        );
        self.functions.insert(VarId::new(1000405), bs_head);
        // bhc_bs_last(ptr) -> i64
        let bs_last = self.module.llvm_module().add_function(
            "bhc_bs_last",
            i64_type.fn_type(&[ptr_type.into()], false),
            None,
        );
        self.functions.insert(VarId::new(1000406), bs_last);
        // bhc_bs_tail(ptr) -> ptr
        let bs_tail = self.module.llvm_module().add_function(
            "bhc_bs_tail",
            ptr_type.fn_type(&[ptr_type.into()], false),
            None,
        );
        self.functions.insert(VarId::new(1000407), bs_tail);
        // bhc_bs_init(ptr) -> ptr
        let bs_init = self.module.llvm_module().add_function(
            "bhc_bs_init",
            ptr_type.fn_type(&[ptr_type.into()], false),
            None,
        );
        self.functions.insert(VarId::new(1000408), bs_init);
        // bhc_bs_append(ptr, ptr) -> ptr
        let bs_append = self.module.llvm_module().add_function(
            "bhc_bs_append",
            ptr_type.fn_type(&[ptr_type.into(), ptr_type.into()], false),
            None,
        );
        self.functions.insert(VarId::new(1000409), bs_append);
        // bhc_bs_cons(i64, ptr) -> ptr
        let bs_cons = self.module.llvm_module().add_function(
            "bhc_bs_cons",
            ptr_type.fn_type(&[i64_type.into(), ptr_type.into()], false),
            None,
        );
        self.functions.insert(VarId::new(1000410), bs_cons);
        // bhc_bs_snoc(ptr, i64) -> ptr
        let bs_snoc = self.module.llvm_module().add_function(
            "bhc_bs_snoc",
            ptr_type.fn_type(&[ptr_type.into(), i64_type.into()], false),
            None,
        );
        self.functions.insert(VarId::new(1000411), bs_snoc);
        // bhc_bs_take(i64, ptr) -> ptr
        let bs_take = self.module.llvm_module().add_function(
            "bhc_bs_take",
            ptr_type.fn_type(&[i64_type.into(), ptr_type.into()], false),
            None,
        );
        self.functions.insert(VarId::new(1000412), bs_take);
        // bhc_bs_drop(i64, ptr) -> ptr
        let bs_drop = self.module.llvm_module().add_function(
            "bhc_bs_drop",
            ptr_type.fn_type(&[i64_type.into(), ptr_type.into()], false),
            None,
        );
        self.functions.insert(VarId::new(1000413), bs_drop);
        // bhc_bs_reverse(ptr) -> ptr
        let bs_reverse = self.module.llvm_module().add_function(
            "bhc_bs_reverse",
            ptr_type.fn_type(&[ptr_type.into()], false),
            None,
        );
        self.functions.insert(VarId::new(1000414), bs_reverse);
        // bhc_bs_elem(i64, ptr) -> i64
        let bs_elem = self.module.llvm_module().add_function(
            "bhc_bs_elem",
            i64_type.fn_type(&[i64_type.into(), ptr_type.into()], false),
            None,
        );
        self.functions.insert(VarId::new(1000415), bs_elem);
        // bhc_bs_index(ptr, i64) -> i64
        let bs_index = self.module.llvm_module().add_function(
            "bhc_bs_index",
            i64_type.fn_type(&[ptr_type.into(), i64_type.into()], false),
            None,
        );
        self.functions.insert(VarId::new(1000416), bs_index);
        // bhc_bs_eq(ptr, ptr) -> i64
        let bs_eq = self.module.llvm_module().add_function(
            "bhc_bs_eq",
            i64_type.fn_type(&[ptr_type.into(), ptr_type.into()], false),
            None,
        );
        self.functions.insert(VarId::new(1000417), bs_eq);
        // bhc_bs_compare(ptr, ptr) -> i64
        let bs_compare = self.module.llvm_module().add_function(
            "bhc_bs_compare",
            i64_type.fn_type(&[ptr_type.into(), ptr_type.into()], false),
            None,
        );
        self.functions.insert(VarId::new(1000418), bs_compare);
        // bhc_bs_is_prefix_of(ptr, ptr) -> i64
        let bs_is_prefix = self.module.llvm_module().add_function(
            "bhc_bs_is_prefix_of",
            i64_type.fn_type(&[ptr_type.into(), ptr_type.into()], false),
            None,
        );
        self.functions.insert(VarId::new(1000419), bs_is_prefix);
        // bhc_bs_is_suffix_of(ptr, ptr) -> i64
        let bs_is_suffix = self.module.llvm_module().add_function(
            "bhc_bs_is_suffix_of",
            i64_type.fn_type(&[ptr_type.into(), ptr_type.into()], false),
            None,
        );
        self.functions.insert(VarId::new(1000420), bs_is_suffix);
        // bhc_bs_unpack(ptr, i64) -> i64  (for unpack iteration)
        let bs_unpack = self.module.llvm_module().add_function(
            "bhc_bs_unpack",
            i64_type.fn_type(&[ptr_type.into(), i64_type.into()], false),
            None,
        );
        self.functions.insert(VarId::new(1000421), bs_unpack);
        // bhc_bs_read_file(path_ptr) -> ptr
        let bs_read_file = self.module.llvm_module().add_function(
            "bhc_bs_read_file",
            ptr_type.fn_type(&[ptr_type.into()], false),
            None,
        );
        self.functions.insert(VarId::new(1000422), bs_read_file);
        // bhc_bs_write_file(path_ptr, bs_ptr) -> void
        let bs_write_file = self.module.llvm_module().add_function(
            "bhc_bs_write_file",
            void_type.fn_type(&[ptr_type.into(), ptr_type.into()], false),
            None,
        );
        self.functions.insert(VarId::new(1000423), bs_write_file);

        // ---- ByteArray RTS functions (VarId 1250-1258) ----
        // bhc_bytearray_malloc(i64) -> ptr
        let ba_malloc = self.module.llvm_module().add_function(
            "bhc_bytearray_malloc",
            ptr_type.fn_type(&[i64_type.into()], false),
            None,
        );
        self.functions.insert(VarId::new(1000250), ba_malloc);
        // bhc_bytearray_contents(ptr) -> ptr
        let ba_contents = self.module.llvm_module().add_function(
            "bhc_bytearray_contents",
            ptr_type.fn_type(&[ptr_type.into()], false),
            None,
        );
        self.functions.insert(VarId::new(1000251), ba_contents);
        // bhc_bytearray_index(ptr, i64) -> i64
        let ba_index = self.module.llvm_module().add_function(
            "bhc_bytearray_index",
            i64_type.fn_type(&[ptr_type.into(), i64_type.into()], false),
            None,
        );
        self.functions.insert(VarId::new(1000252), ba_index);
        // bhc_bytearray_copy(ptr, ptr, i64, i64) -> void
        let ba_copy = self.module.llvm_module().add_function(
            "bhc_bytearray_copy",
            void_type.fn_type(
                &[
                    ptr_type.into(),
                    ptr_type.into(),
                    i64_type.into(),
                    i64_type.into(),
                ],
                false,
            ),
            None,
        );
        self.functions.insert(VarId::new(1000253), ba_copy);
        // bhc_ptr_plus(ptr, i64) -> ptr
        let ba_ptr_plus = self.module.llvm_module().add_function(
            "bhc_ptr_plus",
            ptr_type.fn_type(&[ptr_type.into(), i64_type.into()], false),
            None,
        );
        self.functions.insert(VarId::new(1000254), ba_ptr_plus);
        // bhc_poke_byte(ptr, i64, i64) -> void
        let ba_poke = self.module.llvm_module().add_function(
            "bhc_poke_byte",
            void_type.fn_type(&[ptr_type.into(), i64_type.into(), i64_type.into()], false),
            None,
        );
        self.functions.insert(VarId::new(1000255), ba_poke);
        // bhc_cstring_length(ptr) -> i64
        let ba_strlen = self.module.llvm_module().add_function(
            "bhc_cstring_length",
            i64_type.fn_type(&[ptr_type.into()], false),
            None,
        );
        self.functions.insert(VarId::new(1000256), ba_strlen);
        // bhc_peek_array(i64, ptr) -> ptr
        let ba_peek = self.module.llvm_module().add_function(
            "bhc_peek_array",
            ptr_type.fn_type(&[i64_type.into(), ptr_type.into()], false),
            None,
        );
        self.functions.insert(VarId::new(1000257), ba_peek);

        // ---- Bitcast RTS functions (VarId 1258-1261) ----
        // bhc_float_to_word32(f32) -> i32
        let f32_to_i32 = i32_type.fn_type(&[f32_type.into()], false);
        let float_to_word32 =
            self.module
                .llvm_module()
                .add_function("bhc_float_to_word32", f32_to_i32, None);
        self.functions.insert(VarId::new(1000258), float_to_word32);
        // bhc_double_to_word64(f64) -> i64
        let f64_to_i64_bc = i64_type.fn_type(&[f64_type.into()], false);
        let double_to_word64 =
            self.module
                .llvm_module()
                .add_function("bhc_double_to_word64", f64_to_i64_bc, None);
        self.functions.insert(VarId::new(1000259), double_to_word64);
        // bhc_word32_to_float(i32) -> f32
        let i32_to_f32 = f32_type.fn_type(&[i32_type.into()], false);
        let word32_to_float =
            self.module
                .llvm_module()
                .add_function("bhc_word32_to_float", i32_to_f32, None);
        self.functions.insert(VarId::new(1000260), word32_to_float);
        // bhc_word64_to_double(i64) -> f64
        let i64_to_f64 = f64_type.fn_type(&[i64_type.into()], false);
        let word64_to_double =
            self.module
                .llvm_module()
                .add_function("bhc_word64_to_double", i64_to_f64, None);
        self.functions.insert(VarId::new(1000261), word64_to_double);

        // ---- Lazy Text RTS functions (VarId 1000270-1000283) ----
        // bhc_lazy_text_empty() -> ptr
        let lt_empty = self.module.llvm_module().add_function(
            "bhc_lazy_text_empty",
            ptr_type.fn_type(&[], false),
            None,
        );
        self.functions.insert(VarId::new(1000270), lt_empty);
        // bhc_lazy_text_from_strict(ptr) -> ptr
        let lt_from_strict = self.module.llvm_module().add_function(
            "bhc_lazy_text_from_strict",
            ptr_type.fn_type(&[ptr_type.into()], false),
            None,
        );
        self.functions.insert(VarId::new(1000271), lt_from_strict);
        // bhc_lazy_text_to_strict(ptr) -> ptr
        let lt_to_strict = self.module.llvm_module().add_function(
            "bhc_lazy_text_to_strict",
            ptr_type.fn_type(&[ptr_type.into()], false),
            None,
        );
        self.functions.insert(VarId::new(1000272), lt_to_strict);
        // bhc_lazy_text_pack(ptr) -> ptr
        let lt_pack = self.module.llvm_module().add_function(
            "bhc_lazy_text_pack",
            ptr_type.fn_type(&[ptr_type.into()], false),
            None,
        );
        self.functions.insert(VarId::new(1000273), lt_pack);
        // bhc_lazy_text_unpack(ptr) -> ptr
        let lt_unpack = self.module.llvm_module().add_function(
            "bhc_lazy_text_unpack",
            ptr_type.fn_type(&[ptr_type.into()], false),
            None,
        );
        self.functions.insert(VarId::new(1000274), lt_unpack);
        // bhc_lazy_text_null(ptr) -> i64
        let lt_null = self.module.llvm_module().add_function(
            "bhc_lazy_text_null",
            i64_type.fn_type(&[ptr_type.into()], false),
            None,
        );
        self.functions.insert(VarId::new(1000275), lt_null);
        // bhc_lazy_text_length(ptr) -> i64
        let lt_length = self.module.llvm_module().add_function(
            "bhc_lazy_text_length",
            i64_type.fn_type(&[ptr_type.into()], false),
            None,
        );
        self.functions.insert(VarId::new(1000276), lt_length);
        // bhc_lazy_text_append(ptr, ptr) -> ptr
        let lt_append = self.module.llvm_module().add_function(
            "bhc_lazy_text_append",
            ptr_type.fn_type(&[ptr_type.into(), ptr_type.into()], false),
            None,
        );
        self.functions.insert(VarId::new(1000277), lt_append);
        // bhc_lazy_text_from_chunks(ptr) -> ptr
        let lt_from_chunks = self.module.llvm_module().add_function(
            "bhc_lazy_text_from_chunks",
            ptr_type.fn_type(&[ptr_type.into()], false),
            None,
        );
        self.functions.insert(VarId::new(1000278), lt_from_chunks);
        // bhc_lazy_text_to_chunks(ptr) -> ptr
        let lt_to_chunks = self.module.llvm_module().add_function(
            "bhc_lazy_text_to_chunks",
            ptr_type.fn_type(&[ptr_type.into()], false),
            None,
        );
        self.functions.insert(VarId::new(1000279), lt_to_chunks);
        // bhc_lazy_text_head(ptr) -> i64
        let lt_head = self.module.llvm_module().add_function(
            "bhc_lazy_text_head",
            i64_type.fn_type(&[ptr_type.into()], false),
            None,
        );
        self.functions.insert(VarId::new(1000280), lt_head);
        // bhc_lazy_text_tail(ptr) -> ptr
        let lt_tail = self.module.llvm_module().add_function(
            "bhc_lazy_text_tail",
            ptr_type.fn_type(&[ptr_type.into()], false),
            None,
        );
        self.functions.insert(VarId::new(1000281), lt_tail);
        // bhc_lazy_text_take(i64, ptr) -> ptr
        let lt_take = self.module.llvm_module().add_function(
            "bhc_lazy_text_take",
            ptr_type.fn_type(&[i64_type.into(), ptr_type.into()], false),
            None,
        );
        self.functions.insert(VarId::new(1000282), lt_take);
        // bhc_lazy_text_drop(i64, ptr) -> ptr
        let lt_drop = self.module.llvm_module().add_function(
            "bhc_lazy_text_drop",
            ptr_type.fn_type(&[i64_type.into(), ptr_type.into()], false),
            None,
        );
        self.functions.insert(VarId::new(1000283), lt_drop);

        // ---- Lazy ByteString RTS functions (VarId 1000440-1000459) ----
        // bhc_lazy_bs_empty() -> ptr
        let lbs_empty = self.module.llvm_module().add_function(
            "bhc_lazy_bs_empty",
            ptr_type.fn_type(&[], false),
            None,
        );
        self.functions.insert(VarId::new(1000440), lbs_empty);
        // bhc_lazy_bs_from_strict(ptr) -> ptr
        let lbs_from_strict = self.module.llvm_module().add_function(
            "bhc_lazy_bs_from_strict",
            ptr_type.fn_type(&[ptr_type.into()], false),
            None,
        );
        self.functions.insert(VarId::new(1000441), lbs_from_strict);
        // bhc_lazy_bs_to_strict(ptr) -> ptr
        let lbs_to_strict = self.module.llvm_module().add_function(
            "bhc_lazy_bs_to_strict",
            ptr_type.fn_type(&[ptr_type.into()], false),
            None,
        );
        self.functions.insert(VarId::new(1000442), lbs_to_strict);
        // bhc_lazy_bs_from_chunks(ptr) -> ptr
        let lbs_from_chunks = self.module.llvm_module().add_function(
            "bhc_lazy_bs_from_chunks",
            ptr_type.fn_type(&[ptr_type.into()], false),
            None,
        );
        self.functions.insert(VarId::new(1000443), lbs_from_chunks);
        // bhc_lazy_bs_to_chunks(ptr) -> ptr
        let lbs_to_chunks = self.module.llvm_module().add_function(
            "bhc_lazy_bs_to_chunks",
            ptr_type.fn_type(&[ptr_type.into()], false),
            None,
        );
        self.functions.insert(VarId::new(1000444), lbs_to_chunks);
        // bhc_lazy_bs_null(ptr) -> i64
        let lbs_null = self.module.llvm_module().add_function(
            "bhc_lazy_bs_null",
            i64_type.fn_type(&[ptr_type.into()], false),
            None,
        );
        self.functions.insert(VarId::new(1000445), lbs_null);
        // bhc_lazy_bs_length(ptr) -> i64
        let lbs_length = self.module.llvm_module().add_function(
            "bhc_lazy_bs_length",
            i64_type.fn_type(&[ptr_type.into()], false),
            None,
        );
        self.functions.insert(VarId::new(1000446), lbs_length);
        // bhc_lazy_bs_pack(ptr) -> ptr
        let lbs_pack = self.module.llvm_module().add_function(
            "bhc_lazy_bs_pack",
            ptr_type.fn_type(&[ptr_type.into()], false),
            None,
        );
        self.functions.insert(VarId::new(1000447), lbs_pack);
        // bhc_lazy_bs_append(ptr, ptr) -> ptr
        let lbs_append = self.module.llvm_module().add_function(
            "bhc_lazy_bs_append",
            ptr_type.fn_type(&[ptr_type.into(), ptr_type.into()], false),
            None,
        );
        self.functions.insert(VarId::new(1000448), lbs_append);
        // bhc_lazy_bs_head(ptr) -> i64
        let lbs_head = self.module.llvm_module().add_function(
            "bhc_lazy_bs_head",
            i64_type.fn_type(&[ptr_type.into()], false),
            None,
        );
        self.functions.insert(VarId::new(1000449), lbs_head);
        // bhc_lazy_bs_tail(ptr) -> ptr
        let lbs_tail = self.module.llvm_module().add_function(
            "bhc_lazy_bs_tail",
            ptr_type.fn_type(&[ptr_type.into()], false),
            None,
        );
        self.functions.insert(VarId::new(1000450), lbs_tail);
        // bhc_lazy_bs_take(i64, ptr) -> ptr
        let lbs_take = self.module.llvm_module().add_function(
            "bhc_lazy_bs_take",
            ptr_type.fn_type(&[i64_type.into(), ptr_type.into()], false),
            None,
        );
        self.functions.insert(VarId::new(1000451), lbs_take);
        // bhc_lazy_bs_drop(i64, ptr) -> ptr
        let lbs_drop = self.module.llvm_module().add_function(
            "bhc_lazy_bs_drop",
            ptr_type.fn_type(&[i64_type.into(), ptr_type.into()], false),
            None,
        );
        self.functions.insert(VarId::new(1000452), lbs_drop);
        // bhc_lazy_bs_filter(fn_ptr, env_ptr, ptr) -> ptr
        let lbs_filter = self.module.llvm_module().add_function(
            "bhc_lazy_bs_filter",
            ptr_type.fn_type(&[ptr_type.into(), ptr_type.into(), ptr_type.into()], false),
            None,
        );
        self.functions.insert(VarId::new(1000453), lbs_filter);
        // bhc_lazy_bs_is_prefix_of(ptr, ptr) -> i64
        let lbs_is_prefix = self.module.llvm_module().add_function(
            "bhc_lazy_bs_is_prefix_of",
            i64_type.fn_type(&[ptr_type.into(), ptr_type.into()], false),
            None,
        );
        self.functions.insert(VarId::new(1000454), lbs_is_prefix);
        // bhc_lazy_bs_read_file(ptr) -> ptr
        let lbs_read_file = self.module.llvm_module().add_function(
            "bhc_lazy_bs_read_file",
            ptr_type.fn_type(&[ptr_type.into()], false),
            None,
        );
        self.functions.insert(VarId::new(1000455), lbs_read_file);
        // bhc_lazy_bs_write_file(ptr, ptr) -> void
        let lbs_write_file = self.module.llvm_module().add_function(
            "bhc_lazy_bs_write_file",
            void_type.fn_type(&[ptr_type.into(), ptr_type.into()], false),
            None,
        );
        self.functions.insert(VarId::new(1000456), lbs_write_file);
        // bhc_lazy_bs_put_str(ptr) -> void
        let lbs_put_str = self.module.llvm_module().add_function(
            "bhc_lazy_bs_put_str",
            void_type.fn_type(&[ptr_type.into()], false),
            None,
        );
        self.functions.insert(VarId::new(1000457), lbs_put_str);
        // bhc_lazy_bs_h_put_str(ptr, ptr) -> void
        let lbs_h_put_str = self.module.llvm_module().add_function(
            "bhc_lazy_bs_h_put_str",
            void_type.fn_type(&[ptr_type.into(), ptr_type.into()], false),
            None,
        );
        self.functions.insert(VarId::new(1000458), lbs_h_put_str);
        // bhc_lazy_bs_h_get_contents(ptr) -> ptr
        let lbs_h_get_contents = self.module.llvm_module().add_function(
            "bhc_lazy_bs_h_get_contents",
            ptr_type.fn_type(&[ptr_type.into()], false),
            None,
        );
        self.functions
            .insert(VarId::new(1000459), lbs_h_get_contents);

        // ---- Lazy ByteString Char8 RTS functions (VarId 1000470-1000475) ----
        // bhc_lazy_bs_char8_unpack(ptr) -> ptr
        let lbs_c8_unpack = self.module.llvm_module().add_function(
            "bhc_lazy_bs_char8_unpack",
            ptr_type.fn_type(&[ptr_type.into()], false),
            None,
        );
        self.functions.insert(VarId::new(1000470), lbs_c8_unpack);
        // bhc_lazy_bs_char8_lines(ptr) -> ptr
        let lbs_c8_lines = self.module.llvm_module().add_function(
            "bhc_lazy_bs_char8_lines",
            ptr_type.fn_type(&[ptr_type.into()], false),
            None,
        );
        self.functions.insert(VarId::new(1000471), lbs_c8_lines);
        // bhc_lazy_bs_char8_unlines(ptr) -> ptr
        let lbs_c8_unlines = self.module.llvm_module().add_function(
            "bhc_lazy_bs_char8_unlines",
            ptr_type.fn_type(&[ptr_type.into()], false),
            None,
        );
        self.functions.insert(VarId::new(1000472), lbs_c8_unlines);
        // bhc_lazy_bs_char8_take(i64, ptr) -> ptr
        let lbs_c8_take = self.module.llvm_module().add_function(
            "bhc_lazy_bs_char8_take",
            ptr_type.fn_type(&[i64_type.into(), ptr_type.into()], false),
            None,
        );
        self.functions.insert(VarId::new(1000473), lbs_c8_take);
        // bhc_lazy_bs_char8_drop_while(fn_ptr, env_ptr, ptr) -> ptr
        let lbs_c8_drop_while = self.module.llvm_module().add_function(
            "bhc_lazy_bs_char8_drop_while",
            ptr_type.fn_type(&[ptr_type.into(), ptr_type.into(), ptr_type.into()], false),
            None,
        );
        self.functions
            .insert(VarId::new(1000474), lbs_c8_drop_while);
        // bhc_lazy_bs_char8_cons(i64, ptr) -> ptr
        let lbs_c8_cons = self.module.llvm_module().add_function(
            "bhc_lazy_bs_char8_cons",
            ptr_type.fn_type(&[i64_type.into(), ptr_type.into()], false),
            None,
        );
        self.functions.insert(VarId::new(1000475), lbs_c8_cons);

        // ---- Lazy Text Encoding RTS functions (VarId 1000476-1000477) ----
        // bhc_lazy_text_encode_utf8(ptr) -> ptr
        let lt_encode_utf8 = self.module.llvm_module().add_function(
            "bhc_lazy_text_encode_utf8",
            ptr_type.fn_type(&[ptr_type.into()], false),
            None,
        );
        self.functions.insert(VarId::new(1000476), lt_encode_utf8);
        // bhc_lazy_text_decode_utf8(ptr) -> ptr
        let lt_decode_utf8 = self.module.llvm_module().add_function(
            "bhc_lazy_text_decode_utf8",
            ptr_type.fn_type(&[ptr_type.into()], false),
            None,
        );
        self.functions.insert(VarId::new(1000477), lt_decode_utf8);

        // ---- ByteString Builder RTS functions (VarId 1000478-1000495) ----
        // bhc_bsb_singleton(i64) -> ptr
        let bsb_singleton = self.module.llvm_module().add_function(
            "bhc_bsb_singleton",
            ptr_type.fn_type(&[i64_type.into()], false),
            None,
        );
        self.functions.insert(VarId::new(1000478), bsb_singleton);
        // bhc_bsb_char_utf8(i64) -> ptr
        let bsb_char_utf8 = self.module.llvm_module().add_function(
            "bhc_bsb_char_utf8",
            ptr_type.fn_type(&[i64_type.into()], false),
            None,
        );
        self.functions.insert(VarId::new(1000479), bsb_char_utf8);
        // bhc_bsb_string_utf8(ptr) -> ptr
        let bsb_string_utf8 = self.module.llvm_module().add_function(
            "bhc_bsb_string_utf8",
            ptr_type.fn_type(&[ptr_type.into()], false),
            None,
        );
        self.functions.insert(VarId::new(1000480), bsb_string_utf8);
        // bhc_bsb_int_dec(i64) -> ptr
        let bsb_int_dec = self.module.llvm_module().add_function(
            "bhc_bsb_int_dec",
            ptr_type.fn_type(&[i64_type.into()], false),
            None,
        );
        self.functions.insert(VarId::new(1000481), bsb_int_dec);
        // bhc_bsb_char7(i64) -> ptr
        let bsb_char7 = self.module.llvm_module().add_function(
            "bhc_bsb_char7",
            ptr_type.fn_type(&[i64_type.into()], false),
            None,
        );
        self.functions.insert(VarId::new(1000482), bsb_char7);
        // bhc_bsb_char8(i64) -> ptr
        let bsb_char8 = self.module.llvm_module().add_function(
            "bhc_bsb_char8",
            ptr_type.fn_type(&[i64_type.into()], false),
            None,
        );
        self.functions.insert(VarId::new(1000483), bsb_char8);
        // bhc_bsb_string7(ptr) -> ptr
        let bsb_string7 = self.module.llvm_module().add_function(
            "bhc_bsb_string7",
            ptr_type.fn_type(&[ptr_type.into()], false),
            None,
        );
        self.functions.insert(VarId::new(1000484), bsb_string7);
        // bhc_bsb_string8(ptr) -> ptr
        let bsb_string8 = self.module.llvm_module().add_function(
            "bhc_bsb_string8",
            ptr_type.fn_type(&[ptr_type.into()], false),
            None,
        );
        self.functions.insert(VarId::new(1000485), bsb_string8);
        // bhc_bsb_word16_be(i64) -> ptr
        let bsb_word16_be = self.module.llvm_module().add_function(
            "bhc_bsb_word16_be",
            ptr_type.fn_type(&[i64_type.into()], false),
            None,
        );
        self.functions.insert(VarId::new(1000486), bsb_word16_be);
        // bhc_bsb_word32_be(i64) -> ptr
        let bsb_word32_be = self.module.llvm_module().add_function(
            "bhc_bsb_word32_be",
            ptr_type.fn_type(&[i64_type.into()], false),
            None,
        );
        self.functions.insert(VarId::new(1000487), bsb_word32_be);
        // bhc_bsb_word64_be(i64) -> ptr
        let bsb_word64_be = self.module.llvm_module().add_function(
            "bhc_bsb_word64_be",
            ptr_type.fn_type(&[i64_type.into()], false),
            None,
        );
        self.functions.insert(VarId::new(1000488), bsb_word64_be);
        // bhc_bsb_word16_le(i64) -> ptr
        let bsb_word16_le = self.module.llvm_module().add_function(
            "bhc_bsb_word16_le",
            ptr_type.fn_type(&[i64_type.into()], false),
            None,
        );
        self.functions.insert(VarId::new(1000489), bsb_word16_le);
        // bhc_bsb_word32_le(i64) -> ptr
        let bsb_word32_le = self.module.llvm_module().add_function(
            "bhc_bsb_word32_le",
            ptr_type.fn_type(&[i64_type.into()], false),
            None,
        );
        self.functions.insert(VarId::new(1000490), bsb_word32_le);
        // bhc_bsb_word64_le(i64) -> ptr
        let bsb_word64_le = self.module.llvm_module().add_function(
            "bhc_bsb_word64_le",
            ptr_type.fn_type(&[i64_type.into()], false),
            None,
        );
        self.functions.insert(VarId::new(1000491), bsb_word64_le);
        // bhc_bsb_word_hex(i64) -> ptr
        let bsb_word_hex = self.module.llvm_module().add_function(
            "bhc_bsb_word_hex",
            ptr_type.fn_type(&[i64_type.into()], false),
            None,
        );
        self.functions.insert(VarId::new(1000492), bsb_word_hex);
        // bhc_bsb_word8_hex_fixed(i64) -> ptr
        let bsb_word8_hex_fixed = self.module.llvm_module().add_function(
            "bhc_bsb_word8_hex_fixed",
            ptr_type.fn_type(&[i64_type.into()], false),
            None,
        );
        self.functions
            .insert(VarId::new(1000493), bsb_word8_hex_fixed);
        // Note: bhc_float_to_word32 and bhc_double_to_word64 are declared above
        // at VarIds 1000258 and 1000259 (in bytearray section).

        // ---- Exception RTS functions (VarId 1080-1091) ----
        // bhc_throw(ptr) -> ptr (stores exception in TLS, returns sentinel null)
        let throw_fn = self.module.llvm_module().add_function(
            "bhc_throw",
            ptr_type.fn_type(&[ptr_type.into()], false),
            None,
        );
        self.functions.insert(VarId::new(1000080), throw_fn);
        // bhc_catch(action_fn, action_env, handler_fn, handler_env) -> ptr
        let catch_type = ptr_type.fn_type(
            &[
                ptr_type.into(),
                ptr_type.into(),
                ptr_type.into(),
                ptr_type.into(),
            ],
            false,
        );
        let catch_fn = self
            .module
            .llvm_module()
            .add_function("bhc_catch", catch_type, None);
        self.functions.insert(VarId::new(1000081), catch_fn);
        // bhc_evaluate(ptr) -> ptr
        let evaluate_fn = self.module.llvm_module().add_function(
            "bhc_evaluate",
            ptr_type.fn_type(&[ptr_type.into()], false),
            None,
        );
        self.functions.insert(VarId::new(1000082), evaluate_fn);
        // bhc_mask(fn_ptr, env_ptr) -> ptr (simple mask for mask_)
        let mask_type = ptr_type.fn_type(&[ptr_type.into(), ptr_type.into()], false);
        let mask_fn = self
            .module
            .llvm_module()
            .add_function("bhc_mask", mask_type, None);
        self.functions.insert(VarId::new(1000083), mask_fn);
        // bhc_unmask(fn_ptr, env_ptr) -> ptr
        let unmask_fn = self
            .module
            .llvm_module()
            .add_function("bhc_unmask", mask_type, None);
        self.functions.insert(VarId::new(1000084), unmask_fn);
        // bhc_mask_with_restore(action_fn, action_env) -> ptr (mask with restore closure)
        let mask_restore_fn =
            self.module
                .llvm_module()
                .add_function("bhc_mask_with_restore", mask_type, None);
        self.functions.insert(VarId::new(1000085), mask_restore_fn);
        // bhc_uninterruptible_mask(fn_ptr, env_ptr) -> ptr (simple uninterruptible mask)
        let uninterruptible_mask_fn =
            self.module
                .llvm_module()
                .add_function("bhc_uninterruptible_mask", mask_type, None);
        self.functions
            .insert(VarId::new(1000086), uninterruptible_mask_fn);
        // bhc_uninterruptible_mask_with_restore(action_fn, action_env) -> ptr
        let uninterruptible_mask_restore_fn = self.module.llvm_module().add_function(
            "bhc_uninterruptible_mask_with_restore",
            mask_type,
            None,
        );
        self.functions
            .insert(VarId::new(1000087), uninterruptible_mask_restore_fn);
        // bhc_get_masking_state() -> i64
        let get_mask_type = i64_type.fn_type(&[], false);
        let get_mask_fn =
            self.module
                .llvm_module()
                .add_function("bhc_get_masking_state", get_mask_type, None);
        self.functions.insert(VarId::new(1000824), get_mask_fn);
        // bhc_finally(action_fn, action_env, cleanup_fn, cleanup_env) -> ptr
        let finally_type = ptr_type.fn_type(
            &[
                ptr_type.into(),
                ptr_type.into(),
                ptr_type.into(),
                ptr_type.into(),
            ],
            false,
        );
        let finally_fn = self
            .module
            .llvm_module()
            .add_function("bhc_finally", finally_type, None);
        self.functions.insert(VarId::new(1000088), finally_fn);
        // bhc_on_exception(action_fn, action_env, handler_fn, handler_env) -> ptr
        let on_exception_type = ptr_type.fn_type(
            &[
                ptr_type.into(),
                ptr_type.into(),
                ptr_type.into(),
                ptr_type.into(),
            ],
            false,
        );
        let on_exception_fn =
            self.module
                .llvm_module()
                .add_function("bhc_on_exception", on_exception_type, None);
        self.functions.insert(VarId::new(1000089), on_exception_fn);
        // bhc_bracket(acquire_fn, acquire_env, release_fn, release_env, use_fn, use_env) -> ptr
        let bracket_type = ptr_type.fn_type(
            &[
                ptr_type.into(),
                ptr_type.into(),
                ptr_type.into(),
                ptr_type.into(),
                ptr_type.into(),
                ptr_type.into(),
            ],
            false,
        );
        let bracket_fn = self
            .module
            .llvm_module()
            .add_function("bhc_bracket", bracket_type, None);
        self.functions.insert(VarId::new(1000825), bracket_fn);

        // ---- SomeException / typed catch RTS functions (VarId 1000700-1000704) ----
        // bhc_make_some_exception(i64, ptr) -> ptr
        let make_exc_type = ptr_type.fn_type(&[i64_type.into(), ptr_type.into()], false);
        let make_exc_fn =
            self.module
                .llvm_module()
                .add_function("bhc_make_some_exception", make_exc_type, None);
        self.functions.insert(VarId::new(1000700), make_exc_fn);
        // bhc_exc_get_tag(ptr) -> i64
        let get_tag_type = i64_type.fn_type(&[ptr_type.into()], false);
        let get_tag_fn =
            self.module
                .llvm_module()
                .add_function("bhc_exc_get_tag", get_tag_type, None);
        self.functions.insert(VarId::new(1000701), get_tag_fn);
        // bhc_exc_get_payload(ptr) -> ptr
        let get_payload_fn = self.module.llvm_module().add_function(
            "bhc_exc_get_payload",
            ptr_type.fn_type(&[ptr_type.into()], false),
            None,
        );
        self.functions.insert(VarId::new(1000702), get_payload_fn);
        // bhc_catch_typed(action_fn, action_env, handler_fn, handler_env, type_tag: i64) -> ptr
        let catch_typed_type = ptr_type.fn_type(
            &[
                ptr_type.into(),
                ptr_type.into(),
                ptr_type.into(),
                ptr_type.into(),
                i64_type.into(),
            ],
            false,
        );
        let catch_typed_fn =
            self.module
                .llvm_module()
                .add_function("bhc_catch_typed", catch_typed_type, None);
        self.functions.insert(VarId::new(1000703), catch_typed_fn);
        // bhc_show_exception(ptr) -> ptr
        let show_exc_fn = self.module.llvm_module().add_function(
            "bhc_show_exception",
            ptr_type.fn_type(&[ptr_type.into()], false),
            None,
        );
        self.functions.insert(VarId::new(1000704), show_exc_fn);

        // ---- Additional IO RTS functions (VarId 1300-1314) ----
        // NOTE: Previously used VarId 1085-1099 which collided with exception
        // functions (finally=1088, on_exception=1089, bracket=1090). Moved to
        // 1300+ range to avoid overwriting those entries.
        // bhc_hGetContents(*i8) -> *i8
        let h_get_contents =
            self.module
                .llvm_module()
                .add_function("bhc_hGetContents", ptr_to_ptr, None);
        self.functions.insert(VarId::new(1000300), h_get_contents);
        // bhc_hSeek(*i8, i32, i64) -> void
        let h_seek_type =
            void_type.fn_type(&[ptr_type.into(), i32_type.into(), i64_type.into()], false);
        let h_seek = self
            .module
            .llvm_module()
            .add_function("bhc_hSeek", h_seek_type, None);
        self.functions.insert(VarId::new(1000301), h_seek);
        // bhc_hTell(*i8) -> i64
        let h_tell = self.module.llvm_module().add_function(
            "bhc_hTell",
            i64_type.fn_type(&[ptr_type.into()], false),
            None,
        );
        self.functions.insert(VarId::new(1000302), h_tell);
        // bhc_hIsOpen(*i8) -> i32
        let h_is_open = self
            .module
            .llvm_module()
            .add_function("bhc_hIsOpen", ptr_to_i32, None);
        self.functions.insert(VarId::new(1000303), h_is_open);
        // bhc_hIsClosed(*i8) -> i32
        let h_is_closed = self
            .module
            .llvm_module()
            .add_function("bhc_hIsClosed", ptr_to_i32, None);
        self.functions.insert(VarId::new(1000304), h_is_closed);
        // bhc_hIsReadable(*i8) -> i32
        let h_is_readable =
            self.module
                .llvm_module()
                .add_function("bhc_hIsReadable", ptr_to_i32, None);
        self.functions.insert(VarId::new(1000305), h_is_readable);
        // bhc_hIsWritable(*i8) -> i32
        let h_is_writable =
            self.module
                .llvm_module()
                .add_function("bhc_hIsWritable", ptr_to_i32, None);
        self.functions.insert(VarId::new(1000306), h_is_writable);
        // bhc_hIsSeekable(*i8) -> i32
        let h_is_seekable =
            self.module
                .llvm_module()
                .add_function("bhc_hIsSeekable", ptr_to_i32, None);
        self.functions.insert(VarId::new(1000307), h_is_seekable);
        // bhc_hSetBuffering(*i8, i32) -> void
        let h_set_buf_type = void_type.fn_type(&[ptr_type.into(), i32_type.into()], false);
        let h_set_buffering =
            self.module
                .llvm_module()
                .add_function("bhc_hSetBuffering", h_set_buf_type, None);
        self.functions.insert(VarId::new(1000308), h_set_buffering);
        // bhc_hGetBuffering(*i8) -> i32
        let h_get_buffering =
            self.module
                .llvm_module()
                .add_function("bhc_hGetBuffering", ptr_to_i32, None);
        self.functions.insert(VarId::new(1000309), h_get_buffering);
        // bhc_hPutChar(*i8, i32) -> void
        let h_put_char_type = void_type.fn_type(&[ptr_type.into(), i32_type.into()], false);
        let h_put_char =
            self.module
                .llvm_module()
                .add_function("bhc_hPutChar", h_put_char_type, None);
        self.functions.insert(VarId::new(1000310), h_put_char);
        // bhc_create_directory(*i8) -> void
        let create_dir =
            self.module
                .llvm_module()
                .add_function("bhc_create_directory", ptr_to_void, None);
        self.functions.insert(VarId::new(1000311), create_dir);
        // bhc_remove_file(*i8) -> void
        let remove_file =
            self.module
                .llvm_module()
                .add_function("bhc_remove_file", ptr_to_void, None);
        self.functions.insert(VarId::new(1000312), remove_file);
        // bhc_list_directory(*i8) -> *i8
        let list_dir =
            self.module
                .llvm_module()
                .add_function("bhc_list_directory", ptr_to_ptr, None);
        self.functions.insert(VarId::new(1000313), list_dir);
        // bhc_lookupEnv(*i8) -> *i8
        let lookup_env = self
            .module
            .llvm_module()
            .add_function("bhc_lookupEnv", ptr_to_ptr, None);
        self.functions.insert(VarId::new(1000314), lookup_env);
        // bhc_get_args() -> *i8 (returns cons-list of C strings)
        let get_args = self
            .module
            .llvm_module()
            .add_function("bhc_get_args", void_to_ptr, None);
        self.functions.insert(VarId::new(1000315), get_args);
        // bhc_get_prog_name() -> *i8
        let get_prog_name =
            self.module
                .llvm_module()
                .add_function("bhc_get_prog_name", void_to_ptr, None);
        self.functions.insert(VarId::new(1000316), get_prog_name);
        // bhc_get_current_directory() -> *i8
        let get_cur_dir =
            self.module
                .llvm_module()
                .add_function("bhc_get_current_directory", void_to_ptr, None);
        self.functions.insert(VarId::new(1000317), get_cur_dir);

        // E.19: System.FilePath + System.Directory FFI functions
        let ptr_ptr_to_ptr_type =
            i8_ptr_type.fn_type(&[i8_ptr_type.into(), i8_ptr_type.into()], false);
        // FilePath: </> (combine two paths)
        let combine_fn =
            self.module
                .llvm_module()
                .add_function("bhc_combine", ptr_ptr_to_ptr_type, None);
        self.functions.insert(VarId::new(1000534), combine_fn);
        // FilePath: String -> String (5 functions)
        let take_file_name =
            self.module
                .llvm_module()
                .add_function("bhc_take_file_name", ptr_to_ptr, None);
        self.functions.insert(VarId::new(1000520), take_file_name);
        let take_directory =
            self.module
                .llvm_module()
                .add_function("bhc_take_directory", ptr_to_ptr, None);
        self.functions.insert(VarId::new(1000521), take_directory);
        let take_extension =
            self.module
                .llvm_module()
                .add_function("bhc_take_extension", ptr_to_ptr, None);
        self.functions.insert(VarId::new(1000522), take_extension);
        let drop_extension =
            self.module
                .llvm_module()
                .add_function("bhc_drop_extension", ptr_to_ptr, None);
        self.functions.insert(VarId::new(1000523), drop_extension);
        let take_base_name =
            self.module
                .llvm_module()
                .add_function("bhc_take_base_name", ptr_to_ptr, None);
        self.functions.insert(VarId::new(1000524), take_base_name);
        // FilePath: String -> String -> String
        let replace_extension = self.module.llvm_module().add_function(
            "bhc_replace_extension",
            ptr_ptr_to_ptr_type,
            None,
        );
        self.functions
            .insert(VarId::new(1000525), replace_extension);
        // FilePath: String -> Bool (i32)
        let is_absolute =
            self.module
                .llvm_module()
                .add_function("bhc_is_absolute", ptr_to_i32, None);
        self.functions.insert(VarId::new(1000526), is_absolute);
        let is_relative =
            self.module
                .llvm_module()
                .add_function("bhc_is_relative", ptr_to_i32, None);
        self.functions.insert(VarId::new(1000527), is_relative);
        let has_extension =
            self.module
                .llvm_module()
                .add_function("bhc_has_extension", ptr_to_i32, None);
        self.functions.insert(VarId::new(1000528), has_extension);
        // Directory: String -> IO ()
        let set_cur_dir =
            self.module
                .llvm_module()
                .add_function("bhc_set_current_directory", ptr_to_void, None);
        self.functions.insert(VarId::new(1000529), set_cur_dir);
        let remove_dir =
            self.module
                .llvm_module()
                .add_function("bhc_remove_directory", ptr_to_void, None);
        self.functions.insert(VarId::new(1000530), remove_dir);
        // Directory: String -> String -> IO ()
        let rename_file =
            self.module
                .llvm_module()
                .add_function("bhc_rename_file", ptr_ptr_to_void, None);
        self.functions.insert(VarId::new(1000531), rename_file);
        let copy_file =
            self.module
                .llvm_module()
                .add_function("bhc_copy_file", ptr_ptr_to_void, None);
        self.functions.insert(VarId::new(1000532), copy_file);

        // E.25: String read functions
        // bhc_read_int(ptr) -> i64  (read :: String -> Int)
        let ptr_to_i64 = i64_type.fn_type(&[i8_ptr_type.into()], false);
        let read_int = self
            .module
            .llvm_module()
            .add_function("bhc_read_int", ptr_to_i64, None);
        self.functions.insert(VarId::new(1000540), read_int);
        // bhc_try_read_int(ptr) -> ptr  (readMaybe :: String -> Maybe Int)
        let try_read_int =
            self.module
                .llvm_module()
                .add_function("bhc_try_read_int", ptr_to_ptr, None);
        self.functions.insert(VarId::new(1000541), try_read_int);

        // ---- Rational RTS functions (VarId 1000900-1000915) ----
        // bhc_rational_make(i64, i64) -> ptr
        let rat_make = self.module.llvm_module().add_function(
            "bhc_rational_make",
            ptr_type.fn_type(&[i64_type.into(), i64_type.into()], false),
            None,
        );
        self.functions.insert(VarId::new(1000900), rat_make);
        // bhc_rational_numerator(ptr) -> i64
        let rat_num = self.module.llvm_module().add_function(
            "bhc_rational_numerator",
            i64_type.fn_type(&[ptr_type.into()], false),
            None,
        );
        self.functions.insert(VarId::new(1000901), rat_num);
        // bhc_rational_denominator(ptr) -> i64
        let rat_den = self.module.llvm_module().add_function(
            "bhc_rational_denominator",
            i64_type.fn_type(&[ptr_type.into()], false),
            None,
        );
        self.functions.insert(VarId::new(1000902), rat_den);
        // bhc_rational_add(ptr, ptr) -> ptr
        let rat_add = self.module.llvm_module().add_function(
            "bhc_rational_add",
            ptr_type.fn_type(&[ptr_type.into(), ptr_type.into()], false),
            None,
        );
        self.functions.insert(VarId::new(1000903), rat_add);
        // bhc_rational_sub(ptr, ptr) -> ptr
        let rat_sub = self.module.llvm_module().add_function(
            "bhc_rational_sub",
            ptr_type.fn_type(&[ptr_type.into(), ptr_type.into()], false),
            None,
        );
        self.functions.insert(VarId::new(1000904), rat_sub);
        // bhc_rational_mul(ptr, ptr) -> ptr
        let rat_mul = self.module.llvm_module().add_function(
            "bhc_rational_mul",
            ptr_type.fn_type(&[ptr_type.into(), ptr_type.into()], false),
            None,
        );
        self.functions.insert(VarId::new(1000905), rat_mul);
        // bhc_rational_div(ptr, ptr) -> ptr
        let rat_div = self.module.llvm_module().add_function(
            "bhc_rational_div",
            ptr_type.fn_type(&[ptr_type.into(), ptr_type.into()], false),
            None,
        );
        self.functions.insert(VarId::new(1000906), rat_div);
        // bhc_rational_negate(ptr) -> ptr
        let rat_neg = self.module.llvm_module().add_function(
            "bhc_rational_negate",
            ptr_type.fn_type(&[ptr_type.into()], false),
            None,
        );
        self.functions.insert(VarId::new(1000907), rat_neg);
        // bhc_rational_abs(ptr) -> ptr
        let rat_abs = self.module.llvm_module().add_function(
            "bhc_rational_abs",
            ptr_type.fn_type(&[ptr_type.into()], false),
            None,
        );
        self.functions.insert(VarId::new(1000908), rat_abs);
        // bhc_rational_signum(ptr) -> ptr
        let rat_signum = self.module.llvm_module().add_function(
            "bhc_rational_signum",
            ptr_type.fn_type(&[ptr_type.into()], false),
            None,
        );
        self.functions.insert(VarId::new(1000909), rat_signum);
        // bhc_rational_eq(ptr, ptr) -> i64
        let rat_eq = self.module.llvm_module().add_function(
            "bhc_rational_eq",
            i64_type.fn_type(&[ptr_type.into(), ptr_type.into()], false),
            None,
        );
        self.functions.insert(VarId::new(1000910), rat_eq);
        // bhc_rational_compare(ptr, ptr) -> i32
        let rat_cmp = self.module.llvm_module().add_function(
            "bhc_rational_compare",
            i32_type.fn_type(&[ptr_type.into(), ptr_type.into()], false),
            None,
        );
        self.functions.insert(VarId::new(1000911), rat_cmp);
        // bhc_rational_from_int(i64) -> ptr
        let rat_from_int = self.module.llvm_module().add_function(
            "bhc_rational_from_int",
            ptr_type.fn_type(&[i64_type.into()], false),
            None,
        );
        self.functions.insert(VarId::new(1000912), rat_from_int);
        // bhc_rational_to_double(ptr) -> f64
        let rat_to_dbl = self.module.llvm_module().add_function(
            "bhc_rational_to_double",
            f64_type.fn_type(&[ptr_type.into()], false),
            None,
        );
        self.functions.insert(VarId::new(1000913), rat_to_dbl);
        // bhc_rational_recip(ptr) -> ptr
        let rat_recip = self.module.llvm_module().add_function(
            "bhc_rational_recip",
            ptr_type.fn_type(&[ptr_type.into()], false),
            None,
        );
        self.functions.insert(VarId::new(1000914), rat_recip);
        // bhc_rational_show(ptr) -> ptr
        let rat_show = self.module.llvm_module().add_function(
            "bhc_rational_show",
            ptr_type.fn_type(&[ptr_type.into()], false),
            None,
        );
        self.functions.insert(VarId::new(1000915), rat_show);
    }

    // ========================================================================
    // Foreign Import Support
    // ========================================================================

    /// Declare foreign imports: create C function declarations and BHC wrapper functions.
    ///
    /// For each foreign import:
    /// 1. Declare the C function with its native signature
    /// 2. Create a BHC wrapper that follows the uniform calling convention
    ///    (env_ptr + ptr args, returns ptr)
    /// 3. Register the wrapper in `self.functions` keyed by the foreign import's VarId
    fn declare_foreign_imports(&mut self, foreign_imports: &[ForeignImport]) -> CodegenResult<()> {
        for fi in foreign_imports {
            self.declare_single_foreign_import(fi)?;
        }
        Ok(())
    }

    /// Walk a Haskell type to extract the C parameter types and return type.
    ///
    /// Unwraps `IO a` to get the return type `a`.
    /// Collects function arrow parameter types.
    fn extract_ffi_signature(&self, ty: &Ty) -> (Vec<FfiType>, FfiType) {
        let mut params = Vec::new();
        let mut current = ty;

        // Unwrap foralls
        while let Ty::Forall(_, body) = current {
            current = body;
        }

        // Collect parameter types from function arrows
        while let Ty::Fun(arg, ret) = current {
            params.push(Self::haskell_type_to_ffi(arg));
            current = ret;
        }

        // The return type: unwrap IO if present
        let ret_type = Self::unwrap_io_type(current);

        (params, ret_type)
    }

    /// Map a Haskell type to an FFI type for C interop.
    fn haskell_type_to_ffi(ty: &Ty) -> FfiType {
        match ty {
            Ty::Con(con) => match con.name.as_str() {
                "Int" | "CInt" | "CLong" | "Int64" | "CSize" | "CPtrdiff" => FfiType::Int64,
                "Int32" | "CShort" => FfiType::Int32,
                "Int8" | "CChar" | "CSChar" => FfiType::Int8,
                "Word" | "CUInt" | "CULong" | "Word64" => FfiType::Int64,
                "Word32" | "CUShort" => FfiType::Int32,
                "Word8" | "CUChar" => FfiType::Int8,
                "Double" | "CDouble" => FfiType::Double,
                "Float" | "CFloat" => FfiType::Float,
                "Char" => FfiType::Int64, // BHC boxes Char as i64
                "Bool" => FfiType::Int64,
                "()" => FfiType::Void,
                _ => FfiType::Ptr, // Unknown types passed as pointers
            },
            Ty::Prim(prim) => {
                use bhc_types::PrimTy;
                match prim {
                    PrimTy::I32 => FfiType::Int32,
                    PrimTy::I64 => FfiType::Int64,
                    PrimTy::U32 => FfiType::Int32,
                    PrimTy::U64 => FfiType::Int64,
                    PrimTy::F32 => FfiType::Float,
                    PrimTy::F64 => FfiType::Double,
                    PrimTy::Char => FfiType::Int64,
                    PrimTy::Addr => FfiType::Ptr,
                }
            }
            Ty::App(f, _arg) => {
                // Check for Ptr a, FunPtr a, StablePtr a, etc.
                if let Ty::Con(con) = f.as_ref() {
                    match con.name.as_str() {
                        "Ptr" | "FunPtr" | "StablePtr" | "ForeignPtr" => FfiType::Ptr,
                        "IO" => {
                            // IO a -> just use the inner type
                            Self::haskell_type_to_ffi(_arg)
                        }
                        _ => FfiType::Ptr,
                    }
                } else {
                    FfiType::Ptr
                }
            }
            _ => FfiType::Ptr,
        }
    }

    /// Unwrap IO type: `IO a` -> `a`, or just return the type unchanged.
    fn unwrap_io_type(ty: &Ty) -> FfiType {
        match ty {
            Ty::App(f, arg) => {
                if let Ty::Con(con) = f.as_ref() {
                    if con.name.as_str() == "IO" {
                        return Self::haskell_type_to_ffi(arg);
                    }
                }
                Self::haskell_type_to_ffi(ty)
            }
            Ty::Con(con) if con.name.as_str() == "IO" => {
                // bare IO without type arg — treat as IO ()
                FfiType::Void
            }
            _ => Self::haskell_type_to_ffi(ty),
        }
    }

    /// Convert an FfiType to an LLVM type.
    fn ffi_type_to_llvm(&self, ffi_ty: &FfiType) -> Option<BasicTypeEnum<'ctx>> {
        let tm = self.type_mapper();
        match ffi_ty {
            FfiType::Int8 => Some(self.llvm_ctx.i8_type().into()),
            FfiType::Int32 => Some(tm.i32_type().into()),
            FfiType::Int64 => Some(tm.i64_type().into()),
            FfiType::Float => Some(tm.f32_type().into()),
            FfiType::Double => Some(tm.f64_type().into()),
            FfiType::Ptr => Some(tm.ptr_type().into()),
            FfiType::Void => None,
        }
    }

    /// Declare a single foreign import: C declaration + BHC wrapper.
    fn declare_single_foreign_import(&mut self, fi: &ForeignImport) -> CodegenResult<()> {
        let (param_types, ret_type) = self.extract_ffi_signature(&fi.var.ty);
        let tm = self.type_mapper();
        let ptr_type = tm.ptr_type();
        let i64_type = tm.i64_type();

        // 1. Build the C function's LLVM type
        let c_param_types: Vec<inkwell::types::BasicMetadataTypeEnum<'ctx>> = param_types
            .iter()
            .filter_map(|t| self.ffi_type_to_llvm(t).map(|t| t.into()))
            .collect();

        let c_fn_type = match self.ffi_type_to_llvm(&ret_type) {
            Some(ret_llvm) => ret_llvm.fn_type(&c_param_types, false),
            None => self.llvm_ctx.void_type().fn_type(&c_param_types, false),
        };

        // Declare the C function
        let c_fn = self.module.llvm_module().add_function(
            fi.c_name.as_str(),
            c_fn_type,
            Some(inkwell::module::Linkage::External),
        );

        // 2. Create the BHC wrapper function
        // Wrapper signature: (env_ptr, arg1, arg2, ...) -> ptr
        // where all args are ptr (boxed values)
        let wrapper_param_count = param_types.len();
        let mut wrapper_param_types: Vec<inkwell::types::BasicMetadataTypeEnum<'ctx>> = Vec::new();
        wrapper_param_types.push(ptr_type.into()); // env pointer (unused)
        for _ in 0..wrapper_param_count {
            wrapper_param_types.push(ptr_type.into());
        }

        let wrapper_fn_type = ptr_type.fn_type(&wrapper_param_types, false);
        let wrapper_name = format!("$ffi_wrapper_{}", fi.haskell_name.as_str());
        let wrapper_fn =
            self.module
                .llvm_module()
                .add_function(&wrapper_name, wrapper_fn_type, None);

        // Build the wrapper body
        let entry_bb = self.llvm_ctx.append_basic_block(wrapper_fn, "entry");
        self.builder().position_at_end(entry_bb);

        // Unbox each argument from ptr to native type and call C function
        let mut c_args: Vec<inkwell::values::BasicMetadataValueEnum<'ctx>> = Vec::new();
        for (i, param_ty) in param_types.iter().enumerate() {
            // Parameter i+1 (0 is the env pointer)
            let boxed_arg = wrapper_fn
                .get_nth_param((i + 1) as u32)
                .unwrap()
                .into_pointer_value();

            let unboxed = match param_ty {
                FfiType::Int64 | FfiType::Int32 | FfiType::Int8 => {
                    // Unbox: ptr_to_int
                    let int_val = self
                        .builder()
                        .build_ptr_to_int(boxed_arg, i64_type, &format!("unbox_arg_{i}"))
                        .map_err(|e| {
                            CodegenError::Internal(format!("FFI unbox int failed: {e:?}"))
                        })?;
                    match param_ty {
                        FfiType::Int32 => {
                            let truncated = self
                                .builder()
                                .build_int_truncate(
                                    int_val,
                                    self.type_mapper().i32_type(),
                                    &format!("trunc_arg_{i}"),
                                )
                                .map_err(|e| {
                                    CodegenError::Internal(format!("FFI trunc failed: {e:?}"))
                                })?;
                            truncated.into()
                        }
                        FfiType::Int8 => {
                            let truncated = self
                                .builder()
                                .build_int_truncate(
                                    int_val,
                                    self.llvm_ctx.i8_type(),
                                    &format!("trunc_arg_{i}"),
                                )
                                .map_err(|e| {
                                    CodegenError::Internal(format!("FFI trunc failed: {e:?}"))
                                })?;
                            truncated.into()
                        }
                        _ => int_val.into(), // Int64
                    }
                }
                FfiType::Double => {
                    // Unbox: ptr -> i64 -> f64 (bitcast)
                    let int_val = self
                        .builder()
                        .build_ptr_to_int(boxed_arg, i64_type, &format!("unbox_arg_{i}_bits"))
                        .map_err(|e| {
                            CodegenError::Internal(format!("FFI unbox double failed: {e:?}"))
                        })?;
                    let f64_val = self
                        .builder()
                        .build_bit_cast(
                            int_val,
                            self.type_mapper().f64_type(),
                            &format!("unbox_arg_{i}_f64"),
                        )
                        .map_err(|e| {
                            CodegenError::Internal(format!("FFI bitcast failed: {e:?}"))
                        })?;
                    f64_val.into()
                }
                FfiType::Float => {
                    // Unbox: ptr -> i64 -> f64 -> f32 (truncate)
                    let int_val = self
                        .builder()
                        .build_ptr_to_int(boxed_arg, i64_type, &format!("unbox_arg_{i}_bits"))
                        .map_err(|e| {
                            CodegenError::Internal(format!("FFI unbox float failed: {e:?}"))
                        })?;
                    let f64_val = self
                        .builder()
                        .build_bit_cast(
                            int_val,
                            self.type_mapper().f64_type(),
                            &format!("unbox_arg_{i}_f64"),
                        )
                        .map_err(|e| CodegenError::Internal(format!("FFI bitcast failed: {e:?}")))?
                        .into_float_value();
                    let f32_val = self
                        .builder()
                        .build_float_trunc(
                            f64_val,
                            self.type_mapper().f32_type(),
                            &format!("unbox_arg_{i}_f32"),
                        )
                        .map_err(|e| CodegenError::Internal(format!("FFI ftrunc failed: {e:?}")))?;
                    f32_val.into()
                }
                FfiType::Ptr => {
                    // Pass pointer through as-is
                    boxed_arg.into()
                }
                FfiType::Void => continue, // Shouldn't happen for a parameter
            };
            c_args.push(unboxed);
        }

        // Call the C function
        let c_call = self
            .builder()
            .build_call(c_fn, &c_args, &format!("ffi_call_{}", fi.c_name.as_str()))
            .map_err(|e| CodegenError::Internal(format!("FFI call failed: {e:?}")))?;

        // Box the return value
        let boxed_result: PointerValue<'ctx> = match ret_type {
            FfiType::Int64 | FfiType::Int32 | FfiType::Int8 => {
                let ret_val = c_call
                    .try_as_basic_value()
                    .basic()
                    .ok_or_else(|| {
                        CodegenError::Internal(
                            "FFI call returned void but expected int".to_string(),
                        )
                    })?
                    .into_int_value();
                // Extend to i64 if needed, then box
                let i64_val = if ret_val.get_type().get_bit_width() < 64 {
                    self.builder()
                        .build_int_s_extend(ret_val, i64_type, "box_extend")
                        .map_err(|e| CodegenError::Internal(format!("FFI sext failed: {e:?}")))?
                } else {
                    ret_val
                };
                self.builder()
                    .build_int_to_ptr(i64_val, ptr_type, "box_ret")
                    .map_err(|e| CodegenError::Internal(format!("FFI box int failed: {e:?}")))?
            }
            FfiType::Double => {
                let ret_val = c_call
                    .try_as_basic_value()
                    .basic()
                    .ok_or_else(|| {
                        CodegenError::Internal(
                            "FFI call returned void but expected double".to_string(),
                        )
                    })?
                    .into_float_value();
                // Box: f64 -> i64 bits -> ptr
                let bits = self
                    .builder()
                    .build_bit_cast(ret_val, i64_type, "box_ret_bits")
                    .map_err(|e| {
                        CodegenError::Internal(format!("FFI box double bitcast failed: {e:?}"))
                    })?
                    .into_int_value();
                self.builder()
                    .build_int_to_ptr(bits, ptr_type, "box_ret")
                    .map_err(|e| CodegenError::Internal(format!("FFI box double failed: {e:?}")))?
            }
            FfiType::Float => {
                let ret_val = c_call
                    .try_as_basic_value()
                    .basic()
                    .ok_or_else(|| {
                        CodegenError::Internal(
                            "FFI call returned void but expected float".to_string(),
                        )
                    })?
                    .into_float_value();
                // Extend to f64, then box
                let f64_val = self
                    .builder()
                    .build_float_ext(ret_val, self.type_mapper().f64_type(), "box_ret_ext")
                    .map_err(|e| {
                        CodegenError::Internal(format!("FFI box float ext failed: {e:?}"))
                    })?;
                let bits = self
                    .builder()
                    .build_bit_cast(f64_val, i64_type, "box_ret_bits")
                    .map_err(|e| {
                        CodegenError::Internal(format!("FFI box float bitcast failed: {e:?}"))
                    })?
                    .into_int_value();
                self.builder()
                    .build_int_to_ptr(bits, ptr_type, "box_ret")
                    .map_err(|e| CodegenError::Internal(format!("FFI box float failed: {e:?}")))?
            }
            FfiType::Ptr => {
                let ret_val = c_call
                    .try_as_basic_value()
                    .basic()
                    .ok_or_else(|| {
                        CodegenError::Internal(
                            "FFI call returned void but expected ptr".to_string(),
                        )
                    })?
                    .into_pointer_value();
                ret_val
            }
            FfiType::Void => {
                // Return unit (null pointer)
                ptr_type.const_null()
            }
        };

        self.builder()
            .build_return(Some(&boxed_result))
            .map_err(|e| CodegenError::Internal(format!("FFI return failed: {e:?}")))?;

        // 3. Register the wrapper in the functions map
        self.functions.insert(fi.var.id, wrapper_fn);

        Ok(())
    }

    // ========================================================================
    // ADT (Algebraic Data Type) Value Representation
    // ========================================================================
    //
    // ADT values are represented as heap-allocated structs:
    //
    //   struct ADTValue {
    //       i64 tag;          // Constructor tag (0, 1, 2, ...)
    //       ptr fields[];     // Variable-length array of field pointers
    //   }
    //
    // For example, `Just 42` would be:
    //   { tag: 1, fields: [ptr_to_42] }
    //
    // And `Nothing` would be:
    //   { tag: 0, fields: [] }
    // ========================================================================

    /// Get the LLVM struct type for an ADT value with the given arity.
    fn adt_type(&self, arity: u32) -> inkwell::types::StructType<'ctx> {
        let tm = self.type_mapper();
        let tag_type = tm.i64_type();
        let ptr_type = tm.ptr_type();

        // Create array type for fields
        let fields_type = ptr_type.array_type(arity);

        // Struct: { i64 tag, [arity x ptr] fields }
        self.llvm_ctx
            .struct_type(&[tag_type.into(), fields_type.into()], false)
    }

    /// Allocate an ADT value with the given tag and arity.
    fn alloc_adt(&self, tag: u32, arity: u32) -> CodegenResult<PointerValue<'ctx>> {
        let tm = self.type_mapper();
        let adt_ty = self.adt_type(arity);

        // Calculate size: sizeof(i64) + arity * sizeof(ptr)
        let size = 8 + (arity as u64) * 8;

        // Call bhc_alloc
        let alloc_fn = self
            .functions
            .get(&VarId::new(1000005))
            .ok_or_else(|| CodegenError::Internal("bhc_alloc not declared".to_string()))?;

        let size_val = tm.i64_type().const_int(size, false);
        let raw_ptr = self
            .builder()
            .build_call(*alloc_fn, &[size_val.into()], "adt_alloc")
            .map_err(|e| CodegenError::Internal(format!("failed to call bhc_alloc: {:?}", e)))?
            .try_as_basic_value()
            .basic()
            .ok_or_else(|| CodegenError::Internal("bhc_alloc returned void".to_string()))?;

        let ptr = raw_ptr.into_pointer_value();

        // Store the tag at offset 0
        let tag_ptr = self
            .builder()
            .build_struct_gep(adt_ty, ptr, 0, "tag_ptr")
            .map_err(|e| CodegenError::Internal(format!("failed to get tag ptr: {:?}", e)))?;

        let tag_val = tm.i64_type().const_int(tag as u64, false);
        self.builder()
            .build_store(tag_ptr, tag_val)
            .map_err(|e| CodegenError::Internal(format!("failed to store tag: {:?}", e)))?;

        Ok(ptr)
    }

    /// Store a field value into an ADT at the given index.
    fn store_adt_field(
        &self,
        adt_ptr: PointerValue<'ctx>,
        arity: u32,
        field_index: u32,
        value: BasicValueEnum<'ctx>,
    ) -> CodegenResult<()> {
        let adt_ty = self.adt_type(arity);
        let tm = self.type_mapper();

        // Get pointer to fields array
        let fields_ptr = self
            .builder()
            .build_struct_gep(adt_ty, adt_ptr, 1, "fields_ptr")
            .map_err(|e| CodegenError::Internal(format!("failed to get fields ptr: {:?}", e)))?;

        // Get pointer to specific field
        let field_ptr = unsafe {
            self.builder()
                .build_in_bounds_gep(
                    tm.ptr_type().array_type(arity),
                    fields_ptr,
                    &[
                        tm.i64_type().const_zero(),
                        tm.i64_type().const_int(field_index as u64, false),
                    ],
                    &format!("field_{}", field_index),
                )
                .map_err(|e| CodegenError::Internal(format!("failed to get field ptr: {:?}", e)))?
        };

        // Convert value to pointer if needed
        let ptr_val = self.value_to_ptr(value)?;
        self.builder()
            .build_store(field_ptr, ptr_val)
            .map_err(|e| CodegenError::Internal(format!("failed to store field: {:?}", e)))?;

        Ok(())
    }

    /// Convert a basic value to a pointer (boxing primitives if needed).
    fn value_to_ptr(&self, value: BasicValueEnum<'ctx>) -> CodegenResult<PointerValue<'ctx>> {
        match value {
            BasicValueEnum::PointerValue(p) => Ok(p),
            BasicValueEnum::IntValue(i) => {
                // Box the integer: cast to pointer
                self.builder()
                    .build_int_to_ptr(i, self.type_mapper().ptr_type(), "box_int")
                    .map_err(|e| CodegenError::Internal(format!("failed to box int: {:?}", e)))
            }
            BasicValueEnum::FloatValue(f) => {
                // Box the float: ensure f64, cast bits to i64, then to pointer
                let f64_val = if f.get_type() == self.type_mapper().f32_type() {
                    self.builder()
                        .build_float_ext(f, self.type_mapper().f64_type(), "f32_to_f64")
                        .map_err(|e| {
                            CodegenError::Internal(format!("failed to extend f32: {:?}", e))
                        })?
                } else {
                    f
                };
                let bits = self
                    .builder()
                    .build_bit_cast(f64_val, self.type_mapper().i64_type(), "float_bits")
                    .map_err(|e| CodegenError::Internal(format!("failed to cast float: {:?}", e)))?
                    .into_int_value();
                self.builder()
                    .build_int_to_ptr(bits, self.type_mapper().ptr_type(), "box_float")
                    .map_err(|e| CodegenError::Internal(format!("failed to box float: {:?}", e)))
            }
            _ => Err(CodegenError::Unsupported(
                "cannot box this value type".to_string(),
            )),
        }
    }

    /// Coerce a value to match a target type.
    /// Used to ensure PHI node operands have consistent types.
    fn coerce_to_type(
        &self,
        value: BasicValueEnum<'ctx>,
        target_type: inkwell::types::BasicTypeEnum<'ctx>,
    ) -> CodegenResult<BasicValueEnum<'ctx>> {
        let value_type = value.get_type();

        // If types already match, no coercion needed
        if value_type == target_type {
            return Ok(value);
        }

        let tm = self.type_mapper();

        match (value, target_type) {
            // Pointer to integer: unbox (ptr_to_int)
            (BasicValueEnum::PointerValue(p), inkwell::types::BasicTypeEnum::IntType(int_ty)) => {
                let int_val = self
                    .builder()
                    .build_ptr_to_int(p, int_ty, "coerce_ptr_to_int")
                    .map_err(|e| {
                        CodegenError::Internal(format!("failed to coerce ptr to int: {:?}", e))
                    })?;
                Ok(int_val.into())
            }
            // Integer to pointer: box (int_to_ptr)
            (BasicValueEnum::IntValue(i), inkwell::types::BasicTypeEnum::PointerType(_)) => {
                // First extend/truncate to i64 if needed, then convert to pointer
                let i64_val = if i.get_type().get_bit_width() < 64 {
                    self.builder()
                        .build_int_z_extend(i, tm.i64_type(), "extend_to_i64")
                        .map_err(|e| {
                            CodegenError::Internal(format!("failed to extend int: {:?}", e))
                        })?
                } else if i.get_type().get_bit_width() > 64 {
                    self.builder()
                        .build_int_truncate(i, tm.i64_type(), "truncate_to_i64")
                        .map_err(|e| {
                            CodegenError::Internal(format!("failed to truncate int: {:?}", e))
                        })?
                } else {
                    i
                };
                let ptr_val = self
                    .builder()
                    .build_int_to_ptr(i64_val, tm.ptr_type(), "coerce_int_to_ptr")
                    .map_err(|e| {
                        CodegenError::Internal(format!("failed to coerce int to ptr: {:?}", e))
                    })?;
                Ok(ptr_val.into())
            }
            // Pointer to float: unbox (ptr_to_int then bit_cast)
            (
                BasicValueEnum::PointerValue(p),
                inkwell::types::BasicTypeEnum::FloatType(float_ty),
            ) => {
                let bits = self
                    .builder()
                    .build_ptr_to_int(p, tm.i64_type(), "coerce_ptr_to_bits")
                    .map_err(|e| {
                        CodegenError::Internal(format!("failed to coerce ptr to bits: {:?}", e))
                    })?;
                // For f32, truncate to i32 first, then bit_cast
                let float_val = if float_ty == tm.f32_type() {
                    let bits32 = self
                        .builder()
                        .build_int_truncate(bits, tm.i32_type(), "truncate_bits_f32")
                        .map_err(|e| {
                            CodegenError::Internal(format!("failed to truncate bits: {:?}", e))
                        })?;
                    self.builder()
                        .build_bit_cast(bits32, float_ty, "coerce_to_f32")
                        .map_err(|e| {
                            CodegenError::Internal(format!("failed to coerce to f32: {:?}", e))
                        })?
                } else {
                    self.builder()
                        .build_bit_cast(bits, float_ty, "coerce_to_f64")
                        .map_err(|e| {
                            CodegenError::Internal(format!("failed to coerce to f64: {:?}", e))
                        })?
                };
                Ok(float_val)
            }
            // Float to pointer: box (extend f32→f64 if needed, bit_cast then int_to_ptr)
            (BasicValueEnum::FloatValue(f), inkwell::types::BasicTypeEnum::PointerType(_)) => {
                // Ensure f64 before bitcasting to i64 (f32 is only 32 bits)
                let f64_val = if f.get_type() == tm.f32_type() {
                    self.builder()
                        .build_float_ext(f, tm.f64_type(), "coerce_f32_to_f64")
                        .map_err(|e| {
                            CodegenError::Internal(format!("failed to extend f32: {:?}", e))
                        })?
                } else {
                    f
                };
                let bits = self
                    .builder()
                    .build_bit_cast(f64_val, tm.i64_type(), "float_to_bits")
                    .map_err(|e| {
                        CodegenError::Internal(format!("failed to cast float to bits: {:?}", e))
                    })?
                    .into_int_value();
                let ptr_val = self
                    .builder()
                    .build_int_to_ptr(bits, tm.ptr_type(), "coerce_float_to_ptr")
                    .map_err(|e| {
                        CodegenError::Internal(format!("failed to coerce float to ptr: {:?}", e))
                    })?;
                Ok(ptr_val.into())
            }
            // Integer width conversion
            (BasicValueEnum::IntValue(i), inkwell::types::BasicTypeEnum::IntType(int_ty)) => {
                let src_bits = i.get_type().get_bit_width();
                let dst_bits = int_ty.get_bit_width();
                let result = if src_bits < dst_bits {
                    self.builder()
                        .build_int_s_extend(i, int_ty, "sext")
                        .map_err(|e| {
                            CodegenError::Internal(format!("failed to sign extend: {:?}", e))
                        })?
                } else {
                    self.builder()
                        .build_int_truncate(i, int_ty, "trunc")
                        .map_err(|e| {
                            CodegenError::Internal(format!("failed to truncate: {:?}", e))
                        })?
                };
                Ok(result.into())
            }
            _ => {
                // Types don't match and we don't know how to coerce
                Err(CodegenError::Internal(format!(
                    "cannot coerce {:?} to {:?}",
                    value_type, target_type
                )))
            }
        }
    }

    /// Extract the tag from an ADT value.
    fn extract_adt_tag(&self, adt_ptr: PointerValue<'ctx>) -> CodegenResult<IntValue<'ctx>> {
        // We need to use a generic adt type for reading - use arity 0 since tag is always at offset 0
        let adt_ty = self.adt_type(0);

        let tag_ptr = self
            .builder()
            .build_struct_gep(adt_ty, adt_ptr, 0, "tag_ptr")
            .map_err(|e| CodegenError::Internal(format!("failed to get tag ptr: {:?}", e)))?;

        let tag = self
            .builder()
            .build_load(self.type_mapper().i64_type(), tag_ptr, "tag")
            .map_err(|e| CodegenError::Internal(format!("failed to load tag: {:?}", e)))?;

        Ok(tag.into_int_value())
    }

    /// Extract a boolean tag from either a tagged-int-as-pointer (0/1) or a Bool ADT.
    /// Comparison operators return tagged-int-as-pointer, while functions like `even`/`odd`
    /// return proper Bool ADT structs. This helper handles both representations.
    fn extract_bool_tag(&self, bool_ptr: PointerValue<'ctx>) -> CodegenResult<IntValue<'ctx>> {
        let tm = self.type_mapper();
        let i64_type = tm.i64_type();

        let current_fn = self
            .builder()
            .get_insert_block()
            .and_then(|b| b.get_parent())
            .ok_or_else(|| {
                CodegenError::Internal("extract_bool_tag: no current function".to_string())
            })?;

        let raw_int = self
            .builder()
            .build_ptr_to_int(bool_ptr, i64_type, "bool_raw")
            .map_err(|e| {
                CodegenError::Internal(format!("extract_bool_tag: ptr_to_int: {:?}", e))
            })?;

        // If raw value <= 1, it's a tagged-int-as-pointer (0=False, 1=True)
        // If raw value > 1, it's a heap pointer to a Bool ADT struct
        let is_tagged = self
            .builder()
            .build_int_compare(
                inkwell::IntPredicate::ULE,
                raw_int,
                i64_type.const_int(1, false),
                "is_tagged_bool",
            )
            .map_err(|e| CodegenError::Internal(format!("extract_bool_tag: cmp: {:?}", e)))?;

        let tagged_block = self.llvm_ctx.append_basic_block(current_fn, "bool_tagged");
        let adt_block = self.llvm_ctx.append_basic_block(current_fn, "bool_adt");
        let merge_block = self.llvm_ctx.append_basic_block(current_fn, "bool_merge");

        self.builder()
            .build_conditional_branch(is_tagged, tagged_block, adt_block)
            .map_err(|e| CodegenError::Internal(format!("extract_bool_tag: branch: {:?}", e)))?;

        // Tagged path: raw value IS the tag (0 or 1)
        self.builder().position_at_end(tagged_block);
        self.builder()
            .build_unconditional_branch(merge_block)
            .map_err(|e| CodegenError::Internal(format!("extract_bool_tag: branch: {:?}", e)))?;

        // ADT path: load tag from struct
        self.builder().position_at_end(adt_block);
        let adt_tag = self.extract_adt_tag(bool_ptr)?;
        let adt_end_block = self
            .builder()
            .get_insert_block()
            .ok_or_else(|| CodegenError::Internal("extract_bool_tag: no block".to_string()))?;
        self.builder()
            .build_unconditional_branch(merge_block)
            .map_err(|e| CodegenError::Internal(format!("extract_bool_tag: branch: {:?}", e)))?;

        // Merge: phi between tagged value and ADT tag
        self.builder().position_at_end(merge_block);
        let result = self
            .builder()
            .build_phi(i64_type, "bool_tag")
            .map_err(|e| CodegenError::Internal(format!("extract_bool_tag: phi: {:?}", e)))?;
        result.add_incoming(&[(&raw_int, tagged_block), (&adt_tag, adt_end_block)]);

        Ok(result.as_basic_value().into_int_value())
    }

    /// Extract a field from an ADT value.
    fn extract_adt_field(
        &self,
        adt_ptr: PointerValue<'ctx>,
        arity: u32,
        field_index: u32,
    ) -> CodegenResult<PointerValue<'ctx>> {
        let adt_ty = self.adt_type(arity);
        let tm = self.type_mapper();

        // Get pointer to fields array
        let fields_ptr = self
            .builder()
            .build_struct_gep(adt_ty, adt_ptr, 1, "fields_ptr")
            .map_err(|e| CodegenError::Internal(format!("failed to get fields ptr: {:?}", e)))?;

        // Get pointer to specific field
        let field_ptr = unsafe {
            self.builder()
                .build_in_bounds_gep(
                    tm.ptr_type().array_type(arity),
                    fields_ptr,
                    &[
                        tm.i64_type().const_zero(),
                        tm.i64_type().const_int(field_index as u64, false),
                    ],
                    &format!("field_ptr_{}", field_index),
                )
                .map_err(|e| CodegenError::Internal(format!("failed to get field ptr: {:?}", e)))?
        };

        // Load the field value (which is a pointer)
        let field_val = self
            .builder()
            .build_load(tm.ptr_type(), field_ptr, &format!("field_{}", field_index))
            .map_err(|e| CodegenError::Internal(format!("failed to load field: {:?}", e)))?;

        Ok(field_val.into_pointer_value())
    }

    /// Get the RTS function ID for a builtin name.
    fn rts_function_id(&self, name: &str) -> Option<VarId> {
        match name {
            "print" => Some(VarId::new(1000000)), // bhc_print_int_ln for Int
            "putStrLn" => Some(VarId::new(1000002)), // bhc_print_string_ln
            "putStr" => Some(VarId::new(1000004)), // bhc_print_string
            _ => None,
        }
    }

    // ========================================================================
    // Builtin Functions
    // ========================================================================
    //
    // These are common Haskell functions that we implement directly in LLVM
    // for performance (avoiding function call overhead) or because they need
    // special handling.
    // ========================================================================

    /// Check if a name is a builtin function and return its arity.
    fn builtin_info(&self, name: &str) -> Option<u32> {
        match name {
            // List operations
            "head" => Some(1),
            "tail" => Some(1),
            "null" => Some(1),
            "length" => Some(1),
            "take" => Some(2),
            "drop" => Some(2),
            "reverse" => Some(1),
            "append" | "++" => Some(2),
            "enumFromTo" => Some(2),
            "replicate" => Some(2),
            "sum" => Some(1),
            "product" => Some(1),
            "map" => Some(2),
            "filter" => Some(2),
            "foldr" => Some(3),
            "foldl" => Some(3),
            "foldl'" => Some(3),
            "zipWith" => Some(3),
            "zip" => Some(2),
            "last" => Some(1),
            "init" => Some(1),
            "!!" => Some(2),
            "concatMap" => Some(2),
            "concat" => Some(1),

            // Tuple operations
            "fst" => Some(1),
            "snd" => Some(1),
            "swap" => Some(1),
            "curry" => Some(3),
            "uncurry" => Some(2),

            // Maybe operations
            "fromJust" => Some(1),
            "isJust" => Some(1),
            "isNothing" => Some(1),
            "fromMaybe" => Some(2),
            "maybe" => Some(3),
            "listToMaybe" => Some(1),
            "maybeToList" => Some(1),
            "catMaybes" => Some(1),
            "mapMaybe" => Some(2),

            // Either operations
            "isLeft" => Some(1),
            "isRight" => Some(1),
            "either" => Some(3),
            "fromLeft" => Some(2),
            "fromRight" => Some(2),
            "lefts" => Some(1),
            "rights" => Some(1),
            "partitionEithers" => Some(1),

            // Control
            "guard" => Some(1),

            // Error / Exception handling
            "error" => Some(1),
            "undefined" => Some(0),
            "throw" | "throwIO" => Some(1),
            "catch" => Some(2),
            "try" => Some(1),
            "bracket" => Some(3),
            "finally" | "onException" => Some(2),
            "mask" | "mask_" | "uninterruptibleMask" | "uninterruptibleMask_" => Some(1),
            "getMaskingState" => Some(0),
            "toException" => Some(1),
            "fromException" => Some(1),
            "displayException" => Some(1),
            "userError" => Some(1),
            "ioError" => Some(1),

            // Misc
            "seq" => Some(2),
            "id" => Some(1),
            "const" => Some(2),
            "not" => Some(1),
            "otherwise" => Some(0),
            // E.63: DeepSeq stubs (no-ops in strict runtime)
            "rnf" => Some(1),
            "deepseq" => Some(2),
            // GHC.Generics from/to
            "from" => Some(1),
            "to" => Some(1),
            "force" => Some(1),

            // IO operations
            "putStrLn" => Some(1),
            "putStr" => Some(1),
            "putChar" => Some(1),
            "print" => Some(1),
            "getLine" => Some(0),

            // Monadic operations
            ">>=" => Some(2),
            ">>" => Some(2),
            "return" => Some(1),
            "pure" => Some(1),

            // Numeric / math operations
            "negate" => Some(1),
            "abs" => Some(1),
            "signum" => Some(1),
            "sqrt" => Some(1),
            "exp" => Some(1),
            "log" => Some(1),
            "sin" => Some(1),
            "cos" => Some(1),
            "tan" => Some(1),
            "asin" => Some(1),
            "acos" => Some(1),
            "atan" => Some(1),
            "atan2" => Some(2),
            "ceiling" => Some(1),
            "floor" => Some(1),
            "round" => Some(1),
            "truncate" => Some(1),
            "fromIntegral" => Some(1),
            "toInteger" => Some(1),
            "fromInteger" => Some(1),
            "compare" => Some(2),
            "even" => Some(1),
            "odd" => Some(1),
            "gcd" => Some(2),
            "lcm" => Some(2),
            "divMod" => Some(2),
            "quotRem" => Some(2),
            "%" => Some(2),
            "numerator" => Some(1),
            "denominator" => Some(1),
            "toRational" => Some(1),
            "fromRational" => Some(1),
            "recip" => Some(1),
            "newIORef" => Some(1),
            "readIORef" => Some(1),
            "writeIORef" => Some(2),
            "modifyIORef" => Some(2),
            "modifyIORef'" => Some(2),
            "atomicModifyIORef" => Some(2),
            "atomicModifyIORef'" => Some(2),

            // Character operations
            "ord" => Some(1),
            "chr" => Some(1),
            "isAlpha" => Some(1),
            "isAlphaNum" => Some(1),
            "isAscii" => Some(1),
            "isControl" => Some(1),
            "isDigit" => Some(1),
            "isHexDigit" => Some(1),
            "isLetter" => Some(1),
            "isLower" => Some(1),
            "isNumber" => Some(1),
            "isPrint" => Some(1),
            "isPunctuation" => Some(1),
            "isSpace" => Some(1),
            "isSymbol" => Some(1),
            "isUpper" => Some(1),
            "toLower" => Some(1),
            "toUpper" => Some(1),
            "digitToInt" => Some(1),
            "intToDigit" => Some(1),

            // Advanced list operations
            "scanl" => Some(3),
            "scanl'" => Some(3),
            "scanl1" => Some(2),
            "scanr" => Some(3),
            "scanr1" => Some(2),
            "find" => Some(2),
            "elem" => Some(2),
            "notElem" => Some(2),
            "lookup" => Some(2),
            "partition" => Some(2),
            "span" => Some(2),
            "lines" => Some(1),
            "unlines" => Some(1),
            "words" => Some(1),
            "unwords" => Some(1),
            "nub" => Some(1),
            "delete" => Some(2),
            "union" => Some(2),
            "intersect" => Some(2),
            "sort" => Some(1),
            "sortBy" => Some(2),
            "intercalate" => Some(2),
            "intersperse" => Some(2),
            "transpose" => Some(1),
            "group" => Some(1),
            "sortOn" => Some(2),
            "nubBy" => Some(2),
            "groupBy" => Some(2),
            "deleteBy" => Some(3),
            "unionBy" => Some(3),
            "intersectBy" => Some(3),
            "stripPrefix" => Some(2),
            "insert" => Some(2),
            "mapAccumL" => Some(3),
            "mapAccumR" => Some(3),
            "splitAt" => Some(2),
            "break" => Some(2),
            "any" => Some(2),
            "all" => Some(2),
            "and" => Some(1),
            "or" => Some(1),
            "maximum" => Some(1),
            "minimum" => Some(1),
            "maximumBy" => Some(2),
            "minimumBy" => Some(2),
            "elemIndex" => Some(2),
            "findIndex" => Some(2),
            "isPrefixOf" => Some(2),
            "isSuffixOf" => Some(2),
            "isInfixOf" => Some(2),
            "tails" => Some(1),
            "inits" => Some(1),
            "foldMap" => Some(2),
            "iterate" => Some(2),
            "unfoldr" => Some(2),
            "cycle" => Some(1),
            "repeat" => Some(1),
            "takeWhile" => Some(2),
            "dropWhile" => Some(2),
            "zipWith3" => Some(4),
            "zip3" => Some(3),
            "unzip" => Some(1),

            // IO & System operations
            "readFile" => Some(1),
            "writeFile" => Some(2),
            "appendFile" => Some(2),
            "openFile" => Some(2),
            "hClose" => Some(1),
            "hGetChar" => Some(1),
            "hGetLine" => Some(1),
            "hPutStr" => Some(2),
            "hPutStrLn" => Some(2),
            "hFlush" => Some(1),
            "hIsEOF" => Some(1),
            "hSetBuffering" => Some(2),
            "stdin" => Some(0),
            "stdout" => Some(0),
            "stderr" => Some(0),
            "doesFileExist" => Some(1),
            "doesDirectoryExist" => Some(1),
            "removeFile" => Some(1),
            "getArgs" => Some(0),
            "getProgName" => Some(0),
            "getEnv" => Some(1),
            "lookupEnv" => Some(1),
            "exitSuccess" => Some(0),
            "exitFailure" => Some(0),
            "exitWith" => Some(1),
            "hGetContents" => Some(1),
            "getCurrentDirectory" => Some(0),
            "createDirectory" => Some(1),
            "listDirectory" => Some(1),

            // E.19: FilePath + Directory operations
            "</>" => Some(2),
            "takeFileName" => Some(1),
            "takeDirectory" => Some(1),
            "takeExtension" => Some(1),
            "dropExtension" => Some(1),
            "takeBaseName" => Some(1),
            "replaceExtension" => Some(2),
            "isAbsolute" => Some(1),
            "isRelative" => Some(1),
            "hasExtension" => Some(1),
            "splitExtension" => Some(1),
            "setCurrentDirectory" => Some(1),
            "removeDirectory" => Some(1),
            "renameFile" => Some(2),
            "copyFile" => Some(2),

            // Monadic / higher-order operations
            "fmap" => Some(2),
            "<$>" => Some(2),
            "<*>" => Some(2),
            "join" => Some(1),
            "=<<" => Some(2),
            "when" => Some(2),
            "unless" => Some(2),
            "void" => Some(1),
            "mapM" => Some(2),
            "mapM_" => Some(2),
            "forM" => Some(2),
            "forM_" => Some(2),
            "sequence" => Some(1),
            "sequence_" => Some(1),
            "traverse" => Some(2),
            "traverse_" => Some(2),
            "for" => Some(2),
            "for_" => Some(2),
            "sequenceA" => Some(1),
            "sequenceA_" => Some(1),
            "forever" => Some(1),
            "filterM" => Some(2),
            "foldM" => Some(3),
            "foldM_" => Some(3),
            "replicateM" => Some(2),
            "replicateM_" => Some(2),
            "zipWithM" => Some(3),
            "zipWithM_" => Some(3),

            // Data.Function
            "flip" => Some(3),
            "on" => Some(4),
            "fix" => Some(1),
            "$" => Some(2),
            "&" => Some(2),
            "." => Some(3),
            "succ" => Some(1),
            "pred" => Some(1),
            "fromEnum" => Some(1),
            "toEnum" => Some(1),
            "minBound" => Some(0),
            "maxBound" => Some(0),

            // E.28: Arithmetic, enum, folds, higher-order, IO input
            "min" => Some(2),
            "max" => Some(2),
            "subtract" => Some(2),
            "enumFrom" => Some(1),
            "enumFromThen" => Some(2),
            "enumFromThenTo" => Some(3),
            "foldl1" => Some(2),
            "foldr1" => Some(2),
            "comparing" => Some(3),
            "until" => Some(3),
            "getChar" => Some(0),
            "isEOF" => Some(0),
            "getContents" => Some(0),
            "interact" => Some(1),

            // E.25: String type class methods
            "fromString" => Some(1),
            // E.64: OverloadedLists
            "fromList" => Some(1),
            "read" => Some(1),
            "readMaybe" => Some(1),

            // Show
            "show" => Some(1),
            "showInt" => Some(1),
            "showDouble" => Some(1),
            "showFloat" => Some(1),
            "showBool" => Some(1),
            "showChar" => Some(1),
            "showString" => Some(1),
            "showList" => Some(1),
            "showMaybe" => Some(1),
            "showEither" => Some(1),
            "showTuple2" => Some(1),
            "showUnit" => Some(1),

            // Data.Map operations
            "Data.Map.empty" => Some(0),
            "Data.Map.singleton" => Some(2),
            "Data.Map.null" => Some(1),
            "Data.Map.size" => Some(1),
            "Data.Map.member" => Some(2),
            "Data.Map.notMember" => Some(2),
            "Data.Map.lookup" => Some(2),
            "Data.Map.findWithDefault" => Some(3),
            "Data.Map.!" => Some(2),
            "Data.Map.insert" => Some(3),
            "Data.Map.insertWith" => Some(4),
            "Data.Map.delete" => Some(2),
            "Data.Map.adjust" => Some(3),
            "Data.Map.update" => Some(3),
            "Data.Map.alter" => Some(3),
            "Data.Map.union" => Some(2),
            "Data.Map.unionWith" => Some(3),
            "Data.Map.unionWithKey" => Some(4),
            "Data.Map.unions" => Some(1),
            "Data.Map.intersection" => Some(2),
            "Data.Map.intersectionWith" => Some(3),
            "Data.Map.difference" => Some(2),
            "Data.Map.differenceWith" => Some(3),
            "Data.Map.map" => Some(2),
            "Data.Map.mapWithKey" => Some(2),
            "Data.Map.mapKeys" => Some(2),
            "Data.Map.filter" => Some(2),
            "Data.Map.filterWithKey" => Some(2),
            "Data.Map.mapMaybe" => Some(2),
            "Data.Map.mapMaybeWithKey" => Some(2),
            "Data.Map.foldr" => Some(3),
            "Data.Map.foldl" => Some(3),
            "Data.Map.foldrWithKey" => Some(3),
            "Data.Map.foldlWithKey" => Some(3),
            "Data.Map.keys" => Some(1),
            "Data.Map.elems" => Some(1),
            "Data.Map.assocs" => Some(1),
            "Data.Map.toList" => Some(1),
            "Data.Map.toAscList" => Some(1),
            "Data.Map.toDescList" => Some(1),
            "Data.Map.fromList" => Some(1),
            "Data.Map.fromListWith" => Some(2),
            "Data.Map.keysSet" => Some(1),
            "Data.Map.isSubmapOf" => Some(2),
            // Data.Set operations
            "Data.Set.empty" => Some(0),
            "Data.Set.singleton" => Some(1),
            "Data.Set.null" => Some(1),
            "Data.Set.size" => Some(1),
            "Data.Set.member" => Some(2),
            "Data.Set.notMember" => Some(2),
            "Data.Set.insert" => Some(2),
            "Data.Set.delete" => Some(2),
            "Data.Set.union" => Some(2),
            "Data.Set.unions" => Some(1),
            "Data.Set.intersection" => Some(2),
            "Data.Set.difference" => Some(2),
            "Data.Set.isSubsetOf" => Some(2),
            "Data.Set.isProperSubsetOf" => Some(2),
            "Data.Set.map" => Some(2),
            "Data.Set.filter" => Some(2),
            "Data.Set.partition" => Some(2),
            "Data.Set.foldr" => Some(3),
            "Data.Set.foldl" => Some(3),
            "Data.Set.toList" => Some(1),
            "Data.Set.toAscList" => Some(1),
            "Data.Set.toDescList" => Some(1),
            "Data.Set.fromList" => Some(1),
            "Data.Set.findMin" => Some(1),
            "Data.Set.findMax" => Some(1),
            "Data.Set.deleteMin" => Some(1),
            "Data.Set.deleteMax" => Some(1),
            "Data.Set.elems" => Some(1),
            "Data.Set.lookupMin" => Some(1),
            "Data.Set.lookupMax" => Some(1),
            // Data.IntMap operations
            "Data.IntMap.empty" => Some(0),
            "Data.IntMap.singleton" => Some(2),
            "Data.IntMap.null" => Some(1),
            "Data.IntMap.size" => Some(1),
            "Data.IntMap.member" => Some(2),
            "Data.IntMap.lookup" => Some(2),
            "Data.IntMap.findWithDefault" => Some(3),
            "Data.IntMap.insert" => Some(3),
            "Data.IntMap.insertWith" => Some(4),
            "Data.IntMap.delete" => Some(2),
            "Data.IntMap.adjust" => Some(3),
            "Data.IntMap.union" => Some(2),
            "Data.IntMap.unionWith" => Some(3),
            "Data.IntMap.intersection" => Some(2),
            "Data.IntMap.difference" => Some(2),
            "Data.IntMap.map" => Some(2),
            "Data.IntMap.mapWithKey" => Some(2),
            "Data.IntMap.filter" => Some(2),
            "Data.IntMap.foldr" => Some(3),
            "Data.IntMap.foldlWithKey" => Some(3),
            "Data.IntMap.keys" => Some(1),
            "Data.IntMap.elems" => Some(1),
            "Data.IntMap.toList" => Some(1),
            "Data.IntMap.toAscList" => Some(1),
            "Data.IntMap.fromList" => Some(1),
            // Data.IntSet operations
            "Data.IntSet.empty" => Some(0),
            "Data.IntSet.singleton" => Some(1),
            "Data.IntSet.null" => Some(1),
            "Data.IntSet.size" => Some(1),
            "Data.IntSet.member" => Some(2),
            "Data.IntSet.insert" => Some(2),
            "Data.IntSet.delete" => Some(2),
            "Data.IntSet.union" => Some(2),
            "Data.IntSet.intersection" => Some(2),
            "Data.IntSet.difference" => Some(2),
            "Data.IntSet.isSubsetOf" => Some(2),
            "Data.IntSet.filter" => Some(2),
            "Data.IntSet.foldr" => Some(3),
            "Data.IntSet.toList" => Some(1),
            "Data.IntSet.fromList" => Some(1),
            // Data.Sequence operations
            "Data.Sequence.empty" => Some(0),
            "Data.Sequence.singleton" => Some(1),
            "Data.Sequence.null" => Some(1),
            "Data.Sequence.length" => Some(1),
            "Data.Sequence.index" => Some(2),
            "Data.Sequence.lookup" => Some(2),
            "Data.Sequence.<|" => Some(2),
            "Data.Sequence.|>" => Some(2),
            "Data.Sequence.><" => Some(2),
            "Data.Sequence.take" => Some(2),
            "Data.Sequence.drop" => Some(2),
            "Data.Sequence.reverse" => Some(1),
            "Data.Sequence.update" => Some(3),
            "Data.Sequence.insertAt" => Some(3),
            "Data.Sequence.deleteAt" => Some(2),
            "Data.Sequence.fromList" => Some(1),
            "Data.Sequence.toList" => Some(1),
            "Data.Sequence.replicate" => Some(2),
            "Data.Sequence.viewl" => Some(1),
            "Data.Sequence.viewr" => Some(1),
            "Data.Sequence.filter" => Some(2),
            // Data.Text operations
            "Data.Text.empty" => Some(0),
            "Data.Text.singleton" => Some(1),
            "Data.Text.pack" => Some(1),
            "Data.Text.unpack" => Some(1),
            "Data.Text.null" => Some(1),
            "Data.Text.length" => Some(1),
            "Data.Text.head" => Some(1),
            "Data.Text.last" => Some(1),
            "Data.Text.tail" => Some(1),
            "Data.Text.init" => Some(1),
            "Data.Text.append" | "Data.Text.<>" => Some(2),
            "Data.Text.reverse" => Some(1),
            "Data.Text.take" => Some(2),
            "Data.Text.takeEnd" => Some(2),
            "Data.Text.drop" => Some(2),
            "Data.Text.dropEnd" => Some(2),
            "Data.Text.isPrefixOf" => Some(2),
            "Data.Text.isSuffixOf" => Some(2),
            "Data.Text.isInfixOf" => Some(2),
            "Data.Text.toLower" => Some(1),
            "Data.Text.toUpper" => Some(1),
            "Data.Text.toCaseFold" => Some(1),
            "Data.Text.toTitle" => Some(1),
            "Data.Text.map" => Some(2),
            "Data.Text.eq" | "Data.Text.==" => Some(2),
            "Data.Text.compare" => Some(2),
            // Additional Data.Text operations
            "Data.Text.filter" => Some(2),
            "Data.Text.foldl'" => Some(3),
            "Data.Text.concat" => Some(1),
            "Data.Text.intercalate" => Some(2),
            "Data.Text.strip" => Some(1),
            "Data.Text.words" => Some(1),
            "Data.Text.lines" => Some(1),
            "Data.Text.splitOn" => Some(2),
            "Data.Text.replace" => Some(3),
            // Data.Text.Encoding operations
            "Data.Text.Encoding.encodeUtf8" => Some(1),
            "Data.Text.Encoding.decodeUtf8" => Some(1),
            // Data.Text.IO operations
            "Data.Text.IO.readFile" => Some(1),
            "Data.Text.IO.writeFile" => Some(2),
            "Data.Text.IO.appendFile" => Some(2),
            "Data.Text.IO.hGetContents" => Some(1),
            "Data.Text.IO.hGetLine" => Some(1),
            "Data.Text.IO.hPutStr" => Some(2),
            "Data.Text.IO.hPutStrLn" => Some(2),
            "Data.Text.IO.putStr" => Some(1),
            "Data.Text.IO.putStrLn" => Some(1),
            "Data.Text.IO.getLine" => Some(0),
            "Data.Text.IO.getContents" => Some(0),
            // Data.ByteString operations
            "Data.ByteString.empty" => Some(0),
            "Data.ByteString.singleton" => Some(1),
            "Data.ByteString.pack" => Some(1),
            "Data.ByteString.unpack" => Some(1),
            "Data.ByteString.null" => Some(1),
            "Data.ByteString.length" => Some(1),
            "Data.ByteString.head" => Some(1),
            "Data.ByteString.last" => Some(1),
            "Data.ByteString.tail" => Some(1),
            "Data.ByteString.init" => Some(1),
            "Data.ByteString.append" => Some(2),
            "Data.ByteString.cons" => Some(2),
            "Data.ByteString.snoc" => Some(2),
            "Data.ByteString.take" => Some(2),
            "Data.ByteString.drop" => Some(2),
            "Data.ByteString.reverse" => Some(1),
            "Data.ByteString.elem" => Some(2),
            "Data.ByteString.index" => Some(2),
            "Data.ByteString.eq" => Some(2),
            "Data.ByteString.compare" => Some(2),
            "Data.ByteString.isPrefixOf" => Some(2),
            "Data.ByteString.isSuffixOf" => Some(2),
            "Data.ByteString.readFile" => Some(1),
            "Data.ByteString.writeFile" => Some(2),

            // Data.Text.Lazy operations
            "Data.Text.Lazy.empty" => Some(0),
            "Data.Text.Lazy.fromStrict" => Some(1),
            "Data.Text.Lazy.toStrict" => Some(1),
            "Data.Text.Lazy.pack" => Some(1),
            "Data.Text.Lazy.unpack" => Some(1),
            "Data.Text.Lazy.null" => Some(1),
            "Data.Text.Lazy.length" => Some(1),
            "Data.Text.Lazy.append" | "Data.Text.Lazy.<>" => Some(2),
            "Data.Text.Lazy.fromChunks" => Some(1),
            "Data.Text.Lazy.toChunks" => Some(1),
            "Data.Text.Lazy.head" => Some(1),
            "Data.Text.Lazy.tail" => Some(1),
            "Data.Text.Lazy.take" => Some(2),
            "Data.Text.Lazy.drop" => Some(2),

            // Data.ByteString.Lazy operations
            "Data.ByteString.Lazy.empty" => Some(0),
            "Data.ByteString.Lazy.fromStrict" => Some(1),
            "Data.ByteString.Lazy.toStrict" => Some(1),
            "Data.ByteString.Lazy.fromChunks" => Some(1),
            "Data.ByteString.Lazy.toChunks" => Some(1),
            "Data.ByteString.Lazy.null" => Some(1),
            "Data.ByteString.Lazy.length" => Some(1),
            "Data.ByteString.Lazy.pack" => Some(1),
            "Data.ByteString.Lazy.append" | "Data.ByteString.Lazy.<>" => Some(2),
            "Data.ByteString.Lazy.head" => Some(1),
            "Data.ByteString.Lazy.tail" => Some(1),
            "Data.ByteString.Lazy.take" => Some(2),
            "Data.ByteString.Lazy.drop" => Some(2),
            "Data.ByteString.Lazy.filter" => Some(2),
            "Data.ByteString.Lazy.isPrefixOf" => Some(2),
            "Data.ByteString.Lazy.readFile" => Some(1),
            "Data.ByteString.Lazy.writeFile" => Some(2),
            "Data.ByteString.Lazy.putStr" => Some(1),
            "Data.ByteString.Lazy.hPut" | "Data.ByteString.Lazy.hPutStr" => Some(2),
            "Data.ByteString.Lazy.hGetContents" => Some(1),

            // Data.ByteString.Lazy.Char8 operations
            "Data.ByteString.Lazy.Char8.unpack" => Some(1),
            "Data.ByteString.Lazy.Char8.lines" => Some(1),
            "Data.ByteString.Lazy.Char8.unlines" => Some(1),
            "Data.ByteString.Lazy.Char8.take" => Some(2),
            "Data.ByteString.Lazy.Char8.dropWhile" => Some(2),
            "Data.ByteString.Lazy.Char8.cons" => Some(2),

            // Data.Text.Lazy.Encoding operations
            "Data.Text.Lazy.Encoding.encodeUtf8" => Some(1),
            "Data.Text.Lazy.Encoding.decodeUtf8" => Some(1),

            // Data.ByteString.Builder operations
            "Data.ByteString.Builder.empty" => Some(0),
            "Data.ByteString.Builder.singleton" | "Data.ByteString.Builder.word8" => Some(1),
            "Data.ByteString.Builder.byteString" | "Data.ByteString.Builder.shortByteString" => {
                Some(1)
            }
            "Data.ByteString.Builder.lazyByteString"
            | "Data.ByteString.Builder.toLazyByteString" => Some(1),
            "Data.ByteString.Builder.append" | "Data.ByteString.Builder.<>" => Some(2),
            "Data.ByteString.Builder.toStrictByteString" => Some(1),
            "Data.ByteString.Builder.hPutBuilder" => Some(2),
            "Data.ByteString.Builder.charUtf8" => Some(1),
            "Data.ByteString.Builder.stringUtf8" => Some(1),
            "Data.ByteString.Builder.intDec"
            | "Data.ByteString.Builder.int8Dec"
            | "Data.ByteString.Builder.int16Dec"
            | "Data.ByteString.Builder.int32Dec"
            | "Data.ByteString.Builder.int64Dec"
            | "Data.ByteString.Builder.integerDec"
            | "Data.ByteString.Builder.wordDec"
            | "Data.ByteString.Builder.word8Dec"
            | "Data.ByteString.Builder.word16Dec"
            | "Data.ByteString.Builder.word32Dec"
            | "Data.ByteString.Builder.word64Dec" => Some(1),
            "Data.ByteString.Builder.char7" => Some(1),
            "Data.ByteString.Builder.char8" => Some(1),
            "Data.ByteString.Builder.string7" | "Data.ByteString.Builder.string8" => Some(1),
            "Data.ByteString.Builder.word16BE"
            | "Data.ByteString.Builder.int16BE"
            | "Data.ByteString.Builder.word32BE"
            | "Data.ByteString.Builder.int32BE"
            | "Data.ByteString.Builder.word64BE"
            | "Data.ByteString.Builder.int64BE" => Some(1),
            "Data.ByteString.Builder.word16LE"
            | "Data.ByteString.Builder.int16LE"
            | "Data.ByteString.Builder.word32LE"
            | "Data.ByteString.Builder.int32LE"
            | "Data.ByteString.Builder.word64LE"
            | "Data.ByteString.Builder.int64LE"
            | "Data.ByteString.Builder.word16Host"
            | "Data.ByteString.Builder.int16Host"
            | "Data.ByteString.Builder.word32Host"
            | "Data.ByteString.Builder.int32Host"
            | "Data.ByteString.Builder.word64Host"
            | "Data.ByteString.Builder.int64Host" => Some(1),
            "Data.ByteString.Builder.wordHex"
            | "Data.ByteString.Builder.word8Hex"
            | "Data.ByteString.Builder.word16Hex"
            | "Data.ByteString.Builder.word32Hex"
            | "Data.ByteString.Builder.word64Hex" => Some(1),
            "Data.ByteString.Builder.word8HexFixed" => Some(1),
            "Data.ByteString.Builder.word16HexFixed"
            | "Data.ByteString.Builder.word32HexFixed"
            | "Data.ByteString.Builder.word64HexFixed" => Some(1),
            "Data.ByteString.Builder.floatBE"
            | "Data.ByteString.Builder.doubleBE"
            | "Data.ByteString.Builder.floatLE"
            | "Data.ByteString.Builder.doubleLE"
            | "Data.ByteString.Builder.floatHost"
            | "Data.ByteString.Builder.doubleHost" => Some(1),

            // Identity operations
            "Identity" => Some(1),
            "runIdentity" => Some(1),
            "Identity.fmap" => Some(2),
            "Identity.pure" => Some(1),
            "Identity.<*>" => Some(2),
            "Identity.>>=" => Some(2),
            "Identity.>>" => Some(2),

            // ReaderT operations
            "ReaderT" => Some(1),
            "runReaderT" => Some(2),
            "ReaderT.fmap" => Some(2),
            "ReaderT.pure" => Some(1),
            "ReaderT.<*>" => Some(2),
            "ReaderT.>>=" => Some(2),
            "ReaderT.>>" => Some(2),
            "ReaderT.lift" => Some(1),
            "ReaderT.liftIO" => Some(1),
            "ask" => Some(0),
            "asks" => Some(1),
            "local" => Some(2),

            // StateT operations
            "StateT" => Some(1),
            "runStateT" => Some(2),
            "StateT.fmap" => Some(2),
            "StateT.pure" => Some(1),
            "StateT.<*>" => Some(2),
            "StateT.>>=" => Some(2),
            "StateT.>>" => Some(2),
            "StateT.lift" => Some(1),
            "StateT.liftIO" => Some(1),
            "get" => Some(0),
            "put" => Some(1),
            "modify" => Some(1),
            "gets" => Some(1),
            "evalStateT" => Some(2),
            "execStateT" => Some(2),

            // ExceptT operations
            "ExceptT" => Some(1),
            "runExceptT" => Some(1),
            "ExceptT.fmap" => Some(2),
            "ExceptT.pure" => Some(1),
            "ExceptT.<*>" => Some(2),
            "ExceptT.>>=" => Some(2),
            "ExceptT.>>" => Some(2),
            "ExceptT.lift" => Some(1),
            "ExceptT.liftIO" => Some(1),
            "throwE" => Some(1),
            "catchE" => Some(2),
            // MonadError standard names (mtl-style aliases)
            "throwError" => Some(1),
            "catchError" => Some(2),

            // WriterT operations
            "WriterT" => Some(1),
            "runWriterT" => Some(1),
            "WriterT.fmap" => Some(2),
            "WriterT.pure" => Some(1),
            "WriterT.<*>" => Some(2),
            "WriterT.>>=" => Some(2),
            "WriterT.>>" => Some(2),
            "WriterT.lift" => Some(1),
            "WriterT.liftIO" => Some(1),
            "tell" => Some(1),
            "execWriterT" => Some(1),

            // Generic lift/liftIO (dispatched based on transformer context)
            "lift" => Some(1),
            "liftIO" => Some(1),

            _ => {
                // Check for field selector pattern: $sel_N where N is a digit
                if name.starts_with("$sel_") && name[5..].parse::<usize>().is_ok() {
                    return Some(1); // Field selectors take one argument (the tuple/dict)
                }
                None
            }
        }
    }

    /// Check if an expression is a saturated builtin function application.
    fn is_saturated_builtin<'a>(&self, expr: &'a Expr) -> Option<(&'a str, Vec<&'a Expr>)> {
        // Collect arguments while unwrapping applications
        let mut args = Vec::new();
        let mut current = expr;

        while let Expr::App(func, arg, _) = current {
            args.push(arg.as_ref());
            current = func.as_ref();
        }

        // Skip through type applications (erased at runtime)
        while let Expr::TyApp(inner, _, _) = current {
            current = inner.as_ref();
        }

        // Check if the head is a builtin function
        if let Expr::Var(var, _) = current {
            let name = var.name.as_str();
            if let Some(arity) = self.builtin_info(name) {
                args.reverse();
                if args.len() == arity as usize {
                    return Some((name, args));
                }
            }
        }

        None
    }

    /// Lower a builtin function application.
    fn lower_builtin(
        &mut self,
        name: &str,
        args: &[&Expr],
    ) -> CodegenResult<Option<BasicValueEnum<'ctx>>> {
        match name {
            // List operations
            "head" => self.lower_builtin_head(args[0]),
            "tail" => self.lower_builtin_tail(args[0]),
            "null" => self.lower_builtin_null(args[0]),
            "length" => self.lower_builtin_length(args[0]),
            "take" => self.lower_builtin_take(args[0], args[1]),
            "drop" => self.lower_builtin_drop(args[0], args[1]),
            "reverse" => self.lower_builtin_reverse(args[0]),
            "append" | "++" => self.lower_builtin_append(args[0], args[1]),
            "enumFromTo" => self.lower_builtin_enum_from_to(args[0], args[1]),
            "replicate" => self.lower_builtin_replicate(args[0], args[1]),
            "sum" => self.lower_builtin_sum(args[0]),
            "product" => self.lower_builtin_product(args[0]),
            "map" => self.lower_builtin_map(args[0], args[1]),
            "filter" => self.lower_builtin_filter(args[0], args[1]),
            "foldr" => self.lower_builtin_foldr(args[0], args[1], args[2]),
            "foldl" => self.lower_builtin_foldl(args[0], args[1], args[2]),
            "foldl'" => self.lower_builtin_foldl_strict(args[0], args[1], args[2]),
            "zipWith" => self.lower_builtin_zipwith(args[0], args[1], args[2]),
            "zip" => self.lower_builtin_zip(args[0], args[1]),
            "last" => self.lower_builtin_last(args[0]),
            "init" => self.lower_builtin_init(args[0]),
            "!!" => self.lower_builtin_index(args[0], args[1]),
            "concatMap" => self.lower_builtin_concat_map(args[0], args[1]),
            "concat" => self.lower_builtin_concat(args[0]),

            // Tuple operations
            "fst" => self.lower_builtin_fst(args[0]),
            "snd" => self.lower_builtin_snd(args[0]),
            "swap" => self.lower_builtin_swap(args[0]),

            // Maybe operations
            "fromJust" => self.lower_builtin_from_just(args[0]),
            "isJust" => self.lower_builtin_is_just(args[0]),
            "isNothing" => self.lower_builtin_is_nothing(args[0]),
            "fromMaybe" => self.lower_builtin_from_maybe(args[0], args[1]),
            "maybe" => self.lower_builtin_maybe(args[0], args[1], args[2]),
            "listToMaybe" => self.lower_builtin_list_to_maybe(args[0]),
            "maybeToList" => self.lower_builtin_maybe_to_list(args[0]),
            "catMaybes" => self.lower_builtin_cat_maybes(args[0]),
            "mapMaybe" => self.lower_builtin_map_maybe(args[0], args[1]),

            // Either operations
            "isLeft" => self.lower_builtin_is_left(args[0]),
            "isRight" => self.lower_builtin_is_right(args[0]),
            "either" => self.lower_builtin_either(args[0], args[1], args[2]),
            "fromLeft" => self.lower_builtin_from_left(args[0], args[1]),
            "fromRight" => self.lower_builtin_from_right(args[0], args[1]),
            "lefts" => self.lower_builtin_lefts(args[0]),
            "rights" => self.lower_builtin_rights(args[0]),
            "partitionEithers" => self.lower_builtin_partition_eithers(args[0]),

            // Control
            "guard" => self.lower_builtin_guard(args[0]),

            // Error / Exception handling
            "error" => self.lower_builtin_error(args[0]),
            "undefined" => self.lower_builtin_undefined(),
            "throw" | "throwIO" => self.lower_builtin_throw(args[0]),
            "catch" => self.lower_builtin_catch(args[0], args[1]),
            "try" => self.lower_builtin_try(args[0]),
            "bracket" => self.lower_builtin_bracket(args[0], args[1], args[2]),
            "finally" => self.lower_builtin_finally(args[0], args[1]),
            "onException" => self.lower_builtin_on_exception(args[0], args[1]),
            "mask" if args.len() == 1 => self.lower_builtin_mask(args[0]),
            "mask_" if args.len() == 1 => self.lower_builtin_mask_simple(args[0], false),
            "uninterruptibleMask" if args.len() == 1 => {
                self.lower_builtin_uninterruptible_mask(args[0])
            }
            "uninterruptibleMask_" if args.len() == 1 => {
                self.lower_builtin_mask_simple(args[0], true)
            }
            "getMaskingState" => self.lower_builtin_get_masking_state(),
            "toException" => self.lower_builtin_to_exception(args[0]),
            "fromException" => self.lower_builtin_from_exception(args[0]),
            "displayException" => self.lower_builtin_display_exception(args[0]),
            "userError" => self.lower_builtin_user_error(args[0]),
            "ioError" => self.lower_builtin_throw(args[0]),

            // Misc
            "seq" => self.lower_builtin_seq(args[0], args[1]),
            "id" => self.lower_expr(args[0]),
            "const" => self.lower_expr(args[0]),
            "not" => self.lower_builtin_not(args[0]),
            // E.63: DeepSeq stubs (no-ops in strict runtime)
            "rnf" => {
                // rnf evaluates arg (already done since BHC is strict), returns unit
                let _val = self.lower_expr(args[0])?;
                Ok(Some(self.type_mapper().ptr_type().const_null().into()))
            }
            "deepseq" => {
                // deepseq evaluates first arg (already done), returns second arg
                let _val = self.lower_expr(args[0])?;
                self.lower_expr(args[1])
            }
            "force" => {
                // force is identity — value is already in normal form
                self.lower_expr(args[0])
            }
            // GHC.Generics from/to — dispatch to derived functions
            "from" => self.lower_builtin_generic_from(args[0]),
            "to" => self.lower_builtin_generic_to(args[0]),
            "otherwise" => Ok(Some(
                self.type_mapper().i64_type().const_int(1, false).into(),
            )),

            // IO operations
            "putStrLn" => self.lower_builtin_put_str_ln(args[0]),
            "putStr" => self.lower_builtin_put_str(args[0]),
            "putChar" => self.lower_builtin_put_char(args[0]),
            "print" => self.lower_builtin_print(args[0]),
            "getLine" => self.lower_builtin_get_line(),

            // Monadic operations — dispatch based on transformer stack.
            // Detects the operation's native layer and inserts lifts if needed.
            ">>=" => {
                // For ReaderT-over-StateT, bypass auto-lift and route directly
                // to nested ReaderT bind which threads state through 3-arg closures
                if self.transformer_stack.is_reader_t_over_state_t() {
                    return self.lower_builtin_reader_t_bind(args[0], args[1]);
                }
                // For ExceptT over StateT/ReaderT, bypass auto-lift and route
                // to nested ExceptT bind which threads state/reader-env
                if self.transformer_stack.is_except_t_over_state_t()
                    || self.transformer_stack.is_except_t_over_reader_t()
                {
                    return self.lower_builtin_except_t_bind(args[0], args[1]);
                }
                // For WriterT over StateT/ReaderT, bypass auto-lift and route
                // to nested WriterT bind which threads state/reader-env
                if self.transformer_stack.is_writer_t_over_state_t()
                    || self.transformer_stack.is_writer_t_over_reader_t()
                {
                    return self.lower_builtin_writer_t_bind(args[0], args[1]);
                }

                let op_layer = self.detect_operation_layer(args[0]);
                let current = self.current_transformer_layer();

                // Check if we need to lift the LHS operation to the current layer
                let needs_lift = op_layer != current
                    && self.transformer_stack.contains(op_layer)
                    && self.transformer_stack.lifts_to_reach(op_layer).unwrap_or(0) > 0;

                if needs_lift {
                    // Lift the inner operation to the current stack layer
                    self.lower_bind_with_auto_lift(args[0], args[1], op_layer, current)
                } else {
                    // No lifting needed, dispatch based on detected operation layer
                    self.push_transformer_layer(op_layer);
                    let result = match op_layer {
                        TransformerLayer::ReaderT => {
                            self.lower_builtin_reader_t_bind(args[0], args[1])
                        }
                        TransformerLayer::StateT => {
                            self.lower_builtin_state_t_bind(args[0], args[1])
                        }
                        TransformerLayer::ExceptT => {
                            self.lower_builtin_except_t_bind(args[0], args[1])
                        }
                        TransformerLayer::WriterT => {
                            self.lower_builtin_writer_t_bind(args[0], args[1])
                        }
                        TransformerLayer::IO => self.lower_builtin_bind(args[0], args[1]),
                    };
                    self.pop_transformer_layer();
                    result
                }
            }
            ">>" => {
                // For ReaderT-over-StateT, bypass auto-lift and route directly
                // to nested ReaderT then which threads state through 3-arg closures
                if self.transformer_stack.is_reader_t_over_state_t() {
                    return self.lower_builtin_reader_t_then(args[0], args[1]);
                }
                // For ExceptT over StateT/ReaderT, bypass auto-lift
                if self.transformer_stack.is_except_t_over_state_t()
                    || self.transformer_stack.is_except_t_over_reader_t()
                {
                    return self.lower_builtin_except_t_then(args[0], args[1]);
                }
                // For WriterT over StateT/ReaderT, bypass auto-lift
                if self.transformer_stack.is_writer_t_over_state_t()
                    || self.transformer_stack.is_writer_t_over_reader_t()
                {
                    return self.lower_builtin_writer_t_then(args[0], args[1]);
                }

                let op_layer = self.detect_operation_layer(args[0]);
                let current = self.current_transformer_layer();

                // Check if we need to lift the LHS operation to the current layer
                let needs_lift = op_layer != current
                    && self.transformer_stack.contains(op_layer)
                    && self.transformer_stack.lifts_to_reach(op_layer).unwrap_or(0) > 0;

                if needs_lift {
                    // Lift the inner operation to the current stack layer
                    self.lower_then_with_auto_lift(args[0], args[1], op_layer, current)
                } else {
                    // No lifting needed, dispatch based on detected operation layer
                    self.push_transformer_layer(op_layer);
                    let result = match op_layer {
                        TransformerLayer::ReaderT => {
                            self.lower_builtin_reader_t_then(args[0], args[1])
                        }
                        TransformerLayer::StateT => {
                            self.lower_builtin_state_t_then(args[0], args[1])
                        }
                        TransformerLayer::ExceptT => {
                            self.lower_builtin_except_t_then(args[0], args[1])
                        }
                        TransformerLayer::WriterT => {
                            self.lower_builtin_writer_t_then(args[0], args[1])
                        }
                        TransformerLayer::IO => self.lower_builtin_then(args[0], args[1]),
                    };
                    self.pop_transformer_layer();
                    result
                }
            }
            "return" | "pure" => {
                // For ReaderT-over-StateT, route to nested ReaderT pure
                if self.transformer_stack.is_reader_t_over_state_t() {
                    return self.lower_builtin_reader_t_pure(args[0]);
                }
                // For ExceptT over StateT/ReaderT, route to nested ExceptT pure
                if self.transformer_stack.is_except_t_over_state_t()
                    || self.transformer_stack.is_except_t_over_reader_t()
                {
                    return self.lower_builtin_except_t_pure(args[0]);
                }
                // For WriterT over StateT, route to nested WriterT pure
                // (WriterT over ReaderT: r is absorbed by unused second arg, same as plain)
                if self.transformer_stack.is_writer_t_over_state_t() {
                    return self.lower_builtin_writer_t_pure(args[0]);
                }
                match self.current_transformer_layer() {
                    TransformerLayer::ReaderT => self.lower_builtin_reader_t_pure(args[0]),
                    TransformerLayer::StateT => self.lower_builtin_state_t_pure(args[0]),
                    TransformerLayer::ExceptT => self.lower_builtin_except_t_pure(args[0]),
                    TransformerLayer::WriterT => self.lower_builtin_writer_t_pure(args[0]),
                    TransformerLayer::IO => self.lower_builtin_return(args[0]),
                }
            }

            // Numeric / Math operations
            "negate" => self.lower_builtin_negate(args[0]),
            "abs" => self.lower_builtin_abs(args[0]),
            "signum" => self.lower_builtin_signum(args[0]),
            "sqrt" => self.lower_builtin_math_unary(args[0], 1000013, "sqrt"),
            "exp" => self.lower_builtin_math_unary(args[0], 1000014, "exp"),
            "log" => self.lower_builtin_math_unary(args[0], 1000015, "log"),
            "sin" => self.lower_builtin_math_unary(args[0], 1000016, "sin"),
            "cos" => self.lower_builtin_math_unary(args[0], 1000017, "cos"),
            "tan" => self.lower_builtin_math_unary(args[0], 1000018, "tan"),
            "asin" => self.lower_builtin_math_unary(args[0], 1000019, "asin"),
            "acos" => self.lower_builtin_math_unary(args[0], 1000020, "acos"),
            "atan" => self.lower_builtin_math_unary(args[0], 1000021, "atan"),
            "atan2" => self.lower_builtin_math_binary(args[0], args[1], 1000022, "atan2"),
            "ceiling" => self.lower_builtin_float_to_int(args[0], 1000023, "ceiling"),
            "floor" => self.lower_builtin_float_to_int(args[0], 1000024, "floor"),
            "round" => self.lower_builtin_float_to_int(args[0], 1000025, "round"),
            "truncate" => self.lower_builtin_float_to_int(args[0], 1000026, "truncate"),

            // Rational operations
            "%" => self.lower_builtin_rational_make(args[0], args[1]),
            "numerator" => self.lower_builtin_rational_unary(args[0], 1000901, "numerator"),
            "denominator" => self.lower_builtin_rational_unary(args[0], 1000902, "denominator"),
            "toRational" => self.lower_builtin_to_rational(args[0]),
            "fromRational" => self.lower_builtin_from_rational(args[0]),
            "recip" => self.lower_builtin_rational_unary(args[0], 1000914, "recip"),

            // Numeric conversions (identity for BHC's single Int type)
            "fromIntegral" | "toInteger" => self.lower_expr(args[0]),
            "fromInteger" => {
                // Check if the context expects Rational
                // We detect this by examining the expression's type annotation
                let expr_ty = args[0].ty();
                if self.is_rational_type(&expr_ty) {
                    // Already Rational - identity
                    self.lower_expr(args[0])
                } else {
                    // Default: identity (Int -> Int)
                    self.lower_expr(args[0])
                }
            }

            // String type class methods (E.25)
            "fromString" => self.lower_expr(args[0]), // identity: String = [Char]
            // OverloadedLists (E.64)
            "fromList" => self.lower_expr(args[0]), // identity: list stays as list
            "read" => self.lower_builtin_read(args[0]),
            "readMaybe" => self.lower_builtin_read_maybe(args[0]),

            // Ordering
            "compare" => self.lower_builtin_compare(args[0], args[1]),

            // Integer predicates
            "even" => self.lower_builtin_even(args[0]),
            "odd" => self.lower_builtin_odd(args[0]),

            // Integer operations via RTS
            "gcd" => self.lower_builtin_int_binop_rts(args[0], args[1], 1000500, "gcd"),
            "lcm" => self.lower_builtin_int_binop_rts(args[0], args[1], 1000501, "lcm"),

            // Division with tuples
            "divMod" => self.lower_builtin_divmod(args[0], args[1]),
            "quotRem" => self.lower_builtin_quotrem(args[0], args[1]),

            // IORef operations
            "newIORef" => self.lower_builtin_new_ioref(args[0]),
            "readIORef" => self.lower_builtin_read_ioref(args[0]),
            "writeIORef" => self.lower_builtin_write_ioref(args[0], args[1]),
            "modifyIORef" | "modifyIORef'" => self.lower_builtin_modify_ioref(args[0], args[1]),
            "atomicModifyIORef" | "atomicModifyIORef'" => {
                self.lower_builtin_atomic_modify_ioref(args[0], args[1])
            }

            // Character operations
            "ord" => self.lower_builtin_ord(args[0]),
            "chr" => self.lower_builtin_chr(args[0]),
            "isAlpha" => self.lower_builtin_char_pred(args[0], 1000030, "is_alpha"),
            "isDigit" => self.lower_builtin_char_pred(args[0], 1000031, "is_digit"),
            "isAlphaNum" => self.lower_builtin_char_pred(args[0], 1000032, "is_alnum"),
            "isSpace" => self.lower_builtin_char_pred(args[0], 1000033, "is_space"),
            "isUpper" => self.lower_builtin_char_pred(args[0], 1000034, "is_upper"),
            "isLower" => self.lower_builtin_char_pred(args[0], 1000035, "is_lower"),
            "isPrint" => self.lower_builtin_char_pred(args[0], 1000038, "is_print"),
            "isAscii" => self.lower_builtin_char_pred(args[0], 1000039, "is_ascii"),
            "isControl" => self.lower_builtin_char_pred(args[0], 1000040, "is_control"),
            "isHexDigit" => self.lower_builtin_char_pred(args[0], 1000041, "is_hex"),
            "isLetter" => self.lower_builtin_char_pred(args[0], 1000042, "is_letter"),
            "isNumber" => self.lower_builtin_char_pred(args[0], 1000043, "is_number"),
            "isPunctuation" => self.lower_builtin_char_pred(args[0], 1000044, "is_punct"),
            "isSymbol" => self.lower_builtin_char_pred(args[0], 1000045, "is_symbol"),
            "toLower" => self.lower_builtin_char_conv(args[0], 1000037, "to_lower"),
            "toUpper" => self.lower_builtin_char_conv(args[0], 1000036, "to_upper"),
            "digitToInt" => self.lower_builtin_digit_to_int(args[0]),
            "intToDigit" => self.lower_builtin_int_to_digit(args[0]),

            // IO & System operations
            "readFile" => self.lower_builtin_read_file(args[0]),
            "writeFile" => self.lower_builtin_write_file(args[0], args[1]),
            "appendFile" => self.lower_builtin_append_file(args[0], args[1]),
            "openFile" => self.lower_builtin_open_file(args[0], args[1]),
            "hClose" => self.lower_builtin_hclose(args[0]),
            "hGetChar" => self.lower_builtin_hgetchar(args[0]),
            "hGetLine" => self.lower_builtin_hgetline(args[0]),
            "hPutStr" => self.lower_builtin_hputstr(args[0], args[1]),
            "hPutStrLn" => self.lower_builtin_hputstrln(args[0], args[1]),
            "hFlush" => self.lower_builtin_hflush(args[0]),
            "hIsEOF" => self.lower_builtin_hiseof(args[0]),
            "hSetBuffering" => self.lower_builtin_hset_buffering(args[0], args[1]),
            "stdin" => self.lower_builtin_std_handle(1060, "stdin"),
            "stdout" => self.lower_builtin_std_handle(1061, "stdout"),
            "stderr" => self.lower_builtin_std_handle(1062, "stderr"),
            "doesFileExist" => self.lower_builtin_file_pred(args[0], 1000063, "exists"),
            "doesDirectoryExist" => self.lower_builtin_file_pred(args[0], 1000064, "is_dir"),
            "removeFile" => self.lower_builtin_remove_file(args[0]),
            "getArgs" => self.lower_builtin_get_args(),
            "getProgName" => self.lower_builtin_get_prog_name(),
            "getEnv" => self.lower_builtin_get_env(args[0]),
            "lookupEnv" => self.lower_builtin_lookup_env(args[0]),
            "exitSuccess" => self.lower_builtin_exit_success(),
            "exitFailure" => self.lower_builtin_exit_failure(),
            "exitWith" => self.lower_builtin_exit_with(args[0]),
            "hGetContents" => self.lower_builtin_hgetcontents(args[0]),
            "getCurrentDirectory" => self.lower_builtin_get_current_directory(),
            "createDirectory" => self.lower_builtin_create_directory(args[0]),
            "listDirectory" => self.lower_builtin_list_directory(args[0]),

            // E.19: FilePath operations
            "</>" => {
                self.lower_builtin_filepath_two_str_to_str(args[0], args[1], 1000534, "combine")
            }
            "takeFileName" => {
                self.lower_builtin_filepath_str_to_str(args[0], 1000520, "takeFileName")
            }
            "takeDirectory" => {
                self.lower_builtin_filepath_str_to_str(args[0], 1000521, "takeDirectory")
            }
            "takeExtension" => {
                self.lower_builtin_filepath_str_to_str(args[0], 1000522, "takeExtension")
            }
            "dropExtension" => {
                self.lower_builtin_filepath_str_to_str(args[0], 1000523, "dropExtension")
            }
            "takeBaseName" => {
                self.lower_builtin_filepath_str_to_str(args[0], 1000524, "takeBaseName")
            }
            "replaceExtension" => self.lower_builtin_filepath_two_str_to_str(
                args[0],
                args[1],
                1000525,
                "replaceExtension",
            ),
            "isAbsolute" => self.lower_builtin_file_pred(args[0], 1000526, "is_absolute"),
            "isRelative" => self.lower_builtin_file_pred(args[0], 1000527, "is_relative"),
            "hasExtension" => self.lower_builtin_file_pred(args[0], 1000528, "has_extension"),
            "splitExtension" => self.lower_builtin_split_extension(args[0]),

            // E.19: Directory operations
            "setCurrentDirectory" => {
                self.lower_builtin_dir_str_to_io_unit(args[0], 1000529, "setCurrentDirectory")
            }
            "removeDirectory" => {
                self.lower_builtin_dir_str_to_io_unit(args[0], 1000530, "removeDirectory")
            }
            "renameFile" => {
                self.lower_builtin_dir_two_str_to_io_unit(args[0], args[1], 1000531, "renameFile")
            }
            "copyFile" => {
                self.lower_builtin_dir_two_str_to_io_unit(args[0], args[1], 1000532, "copyFile")
            }

            // Higher-order / Monadic operations
            "fmap" | "<$>" => self.lower_builtin_fmap(args[0], args[1]),
            "<*>" => self.lower_builtin_ap(args[0], args[1]),
            "join" => self.lower_builtin_join(args[0]),
            "=<<" => self.lower_builtin_bind_flipped(args[0], args[1]),
            "when" => self.lower_builtin_when(args[0], args[1]),
            "unless" => self.lower_builtin_unless(args[0], args[1]),
            "void" => self.lower_builtin_void(args[0]),
            "mapM" => self.lower_builtin_mapm(args[0], args[1]),
            "mapM_" => self.lower_builtin_mapm_(args[0], args[1]),
            "forM" => self.lower_builtin_mapm(args[1], args[0]),
            "forM_" => self.lower_builtin_mapm_(args[1], args[0]),
            "sequence" => self.lower_builtin_sequence(args[0]),
            "sequence_" => self.lower_builtin_sequence_(args[0]),
            "traverse" => self.lower_builtin_traverse(args[0], args[1]),
            "traverse_" => self.lower_builtin_traverse_(args[0], args[1]),
            "for" => self.lower_builtin_traverse(args[1], args[0]), // flipped
            "for_" => self.lower_builtin_traverse_(args[1], args[0]), // flipped
            "sequenceA" => self.lower_builtin_sequence_a(args[0]),
            "sequenceA_" => self.lower_builtin_sequence_a_(args[0]),
            "forever" => self.lower_builtin_forever(args[0]),
            "filterM" => self.lower_builtin_filter_m(args[0], args[1]),
            "foldM" => self.lower_builtin_fold_m(args[0], args[1], args[2]),
            "foldM_" => self.lower_builtin_fold_m_(args[0], args[1], args[2]),
            "replicateM" => self.lower_builtin_replicate_m(args[0], args[1]),
            "replicateM_" => self.lower_builtin_replicate_m_(args[0], args[1]),
            "zipWithM" => self.lower_builtin_zipwith_m(args[0], args[1], args[2]),
            "zipWithM_" => self.lower_builtin_zipwith_m_(args[0], args[1], args[2]),

            // Data.Function
            "flip" => self.lower_builtin_flip(args[0], args[1], args[2]),
            "on" => self.lower_builtin_on(args[0], args[1], args[2], args[3]),
            "fix" => self.lower_builtin_fix(args[0]),
            "$" => self.lower_builtin_apply(args[0], args[1]),
            "&" => self.lower_builtin_reverse_apply(args[0], args[1]),
            "." => self.lower_builtin_compose(args[0], args[1], args[2]),
            "curry" => self.lower_builtin_curry(args[0], args[1], args[2]),
            "uncurry" => self.lower_builtin_uncurry(args[0], args[1]),
            "succ" => self.lower_builtin_succ(args[0]),
            "pred" => self.lower_builtin_pred(args[0]),
            "fromEnum" => self.lower_builtin_from_enum(args[0]),
            "toEnum" => self.lower_builtin_to_enum(args[0]),
            "minBound" => self.lower_builtin_min_bound(),
            "maxBound" => self.lower_builtin_max_bound(),

            // E.28: Arithmetic, enum, folds, higher-order, IO input
            "min" => self.lower_builtin_min(args[0], args[1]),
            "max" => self.lower_builtin_max(args[0], args[1]),
            "subtract" => self.lower_builtin_subtract(args[0], args[1]),
            "enumFrom" => self.lower_builtin_enum_from(args[0]),
            "enumFromThen" => self.lower_builtin_enum_from_then(args[0], args[1]),
            "enumFromThenTo" => self.lower_builtin_enum_from_then_to(args[0], args[1], args[2]),
            "foldl1" => self.lower_builtin_foldl1(args[0], args[1]),
            "foldr1" => self.lower_builtin_foldr1(args[0], args[1]),
            "comparing" => self.lower_builtin_comparing(args[0], args[1], args[2]),
            "until" => self.lower_builtin_until(args[0], args[1], args[2]),
            "getChar" => self.lower_builtin_get_char(),
            "isEOF" => self.lower_builtin_is_eof(),
            "getContents" => self.lower_builtin_get_contents(),
            "interact" => self.lower_builtin_interact(args[0]),

            // Show
            "show" => self.lower_builtin_show(args[0]),
            "showInt" => {
                self.lower_builtin_show_typed(args[0], 1000072, "show_int", ShowCoerce::Int)
            }
            "showDouble" => {
                self.lower_builtin_show_typed(args[0], 1000073, "show_double", ShowCoerce::Double)
            }
            "showFloat" => {
                self.lower_builtin_show_typed(args[0], 1000090, "show_float", ShowCoerce::Float)
            }
            "showBool" => {
                self.lower_builtin_show_typed(args[0], 1000091, "show_bool", ShowCoerce::Bool)
            }
            "showChar" => {
                self.lower_builtin_show_typed(args[0], 1000074, "show_char", ShowCoerce::Char)
            }
            "showString" => self.lower_builtin_show_typed(
                args[0],
                1000092,
                "show_string",
                ShowCoerce::StringList,
            ),
            "showList" => {
                self.lower_builtin_show_typed(args[0], 1000093, "show_list", ShowCoerce::List)
            }
            "showMaybe" => {
                self.lower_builtin_show_typed(args[0], 1000094, "show_maybe", ShowCoerce::MaybeOf)
            }
            "showEither" => {
                self.lower_builtin_show_typed(args[0], 1000095, "show_either", ShowCoerce::EitherOf)
            }
            "showTuple2" => {
                self.lower_builtin_show_typed(args[0], 1000096, "show_tuple2", ShowCoerce::Tuple2Of)
            }
            "showUnit" => {
                self.lower_builtin_show_typed(args[0], 1000097, "show_unit", ShowCoerce::Unit)
            }

            // Advanced list operations (delegate to existing patterns)
            "any" => self.lower_builtin_any(args[0], args[1]),
            "all" => self.lower_builtin_all(args[0], args[1]),
            "and" => self.lower_builtin_and(args[0]),
            "or" => self.lower_builtin_or(args[0]),
            "maximum" => self.lower_builtin_maximum(args[0]),
            "minimum" => self.lower_builtin_minimum(args[0]),
            "elem" => self.lower_builtin_elem(args[0], args[1]),
            "notElem" => self.lower_builtin_not_elem(args[0], args[1]),
            "iterate" => self.lower_builtin_iterate(args[0], args[1]),
            "repeat" => self.lower_builtin_repeat(args[0]),
            "cycle" => self.lower_builtin_cycle(args[0]),
            "takeWhile" => self.lower_builtin_take_while(args[0], args[1]),
            "dropWhile" => self.lower_builtin_drop_while(args[0], args[1]),
            "span" => self.lower_builtin_span(args[0], args[1]),
            "break" => self.lower_builtin_break(args[0], args[1]),
            "splitAt" => self.lower_builtin_split_at(args[0], args[1]),
            "find" => self.lower_builtin_find(args[0], args[1]),
            "lookup" => self.lower_builtin_lookup(args[0], args[1]),
            "partition" => self.lower_builtin_partition(args[0], args[1]),
            "intersperse" => self.lower_builtin_intersperse(args[0], args[1]),
            "intercalate" => self.lower_builtin_intercalate(args[0], args[1]),
            "transpose" => self.lower_builtin_transpose(args[0]),
            "nub" => self.lower_builtin_nub(args[0]),
            "sort" => self.lower_builtin_sort(args[0]),
            "sortBy" => self.lower_builtin_sort_by(args[0], args[1]),
            "group" => self.lower_builtin_group(args[0]),
            "delete" => self.lower_builtin_delete(args[0], args[1]),
            "union" => self.lower_builtin_union(args[0], args[1]),
            "intersect" => self.lower_builtin_intersect(args[0], args[1]),
            "sortOn" => self.lower_builtin_sort_on(args[0], args[1]),
            "nubBy" => self.lower_builtin_nub_by(args[0], args[1]),
            "groupBy" => self.lower_builtin_group_by(args[0], args[1]),
            "deleteBy" => self.lower_builtin_delete_by(args[0], args[1], args[2]),
            "unionBy" => self.lower_builtin_union_by(args[0], args[1], args[2]),
            "intersectBy" => self.lower_builtin_intersect_by(args[0], args[1], args[2]),
            "stripPrefix" => self.lower_builtin_strip_prefix(args[0], args[1]),
            "insert" => self.lower_builtin_insert(args[0], args[1]),
            "mapAccumL" => self.lower_builtin_map_accum_l(args[0], args[1], args[2]),
            "mapAccumR" => self.lower_builtin_map_accum_r(args[0], args[1], args[2]),
            "scanl" => self.lower_builtin_scanl(args[0], args[1], args[2]),
            "scanl'" => self.lower_builtin_scanl(args[0], args[1], args[2]),
            "scanl1" => self.lower_builtin_scanl1(args[0], args[1]),
            "scanr" => self.lower_builtin_scanr(args[0], args[1], args[2]),
            "scanr1" => self.lower_builtin_scanr1(args[0], args[1]),
            "unfoldr" => self.lower_builtin_unfoldr(args[0], args[1]),
            "lines" => self.lower_builtin_lines(args[0]),
            "unlines" => self.lower_builtin_unlines(args[0]),
            "words" => self.lower_builtin_words(args[0]),
            "unwords" => self.lower_builtin_unwords(args[0]),
            "zip3" => self.lower_builtin_zip3(args[0], args[1], args[2]),
            "zipWith3" => self.lower_builtin_zipwith3(args[0], args[1], args[2], args[3]),
            "unzip" => self.lower_builtin_unzip(args[0]),

            // E.16: New list operations
            "elemIndex" => self.lower_builtin_elem_index(args[0], args[1]),
            "findIndex" => self.lower_builtin_find_index(args[0], args[1]),
            "isPrefixOf" => self.lower_builtin_is_prefix_of(args[0], args[1]),
            "isSuffixOf" => self.lower_builtin_is_suffix_of(args[0], args[1]),
            "isInfixOf" => self.lower_builtin_is_infix_of(args[0], args[1]),
            "tails" => self.lower_builtin_tails(args[0]),
            "inits" => self.lower_builtin_inits(args[0]),
            "maximumBy" => self.lower_builtin_maximum_by(args[0], args[1]),
            "minimumBy" => self.lower_builtin_minimum_by(args[0], args[1]),
            "foldMap" => self.lower_builtin_concat_map(args[0], args[1]),

            // Data.Map operations
            "Data.Map.empty" => self.lower_builtin_map_empty(),
            "Data.Map.singleton" => self.lower_builtin_map_singleton(args[0], args[1]),
            "Data.Map.null" => self.lower_builtin_map_null(args[0]),
            "Data.Map.size" => self.lower_builtin_map_size(args[0]),
            "Data.Map.member" => self.lower_builtin_map_member(args[0], args[1]),
            "Data.Map.notMember" => {
                // Call member, then invert the Bool result
                let member_result = self
                    .lower_builtin_map_member(args[0], args[1])?
                    .ok_or_else(|| {
                        CodegenError::Internal("notMember: member returned None".to_string())
                    })?;
                let member_ptr = member_result.into_pointer_value();
                let member_tag = self.extract_adt_tag(member_ptr)?;
                let i64_type = self.type_mapper().i64_type();
                let one = i64_type.const_int(1, false);
                let inverted_tag = self
                    .builder()
                    .build_xor(member_tag, one, "not_member_tag")
                    .map_err(|e| CodegenError::Internal(format!("notMember: xor: {:?}", e)))?;
                self.allocate_bool_adt(inverted_tag, "not_member")
            }
            "Data.Map.lookup" => self.lower_builtin_map_lookup(args[0], args[1]),
            "Data.Map.findWithDefault" => {
                self.lower_builtin_map_find_with_default(args[0], args[1], args[2])
            }
            "Data.Map.!" => self.lower_builtin_map_find_with_default(args[1], args[0], args[1]),
            "Data.Map.insert" => self.lower_builtin_map_insert(args[0], args[1], args[2]),
            "Data.Map.delete" => self.lower_builtin_map_delete(args[0], args[1]),
            "Data.Map.union" => self.lower_builtin_map_union(args[0], args[1]),
            "Data.Map.intersection" => self.lower_builtin_map_intersection(args[0], args[1]),
            "Data.Map.difference" => self.lower_builtin_map_difference(args[0], args[1]),
            "Data.Map.isSubmapOf" => self.lower_builtin_map_is_submap_of(args[0], args[1]),
            "Data.Map.map" => self.lower_builtin_map_map(args[0], args[1]),
            "Data.Map.filter" => self.lower_builtin_map_filter(args[0], args[1]),
            "Data.Map.foldr" => self.lower_builtin_map_foldr(args[0], args[1], args[2]),
            "Data.Map.foldl" => self.lower_builtin_map_foldl(args[0], args[1], args[2]),
            "Data.Map.mapWithKey" => self.lower_builtin_map_map_with_key(args[0], args[1]),
            "Data.Map.filterWithKey" => self.lower_builtin_map_filter_with_key(args[0], args[1]),
            "Data.Map.mapMaybe" => self.lower_builtin_data_map_maybe(args[0], args[1]),
            "Data.Map.mapMaybeWithKey" => {
                self.lower_builtin_data_map_maybe_with_key(args[0], args[1])
            }
            "Data.Map.foldlWithKey" | "Data.Map.foldlWithKey'" => {
                self.lower_builtin_map_foldl_with_key(args[0], args[1], args[2])
            }
            "Data.Map.foldrWithKey" => {
                self.lower_builtin_map_foldr_with_key(args[0], args[1], args[2])
            }
            "Data.Map.unionWith" | "Data.Map.unionWithKey" => {
                self.lower_builtin_map_union_with(args[0], args[1], args[2])
            }
            "Data.Map.intersectionWith" => {
                self.lower_builtin_map_intersection_with(args[0], args[1], args[2])
            }
            "Data.Map.differenceWith" => {
                self.lower_builtin_map_difference_with(args[0], args[1], args[2])
            }
            "Data.Map.insertWith" => {
                self.lower_builtin_map_insert_with(args[0], args[1], args[2], args[3])
            }
            "Data.Map.adjust" => self.lower_builtin_map_adjust(args[0], args[1], args[2]),
            "Data.Map.mapKeys" => self.lower_builtin_map_map_keys(args[0], args[1]),
            "Data.Map.fromListWith" => self.lower_builtin_map_from_list_with(args[0], args[1]),
            "Data.Map.update" => self.lower_builtin_map_update(args[0], args[1], args[2]),
            "Data.Map.alter" => self.lower_builtin_map_alter(args[0], args[1], args[2]),
            "Data.Map.unions" => self.lower_builtin_map_unions(args[0]),
            "Data.Map.keys" => self.lower_builtin_map_keys(args[0]),
            "Data.Map.elems" => self.lower_builtin_map_elems(args[0]),
            "Data.Map.assocs"
            | "Data.Map.toList"
            | "Data.Map.toAscList"
            | "Data.Map.toDescList" => self.lower_builtin_map_to_list(args[0]),
            "Data.Map.keysSet" => self.lower_builtin_map_keys_set(args[0]),
            "Data.Map.fromList" => self.lower_builtin_map_from_list(args[0]),

            // Data.Set operations
            "Data.Set.empty" => self.lower_builtin_set_empty(),
            "Data.Set.singleton" => self.lower_builtin_set_singleton(args[0]),
            "Data.Set.null" => self.lower_builtin_set_null(args[0]),
            "Data.Set.size" => self.lower_builtin_set_size(args[0]),
            "Data.Set.member" | "Data.Set.notMember" => {
                self.lower_builtin_set_member(args[0], args[1])
            }
            "Data.Set.insert" => self.lower_builtin_set_insert(args[0], args[1]),
            "Data.Set.delete" => self.lower_builtin_set_delete(args[0], args[1]),
            "Data.Set.union" => {
                self.lower_builtin_set_binary(args[0], args[1], 1000127, "set_union")
            }
            "Data.Set.intersection" => {
                self.lower_builtin_set_binary(args[0], args[1], 1000128, "set_intersection")
            }
            "Data.Set.difference" => {
                self.lower_builtin_set_binary(args[0], args[1], 1000129, "set_difference")
            }
            "Data.Set.isSubsetOf" => {
                self.lower_builtin_set_predicate(args[0], args[1], 1000130, "set_is_subset_of")
            }
            "Data.Set.isProperSubsetOf" => self.lower_builtin_set_predicate(
                args[0],
                args[1],
                1000131,
                "set_is_proper_subset_of",
            ),
            "Data.Set.findMin" => {
                self.lower_builtin_set_find_extremum(args[0], 1132, "set_find_min")
            }
            "Data.Set.findMax" => {
                self.lower_builtin_set_find_extremum(args[0], 1133, "set_find_max")
            }
            "Data.Set.deleteMin" => {
                self.lower_builtin_set_delete_extremum(args[0], 1000134, "set_delete_min")
            }
            "Data.Set.deleteMax" => {
                self.lower_builtin_set_delete_extremum(args[0], 1000135, "set_delete_max")
            }
            "Data.Set.lookupMin" => {
                self.lower_builtin_set_lookup_extremum(args[0], 1000132, "set_lookup_min")
            }
            "Data.Set.lookupMax" => {
                self.lower_builtin_set_lookup_extremum(args[0], 1000133, "set_lookup_max")
            }
            "Data.Set.map" => self.lower_builtin_set_map(args[0], args[1]),
            "Data.Set.filter" => self.lower_builtin_set_filter(args[0], args[1]),
            "Data.Set.foldr" => self.lower_builtin_set_foldr(args[0], args[1], args[2]),
            "Data.Set.foldl" => self.lower_builtin_set_foldl(args[0], args[1], args[2]),
            "Data.Set.unions" => self.lower_builtin_set_unions(args[0]),
            "Data.Set.partition" => self.lower_builtin_set_partition(args[0], args[1]),
            "Data.Set.toList" | "Data.Set.toAscList" | "Data.Set.toDescList" | "Data.Set.elems" => {
                self.lower_builtin_set_to_list(args[0])
            }
            "Data.Set.fromList" => self.lower_builtin_set_from_list(args[0]),

            // Data.IntMap operations
            "Data.IntMap.empty" => {
                let rts_fn = self.functions.get(&VarId::new(1000140)).ok_or_else(|| {
                    CodegenError::Internal("bhc_intmap_empty not declared".to_string())
                })?;
                let result = self
                    .builder()
                    .build_call(*rts_fn, &[], "intmap_empty")
                    .map_err(|e| CodegenError::Internal(format!("intmap_empty: {:?}", e)))?
                    .try_as_basic_value()
                    .basic()
                    .ok_or_else(|| CodegenError::Internal("intmap_empty: void".to_string()))?;
                Ok(Some(result))
            }
            "Data.IntMap.singleton" => self.lower_builtin_map_singleton(args[0], args[1]),
            "Data.IntMap.null" => self.lower_builtin_map_null(args[0]),
            "Data.IntMap.size" => self.lower_builtin_map_size(args[0]),
            "Data.IntMap.member" => self.lower_builtin_map_member(args[0], args[1]),
            "Data.IntMap.lookup" => self.lower_builtin_map_lookup(args[0], args[1]),
            "Data.IntMap.findWithDefault" => {
                self.lower_builtin_map_find_with_default(args[0], args[1], args[2])
            }
            "Data.IntMap.insert" => self.lower_builtin_map_insert(args[0], args[1], args[2]),
            "Data.IntMap.delete" => self.lower_builtin_map_delete(args[0], args[1]),
            "Data.IntMap.union" => self.lower_builtin_map_union(args[0], args[1]),
            "Data.IntMap.intersection" => self.lower_builtin_map_intersection(args[0], args[1]),
            "Data.IntMap.difference" => self.lower_builtin_map_difference(args[0], args[1]),
            "Data.IntMap.map" => self.lower_builtin_map_map(args[0], args[1]),
            "Data.IntMap.filter" => self.lower_builtin_map_filter(args[0], args[1]),
            "Data.IntMap.foldr" => self.lower_builtin_map_foldr(args[0], args[1], args[2]),
            "Data.IntMap.mapWithKey" => self.lower_builtin_map_map_with_key(args[0], args[1]),
            "Data.IntMap.foldlWithKey" | "Data.IntMap.foldlWithKey'" => {
                self.lower_builtin_map_foldl_with_key(args[0], args[1], args[2])
            }
            "Data.IntMap.insertWith" => {
                self.lower_builtin_map_insert_with(args[0], args[1], args[2], args[3])
            }
            "Data.IntMap.adjust" => self.lower_builtin_map_adjust(args[0], args[1], args[2]),
            "Data.IntMap.unionWith" => self.lower_builtin_map_union_with(args[0], args[1], args[2]),
            "Data.IntMap.keys" => self.lower_builtin_map_keys(args[0]),
            "Data.IntMap.elems" => self.lower_builtin_map_elems(args[0]),
            "Data.IntMap.toList" | "Data.IntMap.toAscList" => {
                self.lower_builtin_map_to_list(args[0])
            }
            "Data.IntMap.fromList" => self.lower_builtin_map_from_list(args[0]),

            // Data.IntSet operations
            "Data.IntSet.empty" => {
                let rts_fn = self.functions.get(&VarId::new(1000150)).ok_or_else(|| {
                    CodegenError::Internal("bhc_intset_empty not declared".to_string())
                })?;
                let result = self
                    .builder()
                    .build_call(*rts_fn, &[], "intset_empty")
                    .map_err(|e| CodegenError::Internal(format!("intset_empty: {:?}", e)))?
                    .try_as_basic_value()
                    .basic()
                    .ok_or_else(|| CodegenError::Internal("intset_empty: void".to_string()))?;
                Ok(Some(result))
            }
            "Data.IntSet.singleton" => self.lower_builtin_set_singleton(args[0]),
            "Data.IntSet.null" => self.lower_builtin_set_null(args[0]),
            "Data.IntSet.size" => self.lower_builtin_set_size(args[0]),
            "Data.IntSet.member" => self.lower_builtin_set_member(args[0], args[1]),
            "Data.IntSet.insert" => self.lower_builtin_set_insert(args[0], args[1]),
            "Data.IntSet.delete" => self.lower_builtin_set_delete(args[0], args[1]),
            "Data.IntSet.union" => {
                self.lower_builtin_set_binary(args[0], args[1], 1000155, "intset_union")
            }
            "Data.IntSet.intersection" => {
                self.lower_builtin_set_binary(args[0], args[1], 1000157, "intset_intersection")
            }
            "Data.IntSet.difference" => {
                self.lower_builtin_set_binary(args[0], args[1], 1000158, "intset_difference")
            }
            "Data.IntSet.isSubsetOf" => {
                self.lower_builtin_set_predicate(args[0], args[1], 1000159, "intset_is_subset_of")
            }
            "Data.IntSet.filter" => self.lower_builtin_set_filter(args[0], args[1]),
            "Data.IntSet.foldr" => self.lower_builtin_set_foldr(args[0], args[1], args[2]),
            "Data.IntSet.toList" => self.lower_builtin_set_to_list(args[0]),
            "Data.IntSet.fromList" => self.lower_builtin_set_from_list(args[0]),

            // Data.Sequence operations (VarIds 1000800-1000823)
            "Data.Sequence.empty" => self.lower_builtin_seq_empty(),
            "Data.Sequence.singleton" => self.lower_builtin_seq_singleton(args[0]),
            "Data.Sequence.null" => self.lower_builtin_seq_null(args[0]),
            "Data.Sequence.length" => self.lower_builtin_seq_length(args[0]),
            "Data.Sequence.index" => self.lower_builtin_seq_index(args[0], args[1]),
            "Data.Sequence.lookup" => self.lower_builtin_seq_lookup(args[0], args[1]),
            "Data.Sequence.<|" => self.lower_builtin_seq_cons(args[0], args[1]),
            "Data.Sequence.|>" => self.lower_builtin_seq_snoc(args[0], args[1]),
            "Data.Sequence.><" => self.lower_builtin_seq_append(args[0], args[1]),
            "Data.Sequence.take" => self.lower_builtin_seq_take(args[0], args[1]),
            "Data.Sequence.drop" => self.lower_builtin_seq_drop(args[0], args[1]),
            "Data.Sequence.reverse" => self.lower_builtin_seq_reverse(args[0]),
            "Data.Sequence.update" => self.lower_builtin_seq_update(args[0], args[1], args[2]),
            "Data.Sequence.insertAt" => self.lower_builtin_seq_insert_at(args[0], args[1], args[2]),
            "Data.Sequence.deleteAt" => self.lower_builtin_seq_delete_at(args[0], args[1]),
            "Data.Sequence.fromList" => self.lower_builtin_seq_from_list(args[0]),
            "Data.Sequence.toList" => self.lower_builtin_seq_to_list(args[0]),
            "Data.Sequence.replicate" => self.lower_builtin_seq_replicate(args[0], args[1]),
            "Data.Sequence.viewl" => self.lower_builtin_seq_viewl(args[0]),
            "Data.Sequence.viewr" => self.lower_builtin_seq_viewr(args[0]),
            "Data.Sequence.filter" => self.lower_builtin_seq_filter(args[0], args[1]),

            // Data.Text operations (VarIds 1000200-1000226)
            "Data.Text.empty" => self.lower_builtin_text_nullary(1000200, "text_empty"),
            "Data.Text.singleton" => {
                self.lower_builtin_text_unary_int_to_ptr(args[0], 1000201, "text_singleton")
            }
            "Data.Text.null" => {
                self.lower_builtin_text_unary_ptr_to_int(args[0], 1000202, "text_null")
            }
            "Data.Text.length" => {
                self.lower_builtin_text_unary_ptr_to_int(args[0], 1000203, "text_length")
            }
            "Data.Text.eq" | "Data.Text.==" => {
                self.lower_builtin_text_binary_ptr_to_int(args[0], args[1], 1000204, "text_eq")
            }
            "Data.Text.compare" => {
                self.lower_builtin_text_binary_ptr_to_int(args[0], args[1], 1000205, "text_compare")
            }
            "Data.Text.head" => {
                self.lower_builtin_text_unary_ptr_to_int(args[0], 1000206, "text_head")
            }
            "Data.Text.last" => {
                self.lower_builtin_text_unary_ptr_to_int(args[0], 1000207, "text_last")
            }
            "Data.Text.tail" => {
                self.lower_builtin_text_unary_ptr_to_ptr(args[0], 1000208, "text_tail")
            }
            "Data.Text.init" => {
                self.lower_builtin_text_unary_ptr_to_ptr(args[0], 1000209, "text_init")
            }
            "Data.Text.append" | "Data.Text.<>" => {
                self.lower_builtin_text_binary_ptr_to_ptr(args[0], args[1], 1000210, "text_append")
            }
            "Data.Text.reverse" => {
                self.lower_builtin_text_unary_ptr_to_ptr(args[0], 1000211, "text_reverse")
            }
            "Data.Text.take" => {
                self.lower_builtin_text_int_ptr_to_ptr(args[0], args[1], 1000212, "text_take")
            }
            "Data.Text.takeEnd" => {
                self.lower_builtin_text_int_ptr_to_ptr(args[0], args[1], 1000213, "text_take_end")
            }
            "Data.Text.drop" => {
                self.lower_builtin_text_int_ptr_to_ptr(args[0], args[1], 1000214, "text_drop")
            }
            "Data.Text.dropEnd" => {
                self.lower_builtin_text_int_ptr_to_ptr(args[0], args[1], 1000215, "text_drop_end")
            }
            "Data.Text.isPrefixOf" => self.lower_builtin_text_binary_ptr_to_int(
                args[0],
                args[1],
                1000216,
                "text_is_prefix_of",
            ),
            "Data.Text.isSuffixOf" => self.lower_builtin_text_binary_ptr_to_int(
                args[0],
                args[1],
                1000217,
                "text_is_suffix_of",
            ),
            "Data.Text.isInfixOf" => self.lower_builtin_text_binary_ptr_to_int(
                args[0],
                args[1],
                1000218,
                "text_is_infix_of",
            ),
            "Data.Text.toLower" => {
                self.lower_builtin_text_unary_ptr_to_ptr(args[0], 1000219, "text_to_lower")
            }
            "Data.Text.toUpper" => {
                self.lower_builtin_text_unary_ptr_to_ptr(args[0], 1000220, "text_to_upper")
            }
            "Data.Text.toCaseFold" => {
                self.lower_builtin_text_unary_ptr_to_ptr(args[0], 1000221, "text_to_case_fold")
            }
            "Data.Text.toTitle" => {
                self.lower_builtin_text_unary_ptr_to_ptr(args[0], 1000222, "text_to_title")
            }
            "Data.Text.pack" => {
                self.lower_builtin_text_unary_ptr_to_ptr(args[0], 1000223, "text_pack")
            }
            "Data.Text.unpack" => self.lower_builtin_text_unpack(args[0]),
            "Data.Text.map" => self.lower_builtin_text_map(args[0], args[1]),
            // Additional Data.Text operations (VarIds 1000227-1000235)
            "Data.Text.filter" => self.lower_builtin_text_filter(args[0], args[1]),
            "Data.Text.foldl'" => self.lower_builtin_text_foldl(args[0], args[1], args[2]),
            "Data.Text.concat" => {
                self.lower_builtin_text_unary_ptr_to_ptr(args[0], 1000229, "text_concat")
            }
            "Data.Text.intercalate" => self.lower_builtin_text_binary_ptr_to_ptr(
                args[0],
                args[1],
                1000230,
                "text_intercalate",
            ),
            "Data.Text.strip" => {
                self.lower_builtin_text_unary_ptr_to_ptr(args[0], 1000231, "text_strip")
            }
            "Data.Text.words" => {
                self.lower_builtin_text_unary_ptr_to_ptr(args[0], 1000232, "text_words")
            }
            "Data.Text.lines" => {
                self.lower_builtin_text_unary_ptr_to_ptr(args[0], 1000233, "text_lines")
            }
            "Data.Text.splitOn" => self.lower_builtin_text_binary_ptr_to_ptr(
                args[0],
                args[1],
                1000234,
                "text_split_on",
            ),
            "Data.Text.replace" => self.lower_builtin_text_ternary_ptr(
                args[0],
                args[1],
                args[2],
                1000235,
                "text_replace",
            ),
            // Data.Text.Encoding (VarIds 1000236-1000237)
            "Data.Text.Encoding.encodeUtf8" => {
                self.lower_builtin_text_unary_ptr_to_ptr(args[0], 1000236, "text_encode_utf8")
            }
            "Data.Text.Encoding.decodeUtf8" => {
                self.lower_builtin_text_unary_ptr_to_ptr(args[0], 1000237, "text_decode_utf8")
            }
            // Data.Text.IO operations (VarIds 1000240-1000246)
            "Data.Text.IO.readFile" => self.lower_builtin_text_io_read_file(args[0]),
            "Data.Text.IO.writeFile" => self.lower_builtin_text_io_write_file(args[0], args[1]),
            "Data.Text.IO.appendFile" => self.lower_builtin_text_io_append_file(args[0], args[1]),
            "Data.Text.IO.hGetContents" => {
                self.lower_builtin_text_io_handle_to_text(args[0], 1000243, "text_h_get_contents")
            }
            "Data.Text.IO.hGetLine" => {
                self.lower_builtin_text_io_handle_to_text(args[0], 1000244, "text_h_get_line")
            }
            "Data.Text.IO.hPutStr" => self.lower_builtin_text_io_handle_text_void(
                args[0],
                args[1],
                1000245,
                "text_h_put_str",
            ),
            "Data.Text.IO.hPutStrLn" => self.lower_builtin_text_io_handle_text_void(
                args[0],
                args[1],
                1000246,
                "text_h_put_str_ln",
            ),
            "Data.Text.IO.putStr" => {
                self.lower_builtin_text_io_stdout_text(args[0], 1000245, "text_io_put_str")
            }
            "Data.Text.IO.putStrLn" => {
                self.lower_builtin_text_io_stdout_text(args[0], 1000246, "text_io_put_str_ln")
            }
            "Data.Text.IO.getLine" => {
                self.lower_builtin_text_io_stdin_to_text(1000244, "text_io_get_line")
            }
            "Data.Text.IO.getContents" => {
                self.lower_builtin_text_io_stdin_to_text(1000243, "text_io_get_contents")
            }
            // Data.ByteString operations (VarIds 1000400-1000423)
            "Data.ByteString.empty" => self.lower_builtin_text_nullary(1000400, "bs_empty"),
            "Data.ByteString.singleton" => {
                self.lower_builtin_text_unary_int_to_ptr(args[0], 1000401, "bs_singleton")
            }
            "Data.ByteString.pack" => {
                self.lower_builtin_text_unary_ptr_to_ptr(args[0], 1000402, "bs_pack")
            }
            "Data.ByteString.null" => {
                self.lower_builtin_text_unary_ptr_to_int(args[0], 1000404, "bs_null")
            }
            "Data.ByteString.length" => {
                self.lower_builtin_text_unary_ptr_to_int(args[0], 1000403, "bs_length")
            }
            "Data.ByteString.head" => {
                self.lower_builtin_text_unary_ptr_to_int(args[0], 1000405, "bs_head")
            }
            "Data.ByteString.last" => {
                self.lower_builtin_text_unary_ptr_to_int(args[0], 1000406, "bs_last")
            }
            "Data.ByteString.tail" => {
                self.lower_builtin_text_unary_ptr_to_ptr(args[0], 1000407, "bs_tail")
            }
            "Data.ByteString.init" => {
                self.lower_builtin_text_unary_ptr_to_ptr(args[0], 1000408, "bs_init")
            }
            "Data.ByteString.append" => {
                self.lower_builtin_text_binary_ptr_to_ptr(args[0], args[1], 1000409, "bs_append")
            }
            "Data.ByteString.cons" => {
                self.lower_builtin_text_int_ptr_to_ptr(args[0], args[1], 1000410, "bs_cons")
            }
            "Data.ByteString.snoc" => {
                self.lower_builtin_text_snoc(args[0], args[1], 1000411, "bs_snoc")
            }
            "Data.ByteString.take" => {
                self.lower_builtin_text_int_ptr_to_ptr(args[0], args[1], 1000412, "bs_take")
            }
            "Data.ByteString.drop" => {
                self.lower_builtin_text_int_ptr_to_ptr(args[0], args[1], 1000413, "bs_drop")
            }
            "Data.ByteString.reverse" => {
                self.lower_builtin_text_unary_ptr_to_ptr(args[0], 1000414, "bs_reverse")
            }
            "Data.ByteString.elem" => {
                self.lower_builtin_text_int_ptr_to_int(args[0], args[1], 1000415, "bs_elem")
            }
            "Data.ByteString.index" => {
                self.lower_builtin_text_ptr_int_to_int(args[0], args[1], 1000416, "bs_index")
            }
            "Data.ByteString.eq" => {
                self.lower_builtin_text_binary_ptr_to_int(args[0], args[1], 1000417, "bs_eq")
            }
            "Data.ByteString.compare" => {
                self.lower_builtin_text_binary_ptr_to_int(args[0], args[1], 1000418, "bs_compare")
            }
            "Data.ByteString.isPrefixOf" => self.lower_builtin_text_binary_ptr_to_int(
                args[0],
                args[1],
                1000419,
                "bs_is_prefix_of",
            ),
            "Data.ByteString.isSuffixOf" => self.lower_builtin_text_binary_ptr_to_int(
                args[0],
                args[1],
                1000420,
                "bs_is_suffix_of",
            ),
            "Data.ByteString.unpack" => self.lower_builtin_bs_unpack(args[0]),
            "Data.ByteString.readFile" => {
                self.lower_builtin_text_unary_ptr_to_ptr(args[0], 1000422, "bs_read_file")
            }
            "Data.ByteString.writeFile" => self.lower_builtin_bs_write_file(args[0], args[1]),

            // ByteArray operations (used by Data.ByteString internals)
            "Data.ByteString.bhc_bytearray_malloc" => {
                self.lower_builtin_text_unary_int_to_ptr(args[0], 1250, "ba_malloc")
            }
            "Data.ByteString.bhc_bytearray_contents" => {
                self.lower_builtin_text_unary_ptr_to_ptr(args[0], 1251, "ba_contents")
            }
            "Data.ByteString.bhc_bytearray_index" => {
                self.lower_builtin_text_ptr_int_to_int(args[0], args[1], 1252, "ba_index")
            }
            "Data.ByteString.bhc_bytearray_copy" => {
                self.lower_builtin_ba_copy(args[0], args[1], args[2], args[3])
            }
            "Data.ByteString.bhc_ptr_plus" => {
                self.lower_builtin_text_ptr_int_to_ptr(args[0], args[1], 1254, "ba_ptr_plus")
            }
            "Data.ByteString.bhc_poke_byte" => {
                self.lower_builtin_ba_poke(args[0], args[1], args[2])
            }
            "Data.ByteString.bhc_cstring_length" => {
                self.lower_builtin_text_unary_ptr_to_int(args[0], 1256, "ba_strlen")
            }
            "Data.ByteString.bhc_peek_array" => {
                self.lower_builtin_text_int_ptr_to_ptr(args[0], args[1], 1257, "ba_peek_array")
            }

            // Data.Text.Lazy operations (VarIds 1000270-1000283)
            "Data.Text.Lazy.empty" => self.lower_builtin_text_nullary(1000270, "lt_empty"),
            "Data.Text.Lazy.fromStrict" => {
                self.lower_builtin_text_unary_ptr_to_ptr(args[0], 1000271, "lt_from_strict")
            }
            "Data.Text.Lazy.toStrict" => {
                self.lower_builtin_text_unary_ptr_to_ptr(args[0], 1000272, "lt_to_strict")
            }
            "Data.Text.Lazy.pack" => {
                self.lower_builtin_text_unary_ptr_to_ptr(args[0], 1000273, "lt_pack")
            }
            "Data.Text.Lazy.unpack" => {
                self.lower_builtin_text_unary_ptr_to_ptr(args[0], 1000274, "lt_unpack")
            }
            "Data.Text.Lazy.null" => {
                self.lower_builtin_text_unary_ptr_to_int(args[0], 1000275, "lt_null")
            }
            "Data.Text.Lazy.length" => {
                self.lower_builtin_text_unary_ptr_to_int(args[0], 1000276, "lt_length")
            }
            "Data.Text.Lazy.append" | "Data.Text.Lazy.<>" => {
                self.lower_builtin_text_binary_ptr_to_ptr(args[0], args[1], 1000277, "lt_append")
            }
            "Data.Text.Lazy.fromChunks" => {
                self.lower_builtin_text_unary_ptr_to_ptr(args[0], 1000278, "lt_from_chunks")
            }
            "Data.Text.Lazy.toChunks" => {
                self.lower_builtin_text_unary_ptr_to_ptr(args[0], 1000279, "lt_to_chunks")
            }
            "Data.Text.Lazy.head" => {
                self.lower_builtin_text_unary_ptr_to_int(args[0], 1000280, "lt_head")
            }
            "Data.Text.Lazy.tail" => {
                self.lower_builtin_text_unary_ptr_to_ptr(args[0], 1000281, "lt_tail")
            }
            "Data.Text.Lazy.take" => {
                self.lower_builtin_text_int_ptr_to_ptr(args[0], args[1], 1000282, "lt_take")
            }
            "Data.Text.Lazy.drop" => {
                self.lower_builtin_text_int_ptr_to_ptr(args[0], args[1], 1000283, "lt_drop")
            }

            // Data.ByteString.Lazy operations (VarIds 1000440-1000459)
            "Data.ByteString.Lazy.empty" => self.lower_builtin_text_nullary(1000440, "lbs_empty"),
            "Data.ByteString.Lazy.fromStrict" => {
                self.lower_builtin_text_unary_ptr_to_ptr(args[0], 1000441, "lbs_from_strict")
            }
            "Data.ByteString.Lazy.toStrict" => {
                self.lower_builtin_text_unary_ptr_to_ptr(args[0], 1000442, "lbs_to_strict")
            }
            "Data.ByteString.Lazy.fromChunks" => {
                self.lower_builtin_text_unary_ptr_to_ptr(args[0], 1000443, "lbs_from_chunks")
            }
            "Data.ByteString.Lazy.toChunks" => {
                self.lower_builtin_text_unary_ptr_to_ptr(args[0], 1000444, "lbs_to_chunks")
            }
            "Data.ByteString.Lazy.null" => {
                self.lower_builtin_text_unary_ptr_to_int(args[0], 1000445, "lbs_null")
            }
            "Data.ByteString.Lazy.length" => {
                self.lower_builtin_text_unary_ptr_to_int(args[0], 1000446, "lbs_length")
            }
            "Data.ByteString.Lazy.pack" => {
                self.lower_builtin_text_unary_ptr_to_ptr(args[0], 1000447, "lbs_pack")
            }
            "Data.ByteString.Lazy.append" | "Data.ByteString.Lazy.<>" => {
                self.lower_builtin_text_binary_ptr_to_ptr(args[0], args[1], 1000448, "lbs_append")
            }
            "Data.ByteString.Lazy.head" => {
                self.lower_builtin_text_unary_ptr_to_int(args[0], 1000449, "lbs_head")
            }
            "Data.ByteString.Lazy.tail" => {
                self.lower_builtin_text_unary_ptr_to_ptr(args[0], 1000450, "lbs_tail")
            }
            "Data.ByteString.Lazy.take" => {
                self.lower_builtin_text_int_ptr_to_ptr(args[0], args[1], 1000451, "lbs_take")
            }
            "Data.ByteString.Lazy.drop" => {
                self.lower_builtin_text_int_ptr_to_ptr(args[0], args[1], 1000452, "lbs_drop")
            }
            "Data.ByteString.Lazy.filter" => self.lower_builtin_lazy_bs_filter(args[0], args[1]),
            "Data.ByteString.Lazy.isPrefixOf" => self.lower_builtin_text_binary_ptr_to_int(
                args[0],
                args[1],
                1000454,
                "lbs_is_prefix_of",
            ),
            "Data.ByteString.Lazy.readFile" => {
                self.lower_builtin_text_unary_ptr_to_ptr(args[0], 1000455, "lbs_read_file")
            }
            "Data.ByteString.Lazy.writeFile" => {
                self.lower_builtin_lazy_bs_write_file(args[0], args[1])
            }
            "Data.ByteString.Lazy.putStr" => {
                self.lower_builtin_text_io_stdout_text(args[0], 1000457, "lbs_put_str")
            }
            "Data.ByteString.Lazy.hPut" | "Data.ByteString.Lazy.hPutStr" => self
                .lower_builtin_text_io_handle_text_void(args[0], args[1], 1000458, "lbs_h_put_str"),
            "Data.ByteString.Lazy.hGetContents" => {
                self.lower_builtin_text_io_handle_to_text(args[0], 1000459, "lbs_h_get_contents")
            }

            // Data.ByteString.Lazy.Char8 operations (VarIds 1000470-1000475)
            "Data.ByteString.Lazy.Char8.unpack" => {
                self.lower_builtin_text_unary_ptr_to_ptr(args[0], 1000470, "lbs_c8_unpack")
            }
            "Data.ByteString.Lazy.Char8.lines" => {
                self.lower_builtin_text_unary_ptr_to_ptr(args[0], 1000471, "lbs_c8_lines")
            }
            "Data.ByteString.Lazy.Char8.unlines" => {
                self.lower_builtin_text_unary_ptr_to_ptr(args[0], 1000472, "lbs_c8_unlines")
            }
            "Data.ByteString.Lazy.Char8.take" => {
                self.lower_builtin_text_int_ptr_to_ptr(args[0], args[1], 1000473, "lbs_c8_take")
            }
            "Data.ByteString.Lazy.Char8.dropWhile" => {
                self.lower_builtin_lazy_bs_drop_while(args[0], args[1])
            }
            "Data.ByteString.Lazy.Char8.cons" => {
                self.lower_builtin_text_int_ptr_to_ptr(args[0], args[1], 1000475, "lbs_c8_cons")
            }

            // Data.Text.Lazy.Encoding operations (VarIds 1000476-1000477)
            "Data.Text.Lazy.Encoding.encodeUtf8" => {
                self.lower_builtin_text_unary_ptr_to_ptr(args[0], 1000476, "lt_encode_utf8")
            }
            "Data.Text.Lazy.Encoding.decodeUtf8" => {
                self.lower_builtin_text_unary_ptr_to_ptr(args[0], 1000477, "lt_decode_utf8")
            }

            // Data.ByteString.Builder operations (VarIds 1000478-1000495 + reuse lazy BS)
            "Data.ByteString.Builder.empty" => {
                self.lower_builtin_text_nullary(1000440, "bsb_empty")
            }
            "Data.ByteString.Builder.singleton" | "Data.ByteString.Builder.word8" => {
                self.lower_builtin_text_unary_int_to_ptr(args[0], 1000478, "bsb_singleton")
            }
            "Data.ByteString.Builder.byteString" | "Data.ByteString.Builder.shortByteString" => {
                self.lower_builtin_text_unary_ptr_to_ptr(args[0], 1000441, "bsb_from_strict")
            }
            "Data.ByteString.Builder.lazyByteString"
            | "Data.ByteString.Builder.toLazyByteString" => self.lower_expr(args[0]),
            "Data.ByteString.Builder.append" | "Data.ByteString.Builder.<>" => {
                self.lower_builtin_text_binary_ptr_to_ptr(args[0], args[1], 1000448, "bsb_append")
            }
            "Data.ByteString.Builder.toStrictByteString" => {
                self.lower_builtin_text_unary_ptr_to_ptr(args[0], 1000442, "bsb_to_strict")
            }
            "Data.ByteString.Builder.hPutBuilder" => {
                self.lower_builtin_text_io_handle_text_void(args[0], args[1], 1000458, "bsb_h_put")
            }
            "Data.ByteString.Builder.charUtf8" => {
                self.lower_builtin_text_unary_int_to_ptr(args[0], 1000479, "bsb_char_utf8")
            }
            "Data.ByteString.Builder.stringUtf8" => {
                self.lower_builtin_text_unary_ptr_to_ptr(args[0], 1000480, "bsb_string_utf8")
            }
            "Data.ByteString.Builder.intDec"
            | "Data.ByteString.Builder.int8Dec"
            | "Data.ByteString.Builder.int16Dec"
            | "Data.ByteString.Builder.int32Dec"
            | "Data.ByteString.Builder.int64Dec"
            | "Data.ByteString.Builder.integerDec"
            | "Data.ByteString.Builder.wordDec"
            | "Data.ByteString.Builder.word8Dec"
            | "Data.ByteString.Builder.word16Dec"
            | "Data.ByteString.Builder.word32Dec"
            | "Data.ByteString.Builder.word64Dec" => {
                self.lower_builtin_text_unary_int_to_ptr(args[0], 1000481, "bsb_int_dec")
            }
            "Data.ByteString.Builder.char7" => {
                self.lower_builtin_text_unary_int_to_ptr(args[0], 1000482, "bsb_char7")
            }
            "Data.ByteString.Builder.char8" => {
                self.lower_builtin_text_unary_int_to_ptr(args[0], 1000483, "bsb_char8")
            }
            "Data.ByteString.Builder.string7" => {
                self.lower_builtin_text_unary_ptr_to_ptr(args[0], 1000484, "bsb_string7")
            }
            "Data.ByteString.Builder.string8" => {
                self.lower_builtin_text_unary_ptr_to_ptr(args[0], 1000485, "bsb_string8")
            }
            "Data.ByteString.Builder.word16BE" | "Data.ByteString.Builder.int16BE" => {
                self.lower_builtin_text_unary_int_to_ptr(args[0], 1000486, "bsb_word16_be")
            }
            "Data.ByteString.Builder.word32BE" | "Data.ByteString.Builder.int32BE" => {
                self.lower_builtin_text_unary_int_to_ptr(args[0], 1000487, "bsb_word32_be")
            }
            "Data.ByteString.Builder.word64BE" | "Data.ByteString.Builder.int64BE" => {
                self.lower_builtin_text_unary_int_to_ptr(args[0], 1000488, "bsb_word64_be")
            }
            "Data.ByteString.Builder.word16LE"
            | "Data.ByteString.Builder.int16LE"
            | "Data.ByteString.Builder.word16Host"
            | "Data.ByteString.Builder.int16Host" => {
                self.lower_builtin_text_unary_int_to_ptr(args[0], 1000489, "bsb_word16_le")
            }
            "Data.ByteString.Builder.word32LE"
            | "Data.ByteString.Builder.int32LE"
            | "Data.ByteString.Builder.word32Host"
            | "Data.ByteString.Builder.int32Host" => {
                self.lower_builtin_text_unary_int_to_ptr(args[0], 1000490, "bsb_word32_le")
            }
            "Data.ByteString.Builder.word64LE"
            | "Data.ByteString.Builder.int64LE"
            | "Data.ByteString.Builder.word64Host"
            | "Data.ByteString.Builder.int64Host" => {
                self.lower_builtin_text_unary_int_to_ptr(args[0], 1000491, "bsb_word64_le")
            }
            "Data.ByteString.Builder.wordHex"
            | "Data.ByteString.Builder.word8Hex"
            | "Data.ByteString.Builder.word16Hex"
            | "Data.ByteString.Builder.word32Hex"
            | "Data.ByteString.Builder.word64Hex" => {
                self.lower_builtin_text_unary_int_to_ptr(args[0], 1000492, "bsb_word_hex")
            }
            "Data.ByteString.Builder.word8HexFixed" => {
                self.lower_builtin_text_unary_int_to_ptr(args[0], 1000493, "bsb_word8_hex_fixed")
            }
            "Data.ByteString.Builder.word16HexFixed" => {
                self.lower_builtin_bsb_word16_hex_fixed(args[0])
            }
            "Data.ByteString.Builder.word32HexFixed" => {
                self.lower_builtin_bsb_word32_hex_fixed(args[0])
            }
            "Data.ByteString.Builder.word64HexFixed" => {
                self.lower_builtin_bsb_word64_hex_fixed(args[0])
            }
            "Data.ByteString.Builder.floatBE" => self.lower_builtin_bsb_float_be(args[0]),
            "Data.ByteString.Builder.doubleBE" => self.lower_builtin_bsb_double_be(args[0]),
            "Data.ByteString.Builder.floatLE" | "Data.ByteString.Builder.floatHost" => {
                self.lower_builtin_bsb_float_le(args[0])
            }
            "Data.ByteString.Builder.doubleLE" | "Data.ByteString.Builder.doubleHost" => {
                self.lower_builtin_bsb_double_le(args[0])
            }

            // Identity operations (newtype = pass through)
            "Identity" | "runIdentity" => self.lower_expr(args[0]),
            "Identity.fmap" => {
                // fmap f (Identity x) = Identity (f x) => just apply f to x
                self.lower_builtin_fmap(args[0], args[1])
            }
            "Identity.pure" => self.lower_expr(args[0]),
            "Identity.<*>" => self.lower_builtin_ap(args[0], args[1]),
            "Identity.>>=" => self.lower_builtin_bind(args[0], args[1]),
            "Identity.>>" => self.lower_builtin_then(args[0], args[1]),

            // ReaderT operations
            "ReaderT" => self.lower_expr(args[0]), // newtype wrap = identity
            "runReaderT" => self.lower_builtin_run_reader_t(args[0], args[1]),
            "ReaderT.pure" => self.lower_builtin_reader_t_pure(args[0]),
            "ReaderT.>>=" => self.lower_builtin_reader_t_bind(args[0], args[1]),
            "ReaderT.>>" => self.lower_builtin_reader_t_then(args[0], args[1]),
            "ReaderT.fmap" => self.lower_builtin_reader_t_fmap(args[0], args[1]),
            "ReaderT.<*>" => self.lower_builtin_reader_t_ap(args[0], args[1]),
            "ReaderT.lift" | "ReaderT.liftIO" => self.lower_builtin_reader_t_lift(args[0]),
            "ask" => self.lower_builtin_ask(),
            "asks" => self.lower_builtin_asks(args[0]),
            "local" => self.lower_builtin_local(args[0], args[1]),

            // StateT operations
            "StateT" => self.lower_expr(args[0]), // newtype wrap = identity
            "runStateT" => self.lower_builtin_run_state_t(args[0], args[1]),
            "StateT.pure" => self.lower_builtin_state_t_pure(args[0]),
            "StateT.>>=" => self.lower_builtin_state_t_bind(args[0], args[1]),
            "StateT.>>" => self.lower_builtin_state_t_then(args[0], args[1]),
            "StateT.fmap" => self.lower_builtin_state_t_fmap(args[0], args[1]),
            "StateT.<*>" => self.lower_builtin_state_t_ap(args[0], args[1]),
            "StateT.lift" | "StateT.liftIO" => self.lower_builtin_state_t_lift(args[0]),
            "get" => self.lower_builtin_get(),
            "put" => self.lower_builtin_put(args[0]),
            "modify" => self.lower_builtin_modify(args[0]),
            "gets" => self.lower_builtin_gets(args[0]),
            "evalStateT" => self.lower_builtin_eval_state_t(args[0], args[1]),
            "execStateT" => self.lower_builtin_exec_state_t(args[0], args[1]),

            // ExceptT operations
            "ExceptT" => self.lower_expr(args[0]), // newtype wrap = identity
            "runExceptT" => self.lower_builtin_run_except_t(args[0]),
            "ExceptT.pure" => self.lower_builtin_except_t_pure(args[0]),
            "ExceptT.>>=" => self.lower_builtin_except_t_bind(args[0], args[1]),
            "ExceptT.>>" => self.lower_builtin_except_t_then(args[0], args[1]),
            "ExceptT.fmap" => self.lower_builtin_except_t_fmap(args[0], args[1]),
            "ExceptT.<*>" => self.lower_builtin_except_t_ap(args[0], args[1]),
            "ExceptT.lift" | "ExceptT.liftIO" => self.lower_builtin_except_t_lift(args[0]),
            "throwE" => self.lower_builtin_throw_e(args[0]),
            "catchE" => self.lower_builtin_catch_e(args[0], args[1]),
            // MonadError standard names (mtl-style aliases)
            "throwError" => self.lower_builtin_throw_e(args[0]),
            "catchError" => self.lower_builtin_catch_e(args[0], args[1]),

            // Generic lift/liftIO — dispatched based on current transformer context
            "lift" | "liftIO" => {
                match self.current_transformer_layer() {
                    TransformerLayer::ExceptT => self.lower_builtin_except_t_lift(args[0]),
                    TransformerLayer::StateT => self.lower_builtin_state_t_lift(args[0]),
                    TransformerLayer::ReaderT => self.lower_builtin_reader_t_lift(args[0]),
                    TransformerLayer::WriterT => self.lower_builtin_writer_t_lift(args[0]),
                    TransformerLayer::IO => {
                        // IO base: lift is identity
                        let val = self.lower_expr(args[0])?.ok_or_else(|| {
                            CodegenError::Internal("lift: arg has no value".to_string())
                        })?;
                        Ok(Some(val))
                    }
                }
            }

            // WriterT operations
            "WriterT" => self.lower_expr(args[0]), // newtype wrap = identity
            "runWriterT" => self.lower_builtin_run_writer_t(args[0]),
            "WriterT.pure" => self.lower_builtin_writer_t_pure(args[0]),
            "WriterT.>>=" => self.lower_builtin_writer_t_bind(args[0], args[1]),
            "WriterT.>>" => self.lower_builtin_writer_t_then(args[0], args[1]),
            "WriterT.fmap" => self.lower_builtin_writer_t_fmap(args[0], args[1]),
            "WriterT.<*>" => self.lower_builtin_writer_t_ap(args[0], args[1]),
            "WriterT.lift" | "WriterT.liftIO" => self.lower_builtin_writer_t_lift(args[0]),
            "tell" => self.lower_builtin_tell(args[0]),
            "execWriterT" => self.lower_builtin_exec_writer_t(args[0]),

            _ => {
                // Check for field selector pattern: $sel_N
                if let Some(index_str) = name.strip_prefix("$sel_") {
                    if let Ok(field_index) = index_str.parse::<u32>() {
                        return self.lower_builtin_field_selector(args[0], field_index);
                    }
                }
                Err(CodegenError::Internal(format!("unknown builtin: {}", name)))
            }
        }
    }

    /// Lower `head` - extract first element of a list.
    /// head [] = error "empty list"
    /// head (x:_) = x
    fn lower_builtin_head(
        &mut self,
        list_expr: &Expr,
    ) -> CodegenResult<Option<BasicValueEnum<'ctx>>> {
        let list_val = self
            .lower_expr(list_expr)?
            .ok_or_else(|| CodegenError::Internal("head: list has no value".to_string()))?;

        let list_ptr = match list_val {
            BasicValueEnum::PointerValue(p) => p,
            _ => return Err(CodegenError::TypeError("head expects a list".to_string())),
        };

        // Extract tag to check if empty
        let tag = self.extract_adt_tag(list_ptr)?;
        let tm = self.type_mapper();

        // Create blocks for empty check
        let current_fn = self
            .builder()
            .get_insert_block()
            .and_then(|b| b.get_parent())
            .ok_or_else(|| CodegenError::Internal("no current function".to_string()))?;

        let empty_block = self
            .llvm_context()
            .append_basic_block(current_fn, "head_empty");
        let cons_block = self
            .llvm_context()
            .append_basic_block(current_fn, "head_cons");

        // Branch on tag (0 = [], 1 = :)
        let is_empty = self
            .builder()
            .build_int_compare(
                inkwell::IntPredicate::EQ,
                tag,
                tm.i64_type().const_zero(),
                "is_empty",
            )
            .map_err(|e| CodegenError::Internal(format!("failed to compare tag: {:?}", e)))?;

        self.builder()
            .build_conditional_branch(is_empty, empty_block, cons_block)
            .map_err(|e| CodegenError::Internal(format!("failed to build branch: {:?}", e)))?;

        // Empty case: call error
        self.builder().position_at_end(empty_block);
        let error_msg = self
            .module
            .add_global_string("head_empty_error", "head: empty list");
        let error_fn = self
            .functions
            .get(&VarId::new(1000006))
            .ok_or_else(|| CodegenError::Internal("bhc_error not declared".to_string()))?;
        self.builder()
            .build_call(*error_fn, &[error_msg.into()], "")
            .map_err(|e| CodegenError::Internal(format!("failed to call error: {:?}", e)))?;
        self.builder()
            .build_unreachable()
            .map_err(|e| CodegenError::Internal(format!("failed to build unreachable: {:?}", e)))?;

        // Cons case: extract head (field 0)
        self.builder().position_at_end(cons_block);
        let head_ptr = self.extract_adt_field(list_ptr, 2, 0)?; // arity=2 for (:)
        Ok(Some(head_ptr.into()))
    }

    /// Lower `tail` - extract rest of a list.
    /// tail [] = error "empty list"
    /// tail (_:xs) = xs
    fn lower_builtin_tail(
        &mut self,
        list_expr: &Expr,
    ) -> CodegenResult<Option<BasicValueEnum<'ctx>>> {
        let list_val = self
            .lower_expr(list_expr)?
            .ok_or_else(|| CodegenError::Internal("tail: list has no value".to_string()))?;

        let list_ptr = match list_val {
            BasicValueEnum::PointerValue(p) => p,
            _ => return Err(CodegenError::TypeError("tail expects a list".to_string())),
        };

        // Extract tag to check if empty
        let tag = self.extract_adt_tag(list_ptr)?;
        let tm = self.type_mapper();

        let current_fn = self
            .builder()
            .get_insert_block()
            .and_then(|b| b.get_parent())
            .ok_or_else(|| CodegenError::Internal("no current function".to_string()))?;

        let empty_block = self
            .llvm_context()
            .append_basic_block(current_fn, "tail_empty");
        let cons_block = self
            .llvm_context()
            .append_basic_block(current_fn, "tail_cons");

        let is_empty = self
            .builder()
            .build_int_compare(
                inkwell::IntPredicate::EQ,
                tag,
                tm.i64_type().const_zero(),
                "is_empty",
            )
            .map_err(|e| CodegenError::Internal(format!("failed to compare tag: {:?}", e)))?;

        self.builder()
            .build_conditional_branch(is_empty, empty_block, cons_block)
            .map_err(|e| CodegenError::Internal(format!("failed to build branch: {:?}", e)))?;

        // Empty case: call error
        self.builder().position_at_end(empty_block);
        let error_msg = self
            .module
            .add_global_string("tail_empty_error", "tail: empty list");
        let error_fn = self
            .functions
            .get(&VarId::new(1000006))
            .ok_or_else(|| CodegenError::Internal("bhc_error not declared".to_string()))?;
        self.builder()
            .build_call(*error_fn, &[error_msg.into()], "")
            .map_err(|e| CodegenError::Internal(format!("failed to call error: {:?}", e)))?;
        self.builder()
            .build_unreachable()
            .map_err(|e| CodegenError::Internal(format!("failed to build unreachable: {:?}", e)))?;

        // Cons case: extract tail (field 1)
        self.builder().position_at_end(cons_block);
        let tail_ptr = self.extract_adt_field(list_ptr, 2, 1)?;
        Ok(Some(tail_ptr.into()))
    }

    /// Lower `null` - check if list is empty.
    /// null [] = True
    /// null (_:_) = False
    fn lower_builtin_null(
        &mut self,
        list_expr: &Expr,
    ) -> CodegenResult<Option<BasicValueEnum<'ctx>>> {
        let list_val = self
            .lower_expr(list_expr)?
            .ok_or_else(|| CodegenError::Internal("null: list has no value".to_string()))?;

        let list_ptr = match list_val {
            BasicValueEnum::PointerValue(p) => p,
            _ => return Err(CodegenError::TypeError("null expects a list".to_string())),
        };

        // Extract tag: 0 = [], 1 = (:)
        let tag = self.extract_adt_tag(list_ptr)?;
        let tm = self.type_mapper();

        // null = (tag == 0)
        let is_null = self
            .builder()
            .build_int_compare(
                inkwell::IntPredicate::EQ,
                tag,
                tm.i64_type().const_zero(),
                "is_null",
            )
            .map_err(|e| CodegenError::Internal(format!("failed to compare: {:?}", e)))?;

        // Extend i1 to i64 for Bool representation
        let result = self
            .builder()
            .build_int_z_extend(is_null, tm.i64_type(), "null_result")
            .map_err(|e| CodegenError::Internal(format!("failed to extend: {:?}", e)))?;

        Ok(Some(result.into()))
    }

    /// Lower `length` - compute list length.
    /// Handles both cons-cell lists (tag 0=nil, 1=cons) and raw C strings
    /// (from readFile etc.) by checking the tag value at runtime.
    fn lower_builtin_length(
        &mut self,
        list_expr: &Expr,
    ) -> CodegenResult<Option<BasicValueEnum<'ctx>>> {
        let list_val = self
            .lower_expr(list_expr)?
            .ok_or_else(|| CodegenError::Internal("length: list has no value".to_string()))?;

        let list_ptr = match list_val {
            BasicValueEnum::PointerValue(p) => p,
            _ => return Err(CodegenError::TypeError("length expects a list".to_string())),
        };

        let tm = self.type_mapper();
        let current_fn = self
            .builder()
            .get_insert_block()
            .and_then(|b| b.get_parent())
            .ok_or_else(|| CodegenError::Internal("no current function".to_string()))?;

        // Read the first i64 from the pointer to determine representation.
        // Cons-cell lists have tag 0 (nil) or 1 (cons).
        // C strings have arbitrary byte content (interpreted as i64, usually > 1).
        let tag = self.extract_adt_tag(list_ptr)?;

        // Create blocks for dispatch
        let is_cons_block = self
            .llvm_context()
            .append_basic_block(current_fn, "length_cons");
        let is_cstring_block = self
            .llvm_context()
            .append_basic_block(current_fn, "length_cstring");
        let loop_header = self
            .llvm_context()
            .append_basic_block(current_fn, "length_header");
        let loop_body = self
            .llvm_context()
            .append_basic_block(current_fn, "length_body");
        let merge_block = self
            .llvm_context()
            .append_basic_block(current_fn, "length_merge");

        let entry_block = self
            .builder()
            .get_insert_block()
            .ok_or_else(|| CodegenError::Internal("no current block".to_string()))?;

        // Check if tag == 0 (nil list — length 0)
        let is_nil = self
            .builder()
            .build_int_compare(
                inkwell::IntPredicate::EQ,
                tag,
                tm.i64_type().const_zero(),
                "is_nil",
            )
            .map_err(|e| CodegenError::Internal(format!("failed to compare: {:?}", e)))?;

        // For tag==0, jump directly to merge with result 0.
        // For tag!=0, check if tag==1 (cons) or >1 (C string).
        let not_nil_block = self
            .llvm_context()
            .append_basic_block(current_fn, "length_not_nil");
        self.builder()
            .build_conditional_branch(is_nil, merge_block, not_nil_block)
            .map_err(|e| CodegenError::Internal(format!("failed to build branch: {:?}", e)))?;

        // not_nil: check tag == 1 (cons) vs > 1 (C string)
        self.builder().position_at_end(not_nil_block);
        let is_cons = self
            .builder()
            .build_int_compare(
                inkwell::IntPredicate::EQ,
                tag,
                tm.i64_type().const_int(1, false),
                "is_cons",
            )
            .map_err(|e| CodegenError::Internal(format!("failed to compare: {:?}", e)))?;
        self.builder()
            .build_conditional_branch(is_cons, is_cons_block, is_cstring_block)
            .map_err(|e| CodegenError::Internal(format!("failed to build branch: {:?}", e)))?;

        // ---- C string path: call bhc_string_length ----
        self.builder().position_at_end(is_cstring_block);
        let strlen_fn = self
            .functions
            .get(&VarId::new(1000180))
            .ok_or_else(|| CodegenError::Internal("bhc_string_length not declared".to_string()))?;
        let strlen_result = self
            .builder()
            .build_call(*strlen_fn, &[list_ptr.into()], "strlen_result")
            .map_err(|e| CodegenError::Internal(format!("strlen call failed: {:?}", e)))?
            .try_as_basic_value()
            .basic()
            .ok_or_else(|| CodegenError::Internal("strlen returned void".to_string()))?;
        self.builder()
            .build_unconditional_branch(merge_block)
            .map_err(|e| CodegenError::Internal(format!("failed to build branch: {:?}", e)))?;
        let cstring_exit_block = self.builder().get_insert_block().unwrap();

        // ---- Cons-cell path: iterative loop ----
        self.builder().position_at_end(is_cons_block);
        let init_count = tm.i64_type().const_int(1, false); // already know first cell is cons
        let first_tail = self.extract_adt_field(list_ptr, 2, 1)?;
        self.builder()
            .build_unconditional_branch(loop_header)
            .map_err(|e| CodegenError::Internal(format!("failed to build branch: {:?}", e)))?;

        // Loop header
        self.builder().position_at_end(loop_header);
        let count_phi = self
            .builder()
            .build_phi(tm.i64_type(), "count")
            .map_err(|e| CodegenError::Internal(format!("failed to build phi: {:?}", e)))?;
        let list_phi = self
            .builder()
            .build_phi(tm.ptr_type(), "list")
            .map_err(|e| CodegenError::Internal(format!("failed to build phi: {:?}", e)))?;

        let current_list = list_phi.as_basic_value().into_pointer_value();
        let cur_tag = self.extract_adt_tag(current_list)?;
        let is_empty = self
            .builder()
            .build_int_compare(
                inkwell::IntPredicate::EQ,
                cur_tag,
                tm.i64_type().const_zero(),
                "is_empty",
            )
            .map_err(|e| CodegenError::Internal(format!("failed to compare: {:?}", e)))?;
        self.builder()
            .build_conditional_branch(is_empty, merge_block, loop_body)
            .map_err(|e| CodegenError::Internal(format!("failed to build branch: {:?}", e)))?;
        let loop_header_exit = self.builder().get_insert_block().unwrap();

        // Loop body
        self.builder().position_at_end(loop_body);
        let new_count = self
            .builder()
            .build_int_add(
                count_phi.as_basic_value().into_int_value(),
                tm.i64_type().const_int(1, false),
                "new_count",
            )
            .map_err(|e| CodegenError::Internal(format!("failed to add: {:?}", e)))?;
        let tail_ptr = self.extract_adt_field(current_list, 2, 1)?;
        self.builder()
            .build_unconditional_branch(loop_header)
            .map_err(|e| CodegenError::Internal(format!("failed to build branch: {:?}", e)))?;

        // Wire up phi nodes
        count_phi.add_incoming(&[(&init_count, is_cons_block), (&new_count, loop_body)]);
        list_phi.add_incoming(&[(&first_tail, is_cons_block), (&tail_ptr, loop_body)]);

        // ---- Merge block: phi to combine all paths ----
        self.builder().position_at_end(merge_block);
        let result_phi = self
            .builder()
            .build_phi(tm.i64_type(), "length_result")
            .map_err(|e| CodegenError::Internal(format!("failed to build phi: {:?}", e)))?;
        let zero = tm.i64_type().const_zero();
        result_phi.add_incoming(&[
            (&zero, entry_block),                                  // nil path
            (&strlen_result.into_int_value(), cstring_exit_block), // C string path
            (
                &count_phi.as_basic_value().into_int_value(),
                loop_header_exit,
            ), // cons loop done
        ]);

        Ok(Some(result_phi.as_basic_value()))
    }

    /// Lower `take` - take first n elements of a list.
    /// Implemented iteratively: collect n elements in reverse, then reverse the result.
    /// Includes take-fusion for infinite generators: iterate, repeat, cycle.
    fn lower_builtin_take(
        &mut self,
        n_expr: &Expr,
        list_expr: &Expr,
    ) -> CodegenResult<Option<BasicValueEnum<'ctx>>> {
        // Take-fusion: detect iterate/repeat/cycle and fuse into bounded loop
        if let Some((inner_name, inner_args)) = self.is_saturated_builtin(list_expr) {
            match inner_name {
                "iterate" => return self.lower_take_iterate(n_expr, inner_args[0], inner_args[1]),
                "repeat" => return self.lower_take_repeat(n_expr, inner_args[0]),
                "cycle" => return self.lower_take_cycle(n_expr, inner_args[0]),
                _ => {}
            }
        }

        let n_val = self
            .lower_expr(n_expr)?
            .ok_or_else(|| CodegenError::Internal("take: n has no value".to_string()))?;
        let list_val = self
            .lower_expr(list_expr)?
            .ok_or_else(|| CodegenError::Internal("take: list has no value".to_string()))?;

        let n = self.to_int_value(n_val)?;
        let list_ptr = match list_val {
            BasicValueEnum::PointerValue(p) => p,
            _ => return Err(CodegenError::TypeError("take expects a list".to_string())),
        };

        let tm = self.type_mapper();
        let current_fn = self
            .builder()
            .get_insert_block()
            .and_then(|b| b.get_parent())
            .ok_or_else(|| CodegenError::Internal("no current function".to_string()))?;

        // Phase 1: Collect n elements into a reversed accumulator
        let collect_header = self
            .llvm_context()
            .append_basic_block(current_fn, "take_collect_header");
        let collect_body = self
            .llvm_context()
            .append_basic_block(current_fn, "take_collect_body");
        let collect_exit = self
            .llvm_context()
            .append_basic_block(current_fn, "take_collect_exit");
        // Phase 2: Reverse the accumulator
        let rev_header = self
            .llvm_context()
            .append_basic_block(current_fn, "take_rev_header");
        let rev_body = self
            .llvm_context()
            .append_basic_block(current_fn, "take_rev_body");
        let rev_exit = self
            .llvm_context()
            .append_basic_block(current_fn, "take_rev_exit");

        // Build nil in entry block before branching
        let nil = self.build_nil()?;
        let entry_block = self.builder().get_insert_block().unwrap();
        self.builder()
            .build_unconditional_branch(collect_header)
            .map_err(|e| CodegenError::Internal(format!("failed to build branch: {:?}", e)))?;

        // Collect loop header
        self.builder().position_at_end(collect_header);
        let acc_phi = self
            .builder()
            .build_phi(tm.ptr_type(), "acc")
            .map_err(|e| CodegenError::Internal(format!("failed to build phi: {:?}", e)))?;
        let list_phi = self
            .builder()
            .build_phi(tm.ptr_type(), "list")
            .map_err(|e| CodegenError::Internal(format!("failed to build phi: {:?}", e)))?;
        let count_phi = self
            .builder()
            .build_phi(tm.i64_type(), "count")
            .map_err(|e| CodegenError::Internal(format!("failed to build phi: {:?}", e)))?;

        // Check if count <= 0 (taken enough)
        let count = count_phi.as_basic_value().into_int_value();
        let count_le_0 = self
            .builder()
            .build_int_compare(
                inkwell::IntPredicate::SLE,
                count,
                tm.i64_type().const_zero(),
                "count_le_0",
            )
            .map_err(|e| CodegenError::Internal(format!("failed to compare: {:?}", e)))?;

        // Check if list is empty
        let current_list = list_phi.as_basic_value().into_pointer_value();
        let tag = self.extract_adt_tag(current_list)?;
        let is_empty = self
            .builder()
            .build_int_compare(
                inkwell::IntPredicate::EQ,
                tag,
                tm.i64_type().const_zero(),
                "is_empty",
            )
            .map_err(|e| CodegenError::Internal(format!("failed to compare: {:?}", e)))?;

        // Exit if count <= 0 OR list is empty
        let should_exit = self
            .builder()
            .build_or(count_le_0, is_empty, "should_exit")
            .map_err(|e| CodegenError::Internal(format!("failed to build or: {:?}", e)))?;
        self.builder()
            .build_conditional_branch(should_exit, collect_exit, collect_body)
            .map_err(|e| CodegenError::Internal(format!("failed to build branch: {:?}", e)))?;

        // Collect loop body: cons head onto accumulator (in reverse), advance
        self.builder().position_at_end(collect_body);
        let head_ptr = self.extract_adt_field(current_list, 2, 0)?;
        let tail_ptr = self.extract_adt_field(current_list, 2, 1)?;
        let new_acc = self.build_cons(head_ptr.into(), acc_phi.as_basic_value())?;
        let new_count = self
            .builder()
            .build_int_sub(count, tm.i64_type().const_int(1, false), "new_count")
            .map_err(|e| CodegenError::Internal(format!("failed to sub: {:?}", e)))?;
        self.builder()
            .build_unconditional_branch(collect_header)
            .map_err(|e| CodegenError::Internal(format!("failed to build branch: {:?}", e)))?;

        acc_phi.add_incoming(&[(&nil, entry_block), (&new_acc, collect_body)]);
        list_phi.add_incoming(&[(&list_ptr, entry_block), (&tail_ptr, collect_body)]);
        count_phi.add_incoming(&[(&n, entry_block), (&new_count, collect_body)]);

        // collect_exit: build nil for reverse phase and branch to rev_header
        self.builder().position_at_end(collect_exit);
        let nil2 = self.build_nil()?;
        // Save the collected accumulator (will be used as rev_list input)
        let collected_acc = acc_phi.as_basic_value().into_pointer_value();
        self.builder()
            .build_unconditional_branch(rev_header)
            .map_err(|e| CodegenError::Internal(format!("failed to build branch: {:?}", e)))?;

        // Reverse loop header
        self.builder().position_at_end(rev_header);
        let rev_acc_phi = self
            .builder()
            .build_phi(tm.ptr_type(), "rev_acc")
            .map_err(|e| CodegenError::Internal(format!("failed to build phi: {:?}", e)))?;
        let rev_list_phi = self
            .builder()
            .build_phi(tm.ptr_type(), "rev_list")
            .map_err(|e| CodegenError::Internal(format!("failed to build phi: {:?}", e)))?;

        let rev_current = rev_list_phi.as_basic_value().into_pointer_value();
        let rev_tag = self.extract_adt_tag(rev_current)?;
        let rev_is_empty = self
            .builder()
            .build_int_compare(
                inkwell::IntPredicate::EQ,
                rev_tag,
                tm.i64_type().const_zero(),
                "rev_is_empty",
            )
            .map_err(|e| CodegenError::Internal(format!("failed to compare: {:?}", e)))?;
        self.builder()
            .build_conditional_branch(rev_is_empty, rev_exit, rev_body)
            .map_err(|e| CodegenError::Internal(format!("failed to build branch: {:?}", e)))?;

        // Reverse loop body
        self.builder().position_at_end(rev_body);
        let rev_head = self.extract_adt_field(rev_current, 2, 0)?;
        let rev_tail = self.extract_adt_field(rev_current, 2, 1)?;
        let new_rev_acc = self.build_cons(rev_head.into(), rev_acc_phi.as_basic_value())?;
        self.builder()
            .build_unconditional_branch(rev_header)
            .map_err(|e| CodegenError::Internal(format!("failed to build branch: {:?}", e)))?;

        // Add phi incoming edges
        rev_acc_phi.add_incoming(&[(&nil2, collect_exit), (&new_rev_acc, rev_body)]);
        rev_list_phi.add_incoming(&[(&collected_acc, collect_exit), (&rev_tail, rev_body)]);

        // Return result
        self.builder().position_at_end(rev_exit);
        Ok(Some(rev_acc_phi.as_basic_value()))
    }

    /// Lower `take n (iterate f seed)` — fused: loop n times applying f.
    fn lower_take_iterate(
        &mut self,
        n_expr: &Expr,
        func_expr: &Expr,
        seed_expr: &Expr,
    ) -> CodegenResult<Option<BasicValueEnum<'ctx>>> {
        let n_val = self
            .lower_expr(n_expr)?
            .ok_or_else(|| CodegenError::Internal("take_iterate: n no value".to_string()))?;
        let n = self.to_int_value(n_val)?;
        let func_val = self
            .lower_expr(func_expr)?
            .ok_or_else(|| CodegenError::Internal("take_iterate: func no value".to_string()))?;
        let func_ptr = match func_val {
            BasicValueEnum::PointerValue(p) => p,
            _ => {
                return Err(CodegenError::TypeError(
                    "take_iterate: func must be closure".to_string(),
                ))
            }
        };
        let seed_val = self
            .lower_expr(seed_expr)?
            .ok_or_else(|| CodegenError::Internal("take_iterate: seed no value".to_string()))?;
        let seed_ptr = self.value_to_ptr(seed_val)?;

        let tm = self.type_mapper();
        let ptr_type = tm.ptr_type();
        let current_fn = self
            .builder()
            .get_insert_block()
            .and_then(|b| b.get_parent())
            .ok_or_else(|| CodegenError::Internal("no current function".to_string()))?;
        let entry_block = self
            .builder()
            .get_insert_block()
            .ok_or_else(|| CodegenError::Internal("no current block".to_string()))?;

        let nil = self.build_nil()?;
        let loop_header = self.llvm_ctx.append_basic_block(current_fn, "ti_header");
        let loop_body = self.llvm_ctx.append_basic_block(current_fn, "ti_body");
        let loop_exit = self.llvm_ctx.append_basic_block(current_fn, "ti_exit");

        self.builder()
            .build_unconditional_branch(loop_header)
            .map_err(|e| CodegenError::Internal(format!("branch: {:?}", e)))?;

        self.builder().position_at_end(loop_header);
        let acc_phi = self
            .builder()
            .build_phi(ptr_type, "ti_acc")
            .map_err(|e| CodegenError::Internal(format!("phi: {:?}", e)))?;
        let result_phi = self
            .builder()
            .build_phi(ptr_type, "ti_result")
            .map_err(|e| CodegenError::Internal(format!("phi: {:?}", e)))?;
        let count_phi = self
            .builder()
            .build_phi(tm.i64_type(), "ti_count")
            .map_err(|e| CodegenError::Internal(format!("phi: {:?}", e)))?;

        let count = count_phi.as_basic_value().into_int_value();
        let done = self
            .builder()
            .build_int_compare(
                inkwell::IntPredicate::SLE,
                count,
                tm.i64_type().const_zero(),
                "done",
            )
            .map_err(|e| CodegenError::Internal(format!("cmp: {:?}", e)))?;
        self.builder()
            .build_conditional_branch(done, loop_exit, loop_body)
            .map_err(|e| CodegenError::Internal(format!("branch: {:?}", e)))?;

        // Body: cons acc onto result, then acc = f(acc)
        self.builder().position_at_end(loop_body);
        let new_cons = self.build_cons(acc_phi.as_basic_value(), result_phi.as_basic_value())?;

        let fn_ptr = self.extract_closure_fn_ptr(func_ptr)?;
        let fn_type = ptr_type.fn_type(&[ptr_type.into(), ptr_type.into()], false);
        let new_acc = self
            .builder()
            .build_indirect_call(
                fn_type,
                fn_ptr,
                &[func_ptr.into(), acc_phi.as_basic_value().into()],
                "ti_new_acc",
            )
            .map_err(|e| CodegenError::Internal(format!("call: {:?}", e)))?
            .try_as_basic_value()
            .basic()
            .ok_or_else(|| CodegenError::Internal("take_iterate: f returned void".to_string()))?;
        let new_acc_ptr = self.value_to_ptr(new_acc)?;
        let new_count = self
            .builder()
            .build_int_sub(count, tm.i64_type().const_int(1, false), "ti_dec")
            .map_err(|e| CodegenError::Internal(format!("sub: {:?}", e)))?;

        self.builder()
            .build_unconditional_branch(loop_header)
            .map_err(|e| CodegenError::Internal(format!("branch: {:?}", e)))?;

        acc_phi.add_incoming(&[(&seed_ptr, entry_block), (&new_acc_ptr, loop_body)]);
        result_phi.add_incoming(&[(&nil, entry_block), (&new_cons, loop_body)]);
        count_phi.add_incoming(&[(&n, entry_block), (&new_count, loop_body)]);

        self.builder().position_at_end(loop_exit);
        let reversed = self
            .build_inline_reverse(result_phi.as_basic_value().into_pointer_value(), current_fn)?;
        Ok(reversed)
    }

    /// Lower `take n (repeat val)` — fused: cons val n times.
    fn lower_take_repeat(
        &mut self,
        n_expr: &Expr,
        val_expr: &Expr,
    ) -> CodegenResult<Option<BasicValueEnum<'ctx>>> {
        let n_val = self
            .lower_expr(n_expr)?
            .ok_or_else(|| CodegenError::Internal("take_repeat: n no value".to_string()))?;
        let n = self.to_int_value(n_val)?;
        let val = self
            .lower_expr(val_expr)?
            .ok_or_else(|| CodegenError::Internal("take_repeat: val no value".to_string()))?;
        let val_ptr = self.value_to_ptr(val)?;

        let tm = self.type_mapper();
        let ptr_type = tm.ptr_type();
        let current_fn = self
            .builder()
            .get_insert_block()
            .and_then(|b| b.get_parent())
            .ok_or_else(|| CodegenError::Internal("no current function".to_string()))?;
        let entry_block = self
            .builder()
            .get_insert_block()
            .ok_or_else(|| CodegenError::Internal("no current block".to_string()))?;

        let nil = self.build_nil()?;
        let loop_header = self.llvm_ctx.append_basic_block(current_fn, "tr_header");
        let loop_body = self.llvm_ctx.append_basic_block(current_fn, "tr_body");
        let loop_exit = self.llvm_ctx.append_basic_block(current_fn, "tr_exit");

        self.builder()
            .build_unconditional_branch(loop_header)
            .map_err(|e| CodegenError::Internal(format!("branch: {:?}", e)))?;

        self.builder().position_at_end(loop_header);
        let result_phi = self
            .builder()
            .build_phi(ptr_type, "tr_result")
            .map_err(|e| CodegenError::Internal(format!("phi: {:?}", e)))?;
        let count_phi = self
            .builder()
            .build_phi(tm.i64_type(), "tr_count")
            .map_err(|e| CodegenError::Internal(format!("phi: {:?}", e)))?;

        let count = count_phi.as_basic_value().into_int_value();
        let done = self
            .builder()
            .build_int_compare(
                inkwell::IntPredicate::SLE,
                count,
                tm.i64_type().const_zero(),
                "done",
            )
            .map_err(|e| CodegenError::Internal(format!("cmp: {:?}", e)))?;
        self.builder()
            .build_conditional_branch(done, loop_exit, loop_body)
            .map_err(|e| CodegenError::Internal(format!("branch: {:?}", e)))?;

        self.builder().position_at_end(loop_body);
        let new_cons = self.build_cons(val_ptr.into(), result_phi.as_basic_value())?;
        let new_count = self
            .builder()
            .build_int_sub(count, tm.i64_type().const_int(1, false), "tr_dec")
            .map_err(|e| CodegenError::Internal(format!("sub: {:?}", e)))?;

        self.builder()
            .build_unconditional_branch(loop_header)
            .map_err(|e| CodegenError::Internal(format!("branch: {:?}", e)))?;

        result_phi.add_incoming(&[(&nil, entry_block), (&new_cons, loop_body)]);
        count_phi.add_incoming(&[(&n, entry_block), (&new_count, loop_body)]);

        // No need to reverse — all elements are the same value, order doesn't matter.
        // But we build in reverse, so reverse for consistency.
        self.builder().position_at_end(loop_exit);
        Ok(Some(result_phi.as_basic_value()))
    }

    /// Lower `take n (cycle xs)` — fused: cycle through source list n times.
    /// Uses select to reset source pointer when exhausted.
    fn lower_take_cycle(
        &mut self,
        n_expr: &Expr,
        list_expr: &Expr,
    ) -> CodegenResult<Option<BasicValueEnum<'ctx>>> {
        let n_val = self
            .lower_expr(n_expr)?
            .ok_or_else(|| CodegenError::Internal("take_cycle: n no value".to_string()))?;
        let n = self.to_int_value(n_val)?;
        let list_val = self
            .lower_expr(list_expr)?
            .ok_or_else(|| CodegenError::Internal("take_cycle: list no value".to_string()))?;
        let list_ptr = match list_val {
            BasicValueEnum::PointerValue(p) => p,
            _ => {
                return Err(CodegenError::TypeError(
                    "take_cycle expects a list".to_string(),
                ))
            }
        };

        let tm = self.type_mapper();
        let ptr_type = tm.ptr_type();
        let current_fn = self
            .builder()
            .get_insert_block()
            .and_then(|b| b.get_parent())
            .ok_or_else(|| CodegenError::Internal("no current function".to_string()))?;
        let entry_block = self
            .builder()
            .get_insert_block()
            .ok_or_else(|| CodegenError::Internal("no current block".to_string()))?;

        let nil = self.build_nil()?;
        let cyc_header = self.llvm_ctx.append_basic_block(current_fn, "cyc_header");
        let cyc_body = self.llvm_ctx.append_basic_block(current_fn, "cyc_body");
        let cyc_exit = self.llvm_ctx.append_basic_block(current_fn, "cyc_exit");

        self.builder()
            .build_unconditional_branch(cyc_header)
            .map_err(|e| CodegenError::Internal(format!("branch: {:?}", e)))?;

        self.builder().position_at_end(cyc_header);
        let c_result = self
            .builder()
            .build_phi(ptr_type, "cyc_result")
            .map_err(|e| CodegenError::Internal(format!("phi: {:?}", e)))?;
        let c_count = self
            .builder()
            .build_phi(tm.i64_type(), "cyc_count")
            .map_err(|e| CodegenError::Internal(format!("phi: {:?}", e)))?;
        let c_src = self
            .builder()
            .build_phi(ptr_type, "cyc_src")
            .map_err(|e| CodegenError::Internal(format!("phi: {:?}", e)))?;

        let cnt = c_count.as_basic_value().into_int_value();
        let cnt_done = self
            .builder()
            .build_int_compare(
                inkwell::IntPredicate::SLE,
                cnt,
                tm.i64_type().const_zero(),
                "cyc_done",
            )
            .map_err(|e| CodegenError::Internal(format!("cmp: {:?}", e)))?;
        self.builder()
            .build_conditional_branch(cnt_done, cyc_exit, cyc_body)
            .map_err(|e| CodegenError::Internal(format!("branch: {:?}", e)))?;

        // cyc_body: if src is exhausted, reset to list_ptr via select; then consume one element
        self.builder().position_at_end(cyc_body);
        let s_tag = self.extract_adt_tag(c_src.as_basic_value().into_pointer_value())?;
        let s_empty = self
            .builder()
            .build_int_compare(
                inkwell::IntPredicate::EQ,
                s_tag,
                tm.i64_type().const_zero(),
                "s_empty",
            )
            .map_err(|e| CodegenError::Internal(format!("cmp: {:?}", e)))?;

        // Use select: if src empty, use list_ptr (reset); else use current src
        let list_as_basic: BasicValueEnum = list_ptr.into();
        let src_as_basic: BasicValueEnum = c_src.as_basic_value();
        let actual_src = self
            .builder()
            .build_select(s_empty, list_as_basic, src_as_basic, "actual_src")
            .map_err(|e| CodegenError::Internal(format!("select: {:?}", e)))?
            .into_pointer_value();

        // Extract head and tail (actual_src is guaranteed non-empty since original list is non-empty)
        let cyc_head = self.extract_adt_field(actual_src, 2, 0)?;
        let cyc_tail = self.extract_adt_field(actual_src, 2, 1)?;

        let cyc_cons = self.build_cons(cyc_head.into(), c_result.as_basic_value())?;
        let cyc_new_count = self
            .builder()
            .build_int_sub(cnt, tm.i64_type().const_int(1, false), "cyc_dec")
            .map_err(|e| CodegenError::Internal(format!("sub: {:?}", e)))?;

        self.builder()
            .build_unconditional_branch(cyc_header)
            .map_err(|e| CodegenError::Internal(format!("branch: {:?}", e)))?;

        c_result.add_incoming(&[(&nil, entry_block), (&cyc_cons, cyc_body)]);
        c_count.add_incoming(&[(&n, entry_block), (&cyc_new_count, cyc_body)]);
        c_src.add_incoming(&[(&list_ptr, entry_block), (&cyc_tail, cyc_body)]);

        // Reverse the result
        self.builder().position_at_end(cyc_exit);
        let reversed =
            self.build_inline_reverse(c_result.as_basic_value().into_pointer_value(), current_fn)?;
        Ok(reversed)
    }

    /// Lower `drop` - drop first n elements of a list.
    /// drop n xs | n <= 0 = xs
    /// drop _ [] = []
    /// drop n (_:xs) = drop (n-1) xs
    fn lower_builtin_drop(
        &mut self,
        n_expr: &Expr,
        list_expr: &Expr,
    ) -> CodegenResult<Option<BasicValueEnum<'ctx>>> {
        let n_val = self
            .lower_expr(n_expr)?
            .ok_or_else(|| CodegenError::Internal("drop: n has no value".to_string()))?;
        let list_val = self
            .lower_expr(list_expr)?
            .ok_or_else(|| CodegenError::Internal("drop: list has no value".to_string()))?;

        let n = self.to_int_value(n_val)?;
        let list_ptr = match list_val {
            BasicValueEnum::PointerValue(p) => p,
            _ => return Err(CodegenError::TypeError("drop expects a list".to_string())),
        };

        let tm = self.type_mapper();
        let current_fn = self
            .builder()
            .get_insert_block()
            .and_then(|b| b.get_parent())
            .ok_or_else(|| CodegenError::Internal("no current function".to_string()))?;

        // Create loop blocks for iterative drop
        let loop_header = self
            .llvm_context()
            .append_basic_block(current_fn, "drop_header");
        let loop_body = self
            .llvm_context()
            .append_basic_block(current_fn, "drop_body");
        let loop_exit = self
            .llvm_context()
            .append_basic_block(current_fn, "drop_exit");

        self.builder()
            .build_unconditional_branch(loop_header)
            .map_err(|e| CodegenError::Internal(format!("failed to build branch: {:?}", e)))?;

        let entry_block = self.builder().get_insert_block().unwrap();

        // Loop header: phi for remaining count and current list
        self.builder().position_at_end(loop_header);
        let count_phi = self
            .builder()
            .build_phi(tm.i64_type(), "count")
            .map_err(|e| CodegenError::Internal(format!("failed to build phi: {:?}", e)))?;
        let list_phi = self
            .builder()
            .build_phi(tm.ptr_type(), "list")
            .map_err(|e| CodegenError::Internal(format!("failed to build phi: {:?}", e)))?;

        // Check if count <= 0 (done dropping)
        let count_le_0 = self
            .builder()
            .build_int_compare(
                inkwell::IntPredicate::SLE,
                count_phi.as_basic_value().into_int_value(),
                tm.i64_type().const_zero(),
                "count_le_0",
            )
            .map_err(|e| CodegenError::Internal(format!("failed to compare: {:?}", e)))?;

        // Check if list is empty
        let current_list = list_phi.as_basic_value().into_pointer_value();
        let tag = self.extract_adt_tag(current_list)?;
        let is_empty = self
            .builder()
            .build_int_compare(
                inkwell::IntPredicate::EQ,
                tag,
                tm.i64_type().const_zero(),
                "is_empty",
            )
            .map_err(|e| CodegenError::Internal(format!("failed to compare: {:?}", e)))?;

        // Exit if count <= 0 OR list is empty
        let should_exit = self
            .builder()
            .build_or(count_le_0, is_empty, "should_exit")
            .map_err(|e| CodegenError::Internal(format!("failed to build or: {:?}", e)))?;
        self.builder()
            .build_conditional_branch(should_exit, loop_exit, loop_body)
            .map_err(|e| CodegenError::Internal(format!("failed to build branch: {:?}", e)))?;

        // Loop body: decrement count, advance to tail
        self.builder().position_at_end(loop_body);
        let new_count = self
            .builder()
            .build_int_sub(
                count_phi.as_basic_value().into_int_value(),
                tm.i64_type().const_int(1, false),
                "new_count",
            )
            .map_err(|e| CodegenError::Internal(format!("failed to sub: {:?}", e)))?;
        let tail_ptr = self.extract_adt_field(current_list, 2, 1)?;
        self.builder()
            .build_unconditional_branch(loop_header)
            .map_err(|e| CodegenError::Internal(format!("failed to build branch: {:?}", e)))?;

        // Add phi incoming edges
        count_phi.add_incoming(&[(&n, entry_block), (&new_count, loop_body)]);
        list_phi.add_incoming(&[(&list_ptr, entry_block), (&tail_ptr, loop_body)]);

        // Loop exit: return current list
        self.builder().position_at_end(loop_exit);
        Ok(Some(list_phi.as_basic_value()))
    }

    /// Lower `reverse` - reverse a list.
    fn lower_builtin_reverse(
        &mut self,
        list_expr: &Expr,
    ) -> CodegenResult<Option<BasicValueEnum<'ctx>>> {
        let list_val = self
            .lower_expr(list_expr)?
            .ok_or_else(|| CodegenError::Internal("reverse: list has no value".to_string()))?;

        let list_ptr = match list_val {
            BasicValueEnum::PointerValue(p) => p,
            _ => {
                return Err(CodegenError::TypeError(
                    "reverse expects a list".to_string(),
                ))
            }
        };

        let tm = self.type_mapper();
        let current_fn = self
            .builder()
            .get_insert_block()
            .and_then(|b| b.get_parent())
            .ok_or_else(|| CodegenError::Internal("no current function".to_string()))?;

        // Create loop blocks for iterative reverse
        let loop_header = self
            .llvm_context()
            .append_basic_block(current_fn, "rev_header");
        let loop_body = self
            .llvm_context()
            .append_basic_block(current_fn, "rev_body");
        let loop_exit = self
            .llvm_context()
            .append_basic_block(current_fn, "rev_exit");

        // Start with empty accumulator
        let nil = self.build_nil()?;
        self.builder()
            .build_unconditional_branch(loop_header)
            .map_err(|e| CodegenError::Internal(format!("failed to build branch: {:?}", e)))?;

        let entry_block = self.builder().get_insert_block().unwrap();

        // Loop header
        self.builder().position_at_end(loop_header);
        let acc_phi = self
            .builder()
            .build_phi(tm.ptr_type(), "acc")
            .map_err(|e| CodegenError::Internal(format!("failed to build phi: {:?}", e)))?;
        let list_phi = self
            .builder()
            .build_phi(tm.ptr_type(), "list")
            .map_err(|e| CodegenError::Internal(format!("failed to build phi: {:?}", e)))?;

        // Check if list is empty
        let current_list = list_phi.as_basic_value().into_pointer_value();
        let tag = self.extract_adt_tag(current_list)?;
        let is_empty = self
            .builder()
            .build_int_compare(
                inkwell::IntPredicate::EQ,
                tag,
                tm.i64_type().const_zero(),
                "is_empty",
            )
            .map_err(|e| CodegenError::Internal(format!("failed to compare: {:?}", e)))?;
        self.builder()
            .build_conditional_branch(is_empty, loop_exit, loop_body)
            .map_err(|e| CodegenError::Internal(format!("failed to build branch: {:?}", e)))?;

        // Loop body: cons head onto accumulator, advance to tail
        self.builder().position_at_end(loop_body);
        let head_ptr = self.extract_adt_field(current_list, 2, 0)?;
        let tail_ptr = self.extract_adt_field(current_list, 2, 1)?;
        let new_acc = self.build_cons(head_ptr.into(), acc_phi.as_basic_value())?;
        self.builder()
            .build_unconditional_branch(loop_header)
            .map_err(|e| CodegenError::Internal(format!("failed to build branch: {:?}", e)))?;

        // Add phi incoming edges
        acc_phi.add_incoming(&[(&nil, entry_block), (&new_acc, loop_body)]);
        list_phi.add_incoming(&[(&list_ptr, entry_block), (&tail_ptr, loop_body)]);

        // Loop exit: return accumulator
        self.builder().position_at_end(loop_exit);
        Ok(Some(acc_phi.as_basic_value()))
    }

    /// Lower `append` / `++` - concatenate two lists.
    fn lower_builtin_append(
        &mut self,
        list1_expr: &Expr,
        list2_expr: &Expr,
    ) -> CodegenResult<Option<BasicValueEnum<'ctx>>> {
        let list1_val = self
            .lower_expr(list1_expr)?
            .ok_or_else(|| CodegenError::Internal("append: list1 has no value".to_string()))?;
        let list2_val = self
            .lower_expr(list2_expr)?
            .ok_or_else(|| CodegenError::Internal("append: list2 has no value".to_string()))?;

        let list1_ptr = match list1_val {
            BasicValueEnum::PointerValue(p) => p,
            _ => return Err(CodegenError::TypeError("append expects a list".to_string())),
        };
        let list2_ptr = match list2_val {
            BasicValueEnum::PointerValue(p) => p,
            _ => return Err(CodegenError::TypeError("append expects a list".to_string())),
        };

        let tm = self.type_mapper();
        let current_fn = self
            .builder()
            .get_insert_block()
            .and_then(|b| b.get_parent())
            .ok_or_else(|| CodegenError::Internal("no current function".to_string()))?;

        // First reverse list1, then fold onto list2
        // This is: append xs ys = foldl (flip (:)) ys (reverse xs)
        // Or equivalently: go [] xs where go acc [] = acc ++ ys; go acc (x:xs) = go (x:acc) xs

        // Step 1: Reverse list1
        let rev_header = self
            .llvm_context()
            .append_basic_block(current_fn, "app_rev_header");
        let rev_body = self
            .llvm_context()
            .append_basic_block(current_fn, "app_rev_body");
        let rev_exit = self
            .llvm_context()
            .append_basic_block(current_fn, "app_rev_exit");
        let fold_header = self
            .llvm_context()
            .append_basic_block(current_fn, "app_fold_header");
        let fold_body = self
            .llvm_context()
            .append_basic_block(current_fn, "app_fold_body");
        let fold_exit = self
            .llvm_context()
            .append_basic_block(current_fn, "app_fold_exit");

        // Start with empty accumulator for reverse
        let nil = self.build_nil()?;
        self.builder()
            .build_unconditional_branch(rev_header)
            .map_err(|e| CodegenError::Internal(format!("failed to build branch: {:?}", e)))?;

        let entry_block = self.builder().get_insert_block().unwrap();

        // Reverse loop header
        self.builder().position_at_end(rev_header);
        let rev_acc_phi = self
            .builder()
            .build_phi(tm.ptr_type(), "rev_acc")
            .map_err(|e| CodegenError::Internal(format!("failed to build phi: {:?}", e)))?;
        let rev_list_phi = self
            .builder()
            .build_phi(tm.ptr_type(), "rev_list")
            .map_err(|e| CodegenError::Internal(format!("failed to build phi: {:?}", e)))?;

        let rev_current = rev_list_phi.as_basic_value().into_pointer_value();
        let rev_tag = self.extract_adt_tag(rev_current)?;
        let rev_is_empty = self
            .builder()
            .build_int_compare(
                inkwell::IntPredicate::EQ,
                rev_tag,
                tm.i64_type().const_zero(),
                "rev_is_empty",
            )
            .map_err(|e| CodegenError::Internal(format!("failed to compare: {:?}", e)))?;
        self.builder()
            .build_conditional_branch(rev_is_empty, rev_exit, rev_body)
            .map_err(|e| CodegenError::Internal(format!("failed to build branch: {:?}", e)))?;

        // Reverse loop body
        self.builder().position_at_end(rev_body);
        let rev_head = self.extract_adt_field(rev_current, 2, 0)?;
        let rev_tail = self.extract_adt_field(rev_current, 2, 1)?;
        let rev_new_acc = self.build_cons(rev_head.into(), rev_acc_phi.as_basic_value())?;
        self.builder()
            .build_unconditional_branch(rev_header)
            .map_err(|e| CodegenError::Internal(format!("failed to build branch: {:?}", e)))?;

        rev_acc_phi.add_incoming(&[(&nil, entry_block), (&rev_new_acc, rev_body)]);
        rev_list_phi.add_incoming(&[(&list1_ptr, entry_block), (&rev_tail, rev_body)]);

        // After reversing, fold reversed list onto list2
        self.builder().position_at_end(rev_exit);
        self.builder()
            .build_unconditional_branch(fold_header)
            .map_err(|e| CodegenError::Internal(format!("failed to build branch: {:?}", e)))?;

        // Fold loop header
        self.builder().position_at_end(fold_header);
        let fold_acc_phi = self
            .builder()
            .build_phi(tm.ptr_type(), "fold_acc")
            .map_err(|e| CodegenError::Internal(format!("failed to build phi: {:?}", e)))?;
        let fold_list_phi = self
            .builder()
            .build_phi(tm.ptr_type(), "fold_list")
            .map_err(|e| CodegenError::Internal(format!("failed to build phi: {:?}", e)))?;

        let fold_current = fold_list_phi.as_basic_value().into_pointer_value();
        let fold_tag = self.extract_adt_tag(fold_current)?;
        let fold_is_empty = self
            .builder()
            .build_int_compare(
                inkwell::IntPredicate::EQ,
                fold_tag,
                tm.i64_type().const_zero(),
                "fold_is_empty",
            )
            .map_err(|e| CodegenError::Internal(format!("failed to compare: {:?}", e)))?;
        self.builder()
            .build_conditional_branch(fold_is_empty, fold_exit, fold_body)
            .map_err(|e| CodegenError::Internal(format!("failed to build branch: {:?}", e)))?;

        // Fold loop body: cons head of reversed list onto accumulator
        self.builder().position_at_end(fold_body);
        let fold_head = self.extract_adt_field(fold_current, 2, 0)?;
        let fold_tail = self.extract_adt_field(fold_current, 2, 1)?;
        let fold_new_acc = self.build_cons(fold_head.into(), fold_acc_phi.as_basic_value())?;
        self.builder()
            .build_unconditional_branch(fold_header)
            .map_err(|e| CodegenError::Internal(format!("failed to build branch: {:?}", e)))?;

        fold_acc_phi.add_incoming(&[(&list2_ptr, rev_exit), (&fold_new_acc, fold_body)]);
        fold_list_phi.add_incoming(&[
            (&rev_acc_phi.as_basic_value().into_pointer_value(), rev_exit),
            (&fold_tail, fold_body),
        ]);

        // Return result
        self.builder().position_at_end(fold_exit);
        Ok(Some(fold_acc_phi.as_basic_value()))
    }

    /// Lower `enumFromTo` - generate a list [from..to].
    fn lower_builtin_enum_from_to(
        &mut self,
        from_expr: &Expr,
        to_expr: &Expr,
    ) -> CodegenResult<Option<BasicValueEnum<'ctx>>> {
        // Check if arguments are user enum constructors
        if let Some(type_name) = self.infer_adt_type_from_expr(from_expr) {
            if self.derived_from_enum_fns.contains_key(&type_name)
                && self.derived_to_enum_fns.contains_key(&type_name)
            {
                return self.lower_enum_from_to_user_enum(from_expr, to_expr, &type_name);
            }
        }

        let from_val = self
            .lower_expr(from_expr)?
            .ok_or_else(|| CodegenError::Internal("enumFromTo: from has no value".to_string()))?;
        let to_val = self
            .lower_expr(to_expr)?
            .ok_or_else(|| CodegenError::Internal("enumFromTo: to has no value".to_string()))?;

        let from = self.to_int_value(from_val)?;
        let to = self.to_int_value(to_val)?;

        let tm = self.type_mapper();
        let current_fn = self
            .builder()
            .get_insert_block()
            .and_then(|b| b.get_parent())
            .ok_or_else(|| CodegenError::Internal("no current function".to_string()))?;

        // Build list backwards from to down to from, then we have [from..to]
        let loop_header = self
            .llvm_context()
            .append_basic_block(current_fn, "enum_header");
        let loop_body = self
            .llvm_context()
            .append_basic_block(current_fn, "enum_body");
        let loop_exit = self
            .llvm_context()
            .append_basic_block(current_fn, "enum_exit");

        // Start with empty list, current = to
        let nil = self.build_nil()?;
        self.builder()
            .build_unconditional_branch(loop_header)
            .map_err(|e| CodegenError::Internal(format!("failed to build branch: {:?}", e)))?;

        let entry_block = self.builder().get_insert_block().unwrap();

        // Loop header
        self.builder().position_at_end(loop_header);
        let acc_phi = self
            .builder()
            .build_phi(tm.ptr_type(), "acc")
            .map_err(|e| CodegenError::Internal(format!("failed to build phi: {:?}", e)))?;
        let current_phi = self
            .builder()
            .build_phi(tm.i64_type(), "current")
            .map_err(|e| CodegenError::Internal(format!("failed to build phi: {:?}", e)))?;

        // Check if current < from (done)
        let current = current_phi.as_basic_value().into_int_value();
        let done = self
            .builder()
            .build_int_compare(inkwell::IntPredicate::SLT, current, from, "done")
            .map_err(|e| CodegenError::Internal(format!("failed to compare: {:?}", e)))?;
        self.builder()
            .build_conditional_branch(done, loop_exit, loop_body)
            .map_err(|e| CodegenError::Internal(format!("failed to build branch: {:?}", e)))?;

        // Loop body: cons current onto accumulator, decrement current
        self.builder().position_at_end(loop_body);
        let boxed_current = self.box_int(current)?;
        let new_acc = self.build_cons(boxed_current.into(), acc_phi.as_basic_value())?;
        let prev_current = self
            .builder()
            .build_int_sub(current, tm.i64_type().const_int(1, false), "prev")
            .map_err(|e| CodegenError::Internal(format!("failed to sub: {:?}", e)))?;
        self.builder()
            .build_unconditional_branch(loop_header)
            .map_err(|e| CodegenError::Internal(format!("failed to build branch: {:?}", e)))?;

        acc_phi.add_incoming(&[(&nil, entry_block), (&new_acc, loop_body)]);
        current_phi.add_incoming(&[(&to, entry_block), (&prev_current, loop_body)]);

        // Return result
        self.builder().position_at_end(loop_exit);
        Ok(Some(acc_phi.as_basic_value()))
    }

    /// Lower `enumFromTo` for user-defined enum types.
    /// Converts from/to to Int via fromEnum, iterates, converts each back via toEnum.
    fn lower_enum_from_to_user_enum(
        &mut self,
        from_expr: &Expr,
        to_expr: &Expr,
        type_name: &str,
    ) -> CodegenResult<Option<BasicValueEnum<'ctx>>> {
        let from_val = self
            .lower_expr(from_expr)?
            .ok_or_else(|| CodegenError::Internal("enumFromTo: from has no value".to_string()))?;
        let to_val = self
            .lower_expr(to_expr)?
            .ok_or_else(|| CodegenError::Internal("enumFromTo: to has no value".to_string()))?;

        // Convert from/to to Int tags via fromEnum
        let from_tag = self.call_derived_from_enum(type_name, from_val)?;
        let to_tag = self.call_derived_from_enum(type_name, to_val)?;

        let ptr_ty = self.type_mapper().ptr_type();
        let i64_ty = self.type_mapper().i64_type();
        let current_fn = self
            .builder()
            .get_insert_block()
            .and_then(|b| b.get_parent())
            .ok_or_else(|| CodegenError::Internal("no current function".to_string()))?;

        // Build list backwards from to_tag down to from_tag
        let loop_header = self
            .llvm_context()
            .append_basic_block(current_fn, "uenum_header");
        let loop_body = self
            .llvm_context()
            .append_basic_block(current_fn, "uenum_body");
        let loop_exit = self
            .llvm_context()
            .append_basic_block(current_fn, "uenum_exit");

        let nil = self.build_nil()?;
        self.builder()
            .build_unconditional_branch(loop_header)
            .map_err(|e| CodegenError::Internal(format!("uenum: branch: {:?}", e)))?;

        let entry_block = self.builder().get_insert_block().unwrap();

        // Loop header
        self.builder().position_at_end(loop_header);
        let acc_phi = self
            .builder()
            .build_phi(ptr_ty, "acc")
            .map_err(|e| CodegenError::Internal(format!("uenum: phi: {:?}", e)))?;
        let current_phi = self
            .builder()
            .build_phi(i64_ty, "current")
            .map_err(|e| CodegenError::Internal(format!("uenum: phi: {:?}", e)))?;

        // Check if current < from_tag (done)
        let current = current_phi.as_basic_value().into_int_value();
        let done = self
            .builder()
            .build_int_compare(inkwell::IntPredicate::SLT, current, from_tag, "done")
            .map_err(|e| CodegenError::Internal(format!("uenum: cmp: {:?}", e)))?;
        self.builder()
            .build_conditional_branch(done, loop_exit, loop_body)
            .map_err(|e| CodegenError::Internal(format!("uenum: branch: {:?}", e)))?;

        // Loop body: convert current tag to enum constructor, cons onto accumulator
        self.builder().position_at_end(loop_body);
        let enum_val = self.call_derived_to_enum(type_name, current)?;
        let new_acc = self.build_cons(enum_val, acc_phi.as_basic_value())?;
        let prev = self
            .builder()
            .build_int_sub(current, i64_ty.const_int(1, false), "prev")
            .map_err(|e| CodegenError::Internal(format!("uenum: sub: {:?}", e)))?;
        self.builder()
            .build_unconditional_branch(loop_header)
            .map_err(|e| CodegenError::Internal(format!("uenum: branch: {:?}", e)))?;

        acc_phi.add_incoming(&[(&nil, entry_block), (&new_acc, loop_body)]);
        current_phi.add_incoming(&[(&to_tag, entry_block), (&prev, loop_body)]);

        // Return result
        self.builder().position_at_end(loop_exit);
        Ok(Some(acc_phi.as_basic_value()))
    }

    /// Lower `replicate` - create a list with n copies of an element.
    fn lower_builtin_replicate(
        &mut self,
        n_expr: &Expr,
        elem_expr: &Expr,
    ) -> CodegenResult<Option<BasicValueEnum<'ctx>>> {
        let n_val = self
            .lower_expr(n_expr)?
            .ok_or_else(|| CodegenError::Internal("replicate: n has no value".to_string()))?;
        let elem_val = self
            .lower_expr(elem_expr)?
            .ok_or_else(|| CodegenError::Internal("replicate: elem has no value".to_string()))?;

        let n = self.to_int_value(n_val)?;
        let elem_ptr = self.value_to_ptr(elem_val)?;

        let tm = self.type_mapper();
        let current_fn = self
            .builder()
            .get_insert_block()
            .and_then(|b| b.get_parent())
            .ok_or_else(|| CodegenError::Internal("no current function".to_string()))?;

        // Build list by consing n times
        let loop_header = self
            .llvm_context()
            .append_basic_block(current_fn, "rep_header");
        let loop_body = self
            .llvm_context()
            .append_basic_block(current_fn, "rep_body");
        let loop_exit = self
            .llvm_context()
            .append_basic_block(current_fn, "rep_exit");

        // Start with empty list
        let nil = self.build_nil()?;
        self.builder()
            .build_unconditional_branch(loop_header)
            .map_err(|e| CodegenError::Internal(format!("failed to build branch: {:?}", e)))?;

        let entry_block = self.builder().get_insert_block().unwrap();

        // Loop header
        self.builder().position_at_end(loop_header);
        let acc_phi = self
            .builder()
            .build_phi(tm.ptr_type(), "acc")
            .map_err(|e| CodegenError::Internal(format!("failed to build phi: {:?}", e)))?;
        let count_phi = self
            .builder()
            .build_phi(tm.i64_type(), "count")
            .map_err(|e| CodegenError::Internal(format!("failed to build phi: {:?}", e)))?;

        // Check if count <= 0
        let count = count_phi.as_basic_value().into_int_value();
        let done = self
            .builder()
            .build_int_compare(
                inkwell::IntPredicate::SLE,
                count,
                tm.i64_type().const_zero(),
                "done",
            )
            .map_err(|e| CodegenError::Internal(format!("failed to compare: {:?}", e)))?;
        self.builder()
            .build_conditional_branch(done, loop_exit, loop_body)
            .map_err(|e| CodegenError::Internal(format!("failed to build branch: {:?}", e)))?;

        // Loop body: cons elem onto accumulator, decrement count
        self.builder().position_at_end(loop_body);
        let new_acc = self.build_cons(elem_ptr.into(), acc_phi.as_basic_value())?;
        let new_count = self
            .builder()
            .build_int_sub(count, tm.i64_type().const_int(1, false), "new_count")
            .map_err(|e| CodegenError::Internal(format!("failed to sub: {:?}", e)))?;
        self.builder()
            .build_unconditional_branch(loop_header)
            .map_err(|e| CodegenError::Internal(format!("failed to build branch: {:?}", e)))?;

        acc_phi.add_incoming(&[(&nil, entry_block), (&new_acc, loop_body)]);
        count_phi.add_incoming(&[(&n, entry_block), (&new_count, loop_body)]);

        // Return result
        self.builder().position_at_end(loop_exit);
        Ok(Some(acc_phi.as_basic_value()))
    }

    /// Build an empty list (Nil / []).
    fn build_nil(&self) -> CodegenResult<inkwell::values::PointerValue<'ctx>> {
        let tm = self.type_mapper();
        // Nil is represented as ADT with tag 0 and no fields
        // Allocate space: tag (i64) only
        let size = tm.i64_type().const_int(8, false);
        let alloc_fn = self
            .functions
            .get(&VarId::new(1000005))
            .ok_or_else(|| CodegenError::Internal("bhc_alloc not declared".to_string()))?;
        let alloc_call = self
            .builder()
            .build_call(*alloc_fn, &[size.into()], "nil_alloc")
            .map_err(|e| CodegenError::Internal(format!("failed to call alloc: {:?}", e)))?;
        let nil_ptr = alloc_call
            .try_as_basic_value()
            .basic()
            .ok_or_else(|| CodegenError::Internal("alloc returned void".to_string()))?
            .into_pointer_value();

        // Store tag = 0
        self.builder()
            .build_store(nil_ptr, tm.i64_type().const_zero())
            .map_err(|e| CodegenError::Internal(format!("failed to store tag: {:?}", e)))?;

        Ok(nil_ptr)
    }

    /// Build a cons cell (x : xs).
    fn build_cons(
        &self,
        head: BasicValueEnum<'ctx>,
        tail: BasicValueEnum<'ctx>,
    ) -> CodegenResult<inkwell::values::PointerValue<'ctx>> {
        let tm = self.type_mapper();
        // Cons is represented as ADT with tag 1 and 2 fields (head, tail)
        // Allocate space: tag (i64) + head (ptr) + tail (ptr) = 24 bytes
        let size = tm.i64_type().const_int(24, false);
        let alloc_fn = self
            .functions
            .get(&VarId::new(1000005))
            .ok_or_else(|| CodegenError::Internal("bhc_alloc not declared".to_string()))?;
        let alloc_call = self
            .builder()
            .build_call(*alloc_fn, &[size.into()], "cons_alloc")
            .map_err(|e| CodegenError::Internal(format!("failed to call alloc: {:?}", e)))?;
        let cons_ptr = alloc_call
            .try_as_basic_value()
            .basic()
            .ok_or_else(|| CodegenError::Internal("alloc returned void".to_string()))?
            .into_pointer_value();

        // Store tag = 1
        self.builder()
            .build_store(cons_ptr, tm.i64_type().const_int(1, false))
            .map_err(|e| CodegenError::Internal(format!("failed to store tag: {:?}", e)))?;

        // Store head at offset 8
        let head_ptr = unsafe {
            self.builder()
                .build_gep(
                    tm.i64_type(),
                    cons_ptr,
                    &[tm.i64_type().const_int(1, false)],
                    "head_ptr",
                )
                .map_err(|e| CodegenError::Internal(format!("failed to build gep: {:?}", e)))?
        };
        let head_as_ptr = self.value_to_ptr(head)?;
        self.builder()
            .build_store(head_ptr, head_as_ptr)
            .map_err(|e| CodegenError::Internal(format!("failed to store head: {:?}", e)))?;

        // Store tail at offset 16
        let tail_slot = unsafe {
            self.builder()
                .build_gep(
                    tm.i64_type(),
                    cons_ptr,
                    &[tm.i64_type().const_int(2, false)],
                    "tail_ptr",
                )
                .map_err(|e| CodegenError::Internal(format!("failed to build gep: {:?}", e)))?
        };
        let tail_as_ptr = self.value_to_ptr(tail)?;
        self.builder()
            .build_store(tail_slot, tail_as_ptr)
            .map_err(|e| CodegenError::Internal(format!("failed to store tail: {:?}", e)))?;

        Ok(cons_ptr)
    }

    /// Convert a C-string (null-terminated i8 pointer) into a [Char] linked list.
    /// Emits LLVM IR that loops over the C-string bytes and builds cons cells.
    fn cstring_to_char_list(
        &self,
        cstr_ptr: inkwell::values::PointerValue<'ctx>,
    ) -> CodegenResult<inkwell::values::PointerValue<'ctx>> {
        let tm = self.type_mapper();
        let current_fn = self
            .builder()
            .get_insert_block()
            .and_then(|b| b.get_parent())
            .ok_or_else(|| {
                CodegenError::Internal("no current function for cstring_to_char_list".to_string())
            })?;

        // Guard: if the C-string pointer is null (e.g. exception sentinel from bhc_throw),
        // return nil (empty list) so execution can continue back to bhc_catch.
        let null_path_block = self
            .llvm_context()
            .append_basic_block(current_fn, "c2l_null_path");
        let convert_block = self
            .llvm_context()
            .append_basic_block(current_fn, "c2l_convert");
        let merge_block = self
            .llvm_context()
            .append_basic_block(current_fn, "c2l_merge");

        let is_null_ptr = self
            .builder()
            .build_is_null(cstr_ptr, "c2l_ptr_null")
            .map_err(|e| CodegenError::Internal(format!("c2l is_null: {:?}", e)))?;
        self.builder()
            .build_conditional_branch(is_null_ptr, null_path_block, convert_block)
            .map_err(|e| CodegenError::Internal(format!("c2l null branch: {:?}", e)))?;

        // Null path: build nil and jump to merge
        self.builder().position_at_end(null_path_block);
        let null_nil = self.build_nil()?;
        let null_nil_block = self.builder().get_insert_block().unwrap();
        self.builder()
            .build_unconditional_branch(merge_block)
            .map_err(|e| CodegenError::Internal(format!("c2l null nil branch: {:?}", e)))?;

        // Non-null path: do the conversion
        self.builder().position_at_end(convert_block);

        // Strategy: compute strlen, then iterate backwards building the list.

        // --- Compute strlen ---
        let strlen_header = self
            .llvm_context()
            .append_basic_block(current_fn, "c2l_strlen_hdr");
        let strlen_body = self
            .llvm_context()
            .append_basic_block(current_fn, "c2l_strlen_body");
        let strlen_done = self
            .llvm_context()
            .append_basic_block(current_fn, "c2l_strlen_done");

        // Capture the block we're in BEFORE branching
        let pre_strlen_block = self.builder().get_insert_block().unwrap();
        self.builder()
            .build_unconditional_branch(strlen_header)
            .map_err(|e| CodegenError::Internal(format!("c2l branch: {:?}", e)))?;

        self.builder().position_at_end(strlen_header);
        let idx_phi = self
            .builder()
            .build_phi(tm.i64_type(), "c2l_idx")
            .map_err(|e| CodegenError::Internal(format!("c2l phi: {:?}", e)))?;
        idx_phi.add_incoming(&[(&tm.i64_type().const_zero(), pre_strlen_block)]);

        let byte_ptr = unsafe {
            self.builder()
                .build_gep(
                    tm.i8_type(),
                    cstr_ptr,
                    &[idx_phi.as_basic_value().into_int_value()],
                    "c2l_byte_ptr",
                )
                .map_err(|e| CodegenError::Internal(format!("c2l gep: {:?}", e)))?
        };
        let byte_val = self
            .builder()
            .build_load(tm.i8_type(), byte_ptr, "c2l_byte")
            .map_err(|e| CodegenError::Internal(format!("c2l load: {:?}", e)))?
            .into_int_value();
        let is_null = self
            .builder()
            .build_int_compare(
                inkwell::IntPredicate::EQ,
                byte_val,
                tm.i8_type().const_zero(),
                "c2l_is_null",
            )
            .map_err(|e| CodegenError::Internal(format!("c2l cmp: {:?}", e)))?;
        self.builder()
            .build_conditional_branch(is_null, strlen_done, strlen_body)
            .map_err(|e| CodegenError::Internal(format!("c2l branch: {:?}", e)))?;

        self.builder().position_at_end(strlen_body);
        let next_idx = self
            .builder()
            .build_int_add(
                idx_phi.as_basic_value().into_int_value(),
                tm.i64_type().const_int(1, false),
                "c2l_next",
            )
            .map_err(|e| CodegenError::Internal(format!("c2l add: {:?}", e)))?;
        idx_phi.add_incoming(&[(&next_idx, strlen_body)]);
        self.builder()
            .build_unconditional_branch(strlen_header)
            .map_err(|e| CodegenError::Internal(format!("c2l branch: {:?}", e)))?;

        // strlen_done: idx_phi contains the length
        self.builder().position_at_end(strlen_done);
        let len = idx_phi.as_basic_value().into_int_value();

        // --- Build list backwards from len-1 to 0 ---
        let build_header = self
            .llvm_context()
            .append_basic_block(current_fn, "c2l_build_hdr");
        let build_body = self
            .llvm_context()
            .append_basic_block(current_fn, "c2l_build_body");
        let build_done = self
            .llvm_context()
            .append_basic_block(current_fn, "c2l_build_done");

        let nil = self.build_nil()?;

        // Capture block before branching (may differ from strlen_done due to build_nil alloc)
        let pre_build_block = self.builder().get_insert_block().unwrap();
        self.builder()
            .build_unconditional_branch(build_header)
            .map_err(|e| CodegenError::Internal(format!("c2l branch: {:?}", e)))?;

        self.builder().position_at_end(build_header);
        let build_idx = self
            .builder()
            .build_phi(tm.i64_type(), "c2l_bidx")
            .map_err(|e| CodegenError::Internal(format!("c2l phi: {:?}", e)))?;
        let build_list = self
            .builder()
            .build_phi(tm.ptr_type(), "c2l_list")
            .map_err(|e| CodegenError::Internal(format!("c2l phi: {:?}", e)))?;

        build_idx.add_incoming(&[(&len, pre_build_block)]);
        build_list.add_incoming(&[(&nil, pre_build_block)]);

        let is_done = self
            .builder()
            .build_int_compare(
                inkwell::IntPredicate::EQ,
                build_idx.as_basic_value().into_int_value(),
                tm.i64_type().const_zero(),
                "c2l_done",
            )
            .map_err(|e| CodegenError::Internal(format!("c2l cmp: {:?}", e)))?;
        self.builder()
            .build_conditional_branch(is_done, build_done, build_body)
            .map_err(|e| CodegenError::Internal(format!("c2l branch: {:?}", e)))?;

        self.builder().position_at_end(build_body);
        let prev_idx = self
            .builder()
            .build_int_sub(
                build_idx.as_basic_value().into_int_value(),
                tm.i64_type().const_int(1, false),
                "c2l_prev",
            )
            .map_err(|e| CodegenError::Internal(format!("c2l sub: {:?}", e)))?;
        let char_ptr = unsafe {
            self.builder()
                .build_gep(tm.i8_type(), cstr_ptr, &[prev_idx], "c2l_char_ptr")
                .map_err(|e| CodegenError::Internal(format!("c2l gep: {:?}", e)))?
        };
        let char_byte = self
            .builder()
            .build_load(tm.i8_type(), char_ptr, "c2l_char")
            .map_err(|e| CodegenError::Internal(format!("c2l load: {:?}", e)))?
            .into_int_value();
        let char_i64 = self
            .builder()
            .build_int_z_extend(char_byte, tm.i64_type(), "c2l_char_ext")
            .map_err(|e| CodegenError::Internal(format!("c2l ext: {:?}", e)))?;
        let char_as_ptr = self.int_to_ptr(char_i64)?;

        let new_cons = self.build_cons(char_as_ptr.into(), build_list.as_basic_value())?;
        // Capture the current block AFTER build_cons (which creates new blocks)
        let build_body_end = self.builder().get_insert_block().unwrap();
        build_idx.add_incoming(&[(&prev_idx, build_body_end)]);
        build_list.add_incoming(&[(&new_cons, build_body_end)]);

        self.builder()
            .build_unconditional_branch(build_header)
            .map_err(|e| CodegenError::Internal(format!("c2l branch: {:?}", e)))?;

        self.builder().position_at_end(build_done);
        let converted_list = build_list.as_basic_value().into_pointer_value();
        self.builder()
            .build_unconditional_branch(merge_block)
            .map_err(|e| CodegenError::Internal(format!("c2l done branch: {:?}", e)))?;

        // Merge: PHI between null path (nil) and convert path (converted list)
        self.builder().position_at_end(merge_block);
        let result_phi = self
            .builder()
            .build_phi(tm.ptr_type(), "c2l_result")
            .map_err(|e| CodegenError::Internal(format!("c2l merge phi: {:?}", e)))?;
        result_phi.add_incoming(&[(&null_nil, null_nil_block), (&converted_list, build_done)]);

        Ok(result_phi.as_basic_value().into_pointer_value())
    }

    /// Convert a [Char] linked list into a C-string (null-terminated i8 pointer).
    /// Emits LLVM IR that first counts the list length, allocates a buffer, then fills it.
    fn char_list_to_cstring(
        &self,
        list_ptr: inkwell::values::PointerValue<'ctx>,
    ) -> CodegenResult<inkwell::values::PointerValue<'ctx>> {
        let tm = self.type_mapper();
        let current_fn = self
            .builder()
            .get_insert_block()
            .and_then(|b| b.get_parent())
            .ok_or_else(|| {
                CodegenError::Internal("no current function for char_list_to_cstring".to_string())
            })?;

        // --- Count list length ---
        let len_header = self
            .llvm_context()
            .append_basic_block(current_fn, "l2c_len_hdr");
        let len_body = self
            .llvm_context()
            .append_basic_block(current_fn, "l2c_len_body");
        let len_done = self
            .llvm_context()
            .append_basic_block(current_fn, "l2c_len_done");

        let pre_len_block = self.builder().get_insert_block().unwrap();
        self.builder()
            .build_unconditional_branch(len_header)
            .map_err(|e| CodegenError::Internal(format!("l2c branch: {:?}", e)))?;

        self.builder().position_at_end(len_header);
        let len_node = self
            .builder()
            .build_phi(tm.ptr_type(), "l2c_node")
            .map_err(|e| CodegenError::Internal(format!("l2c phi: {:?}", e)))?;
        let len_count = self
            .builder()
            .build_phi(tm.i64_type(), "l2c_count")
            .map_err(|e| CodegenError::Internal(format!("l2c phi: {:?}", e)))?;
        len_node.add_incoming(&[(&list_ptr, pre_len_block)]);
        len_count.add_incoming(&[(&tm.i64_type().const_zero(), pre_len_block)]);

        let tag = self.extract_adt_tag(len_node.as_basic_value().into_pointer_value())?;
        let is_nil = self
            .builder()
            .build_int_compare(
                inkwell::IntPredicate::EQ,
                tag,
                tm.i64_type().const_zero(),
                "l2c_nil",
            )
            .map_err(|e| CodegenError::Internal(format!("l2c cmp: {:?}", e)))?;
        self.builder()
            .build_conditional_branch(is_nil, len_done, len_body)
            .map_err(|e| CodegenError::Internal(format!("l2c branch: {:?}", e)))?;

        self.builder().position_at_end(len_body);
        let next_count = self
            .builder()
            .build_int_add(
                len_count.as_basic_value().into_int_value(),
                tm.i64_type().const_int(1, false),
                "l2c_inc",
            )
            .map_err(|e| CodegenError::Internal(format!("l2c add: {:?}", e)))?;
        let tail = self.extract_adt_field(len_node.as_basic_value().into_pointer_value(), 2, 1)?;
        len_node.add_incoming(&[(&tail, len_body)]);
        len_count.add_incoming(&[(&next_count, len_body)]);
        self.builder()
            .build_unconditional_branch(len_header)
            .map_err(|e| CodegenError::Internal(format!("l2c branch: {:?}", e)))?;

        // --- Allocate buffer (len + 1 for null terminator) ---
        self.builder().position_at_end(len_done);
        let total_len = len_count.as_basic_value().into_int_value();
        let buf_size = self
            .builder()
            .build_int_add(total_len, tm.i64_type().const_int(1, false), "l2c_bufsize")
            .map_err(|e| CodegenError::Internal(format!("l2c add: {:?}", e)))?;
        let alloc_fn = self
            .functions
            .get(&VarId::new(1000005))
            .ok_or_else(|| CodegenError::Internal("bhc_alloc not declared".to_string()))?;
        let buf = self
            .builder()
            .build_call(*alloc_fn, &[buf_size.into()], "l2c_buf")
            .map_err(|e| CodegenError::Internal(format!("l2c alloc: {:?}", e)))?
            .try_as_basic_value()
            .basic()
            .ok_or_else(|| CodegenError::Internal("l2c alloc void".to_string()))?
            .into_pointer_value();

        // --- Fill buffer ---
        let fill_header = self
            .llvm_context()
            .append_basic_block(current_fn, "l2c_fill_hdr");
        let fill_body = self
            .llvm_context()
            .append_basic_block(current_fn, "l2c_fill_body");
        let fill_done = self
            .llvm_context()
            .append_basic_block(current_fn, "l2c_fill_done");

        let pre_fill_block = self.builder().get_insert_block().unwrap();
        self.builder()
            .build_unconditional_branch(fill_header)
            .map_err(|e| CodegenError::Internal(format!("l2c branch: {:?}", e)))?;

        self.builder().position_at_end(fill_header);
        let fill_node = self
            .builder()
            .build_phi(tm.ptr_type(), "l2c_fnode")
            .map_err(|e| CodegenError::Internal(format!("l2c phi: {:?}", e)))?;
        let fill_idx = self
            .builder()
            .build_phi(tm.i64_type(), "l2c_fidx")
            .map_err(|e| CodegenError::Internal(format!("l2c phi: {:?}", e)))?;
        fill_node.add_incoming(&[(&list_ptr, pre_fill_block)]);
        fill_idx.add_incoming(&[(&tm.i64_type().const_zero(), pre_fill_block)]);

        let ftag = self.extract_adt_tag(fill_node.as_basic_value().into_pointer_value())?;
        let fis_nil = self
            .builder()
            .build_int_compare(
                inkwell::IntPredicate::EQ,
                ftag,
                tm.i64_type().const_zero(),
                "l2c_fnil",
            )
            .map_err(|e| CodegenError::Internal(format!("l2c cmp: {:?}", e)))?;
        self.builder()
            .build_conditional_branch(fis_nil, fill_done, fill_body)
            .map_err(|e| CodegenError::Internal(format!("l2c branch: {:?}", e)))?;

        self.builder().position_at_end(fill_body);
        let head = self.extract_adt_field(fill_node.as_basic_value().into_pointer_value(), 2, 0)?;
        let char_val = self
            .builder()
            .build_ptr_to_int(head, tm.i64_type(), "l2c_char")
            .map_err(|e| CodegenError::Internal(format!("l2c ptr2int: {:?}", e)))?;
        let char_i8 = self
            .builder()
            .build_int_truncate(char_val, tm.i8_type(), "l2c_i8")
            .map_err(|e| CodegenError::Internal(format!("l2c trunc: {:?}", e)))?;
        let dst = unsafe {
            self.builder()
                .build_gep(
                    tm.i8_type(),
                    buf,
                    &[fill_idx.as_basic_value().into_int_value()],
                    "l2c_dst",
                )
                .map_err(|e| CodegenError::Internal(format!("l2c gep: {:?}", e)))?
        };
        self.builder()
            .build_store(dst, char_i8)
            .map_err(|e| CodegenError::Internal(format!("l2c store: {:?}", e)))?;

        let next_fidx = self
            .builder()
            .build_int_add(
                fill_idx.as_basic_value().into_int_value(),
                tm.i64_type().const_int(1, false),
                "l2c_fnext",
            )
            .map_err(|e| CodegenError::Internal(format!("l2c add: {:?}", e)))?;
        let ftail =
            self.extract_adt_field(fill_node.as_basic_value().into_pointer_value(), 2, 1)?;
        fill_node.add_incoming(&[(&ftail, fill_body)]);
        fill_idx.add_incoming(&[(&next_fidx, fill_body)]);
        self.builder()
            .build_unconditional_branch(fill_header)
            .map_err(|e| CodegenError::Internal(format!("l2c branch: {:?}", e)))?;

        // --- Null-terminate ---
        self.builder().position_at_end(fill_done);
        let null_dst = unsafe {
            self.builder()
                .build_gep(
                    tm.i8_type(),
                    buf,
                    &[fill_idx.as_basic_value().into_int_value()],
                    "l2c_null",
                )
                .map_err(|e| CodegenError::Internal(format!("l2c gep: {:?}", e)))?
        };
        self.builder()
            .build_store(null_dst, tm.i8_type().const_zero())
            .map_err(|e| CodegenError::Internal(format!("l2c store: {:?}", e)))?;

        Ok(buf)
    }

    /// Convert a cons-cell list of C-strings into a cons-cell list of [Char] lists.
    /// Used for converting the result of RTS functions like bhc_string_lines/words.
    fn cstring_list_to_char_list_list(
        &self,
        list_ptr: inkwell::values::PointerValue<'ctx>,
    ) -> CodegenResult<inkwell::values::PointerValue<'ctx>> {
        let tm = self.type_mapper();
        let current_fn = self
            .builder()
            .get_insert_block()
            .and_then(|b| b.get_parent())
            .ok_or_else(|| CodegenError::Internal("no current function".to_string()))?;

        let walk_header = self
            .llvm_context()
            .append_basic_block(current_fn, "csll_walk_hdr");
        let walk_body = self
            .llvm_context()
            .append_basic_block(current_fn, "csll_walk_body");
        let walk_done = self
            .llvm_context()
            .append_basic_block(current_fn, "csll_walk_done");

        let init_nil = self.build_nil()?;
        let pre_walk_block = self.builder().get_insert_block().unwrap();
        self.builder()
            .build_unconditional_branch(walk_header)
            .map_err(|e| CodegenError::Internal(format!("csll branch: {:?}", e)))?;

        self.builder().position_at_end(walk_header);
        let w_node = self
            .builder()
            .build_phi(tm.ptr_type(), "csll_node")
            .map_err(|e| CodegenError::Internal(format!("csll phi: {:?}", e)))?;
        let w_acc = self
            .builder()
            .build_phi(tm.ptr_type(), "csll_acc")
            .map_err(|e| CodegenError::Internal(format!("csll phi: {:?}", e)))?;
        w_node.add_incoming(&[(&list_ptr, pre_walk_block)]);
        w_acc.add_incoming(&[(&init_nil, pre_walk_block)]);

        let tag = self.extract_adt_tag(w_node.as_basic_value().into_pointer_value())?;
        let is_nil = self
            .builder()
            .build_int_compare(
                inkwell::IntPredicate::EQ,
                tag,
                tm.i64_type().const_zero(),
                "csll_nil",
            )
            .map_err(|e| CodegenError::Internal(format!("csll cmp: {:?}", e)))?;
        self.builder()
            .build_conditional_branch(is_nil, walk_done, walk_body)
            .map_err(|e| CodegenError::Internal(format!("csll branch: {:?}", e)))?;

        self.builder().position_at_end(walk_body);
        let cstr_head =
            self.extract_adt_field(w_node.as_basic_value().into_pointer_value(), 2, 0)?;
        let char_list = self.cstring_to_char_list(cstr_head)?;
        let new_acc = self.build_cons(char_list.into(), w_acc.as_basic_value())?;
        let w_tail = self.extract_adt_field(w_node.as_basic_value().into_pointer_value(), 2, 1)?;

        let walk_body_end = self.builder().get_insert_block().unwrap();
        w_node.add_incoming(&[(&w_tail, walk_body_end)]);
        w_acc.add_incoming(&[(&new_acc, walk_body_end)]);
        self.builder()
            .build_unconditional_branch(walk_header)
            .map_err(|e| CodegenError::Internal(format!("csll branch: {:?}", e)))?;

        self.builder().position_at_end(walk_done);
        let reversed = w_acc.as_basic_value().into_pointer_value();
        self.reverse_list(reversed)
    }

    /// Reverse a cons-cell linked list (builds a new reversed list).
    fn reverse_list(
        &self,
        list_ptr: inkwell::values::PointerValue<'ctx>,
    ) -> CodegenResult<inkwell::values::PointerValue<'ctx>> {
        let tm = self.type_mapper();
        let current_fn = self
            .builder()
            .get_insert_block()
            .and_then(|b| b.get_parent())
            .ok_or_else(|| {
                CodegenError::Internal("no current function for reverse_list".to_string())
            })?;

        let rev_header = self
            .llvm_context()
            .append_basic_block(current_fn, "rev_hdr");
        let rev_body = self
            .llvm_context()
            .append_basic_block(current_fn, "rev_body");
        let rev_done = self
            .llvm_context()
            .append_basic_block(current_fn, "rev_done");

        let nil = self.build_nil()?;
        let pre_rev_block = self.builder().get_insert_block().unwrap();
        self.builder()
            .build_unconditional_branch(rev_header)
            .map_err(|e| CodegenError::Internal(format!("rev branch: {:?}", e)))?;

        self.builder().position_at_end(rev_header);
        let r_node = self
            .builder()
            .build_phi(tm.ptr_type(), "rev_node")
            .map_err(|e| CodegenError::Internal(format!("rev phi: {:?}", e)))?;
        let r_acc = self
            .builder()
            .build_phi(tm.ptr_type(), "rev_acc")
            .map_err(|e| CodegenError::Internal(format!("rev phi: {:?}", e)))?;
        r_node.add_incoming(&[(&list_ptr, pre_rev_block)]);
        r_acc.add_incoming(&[(&nil, pre_rev_block)]);

        let tag = self.extract_adt_tag(r_node.as_basic_value().into_pointer_value())?;
        let is_nil = self
            .builder()
            .build_int_compare(
                inkwell::IntPredicate::EQ,
                tag,
                tm.i64_type().const_zero(),
                "rev_nil",
            )
            .map_err(|e| CodegenError::Internal(format!("rev cmp: {:?}", e)))?;
        self.builder()
            .build_conditional_branch(is_nil, rev_done, rev_body)
            .map_err(|e| CodegenError::Internal(format!("rev branch: {:?}", e)))?;

        self.builder().position_at_end(rev_body);
        let head = self.extract_adt_field(r_node.as_basic_value().into_pointer_value(), 2, 0)?;
        let tail = self.extract_adt_field(r_node.as_basic_value().into_pointer_value(), 2, 1)?;
        let new_cons = self.build_cons(head.into(), r_acc.as_basic_value())?;
        let rev_body_end = self.builder().get_insert_block().unwrap();
        r_node.add_incoming(&[(&tail, rev_body_end)]);
        r_acc.add_incoming(&[(&new_cons, rev_body_end)]);
        self.builder()
            .build_unconditional_branch(rev_header)
            .map_err(|e| CodegenError::Internal(format!("rev branch: {:?}", e)))?;

        self.builder().position_at_end(rev_done);
        Ok(r_acc.as_basic_value().into_pointer_value())
    }

    /// Convert a [Char] list of [Char] lists to a cons-cell list of C-strings.
    /// Used for passing list-of-strings to RTS functions like bhc_string_unlines/unwords.
    fn char_list_list_to_cstring_list(
        &self,
        list_ptr: inkwell::values::PointerValue<'ctx>,
    ) -> CodegenResult<inkwell::values::PointerValue<'ctx>> {
        let tm = self.type_mapper();
        let current_fn = self
            .builder()
            .get_insert_block()
            .and_then(|b| b.get_parent())
            .ok_or_else(|| CodegenError::Internal("no current function".to_string()))?;

        let walk_header = self
            .llvm_context()
            .append_basic_block(current_fn, "lccs_walk_hdr");
        let walk_body = self
            .llvm_context()
            .append_basic_block(current_fn, "lccs_walk_body");
        let walk_done = self
            .llvm_context()
            .append_basic_block(current_fn, "lccs_walk_done");

        let init_nil = self.build_nil()?;
        let pre_walk_block = self.builder().get_insert_block().unwrap();
        self.builder()
            .build_unconditional_branch(walk_header)
            .map_err(|e| CodegenError::Internal(format!("lccs branch: {:?}", e)))?;

        self.builder().position_at_end(walk_header);
        let w_node = self
            .builder()
            .build_phi(tm.ptr_type(), "lccs_node")
            .map_err(|e| CodegenError::Internal(format!("lccs phi: {:?}", e)))?;
        let w_acc = self
            .builder()
            .build_phi(tm.ptr_type(), "lccs_acc")
            .map_err(|e| CodegenError::Internal(format!("lccs phi: {:?}", e)))?;
        w_node.add_incoming(&[(&list_ptr, pre_walk_block)]);
        w_acc.add_incoming(&[(&init_nil, pre_walk_block)]);

        let tag = self.extract_adt_tag(w_node.as_basic_value().into_pointer_value())?;
        let is_nil = self
            .builder()
            .build_int_compare(
                inkwell::IntPredicate::EQ,
                tag,
                tm.i64_type().const_zero(),
                "lccs_nil",
            )
            .map_err(|e| CodegenError::Internal(format!("lccs cmp: {:?}", e)))?;
        self.builder()
            .build_conditional_branch(is_nil, walk_done, walk_body)
            .map_err(|e| CodegenError::Internal(format!("lccs branch: {:?}", e)))?;

        self.builder().position_at_end(walk_body);
        let char_list_head =
            self.extract_adt_field(w_node.as_basic_value().into_pointer_value(), 2, 0)?;
        let cstr = self.char_list_to_cstring(char_list_head)?;
        let new_acc = self.build_cons(cstr.into(), w_acc.as_basic_value())?;
        let w_tail = self.extract_adt_field(w_node.as_basic_value().into_pointer_value(), 2, 1)?;
        let walk_body_end = self.builder().get_insert_block().unwrap();
        w_node.add_incoming(&[(&w_tail, walk_body_end)]);
        w_acc.add_incoming(&[(&new_acc, walk_body_end)]);
        self.builder()
            .build_unconditional_branch(walk_header)
            .map_err(|e| CodegenError::Internal(format!("lccs branch: {:?}", e)))?;

        self.builder().position_at_end(walk_done);
        let reversed = w_acc.as_basic_value().into_pointer_value();
        self.reverse_list(reversed)
    }

    /// Try to extract a map application from an expression.
    /// Returns Some((map_fn, inner_list)) if expr is `map f xs`, None otherwise.
    ///
    /// This is used for fusion opportunities like `sum (map f xs)`.
    #[allow(unused_variables)]
    fn try_extract_map_app(_expr: &Expr) -> Option<(&Expr, &Expr)> {
        // TODO: Implement pattern matching for map applications
        // For now, return None to skip fusion and use the non-fused path
        None
    }

    /// Lower a fused sum(map f xs) into a single loop.
    /// This avoids allocating an intermediate list.
    #[allow(unused_variables)]
    fn lower_fused_sum_map(
        &mut self,
        _map_fn: &Expr,
        _inner_list: &Expr,
    ) -> CodegenResult<Option<BasicValueEnum<'ctx>>> {
        // TODO: Implement fused sum/map lowering
        // This should generate a single loop that applies map_fn to each element
        // and accumulates the sum, without creating an intermediate list.
        Err(CodegenError::Internal(
            "fused sum/map not yet implemented".to_string(),
        ))
    }

    /// Lower `sum` - sum all elements of a list.
    /// sum [] = 0
    /// sum (x:xs) = x + sum xs
    fn lower_builtin_sum(
        &mut self,
        list_expr: &Expr,
    ) -> CodegenResult<Option<BasicValueEnum<'ctx>>> {
        // Check for fusion opportunity: sum (map f xs)
        // This fuses into a single loop that applies f to each element and sums
        if let Some((map_fn, inner_list)) = Self::try_extract_map_app(list_expr) {
            return self.lower_fused_sum_map(map_fn, inner_list);
        }

        let list_val = self
            .lower_expr(list_expr)?
            .ok_or_else(|| CodegenError::Internal("sum: list has no value".to_string()))?;

        let list_ptr = match list_val {
            BasicValueEnum::PointerValue(p) => p,
            _ => return Err(CodegenError::TypeError("sum expects a list".to_string())),
        };

        let tm = self.type_mapper();
        let current_fn = self
            .builder()
            .get_insert_block()
            .and_then(|b| b.get_parent())
            .ok_or_else(|| CodegenError::Internal("no current function".to_string()))?;

        let entry_block = self
            .builder()
            .get_insert_block()
            .ok_or_else(|| CodegenError::Internal("no current block".to_string()))?;
        let loop_header = self.llvm_ctx.append_basic_block(current_fn, "sum_header");
        let loop_body = self.llvm_ctx.append_basic_block(current_fn, "sum_body");
        let loop_exit = self.llvm_ctx.append_basic_block(current_fn, "sum_exit");

        // Jump to loop header
        self.builder()
            .build_unconditional_branch(loop_header)
            .map_err(|e| CodegenError::Internal(format!("failed to build branch: {:?}", e)))?;

        // Loop header: phi for accumulator and current list pointer
        self.builder().position_at_end(loop_header);
        let acc_phi = self
            .builder()
            .build_phi(tm.i64_type(), "sum_acc")
            .map_err(|e| CodegenError::Internal(format!("failed to build phi: {:?}", e)))?;
        let list_phi = self
            .builder()
            .build_phi(tm.ptr_type(), "sum_list")
            .map_err(|e| CodegenError::Internal(format!("failed to build phi: {:?}", e)))?;

        // Check if list is empty (tag == 0)
        let tag = self.extract_adt_tag(list_phi.as_basic_value().into_pointer_value())?;
        let is_empty = self
            .builder()
            .build_int_compare(
                inkwell::IntPredicate::EQ,
                tag,
                tm.i64_type().const_zero(),
                "is_empty",
            )
            .map_err(|e| CodegenError::Internal(format!("failed to compare tag: {:?}", e)))?;

        self.builder()
            .build_conditional_branch(is_empty, loop_exit, loop_body)
            .map_err(|e| CodegenError::Internal(format!("failed to build branch: {:?}", e)))?;

        // Loop body: extract head, add to accumulator, continue with tail
        self.builder().position_at_end(loop_body);
        let head_ptr =
            self.extract_adt_field(list_phi.as_basic_value().into_pointer_value(), 2, 0)?;
        let head_val = self
            .builder()
            .build_ptr_to_int(head_ptr, tm.i64_type(), "head_val")
            .map_err(|e| CodegenError::Internal(format!("failed to ptr_to_int: {:?}", e)))?;
        let tail_ptr =
            self.extract_adt_field(list_phi.as_basic_value().into_pointer_value(), 2, 1)?;

        let new_acc = self
            .builder()
            .build_int_add(
                acc_phi.as_basic_value().into_int_value(),
                head_val,
                "new_acc",
            )
            .map_err(|e| CodegenError::Internal(format!("failed to add: {:?}", e)))?;

        self.builder()
            .build_unconditional_branch(loop_header)
            .map_err(|e| CodegenError::Internal(format!("failed to build branch: {:?}", e)))?;

        // Add incoming values to phi nodes
        acc_phi.add_incoming(&[
            (&tm.i64_type().const_zero(), entry_block),
            (&new_acc, loop_body),
        ]);
        list_phi.add_incoming(&[(&list_ptr, entry_block), (&tail_ptr, loop_body)]);

        // Exit: return accumulator (boxed as pointer)
        self.builder().position_at_end(loop_exit);
        let result = self
            .builder()
            .build_int_to_ptr(
                acc_phi.as_basic_value().into_int_value(),
                tm.ptr_type(),
                "result",
            )
            .map_err(|e| CodegenError::Internal(format!("failed to int_to_ptr: {:?}", e)))?;
        Ok(Some(result.into()))
    }

    /// Lower `product` - multiply all elements of a list.
    /// product [] = 1
    /// product (x:xs) = x * product xs
    fn lower_builtin_product(
        &mut self,
        list_expr: &Expr,
    ) -> CodegenResult<Option<BasicValueEnum<'ctx>>> {
        let list_val = self
            .lower_expr(list_expr)?
            .ok_or_else(|| CodegenError::Internal("product: list has no value".to_string()))?;

        let list_ptr = match list_val {
            BasicValueEnum::PointerValue(p) => p,
            _ => {
                return Err(CodegenError::TypeError(
                    "product expects a list".to_string(),
                ))
            }
        };

        let tm = self.type_mapper();
        let current_fn = self
            .builder()
            .get_insert_block()
            .and_then(|b| b.get_parent())
            .ok_or_else(|| CodegenError::Internal("no current function".to_string()))?;

        let entry_block = self
            .builder()
            .get_insert_block()
            .ok_or_else(|| CodegenError::Internal("no current block".to_string()))?;
        let loop_header = self.llvm_ctx.append_basic_block(current_fn, "prod_header");
        let loop_body = self.llvm_ctx.append_basic_block(current_fn, "prod_body");
        let loop_exit = self.llvm_ctx.append_basic_block(current_fn, "prod_exit");

        // Jump to loop header
        self.builder()
            .build_unconditional_branch(loop_header)
            .map_err(|e| CodegenError::Internal(format!("failed to build branch: {:?}", e)))?;

        // Loop header: phi for accumulator and current list pointer
        self.builder().position_at_end(loop_header);
        let acc_phi = self
            .builder()
            .build_phi(tm.i64_type(), "prod_acc")
            .map_err(|e| CodegenError::Internal(format!("failed to build phi: {:?}", e)))?;
        let list_phi = self
            .builder()
            .build_phi(tm.ptr_type(), "prod_list")
            .map_err(|e| CodegenError::Internal(format!("failed to build phi: {:?}", e)))?;

        // Check if list is empty (tag == 0)
        let tag = self.extract_adt_tag(list_phi.as_basic_value().into_pointer_value())?;
        let is_empty = self
            .builder()
            .build_int_compare(
                inkwell::IntPredicate::EQ,
                tag,
                tm.i64_type().const_zero(),
                "is_empty",
            )
            .map_err(|e| CodegenError::Internal(format!("failed to compare tag: {:?}", e)))?;

        self.builder()
            .build_conditional_branch(is_empty, loop_exit, loop_body)
            .map_err(|e| CodegenError::Internal(format!("failed to build branch: {:?}", e)))?;

        // Loop body: extract head, multiply with accumulator, continue with tail
        self.builder().position_at_end(loop_body);
        let head_ptr =
            self.extract_adt_field(list_phi.as_basic_value().into_pointer_value(), 2, 0)?;
        let head_val = self
            .builder()
            .build_ptr_to_int(head_ptr, tm.i64_type(), "head_val")
            .map_err(|e| CodegenError::Internal(format!("failed to ptr_to_int: {:?}", e)))?;
        let tail_ptr =
            self.extract_adt_field(list_phi.as_basic_value().into_pointer_value(), 2, 1)?;

        let new_acc = self
            .builder()
            .build_int_mul(
                acc_phi.as_basic_value().into_int_value(),
                head_val,
                "new_acc",
            )
            .map_err(|e| CodegenError::Internal(format!("failed to mul: {:?}", e)))?;

        self.builder()
            .build_unconditional_branch(loop_header)
            .map_err(|e| CodegenError::Internal(format!("failed to build branch: {:?}", e)))?;

        // Add incoming values to phi nodes (start with 1 for product)
        acc_phi.add_incoming(&[
            (&tm.i64_type().const_int(1, false), entry_block),
            (&new_acc, loop_body),
        ]);
        list_phi.add_incoming(&[(&list_ptr, entry_block), (&tail_ptr, loop_body)]);

        // Exit: return accumulator (boxed as pointer)
        self.builder().position_at_end(loop_exit);
        let result = self
            .builder()
            .build_int_to_ptr(
                acc_phi.as_basic_value().into_int_value(),
                tm.ptr_type(),
                "result",
            )
            .map_err(|e| CodegenError::Internal(format!("failed to int_to_ptr: {:?}", e)))?;
        Ok(Some(result.into()))
    }

    /// Lower `map` - apply a function to each element of a list.
    /// map f [] = []
    /// map f (x:xs) = f x : map f xs
    fn lower_builtin_map(
        &mut self,
        fn_expr: &Expr,
        list_expr: &Expr,
    ) -> CodegenResult<Option<BasicValueEnum<'ctx>>> {
        // Lower the function (should be a closure)
        let fn_val = self
            .lower_expr(fn_expr)?
            .ok_or_else(|| CodegenError::Internal("map: function has no value".to_string()))?;

        let fn_ptr = match fn_val {
            BasicValueEnum::PointerValue(p) => p,
            _ => {
                return Err(CodegenError::TypeError(
                    "map: function must be a closure".to_string(),
                ))
            }
        };

        let list_val = self
            .lower_expr(list_expr)?
            .ok_or_else(|| CodegenError::Internal("map: list has no value".to_string()))?;

        let list_ptr = match list_val {
            BasicValueEnum::PointerValue(p) => p,
            _ => return Err(CodegenError::TypeError("map expects a list".to_string())),
        };

        let tm = self.type_mapper();
        let ptr_type = tm.ptr_type();
        let current_fn = self
            .builder()
            .get_insert_block()
            .and_then(|b| b.get_parent())
            .ok_or_else(|| CodegenError::Internal("no current function".to_string()))?;

        let entry_block = self
            .builder()
            .get_insert_block()
            .ok_or_else(|| CodegenError::Internal("no current block".to_string()))?;
        let loop_header = self.llvm_ctx.append_basic_block(current_fn, "map_header");
        let loop_body = self.llvm_ctx.append_basic_block(current_fn, "map_body");
        let loop_exit = self.llvm_ctx.append_basic_block(current_fn, "map_exit");

        // Build nil in entry block before branching
        let nil = self.build_nil()?;

        // Build result list in reverse (we'll reverse at the end)
        self.builder()
            .build_unconditional_branch(loop_header)
            .map_err(|e| CodegenError::Internal(format!("failed to build branch: {:?}", e)))?;

        // Loop header
        self.builder().position_at_end(loop_header);
        let result_phi = self
            .builder()
            .build_phi(ptr_type, "map_result")
            .map_err(|e| CodegenError::Internal(format!("failed to build phi: {:?}", e)))?;
        let list_phi = self
            .builder()
            .build_phi(ptr_type, "map_list")
            .map_err(|e| CodegenError::Internal(format!("failed to build phi: {:?}", e)))?;

        // Check if list is empty
        let tag = self.extract_adt_tag(list_phi.as_basic_value().into_pointer_value())?;
        let is_empty = self
            .builder()
            .build_int_compare(
                inkwell::IntPredicate::EQ,
                tag,
                tm.i64_type().const_zero(),
                "is_empty",
            )
            .map_err(|e| CodegenError::Internal(format!("failed to compare tag: {:?}", e)))?;

        self.builder()
            .build_conditional_branch(is_empty, loop_exit, loop_body)
            .map_err(|e| CodegenError::Internal(format!("failed to build branch: {:?}", e)))?;

        // Loop body: apply function to head, cons result onto accumulator
        self.builder().position_at_end(loop_body);
        let head_ptr =
            self.extract_adt_field(list_phi.as_basic_value().into_pointer_value(), 2, 0)?;
        let tail_ptr =
            self.extract_adt_field(list_phi.as_basic_value().into_pointer_value(), 2, 1)?;

        // Call the function closure: fn_ptr(fn_ptr, head)
        let closure_fn_ptr = self.extract_closure_fn_ptr(fn_ptr)?;
        let fn_type = ptr_type.fn_type(&[ptr_type.into(), ptr_type.into()], false);
        let mapped_val = self
            .builder()
            .build_indirect_call(
                fn_type,
                closure_fn_ptr,
                &[fn_ptr.into(), head_ptr.into()],
                "mapped",
            )
            .map_err(|e| CodegenError::Internal(format!("failed to call map fn: {:?}", e)))?
            .try_as_basic_value()
            .basic()
            .ok_or_else(|| CodegenError::Internal("map function returned void".to_string()))?;

        // Build new cons cell
        let new_cons = self.build_cons(mapped_val, result_phi.as_basic_value())?;

        self.builder()
            .build_unconditional_branch(loop_header)
            .map_err(|e| CodegenError::Internal(format!("failed to build branch: {:?}", e)))?;

        // Add phi incoming values (nil was built in entry block before branching)
        result_phi.add_incoming(&[(&nil, entry_block), (&new_cons, loop_body)]);
        list_phi.add_incoming(&[(&list_ptr, entry_block), (&tail_ptr, loop_body)]);

        // Exit: reverse the result (we built it in reverse order)
        self.builder().position_at_end(loop_exit);

        // Call builtin reverse - but we need to do it inline to avoid recursion issues
        // For simplicity, we'll use an iterative reverse
        let rev_header = self.llvm_ctx.append_basic_block(current_fn, "rev_header");
        let rev_body = self.llvm_ctx.append_basic_block(current_fn, "rev_body");
        let rev_exit = self.llvm_ctx.append_basic_block(current_fn, "rev_exit");

        // Build nil2 before branching
        let nil2 = self.build_nil()?;

        self.builder()
            .build_unconditional_branch(rev_header)
            .map_err(|e| CodegenError::Internal(format!("failed to build branch: {:?}", e)))?;

        self.builder().position_at_end(rev_header);
        let rev_acc = self
            .builder()
            .build_phi(ptr_type, "rev_acc")
            .map_err(|e| CodegenError::Internal(format!("failed to build phi: {:?}", e)))?;
        let rev_list = self
            .builder()
            .build_phi(ptr_type, "rev_list")
            .map_err(|e| CodegenError::Internal(format!("failed to build phi: {:?}", e)))?;

        let rev_tag = self.extract_adt_tag(rev_list.as_basic_value().into_pointer_value())?;
        let rev_is_empty = self
            .builder()
            .build_int_compare(
                inkwell::IntPredicate::EQ,
                rev_tag,
                tm.i64_type().const_zero(),
                "rev_is_empty",
            )
            .map_err(|e| CodegenError::Internal(format!("failed to compare tag: {:?}", e)))?;

        self.builder()
            .build_conditional_branch(rev_is_empty, rev_exit, rev_body)
            .map_err(|e| CodegenError::Internal(format!("failed to build branch: {:?}", e)))?;

        self.builder().position_at_end(rev_body);
        let rev_head =
            self.extract_adt_field(rev_list.as_basic_value().into_pointer_value(), 2, 0)?;
        let rev_tail =
            self.extract_adt_field(rev_list.as_basic_value().into_pointer_value(), 2, 1)?;
        let rev_new_cons = self.build_cons(rev_head.into(), rev_acc.as_basic_value())?;

        self.builder()
            .build_unconditional_branch(rev_header)
            .map_err(|e| CodegenError::Internal(format!("failed to build branch: {:?}", e)))?;

        // nil2 was built in loop_exit before branching
        rev_acc.add_incoming(&[(&nil2, loop_exit), (&rev_new_cons, rev_body)]);
        rev_list.add_incoming(&[
            (&result_phi.as_basic_value(), loop_exit),
            (&rev_tail, rev_body),
        ]);

        self.builder().position_at_end(rev_exit);
        Ok(Some(rev_acc.as_basic_value()))
    }

    /// Lower `filter` - keep elements that satisfy a predicate.
    /// filter p [] = []
    /// filter p (x:xs) = if p x then x : filter p xs else filter p xs
    fn lower_builtin_filter(
        &mut self,
        pred_expr: &Expr,
        list_expr: &Expr,
    ) -> CodegenResult<Option<BasicValueEnum<'ctx>>> {
        // Lower the predicate (should be a closure)
        let pred_val = self
            .lower_expr(pred_expr)?
            .ok_or_else(|| CodegenError::Internal("filter: predicate has no value".to_string()))?;

        let pred_ptr = match pred_val {
            BasicValueEnum::PointerValue(p) => p,
            _ => {
                return Err(CodegenError::TypeError(
                    "filter: predicate must be a closure".to_string(),
                ))
            }
        };

        let list_val = self
            .lower_expr(list_expr)?
            .ok_or_else(|| CodegenError::Internal("filter: list has no value".to_string()))?;

        let list_ptr = match list_val {
            BasicValueEnum::PointerValue(p) => p,
            _ => return Err(CodegenError::TypeError("filter expects a list".to_string())),
        };

        let tm = self.type_mapper();
        let ptr_type = tm.ptr_type();
        let current_fn = self
            .builder()
            .get_insert_block()
            .and_then(|b| b.get_parent())
            .ok_or_else(|| CodegenError::Internal("no current function".to_string()))?;

        let entry_block = self
            .builder()
            .get_insert_block()
            .ok_or_else(|| CodegenError::Internal("no current block".to_string()))?;
        let loop_header = self
            .llvm_ctx
            .append_basic_block(current_fn, "filter_header");
        let loop_body = self.llvm_ctx.append_basic_block(current_fn, "filter_body");
        let loop_keep = self.llvm_ctx.append_basic_block(current_fn, "filter_keep");
        let loop_skip = self.llvm_ctx.append_basic_block(current_fn, "filter_skip");
        let loop_exit = self.llvm_ctx.append_basic_block(current_fn, "filter_exit");

        // Build nil in entry block before branching
        let nil = self.build_nil()?;

        self.builder()
            .build_unconditional_branch(loop_header)
            .map_err(|e| CodegenError::Internal(format!("failed to build branch: {:?}", e)))?;

        // Loop header
        self.builder().position_at_end(loop_header);
        let result_phi = self
            .builder()
            .build_phi(ptr_type, "filter_result")
            .map_err(|e| CodegenError::Internal(format!("failed to build phi: {:?}", e)))?;
        let list_phi = self
            .builder()
            .build_phi(ptr_type, "filter_list")
            .map_err(|e| CodegenError::Internal(format!("failed to build phi: {:?}", e)))?;

        // Check if list is empty
        let tag = self.extract_adt_tag(list_phi.as_basic_value().into_pointer_value())?;
        let is_empty = self
            .builder()
            .build_int_compare(
                inkwell::IntPredicate::EQ,
                tag,
                tm.i64_type().const_zero(),
                "is_empty",
            )
            .map_err(|e| CodegenError::Internal(format!("failed to compare tag: {:?}", e)))?;

        self.builder()
            .build_conditional_branch(is_empty, loop_exit, loop_body)
            .map_err(|e| CodegenError::Internal(format!("failed to build branch: {:?}", e)))?;

        // Loop body: check predicate
        self.builder().position_at_end(loop_body);
        let head_ptr =
            self.extract_adt_field(list_phi.as_basic_value().into_pointer_value(), 2, 0)?;
        let tail_ptr =
            self.extract_adt_field(list_phi.as_basic_value().into_pointer_value(), 2, 1)?;

        // Call predicate: pred_ptr(pred_ptr, head)
        let pred_fn_ptr = self.extract_closure_fn_ptr(pred_ptr)?;
        let fn_type = ptr_type.fn_type(&[ptr_type.into(), ptr_type.into()], false);
        let pred_result = self
            .builder()
            .build_indirect_call(
                fn_type,
                pred_fn_ptr,
                &[pred_ptr.into(), head_ptr.into()],
                "pred_result",
            )
            .map_err(|e| CodegenError::Internal(format!("failed to call predicate: {:?}", e)))?
            .try_as_basic_value()
            .basic()
            .ok_or_else(|| CodegenError::Internal("predicate returned void".to_string()))?;

        // Check if predicate returned True
        // Handle both tagged-int-as-pointer (0/1) and Bool ADT (heap struct with tag)
        let pred_bool = self.extract_bool_tag(pred_result.into_pointer_value())?;
        let is_true = self
            .builder()
            .build_int_compare(
                inkwell::IntPredicate::NE,
                pred_bool,
                tm.i64_type().const_zero(),
                "is_true",
            )
            .map_err(|e| CodegenError::Internal(format!("failed to compare: {:?}", e)))?;

        self.builder()
            .build_conditional_branch(is_true, loop_keep, loop_skip)
            .map_err(|e| CodegenError::Internal(format!("failed to build branch: {:?}", e)))?;

        // Keep: cons head onto result
        self.builder().position_at_end(loop_keep);
        let new_cons = self.build_cons(head_ptr.into(), result_phi.as_basic_value())?;
        self.builder()
            .build_unconditional_branch(loop_header)
            .map_err(|e| CodegenError::Internal(format!("failed to build branch: {:?}", e)))?;

        // Skip: just continue
        self.builder().position_at_end(loop_skip);
        self.builder()
            .build_unconditional_branch(loop_header)
            .map_err(|e| CodegenError::Internal(format!("failed to build branch: {:?}", e)))?;

        // Add phi incoming values (nil was built in entry block)
        result_phi.add_incoming(&[
            (&nil, entry_block),
            (&new_cons, loop_keep),
            (&result_phi.as_basic_value(), loop_skip),
        ]);
        list_phi.add_incoming(&[
            (&list_ptr, entry_block),
            (&tail_ptr, loop_keep),
            (&tail_ptr, loop_skip),
        ]);

        // Exit: reverse the result
        self.builder().position_at_end(loop_exit);

        // Inline reverse
        let rev_header = self.llvm_ctx.append_basic_block(current_fn, "rev_header");
        let rev_body = self.llvm_ctx.append_basic_block(current_fn, "rev_body");
        let rev_exit = self.llvm_ctx.append_basic_block(current_fn, "rev_exit");

        // Build nil2 before branching
        let nil2 = self.build_nil()?;

        self.builder()
            .build_unconditional_branch(rev_header)
            .map_err(|e| CodegenError::Internal(format!("failed to build branch: {:?}", e)))?;

        self.builder().position_at_end(rev_header);
        let rev_acc = self
            .builder()
            .build_phi(ptr_type, "rev_acc")
            .map_err(|e| CodegenError::Internal(format!("failed to build phi: {:?}", e)))?;
        let rev_list = self
            .builder()
            .build_phi(ptr_type, "rev_list")
            .map_err(|e| CodegenError::Internal(format!("failed to build phi: {:?}", e)))?;

        let rev_tag = self.extract_adt_tag(rev_list.as_basic_value().into_pointer_value())?;
        let rev_is_empty = self
            .builder()
            .build_int_compare(
                inkwell::IntPredicate::EQ,
                rev_tag,
                tm.i64_type().const_zero(),
                "rev_is_empty",
            )
            .map_err(|e| CodegenError::Internal(format!("failed to compare tag: {:?}", e)))?;

        self.builder()
            .build_conditional_branch(rev_is_empty, rev_exit, rev_body)
            .map_err(|e| CodegenError::Internal(format!("failed to build branch: {:?}", e)))?;

        self.builder().position_at_end(rev_body);
        let rev_head =
            self.extract_adt_field(rev_list.as_basic_value().into_pointer_value(), 2, 0)?;
        let rev_tail =
            self.extract_adt_field(rev_list.as_basic_value().into_pointer_value(), 2, 1)?;
        let rev_new_cons = self.build_cons(rev_head.into(), rev_acc.as_basic_value())?;

        self.builder()
            .build_unconditional_branch(rev_header)
            .map_err(|e| CodegenError::Internal(format!("failed to build branch: {:?}", e)))?;

        // nil2 was built in loop_exit before branching
        rev_acc.add_incoming(&[(&nil2, loop_exit), (&rev_new_cons, rev_body)]);
        rev_list.add_incoming(&[
            (&result_phi.as_basic_value(), loop_exit),
            (&rev_tail, rev_body),
        ]);

        self.builder().position_at_end(rev_exit);
        Ok(Some(rev_acc.as_basic_value()))
    }

    /// Lower `foldl` - left fold over a list.
    /// foldl f z [] = z
    /// foldl f z (x:xs) = foldl f (f z x) xs
    ///
    /// Iterative implementation: accumulates from left to right
    fn lower_builtin_foldl(
        &mut self,
        func_expr: &Expr,
        init_expr: &Expr,
        list_expr: &Expr,
    ) -> CodegenResult<Option<BasicValueEnum<'ctx>>> {
        // Lower the function (should be a closure)
        let func_val = self
            .lower_expr(func_expr)?
            .ok_or_else(|| CodegenError::Internal("foldl: function has no value".to_string()))?;

        let func_ptr = match func_val {
            BasicValueEnum::PointerValue(p) => p,
            _ => {
                return Err(CodegenError::TypeError(
                    "foldl: function must be a closure".to_string(),
                ))
            }
        };

        // Lower the initial value
        let init_val = self.lower_expr(init_expr)?.ok_or_else(|| {
            CodegenError::Internal("foldl: initial value has no value".to_string())
        })?;
        let init_ptr = self.value_to_ptr(init_val)?;

        // Lower the list
        let list_val = self
            .lower_expr(list_expr)?
            .ok_or_else(|| CodegenError::Internal("foldl: list has no value".to_string()))?;

        let list_ptr = match list_val {
            BasicValueEnum::PointerValue(p) => p,
            _ => return Err(CodegenError::TypeError("foldl expects a list".to_string())),
        };

        let tm = self.type_mapper();
        let ptr_type = tm.ptr_type();
        let current_fn = self
            .builder()
            .get_insert_block()
            .and_then(|b| b.get_parent())
            .ok_or_else(|| CodegenError::Internal("no current function".to_string()))?;

        let entry_block = self
            .builder()
            .get_insert_block()
            .ok_or_else(|| CodegenError::Internal("no current block".to_string()))?;
        let loop_header = self.llvm_ctx.append_basic_block(current_fn, "foldl_header");
        let loop_body = self.llvm_ctx.append_basic_block(current_fn, "foldl_body");
        let loop_exit = self.llvm_ctx.append_basic_block(current_fn, "foldl_exit");

        self.builder()
            .build_unconditional_branch(loop_header)
            .map_err(|e| CodegenError::Internal(format!("failed to build branch: {:?}", e)))?;

        // Loop header
        self.builder().position_at_end(loop_header);
        let acc_phi = self
            .builder()
            .build_phi(ptr_type, "foldl_acc")
            .map_err(|e| CodegenError::Internal(format!("failed to build phi: {:?}", e)))?;
        let list_phi = self
            .builder()
            .build_phi(ptr_type, "foldl_list")
            .map_err(|e| CodegenError::Internal(format!("failed to build phi: {:?}", e)))?;

        // Check if list is empty
        let tag = self.extract_adt_tag(list_phi.as_basic_value().into_pointer_value())?;
        let is_empty = self
            .builder()
            .build_int_compare(
                inkwell::IntPredicate::EQ,
                tag,
                tm.i64_type().const_zero(),
                "is_empty",
            )
            .map_err(|e| CodegenError::Internal(format!("failed to compare tag: {:?}", e)))?;

        self.builder()
            .build_conditional_branch(is_empty, loop_exit, loop_body)
            .map_err(|e| CodegenError::Internal(format!("failed to build branch: {:?}", e)))?;

        // Loop body: acc = f acc head; list = tail
        self.builder().position_at_end(loop_body);
        let head_ptr =
            self.extract_adt_field(list_phi.as_basic_value().into_pointer_value(), 2, 0)?;
        let tail_ptr =
            self.extract_adt_field(list_phi.as_basic_value().into_pointer_value(), 2, 1)?;

        // Call f acc head - function takes (closure_ptr, acc, head) and returns new acc
        // The closure stores the function pointer which expects all args at once
        let fn_ptr = self.extract_closure_fn_ptr(func_ptr)?;
        let fn_type = ptr_type.fn_type(&[ptr_type.into(), ptr_type.into(), ptr_type.into()], false);
        let new_acc = self
            .builder()
            .build_indirect_call(
                fn_type,
                fn_ptr,
                &[
                    func_ptr.into(),
                    acc_phi.as_basic_value().into(),
                    head_ptr.into(),
                ],
                "foldl_result",
            )
            .map_err(|e| CodegenError::Internal(format!("failed to call function: {:?}", e)))?
            .try_as_basic_value()
            .basic()
            .ok_or_else(|| CodegenError::Internal("foldl: function returned void".to_string()))?;

        self.builder()
            .build_unconditional_branch(loop_header)
            .map_err(|e| CodegenError::Internal(format!("failed to build branch: {:?}", e)))?;

        // Add phi incoming values
        acc_phi.add_incoming(&[(&init_ptr, entry_block), (&new_acc, loop_body)]);
        list_phi.add_incoming(&[(&list_ptr, entry_block), (&tail_ptr, loop_body)]);

        // Exit: return accumulator
        self.builder().position_at_end(loop_exit);
        Ok(Some(acc_phi.as_basic_value()))
    }

    /// Lower `foldr` - right fold over a list.
    /// foldr f z [] = z
    /// foldr f z (x:xs) = f x (foldr f z xs)
    ///
    /// Implementation: reverse the list first, then fold left with swapped args
    fn lower_builtin_foldr(
        &mut self,
        func_expr: &Expr,
        init_expr: &Expr,
        list_expr: &Expr,
    ) -> CodegenResult<Option<BasicValueEnum<'ctx>>> {
        // Check if container is a user ADT with derived Foldable
        if let Some(type_name) = self.infer_adt_type_from_expr(list_expr) {
            if self.derived_foldable_fns.contains_key(&type_name) {
                return self.lower_foldr_derived(func_expr, init_expr, list_expr, &type_name);
            }
        }

        // Lower the function (should be a closure)
        let func_val = self
            .lower_expr(func_expr)?
            .ok_or_else(|| CodegenError::Internal("foldr: function has no value".to_string()))?;

        let func_ptr = match func_val {
            BasicValueEnum::PointerValue(p) => p,
            _ => {
                return Err(CodegenError::TypeError(
                    "foldr: function must be a closure".to_string(),
                ))
            }
        };

        // Lower the initial value
        let init_val = self.lower_expr(init_expr)?.ok_or_else(|| {
            CodegenError::Internal("foldr: initial value has no value".to_string())
        })?;
        let init_ptr = self.value_to_ptr(init_val)?;

        // Lower the list
        let list_val = self
            .lower_expr(list_expr)?
            .ok_or_else(|| CodegenError::Internal("foldr: list has no value".to_string()))?;

        let list_ptr = match list_val {
            BasicValueEnum::PointerValue(p) => p,
            _ => return Err(CodegenError::TypeError("foldr expects a list".to_string())),
        };

        let tm = self.type_mapper();
        let ptr_type = tm.ptr_type();
        let current_fn = self
            .builder()
            .get_insert_block()
            .and_then(|b| b.get_parent())
            .ok_or_else(|| CodegenError::Internal("no current function".to_string()))?;

        // First, reverse the list
        let rev_entry = self
            .builder()
            .get_insert_block()
            .ok_or_else(|| CodegenError::Internal("no current block".to_string()))?;
        let rev_header = self
            .llvm_ctx
            .append_basic_block(current_fn, "foldr_rev_header");
        let rev_body = self
            .llvm_ctx
            .append_basic_block(current_fn, "foldr_rev_body");
        let rev_exit = self
            .llvm_ctx
            .append_basic_block(current_fn, "foldr_rev_exit");

        // Build nil before branching
        let nil = self.build_nil()?;

        self.builder()
            .build_unconditional_branch(rev_header)
            .map_err(|e| CodegenError::Internal(format!("failed to build branch: {:?}", e)))?;

        // Reverse loop
        self.builder().position_at_end(rev_header);
        let rev_acc = self
            .builder()
            .build_phi(ptr_type, "rev_acc")
            .map_err(|e| CodegenError::Internal(format!("failed to build phi: {:?}", e)))?;
        let rev_list = self
            .builder()
            .build_phi(ptr_type, "rev_list")
            .map_err(|e| CodegenError::Internal(format!("failed to build phi: {:?}", e)))?;

        let rev_tag = self.extract_adt_tag(rev_list.as_basic_value().into_pointer_value())?;
        let rev_is_empty = self
            .builder()
            .build_int_compare(
                inkwell::IntPredicate::EQ,
                rev_tag,
                tm.i64_type().const_zero(),
                "rev_is_empty",
            )
            .map_err(|e| CodegenError::Internal(format!("failed to compare tag: {:?}", e)))?;

        self.builder()
            .build_conditional_branch(rev_is_empty, rev_exit, rev_body)
            .map_err(|e| CodegenError::Internal(format!("failed to build branch: {:?}", e)))?;

        self.builder().position_at_end(rev_body);
        let rev_head =
            self.extract_adt_field(rev_list.as_basic_value().into_pointer_value(), 2, 0)?;
        let rev_tail =
            self.extract_adt_field(rev_list.as_basic_value().into_pointer_value(), 2, 1)?;
        let rev_new_cons = self.build_cons(rev_head.into(), rev_acc.as_basic_value())?;

        self.builder()
            .build_unconditional_branch(rev_header)
            .map_err(|e| CodegenError::Internal(format!("failed to build branch: {:?}", e)))?;

        rev_acc.add_incoming(&[(&nil, rev_entry), (&rev_new_cons, rev_body)]);
        rev_list.add_incoming(&[(&list_ptr, rev_entry), (&rev_tail, rev_body)]);

        // Now fold the reversed list with f applied as f elem acc
        self.builder().position_at_end(rev_exit);

        let fold_header = self
            .llvm_ctx
            .append_basic_block(current_fn, "foldr_fold_header");
        let fold_body = self
            .llvm_ctx
            .append_basic_block(current_fn, "foldr_fold_body");
        let fold_exit = self
            .llvm_ctx
            .append_basic_block(current_fn, "foldr_fold_exit");

        self.builder()
            .build_unconditional_branch(fold_header)
            .map_err(|e| CodegenError::Internal(format!("failed to build branch: {:?}", e)))?;

        // Fold loop
        self.builder().position_at_end(fold_header);
        let acc_phi = self
            .builder()
            .build_phi(ptr_type, "foldr_acc")
            .map_err(|e| CodegenError::Internal(format!("failed to build phi: {:?}", e)))?;
        let list_phi = self
            .builder()
            .build_phi(ptr_type, "foldr_list")
            .map_err(|e| CodegenError::Internal(format!("failed to build phi: {:?}", e)))?;

        let tag = self.extract_adt_tag(list_phi.as_basic_value().into_pointer_value())?;
        let is_empty = self
            .builder()
            .build_int_compare(
                inkwell::IntPredicate::EQ,
                tag,
                tm.i64_type().const_zero(),
                "is_empty",
            )
            .map_err(|e| CodegenError::Internal(format!("failed to compare tag: {:?}", e)))?;

        self.builder()
            .build_conditional_branch(is_empty, fold_exit, fold_body)
            .map_err(|e| CodegenError::Internal(format!("failed to build branch: {:?}", e)))?;

        self.builder().position_at_end(fold_body);
        let head_ptr =
            self.extract_adt_field(list_phi.as_basic_value().into_pointer_value(), 2, 0)?;
        let tail_ptr =
            self.extract_adt_field(list_phi.as_basic_value().into_pointer_value(), 2, 1)?;

        // Call f head acc - function takes (closure_ptr, elem, acc) and returns new acc
        // Note: for foldr, the function signature is (a -> b -> b), so elem comes first, then acc
        let fn_ptr = self.extract_closure_fn_ptr(func_ptr)?;
        let fn_type = ptr_type.fn_type(&[ptr_type.into(), ptr_type.into(), ptr_type.into()], false);
        let new_acc = self
            .builder()
            .build_indirect_call(
                fn_type,
                fn_ptr,
                &[
                    func_ptr.into(),
                    head_ptr.into(),
                    acc_phi.as_basic_value().into(),
                ],
                "foldr_result",
            )
            .map_err(|e| CodegenError::Internal(format!("failed to call function: {:?}", e)))?
            .try_as_basic_value()
            .basic()
            .ok_or_else(|| CodegenError::Internal("foldr: function returned void".to_string()))?;

        self.builder()
            .build_unconditional_branch(fold_header)
            .map_err(|e| CodegenError::Internal(format!("failed to build branch: {:?}", e)))?;

        // Add phi incoming values
        acc_phi.add_incoming(&[(&init_ptr, rev_exit), (&new_acc, fold_body)]);
        list_phi.add_incoming(&[
            (&rev_acc.as_basic_value(), rev_exit),
            (&tail_ptr, fold_body),
        ]);

        // Exit: return accumulator
        self.builder().position_at_end(fold_exit);
        Ok(Some(acc_phi.as_basic_value()))
    }

    /// Lower `foldl'` - strict left fold (same as foldl but forces accumulator).
    /// In our implementation, foldl is already strict, so this is the same.
    fn lower_builtin_foldl_strict(
        &mut self,
        func_expr: &Expr,
        init_expr: &Expr,
        list_expr: &Expr,
    ) -> CodegenResult<Option<BasicValueEnum<'ctx>>> {
        // Our foldl is already strict, so just delegate
        self.lower_builtin_foldl(func_expr, init_expr, list_expr)
    }

    /// Lower `zipWith` - apply a function to pairs of elements from two lists.
    /// zipWith f [] _ = []
    /// zipWith f _ [] = []
    /// zipWith f (x:xs) (y:ys) = f x y : zipWith f xs ys
    fn lower_builtin_zipwith(
        &mut self,
        func_expr: &Expr,
        list1_expr: &Expr,
        list2_expr: &Expr,
    ) -> CodegenResult<Option<BasicValueEnum<'ctx>>> {
        // Lower the function (should be a closure)
        let func_val = self
            .lower_expr(func_expr)?
            .ok_or_else(|| CodegenError::Internal("zipWith: function has no value".to_string()))?;

        let func_ptr = match func_val {
            BasicValueEnum::PointerValue(p) => p,
            _ => {
                return Err(CodegenError::TypeError(
                    "zipWith: function must be a closure".to_string(),
                ))
            }
        };

        let list1_val = self
            .lower_expr(list1_expr)?
            .ok_or_else(|| CodegenError::Internal("zipWith: list1 has no value".to_string()))?;

        let list1_ptr = match list1_val {
            BasicValueEnum::PointerValue(p) => p,
            _ => return Err(CodegenError::TypeError("zipWith expects lists".to_string())),
        };

        let list2_val = self
            .lower_expr(list2_expr)?
            .ok_or_else(|| CodegenError::Internal("zipWith: list2 has no value".to_string()))?;

        let list2_ptr = match list2_val {
            BasicValueEnum::PointerValue(p) => p,
            _ => return Err(CodegenError::TypeError("zipWith expects lists".to_string())),
        };

        let tm = self.type_mapper();
        let ptr_type = tm.ptr_type();
        let current_fn = self
            .builder()
            .get_insert_block()
            .and_then(|b| b.get_parent())
            .ok_or_else(|| CodegenError::Internal("no current function".to_string()))?;

        let entry_block = self
            .builder()
            .get_insert_block()
            .ok_or_else(|| CodegenError::Internal("no current block".to_string()))?;
        let loop_header = self
            .llvm_ctx
            .append_basic_block(current_fn, "zipwith_header");
        let loop_body = self.llvm_ctx.append_basic_block(current_fn, "zipwith_body");
        let loop_exit = self.llvm_ctx.append_basic_block(current_fn, "zipwith_exit");

        // Build nil in entry block
        let nil = self.build_nil()?;

        self.builder()
            .build_unconditional_branch(loop_header)
            .map_err(|e| CodegenError::Internal(format!("failed to build branch: {:?}", e)))?;

        // Loop header
        self.builder().position_at_end(loop_header);
        let result_phi = self
            .builder()
            .build_phi(ptr_type, "zipwith_result")
            .map_err(|e| CodegenError::Internal(format!("failed to build phi: {:?}", e)))?;
        let list1_phi = self
            .builder()
            .build_phi(ptr_type, "zipwith_list1")
            .map_err(|e| CodegenError::Internal(format!("failed to build phi: {:?}", e)))?;
        let list2_phi = self
            .builder()
            .build_phi(ptr_type, "zipwith_list2")
            .map_err(|e| CodegenError::Internal(format!("failed to build phi: {:?}", e)))?;

        // Check if either list is empty
        let tag1 = self.extract_adt_tag(list1_phi.as_basic_value().into_pointer_value())?;
        let tag2 = self.extract_adt_tag(list2_phi.as_basic_value().into_pointer_value())?;
        let is_empty1 = self
            .builder()
            .build_int_compare(
                inkwell::IntPredicate::EQ,
                tag1,
                tm.i64_type().const_zero(),
                "is_empty1",
            )
            .map_err(|e| CodegenError::Internal(format!("failed to compare tag: {:?}", e)))?;
        let is_empty2 = self
            .builder()
            .build_int_compare(
                inkwell::IntPredicate::EQ,
                tag2,
                tm.i64_type().const_zero(),
                "is_empty2",
            )
            .map_err(|e| CodegenError::Internal(format!("failed to compare tag: {:?}", e)))?;
        let is_empty = self
            .builder()
            .build_or(is_empty1, is_empty2, "is_empty")
            .map_err(|e| CodegenError::Internal(format!("failed to build or: {:?}", e)))?;

        self.builder()
            .build_conditional_branch(is_empty, loop_exit, loop_body)
            .map_err(|e| CodegenError::Internal(format!("failed to build branch: {:?}", e)))?;

        // Loop body
        self.builder().position_at_end(loop_body);
        let head1_ptr =
            self.extract_adt_field(list1_phi.as_basic_value().into_pointer_value(), 2, 0)?;
        let tail1_ptr =
            self.extract_adt_field(list1_phi.as_basic_value().into_pointer_value(), 2, 1)?;
        let head2_ptr =
            self.extract_adt_field(list2_phi.as_basic_value().into_pointer_value(), 2, 0)?;
        let tail2_ptr =
            self.extract_adt_field(list2_phi.as_basic_value().into_pointer_value(), 2, 1)?;

        // Call the function: f x y
        // For binary functions (most common case with zipWith), we call with both args at once.
        // The closure convention is: closure_fn(closure_ptr, arg1, arg2) -> result
        let closure_fn_ptr = self.extract_closure_fn_ptr(func_ptr)?;
        let fn_type = ptr_type.fn_type(&[ptr_type.into(), ptr_type.into(), ptr_type.into()], false);
        let mapped_val = self
            .builder()
            .build_indirect_call(
                fn_type,
                closure_fn_ptr,
                &[func_ptr.into(), head1_ptr.into(), head2_ptr.into()],
                "mapped",
            )
            .map_err(|e| CodegenError::Internal(format!("failed to call function: {:?}", e)))?
            .try_as_basic_value()
            .basic()
            .ok_or_else(|| CodegenError::Internal("zipWith: function returned void".to_string()))?;

        // Build cons cell
        let new_cons = self.build_cons(mapped_val, result_phi.as_basic_value())?;

        self.builder()
            .build_unconditional_branch(loop_header)
            .map_err(|e| CodegenError::Internal(format!("failed to build branch: {:?}", e)))?;

        // Add phi incoming values
        result_phi.add_incoming(&[(&nil, entry_block), (&new_cons, loop_body)]);
        list1_phi.add_incoming(&[(&list1_ptr, entry_block), (&tail1_ptr, loop_body)]);
        list2_phi.add_incoming(&[(&list2_ptr, entry_block), (&tail2_ptr, loop_body)]);

        // Exit: reverse the result
        self.builder().position_at_end(loop_exit);

        // Inline reverse
        let rev_header = self.llvm_ctx.append_basic_block(current_fn, "rev_header");
        let rev_body = self.llvm_ctx.append_basic_block(current_fn, "rev_body");
        let rev_exit = self.llvm_ctx.append_basic_block(current_fn, "rev_exit");

        let nil2 = self.build_nil()?;

        self.builder()
            .build_unconditional_branch(rev_header)
            .map_err(|e| CodegenError::Internal(format!("failed to build branch: {:?}", e)))?;

        self.builder().position_at_end(rev_header);
        let rev_acc = self
            .builder()
            .build_phi(ptr_type, "rev_acc")
            .map_err(|e| CodegenError::Internal(format!("failed to build phi: {:?}", e)))?;
        let rev_list = self
            .builder()
            .build_phi(ptr_type, "rev_list")
            .map_err(|e| CodegenError::Internal(format!("failed to build phi: {:?}", e)))?;

        let rev_tag = self.extract_adt_tag(rev_list.as_basic_value().into_pointer_value())?;
        let rev_is_empty = self
            .builder()
            .build_int_compare(
                inkwell::IntPredicate::EQ,
                rev_tag,
                tm.i64_type().const_zero(),
                "rev_is_empty",
            )
            .map_err(|e| CodegenError::Internal(format!("failed to compare tag: {:?}", e)))?;

        self.builder()
            .build_conditional_branch(rev_is_empty, rev_exit, rev_body)
            .map_err(|e| CodegenError::Internal(format!("failed to build branch: {:?}", e)))?;

        self.builder().position_at_end(rev_body);
        let rev_head =
            self.extract_adt_field(rev_list.as_basic_value().into_pointer_value(), 2, 0)?;
        let rev_tail =
            self.extract_adt_field(rev_list.as_basic_value().into_pointer_value(), 2, 1)?;
        let rev_new_cons = self.build_cons(rev_head.into(), rev_acc.as_basic_value())?;

        self.builder()
            .build_unconditional_branch(rev_header)
            .map_err(|e| CodegenError::Internal(format!("failed to build branch: {:?}", e)))?;

        rev_acc.add_incoming(&[(&nil2, loop_exit), (&rev_new_cons, rev_body)]);
        rev_list.add_incoming(&[
            (&result_phi.as_basic_value(), loop_exit),
            (&rev_tail, rev_body),
        ]);

        self.builder().position_at_end(rev_exit);
        Ok(Some(rev_acc.as_basic_value()))
    }

    /// Lower `zip` - combine two lists into a list of pairs.
    /// zip [] _ = []
    /// zip _ [] = []
    /// zip (x:xs) (y:ys) = (x,y) : zip xs ys
    fn lower_builtin_zip(
        &mut self,
        list1_expr: &Expr,
        list2_expr: &Expr,
    ) -> CodegenResult<Option<BasicValueEnum<'ctx>>> {
        let list1_val = self
            .lower_expr(list1_expr)?
            .ok_or_else(|| CodegenError::Internal("zip: list1 has no value".to_string()))?;

        let list1_ptr = match list1_val {
            BasicValueEnum::PointerValue(p) => p,
            _ => return Err(CodegenError::TypeError("zip expects lists".to_string())),
        };

        let list2_val = self
            .lower_expr(list2_expr)?
            .ok_or_else(|| CodegenError::Internal("zip: list2 has no value".to_string()))?;

        let list2_ptr = match list2_val {
            BasicValueEnum::PointerValue(p) => p,
            _ => return Err(CodegenError::TypeError("zip expects lists".to_string())),
        };

        let tm = self.type_mapper();
        let ptr_type = tm.ptr_type();
        let current_fn = self
            .builder()
            .get_insert_block()
            .and_then(|b| b.get_parent())
            .ok_or_else(|| CodegenError::Internal("no current function".to_string()))?;

        let entry_block = self
            .builder()
            .get_insert_block()
            .ok_or_else(|| CodegenError::Internal("no current block".to_string()))?;
        let loop_header = self.llvm_ctx.append_basic_block(current_fn, "zip_header");
        let loop_body = self.llvm_ctx.append_basic_block(current_fn, "zip_body");
        let loop_exit = self.llvm_ctx.append_basic_block(current_fn, "zip_exit");

        // Build nil in entry block
        let nil = self.build_nil()?;

        self.builder()
            .build_unconditional_branch(loop_header)
            .map_err(|e| CodegenError::Internal(format!("failed to build branch: {:?}", e)))?;

        // Loop header
        self.builder().position_at_end(loop_header);
        let result_phi = self
            .builder()
            .build_phi(ptr_type, "zip_result")
            .map_err(|e| CodegenError::Internal(format!("failed to build phi: {:?}", e)))?;
        let list1_phi = self
            .builder()
            .build_phi(ptr_type, "zip_list1")
            .map_err(|e| CodegenError::Internal(format!("failed to build phi: {:?}", e)))?;
        let list2_phi = self
            .builder()
            .build_phi(ptr_type, "zip_list2")
            .map_err(|e| CodegenError::Internal(format!("failed to build phi: {:?}", e)))?;

        // Check if either list is empty
        let tag1 = self.extract_adt_tag(list1_phi.as_basic_value().into_pointer_value())?;
        let tag2 = self.extract_adt_tag(list2_phi.as_basic_value().into_pointer_value())?;
        let is_empty1 = self
            .builder()
            .build_int_compare(
                inkwell::IntPredicate::EQ,
                tag1,
                tm.i64_type().const_zero(),
                "is_empty1",
            )
            .map_err(|e| CodegenError::Internal(format!("failed to compare tag: {:?}", e)))?;
        let is_empty2 = self
            .builder()
            .build_int_compare(
                inkwell::IntPredicate::EQ,
                tag2,
                tm.i64_type().const_zero(),
                "is_empty2",
            )
            .map_err(|e| CodegenError::Internal(format!("failed to compare tag: {:?}", e)))?;
        let is_empty = self
            .builder()
            .build_or(is_empty1, is_empty2, "is_empty")
            .map_err(|e| CodegenError::Internal(format!("failed to build or: {:?}", e)))?;

        self.builder()
            .build_conditional_branch(is_empty, loop_exit, loop_body)
            .map_err(|e| CodegenError::Internal(format!("failed to build branch: {:?}", e)))?;

        // Loop body
        self.builder().position_at_end(loop_body);
        let head1_ptr =
            self.extract_adt_field(list1_phi.as_basic_value().into_pointer_value(), 2, 0)?;
        let tail1_ptr =
            self.extract_adt_field(list1_phi.as_basic_value().into_pointer_value(), 2, 1)?;
        let head2_ptr =
            self.extract_adt_field(list2_phi.as_basic_value().into_pointer_value(), 2, 0)?;
        let tail2_ptr =
            self.extract_adt_field(list2_phi.as_basic_value().into_pointer_value(), 2, 1)?;

        // Build tuple (x, y) - represented as ADT with tag 0 and 2 fields
        let pair_ptr = self.build_pair(head1_ptr.into(), head2_ptr.into())?;

        // Build cons cell
        let new_cons = self.build_cons(pair_ptr.into(), result_phi.as_basic_value())?;

        self.builder()
            .build_unconditional_branch(loop_header)
            .map_err(|e| CodegenError::Internal(format!("failed to build branch: {:?}", e)))?;

        // Add phi incoming values
        result_phi.add_incoming(&[(&nil, entry_block), (&new_cons, loop_body)]);
        list1_phi.add_incoming(&[(&list1_ptr, entry_block), (&tail1_ptr, loop_body)]);
        list2_phi.add_incoming(&[(&list2_ptr, entry_block), (&tail2_ptr, loop_body)]);

        // Exit: reverse the result
        self.builder().position_at_end(loop_exit);

        // Inline reverse
        let rev_header = self.llvm_ctx.append_basic_block(current_fn, "rev_header");
        let rev_body = self.llvm_ctx.append_basic_block(current_fn, "rev_body");
        let rev_exit = self.llvm_ctx.append_basic_block(current_fn, "rev_exit");

        let nil2 = self.build_nil()?;

        self.builder()
            .build_unconditional_branch(rev_header)
            .map_err(|e| CodegenError::Internal(format!("failed to build branch: {:?}", e)))?;

        self.builder().position_at_end(rev_header);
        let rev_acc = self
            .builder()
            .build_phi(ptr_type, "rev_acc")
            .map_err(|e| CodegenError::Internal(format!("failed to build phi: {:?}", e)))?;
        let rev_list = self
            .builder()
            .build_phi(ptr_type, "rev_list")
            .map_err(|e| CodegenError::Internal(format!("failed to build phi: {:?}", e)))?;

        let rev_tag = self.extract_adt_tag(rev_list.as_basic_value().into_pointer_value())?;
        let rev_is_empty = self
            .builder()
            .build_int_compare(
                inkwell::IntPredicate::EQ,
                rev_tag,
                tm.i64_type().const_zero(),
                "rev_is_empty",
            )
            .map_err(|e| CodegenError::Internal(format!("failed to compare tag: {:?}", e)))?;

        self.builder()
            .build_conditional_branch(rev_is_empty, rev_exit, rev_body)
            .map_err(|e| CodegenError::Internal(format!("failed to build branch: {:?}", e)))?;

        self.builder().position_at_end(rev_body);
        let rev_head =
            self.extract_adt_field(rev_list.as_basic_value().into_pointer_value(), 2, 0)?;
        let rev_tail =
            self.extract_adt_field(rev_list.as_basic_value().into_pointer_value(), 2, 1)?;
        let rev_new_cons = self.build_cons(rev_head.into(), rev_acc.as_basic_value())?;

        self.builder()
            .build_unconditional_branch(rev_header)
            .map_err(|e| CodegenError::Internal(format!("failed to build branch: {:?}", e)))?;

        rev_acc.add_incoming(&[(&nil2, loop_exit), (&rev_new_cons, rev_body)]);
        rev_list.add_incoming(&[
            (&result_phi.as_basic_value(), loop_exit),
            (&rev_tail, rev_body),
        ]);

        self.builder().position_at_end(rev_exit);
        Ok(Some(rev_acc.as_basic_value()))
    }

    /// Build a pair (tuple of 2 elements).
    fn build_pair(
        &self,
        fst: BasicValueEnum<'ctx>,
        snd: BasicValueEnum<'ctx>,
    ) -> CodegenResult<inkwell::values::PointerValue<'ctx>> {
        let tm = self.type_mapper();
        // Pair is represented as ADT with tag 0 and 2 fields
        // Allocate space: tag (i64) + fst (ptr) + snd (ptr) = 24 bytes
        let size = tm.i64_type().const_int(24, false);
        let alloc_fn = self
            .functions
            .get(&VarId::new(1000005))
            .ok_or_else(|| CodegenError::Internal("bhc_alloc not declared".to_string()))?;
        let alloc_call = self
            .builder()
            .build_call(*alloc_fn, &[size.into()], "pair_alloc")
            .map_err(|e| CodegenError::Internal(format!("failed to call alloc: {:?}", e)))?;
        let pair_ptr = alloc_call
            .try_as_basic_value()
            .basic()
            .ok_or_else(|| CodegenError::Internal("alloc returned void".to_string()))?
            .into_pointer_value();

        // Store tag = 0 (tuple constructor)
        self.builder()
            .build_store(pair_ptr, tm.i64_type().const_zero())
            .map_err(|e| CodegenError::Internal(format!("failed to store tag: {:?}", e)))?;

        // Store fst at offset 8
        let fst_slot = unsafe {
            self.builder()
                .build_gep(
                    tm.i64_type(),
                    pair_ptr,
                    &[tm.i64_type().const_int(1, false)],
                    "fst_ptr",
                )
                .map_err(|e| CodegenError::Internal(format!("failed to build gep: {:?}", e)))?
        };
        let fst_as_ptr = self.value_to_ptr(fst)?;
        self.builder()
            .build_store(fst_slot, fst_as_ptr)
            .map_err(|e| CodegenError::Internal(format!("failed to store fst: {:?}", e)))?;

        // Store snd at offset 16
        let snd_slot = unsafe {
            self.builder()
                .build_gep(
                    tm.i64_type(),
                    pair_ptr,
                    &[tm.i64_type().const_int(2, false)],
                    "snd_ptr",
                )
                .map_err(|e| CodegenError::Internal(format!("failed to build gep: {:?}", e)))?
        };
        let snd_as_ptr = self.value_to_ptr(snd)?;
        self.builder()
            .build_store(snd_slot, snd_as_ptr)
            .map_err(|e| CodegenError::Internal(format!("failed to store snd: {:?}", e)))?;

        Ok(pair_ptr)
    }

    /// Build a triple (3-tuple). ADT with tag 0 and 3 fields.
    fn build_triple(
        &self,
        fst: BasicValueEnum<'ctx>,
        snd: BasicValueEnum<'ctx>,
        trd: BasicValueEnum<'ctx>,
    ) -> CodegenResult<inkwell::values::PointerValue<'ctx>> {
        let triple_ptr = self.alloc_adt(0, 3)?;
        self.store_adt_field(triple_ptr, 3, 0, fst)?;
        self.store_adt_field(triple_ptr, 3, 1, snd)?;
        self.store_adt_field(triple_ptr, 3, 2, trd)?;
        Ok(triple_ptr)
    }

    /// Lower `last` - get last element of a list.
    /// last [] = error "empty list"
    /// last [x] = x
    /// last (_:xs) = last xs
    fn lower_builtin_last(
        &mut self,
        list_expr: &Expr,
    ) -> CodegenResult<Option<BasicValueEnum<'ctx>>> {
        let list_val = self
            .lower_expr(list_expr)?
            .ok_or_else(|| CodegenError::Internal("last: list has no value".to_string()))?;

        let list_ptr = match list_val {
            BasicValueEnum::PointerValue(p) => p,
            _ => return Err(CodegenError::TypeError("last expects a list".to_string())),
        };

        let tm = self.type_mapper();
        let ptr_type = tm.ptr_type();
        let current_fn = self
            .builder()
            .get_insert_block()
            .and_then(|b| b.get_parent())
            .ok_or_else(|| CodegenError::Internal("no current function".to_string()))?;

        let entry_block = self
            .builder()
            .get_insert_block()
            .ok_or_else(|| CodegenError::Internal("no current block".to_string()))?;

        // Check if list is empty at start
        let tag = self.extract_adt_tag(list_ptr)?;
        let is_empty = self
            .builder()
            .build_int_compare(
                inkwell::IntPredicate::EQ,
                tag,
                tm.i64_type().const_zero(),
                "is_empty",
            )
            .map_err(|e| CodegenError::Internal(format!("failed to compare tag: {:?}", e)))?;

        let error_block = self.llvm_ctx.append_basic_block(current_fn, "last_error");
        let loop_header = self.llvm_ctx.append_basic_block(current_fn, "last_header");
        let loop_body = self.llvm_ctx.append_basic_block(current_fn, "last_body");
        let loop_exit = self.llvm_ctx.append_basic_block(current_fn, "last_exit");

        self.builder()
            .build_conditional_branch(is_empty, error_block, loop_header)
            .map_err(|e| CodegenError::Internal(format!("failed to build branch: {:?}", e)))?;

        // Error block: empty list
        self.builder().position_at_end(error_block);
        let error_msg = self
            .module
            .add_global_string("last_error", "last: empty list");
        let error_fn = self
            .functions
            .get(&VarId::new(1000006))
            .ok_or_else(|| CodegenError::Internal("bhc_error not declared".to_string()))?;
        self.builder()
            .build_call(*error_fn, &[error_msg.into()], "")
            .map_err(|e| CodegenError::Internal(format!("failed to call error: {:?}", e)))?;
        self.builder()
            .build_unreachable()
            .map_err(|e| CodegenError::Internal(format!("failed to build unreachable: {:?}", e)))?;

        // Loop header
        self.builder().position_at_end(loop_header);
        let list_phi = self
            .builder()
            .build_phi(ptr_type, "last_list")
            .map_err(|e| CodegenError::Internal(format!("failed to build phi: {:?}", e)))?;

        // Extract head and tail
        let head_ptr =
            self.extract_adt_field(list_phi.as_basic_value().into_pointer_value(), 2, 0)?;
        let tail_ptr =
            self.extract_adt_field(list_phi.as_basic_value().into_pointer_value(), 2, 1)?;

        // Check if tail is empty (this element is the last)
        let tail_tag = self.extract_adt_tag(tail_ptr)?;
        let tail_is_empty = self
            .builder()
            .build_int_compare(
                inkwell::IntPredicate::EQ,
                tail_tag,
                tm.i64_type().const_zero(),
                "tail_is_empty",
            )
            .map_err(|e| CodegenError::Internal(format!("failed to compare tag: {:?}", e)))?;

        self.builder()
            .build_conditional_branch(tail_is_empty, loop_exit, loop_body)
            .map_err(|e| CodegenError::Internal(format!("failed to build branch: {:?}", e)))?;

        // Loop body: continue with tail
        self.builder().position_at_end(loop_body);
        self.builder()
            .build_unconditional_branch(loop_header)
            .map_err(|e| CodegenError::Internal(format!("failed to build branch: {:?}", e)))?;

        // Add phi incoming values
        list_phi.add_incoming(&[(&list_ptr, entry_block), (&tail_ptr, loop_body)]);

        // Exit: return head
        self.builder().position_at_end(loop_exit);
        Ok(Some(head_ptr.into()))
    }

    /// Lower `init` - get all but last element of a list.
    /// init [] = error "empty list"
    /// init [x] = []
    /// init (x:xs) = x : init xs
    fn lower_builtin_init(
        &mut self,
        list_expr: &Expr,
    ) -> CodegenResult<Option<BasicValueEnum<'ctx>>> {
        let list_val = self
            .lower_expr(list_expr)?
            .ok_or_else(|| CodegenError::Internal("init: list has no value".to_string()))?;

        let list_ptr = match list_val {
            BasicValueEnum::PointerValue(p) => p,
            _ => return Err(CodegenError::TypeError("init expects a list".to_string())),
        };

        let tm = self.type_mapper();
        let ptr_type = tm.ptr_type();
        let current_fn = self
            .builder()
            .get_insert_block()
            .and_then(|b| b.get_parent())
            .ok_or_else(|| CodegenError::Internal("no current function".to_string()))?;

        let entry_block = self
            .builder()
            .get_insert_block()
            .ok_or_else(|| CodegenError::Internal("no current block".to_string()))?;

        // Check if list is empty at start
        let tag = self.extract_adt_tag(list_ptr)?;
        let is_empty = self
            .builder()
            .build_int_compare(
                inkwell::IntPredicate::EQ,
                tag,
                tm.i64_type().const_zero(),
                "is_empty",
            )
            .map_err(|e| CodegenError::Internal(format!("failed to compare tag: {:?}", e)))?;

        let error_block = self.llvm_ctx.append_basic_block(current_fn, "init_error");
        let loop_header = self.llvm_ctx.append_basic_block(current_fn, "init_header");
        let loop_body = self.llvm_ctx.append_basic_block(current_fn, "init_body");
        let loop_exit = self.llvm_ctx.append_basic_block(current_fn, "init_exit");

        // Build nil in entry block
        let nil = self.build_nil()?;

        self.builder()
            .build_conditional_branch(is_empty, error_block, loop_header)
            .map_err(|e| CodegenError::Internal(format!("failed to build branch: {:?}", e)))?;

        // Error block: empty list
        self.builder().position_at_end(error_block);
        let error_msg = self
            .module
            .add_global_string("init_error", "init: empty list");
        let error_fn = self
            .functions
            .get(&VarId::new(1000006))
            .ok_or_else(|| CodegenError::Internal("bhc_error not declared".to_string()))?;
        self.builder()
            .build_call(*error_fn, &[error_msg.into()], "")
            .map_err(|e| CodegenError::Internal(format!("failed to call error: {:?}", e)))?;
        self.builder()
            .build_unreachable()
            .map_err(|e| CodegenError::Internal(format!("failed to build unreachable: {:?}", e)))?;

        // Loop header
        self.builder().position_at_end(loop_header);
        let result_phi = self
            .builder()
            .build_phi(ptr_type, "init_result")
            .map_err(|e| CodegenError::Internal(format!("failed to build phi: {:?}", e)))?;
        let list_phi = self
            .builder()
            .build_phi(ptr_type, "init_list")
            .map_err(|e| CodegenError::Internal(format!("failed to build phi: {:?}", e)))?;

        // Extract head and tail
        let head_ptr =
            self.extract_adt_field(list_phi.as_basic_value().into_pointer_value(), 2, 0)?;
        let tail_ptr =
            self.extract_adt_field(list_phi.as_basic_value().into_pointer_value(), 2, 1)?;

        // Check if tail is empty (this element is the last - don't include it)
        let tail_tag = self.extract_adt_tag(tail_ptr)?;
        let tail_is_empty = self
            .builder()
            .build_int_compare(
                inkwell::IntPredicate::EQ,
                tail_tag,
                tm.i64_type().const_zero(),
                "tail_is_empty",
            )
            .map_err(|e| CodegenError::Internal(format!("failed to compare tag: {:?}", e)))?;

        self.builder()
            .build_conditional_branch(tail_is_empty, loop_exit, loop_body)
            .map_err(|e| CodegenError::Internal(format!("failed to build branch: {:?}", e)))?;

        // Loop body: cons head onto result, continue with tail
        self.builder().position_at_end(loop_body);
        let new_cons = self.build_cons(head_ptr.into(), result_phi.as_basic_value())?;
        self.builder()
            .build_unconditional_branch(loop_header)
            .map_err(|e| CodegenError::Internal(format!("failed to build branch: {:?}", e)))?;

        // Add phi incoming values
        result_phi.add_incoming(&[(&nil, entry_block), (&new_cons, loop_body)]);
        list_phi.add_incoming(&[(&list_ptr, entry_block), (&tail_ptr, loop_body)]);

        // Exit: reverse the result
        self.builder().position_at_end(loop_exit);

        // Inline reverse
        let rev_header = self.llvm_ctx.append_basic_block(current_fn, "rev_header");
        let rev_body = self.llvm_ctx.append_basic_block(current_fn, "rev_body");
        let rev_exit = self.llvm_ctx.append_basic_block(current_fn, "rev_exit");

        let nil2 = self.build_nil()?;

        self.builder()
            .build_unconditional_branch(rev_header)
            .map_err(|e| CodegenError::Internal(format!("failed to build branch: {:?}", e)))?;

        self.builder().position_at_end(rev_header);
        let rev_acc = self
            .builder()
            .build_phi(ptr_type, "rev_acc")
            .map_err(|e| CodegenError::Internal(format!("failed to build phi: {:?}", e)))?;
        let rev_list = self
            .builder()
            .build_phi(ptr_type, "rev_list")
            .map_err(|e| CodegenError::Internal(format!("failed to build phi: {:?}", e)))?;

        let rev_tag = self.extract_adt_tag(rev_list.as_basic_value().into_pointer_value())?;
        let rev_is_empty = self
            .builder()
            .build_int_compare(
                inkwell::IntPredicate::EQ,
                rev_tag,
                tm.i64_type().const_zero(),
                "rev_is_empty",
            )
            .map_err(|e| CodegenError::Internal(format!("failed to compare tag: {:?}", e)))?;

        self.builder()
            .build_conditional_branch(rev_is_empty, rev_exit, rev_body)
            .map_err(|e| CodegenError::Internal(format!("failed to build branch: {:?}", e)))?;

        self.builder().position_at_end(rev_body);
        let rev_head =
            self.extract_adt_field(rev_list.as_basic_value().into_pointer_value(), 2, 0)?;
        let rev_tail =
            self.extract_adt_field(rev_list.as_basic_value().into_pointer_value(), 2, 1)?;
        let rev_new_cons = self.build_cons(rev_head.into(), rev_acc.as_basic_value())?;

        self.builder()
            .build_unconditional_branch(rev_header)
            .map_err(|e| CodegenError::Internal(format!("failed to build branch: {:?}", e)))?;

        rev_acc.add_incoming(&[(&nil2, loop_exit), (&rev_new_cons, rev_body)]);
        rev_list.add_incoming(&[
            (&result_phi.as_basic_value(), loop_exit),
            (&rev_tail, rev_body),
        ]);

        self.builder().position_at_end(rev_exit);
        Ok(Some(rev_acc.as_basic_value()))
    }

    /// Lower `!!` - index into a list.
    /// [] !! _ = error "index too large"
    /// (x:_) !! 0 = x
    /// (_:xs) !! n = xs !! (n-1)
    fn lower_builtin_index(
        &mut self,
        list_expr: &Expr,
        index_expr: &Expr,
    ) -> CodegenResult<Option<BasicValueEnum<'ctx>>> {
        let list_val = self
            .lower_expr(list_expr)?
            .ok_or_else(|| CodegenError::Internal("!!: list has no value".to_string()))?;

        let list_ptr = match list_val {
            BasicValueEnum::PointerValue(p) => p,
            _ => return Err(CodegenError::TypeError("!! expects a list".to_string())),
        };

        let index_val = self
            .lower_expr(index_expr)?
            .ok_or_else(|| CodegenError::Internal("!!: index has no value".to_string()))?;

        let index = self.to_int_value(index_val)?;

        let tm = self.type_mapper();
        let ptr_type = tm.ptr_type();
        let current_fn = self
            .builder()
            .get_insert_block()
            .and_then(|b| b.get_parent())
            .ok_or_else(|| CodegenError::Internal("no current function".to_string()))?;

        let entry_block = self
            .builder()
            .get_insert_block()
            .ok_or_else(|| CodegenError::Internal("no current block".to_string()))?;

        let loop_header = self.llvm_ctx.append_basic_block(current_fn, "index_header");
        let check_empty = self
            .llvm_ctx
            .append_basic_block(current_fn, "index_check_empty");
        let check_zero = self
            .llvm_ctx
            .append_basic_block(current_fn, "index_check_zero");
        let loop_body = self.llvm_ctx.append_basic_block(current_fn, "index_body");
        let error_block = self.llvm_ctx.append_basic_block(current_fn, "index_error");
        let loop_exit = self.llvm_ctx.append_basic_block(current_fn, "index_exit");

        self.builder()
            .build_unconditional_branch(loop_header)
            .map_err(|e| CodegenError::Internal(format!("failed to build branch: {:?}", e)))?;

        // Loop header
        self.builder().position_at_end(loop_header);
        let list_phi = self
            .builder()
            .build_phi(ptr_type, "index_list")
            .map_err(|e| CodegenError::Internal(format!("failed to build phi: {:?}", e)))?;
        let idx_phi = self
            .builder()
            .build_phi(tm.i64_type(), "index_idx")
            .map_err(|e| CodegenError::Internal(format!("failed to build phi: {:?}", e)))?;

        self.builder()
            .build_unconditional_branch(check_empty)
            .map_err(|e| CodegenError::Internal(format!("failed to build branch: {:?}", e)))?;

        // Check if list is empty
        self.builder().position_at_end(check_empty);
        let tag = self.extract_adt_tag(list_phi.as_basic_value().into_pointer_value())?;
        let is_empty = self
            .builder()
            .build_int_compare(
                inkwell::IntPredicate::EQ,
                tag,
                tm.i64_type().const_zero(),
                "is_empty",
            )
            .map_err(|e| CodegenError::Internal(format!("failed to compare tag: {:?}", e)))?;

        self.builder()
            .build_conditional_branch(is_empty, error_block, check_zero)
            .map_err(|e| CodegenError::Internal(format!("failed to build branch: {:?}", e)))?;

        // Check if index is zero
        self.builder().position_at_end(check_zero);
        let is_zero = self
            .builder()
            .build_int_compare(
                inkwell::IntPredicate::EQ,
                idx_phi.as_basic_value().into_int_value(),
                tm.i64_type().const_zero(),
                "is_zero",
            )
            .map_err(|e| CodegenError::Internal(format!("failed to compare: {:?}", e)))?;

        self.builder()
            .build_conditional_branch(is_zero, loop_exit, loop_body)
            .map_err(|e| CodegenError::Internal(format!("failed to build branch: {:?}", e)))?;

        // Error block: index out of bounds
        self.builder().position_at_end(error_block);
        let error_msg = self
            .module
            .add_global_string("index_error", "!!: index too large");
        let error_fn = self
            .functions
            .get(&VarId::new(1000006))
            .ok_or_else(|| CodegenError::Internal("bhc_error not declared".to_string()))?;
        self.builder()
            .build_call(*error_fn, &[error_msg.into()], "")
            .map_err(|e| CodegenError::Internal(format!("failed to call error: {:?}", e)))?;
        self.builder()
            .build_unreachable()
            .map_err(|e| CodegenError::Internal(format!("failed to build unreachable: {:?}", e)))?;

        // Loop body: decrement index, continue with tail
        self.builder().position_at_end(loop_body);
        let tail_ptr =
            self.extract_adt_field(list_phi.as_basic_value().into_pointer_value(), 2, 1)?;
        let new_idx = self
            .builder()
            .build_int_sub(
                idx_phi.as_basic_value().into_int_value(),
                tm.i64_type().const_int(1, false),
                "new_idx",
            )
            .map_err(|e| CodegenError::Internal(format!("failed to sub: {:?}", e)))?;
        self.builder()
            .build_unconditional_branch(loop_header)
            .map_err(|e| CodegenError::Internal(format!("failed to build branch: {:?}", e)))?;

        // Add phi incoming values
        list_phi.add_incoming(&[(&list_ptr, entry_block), (&tail_ptr, loop_body)]);
        idx_phi.add_incoming(&[(&index, entry_block), (&new_idx, loop_body)]);

        // Exit: return head
        self.builder().position_at_end(loop_exit);
        let head_ptr =
            self.extract_adt_field(list_phi.as_basic_value().into_pointer_value(), 2, 0)?;
        Ok(Some(head_ptr.into()))
    }

    /// Lower `concat` - flatten a list of lists.
    /// concat [] = []
    /// concat (xs:xss) = xs ++ concat xss
    fn lower_builtin_concat(
        &mut self,
        list_expr: &Expr,
    ) -> CodegenResult<Option<BasicValueEnum<'ctx>>> {
        let list_val = self
            .lower_expr(list_expr)?
            .ok_or_else(|| CodegenError::Internal("concat: list has no value".to_string()))?;

        let list_ptr = match list_val {
            BasicValueEnum::PointerValue(p) => p,
            _ => {
                return Err(CodegenError::TypeError(
                    "concat expects a list of lists".to_string(),
                ))
            }
        };

        let tm = self.type_mapper();
        let ptr_type = tm.ptr_type();
        let current_fn = self
            .builder()
            .get_insert_block()
            .and_then(|b| b.get_parent())
            .ok_or_else(|| CodegenError::Internal("no current function".to_string()))?;

        let entry_block = self
            .builder()
            .get_insert_block()
            .ok_or_else(|| CodegenError::Internal("no current block".to_string()))?;

        // Build nil in entry block
        let nil = self.build_nil()?;

        // Outer loop: iterate over list of lists
        let outer_header = self
            .llvm_ctx
            .append_basic_block(current_fn, "concat_outer_header");
        let outer_body = self
            .llvm_ctx
            .append_basic_block(current_fn, "concat_outer_body");
        let outer_exit = self
            .llvm_ctx
            .append_basic_block(current_fn, "concat_outer_exit");

        // Inner loop: iterate over current inner list
        let inner_header = self
            .llvm_ctx
            .append_basic_block(current_fn, "concat_inner_header");
        let inner_body = self
            .llvm_ctx
            .append_basic_block(current_fn, "concat_inner_body");
        let inner_exit = self
            .llvm_ctx
            .append_basic_block(current_fn, "concat_inner_exit");

        self.builder()
            .build_unconditional_branch(outer_header)
            .map_err(|e| CodegenError::Internal(format!("failed to build branch: {:?}", e)))?;

        // Outer loop header
        self.builder().position_at_end(outer_header);
        let result_phi = self
            .builder()
            .build_phi(ptr_type, "concat_result")
            .map_err(|e| CodegenError::Internal(format!("failed to build phi: {:?}", e)))?;
        let outer_list_phi = self
            .builder()
            .build_phi(ptr_type, "concat_outer_list")
            .map_err(|e| CodegenError::Internal(format!("failed to build phi: {:?}", e)))?;

        // Check if outer list is empty
        let outer_tag =
            self.extract_adt_tag(outer_list_phi.as_basic_value().into_pointer_value())?;
        let outer_is_empty = self
            .builder()
            .build_int_compare(
                inkwell::IntPredicate::EQ,
                outer_tag,
                tm.i64_type().const_zero(),
                "outer_is_empty",
            )
            .map_err(|e| CodegenError::Internal(format!("failed to compare tag: {:?}", e)))?;

        self.builder()
            .build_conditional_branch(outer_is_empty, outer_exit, outer_body)
            .map_err(|e| CodegenError::Internal(format!("failed to build branch: {:?}", e)))?;

        // Outer loop body: get current inner list and process it
        self.builder().position_at_end(outer_body);
        let inner_list_ptr =
            self.extract_adt_field(outer_list_phi.as_basic_value().into_pointer_value(), 2, 0)?;
        let outer_tail_ptr =
            self.extract_adt_field(outer_list_phi.as_basic_value().into_pointer_value(), 2, 1)?;

        self.builder()
            .build_unconditional_branch(inner_header)
            .map_err(|e| CodegenError::Internal(format!("failed to build branch: {:?}", e)))?;

        // Inner loop header
        self.builder().position_at_end(inner_header);
        let inner_result_phi = self
            .builder()
            .build_phi(ptr_type, "concat_inner_result")
            .map_err(|e| CodegenError::Internal(format!("failed to build phi: {:?}", e)))?;
        let inner_list_phi = self
            .builder()
            .build_phi(ptr_type, "concat_inner_list")
            .map_err(|e| CodegenError::Internal(format!("failed to build phi: {:?}", e)))?;

        // Check if inner list is empty
        let inner_tag =
            self.extract_adt_tag(inner_list_phi.as_basic_value().into_pointer_value())?;
        let inner_is_empty = self
            .builder()
            .build_int_compare(
                inkwell::IntPredicate::EQ,
                inner_tag,
                tm.i64_type().const_zero(),
                "inner_is_empty",
            )
            .map_err(|e| CodegenError::Internal(format!("failed to compare tag: {:?}", e)))?;

        self.builder()
            .build_conditional_branch(inner_is_empty, inner_exit, inner_body)
            .map_err(|e| CodegenError::Internal(format!("failed to build branch: {:?}", e)))?;

        // Inner loop body: cons head onto result
        self.builder().position_at_end(inner_body);
        let head_ptr =
            self.extract_adt_field(inner_list_phi.as_basic_value().into_pointer_value(), 2, 0)?;
        let tail_ptr =
            self.extract_adt_field(inner_list_phi.as_basic_value().into_pointer_value(), 2, 1)?;
        let new_cons = self.build_cons(head_ptr.into(), inner_result_phi.as_basic_value())?;

        self.builder()
            .build_unconditional_branch(inner_header)
            .map_err(|e| CodegenError::Internal(format!("failed to build branch: {:?}", e)))?;

        // Inner phi incoming values
        inner_result_phi.add_incoming(&[
            (&result_phi.as_basic_value(), outer_body),
            (&new_cons, inner_body),
        ]);
        inner_list_phi.add_incoming(&[(&inner_list_ptr, outer_body), (&tail_ptr, inner_body)]);

        // Inner loop exit: continue with outer loop
        self.builder().position_at_end(inner_exit);
        self.builder()
            .build_unconditional_branch(outer_header)
            .map_err(|e| CodegenError::Internal(format!("failed to build branch: {:?}", e)))?;

        // Outer phi incoming values
        result_phi.add_incoming(&[
            (&nil, entry_block),
            (&inner_result_phi.as_basic_value(), inner_exit),
        ]);
        outer_list_phi.add_incoming(&[(&list_ptr, entry_block), (&outer_tail_ptr, inner_exit)]);

        // Outer exit: reverse the result
        self.builder().position_at_end(outer_exit);

        // Inline reverse
        let rev_header = self.llvm_ctx.append_basic_block(current_fn, "rev_header");
        let rev_body = self.llvm_ctx.append_basic_block(current_fn, "rev_body");
        let rev_exit = self.llvm_ctx.append_basic_block(current_fn, "rev_exit");

        let nil2 = self.build_nil()?;

        self.builder()
            .build_unconditional_branch(rev_header)
            .map_err(|e| CodegenError::Internal(format!("failed to build branch: {:?}", e)))?;

        self.builder().position_at_end(rev_header);
        let rev_acc = self
            .builder()
            .build_phi(ptr_type, "rev_acc")
            .map_err(|e| CodegenError::Internal(format!("failed to build phi: {:?}", e)))?;
        let rev_list = self
            .builder()
            .build_phi(ptr_type, "rev_list")
            .map_err(|e| CodegenError::Internal(format!("failed to build phi: {:?}", e)))?;

        let rev_tag = self.extract_adt_tag(rev_list.as_basic_value().into_pointer_value())?;
        let rev_is_empty = self
            .builder()
            .build_int_compare(
                inkwell::IntPredicate::EQ,
                rev_tag,
                tm.i64_type().const_zero(),
                "rev_is_empty",
            )
            .map_err(|e| CodegenError::Internal(format!("failed to compare tag: {:?}", e)))?;

        self.builder()
            .build_conditional_branch(rev_is_empty, rev_exit, rev_body)
            .map_err(|e| CodegenError::Internal(format!("failed to build branch: {:?}", e)))?;

        self.builder().position_at_end(rev_body);
        let rev_head =
            self.extract_adt_field(rev_list.as_basic_value().into_pointer_value(), 2, 0)?;
        let rev_tail =
            self.extract_adt_field(rev_list.as_basic_value().into_pointer_value(), 2, 1)?;
        let rev_new_cons = self.build_cons(rev_head.into(), rev_acc.as_basic_value())?;

        self.builder()
            .build_unconditional_branch(rev_header)
            .map_err(|e| CodegenError::Internal(format!("failed to build branch: {:?}", e)))?;

        rev_acc.add_incoming(&[(&nil2, outer_exit), (&rev_new_cons, rev_body)]);
        rev_list.add_incoming(&[
            (&result_phi.as_basic_value(), outer_exit),
            (&rev_tail, rev_body),
        ]);

        self.builder().position_at_end(rev_exit);
        Ok(Some(rev_acc.as_basic_value()))
    }

    /// Lower `concatMap` - map and concatenate.
    /// concatMap f xs = concat (map f xs)
    fn lower_builtin_concat_map(
        &mut self,
        func_expr: &Expr,
        list_expr: &Expr,
    ) -> CodegenResult<Option<BasicValueEnum<'ctx>>> {
        // For efficiency, we implement this directly rather than via concat (map f xs)
        let func_val = self.lower_expr(func_expr)?.ok_or_else(|| {
            CodegenError::Internal("concatMap: function has no value".to_string())
        })?;

        let func_ptr = match func_val {
            BasicValueEnum::PointerValue(p) => p,
            _ => {
                return Err(CodegenError::TypeError(
                    "concatMap: function must be a closure".to_string(),
                ))
            }
        };

        let list_val = self
            .lower_expr(list_expr)?
            .ok_or_else(|| CodegenError::Internal("concatMap: list has no value".to_string()))?;

        let list_ptr = match list_val {
            BasicValueEnum::PointerValue(p) => p,
            _ => {
                return Err(CodegenError::TypeError(
                    "concatMap expects a list".to_string(),
                ))
            }
        };

        let tm = self.type_mapper();
        let ptr_type = tm.ptr_type();
        let current_fn = self
            .builder()
            .get_insert_block()
            .and_then(|b| b.get_parent())
            .ok_or_else(|| CodegenError::Internal("no current function".to_string()))?;

        let entry_block = self
            .builder()
            .get_insert_block()
            .ok_or_else(|| CodegenError::Internal("no current block".to_string()))?;

        // Build nil in entry block
        let nil = self.build_nil()?;

        // Outer loop: iterate over input list
        let outer_header = self
            .llvm_ctx
            .append_basic_block(current_fn, "concatmap_outer_header");
        let outer_body = self
            .llvm_ctx
            .append_basic_block(current_fn, "concatmap_outer_body");
        let outer_exit = self
            .llvm_ctx
            .append_basic_block(current_fn, "concatmap_outer_exit");

        // Inner loop: iterate over result of f applied to current element
        let inner_header = self
            .llvm_ctx
            .append_basic_block(current_fn, "concatmap_inner_header");
        let inner_body = self
            .llvm_ctx
            .append_basic_block(current_fn, "concatmap_inner_body");
        let inner_exit = self
            .llvm_ctx
            .append_basic_block(current_fn, "concatmap_inner_exit");

        self.builder()
            .build_unconditional_branch(outer_header)
            .map_err(|e| CodegenError::Internal(format!("failed to build branch: {:?}", e)))?;

        // Outer loop header
        self.builder().position_at_end(outer_header);
        let result_phi = self
            .builder()
            .build_phi(ptr_type, "concatmap_result")
            .map_err(|e| CodegenError::Internal(format!("failed to build phi: {:?}", e)))?;
        let outer_list_phi = self
            .builder()
            .build_phi(ptr_type, "concatmap_outer_list")
            .map_err(|e| CodegenError::Internal(format!("failed to build phi: {:?}", e)))?;

        // Check if outer list is empty
        let outer_tag =
            self.extract_adt_tag(outer_list_phi.as_basic_value().into_pointer_value())?;
        let outer_is_empty = self
            .builder()
            .build_int_compare(
                inkwell::IntPredicate::EQ,
                outer_tag,
                tm.i64_type().const_zero(),
                "outer_is_empty",
            )
            .map_err(|e| CodegenError::Internal(format!("failed to compare tag: {:?}", e)))?;

        self.builder()
            .build_conditional_branch(outer_is_empty, outer_exit, outer_body)
            .map_err(|e| CodegenError::Internal(format!("failed to build branch: {:?}", e)))?;

        // Outer loop body: apply f to head, process result
        self.builder().position_at_end(outer_body);
        let head_ptr =
            self.extract_adt_field(outer_list_phi.as_basic_value().into_pointer_value(), 2, 0)?;
        let outer_tail_ptr =
            self.extract_adt_field(outer_list_phi.as_basic_value().into_pointer_value(), 2, 1)?;

        // Call f on head
        let closure_fn_ptr = self.extract_closure_fn_ptr(func_ptr)?;
        let fn_type = ptr_type.fn_type(&[ptr_type.into(), ptr_type.into()], false);
        let inner_list_ptr = self
            .builder()
            .build_indirect_call(
                fn_type,
                closure_fn_ptr,
                &[func_ptr.into(), head_ptr.into()],
                "f_result",
            )
            .map_err(|e| CodegenError::Internal(format!("failed to call function: {:?}", e)))?
            .try_as_basic_value()
            .basic()
            .ok_or_else(|| {
                CodegenError::Internal("concatMap: function returned void".to_string())
            })?;

        self.builder()
            .build_unconditional_branch(inner_header)
            .map_err(|e| CodegenError::Internal(format!("failed to build branch: {:?}", e)))?;

        // Inner loop header
        self.builder().position_at_end(inner_header);
        let inner_result_phi = self
            .builder()
            .build_phi(ptr_type, "concatmap_inner_result")
            .map_err(|e| CodegenError::Internal(format!("failed to build phi: {:?}", e)))?;
        let inner_list_phi = self
            .builder()
            .build_phi(ptr_type, "concatmap_inner_list")
            .map_err(|e| CodegenError::Internal(format!("failed to build phi: {:?}", e)))?;

        // Check if inner list is empty
        let inner_tag =
            self.extract_adt_tag(inner_list_phi.as_basic_value().into_pointer_value())?;
        let inner_is_empty = self
            .builder()
            .build_int_compare(
                inkwell::IntPredicate::EQ,
                inner_tag,
                tm.i64_type().const_zero(),
                "inner_is_empty",
            )
            .map_err(|e| CodegenError::Internal(format!("failed to compare tag: {:?}", e)))?;

        self.builder()
            .build_conditional_branch(inner_is_empty, inner_exit, inner_body)
            .map_err(|e| CodegenError::Internal(format!("failed to build branch: {:?}", e)))?;

        // Inner loop body: cons head onto result
        self.builder().position_at_end(inner_body);
        let inner_head_ptr =
            self.extract_adt_field(inner_list_phi.as_basic_value().into_pointer_value(), 2, 0)?;
        let inner_tail_ptr =
            self.extract_adt_field(inner_list_phi.as_basic_value().into_pointer_value(), 2, 1)?;
        let new_cons = self.build_cons(inner_head_ptr.into(), inner_result_phi.as_basic_value())?;

        self.builder()
            .build_unconditional_branch(inner_header)
            .map_err(|e| CodegenError::Internal(format!("failed to build branch: {:?}", e)))?;

        // Inner phi incoming values
        inner_result_phi.add_incoming(&[
            (&result_phi.as_basic_value(), outer_body),
            (&new_cons, inner_body),
        ]);
        inner_list_phi
            .add_incoming(&[(&inner_list_ptr, outer_body), (&inner_tail_ptr, inner_body)]);

        // Inner loop exit: continue with outer loop
        self.builder().position_at_end(inner_exit);
        self.builder()
            .build_unconditional_branch(outer_header)
            .map_err(|e| CodegenError::Internal(format!("failed to build branch: {:?}", e)))?;

        // Outer phi incoming values
        result_phi.add_incoming(&[
            (&nil, entry_block),
            (&inner_result_phi.as_basic_value(), inner_exit),
        ]);
        outer_list_phi.add_incoming(&[(&list_ptr, entry_block), (&outer_tail_ptr, inner_exit)]);

        // Outer exit: reverse the result
        self.builder().position_at_end(outer_exit);

        // Inline reverse
        let rev_header = self.llvm_ctx.append_basic_block(current_fn, "rev_header");
        let rev_body = self.llvm_ctx.append_basic_block(current_fn, "rev_body");
        let rev_exit = self.llvm_ctx.append_basic_block(current_fn, "rev_exit");

        let nil2 = self.build_nil()?;

        self.builder()
            .build_unconditional_branch(rev_header)
            .map_err(|e| CodegenError::Internal(format!("failed to build branch: {:?}", e)))?;

        self.builder().position_at_end(rev_header);
        let rev_acc = self
            .builder()
            .build_phi(ptr_type, "rev_acc")
            .map_err(|e| CodegenError::Internal(format!("failed to build phi: {:?}", e)))?;
        let rev_list = self
            .builder()
            .build_phi(ptr_type, "rev_list")
            .map_err(|e| CodegenError::Internal(format!("failed to build phi: {:?}", e)))?;

        let rev_tag = self.extract_adt_tag(rev_list.as_basic_value().into_pointer_value())?;
        let rev_is_empty = self
            .builder()
            .build_int_compare(
                inkwell::IntPredicate::EQ,
                rev_tag,
                tm.i64_type().const_zero(),
                "rev_is_empty",
            )
            .map_err(|e| CodegenError::Internal(format!("failed to compare tag: {:?}", e)))?;

        self.builder()
            .build_conditional_branch(rev_is_empty, rev_exit, rev_body)
            .map_err(|e| CodegenError::Internal(format!("failed to build branch: {:?}", e)))?;

        self.builder().position_at_end(rev_body);
        let rev_head =
            self.extract_adt_field(rev_list.as_basic_value().into_pointer_value(), 2, 0)?;
        let rev_tail =
            self.extract_adt_field(rev_list.as_basic_value().into_pointer_value(), 2, 1)?;
        let rev_new_cons = self.build_cons(rev_head.into(), rev_acc.as_basic_value())?;

        self.builder()
            .build_unconditional_branch(rev_header)
            .map_err(|e| CodegenError::Internal(format!("failed to build branch: {:?}", e)))?;

        rev_acc.add_incoming(&[(&nil2, outer_exit), (&rev_new_cons, rev_body)]);
        rev_list.add_incoming(&[
            (&result_phi.as_basic_value(), outer_exit),
            (&rev_tail, rev_body),
        ]);

        self.builder().position_at_end(rev_exit);
        Ok(Some(rev_acc.as_basic_value()))
    }

    /// Lower `fst` - extract first element of a pair.
    fn lower_builtin_fst(
        &mut self,
        pair_expr: &Expr,
    ) -> CodegenResult<Option<BasicValueEnum<'ctx>>> {
        let pair_val = self
            .lower_expr(pair_expr)?
            .ok_or_else(|| CodegenError::Internal("fst: pair has no value".to_string()))?;

        let pair_ptr = match pair_val {
            BasicValueEnum::PointerValue(p) => p,
            _ => return Err(CodegenError::TypeError("fst expects a tuple".to_string())),
        };

        // Extract field 0 (first element)
        let fst_ptr = self.extract_adt_field(pair_ptr, 2, 0)?;
        Ok(Some(fst_ptr.into()))
    }

    /// Lower `snd` - extract second element of a pair.
    fn lower_builtin_snd(
        &mut self,
        pair_expr: &Expr,
    ) -> CodegenResult<Option<BasicValueEnum<'ctx>>> {
        let pair_val = self
            .lower_expr(pair_expr)?
            .ok_or_else(|| CodegenError::Internal("snd: pair has no value".to_string()))?;

        let pair_ptr = match pair_val {
            BasicValueEnum::PointerValue(p) => p,
            _ => return Err(CodegenError::TypeError("snd expects a tuple".to_string())),
        };

        // Extract field 1 (second element)
        let snd_ptr = self.extract_adt_field(pair_ptr, 2, 1)?;
        Ok(Some(snd_ptr.into()))
    }

    /// Lower `$sel_N` - extract Nth field from a tuple/dictionary.
    ///
    /// Field selectors are used for type class dictionary method extraction.
    /// The dictionary is represented as a tuple (ADT with tag 0 and N fields),
    /// and $sel_N extracts the Nth field.
    ///
    /// Memory layout of a tuple: { i64 tag, ptr field_0, ptr field_1, ... }
    /// So field N is at byte offset: 8 + N * 8 = (1 + N) * 8
    fn lower_builtin_field_selector(
        &mut self,
        tuple_expr: &Expr,
        field_index: u32,
    ) -> CodegenResult<Option<BasicValueEnum<'ctx>>> {
        let tuple_val = self.lower_expr(tuple_expr)?.ok_or_else(|| {
            CodegenError::Internal(format!("$sel_{}: tuple has no value", field_index))
        })?;

        let tuple_ptr = match tuple_val {
            BasicValueEnum::PointerValue(p) => p,
            _ => {
                return Err(CodegenError::TypeError(format!(
                    "$sel_{} expects a tuple/dictionary",
                    field_index
                )))
            }
        };

        let tm = self.type_mapper();

        // Use raw pointer arithmetic to access the field at the correct offset.
        // ADT layout: { i64 tag, ptr field_0, ptr field_1, ... }
        // Field N is at index (1 + N) when treating as array of i64-sized elements.
        let field_offset = 1 + field_index; // Skip tag (index 0)

        let field_ptr = unsafe {
            self.builder()
                .build_gep(
                    tm.i64_type(), // Treating memory as array of 8-byte slots
                    tuple_ptr,
                    &[tm.i64_type().const_int(field_offset as u64, false)],
                    &format!("field_ptr_{}", field_index),
                )
                .map_err(|e| {
                    CodegenError::Internal(format!("failed to build field gep: {:?}", e))
                })?
        };

        // Load the field value (which is a pointer)
        let field_val = self
            .builder()
            .build_load(tm.ptr_type(), field_ptr, &format!("field_{}", field_index))
            .map_err(|e| CodegenError::Internal(format!("failed to load field: {:?}", e)))?;

        Ok(Some(field_val))
    }

    /// Lower `fromJust` - extract value from Just, error on Nothing.
    fn lower_builtin_from_just(
        &mut self,
        maybe_expr: &Expr,
    ) -> CodegenResult<Option<BasicValueEnum<'ctx>>> {
        let maybe_val = self
            .lower_expr(maybe_expr)?
            .ok_or_else(|| CodegenError::Internal("fromJust: maybe has no value".to_string()))?;

        let maybe_ptr = match maybe_val {
            BasicValueEnum::PointerValue(p) => p,
            _ => {
                return Err(CodegenError::TypeError(
                    "fromJust expects Maybe".to_string(),
                ))
            }
        };

        let tag = self.extract_adt_tag(maybe_ptr)?;
        let tm = self.type_mapper();

        let current_fn = self
            .builder()
            .get_insert_block()
            .and_then(|b| b.get_parent())
            .ok_or_else(|| CodegenError::Internal("no current function".to_string()))?;

        let nothing_block = self
            .llvm_context()
            .append_basic_block(current_fn, "fromJust_nothing");
        let just_block = self
            .llvm_context()
            .append_basic_block(current_fn, "fromJust_just");

        // Tag: Nothing=0, Just=1
        let is_nothing = self
            .builder()
            .build_int_compare(
                inkwell::IntPredicate::EQ,
                tag,
                tm.i64_type().const_zero(),
                "is_nothing",
            )
            .map_err(|e| CodegenError::Internal(format!("failed to compare: {:?}", e)))?;

        self.builder()
            .build_conditional_branch(is_nothing, nothing_block, just_block)
            .map_err(|e| CodegenError::Internal(format!("failed to build branch: {:?}", e)))?;

        // Nothing case: error
        self.builder().position_at_end(nothing_block);
        let error_msg = self
            .module
            .add_global_string("fromJust_error", "fromJust: Nothing");
        let error_fn = self
            .functions
            .get(&VarId::new(1000006))
            .ok_or_else(|| CodegenError::Internal("bhc_error not declared".to_string()))?;
        self.builder()
            .build_call(*error_fn, &[error_msg.into()], "")
            .map_err(|e| CodegenError::Internal(format!("failed to call error: {:?}", e)))?;
        self.builder()
            .build_unreachable()
            .map_err(|e| CodegenError::Internal(format!("failed to build unreachable: {:?}", e)))?;

        // Just case: extract value
        self.builder().position_at_end(just_block);
        let val_ptr = self.extract_adt_field(maybe_ptr, 1, 0)?;
        Ok(Some(val_ptr.into()))
    }

    /// Lower `isJust` - check if Maybe is Just.
    fn lower_builtin_is_just(
        &mut self,
        maybe_expr: &Expr,
    ) -> CodegenResult<Option<BasicValueEnum<'ctx>>> {
        let maybe_val = self
            .lower_expr(maybe_expr)?
            .ok_or_else(|| CodegenError::Internal("isJust: maybe has no value".to_string()))?;

        let maybe_ptr = match maybe_val {
            BasicValueEnum::PointerValue(p) => p,
            _ => return Err(CodegenError::TypeError("isJust expects Maybe".to_string())),
        };

        let tag = self.extract_adt_tag(maybe_ptr)?;
        let tm = self.type_mapper();

        // isJust = (tag == 1)
        let is_just = self
            .builder()
            .build_int_compare(
                inkwell::IntPredicate::EQ,
                tag,
                tm.i64_type().const_int(1, false),
                "is_just",
            )
            .map_err(|e| CodegenError::Internal(format!("failed to compare: {:?}", e)))?;

        let result = self
            .builder()
            .build_int_z_extend(is_just, tm.i64_type(), "isJust_result")
            .map_err(|e| CodegenError::Internal(format!("failed to extend: {:?}", e)))?;

        Ok(Some(result.into()))
    }

    /// Lower `isNothing` - check if Maybe is Nothing.
    fn lower_builtin_is_nothing(
        &mut self,
        maybe_expr: &Expr,
    ) -> CodegenResult<Option<BasicValueEnum<'ctx>>> {
        let maybe_val = self
            .lower_expr(maybe_expr)?
            .ok_or_else(|| CodegenError::Internal("isNothing: maybe has no value".to_string()))?;

        let maybe_ptr = match maybe_val {
            BasicValueEnum::PointerValue(p) => p,
            _ => {
                return Err(CodegenError::TypeError(
                    "isNothing expects Maybe".to_string(),
                ))
            }
        };

        let tag = self.extract_adt_tag(maybe_ptr)?;
        let tm = self.type_mapper();

        // isNothing = (tag == 0)
        let is_nothing = self
            .builder()
            .build_int_compare(
                inkwell::IntPredicate::EQ,
                tag,
                tm.i64_type().const_zero(),
                "is_nothing",
            )
            .map_err(|e| CodegenError::Internal(format!("failed to compare: {:?}", e)))?;

        let result = self
            .builder()
            .build_int_z_extend(is_nothing, tm.i64_type(), "isNothing_result")
            .map_err(|e| CodegenError::Internal(format!("failed to extend: {:?}", e)))?;

        Ok(Some(result.into()))
    }

    /// Lower `isLeft` - check if Either is Left.
    fn lower_builtin_is_left(
        &mut self,
        either_expr: &Expr,
    ) -> CodegenResult<Option<BasicValueEnum<'ctx>>> {
        let either_val = self
            .lower_expr(either_expr)?
            .ok_or_else(|| CodegenError::Internal("isLeft: either has no value".to_string()))?;

        let either_ptr = match either_val {
            BasicValueEnum::PointerValue(p) => p,
            _ => return Err(CodegenError::TypeError("isLeft expects Either".to_string())),
        };

        let tag = self.extract_adt_tag(either_ptr)?;
        let tm = self.type_mapper();

        // isLeft = (tag == 0)
        let is_left = self
            .builder()
            .build_int_compare(
                inkwell::IntPredicate::EQ,
                tag,
                tm.i64_type().const_zero(),
                "is_left",
            )
            .map_err(|e| CodegenError::Internal(format!("failed to compare: {:?}", e)))?;

        let result = self
            .builder()
            .build_int_z_extend(is_left, tm.i64_type(), "isLeft_result")
            .map_err(|e| CodegenError::Internal(format!("failed to extend: {:?}", e)))?;

        Ok(Some(result.into()))
    }

    /// Lower `isRight` - check if Either is Right.
    fn lower_builtin_is_right(
        &mut self,
        either_expr: &Expr,
    ) -> CodegenResult<Option<BasicValueEnum<'ctx>>> {
        let either_val = self
            .lower_expr(either_expr)?
            .ok_or_else(|| CodegenError::Internal("isRight: either has no value".to_string()))?;

        let either_ptr = match either_val {
            BasicValueEnum::PointerValue(p) => p,
            _ => {
                return Err(CodegenError::TypeError(
                    "isRight expects Either".to_string(),
                ))
            }
        };

        let tag = self.extract_adt_tag(either_ptr)?;
        let tm = self.type_mapper();

        // isRight = (tag == 1)
        let is_right = self
            .builder()
            .build_int_compare(
                inkwell::IntPredicate::EQ,
                tag,
                tm.i64_type().const_int(1, false),
                "is_right",
            )
            .map_err(|e| CodegenError::Internal(format!("failed to compare: {:?}", e)))?;

        let result = self
            .builder()
            .build_int_z_extend(is_right, tm.i64_type(), "isRight_result")
            .map_err(|e| CodegenError::Internal(format!("failed to extend: {:?}", e)))?;

        Ok(Some(result.into()))
    }

    /// Lower `fromMaybe def maybe` — extract Just value or return default.
    fn lower_builtin_from_maybe(
        &mut self,
        def_expr: &Expr,
        maybe_expr: &Expr,
    ) -> CodegenResult<Option<BasicValueEnum<'ctx>>> {
        let def_val = self
            .lower_expr(def_expr)?
            .ok_or_else(|| CodegenError::Internal("fromMaybe: default has no value".to_string()))?;

        let maybe_val = self
            .lower_expr(maybe_expr)?
            .ok_or_else(|| CodegenError::Internal("fromMaybe: maybe has no value".to_string()))?;

        let maybe_ptr = match maybe_val {
            BasicValueEnum::PointerValue(p) => p,
            _ => {
                return Err(CodegenError::TypeError(
                    "fromMaybe expects Maybe".to_string(),
                ))
            }
        };

        let tag = self.extract_adt_tag(maybe_ptr)?;
        let tm = self.type_mapper();
        let ptr_type = tm.ptr_type();

        let current_fn = self
            .builder()
            .get_insert_block()
            .and_then(|b| b.get_parent())
            .ok_or_else(|| CodegenError::Internal("no current function".to_string()))?;

        let nothing_block = self
            .llvm_ctx
            .append_basic_block(current_fn, "fromMaybe_nothing");
        let just_block = self
            .llvm_ctx
            .append_basic_block(current_fn, "fromMaybe_just");
        let merge_block = self
            .llvm_ctx
            .append_basic_block(current_fn, "fromMaybe_merge");

        let is_nothing = self
            .builder()
            .build_int_compare(
                inkwell::IntPredicate::EQ,
                tag,
                tm.i64_type().const_zero(),
                "is_nothing",
            )
            .map_err(|e| CodegenError::Internal(format!("failed to compare: {:?}", e)))?;

        self.builder()
            .build_conditional_branch(is_nothing, nothing_block, just_block)
            .map_err(|e| CodegenError::Internal(format!("failed to build branch: {:?}", e)))?;

        // Nothing → return default
        self.builder().position_at_end(nothing_block);
        let def_as_ptr = match def_val {
            BasicValueEnum::PointerValue(p) => p,
            BasicValueEnum::IntValue(i) => self
                .builder()
                .build_int_to_ptr(i, ptr_type, "def_as_ptr")
                .map_err(|e| CodegenError::Internal(format!("failed to cast: {:?}", e)))?,
            _ => {
                return Err(CodegenError::TypeError(
                    "fromMaybe: unexpected default type".to_string(),
                ))
            }
        };
        self.builder()
            .build_unconditional_branch(merge_block)
            .map_err(|e| CodegenError::Internal(format!("failed to build branch: {:?}", e)))?;

        // Just → extract payload
        self.builder().position_at_end(just_block);
        let val_ptr = self.extract_adt_field(maybe_ptr, 1, 0)?;
        self.builder()
            .build_unconditional_branch(merge_block)
            .map_err(|e| CodegenError::Internal(format!("failed to build branch: {:?}", e)))?;

        // Merge
        self.builder().position_at_end(merge_block);
        let phi = self
            .builder()
            .build_phi(ptr_type, "fromMaybe_result")
            .map_err(|e| CodegenError::Internal(format!("failed to build phi: {:?}", e)))?;
        phi.add_incoming(&[(&def_as_ptr, nothing_block), (&val_ptr, just_block)]);

        Ok(Some(phi.as_basic_value()))
    }

    /// Lower `maybe def f maybe_val` — case analysis on Maybe.
    fn lower_builtin_maybe(
        &mut self,
        def_expr: &Expr,
        f_expr: &Expr,
        maybe_expr: &Expr,
    ) -> CodegenResult<Option<BasicValueEnum<'ctx>>> {
        let def_val = self
            .lower_expr(def_expr)?
            .ok_or_else(|| CodegenError::Internal("maybe: default has no value".to_string()))?;

        let f_val = self
            .lower_expr(f_expr)?
            .ok_or_else(|| CodegenError::Internal("maybe: function has no value".to_string()))?;

        let f_ptr = match f_val {
            BasicValueEnum::PointerValue(p) => p,
            _ => {
                return Err(CodegenError::TypeError(
                    "maybe: f must be a closure".to_string(),
                ))
            }
        };

        let maybe_val = self
            .lower_expr(maybe_expr)?
            .ok_or_else(|| CodegenError::Internal("maybe: maybe has no value".to_string()))?;

        let maybe_ptr = match maybe_val {
            BasicValueEnum::PointerValue(p) => p,
            _ => return Err(CodegenError::TypeError("maybe expects Maybe".to_string())),
        };

        let tag = self.extract_adt_tag(maybe_ptr)?;
        let tm = self.type_mapper();
        let ptr_type = tm.ptr_type();

        let current_fn = self
            .builder()
            .get_insert_block()
            .and_then(|b| b.get_parent())
            .ok_or_else(|| CodegenError::Internal("no current function".to_string()))?;

        let nothing_block = self
            .llvm_ctx
            .append_basic_block(current_fn, "maybe_nothing");
        let just_block = self.llvm_ctx.append_basic_block(current_fn, "maybe_just");
        let merge_block = self.llvm_ctx.append_basic_block(current_fn, "maybe_merge");

        let is_nothing = self
            .builder()
            .build_int_compare(
                inkwell::IntPredicate::EQ,
                tag,
                tm.i64_type().const_zero(),
                "is_nothing",
            )
            .map_err(|e| CodegenError::Internal(format!("failed to compare: {:?}", e)))?;

        self.builder()
            .build_conditional_branch(is_nothing, nothing_block, just_block)
            .map_err(|e| CodegenError::Internal(format!("failed to build branch: {:?}", e)))?;

        // Nothing → return default
        self.builder().position_at_end(nothing_block);
        let def_as_ptr = match def_val {
            BasicValueEnum::PointerValue(p) => p,
            BasicValueEnum::IntValue(i) => self
                .builder()
                .build_int_to_ptr(i, ptr_type, "def_as_ptr")
                .map_err(|e| CodegenError::Internal(format!("failed to cast: {:?}", e)))?,
            _ => {
                return Err(CodegenError::TypeError(
                    "maybe: unexpected default type".to_string(),
                ))
            }
        };
        self.builder()
            .build_unconditional_branch(merge_block)
            .map_err(|e| CodegenError::Internal(format!("failed to build branch: {:?}", e)))?;

        // Just → call f(payload)
        self.builder().position_at_end(just_block);
        let payload = self.extract_adt_field(maybe_ptr, 1, 0)?;
        let fn_ptr = self.extract_closure_fn_ptr(f_ptr)?;
        let fn_type = ptr_type.fn_type(&[ptr_type.into(), ptr_type.into()], false);
        let call_result = self
            .builder()
            .build_indirect_call(
                fn_type,
                fn_ptr,
                &[f_ptr.into(), payload.into()],
                "maybe_call",
            )
            .map_err(|e| CodegenError::Internal(format!("failed to call f: {:?}", e)))?
            .try_as_basic_value()
            .basic()
            .ok_or_else(|| CodegenError::Internal("maybe: f returned void".to_string()))?;
        let call_as_ptr = match call_result {
            BasicValueEnum::PointerValue(p) => p,
            BasicValueEnum::IntValue(i) => self
                .builder()
                .build_int_to_ptr(i, ptr_type, "call_as_ptr")
                .map_err(|e| CodegenError::Internal(format!("failed to cast: {:?}", e)))?,
            _ => {
                return Err(CodegenError::TypeError(
                    "maybe: f returned unexpected type".to_string(),
                ))
            }
        };
        self.builder()
            .build_unconditional_branch(merge_block)
            .map_err(|e| CodegenError::Internal(format!("failed to build branch: {:?}", e)))?;

        // Merge
        self.builder().position_at_end(merge_block);
        let phi = self
            .builder()
            .build_phi(ptr_type, "maybe_result")
            .map_err(|e| CodegenError::Internal(format!("failed to build phi: {:?}", e)))?;
        phi.add_incoming(&[(&def_as_ptr, nothing_block), (&call_as_ptr, just_block)]);

        Ok(Some(phi.as_basic_value()))
    }

    /// Lower `listToMaybe list` — head of list as Maybe.
    fn lower_builtin_list_to_maybe(
        &mut self,
        list_expr: &Expr,
    ) -> CodegenResult<Option<BasicValueEnum<'ctx>>> {
        let list_val = self
            .lower_expr(list_expr)?
            .ok_or_else(|| CodegenError::Internal("listToMaybe: list has no value".to_string()))?;

        let list_ptr = match list_val {
            BasicValueEnum::PointerValue(p) => p,
            _ => {
                return Err(CodegenError::TypeError(
                    "listToMaybe expects a list".to_string(),
                ))
            }
        };

        let tag = self.extract_adt_tag(list_ptr)?;
        let tm = self.type_mapper();
        let ptr_type = tm.ptr_type();

        let current_fn = self
            .builder()
            .get_insert_block()
            .and_then(|b| b.get_parent())
            .ok_or_else(|| CodegenError::Internal("no current function".to_string()))?;

        let nil_block = self.llvm_ctx.append_basic_block(current_fn, "ltm_nil");
        let cons_block = self.llvm_ctx.append_basic_block(current_fn, "ltm_cons");
        let merge_block = self.llvm_ctx.append_basic_block(current_fn, "ltm_merge");

        let is_nil = self
            .builder()
            .build_int_compare(
                inkwell::IntPredicate::EQ,
                tag,
                tm.i64_type().const_zero(),
                "is_nil",
            )
            .map_err(|e| CodegenError::Internal(format!("failed to compare: {:?}", e)))?;

        self.builder()
            .build_conditional_branch(is_nil, nil_block, cons_block)
            .map_err(|e| CodegenError::Internal(format!("failed to build branch: {:?}", e)))?;

        // Nil → Nothing (tag=0, arity=0)
        self.builder().position_at_end(nil_block);
        let nothing = self.alloc_adt(0, 0)?;
        self.builder()
            .build_unconditional_branch(merge_block)
            .map_err(|e| CodegenError::Internal(format!("failed to build branch: {:?}", e)))?;

        // Cons → Just head (tag=1, arity=1)
        self.builder().position_at_end(cons_block);
        let head = self.extract_adt_field(list_ptr, 2, 0)?;
        let just = self.alloc_adt(1, 1)?;
        self.store_adt_field(just, 1, 0, head.into())?;
        self.builder()
            .build_unconditional_branch(merge_block)
            .map_err(|e| CodegenError::Internal(format!("failed to build branch: {:?}", e)))?;

        // Merge
        self.builder().position_at_end(merge_block);
        let phi = self
            .builder()
            .build_phi(ptr_type, "listToMaybe_result")
            .map_err(|e| CodegenError::Internal(format!("failed to build phi: {:?}", e)))?;
        phi.add_incoming(&[(&nothing, nil_block), (&just, cons_block)]);

        Ok(Some(phi.as_basic_value()))
    }

    /// Lower `maybeToList maybe` — Maybe to singleton or empty list.
    fn lower_builtin_maybe_to_list(
        &mut self,
        maybe_expr: &Expr,
    ) -> CodegenResult<Option<BasicValueEnum<'ctx>>> {
        let maybe_val = self
            .lower_expr(maybe_expr)?
            .ok_or_else(|| CodegenError::Internal("maybeToList: maybe has no value".to_string()))?;

        let maybe_ptr = match maybe_val {
            BasicValueEnum::PointerValue(p) => p,
            _ => {
                return Err(CodegenError::TypeError(
                    "maybeToList expects Maybe".to_string(),
                ))
            }
        };

        let tag = self.extract_adt_tag(maybe_ptr)?;
        let tm = self.type_mapper();
        let ptr_type = tm.ptr_type();

        let current_fn = self
            .builder()
            .get_insert_block()
            .and_then(|b| b.get_parent())
            .ok_or_else(|| CodegenError::Internal("no current function".to_string()))?;

        let nothing_block = self.llvm_ctx.append_basic_block(current_fn, "mtl_nothing");
        let just_block = self.llvm_ctx.append_basic_block(current_fn, "mtl_just");
        let merge_block = self.llvm_ctx.append_basic_block(current_fn, "mtl_merge");

        let is_nothing = self
            .builder()
            .build_int_compare(
                inkwell::IntPredicate::EQ,
                tag,
                tm.i64_type().const_zero(),
                "is_nothing",
            )
            .map_err(|e| CodegenError::Internal(format!("failed to compare: {:?}", e)))?;

        self.builder()
            .build_conditional_branch(is_nothing, nothing_block, just_block)
            .map_err(|e| CodegenError::Internal(format!("failed to build branch: {:?}", e)))?;

        // Nothing → []
        self.builder().position_at_end(nothing_block);
        let nil = self.build_nil()?;
        self.builder()
            .build_unconditional_branch(merge_block)
            .map_err(|e| CodegenError::Internal(format!("failed to build branch: {:?}", e)))?;

        // Just → [payload]
        self.builder().position_at_end(just_block);
        let payload = self.extract_adt_field(maybe_ptr, 1, 0)?;
        let nil2 = self.build_nil()?;
        let singleton = self.build_cons(payload.into(), nil2.into())?;
        self.builder()
            .build_unconditional_branch(merge_block)
            .map_err(|e| CodegenError::Internal(format!("failed to build branch: {:?}", e)))?;

        // Merge
        self.builder().position_at_end(merge_block);
        let phi = self
            .builder()
            .build_phi(ptr_type, "maybeToList_result")
            .map_err(|e| CodegenError::Internal(format!("failed to build phi: {:?}", e)))?;
        phi.add_incoming(&[(&nil, nothing_block), (&singleton, just_block)]);

        Ok(Some(phi.as_basic_value()))
    }

    /// Lower `guard bool` — [() | True], [| False].
    fn lower_builtin_guard(
        &mut self,
        bool_expr: &Expr,
    ) -> CodegenResult<Option<BasicValueEnum<'ctx>>> {
        let bool_val = self
            .lower_expr(bool_expr)?
            .ok_or_else(|| CodegenError::Internal("guard: bool has no value".to_string()))?;

        let tm = self.type_mapper();
        let ptr_type = tm.ptr_type();

        // Bool is an ADT with tag 0=False, 1=True — extract the tag
        let bool_int = match bool_val {
            BasicValueEnum::PointerValue(p) => self.extract_adt_tag(p)?,
            BasicValueEnum::IntValue(i) => i,
            _ => return Err(CodegenError::TypeError("guard expects Bool".to_string())),
        };

        let current_fn = self
            .builder()
            .get_insert_block()
            .and_then(|b| b.get_parent())
            .ok_or_else(|| CodegenError::Internal("no current function".to_string()))?;

        let true_block = self.llvm_ctx.append_basic_block(current_fn, "guard_true");
        let false_block = self.llvm_ctx.append_basic_block(current_fn, "guard_false");
        let merge_block = self.llvm_ctx.append_basic_block(current_fn, "guard_merge");

        let is_true = self
            .builder()
            .build_int_compare(
                inkwell::IntPredicate::NE,
                bool_int,
                tm.i64_type().const_zero(),
                "is_true",
            )
            .map_err(|e| CodegenError::Internal(format!("failed to compare: {:?}", e)))?;

        self.builder()
            .build_conditional_branch(is_true, true_block, false_block)
            .map_err(|e| CodegenError::Internal(format!("failed to build branch: {:?}", e)))?;

        // True → [()] — unit is null pointer
        self.builder().position_at_end(true_block);
        let unit_val = ptr_type.const_null();
        let nil = self.build_nil()?;
        let singleton = self.build_cons(unit_val.into(), nil.into())?;
        self.builder()
            .build_unconditional_branch(merge_block)
            .map_err(|e| CodegenError::Internal(format!("failed to build branch: {:?}", e)))?;

        // False → []
        self.builder().position_at_end(false_block);
        let empty = self.build_nil()?;
        self.builder()
            .build_unconditional_branch(merge_block)
            .map_err(|e| CodegenError::Internal(format!("failed to build branch: {:?}", e)))?;

        // Merge
        self.builder().position_at_end(merge_block);
        let phi = self
            .builder()
            .build_phi(ptr_type, "guard_result")
            .map_err(|e| CodegenError::Internal(format!("failed to build phi: {:?}", e)))?;
        phi.add_incoming(&[(&singleton, true_block), (&empty, false_block)]);

        Ok(Some(phi.as_basic_value()))
    }

    /// Lower `fromLeft def either` — extract Left or return default.
    fn lower_builtin_from_left(
        &mut self,
        def_expr: &Expr,
        either_expr: &Expr,
    ) -> CodegenResult<Option<BasicValueEnum<'ctx>>> {
        let def_val = self
            .lower_expr(def_expr)?
            .ok_or_else(|| CodegenError::Internal("fromLeft: default has no value".to_string()))?;

        let either_val = self
            .lower_expr(either_expr)?
            .ok_or_else(|| CodegenError::Internal("fromLeft: either has no value".to_string()))?;

        let either_ptr = match either_val {
            BasicValueEnum::PointerValue(p) => p,
            _ => {
                return Err(CodegenError::TypeError(
                    "fromLeft expects Either".to_string(),
                ))
            }
        };

        let tag = self.extract_adt_tag(either_ptr)?;
        let tm = self.type_mapper();
        let ptr_type = tm.ptr_type();

        let current_fn = self
            .builder()
            .get_insert_block()
            .and_then(|b| b.get_parent())
            .ok_or_else(|| CodegenError::Internal("no current function".to_string()))?;

        let left_block = self
            .llvm_ctx
            .append_basic_block(current_fn, "fromLeft_left");
        let right_block = self
            .llvm_ctx
            .append_basic_block(current_fn, "fromLeft_right");
        let merge_block = self
            .llvm_ctx
            .append_basic_block(current_fn, "fromLeft_merge");

        // Left=0, Right=1
        let is_left = self
            .builder()
            .build_int_compare(
                inkwell::IntPredicate::EQ,
                tag,
                tm.i64_type().const_zero(),
                "is_left",
            )
            .map_err(|e| CodegenError::Internal(format!("failed to compare: {:?}", e)))?;

        self.builder()
            .build_conditional_branch(is_left, left_block, right_block)
            .map_err(|e| CodegenError::Internal(format!("failed to build branch: {:?}", e)))?;

        // Left → extract payload
        self.builder().position_at_end(left_block);
        let payload = self.extract_adt_field(either_ptr, 1, 0)?;
        self.builder()
            .build_unconditional_branch(merge_block)
            .map_err(|e| CodegenError::Internal(format!("failed to build branch: {:?}", e)))?;

        // Right → return default
        self.builder().position_at_end(right_block);
        let def_as_ptr = match def_val {
            BasicValueEnum::PointerValue(p) => p,
            BasicValueEnum::IntValue(i) => self
                .builder()
                .build_int_to_ptr(i, ptr_type, "def_as_ptr")
                .map_err(|e| CodegenError::Internal(format!("failed to cast: {:?}", e)))?,
            _ => {
                return Err(CodegenError::TypeError(
                    "fromLeft: unexpected default type".to_string(),
                ))
            }
        };
        self.builder()
            .build_unconditional_branch(merge_block)
            .map_err(|e| CodegenError::Internal(format!("failed to build branch: {:?}", e)))?;

        // Merge
        self.builder().position_at_end(merge_block);
        let phi = self
            .builder()
            .build_phi(ptr_type, "fromLeft_result")
            .map_err(|e| CodegenError::Internal(format!("failed to build phi: {:?}", e)))?;
        phi.add_incoming(&[(&payload, left_block), (&def_as_ptr, right_block)]);

        Ok(Some(phi.as_basic_value()))
    }

    /// Lower `fromRight def either` — extract Right or return default.
    fn lower_builtin_from_right(
        &mut self,
        def_expr: &Expr,
        either_expr: &Expr,
    ) -> CodegenResult<Option<BasicValueEnum<'ctx>>> {
        let def_val = self
            .lower_expr(def_expr)?
            .ok_or_else(|| CodegenError::Internal("fromRight: default has no value".to_string()))?;

        let either_val = self
            .lower_expr(either_expr)?
            .ok_or_else(|| CodegenError::Internal("fromRight: either has no value".to_string()))?;

        let either_ptr = match either_val {
            BasicValueEnum::PointerValue(p) => p,
            _ => {
                return Err(CodegenError::TypeError(
                    "fromRight expects Either".to_string(),
                ))
            }
        };

        let tag = self.extract_adt_tag(either_ptr)?;
        let tm = self.type_mapper();
        let ptr_type = tm.ptr_type();

        let current_fn = self
            .builder()
            .get_insert_block()
            .and_then(|b| b.get_parent())
            .ok_or_else(|| CodegenError::Internal("no current function".to_string()))?;

        let left_block = self
            .llvm_ctx
            .append_basic_block(current_fn, "fromRight_left");
        let right_block = self
            .llvm_ctx
            .append_basic_block(current_fn, "fromRight_right");
        let merge_block = self
            .llvm_ctx
            .append_basic_block(current_fn, "fromRight_merge");

        // Right=1
        let is_right = self
            .builder()
            .build_int_compare(
                inkwell::IntPredicate::EQ,
                tag,
                tm.i64_type().const_int(1, false),
                "is_right",
            )
            .map_err(|e| CodegenError::Internal(format!("failed to compare: {:?}", e)))?;

        self.builder()
            .build_conditional_branch(is_right, right_block, left_block)
            .map_err(|e| CodegenError::Internal(format!("failed to build branch: {:?}", e)))?;

        // Right → extract payload
        self.builder().position_at_end(right_block);
        let payload = self.extract_adt_field(either_ptr, 1, 0)?;
        self.builder()
            .build_unconditional_branch(merge_block)
            .map_err(|e| CodegenError::Internal(format!("failed to build branch: {:?}", e)))?;

        // Left → return default
        self.builder().position_at_end(left_block);
        let def_as_ptr = match def_val {
            BasicValueEnum::PointerValue(p) => p,
            BasicValueEnum::IntValue(i) => self
                .builder()
                .build_int_to_ptr(i, ptr_type, "def_as_ptr")
                .map_err(|e| CodegenError::Internal(format!("failed to cast: {:?}", e)))?,
            _ => {
                return Err(CodegenError::TypeError(
                    "fromRight: unexpected default type".to_string(),
                ))
            }
        };
        self.builder()
            .build_unconditional_branch(merge_block)
            .map_err(|e| CodegenError::Internal(format!("failed to build branch: {:?}", e)))?;

        // Merge
        self.builder().position_at_end(merge_block);
        let phi = self
            .builder()
            .build_phi(ptr_type, "fromRight_result")
            .map_err(|e| CodegenError::Internal(format!("failed to build phi: {:?}", e)))?;
        phi.add_incoming(&[(&payload, right_block), (&def_as_ptr, left_block)]);

        Ok(Some(phi.as_basic_value()))
    }

    /// Lower `either f g either_val` — case analysis on Either.
    fn lower_builtin_either(
        &mut self,
        f_expr: &Expr,
        g_expr: &Expr,
        either_expr: &Expr,
    ) -> CodegenResult<Option<BasicValueEnum<'ctx>>> {
        let f_val = self
            .lower_expr(f_expr)?
            .ok_or_else(|| CodegenError::Internal("either: f has no value".to_string()))?;
        let f_ptr = match f_val {
            BasicValueEnum::PointerValue(p) => p,
            _ => {
                return Err(CodegenError::TypeError(
                    "either: f must be a closure".to_string(),
                ))
            }
        };

        let g_val = self
            .lower_expr(g_expr)?
            .ok_or_else(|| CodegenError::Internal("either: g has no value".to_string()))?;
        let g_ptr = match g_val {
            BasicValueEnum::PointerValue(p) => p,
            _ => {
                return Err(CodegenError::TypeError(
                    "either: g must be a closure".to_string(),
                ))
            }
        };

        let either_val = self
            .lower_expr(either_expr)?
            .ok_or_else(|| CodegenError::Internal("either: either has no value".to_string()))?;
        let either_ptr = match either_val {
            BasicValueEnum::PointerValue(p) => p,
            _ => return Err(CodegenError::TypeError("either expects Either".to_string())),
        };

        let tag = self.extract_adt_tag(either_ptr)?;
        let tm = self.type_mapper();
        let ptr_type = tm.ptr_type();

        let current_fn = self
            .builder()
            .get_insert_block()
            .and_then(|b| b.get_parent())
            .ok_or_else(|| CodegenError::Internal("no current function".to_string()))?;

        let left_block = self.llvm_ctx.append_basic_block(current_fn, "either_left");
        let right_block = self.llvm_ctx.append_basic_block(current_fn, "either_right");
        let merge_block = self.llvm_ctx.append_basic_block(current_fn, "either_merge");

        let is_left = self
            .builder()
            .build_int_compare(
                inkwell::IntPredicate::EQ,
                tag,
                tm.i64_type().const_zero(),
                "is_left",
            )
            .map_err(|e| CodegenError::Internal(format!("failed to compare: {:?}", e)))?;

        self.builder()
            .build_conditional_branch(is_left, left_block, right_block)
            .map_err(|e| CodegenError::Internal(format!("failed to build branch: {:?}", e)))?;

        let fn_type = ptr_type.fn_type(&[ptr_type.into(), ptr_type.into()], false);

        // Left → call f(payload)
        self.builder().position_at_end(left_block);
        let left_payload = self.extract_adt_field(either_ptr, 1, 0)?;
        let f_fn_ptr = self.extract_closure_fn_ptr(f_ptr)?;
        let left_result = self
            .builder()
            .build_indirect_call(
                fn_type,
                f_fn_ptr,
                &[f_ptr.into(), left_payload.into()],
                "either_f_call",
            )
            .map_err(|e| CodegenError::Internal(format!("failed to call f: {:?}", e)))?
            .try_as_basic_value()
            .basic()
            .ok_or_else(|| CodegenError::Internal("either: f returned void".to_string()))?;
        let left_as_ptr = match left_result {
            BasicValueEnum::PointerValue(p) => p,
            BasicValueEnum::IntValue(i) => self
                .builder()
                .build_int_to_ptr(i, ptr_type, "left_as_ptr")
                .map_err(|e| CodegenError::Internal(format!("failed to cast: {:?}", e)))?,
            _ => {
                return Err(CodegenError::TypeError(
                    "either: f returned unexpected type".to_string(),
                ))
            }
        };
        self.builder()
            .build_unconditional_branch(merge_block)
            .map_err(|e| CodegenError::Internal(format!("failed to build branch: {:?}", e)))?;

        // Right → call g(payload)
        self.builder().position_at_end(right_block);
        let right_payload = self.extract_adt_field(either_ptr, 1, 0)?;
        let g_fn_ptr = self.extract_closure_fn_ptr(g_ptr)?;
        let right_result = self
            .builder()
            .build_indirect_call(
                fn_type,
                g_fn_ptr,
                &[g_ptr.into(), right_payload.into()],
                "either_g_call",
            )
            .map_err(|e| CodegenError::Internal(format!("failed to call g: {:?}", e)))?
            .try_as_basic_value()
            .basic()
            .ok_or_else(|| CodegenError::Internal("either: g returned void".to_string()))?;
        let right_as_ptr = match right_result {
            BasicValueEnum::PointerValue(p) => p,
            BasicValueEnum::IntValue(i) => self
                .builder()
                .build_int_to_ptr(i, ptr_type, "right_as_ptr")
                .map_err(|e| CodegenError::Internal(format!("failed to cast: {:?}", e)))?,
            _ => {
                return Err(CodegenError::TypeError(
                    "either: g returned unexpected type".to_string(),
                ))
            }
        };
        self.builder()
            .build_unconditional_branch(merge_block)
            .map_err(|e| CodegenError::Internal(format!("failed to build branch: {:?}", e)))?;

        // Merge
        self.builder().position_at_end(merge_block);
        let phi = self
            .builder()
            .build_phi(ptr_type, "either_result")
            .map_err(|e| CodegenError::Internal(format!("failed to build phi: {:?}", e)))?;
        phi.add_incoming(&[(&left_as_ptr, left_block), (&right_as_ptr, right_block)]);

        Ok(Some(phi.as_basic_value()))
    }

    /// Lower `catMaybes list` — filter+extract Just values from [Maybe a].
    fn lower_builtin_cat_maybes(
        &mut self,
        list_expr: &Expr,
    ) -> CodegenResult<Option<BasicValueEnum<'ctx>>> {
        let list_val = self
            .lower_expr(list_expr)?
            .ok_or_else(|| CodegenError::Internal("catMaybes: list has no value".to_string()))?;

        let list_ptr = match list_val {
            BasicValueEnum::PointerValue(p) => p,
            _ => {
                return Err(CodegenError::TypeError(
                    "catMaybes expects a list".to_string(),
                ))
            }
        };

        let tm = self.type_mapper();
        let ptr_type = tm.ptr_type();
        let current_fn = self
            .builder()
            .get_insert_block()
            .and_then(|b| b.get_parent())
            .ok_or_else(|| CodegenError::Internal("no current function".to_string()))?;

        let entry_block = self
            .builder()
            .get_insert_block()
            .ok_or_else(|| CodegenError::Internal("no current block".to_string()))?;

        let loop_header = self.llvm_ctx.append_basic_block(current_fn, "catM_header");
        let loop_body = self.llvm_ctx.append_basic_block(current_fn, "catM_body");
        let loop_keep = self.llvm_ctx.append_basic_block(current_fn, "catM_keep");
        let loop_skip = self.llvm_ctx.append_basic_block(current_fn, "catM_skip");
        let loop_exit = self.llvm_ctx.append_basic_block(current_fn, "catM_exit");

        let nil = self.build_nil()?;
        self.builder()
            .build_unconditional_branch(loop_header)
            .map_err(|e| CodegenError::Internal(format!("failed to build branch: {:?}", e)))?;

        // Loop header
        self.builder().position_at_end(loop_header);
        let acc_phi = self
            .builder()
            .build_phi(ptr_type, "catM_acc")
            .map_err(|e| CodegenError::Internal(format!("failed to build phi: {:?}", e)))?;
        let list_phi = self
            .builder()
            .build_phi(ptr_type, "catM_list")
            .map_err(|e| CodegenError::Internal(format!("failed to build phi: {:?}", e)))?;

        let tag = self.extract_adt_tag(list_phi.as_basic_value().into_pointer_value())?;
        let is_empty = self
            .builder()
            .build_int_compare(
                inkwell::IntPredicate::EQ,
                tag,
                tm.i64_type().const_zero(),
                "is_empty",
            )
            .map_err(|e| CodegenError::Internal(format!("failed to compare: {:?}", e)))?;
        self.builder()
            .build_conditional_branch(is_empty, loop_exit, loop_body)
            .map_err(|e| CodegenError::Internal(format!("failed to build branch: {:?}", e)))?;

        // Loop body: extract head (a Maybe) and tail
        self.builder().position_at_end(loop_body);
        let head = self.extract_adt_field(list_phi.as_basic_value().into_pointer_value(), 2, 0)?;
        let tail = self.extract_adt_field(list_phi.as_basic_value().into_pointer_value(), 2, 1)?;

        // Check if head is Just (tag==1) or Nothing (tag==0)
        let maybe_tag = self.extract_adt_tag(head)?;
        let is_just = self
            .builder()
            .build_int_compare(
                inkwell::IntPredicate::NE,
                maybe_tag,
                tm.i64_type().const_zero(),
                "is_just",
            )
            .map_err(|e| CodegenError::Internal(format!("failed to compare: {:?}", e)))?;
        self.builder()
            .build_conditional_branch(is_just, loop_keep, loop_skip)
            .map_err(|e| CodegenError::Internal(format!("failed to build branch: {:?}", e)))?;

        // Keep: extract Just payload, cons onto acc
        self.builder().position_at_end(loop_keep);
        let payload = self.extract_adt_field(head, 1, 0)?;
        let new_cons = self.build_cons(payload.into(), acc_phi.as_basic_value())?;
        self.builder()
            .build_unconditional_branch(loop_header)
            .map_err(|e| CodegenError::Internal(format!("failed to build branch: {:?}", e)))?;

        // Skip
        self.builder().position_at_end(loop_skip);
        self.builder()
            .build_unconditional_branch(loop_header)
            .map_err(|e| CodegenError::Internal(format!("failed to build branch: {:?}", e)))?;

        // Phi incoming
        acc_phi.add_incoming(&[
            (&nil, entry_block),
            (&new_cons, loop_keep),
            (&acc_phi.as_basic_value(), loop_skip),
        ]);
        list_phi.add_incoming(&[
            (&list_ptr, entry_block),
            (&tail, loop_keep),
            (&tail, loop_skip),
        ]);

        // Exit: reverse accumulated result
        self.builder().position_at_end(loop_exit);
        self.build_inline_reverse(acc_phi.as_basic_value().into_pointer_value(), current_fn)
    }

    /// Lower `lefts list` — extract Left values from [Either a b].
    fn lower_builtin_lefts(
        &mut self,
        list_expr: &Expr,
    ) -> CodegenResult<Option<BasicValueEnum<'ctx>>> {
        self.lower_builtin_filter_either(list_expr, 0, "lefts")
    }

    /// Lower `rights list` — extract Right values from [Either a b].
    fn lower_builtin_rights(
        &mut self,
        list_expr: &Expr,
    ) -> CodegenResult<Option<BasicValueEnum<'ctx>>> {
        self.lower_builtin_filter_either(list_expr, 1, "rights")
    }

    /// Shared implementation for lefts/rights — filter Either list by tag.
    fn lower_builtin_filter_either(
        &mut self,
        list_expr: &Expr,
        target_tag: u64,
        label: &str,
    ) -> CodegenResult<Option<BasicValueEnum<'ctx>>> {
        let list_val = self
            .lower_expr(list_expr)?
            .ok_or_else(|| CodegenError::Internal(format!("{}: list has no value", label)))?;

        let list_ptr = match list_val {
            BasicValueEnum::PointerValue(p) => p,
            _ => return Err(CodegenError::TypeError(format!("{} expects a list", label))),
        };

        let tm = self.type_mapper();
        let ptr_type = tm.ptr_type();
        let current_fn = self
            .builder()
            .get_insert_block()
            .and_then(|b| b.get_parent())
            .ok_or_else(|| CodegenError::Internal("no current function".to_string()))?;

        let entry_block = self
            .builder()
            .get_insert_block()
            .ok_or_else(|| CodegenError::Internal("no current block".to_string()))?;

        let loop_header = self
            .llvm_ctx
            .append_basic_block(current_fn, &format!("{}_header", label));
        let loop_body = self
            .llvm_ctx
            .append_basic_block(current_fn, &format!("{}_body", label));
        let loop_keep = self
            .llvm_ctx
            .append_basic_block(current_fn, &format!("{}_keep", label));
        let loop_skip = self
            .llvm_ctx
            .append_basic_block(current_fn, &format!("{}_skip", label));
        let loop_exit = self
            .llvm_ctx
            .append_basic_block(current_fn, &format!("{}_exit", label));

        let nil = self.build_nil()?;
        self.builder()
            .build_unconditional_branch(loop_header)
            .map_err(|e| CodegenError::Internal(format!("failed to build branch: {:?}", e)))?;

        // Loop header
        self.builder().position_at_end(loop_header);
        let acc_phi = self
            .builder()
            .build_phi(ptr_type, &format!("{}_acc", label))
            .map_err(|e| CodegenError::Internal(format!("failed to build phi: {:?}", e)))?;
        let list_phi = self
            .builder()
            .build_phi(ptr_type, &format!("{}_list", label))
            .map_err(|e| CodegenError::Internal(format!("failed to build phi: {:?}", e)))?;

        let tag = self.extract_adt_tag(list_phi.as_basic_value().into_pointer_value())?;
        let is_empty = self
            .builder()
            .build_int_compare(
                inkwell::IntPredicate::EQ,
                tag,
                tm.i64_type().const_zero(),
                "is_empty",
            )
            .map_err(|e| CodegenError::Internal(format!("failed to compare: {:?}", e)))?;
        self.builder()
            .build_conditional_branch(is_empty, loop_exit, loop_body)
            .map_err(|e| CodegenError::Internal(format!("failed to build branch: {:?}", e)))?;

        // Loop body: extract head (an Either) and tail
        self.builder().position_at_end(loop_body);
        let head = self.extract_adt_field(list_phi.as_basic_value().into_pointer_value(), 2, 0)?;
        let tail = self.extract_adt_field(list_phi.as_basic_value().into_pointer_value(), 2, 1)?;

        // Check if head matches target tag
        let either_tag = self.extract_adt_tag(head)?;
        let matches = self
            .builder()
            .build_int_compare(
                inkwell::IntPredicate::EQ,
                either_tag,
                tm.i64_type().const_int(target_tag, false),
                "tag_match",
            )
            .map_err(|e| CodegenError::Internal(format!("failed to compare: {:?}", e)))?;
        self.builder()
            .build_conditional_branch(matches, loop_keep, loop_skip)
            .map_err(|e| CodegenError::Internal(format!("failed to build branch: {:?}", e)))?;

        // Keep: extract payload, cons onto acc
        self.builder().position_at_end(loop_keep);
        let payload = self.extract_adt_field(head, 1, 0)?;
        let new_cons = self.build_cons(payload.into(), acc_phi.as_basic_value())?;
        self.builder()
            .build_unconditional_branch(loop_header)
            .map_err(|e| CodegenError::Internal(format!("failed to build branch: {:?}", e)))?;

        // Skip
        self.builder().position_at_end(loop_skip);
        self.builder()
            .build_unconditional_branch(loop_header)
            .map_err(|e| CodegenError::Internal(format!("failed to build branch: {:?}", e)))?;

        // Phi incoming
        acc_phi.add_incoming(&[
            (&nil, entry_block),
            (&new_cons, loop_keep),
            (&acc_phi.as_basic_value(), loop_skip),
        ]);
        list_phi.add_incoming(&[
            (&list_ptr, entry_block),
            (&tail, loop_keep),
            (&tail, loop_skip),
        ]);

        // Exit: reverse accumulated result
        self.builder().position_at_end(loop_exit);
        self.build_inline_reverse(acc_phi.as_basic_value().into_pointer_value(), current_fn)
    }

    /// Lower `mapMaybe f list` — apply f, keep Just results.
    fn lower_builtin_map_maybe(
        &mut self,
        f_expr: &Expr,
        list_expr: &Expr,
    ) -> CodegenResult<Option<BasicValueEnum<'ctx>>> {
        let f_val = self
            .lower_expr(f_expr)?
            .ok_or_else(|| CodegenError::Internal("mapMaybe: f has no value".to_string()))?;
        let f_ptr = match f_val {
            BasicValueEnum::PointerValue(p) => p,
            _ => {
                return Err(CodegenError::TypeError(
                    "mapMaybe: f must be a closure".to_string(),
                ))
            }
        };

        let list_val = self
            .lower_expr(list_expr)?
            .ok_or_else(|| CodegenError::Internal("mapMaybe: list has no value".to_string()))?;
        let list_ptr = match list_val {
            BasicValueEnum::PointerValue(p) => p,
            _ => {
                return Err(CodegenError::TypeError(
                    "mapMaybe expects a list".to_string(),
                ))
            }
        };

        let tm = self.type_mapper();
        let ptr_type = tm.ptr_type();
        let current_fn = self
            .builder()
            .get_insert_block()
            .and_then(|b| b.get_parent())
            .ok_or_else(|| CodegenError::Internal("no current function".to_string()))?;

        let entry_block = self
            .builder()
            .get_insert_block()
            .ok_or_else(|| CodegenError::Internal("no current block".to_string()))?;

        let loop_header = self.llvm_ctx.append_basic_block(current_fn, "mapM_header");
        let loop_body = self.llvm_ctx.append_basic_block(current_fn, "mapM_body");
        let loop_keep = self.llvm_ctx.append_basic_block(current_fn, "mapM_keep");
        let loop_skip = self.llvm_ctx.append_basic_block(current_fn, "mapM_skip");
        let loop_exit = self.llvm_ctx.append_basic_block(current_fn, "mapM_exit");

        let nil = self.build_nil()?;
        self.builder()
            .build_unconditional_branch(loop_header)
            .map_err(|e| CodegenError::Internal(format!("failed to build branch: {:?}", e)))?;

        // Loop header
        self.builder().position_at_end(loop_header);
        let acc_phi = self
            .builder()
            .build_phi(ptr_type, "mapM_acc")
            .map_err(|e| CodegenError::Internal(format!("failed to build phi: {:?}", e)))?;
        let list_phi = self
            .builder()
            .build_phi(ptr_type, "mapM_list")
            .map_err(|e| CodegenError::Internal(format!("failed to build phi: {:?}", e)))?;

        let tag = self.extract_adt_tag(list_phi.as_basic_value().into_pointer_value())?;
        let is_empty = self
            .builder()
            .build_int_compare(
                inkwell::IntPredicate::EQ,
                tag,
                tm.i64_type().const_zero(),
                "is_empty",
            )
            .map_err(|e| CodegenError::Internal(format!("failed to compare: {:?}", e)))?;
        self.builder()
            .build_conditional_branch(is_empty, loop_exit, loop_body)
            .map_err(|e| CodegenError::Internal(format!("failed to build branch: {:?}", e)))?;

        // Loop body: extract head and tail, call f(head)
        self.builder().position_at_end(loop_body);
        let head = self.extract_adt_field(list_phi.as_basic_value().into_pointer_value(), 2, 0)?;
        let tail = self.extract_adt_field(list_phi.as_basic_value().into_pointer_value(), 2, 1)?;

        // Call f(head) → Maybe result
        let fn_ptr = self.extract_closure_fn_ptr(f_ptr)?;
        let fn_type = ptr_type.fn_type(&[ptr_type.into(), ptr_type.into()], false);
        let maybe_result = self
            .builder()
            .build_indirect_call(fn_type, fn_ptr, &[f_ptr.into(), head.into()], "mapM_call")
            .map_err(|e| CodegenError::Internal(format!("failed to call f: {:?}", e)))?
            .try_as_basic_value()
            .basic()
            .ok_or_else(|| CodegenError::Internal("mapMaybe: f returned void".to_string()))?;
        let maybe_ptr = match maybe_result {
            BasicValueEnum::PointerValue(p) => p,
            _ => {
                return Err(CodegenError::TypeError(
                    "mapMaybe: f should return Maybe".to_string(),
                ))
            }
        };

        // Check if result is Just
        let maybe_tag = self.extract_adt_tag(maybe_ptr)?;
        let is_just = self
            .builder()
            .build_int_compare(
                inkwell::IntPredicate::NE,
                maybe_tag,
                tm.i64_type().const_zero(),
                "is_just",
            )
            .map_err(|e| CodegenError::Internal(format!("failed to compare: {:?}", e)))?;
        self.builder()
            .build_conditional_branch(is_just, loop_keep, loop_skip)
            .map_err(|e| CodegenError::Internal(format!("failed to build branch: {:?}", e)))?;

        // Keep: extract Just payload, cons onto acc
        self.builder().position_at_end(loop_keep);
        let payload = self.extract_adt_field(maybe_ptr, 1, 0)?;
        let new_cons = self.build_cons(payload.into(), acc_phi.as_basic_value())?;
        self.builder()
            .build_unconditional_branch(loop_header)
            .map_err(|e| CodegenError::Internal(format!("failed to build branch: {:?}", e)))?;

        // Skip
        self.builder().position_at_end(loop_skip);
        self.builder()
            .build_unconditional_branch(loop_header)
            .map_err(|e| CodegenError::Internal(format!("failed to build branch: {:?}", e)))?;

        // Phi incoming
        acc_phi.add_incoming(&[
            (&nil, entry_block),
            (&new_cons, loop_keep),
            (&acc_phi.as_basic_value(), loop_skip),
        ]);
        list_phi.add_incoming(&[
            (&list_ptr, entry_block),
            (&tail, loop_keep),
            (&tail, loop_skip),
        ]);

        // Exit: reverse accumulated result
        self.builder().position_at_end(loop_exit);
        self.build_inline_reverse(acc_phi.as_basic_value().into_pointer_value(), current_fn)
    }

    /// Lower `partitionEithers list` — split [Either a b] into ([a], [b]).
    fn lower_builtin_partition_eithers(
        &mut self,
        list_expr: &Expr,
    ) -> CodegenResult<Option<BasicValueEnum<'ctx>>> {
        let list_val = self.lower_expr(list_expr)?.ok_or_else(|| {
            CodegenError::Internal("partitionEithers: list has no value".to_string())
        })?;

        let list_ptr = match list_val {
            BasicValueEnum::PointerValue(p) => p,
            _ => {
                return Err(CodegenError::TypeError(
                    "partitionEithers expects a list".to_string(),
                ))
            }
        };

        let tm = self.type_mapper();
        let ptr_type = tm.ptr_type();
        let current_fn = self
            .builder()
            .get_insert_block()
            .and_then(|b| b.get_parent())
            .ok_or_else(|| CodegenError::Internal("no current function".to_string()))?;

        let entry_block = self
            .builder()
            .get_insert_block()
            .ok_or_else(|| CodegenError::Internal("no current block".to_string()))?;

        let loop_header = self.llvm_ctx.append_basic_block(current_fn, "pe_header");
        let loop_body = self.llvm_ctx.append_basic_block(current_fn, "pe_body");
        let loop_left = self.llvm_ctx.append_basic_block(current_fn, "pe_left");
        let loop_right = self.llvm_ctx.append_basic_block(current_fn, "pe_right");
        let loop_exit = self.llvm_ctx.append_basic_block(current_fn, "pe_exit");

        let nil = self.build_nil()?;
        let nil2 = self.build_nil()?;
        self.builder()
            .build_unconditional_branch(loop_header)
            .map_err(|e| CodegenError::Internal(format!("failed to build branch: {:?}", e)))?;

        // Loop header: two accumulators
        self.builder().position_at_end(loop_header);
        let lefts_phi = self
            .builder()
            .build_phi(ptr_type, "pe_lefts")
            .map_err(|e| CodegenError::Internal(format!("failed to build phi: {:?}", e)))?;
        let rights_phi = self
            .builder()
            .build_phi(ptr_type, "pe_rights")
            .map_err(|e| CodegenError::Internal(format!("failed to build phi: {:?}", e)))?;
        let list_phi = self
            .builder()
            .build_phi(ptr_type, "pe_list")
            .map_err(|e| CodegenError::Internal(format!("failed to build phi: {:?}", e)))?;

        let tag = self.extract_adt_tag(list_phi.as_basic_value().into_pointer_value())?;
        let is_empty = self
            .builder()
            .build_int_compare(
                inkwell::IntPredicate::EQ,
                tag,
                tm.i64_type().const_zero(),
                "is_empty",
            )
            .map_err(|e| CodegenError::Internal(format!("failed to compare: {:?}", e)))?;
        self.builder()
            .build_conditional_branch(is_empty, loop_exit, loop_body)
            .map_err(|e| CodegenError::Internal(format!("failed to build branch: {:?}", e)))?;

        // Loop body: extract head (Either) and tail
        self.builder().position_at_end(loop_body);
        let head = self.extract_adt_field(list_phi.as_basic_value().into_pointer_value(), 2, 0)?;
        let tail = self.extract_adt_field(list_phi.as_basic_value().into_pointer_value(), 2, 1)?;

        let either_tag = self.extract_adt_tag(head)?;
        let is_left = self
            .builder()
            .build_int_compare(
                inkwell::IntPredicate::EQ,
                either_tag,
                tm.i64_type().const_zero(),
                "is_left",
            )
            .map_err(|e| CodegenError::Internal(format!("failed to compare: {:?}", e)))?;
        self.builder()
            .build_conditional_branch(is_left, loop_left, loop_right)
            .map_err(|e| CodegenError::Internal(format!("failed to build branch: {:?}", e)))?;

        // Left: cons payload onto lefts_acc
        self.builder().position_at_end(loop_left);
        let left_payload = self.extract_adt_field(head, 1, 0)?;
        let new_lefts = self.build_cons(left_payload.into(), lefts_phi.as_basic_value())?;
        self.builder()
            .build_unconditional_branch(loop_header)
            .map_err(|e| CodegenError::Internal(format!("failed to build branch: {:?}", e)))?;

        // Right: cons payload onto rights_acc
        self.builder().position_at_end(loop_right);
        let right_payload = self.extract_adt_field(head, 1, 0)?;
        let new_rights = self.build_cons(right_payload.into(), rights_phi.as_basic_value())?;
        self.builder()
            .build_unconditional_branch(loop_header)
            .map_err(|e| CodegenError::Internal(format!("failed to build branch: {:?}", e)))?;

        // Phi incoming
        lefts_phi.add_incoming(&[
            (&nil, entry_block),
            (&new_lefts, loop_left),
            (&lefts_phi.as_basic_value(), loop_right),
        ]);
        rights_phi.add_incoming(&[
            (&nil2, entry_block),
            (&rights_phi.as_basic_value(), loop_left),
            (&new_rights, loop_right),
        ]);
        list_phi.add_incoming(&[
            (&list_ptr, entry_block),
            (&tail, loop_left),
            (&tail, loop_right),
        ]);

        // Exit: reverse both lists, build tuple
        self.builder().position_at_end(loop_exit);
        let rev_lefts = self
            .build_inline_reverse(lefts_phi.as_basic_value().into_pointer_value(), current_fn)?
            .ok_or_else(|| CodegenError::Internal("reverse returned None".to_string()))?;
        let rev_lefts_ptr = match rev_lefts {
            BasicValueEnum::PointerValue(p) => p,
            _ => {
                return Err(CodegenError::Internal(
                    "reverse should return pointer".to_string(),
                ))
            }
        };

        let rev_rights = self
            .build_inline_reverse(rights_phi.as_basic_value().into_pointer_value(), current_fn)?
            .ok_or_else(|| CodegenError::Internal("reverse returned None".to_string()))?;
        let rev_rights_ptr = match rev_rights {
            BasicValueEnum::PointerValue(p) => p,
            _ => {
                return Err(CodegenError::Internal(
                    "reverse should return pointer".to_string(),
                ))
            }
        };

        // Build tuple: alloc_adt(tag=0, arity=2) with two list fields
        let tuple = self.alloc_adt(0, 2)?;
        self.store_adt_field(tuple, 2, 0, rev_lefts_ptr.into())?;
        self.store_adt_field(tuple, 2, 1, rev_rights_ptr.into())?;

        Ok(Some(tuple.into()))
    }

    /// Build an inline reverse loop. Returns the reversed list.
    /// Assumes builder is positioned at the block that should start the reverse.
    fn build_inline_reverse(
        &mut self,
        list_ptr: PointerValue<'ctx>,
        current_fn: inkwell::values::FunctionValue<'ctx>,
    ) -> CodegenResult<Option<BasicValueEnum<'ctx>>> {
        let tm = self.type_mapper();
        let ptr_type = tm.ptr_type();

        let pre_rev_block = self
            .builder()
            .get_insert_block()
            .ok_or_else(|| CodegenError::Internal("no current block".to_string()))?;

        let rev_header = self.llvm_ctx.append_basic_block(current_fn, "rev_header");
        let rev_body = self.llvm_ctx.append_basic_block(current_fn, "rev_body");
        let rev_exit = self.llvm_ctx.append_basic_block(current_fn, "rev_exit");

        let nil = self.build_nil()?;
        self.builder()
            .build_unconditional_branch(rev_header)
            .map_err(|e| CodegenError::Internal(format!("failed to build branch: {:?}", e)))?;

        // Reverse header
        self.builder().position_at_end(rev_header);
        let rev_acc = self
            .builder()
            .build_phi(ptr_type, "rev_acc")
            .map_err(|e| CodegenError::Internal(format!("failed to build phi: {:?}", e)))?;
        let rev_list = self
            .builder()
            .build_phi(ptr_type, "rev_list")
            .map_err(|e| CodegenError::Internal(format!("failed to build phi: {:?}", e)))?;

        let rev_tag = self.extract_adt_tag(rev_list.as_basic_value().into_pointer_value())?;
        let rev_is_empty = self
            .builder()
            .build_int_compare(
                inkwell::IntPredicate::EQ,
                rev_tag,
                tm.i64_type().const_zero(),
                "rev_empty",
            )
            .map_err(|e| CodegenError::Internal(format!("failed to compare: {:?}", e)))?;
        self.builder()
            .build_conditional_branch(rev_is_empty, rev_exit, rev_body)
            .map_err(|e| CodegenError::Internal(format!("failed to build branch: {:?}", e)))?;

        // Reverse body: cons head onto acc
        self.builder().position_at_end(rev_body);
        let rev_head =
            self.extract_adt_field(rev_list.as_basic_value().into_pointer_value(), 2, 0)?;
        let rev_tail =
            self.extract_adt_field(rev_list.as_basic_value().into_pointer_value(), 2, 1)?;
        let rev_new_cons = self.build_cons(rev_head.into(), rev_acc.as_basic_value())?;
        self.builder()
            .build_unconditional_branch(rev_header)
            .map_err(|e| CodegenError::Internal(format!("failed to build branch: {:?}", e)))?;

        // Phi incoming
        rev_acc.add_incoming(&[(&nil, pre_rev_block), (&rev_new_cons, rev_body)]);
        rev_list.add_incoming(&[(&list_ptr, pre_rev_block), (&rev_tail, rev_body)]);

        self.builder().position_at_end(rev_exit);
        Ok(Some(rev_acc.as_basic_value()))
    }

    /// Lower `error` - throw an ErrorCall exception (catchable).
    ///
    /// Wraps the error message in a SomeException with tag 2 (ErrorCall)
    /// and calls bhc_throw, making `error "msg"` catchable by `catch`.
    fn lower_builtin_error(
        &mut self,
        msg_expr: &Expr,
    ) -> CodegenResult<Option<BasicValueEnum<'ctx>>> {
        let msg_val = self
            .lower_expr(msg_expr)?
            .ok_or_else(|| CodegenError::Internal("error: message has no value".to_string()))?;

        let msg_ptr = match msg_val {
            BasicValueEnum::PointerValue(p) => p,
            _ => {
                // If it's not a pointer, use a default error message
                self.module
                    .add_global_string("error_default", "error called")
            }
        };

        // Convert Haskell char list to C-string for the payload
        let cstr_ptr = self.char_list_to_cstring(msg_ptr)?;

        // Wrap in SomeException with tag 2 (ErrorCall)
        let make_exc_fn = self.functions.get(&VarId::new(1000700)).ok_or_else(|| {
            CodegenError::Internal("bhc_make_some_exception not declared".to_string())
        })?;

        let i64_type = self.llvm_context().i64_type();
        let tag_val = i64_type.const_int(2, false); // EXC_TAG_ERROR_CALL

        let some_exc = self
            .builder()
            .build_call(
                *make_exc_fn,
                &[tag_val.into(), cstr_ptr.into()],
                "error_exc",
            )
            .map_err(|e| CodegenError::Internal(format!("failed to make ErrorCall: {:?}", e)))?
            .try_as_basic_value()
            .basic()
            .ok_or_else(|| {
                CodegenError::Internal("make_some_exception: returned void".to_string())
            })?;

        // Throw the exception (returns sentinel null)
        let throw_fn = self
            .functions
            .get(&VarId::new(1000080))
            .ok_or_else(|| CodegenError::Internal("bhc_throw not declared".to_string()))?;

        let result = self
            .builder()
            .build_call(*throw_fn, &[some_exc.into()], "error_sentinel")
            .map_err(|e| CodegenError::Internal(format!("failed to call throw: {:?}", e)))?
            .try_as_basic_value()
            .basic()
            .ok_or_else(|| CodegenError::Internal("throw: returned void".to_string()))?;

        Ok(Some(result))
    }

    /// Lower `undefined` - always errors.
    fn lower_builtin_undefined(&mut self) -> CodegenResult<Option<BasicValueEnum<'ctx>>> {
        let error_msg = self
            .module
            .add_global_string("undefined_error", "undefined");
        let error_fn = self
            .functions
            .get(&VarId::new(1000006))
            .ok_or_else(|| CodegenError::Internal("bhc_error not declared".to_string()))?;

        self.builder()
            .build_call(*error_fn, &[error_msg.into()], "")
            .map_err(|e| CodegenError::Internal(format!("failed to call error: {:?}", e)))?;

        self.builder()
            .build_unreachable()
            .map_err(|e| CodegenError::Internal(format!("failed to build unreachable: {:?}", e)))?;

        Ok(Some(self.type_mapper().ptr_type().const_null().into()))
    }

    /// Lower `throw` / `throwIO` - wrap in SomeException, call bhc_throw.
    fn lower_builtin_throw(
        &mut self,
        msg_expr: &Expr,
    ) -> CodegenResult<Option<BasicValueEnum<'ctx>>> {
        let msg_val = self
            .lower_expr(msg_expr)?
            .ok_or_else(|| CodegenError::Internal("throw: message has no value".to_string()))?;

        let msg_ptr = self.value_to_ptr(msg_val)?;

        // Wrap in SomeException with tag 0 (generic)
        let make_exc_fn = self.functions.get(&VarId::new(1000700)).ok_or_else(|| {
            CodegenError::Internal("bhc_make_some_exception not declared".to_string())
        })?;

        let i64_type = self.llvm_context().i64_type();
        let tag_val = i64_type.const_int(0, false);

        let some_exc = self
            .builder()
            .build_call(*make_exc_fn, &[tag_val.into(), msg_ptr.into()], "some_exc")
            .map_err(|e| CodegenError::Internal(format!("failed to make SomeException: {:?}", e)))?
            .try_as_basic_value()
            .basic()
            .ok_or_else(|| {
                CodegenError::Internal("make_some_exception: returned void".to_string())
            })?;

        let throw_fn = self
            .functions
            .get(&VarId::new(1000080))
            .ok_or_else(|| CodegenError::Internal("bhc_throw not declared".to_string()))?;

        let result = self
            .builder()
            .build_call(*throw_fn, &[some_exc.into()], "throw_sentinel")
            .map_err(|e| CodegenError::Internal(format!("failed to call throw: {:?}", e)))?
            .try_as_basic_value()
            .basic()
            .ok_or_else(|| CodegenError::Internal("throw: returned void".to_string()))?;

        Ok(Some(result))
    }

    /// Lower `catch` - call bhc_catch with action and handler closures.
    ///
    /// catch :: IO a -> (SomeException -> IO a) -> IO a
    ///
    /// Extracts fn_ptr and closure_ptr from both the action and handler,
    /// then calls bhc_catch(action_fn, action_env, handler_fn, handler_env).
    /// Wrap an IO action expression as a thunk (closure) for deferred execution.
    ///
    /// This creates a new function that evaluates the expression when called,
    /// and wraps it in a closure. Used for `catch`, `bracket`, and similar
    /// functions that need to defer IO actions.
    fn wrap_io_as_thunk(&mut self, expr: &Expr) -> CodegenResult<PointerValue<'ctx>> {
        // If the expression is already a lambda/closure, just lower it normally
        if matches!(expr, Expr::Lam(_, _, _)) {
            let val = self
                .lower_expr(expr)?
                .ok_or_else(|| CodegenError::Internal("wrap_io_as_thunk: no value".to_string()))?;
            return self.value_to_ptr(val);
        }

        // Compute free variables
        let free = self.free_vars(expr);

        // Collect captured variable values
        let mut captured: Vec<(VarId, BasicValueEnum<'ctx>)> = Vec::new();
        for var_id in &free {
            if let Some(val) = self.env.get(var_id) {
                captured.push((*var_id, *val));
            }
        }

        // Save current state
        let current_block = self.builder().get_insert_block();
        let fn_name = self.next_closure_name();

        let tm = self.type_mapper();
        let ptr_type = tm.ptr_type();

        // Create function: fn(env_ptr) -> result_ptr
        let fn_type = ptr_type.fn_type(&[ptr_type.into()], false);
        let lifted_fn = self.module.add_function(&fn_name, fn_type);

        let entry = self.llvm_context().append_basic_block(lifted_fn, "entry");
        self.builder().position_at_end(entry);

        // Save and replace environment
        let old_env = std::mem::take(&mut self.env);

        // Restore captured variables from closure env
        if !captured.is_empty() {
            let closure_ptr = lifted_fn
                .get_first_param()
                .ok_or_else(|| CodegenError::Internal("missing closure param".to_string()))?
                .into_pointer_value();

            for (i, (var_id, _)) in captured.iter().enumerate() {
                let elem_ptr =
                    self.extract_closure_env_elem(closure_ptr, captured.len() as u32, i as u32)?;
                self.env.insert(*var_id, elem_ptr.into());
            }
        }

        // Lower the expression inside the thunk
        let result = self.lower_expr(expr)?;

        // Return result
        if let Some(val) = result {
            let ret_ptr = self.value_to_ptr(val)?;
            self.builder()
                .build_return(Some(&ret_ptr))
                .map_err(|e| CodegenError::Internal(format!("thunk return failed: {:?}", e)))?;
        } else {
            let null = ptr_type.const_null();
            self.builder().build_return(Some(&null)).map_err(|e| {
                CodegenError::Internal(format!("thunk null return failed: {:?}", e))
            })?;
        }

        // Restore environment and insertion point
        self.env = old_env;
        if let Some(block) = current_block {
            self.builder().position_at_end(block);
        }

        // Create closure
        let fn_ptr = lifted_fn.as_global_value().as_pointer_value();
        self.alloc_closure(fn_ptr, &captured)
    }

    fn lower_builtin_catch(
        &mut self,
        action_expr: &Expr,
        handler_expr: &Expr,
    ) -> CodegenResult<Option<BasicValueEnum<'ctx>>> {
        // Wrap the action as a thunk so it's deferred (not eagerly evaluated)
        let action_closure = self.wrap_io_as_thunk(action_expr)?;
        let handler_val = self
            .lower_expr(handler_expr)?
            .ok_or_else(|| CodegenError::Internal("catch: handler has no value".to_string()))?;
        let handler_closure = self.value_to_ptr(handler_val)?;

        let action_fn = self.extract_closure_fn_ptr(action_closure)?;
        let handler_fn = self.extract_closure_fn_ptr(handler_closure)?;

        // Infer exception type tag from handler expression
        let type_tag = self.infer_exception_tag_from_handler(handler_expr);
        let i64_type = self.llvm_context().i64_type();
        let tag_val = i64_type.const_int(type_tag, false);

        // Use bhc_catch_typed for type-filtered catch
        let catch_rts = self
            .functions
            .get(&VarId::new(1000703))
            .ok_or_else(|| CodegenError::Internal("bhc_catch_typed not declared".to_string()))?;

        let result = self
            .builder()
            .build_call(
                *catch_rts,
                &[
                    action_fn.into(),
                    action_closure.into(),
                    handler_fn.into(),
                    handler_closure.into(),
                    tag_val.into(),
                ],
                "catch_result",
            )
            .map_err(|e| CodegenError::Internal(format!("catch call failed: {:?}", e)))?
            .try_as_basic_value()
            .basic()
            .ok_or_else(|| CodegenError::Internal("catch: returned void".to_string()))?;

        Ok(Some(result))
    }

    /// Infer the exception type tag from the handler expression's parameter type.
    ///
    /// Returns 0 (catch-all) for most cases, 1 for IOException, 2 for ErrorCall.
    fn infer_exception_tag_from_handler(&self, handler_expr: &Expr) -> u64 {
        // Try to extract the handler's parameter type from its lambda variable
        match handler_expr {
            Expr::Lam(var, _, _) => {
                let ty_name = format!("{:?}", var.ty);
                if ty_name.contains("IOException") {
                    1 // EXC_TAG_IO_EXCEPTION
                } else if ty_name.contains("ErrorCall") {
                    2 // EXC_TAG_ERROR_CALL
                } else {
                    0 // EXC_TAG_SOME_EXCEPTION (catch-all)
                }
            }
            _ => 0, // Default: catch-all
        }
    }

    /// Lower `toException` - wrap a value in SomeException (tag 0).
    fn lower_builtin_to_exception(
        &mut self,
        val_expr: &Expr,
    ) -> CodegenResult<Option<BasicValueEnum<'ctx>>> {
        let val = self
            .lower_expr(val_expr)?
            .ok_or_else(|| CodegenError::Internal("toException: value has no value".to_string()))?;
        let val_ptr = self.value_to_ptr(val)?;

        let make_exc_fn = self.functions.get(&VarId::new(1000700)).ok_or_else(|| {
            CodegenError::Internal("bhc_make_some_exception not declared".to_string())
        })?;

        let i64_type = self.llvm_context().i64_type();
        let tag_val = i64_type.const_int(0, false);

        let result = self
            .builder()
            .build_call(*make_exc_fn, &[tag_val.into(), val_ptr.into()], "to_exc")
            .map_err(|e| CodegenError::Internal(format!("toException failed: {:?}", e)))?
            .try_as_basic_value()
            .basic()
            .ok_or_else(|| CodegenError::Internal("toException: returned void".to_string()))?;

        Ok(Some(result))
    }

    /// Lower `fromException` - extract payload from SomeException, wrap in Just.
    fn lower_builtin_from_exception(
        &mut self,
        exc_expr: &Expr,
    ) -> CodegenResult<Option<BasicValueEnum<'ctx>>> {
        let exc_val = self
            .lower_expr(exc_expr)?
            .ok_or_else(|| CodegenError::Internal("fromException: no value".to_string()))?;
        let exc_ptr = self.value_to_ptr(exc_val)?;

        let get_payload_fn = self.functions.get(&VarId::new(1000702)).ok_or_else(|| {
            CodegenError::Internal("bhc_exc_get_payload not declared".to_string())
        })?;

        let payload = self
            .builder()
            .build_call(*get_payload_fn, &[exc_ptr.into()], "exc_payload")
            .map_err(|e| CodegenError::Internal(format!("fromException failed: {:?}", e)))?
            .try_as_basic_value()
            .basic()
            .ok_or_else(|| CodegenError::Internal("fromException: returned void".to_string()))?;

        // Wrap in Just (tag=1, arity=1)
        let just = self.alloc_adt(1, 1)?;
        self.store_adt_field(just, 1, 0, payload)?;
        Ok(Some(just.into()))
    }

    /// Lower `displayException` - convert exception to string.
    fn lower_builtin_display_exception(
        &mut self,
        exc_expr: &Expr,
    ) -> CodegenResult<Option<BasicValueEnum<'ctx>>> {
        let exc_val = self
            .lower_expr(exc_expr)?
            .ok_or_else(|| CodegenError::Internal("displayException: no value".to_string()))?;
        let exc_ptr = self.value_to_ptr(exc_val)?;

        let show_exc_fn = self
            .functions
            .get(&VarId::new(1000704))
            .ok_or_else(|| CodegenError::Internal("bhc_show_exception not declared".to_string()))?;

        let cstr = self
            .builder()
            .build_call(*show_exc_fn, &[exc_ptr.into()], "exc_str")
            .map_err(|e| CodegenError::Internal(format!("displayException failed: {:?}", e)))?
            .try_as_basic_value()
            .basic()
            .ok_or_else(|| CodegenError::Internal("displayException: returned void".to_string()))?;

        // Convert C-string to Haskell char list
        let result = self.cstring_to_char_list(cstr.into_pointer_value())?;
        Ok(Some(result.into()))
    }

    /// Lower `userError` - create an IOException from a String.
    fn lower_builtin_user_error(
        &mut self,
        msg_expr: &Expr,
    ) -> CodegenResult<Option<BasicValueEnum<'ctx>>> {
        let msg_val = self
            .lower_expr(msg_expr)?
            .ok_or_else(|| CodegenError::Internal("userError: no value".to_string()))?;
        let msg_ptr = self.value_to_ptr(msg_val)?;

        // Convert char list to C-string
        let cstr_ptr = self.char_list_to_cstring(msg_ptr)?;

        // Create IOException (tag 1)
        let make_exc_fn = self.functions.get(&VarId::new(1000700)).ok_or_else(|| {
            CodegenError::Internal("bhc_make_some_exception not declared".to_string())
        })?;

        let i64_type = self.llvm_context().i64_type();
        let tag_val = i64_type.const_int(1, false); // EXC_TAG_IO_EXCEPTION

        let result = self
            .builder()
            .build_call(
                *make_exc_fn,
                &[tag_val.into(), cstr_ptr.into()],
                "user_error",
            )
            .map_err(|e| CodegenError::Internal(format!("userError failed: {:?}", e)))?
            .try_as_basic_value()
            .basic()
            .ok_or_else(|| CodegenError::Internal("userError: returned void".to_string()))?;

        Ok(Some(result))
    }

    /// Lower `try` - execute action wrapped in catch.
    ///
    /// try :: IO a -> IO (Either SomeException a)
    ///
    /// Uses bhc_catch with a handler that returns the exception as a Left value.
    /// Simplified: wraps in catch; on success returns the result directly.
    /// Full Either construction would require ADT allocation at codegen level.
    fn lower_builtin_try(
        &mut self,
        action_expr: &Expr,
    ) -> CodegenResult<Option<BasicValueEnum<'ctx>>> {
        // For now, execute the action directly. A proper implementation would
        // wrap in bhc_catch and construct Either values, but that requires
        // ADT allocation infrastructure that's not yet available here.
        let result = self.lower_expr(action_expr)?;
        Ok(result)
    }

    /// Lower `bracket` - acquire/use/release with exception safety.
    ///
    /// bracket :: IO a -> (a -> IO b) -> (a -> IO c) -> IO c
    ///
    /// Calls bhc_bracket(acquire_fn, acquire_env, release_fn, release_env, use_fn, use_env).
    /// The RTS ensures release always runs even if use throws.
    fn lower_builtin_bracket(
        &mut self,
        acquire_expr: &Expr,
        release_expr: &Expr,
        use_expr: &Expr,
    ) -> CodegenResult<Option<BasicValueEnum<'ctx>>> {
        // Wrap acquire as a thunk (deferred IO action)
        let acquire_closure = self.wrap_io_as_thunk(acquire_expr)?;
        // release and use are functions (lambdas), lower normally
        let release_val = self
            .lower_expr(release_expr)?
            .ok_or_else(|| CodegenError::Internal("bracket: release has no value".to_string()))?;
        let use_val = self
            .lower_expr(use_expr)?
            .ok_or_else(|| CodegenError::Internal("bracket: use has no value".to_string()))?;

        let release_closure = self.value_to_ptr(release_val)?;
        let use_closure = self.value_to_ptr(use_val)?;

        let acquire_fn = self.extract_closure_fn_ptr(acquire_closure)?;
        let release_fn = self.extract_closure_fn_ptr(release_closure)?;
        let use_fn = self.extract_closure_fn_ptr(use_closure)?;

        let bracket_rts = self
            .functions
            .get(&VarId::new(1000825))
            .ok_or_else(|| CodegenError::Internal("bhc_bracket not declared".to_string()))?;

        let result = self
            .builder()
            .build_call(
                *bracket_rts,
                &[
                    acquire_fn.into(),
                    acquire_closure.into(),
                    release_fn.into(),
                    release_closure.into(),
                    use_fn.into(),
                    use_closure.into(),
                ],
                "bracket_result",
            )
            .map_err(|e| CodegenError::Internal(format!("bracket call failed: {:?}", e)))?
            .try_as_basic_value()
            .basic()
            .ok_or_else(|| CodegenError::Internal("bracket: returned void".to_string()))?;

        Ok(Some(result))
    }

    /// Lower `finally` - execute action with guaranteed cleanup.
    ///
    /// finally :: IO a -> IO b -> IO a
    ///
    /// Calls bhc_finally(action_fn, action_env, cleanup_fn, cleanup_env).
    /// The RTS ensures cleanup always runs, re-throwing any exception afterward.
    fn lower_builtin_finally(
        &mut self,
        action_expr: &Expr,
        cleanup_expr: &Expr,
    ) -> CodegenResult<Option<BasicValueEnum<'ctx>>> {
        // Wrap action and cleanup as thunks so they're deferred (not eagerly evaluated)
        let action_closure = self.wrap_io_as_thunk(action_expr)?;
        let cleanup_closure = self.wrap_io_as_thunk(cleanup_expr)?;

        let action_fn = self.extract_closure_fn_ptr(action_closure)?;
        let cleanup_fn = self.extract_closure_fn_ptr(cleanup_closure)?;

        let finally_rts = self
            .functions
            .get(&VarId::new(1000088))
            .ok_or_else(|| CodegenError::Internal("bhc_finally not declared".to_string()))?;

        let result = self
            .builder()
            .build_call(
                *finally_rts,
                &[
                    action_fn.into(),
                    action_closure.into(),
                    cleanup_fn.into(),
                    cleanup_closure.into(),
                ],
                "finally_result",
            )
            .map_err(|e| CodegenError::Internal(format!("finally call failed: {:?}", e)))?
            .try_as_basic_value()
            .basic()
            .ok_or_else(|| CodegenError::Internal("finally: returned void".to_string()))?;

        Ok(Some(result))
    }

    /// Lower `onException` - execute action, run handler only on exception.
    ///
    /// onException :: IO a -> IO b -> IO a
    ///
    /// Calls bhc_on_exception(action_fn, action_env, handler_fn, handler_env).
    /// The RTS runs the handler only if the action throws, then re-throws.
    fn lower_builtin_on_exception(
        &mut self,
        action_expr: &Expr,
        handler_expr: &Expr,
    ) -> CodegenResult<Option<BasicValueEnum<'ctx>>> {
        // Wrap action and handler as thunks so they're deferred (not eagerly evaluated)
        let action_closure = self.wrap_io_as_thunk(action_expr)?;
        let handler_closure = self.wrap_io_as_thunk(handler_expr)?;

        let action_fn = self.extract_closure_fn_ptr(action_closure)?;
        let handler_fn = self.extract_closure_fn_ptr(handler_closure)?;

        let on_exc_rts = self
            .functions
            .get(&VarId::new(1000089))
            .ok_or_else(|| CodegenError::Internal("bhc_on_exception not declared".to_string()))?;

        let result = self
            .builder()
            .build_call(
                *on_exc_rts,
                &[
                    action_fn.into(),
                    action_closure.into(),
                    handler_fn.into(),
                    handler_closure.into(),
                ],
                "on_exception_result",
            )
            .map_err(|e| CodegenError::Internal(format!("onException call failed: {:?}", e)))?
            .try_as_basic_value()
            .basic()
            .ok_or_else(|| CodegenError::Internal("onException: returned void".to_string()))?;

        Ok(Some(result))
    }

    /// Lower `mask_` / `uninterruptibleMask_` — simple masking without restore.
    ///
    /// mask_ :: IO a -> IO a
    /// Wraps the action as a thunk and calls bhc_mask / bhc_uninterruptible_mask.
    fn lower_builtin_mask_simple(
        &mut self,
        action_expr: &Expr,
        uninterruptible: bool,
    ) -> CodegenResult<Option<BasicValueEnum<'ctx>>> {
        let action_closure = self.wrap_io_as_thunk(action_expr)?;
        let action_fn = self.extract_closure_fn_ptr(action_closure)?;

        let rts_id = if uninterruptible {
            VarId::new(1000086) // bhc_uninterruptible_mask
        } else {
            VarId::new(1000083) // bhc_mask
        };
        let rts_fn = self.functions.get(&rts_id).ok_or_else(|| {
            CodegenError::Internal("bhc_mask/bhc_uninterruptible_mask not declared".to_string())
        })?;

        let result = self
            .builder()
            .build_call(
                *rts_fn,
                &[action_fn.into(), action_closure.into()],
                "mask_result",
            )
            .map_err(|e| CodegenError::Internal(format!("mask call failed: {:?}", e)))?
            .try_as_basic_value()
            .basic()
            .ok_or_else(|| CodegenError::Internal("mask: returned void".to_string()))?;

        Ok(Some(result))
    }

    /// Lower `mask` — masking with restore function.
    ///
    /// mask :: ((IO a -> IO a) -> IO b) -> IO b
    /// The action receives a restore closure created by the RTS.
    fn lower_builtin_mask(
        &mut self,
        action_expr: &Expr,
    ) -> CodegenResult<Option<BasicValueEnum<'ctx>>> {
        let action_val = self
            .lower_expr(action_expr)?
            .ok_or_else(|| CodegenError::Internal("mask: action has no value".to_string()))?;
        let action_closure = self.value_to_ptr(action_val)?;
        let action_fn = self.extract_closure_fn_ptr(action_closure)?;

        let rts_fn = self
            .functions
            .get(&VarId::new(1000085)) // bhc_mask_with_restore
            .ok_or_else(|| {
                CodegenError::Internal("bhc_mask_with_restore not declared".to_string())
            })?;

        let result = self
            .builder()
            .build_call(
                *rts_fn,
                &[action_fn.into(), action_closure.into()],
                "mask_restore_result",
            )
            .map_err(|e| CodegenError::Internal(format!("mask_with_restore call failed: {:?}", e)))?
            .try_as_basic_value()
            .basic()
            .ok_or_else(|| {
                CodegenError::Internal("mask_with_restore: returned void".to_string())
            })?;

        Ok(Some(result))
    }

    /// Lower `uninterruptibleMask` — uninterruptible masking with restore function.
    fn lower_builtin_uninterruptible_mask(
        &mut self,
        action_expr: &Expr,
    ) -> CodegenResult<Option<BasicValueEnum<'ctx>>> {
        let action_val = self.lower_expr(action_expr)?.ok_or_else(|| {
            CodegenError::Internal("uninterruptibleMask: action has no value".to_string())
        })?;
        let action_closure = self.value_to_ptr(action_val)?;
        let action_fn = self.extract_closure_fn_ptr(action_closure)?;

        let rts_fn = self
            .functions
            .get(&VarId::new(1000087)) // bhc_uninterruptible_mask_with_restore
            .ok_or_else(|| {
                CodegenError::Internal(
                    "bhc_uninterruptible_mask_with_restore not declared".to_string(),
                )
            })?;

        let result = self
            .builder()
            .build_call(
                *rts_fn,
                &[action_fn.into(), action_closure.into()],
                "uninterruptible_mask_result",
            )
            .map_err(|e| {
                CodegenError::Internal(format!("uninterruptible_mask_with_restore failed: {:?}", e))
            })?
            .try_as_basic_value()
            .basic()
            .ok_or_else(|| {
                CodegenError::Internal(
                    "uninterruptible_mask_with_restore: returned void".to_string(),
                )
            })?;

        Ok(Some(result))
    }

    /// Lower `getMaskingState` — returns a MaskingState ADT.
    ///
    /// Calls bhc_get_masking_state() to get i64 (0/1/2), then allocates
    /// a MaskingState ADT with that tag.
    fn lower_builtin_get_masking_state(&mut self) -> CodegenResult<Option<BasicValueEnum<'ctx>>> {
        let rts_fn = self
            .functions
            .get(&VarId::new(1000824)) // bhc_get_masking_state
            .ok_or_else(|| {
                CodegenError::Internal("bhc_get_masking_state not declared".to_string())
            })?;

        let state_val = self
            .builder()
            .build_call(*rts_fn, &[], "masking_state")
            .map_err(|e| CodegenError::Internal(format!("get_masking_state failed: {:?}", e)))?
            .try_as_basic_value()
            .basic()
            .ok_or_else(|| {
                CodegenError::Internal("get_masking_state: returned void".to_string())
            })?;

        // Allocate a MaskingState ADT with tag = state_val, 0 fields
        let tm = self.type_mapper();
        let adt_ty = self.adt_type(0);
        let size = 8u64; // Just the tag, no fields
        let alloc_fn = self
            .functions
            .get(&VarId::new(1000005))
            .ok_or_else(|| CodegenError::Internal("bhc_alloc not declared".to_string()))?;
        let size_val = tm.i64_type().const_int(size, false);
        let raw_ptr = self
            .builder()
            .build_call(*alloc_fn, &[size_val.into()], "masking_state_alloc")
            .map_err(|e| CodegenError::Internal(format!("alloc failed: {:?}", e)))?
            .try_as_basic_value()
            .basic()
            .ok_or_else(|| CodegenError::Internal("alloc returned void".to_string()))?;

        // Store the tag
        let tag_ptr = self
            .builder()
            .build_struct_gep(adt_ty, raw_ptr.into_pointer_value(), 0, "tag_slot")
            .map_err(|e| CodegenError::Internal(format!("gep failed: {:?}", e)))?;
        self.builder()
            .build_store(tag_ptr, state_val.into_int_value())
            .map_err(|e| CodegenError::Internal(format!("store failed: {:?}", e)))?;

        Ok(Some(raw_ptr.into_pointer_value().into()))
    }

    /// Lower `seq` - force evaluation of first argument, return second.
    fn lower_builtin_seq(
        &mut self,
        a_expr: &Expr,
        b_expr: &Expr,
    ) -> CodegenResult<Option<BasicValueEnum<'ctx>>> {
        // Evaluate first argument (for its effect of forcing evaluation)
        let _a = self.lower_expr(a_expr)?;
        // Return second argument
        self.lower_expr(b_expr)
    }

    /// Lower `not` - boolean negation.
    fn lower_builtin_not(
        &mut self,
        bool_expr: &Expr,
    ) -> CodegenResult<Option<BasicValueEnum<'ctx>>> {
        let bool_val = self
            .lower_expr(bool_expr)?
            .ok_or_else(|| CodegenError::Internal("not: argument has no value".to_string()))?;

        let tm = self.type_mapper();

        // Handle different representations of Bool
        let result = match bool_val {
            BasicValueEnum::IntValue(i) => {
                // XOR with 1 to flip the boolean
                let one = i.get_type().const_int(1, false);
                self.builder()
                    .build_xor(i, one, "not_result")
                    .map_err(|e| CodegenError::Internal(format!("failed to build not: {:?}", e)))?
                    .into()
            }
            BasicValueEnum::PointerValue(p) => {
                // Convert pointer to int, XOR with 1, convert back
                let int_val = self
                    .builder()
                    .build_ptr_to_int(p, tm.i64_type(), "bool_to_int")
                    .map_err(|e| {
                        CodegenError::Internal(format!("failed to convert bool: {:?}", e))
                    })?;
                let one = tm.i64_type().const_int(1, false);
                let xored = self
                    .builder()
                    .build_xor(int_val, one, "not_result")
                    .map_err(|e| CodegenError::Internal(format!("failed to build not: {:?}", e)))?;
                self.builder()
                    .build_int_to_ptr(xored, tm.ptr_type(), "not_to_ptr")
                    .map_err(|e| {
                        CodegenError::Internal(format!("failed to convert not result: {:?}", e))
                    })?
                    .into()
            }
            _ => return Err(CodegenError::TypeError("not expects a boolean".to_string())),
        };

        Ok(Some(result))
    }

    // ========================================================================
    // IO Builtin Functions
    // ========================================================================

    /// Lower `putStrLn` - print a string followed by newline.
    fn lower_builtin_put_str_ln(
        &mut self,
        str_expr: &Expr,
    ) -> CodegenResult<Option<BasicValueEnum<'ctx>>> {
        // Print the string (which is a [Char] linked list), then print a newline
        self.lower_print_char_list(str_expr)?;

        // Print newline
        let newline_fn = self
            .functions
            .get(&VarId::new(1000010))
            .ok_or_else(|| CodegenError::Internal("bhc_print_newline not declared".to_string()))?;
        self.builder()
            .build_call(*newline_fn, &[], "")
            .map_err(|e| CodegenError::Internal(format!("failed to call newline: {:?}", e)))?;

        // Return unit (null pointer for IO ())
        Ok(Some(self.type_mapper().ptr_type().const_null().into()))
    }

    /// Lower `putStr` - print a string without newline.
    fn lower_builtin_put_str(
        &mut self,
        str_expr: &Expr,
    ) -> CodegenResult<Option<BasicValueEnum<'ctx>>> {
        // Print the string (which is a [Char] linked list)
        self.lower_print_char_list(str_expr)?;

        // Return unit
        Ok(Some(self.type_mapper().ptr_type().const_null().into()))
    }

    /// Emit a loop that walks a [Char] linked list and prints each character.
    fn lower_print_char_list(&mut self, str_expr: &Expr) -> CodegenResult<()> {
        let str_val = self
            .lower_expr(str_expr)?
            .ok_or_else(|| CodegenError::Internal("print: string has no value".to_string()))?;

        let list_ptr = match str_val {
            BasicValueEnum::PointerValue(p) => p,
            _ => {
                return Err(CodegenError::TypeError(
                    "print expects a string (pointer)".to_string(),
                ))
            }
        };

        let tm = self.type_mapper();
        let current_fn = self
            .builder()
            .get_insert_block()
            .and_then(|b| b.get_parent())
            .ok_or_else(|| CodegenError::Internal("no current function".to_string()))?;

        let print_char_fn = self
            .functions
            .get(&VarId::new(1000009))
            .ok_or_else(|| CodegenError::Internal("bhc_print_char not declared".to_string()))?;

        // Create loop blocks
        let loop_header = self
            .llvm_context()
            .append_basic_block(current_fn, "print_loop");
        let loop_body = self
            .llvm_context()
            .append_basic_block(current_fn, "print_body");
        let loop_end = self
            .llvm_context()
            .append_basic_block(current_fn, "print_done");

        // Branch to loop header
        let pre_loop_block = self.builder().get_insert_block().unwrap();
        self.builder()
            .build_unconditional_branch(loop_header)
            .map_err(|e| CodegenError::Internal(format!("failed to branch: {:?}", e)))?;

        // Loop header: phi node for current list pointer
        self.builder().position_at_end(loop_header);
        let current_node = self
            .builder()
            .build_phi(tm.ptr_type(), "cur_node")
            .map_err(|e| CodegenError::Internal(format!("failed to build phi: {:?}", e)))?;
        current_node.add_incoming(&[(&list_ptr, pre_loop_block)]);

        // Load tag from current node
        let tag = self.extract_adt_tag(current_node.as_basic_value().into_pointer_value())?;
        let is_nil = self
            .builder()
            .build_int_compare(
                inkwell::IntPredicate::EQ,
                tag,
                tm.i64_type().const_zero(),
                "is_nil",
            )
            .map_err(|e| CodegenError::Internal(format!("failed to compare: {:?}", e)))?;
        self.builder()
            .build_conditional_branch(is_nil, loop_end, loop_body)
            .map_err(|e| CodegenError::Internal(format!("failed to branch: {:?}", e)))?;

        // Loop body: extract char, print it, advance to tail
        self.builder().position_at_end(loop_body);
        let node_ptr = current_node.as_basic_value().into_pointer_value();

        // Extract head (char) at field 0
        let head_ptr = self.extract_adt_field(node_ptr, 2, 0)?;
        // The char was stored via int_to_ptr, so convert back to int
        let char_val = self
            .builder()
            .build_ptr_to_int(head_ptr, tm.i64_type(), "char_val")
            .map_err(|e| CodegenError::Internal(format!("failed to get char: {:?}", e)))?;
        // Truncate to i32 for bhc_print_char
        let char_i32 = self
            .builder()
            .build_int_truncate(char_val, tm.i32_type(), "char_i32")
            .map_err(|e| CodegenError::Internal(format!("failed to truncate: {:?}", e)))?;
        self.builder()
            .build_call(*print_char_fn, &[char_i32.into()], "")
            .map_err(|e| CodegenError::Internal(format!("failed to call print_char: {:?}", e)))?;

        // Extract tail at field 1
        let tail_ptr = self.extract_adt_field(node_ptr, 2, 1)?;
        current_node.add_incoming(&[(&tail_ptr, loop_body)]);

        self.builder()
            .build_unconditional_branch(loop_header)
            .map_err(|e| CodegenError::Internal(format!("failed to branch: {:?}", e)))?;

        // Continue after loop
        self.builder().position_at_end(loop_end);
        Ok(())
    }

    /// Lower `putChar` - print a single character.
    fn lower_builtin_put_char(
        &mut self,
        char_expr: &Expr,
    ) -> CodegenResult<Option<BasicValueEnum<'ctx>>> {
        let char_val = self
            .lower_expr(char_expr)?
            .ok_or_else(|| CodegenError::Internal("putChar: char has no value".to_string()))?;

        let char_int = match char_val {
            BasicValueEnum::IntValue(i) => i,
            BasicValueEnum::PointerValue(p) => {
                // Might be a boxed char - unbox it
                self.builder()
                    .build_ptr_to_int(p, self.type_mapper().i64_type(), "unbox_char")
                    .map_err(|e| CodegenError::Internal(format!("failed to unbox char: {:?}", e)))?
            }
            _ => {
                return Err(CodegenError::TypeError(
                    "putChar expects a Char".to_string(),
                ))
            }
        };

        // Truncate to i32 for the RTS call
        let char_i32 = self
            .builder()
            .build_int_truncate(char_int, self.type_mapper().i32_type(), "char_i32")
            .map_err(|e| CodegenError::Internal(format!("failed to truncate char: {:?}", e)))?;

        // Call bhc_print_char
        let print_fn = self
            .functions
            .get(&VarId::new(1000009))
            .ok_or_else(|| CodegenError::Internal("bhc_print_char not declared".to_string()))?;

        self.builder()
            .build_call(*print_fn, &[char_i32.into()], "")
            .map_err(|e| CodegenError::Internal(format!("failed to call print_char: {:?}", e)))?;

        // Return unit
        Ok(Some(self.type_mapper().ptr_type().const_null().into()))
    }

    /// Lower `print` - print a value with its Show instance (simplified).
    ///
    /// For now, we detect the type at codegen time and call the appropriate
    /// print function. This is a simplification - real Haskell uses type classes.
    fn lower_builtin_print(
        &mut self,
        val_expr: &Expr,
    ) -> CodegenResult<Option<BasicValueEnum<'ctx>>> {
        // Get the type of the expression to decide how to print
        let expr_ty = val_expr.ty();

        let val = self
            .lower_expr(val_expr)?
            .ok_or_else(|| CodegenError::Internal("print: value has no result".to_string()))?;

        match val {
            BasicValueEnum::IntValue(i) => {
                // Check if this is a boolean (from comparison or boolean function)
                if self.is_bool_type(&expr_ty) || self.expr_looks_like_bool(val_expr) {
                    // Print as boolean (True/False)
                    let print_fn = self.functions.get(&VarId::new(1000008)).ok_or_else(|| {
                        CodegenError::Internal("bhc_print_bool_ln not declared".to_string())
                    })?;

                    self.builder()
                        .build_call(*print_fn, &[i.into()], "")
                        .map_err(|e| {
                            CodegenError::Internal(format!("failed to call print_bool: {:?}", e))
                        })?;
                } else {
                    // Print as integer
                    let print_fn = self.functions.get(&VarId::new(1000000)).ok_or_else(|| {
                        CodegenError::Internal("bhc_print_int_ln not declared".to_string())
                    })?;

                    self.builder()
                        .build_call(*print_fn, &[i.into()], "")
                        .map_err(|e| {
                            CodegenError::Internal(format!("failed to call print_int: {:?}", e))
                        })?;
                }
            }
            BasicValueEnum::FloatValue(f) => {
                // Print as double
                let print_fn = self.functions.get(&VarId::new(1000001)).ok_or_else(|| {
                    CodegenError::Internal("bhc_print_double_ln not declared".to_string())
                })?;

                // Extend float to double if needed (check if it's f32)
                let f64_type = self.type_mapper().f64_type();
                let f32_type = self.type_mapper().f32_type();
                let f64_val = if f.get_type() == f32_type {
                    self.builder()
                        .build_float_ext(f, f64_type, "to_f64")
                        .map_err(|e| {
                            CodegenError::Internal(format!("failed to extend float: {:?}", e))
                        })?
                } else {
                    f
                };

                self.builder()
                    .build_call(*print_fn, &[f64_val.into()], "")
                    .map_err(|e| {
                        CodegenError::Internal(format!("failed to call print_double: {:?}", e))
                    })?;
            }
            BasicValueEnum::PointerValue(p) => {
                // Pointer could be a boxed value from closure call, string, or ADT.
                // Use the expression's type to decide how to print.
                // Check list first - by type or by expression structure (for when type is Error)
                // Compound showable values (lists, Maybe, Either, tuples) must
                // be rendered through the typed `show` machinery so their
                // elements/fields show recursively instead of printing raw
                // pointers — matching `print x = putStrLn (show x)`. Detect them
                // structurally (via `infer_show_from_expr`) as well as by type,
                // because the type is often erased to `Error` by codegen time —
                // the greedy "boxed int" branch below would otherwise claim them.
                // `print` on a String must show it *with quotes* (`print x =
                // putStrLn (show x)`), unlike `putStrLn`/`putStr`. Route it
                // through the show machinery too (the StringList descriptor adds
                // the quotes), alongside the other compound types.
                let compound_show = matches!(
                    self.infer_show_from_expr(val_expr).map(|(c, _, _)| c),
                    Some(
                        ShowCoerce::List
                            | ShowCoerce::MaybeOf
                            | ShowCoerce::EitherOf
                            | ShowCoerce::Tuple2Of
                            | ShowCoerce::StringList
                    )
                ) || self.is_list_type(&expr_ty)
                    || self.expr_looks_like_list(val_expr)
                    || self.is_string_type(&expr_ty)
                    || self.is_maybe_type(&expr_ty).is_some()
                    || self.is_either_type(&expr_ty).is_some()
                    || self.is_tuple_type(&expr_ty).is_some();
                if compound_show {
                    self.print_via_show_desc(p, val_expr)?;
                } else if let Some(adt_type) = self
                    .infer_adt_type_from_expr(val_expr)
                    .filter(|tn| self.derived_show_fns.contains_key(tn))
                {
                    // A user-defined ADT with derived Show. Detected
                    // structurally because its type is usually erased to `Error`
                    // by codegen (otherwise the boxed-int branch below would
                    // print its pointer).
                    self.print_adt_via_derived_show(p, &adt_type)?;
                } else if self.is_bool_type(&expr_ty) || self.expr_looks_like_bool(val_expr) {
                    // Bool value - use extract_bool_tag which handles both Bool ADT
                    // (heap-allocated from any/all/even/odd/char predicates) and
                    // tagged-int-as-pointer (from comparison operators)
                    let bool_tag = self.extract_bool_tag(p)?;

                    let print_fn = self.functions.get(&VarId::new(1000008)).ok_or_else(|| {
                        CodegenError::Internal("bhc_print_bool_ln not declared".to_string())
                    })?;

                    self.builder()
                        .build_call(*print_fn, &[bool_tag.into()], "")
                        .map_err(|e| {
                            CodegenError::Internal(format!("failed to call print_bool: {:?}", e))
                        })?;
                } else if (self.is_int_type(&expr_ty) || self.is_type_variable_or_error(&expr_ty))
                    && !self.expr_returns_double(val_expr)
                {
                    // Boxed integer (or polymorphic type that might be int) - unbox and print as int.
                    // For type variables from closures, we assume int since that's the most common case.
                    // This is safe because boxed ints use int_to_ptr which stores the value in the
                    // pointer bits, not as an actual memory reference.
                    let int_val = self
                        .builder()
                        .build_ptr_to_int(p, self.type_mapper().i64_type(), "unbox_int")
                        .map_err(|e| {
                            CodegenError::Internal(format!("failed to unbox int: {:?}", e))
                        })?;

                    let print_fn = self.functions.get(&VarId::new(1000000)).ok_or_else(|| {
                        CodegenError::Internal("bhc_print_int_ln not declared".to_string())
                    })?;

                    self.builder()
                        .build_call(*print_fn, &[int_val.into()], "")
                        .map_err(|e| {
                            CodegenError::Internal(format!("failed to call print_int: {:?}", e))
                        })?;
                } else if self.is_float_type(&expr_ty) || self.expr_returns_double(val_expr) {
                    // Boxed float/double - unbox and print as double. The
                    // structural `expr_returns_double` check catches results
                    // whose type is erased to `Error` (e.g. `sqrt x`, `abs d`).
                    let bits = self
                        .builder()
                        .build_ptr_to_int(p, self.type_mapper().i64_type(), "unbox_float_bits")
                        .map_err(|e| {
                            CodegenError::Internal(format!("failed to unbox float: {:?}", e))
                        })?;
                    let float_val = self
                        .builder()
                        .build_bit_cast(bits, self.type_mapper().f64_type(), "to_double")
                        .map_err(|e| {
                            CodegenError::Internal(format!("failed to cast to double: {:?}", e))
                        })?;

                    let print_fn = self.functions.get(&VarId::new(1000001)).ok_or_else(|| {
                        CodegenError::Internal("bhc_print_double_ln not declared".to_string())
                    })?;

                    self.builder()
                        .build_call(*print_fn, &[float_val.into()], "")
                        .map_err(|e| {
                            CodegenError::Internal(format!("failed to call print_double: {:?}", e))
                        })?;
                } else {
                    // Assume it's a string or other pointer type
                    let print_fn = self.functions.get(&VarId::new(1000002)).ok_or_else(|| {
                        CodegenError::Internal("bhc_print_string_ln not declared".to_string())
                    })?;

                    self.builder()
                        .build_call(*print_fn, &[p.into()], "")
                        .map_err(|e| {
                            CodegenError::Internal(format!("failed to call print_string: {:?}", e))
                        })?;
                }
            }
            _ => {
                return Err(CodegenError::Unsupported(
                    "print: unsupported value type".to_string(),
                ));
            }
        }

        // Return unit
        Ok(Some(self.type_mapper().ptr_type().const_null().into()))
    }

    /// Print a value by rendering it through the typed `show` machinery
    /// (`bhc_show_with_desc`) and writing the result with a trailing newline —
    /// i.e. `print x = putStrLn (show x)`. Used for compound types (lists,
    /// Maybe, Either, tuples) whose elements/fields must be shown recursively
    /// rather than printed as raw pointers.
    fn print_via_show_desc(
        &mut self,
        p: inkwell::values::PointerValue<'ctx>,
        val_expr: &Expr,
    ) -> CodegenResult<()> {
        let desc = self.build_show_descriptor(val_expr);
        let show_fn = *self
            .functions
            .get(&VarId::new(1000099))
            .ok_or_else(|| CodegenError::Internal("bhc_show_with_desc not declared".to_string()))?;
        let cstr = self
            .builder()
            .build_call(show_fn, &[p.into(), desc.into()], "show_desc")
            .map_err(|e| CodegenError::Internal(format!("show_with_desc call failed: {:?}", e)))?
            .try_as_basic_value()
            .basic()
            .ok_or_else(|| CodegenError::Internal("show_with_desc: returned void".to_string()))?;
        let print_ln = *self.functions.get(&VarId::new(1000002)).ok_or_else(|| {
            CodegenError::Internal("bhc_print_string_ln not declared".to_string())
        })?;
        self.builder()
            .build_call(print_ln, &[cstr.into_pointer_value().into()], "")
            .map_err(|e| CodegenError::Internal(format!("print_string_ln call failed: {:?}", e)))?;
        Ok(())
    }

    /// Print a user-defined ADT value that has a derived `Show`: call its
    /// `$derived_show_<Type>` function on the already-lowered value `p`, then
    /// print the resulting `[Char]` with a trailing newline — `print x =
    /// putStrLn (show x)`. Uses `p` directly (no re-lowering of the argument).
    fn print_adt_via_derived_show(
        &mut self,
        p: inkwell::values::PointerValue<'ctx>,
        type_name: &str,
    ) -> CodegenResult<()> {
        let show_var_id = *self
            .derived_show_fns
            .get(type_name)
            .ok_or_else(|| CodegenError::Internal(format!("no derived show for {type_name}")))?;
        let show_fn = *self.functions.get(&show_var_id).ok_or_else(|| {
            CodegenError::Internal(format!("derived show fn for {type_name} not declared"))
        })?;
        // Derived show uses the standard (env, value) -> string closure calling
        // convention, returning a `[Char]`.
        let ptr_type = self.type_mapper().ptr_type();
        let null_env = ptr_type.const_null();
        let fn_ptr = show_fn.as_global_value().as_pointer_value();
        let fn_type = ptr_type.fn_type(&[ptr_type.into(), ptr_type.into()], false);
        let result = self
            .builder()
            .build_indirect_call(
                fn_type,
                fn_ptr,
                &[null_env.into(), p.into()],
                "derived_show",
            )
            .map_err(|e| CodegenError::Internal(format!("derived show call: {:?}", e)))?
            .try_as_basic_value()
            .basic()
            .ok_or_else(|| CodegenError::Internal("derived show: returned void".to_string()))?;
        let list_ptr = self.value_to_ptr(result)?;
        self.lower_print_char_list_ptr(list_ptr)?;
        let newline_fn = *self
            .functions
            .get(&VarId::new(1000010))
            .ok_or_else(|| CodegenError::Internal("bhc_print_newline not declared".to_string()))?;
        self.builder()
            .build_call(newline_fn, &[], "")
            .map_err(|e| CodegenError::Internal(format!("print newline: {:?}", e)))?;
        Ok(())
    }

    /// Check if a type is an integer type.
    fn is_int_type(&self, ty: &Ty) -> bool {
        match ty {
            Ty::Con(con) => {
                let name = con.name.as_str();
                matches!(
                    name,
                    "Int" | "Int#" | "Int64" | "Int32" | "Word" | "Word64" | "Word32"
                )
            }
            Ty::Prim(prim) => {
                use bhc_types::PrimTy;
                matches!(prim, PrimTy::I32 | PrimTy::I64 | PrimTy::U32 | PrimTy::U64)
            }
            Ty::App(f, _) => self.is_int_type(f),
            Ty::Forall(_, body) => self.is_int_type(body),
            _ => false,
        }
    }

    /// E.45: Check if a type is the Integer (arbitrary precision) type.
    fn is_integer_type(&self, ty: &Ty) -> bool {
        match ty {
            Ty::Con(con) => con.name.as_str() == "Integer",
            Ty::App(f, _) => self.is_integer_type(f),
            Ty::Forall(_, body) => self.is_integer_type(body),
            _ => false,
        }
    }

    /// E.45: Check if the parameter at `param_idx` in a function type is Integer.
    /// Walks through Ty::Fun(param, result) chain to find the param at the given index.
    fn is_integer_param_at(&self, fn_ty: &Ty, param_idx: usize) -> bool {
        let mut current = fn_ty;
        // Skip through foralls
        while let Ty::Forall(_, body) = current {
            current = body;
        }
        // Walk the function arrow chain to the param_idx-th parameter
        for _ in 0..param_idx {
            if let Ty::Fun(_, result) = current {
                current = result;
            } else {
                return false;
            }
        }
        if let Ty::Fun(param, _) = current {
            self.is_integer_type(param)
        } else {
            false
        }
    }

    /// E.45: Extract a list of which parameter positions in a function type are Integer.
    /// For `Integer -> Integer -> IO ()`, returns `[true, true]`.
    fn extract_integer_param_positions(&self, fn_ty: &Ty) -> Vec<bool> {
        let mut positions = Vec::new();
        let mut current = fn_ty;
        // Skip through foralls
        while let Ty::Forall(_, body) = current {
            current = body;
        }
        // Walk the function arrow chain
        while let Ty::Fun(param, result) = current {
            positions.push(self.is_integer_type(param));
            current = result;
        }
        positions
    }

    /// Check if a type is a float type.
    fn is_float_type(&self, ty: &Ty) -> bool {
        match ty {
            Ty::Con(con) => {
                let name = con.name.as_str();
                matches!(name, "Float" | "Float#" | "Double" | "Double#")
            }
            Ty::Prim(prim) => {
                use bhc_types::PrimTy;
                matches!(prim, PrimTy::F32 | PrimTy::F64)
            }
            Ty::App(f, _) => self.is_float_type(f),
            Ty::Forall(_, body) => self.is_float_type(body),
            _ => false,
        }
    }

    /// Check if a type is a Bool type.
    fn is_bool_type(&self, ty: &Ty) -> bool {
        match ty {
            Ty::Con(con) => {
                let name = con.name.as_str();
                matches!(name, "Bool" | "Boolean")
            }
            Ty::App(f, _) => self.is_bool_type(f),
            Ty::Forall(_, body) => self.is_bool_type(body),
            _ => false,
        }
    }

    /// Check if a type is a type variable (polymorphic) or error type.
    /// We treat error types the same as type variables since we don't know
    /// the concrete type and should default to treating it as a potential integer.
    fn is_type_variable_or_error(&self, ty: &Ty) -> bool {
        match ty {
            Ty::Var(_) => true,
            Ty::Error => true, // Error type might be unresolved - treat as unknown
            Ty::App(f, _) => self.is_type_variable_or_error(f),
            Ty::Forall(_, body) => self.is_type_variable_or_error(body),
            _ => false,
        }
    }

    /// Check if a type is a list type.
    fn is_list_type(&self, ty: &Ty) -> bool {
        match ty {
            // Direct list type
            Ty::List(_) => true,
            // Type application form: [] a
            Ty::App(f, _) => {
                // Check if the type constructor is []
                if let Ty::Con(con) = f.as_ref() {
                    con.name.as_str() == "[]" || con.name.as_str() == "List"
                } else {
                    false
                }
            }
            // Just "[]" without argument (unlikely but handle it)
            Ty::Con(con) => con.name.as_str() == "[]" || con.name.as_str() == "List",
            Ty::Forall(_, body) => self.is_list_type(body),
            _ => false,
        }
    }

    /// Check if a type is a Char type.
    fn is_char_type(&self, ty: &Ty) -> bool {
        match ty {
            Ty::Con(con) => con.name.as_str() == "Char",
            Ty::App(f, _) => self.is_char_type(f),
            Ty::Forall(_, body) => self.is_char_type(body),
            _ => false,
        }
    }

    /// Check if a type is specifically Double (not Float).
    fn is_double_type(&self, ty: &Ty) -> bool {
        match ty {
            Ty::Con(con) => matches!(con.name.as_str(), "Double" | "Double#"),
            Ty::Prim(prim) => matches!(prim, bhc_types::PrimTy::F64),
            Ty::App(f, _) => self.is_double_type(f),
            Ty::Forall(_, body) => self.is_double_type(body),
            _ => false,
        }
    }

    /// Check if a type is [Char] (String).
    fn is_string_type(&self, ty: &Ty) -> bool {
        match ty {
            Ty::List(elem) => self.is_char_type(elem),
            Ty::App(f, a) => {
                if let Ty::Con(con) = f.as_ref() {
                    (con.name.as_str() == "[]" || con.name.as_str() == "List")
                        && self.is_char_type(a)
                } else {
                    false
                }
            }
            Ty::Con(con) => con.name.as_str() == "String",
            Ty::Forall(_, body) => self.is_string_type(body),
            _ => false,
        }
    }

    /// Check if a type is the Rational type.
    fn is_rational_type(&self, ty: &Ty) -> bool {
        match ty {
            Ty::Con(con) => con.name.as_str() == "Rational",
            Ty::App(f, _) => self.is_rational_type(f),
            Ty::Forall(_, body) => self.is_rational_type(body),
            _ => false,
        }
    }

    /// Check if a type is Maybe a, returning the inner type.
    fn is_maybe_type<'t>(&self, ty: &'t Ty) -> Option<&'t Ty> {
        match ty {
            Ty::App(f, a) => {
                if let Ty::Con(con) = f.as_ref() {
                    if con.name.as_str() == "Maybe" {
                        return Some(a);
                    }
                }
                None
            }
            Ty::Forall(_, body) => self.is_maybe_type(body),
            _ => None,
        }
    }

    /// Check if a type is Either a b, returning (left, right) types.
    fn is_either_type<'t>(&self, ty: &'t Ty) -> Option<(&'t Ty, &'t Ty)> {
        match ty {
            Ty::App(f, b) => {
                if let Ty::App(ff, a) = f.as_ref() {
                    if let Ty::Con(con) = ff.as_ref() {
                        if con.name.as_str() == "Either" {
                            return Some((a, b));
                        }
                    }
                }
                None
            }
            Ty::Forall(_, body) => self.is_either_type(body),
            _ => None,
        }
    }

    /// Check if a type is a 2-tuple (a, b), returning (fst, snd) types.
    fn is_tuple_type<'t>(&self, ty: &'t Ty) -> Option<(&'t Ty, &'t Ty)> {
        match ty {
            Ty::Tuple(elems) if elems.len() == 2 => Some((&elems[0], &elems[1])),
            Ty::App(f, b) => {
                if let Ty::App(ff, a) = f.as_ref() {
                    if let Ty::Con(con) = ff.as_ref() {
                        if con.name.as_str() == "(,)" || con.name.as_str() == "Tuple2" {
                            return Some((a, b));
                        }
                    }
                }
                None
            }
            Ty::Forall(_, body) => self.is_tuple_type(body),
            _ => None,
        }
    }

    /// Check if a type is unit ().
    fn is_unit_type(&self, ty: &Ty) -> bool {
        match ty {
            Ty::Tuple(elems) => elems.is_empty(),
            Ty::Con(con) => con.name.as_str() == "()" || con.name.as_str() == "Unit",
            Ty::Forall(_, body) => self.is_unit_type(body),
            _ => false,
        }
    }

    /// Get the element type from a list type.
    // Type-inspection helper retained for list-handling lowering paths.
    #[allow(dead_code)]
    fn list_elem_type<'t>(&self, ty: &'t Ty) -> Option<&'t Ty> {
        match ty {
            Ty::List(elem) => Some(elem),
            Ty::App(f, a) => {
                if let Ty::Con(con) = f.as_ref() {
                    if con.name.as_str() == "[]" || con.name.as_str() == "List" {
                        return Some(a);
                    }
                }
                None
            }
            Ty::Forall(_, body) => self.list_elem_type(body),
            _ => None,
        }
    }

    /// Convert a type to a show type tag for RTS functions.
    /// 0=Int, 1=Double, 2=Float, 3=Bool, 4=Char, 5=String
    // Retained for RTS show dispatch; not currently called on all paths.
    #[allow(dead_code)]
    fn type_to_show_tag(&self, ty: &Ty) -> i64 {
        if self.is_char_type(ty) {
            return 4;
        }
        if self.is_bool_type(ty) {
            return 3;
        }
        if self.is_double_type(ty) {
            return 1;
        }
        if self.is_float_type(ty) {
            // is_float_type matches both Float and Double; if we got here, it's Float only
            // since Double was already checked above
            return 2;
        }
        if self.is_string_type(ty) {
            return 5;
        }
        if self.is_int_type(ty) {
            return 0;
        }
        0 // default to Int
    }

    /// Check if a function expression (in App position) returns Bool when fully applied.
    fn expr_returns_bool(&self, func_expr: &Expr) -> bool {
        match func_expr {
            // Unary Bool-returning functions: even, odd, not, null, isAlpha, etc.
            Expr::Var(var, _) => {
                let name = var.name.as_str();
                matches!(
                    name,
                    "even"
                        | "odd"
                        | "not"
                        | "null"
                        | "isEOF"
                        | "isAlpha"
                        | "isAlphaNum"
                        | "isAscii"
                        | "isControl"
                        | "isDigit"
                        | "isHexDigit"
                        | "isLetter"
                        | "isLower"
                        | "isNumber"
                        | "isPrint"
                        | "isPunctuation"
                        | "isSpace"
                        | "isSymbol"
                        | "isUpper"
                        | "isJust"
                        | "isNothing"
                        | "isLeft"
                        | "isRight"
                        | "isAbsolute"
                        | "isRelative"
                        | "hasExtension"
                        | "doesFileExist"
                        | "doesDirectoryExist"
                        | "Data.Map.null"
                        | "Data.Set.null"
                        | "Data.IntMap.null"
                        | "Data.IntSet.null"
                        | "Data.Sequence.null"
                )
            }
            // Binary Bool-returning: fully applied comparison operators
            // e.g. App(App(==, x), y) — here func_expr is App(==, x)
            Expr::App(f, _, _) => match f.as_ref() {
                Expr::Var(var, _) => {
                    let name = var.name.as_str();
                    matches!(
                        name,
                        "==" | "/="
                            | "<"
                            | ">"
                            | "<="
                            | ">="
                            | "&&"
                            | "||"
                            | "and"
                            | "or"
                            | "elem"
                            | "notElem"
                            | "any"
                            | "all"
                            | "Data.Map.member"
                            | "Data.Map.notMember"
                            | "Data.Map.isSubmapOf"
                            | "Data.Set.member"
                            | "Data.Set.notMember"
                            | "Data.Set.isSubsetOf"
                            | "Data.IntMap.member"
                            | "Data.IntSet.member"
                            | "isPrefixOf"
                            | "isSuffixOf"
                            | "isInfixOf"
                    )
                }
                _ => false,
            },
            Expr::TyApp(inner, _, _) => self.expr_returns_bool(inner),
            _ => false,
        }
    }

    /// Check if a fully-applied expression returns Int (e.g., gcd 12 8).
    fn expr_returns_int(&self, expr: &Expr) -> bool {
        match expr {
            Expr::App(f, _, _) => {
                match f.as_ref() {
                    // Fully applied binary Int ops: gcd, lcm, div, mod, etc.
                    Expr::App(ff, _, _) => match ff.as_ref() {
                        Expr::Var(var, _) => {
                            let name = var.name.as_str();
                            matches!(
                                name,
                                "gcd"
                                    | "lcm"
                                    | "quot"
                                    | "rem"
                                    | "+"
                                    | "-"
                                    | "*"
                                    | "div"
                                    | "mod"
                                    | "min"
                                    | "max"
                                    | "subtract"
                            )
                        }
                        _ => false,
                    },
                    // Unary Int ops
                    Expr::Var(var, _) => {
                        let name = var.name.as_str();
                        if name == "succ" || name == "pred" {
                            // E.54: succ/pred on user enums return ADT, not Int
                            if let Expr::App(_, arg, _) = expr {
                                return self.infer_adt_type_from_expr(arg).is_none();
                            }
                        }
                        if name == "read" && !self.derived_read_fns.is_empty() {
                            return false; // read returns ADT, not Int
                        }
                        matches!(
                            name,
                            "length"
                                | "ord"
                                | "abs"
                                | "signum"
                                | "negate"
                                | "fromIntegral"
                                | "toInteger"
                                | "fromInteger"
                                | "digitToInt"
                                | "read"
                                | "succ"
                                | "pred"
                        )
                    }
                    _ => false,
                }
            }
            _ => false,
        }
    }

    /// Check if a fully-applied expression returns Double/Float.
    fn expr_returns_double(&self, expr: &Expr) -> bool {
        match expr {
            Expr::Lit(Literal::Double(_), _, _) | Expr::Lit(Literal::Float(_), _, _) => true,
            Expr::App(f, arg, _) => {
                match f.as_ref() {
                    // Unary Double-returning functions
                    Expr::Var(var, _) => {
                        let n = var.name.as_str();
                        // Always-Double `Floating` operations.
                        if matches!(
                            n,
                            "sqrt"
                                | "sin"
                                | "cos"
                                | "tan"
                                | "exp"
                                | "log"
                                | "asin"
                                | "acos"
                                | "atan"
                                | "sinh"
                                | "cosh"
                                | "tanh"
                                | "recip"
                        ) {
                            return true;
                        }
                        // Type-preserving `Num` ops: the result type equals the
                        // argument's, so a `Double` argument means a `Double`
                        // result (but an `Int` argument an `Int` result).
                        if matches!(n, "abs" | "negate" | "signum") {
                            return self.expr_returns_double(arg);
                        }
                        // Type-based inference: check if function returns Double or IO Double
                        self.fun_returns_double(&var.ty)
                    }
                    // Binary Double-returning: App(App(op, x), y) — f is App(op, x)
                    Expr::App(ff, _, _) => {
                        match ff.as_ref() {
                            Expr::Var(var, _) => {
                                let n = var.name.as_str();
                                if matches!(n, "/" | "**") {
                                    return true;
                                }
                                // Type-based: check if the partially-applied function returns Double
                                // For a binary function A -> B -> C, after one application we have B -> C
                                if let Ty::Fun(_, ret) = &var.ty {
                                    self.fun_returns_double(ret)
                                } else {
                                    false
                                }
                            }
                            _ => false,
                        }
                    }
                    _ => false,
                }
            }
            Expr::TyApp(inner, _, _) => self.expr_returns_double(inner),
            _ => false,
        }
    }

    /// Check if a function type's return type is Double (unwrapping IO if needed).
    fn fun_returns_double(&self, ty: &Ty) -> bool {
        match ty {
            Ty::Fun(_, ret) => self.fun_returns_double(ret),
            _ => self.is_double_or_io_double(ty),
        }
    }

    /// Check if a type is Double or IO Double.
    fn is_double_or_io_double(&self, ty: &Ty) -> bool {
        if self.is_double_type(ty) {
            return true;
        }
        // Check for IO Double: App(Con("IO"), Con("Double"))
        if let Ty::App(f, a) = ty {
            if let Ty::Con(con) = f.as_ref() {
                if con.name.as_str() == "IO" {
                    return self.is_double_type(a);
                }
            }
        }
        false
    }

    /// Check if a function expression returns Ordering (e.g., `compare`).
    fn expr_returns_ordering(&self, func_expr: &Expr) -> bool {
        match func_expr {
            // Fully applied binary: App(compare, x) — the outer App provides the second arg
            Expr::App(ff, _, _) => match ff.as_ref() {
                Expr::Var(var, _) => var.name.as_str() == "compare",
                _ => false,
            },
            Expr::TyApp(inner, _, _) => self.expr_returns_ordering(inner),
            _ => false,
        }
    }

    /// Check if an expression looks like a list based on its structure.
    /// This is used when type information is unavailable (Error type).
    fn expr_looks_like_list(&self, expr: &Expr) -> bool {
        match expr {
            // Application of a constructor or function
            Expr::App(f, _, _) => self.expr_looks_like_list(f),
            // Type application
            Expr::TyApp(e, _, _) => self.expr_looks_like_list(e),
            // Let binding - check the body
            Expr::Let(_, body, _) => self.expr_looks_like_list(body),
            // Variable that's a list constructor or function returning a list
            Expr::Var(var, _) => {
                let name = var.name.as_str();
                // List constructors and functions that return lists
                matches!(
                    name,
                    ":" | "[]"
                        | "Nil"
                        | "Cons"
                        | "map"
                        | "filter"
                        | "reverse"
                        | "take"
                        | "drop"
                        | "enumFromTo"
                        | "enumFrom"
                        | "enumFromThen"
                        | "enumFromThenTo"
                        | "replicate"
                        | "append"
                        | "tail"
                        | "maybeToList"
                        | "catMaybes"
                        | "mapMaybe"
                        | "lefts"
                        | "rights"
                        | "guard"
                        | "sort"
                        | "sortBy"
                        | "sortOn"
                        | "nub"
                        | "nubBy"
                        | "delete"
                        | "deleteBy"
                        | "union"
                        | "unionBy"
                        | "intersect"
                        | "intersectBy"
                        | "insert"
                        | "concat"
                        | "concatMap"
                        | "zip"
                        | "zipWith"
                        | "zip3"
                        | "zipWith3"
                        | "transpose"
                        | "intersperse"
                        | "intercalate"
                        | "takeWhile"
                        | "dropWhile"
                        | "Data.Map.toList"
                        | "Data.Map.toAscList"
                        | "Data.Map.toDescList"
                        | "Data.Map.keys"
                        | "Data.Map.elems"
                        | "Data.Map.assocs"
                        | "Data.Set.toList"
                        | "Data.Set.toAscList"
                        | "Data.Set.toDescList"
                        | "Data.Set.elems"
                        | "Data.Sequence.toList"
                )
            }
            _ => false,
        }
    }

    /// Check if an expression looks like a boolean based on its structure.
    /// This is used when type information is unavailable (Error type).
    fn expr_looks_like_bool(&self, expr: &Expr) -> bool {
        match expr {
            // Application of comparison or boolean function
            Expr::App(f, _, _) => self.expr_looks_like_bool(f),
            // Type application
            Expr::TyApp(e, _, _) => self.expr_looks_like_bool(e),
            // Let binding - check the body
            Expr::Let(_, body, _) => self.expr_looks_like_bool(body),
            // Variable that's a comparison operator or boolean function
            Expr::Var(var, _) => {
                let name = var.name.as_str();
                // Comparison operators and boolean functions
                matches!(
                    name,
                    ">" | "<"
                        | ">="
                        | "<="
                        | "=="
                        | "/="
                        | "True"
                        | "False"
                        | "not"
                        | "&&"
                        | "||"
                        | "and"
                        | "or"
                        | "isJust"
                        | "isNothing"
                        | "isLeft"
                        | "isRight"
                        | "null"
                        | "elem"
                        | "notElem"
                        | "even"
                        | "odd"
                        | "any"
                        | "all"
                        | "greaterThan"
                        | "lessThan"
                        | "equals"
                        | "isAlpha"
                        | "isAlphaNum"
                        | "isAscii"
                        | "isControl"
                        | "isDigit"
                        | "isHexDigit"
                        | "isLetter"
                        | "isLower"
                        | "isNumber"
                        | "isPrint"
                        | "isPunctuation"
                        | "isSpace"
                        | "isSymbol"
                        | "isUpper"
                        | "isAbsolute"
                        | "isRelative"
                        | "hasExtension"
                        | "doesFileExist"
                        | "doesDirectoryExist"
                        | "isPrefixOf"
                        | "isSuffixOf"
                        | "isInfixOf"
                        | "Data.Map.null"
                        | "Data.Set.null"
                        | "Data.IntMap.null"
                        | "Data.IntSet.null"
                        | "Data.Sequence.null"
                        | "Data.Map.member"
                        | "Data.Map.notMember"
                        | "Data.Map.isSubmapOf"
                        | "Data.Set.member"
                        | "Data.Set.notMember"
                        | "Data.Set.isSubsetOf"
                        | "Data.IntMap.member"
                        | "Data.IntSet.member"
                )
            }
            _ => false,
        }
    }

    /// Lower `getLine` - read a line from stdin via RTS `bhc_getLine`.
    fn lower_builtin_get_line(&mut self) -> CodegenResult<Option<BasicValueEnum<'ctx>>> {
        let rts_fn = self
            .functions
            .get(&VarId::new(1000071))
            .ok_or_else(|| CodegenError::Internal("bhc_getLine not declared".to_string()))?;
        let result = self
            .builder()
            .build_call(*rts_fn, &[], "getline_result")
            .map_err(|e| CodegenError::Internal(format!("getLine call failed: {:?}", e)))?
            .try_as_basic_value()
            .basic()
            .ok_or_else(|| CodegenError::Internal("getLine: returned void".to_string()))?;
        Ok(Some(result))
    }

    /// Lower `>>=` (bind) for monads.
    /// For IO: execute first action, pass result to function, execute result.
    fn lower_builtin_bind(
        &mut self,
        action_expr: &Expr,
        func_expr: &Expr,
    ) -> CodegenResult<Option<BasicValueEnum<'ctx>>> {
        // Execute the first action
        let action_result = self
            .lower_expr(action_expr)?
            .ok_or_else(|| CodegenError::Internal(">>=: action has no value".to_string()))?;

        // Lower the function
        let func_val = self
            .lower_expr(func_expr)?
            .ok_or_else(|| CodegenError::Internal(">>=: function has no value".to_string()))?;

        // Apply the function to the action's result
        let func_ptr = match func_val {
            BasicValueEnum::PointerValue(p) => p,
            _ => {
                return Err(CodegenError::TypeError(
                    ">>=: function must be a closure".to_string(),
                ))
            }
        };

        let tm = self.type_mapper();
        let ptr_type = tm.ptr_type();

        // Convert action result to pointer for uniform calling convention
        let action_ptr = self.value_to_ptr(action_result)?;

        // Call the function with the action result
        let fn_ptr = self.extract_closure_fn_ptr(func_ptr)?;
        let fn_type = ptr_type.fn_type(&[ptr_type.into(), ptr_type.into()], false);
        let result = self
            .builder()
            .build_indirect_call(
                fn_type,
                fn_ptr,
                &[func_ptr.into(), action_ptr.into()],
                "bind_result",
            )
            .map_err(|e| CodegenError::Internal(format!("failed to call bind function: {:?}", e)))?
            .try_as_basic_value()
            .basic()
            .ok_or_else(|| CodegenError::Internal(">>=: function returned void".to_string()))?;

        Ok(Some(result))
    }

    /// Lower `>>` (then) for monads.
    /// For IO: execute first action, ignore result, execute second action.
    fn lower_builtin_then(
        &mut self,
        action1_expr: &Expr,
        action2_expr: &Expr,
    ) -> CodegenResult<Option<BasicValueEnum<'ctx>>> {
        // Execute the first action (result is discarded)
        let _action1_result = self.lower_expr(action1_expr)?;

        // Execute the second action and return its result
        self.lower_expr(action2_expr)
    }

    /// Lower `return` / `pure` for monads.
    /// For IO: just return the value wrapped (identity for our simple model).
    fn lower_builtin_return(
        &mut self,
        value_expr: &Expr,
    ) -> CodegenResult<Option<BasicValueEnum<'ctx>>> {
        // For our simple IO model, return is identity
        self.lower_expr(value_expr)
    }

    // ========================================================================
    // Monad Transformer Helpers
    // ========================================================================

    /// Helper to build a closure that captures values from the current scope.
    /// `body_name` is a unique name for the closure function.
    /// `num_captures` is how many captured values there are.
    /// `build_body` receives (builder, env_ptr, arg) and should build the body + return.
    // Monad-transformer closure helper retained for transformer lowering paths.
    #[allow(dead_code)]
    fn build_transformer_closure(
        &mut self,
        body_name: &str,
        captures: &[BasicValueEnum<'ctx>],
    ) -> CodegenResult<PointerValue<'ctx>> {
        let tm = self.type_mapper();
        let ptr_type = tm.ptr_type();
        let _num_captures = captures.len() as u32;

        // Create the closure function: (ptr env, ptr arg) -> ptr
        let fn_type = ptr_type.fn_type(&[ptr_type.into(), ptr_type.into()], false);
        let func = self
            .module
            .llvm_module()
            .add_function(body_name, fn_type, None);

        // Allocate closure with captures
        let capture_pairs: Vec<(VarId, BasicValueEnum<'ctx>)> = captures
            .iter()
            .enumerate()
            .map(|(i, v)| (VarId::new(900000 + i), *v))
            .collect();

        let fn_ptr = func.as_global_value().as_pointer_value();
        let closure_ptr = self.alloc_closure(fn_ptr, &capture_pairs)?;
        Ok(closure_ptr)
    }

    /// Helper: emit a transformer closure body function that has already been added to the module.
    /// Returns the function value so the caller can build its body.
    /// Takes 2 arguments: (closure_env, state)
    fn get_or_create_transformer_fn(&mut self, name: &str) -> FunctionValue<'ctx> {
        let ptr_type = self.type_mapper().ptr_type();
        if let Some(existing) = self.module.llvm_module().get_function(name) {
            existing
        } else {
            let fn_type = ptr_type.fn_type(&[ptr_type.into(), ptr_type.into()], false);
            self.module.llvm_module().add_function(name, fn_type, None)
        }
    }

    /// Helper for nested transformers (e.g., StateT over ReaderT).
    /// Takes 3 arguments: (closure_env, state, reader_env)
    fn get_or_create_nested_transformer_fn(&mut self, name: &str) -> FunctionValue<'ctx> {
        let ptr_type = self.type_mapper().ptr_type();
        if let Some(existing) = self.module.llvm_module().get_function(name) {
            existing
        } else {
            let fn_type =
                ptr_type.fn_type(&[ptr_type.into(), ptr_type.into(), ptr_type.into()], false);
            self.module.llvm_module().add_function(name, fn_type, None)
        }
    }

    /// Helper: get or create a list append function for use in WriterT.
    /// The function has signature: (ptr, ptr) -> ptr (list1, list2) -> appended
    fn get_or_create_list_append_fn(&mut self) -> CodegenResult<FunctionValue<'ctx>> {
        let fn_name = "bhc_list_append";
        let ptr_type = self.type_mapper().ptr_type();

        if let Some(existing) = self.module.llvm_module().get_function(fn_name) {
            return Ok(existing);
        }

        let fn_type = ptr_type.fn_type(&[ptr_type.into(), ptr_type.into()], false);
        let func = self
            .module
            .llvm_module()
            .add_function(fn_name, fn_type, None);

        // Build the function body - implements: append xs ys = foldr (:) ys xs
        // which reverses xs, then conses each element onto ys
        let entry = self.llvm_ctx.append_basic_block(func, "entry");
        let rev_header = self.llvm_ctx.append_basic_block(func, "rev_header");
        let rev_body = self.llvm_ctx.append_basic_block(func, "rev_body");
        let rev_exit = self.llvm_ctx.append_basic_block(func, "rev_exit");
        let fold_header = self.llvm_ctx.append_basic_block(func, "fold_header");
        let fold_body = self.llvm_ctx.append_basic_block(func, "fold_body");
        let fold_exit = self.llvm_ctx.append_basic_block(func, "fold_exit");

        let saved_bb = self.builder().get_insert_block();
        self.builder().position_at_end(entry);

        let list1 = func.get_nth_param(0).unwrap().into_pointer_value();
        let list2 = func.get_nth_param(1).unwrap().into_pointer_value();
        let tm = self.type_mapper();

        // Start with empty accumulator for reverse
        let nil = self.build_nil()?;
        self.builder()
            .build_unconditional_branch(rev_header)
            .map_err(|e| CodegenError::Internal(format!("append branch: {:?}", e)))?;

        // Reverse loop header
        self.builder().position_at_end(rev_header);
        let rev_acc_phi = self
            .builder()
            .build_phi(ptr_type, "rev_acc")
            .map_err(|e| CodegenError::Internal(format!("append phi: {:?}", e)))?;
        let rev_list_phi = self
            .builder()
            .build_phi(ptr_type, "rev_list")
            .map_err(|e| CodegenError::Internal(format!("append phi: {:?}", e)))?;

        rev_acc_phi.add_incoming(&[(&nil, entry)]);
        rev_list_phi.add_incoming(&[(&list1, entry)]);

        let rev_current = rev_list_phi.as_basic_value().into_pointer_value();
        let rev_tag = self.extract_adt_tag(rev_current)?;
        let rev_is_empty = self
            .builder()
            .build_int_compare(
                inkwell::IntPredicate::EQ,
                rev_tag,
                tm.i64_type().const_zero(),
                "rev_is_empty",
            )
            .map_err(|e| CodegenError::Internal(format!("append cmp: {:?}", e)))?;
        self.builder()
            .build_conditional_branch(rev_is_empty, rev_exit, rev_body)
            .map_err(|e| CodegenError::Internal(format!("append branch: {:?}", e)))?;

        // Reverse loop body
        self.builder().position_at_end(rev_body);
        let rev_head = self.extract_adt_field(rev_current, 2, 0)?;
        let rev_tail = self.extract_adt_field(rev_current, 2, 1)?;
        let rev_new_acc = self.build_cons(rev_head.into(), rev_acc_phi.as_basic_value())?;
        self.builder()
            .build_unconditional_branch(rev_header)
            .map_err(|e| CodegenError::Internal(format!("append branch: {:?}", e)))?;

        rev_acc_phi.add_incoming(&[(&rev_new_acc, rev_body)]);
        rev_list_phi.add_incoming(&[(&rev_tail, rev_body)]);

        // Reverse done, start fold
        self.builder().position_at_end(rev_exit);
        let reversed = rev_acc_phi.as_basic_value();
        self.builder()
            .build_unconditional_branch(fold_header)
            .map_err(|e| CodegenError::Internal(format!("append branch: {:?}", e)))?;

        // Fold loop header
        self.builder().position_at_end(fold_header);
        let fold_acc_phi = self
            .builder()
            .build_phi(ptr_type, "fold_acc")
            .map_err(|e| CodegenError::Internal(format!("append phi: {:?}", e)))?;
        let fold_list_phi = self
            .builder()
            .build_phi(ptr_type, "fold_list")
            .map_err(|e| CodegenError::Internal(format!("append phi: {:?}", e)))?;

        fold_acc_phi.add_incoming(&[(&list2, rev_exit)]);
        fold_list_phi.add_incoming(&[(&reversed, rev_exit)]);

        let fold_current = fold_list_phi.as_basic_value().into_pointer_value();
        let fold_tag = self.extract_adt_tag(fold_current)?;
        let fold_is_empty = self
            .builder()
            .build_int_compare(
                inkwell::IntPredicate::EQ,
                fold_tag,
                tm.i64_type().const_zero(),
                "fold_is_empty",
            )
            .map_err(|e| CodegenError::Internal(format!("append cmp: {:?}", e)))?;
        self.builder()
            .build_conditional_branch(fold_is_empty, fold_exit, fold_body)
            .map_err(|e| CodegenError::Internal(format!("append branch: {:?}", e)))?;

        // Fold loop body
        self.builder().position_at_end(fold_body);
        let fold_head = self.extract_adt_field(fold_current, 2, 0)?;
        let fold_tail = self.extract_adt_field(fold_current, 2, 1)?;
        let fold_new_acc = self.build_cons(fold_head.into(), fold_acc_phi.as_basic_value())?;
        self.builder()
            .build_unconditional_branch(fold_header)
            .map_err(|e| CodegenError::Internal(format!("append branch: {:?}", e)))?;

        fold_acc_phi.add_incoming(&[(&fold_new_acc, fold_body)]);
        fold_list_phi.add_incoming(&[(&fold_tail, fold_body)]);

        // Return result
        self.builder().position_at_end(fold_exit);
        self.builder()
            .build_return(Some(&fold_acc_phi.as_basic_value()))
            .map_err(|e| CodegenError::Internal(format!("append return: {:?}", e)))?;

        if let Some(bb) = saved_bb {
            self.builder().position_at_end(bb);
        }
        Ok(func)
    }

    // ========================================================================
    // ReaderT Operations
    // ========================================================================

    /// runReaderT m r = m(r)
    ///
    /// For nested transformers like `ReaderT r (StateT s IO)`, this returns
    /// a StateT closure instead of executing directly.
    fn lower_builtin_run_reader_t(
        &mut self,
        m_expr: &Expr,
        r_expr: &Expr,
    ) -> CodegenResult<Option<BasicValueEnum<'ctx>>> {
        // Check if this is a nested transformer
        let m_ty = m_expr.ty();
        let inner_monad = self.extract_inner_monad_from_reader_t(&m_ty);

        match inner_monad {
            Some(TransformerLayer::StateT) => {
                // ReaderT r (StateT s IO) - return a StateT closure
                self.lower_run_reader_t_over_state_t(m_expr, r_expr)
            }
            Some(TransformerLayer::IO) | None => {
                // ReaderT r IO - direct execution (current behavior)
                self.lower_run_reader_t_direct(m_expr, r_expr)
            }
            Some(_other) => Err(CodegenError::Internal(
                "runReaderT over non-IO/StateT inner monad not yet supported".to_string(),
            )),
        }
    }

    /// Direct runReaderT for ReaderT r IO - the current behavior.
    fn lower_run_reader_t_direct(
        &mut self,
        m_expr: &Expr,
        r_expr: &Expr,
    ) -> CodegenResult<Option<BasicValueEnum<'ctx>>> {
        self.push_transformer_layer(TransformerLayer::ReaderT);
        let m_val = self
            .lower_expr(m_expr)?
            .ok_or_else(|| CodegenError::Internal("runReaderT: m has no value".to_string()))?;
        self.pop_transformer_layer();
        let r_val = self
            .lower_expr(r_expr)?
            .ok_or_else(|| CodegenError::Internal("runReaderT: r has no value".to_string()))?;

        let m_ptr = m_val.into_pointer_value();
        let r_ptr = self.value_to_ptr(r_val)?;
        let fn_ptr = self.extract_closure_fn_ptr(m_ptr)?;

        let ptr_type = self.type_mapper().ptr_type();
        let fn_type = ptr_type.fn_type(&[ptr_type.into(), ptr_type.into()], false);
        let result = self
            .builder()
            .build_indirect_call(
                fn_type,
                fn_ptr,
                &[m_ptr.into(), r_ptr.into()],
                "run_reader_t",
            )
            .map_err(|e| CodegenError::Internal(format!("runReaderT call failed: {:?}", e)))?
            .try_as_basic_value()
            .basic()
            .ok_or_else(|| CodegenError::Internal("runReaderT: void".to_string()))?;
        Ok(Some(result))
    }

    /// runReaderT for ReaderT r (StateT s IO) - returns a StateT closure.
    ///
    /// The returned StateT closure takes (env, state) and internally calls m with 3 args:
    /// m(m, reader_env, state) -> (value, final_state)
    fn lower_run_reader_t_over_state_t(
        &mut self,
        m_expr: &Expr,
        r_expr: &Expr,
    ) -> CodegenResult<Option<BasicValueEnum<'ctx>>> {
        // Lower m — m is typically a top-level function reference whose body
        // is already lowered with the correct transformer stack by its own handler.
        // Do NOT push extra layers here — they would stack on top of the function's own
        // layers if m's body is lowered inline (forward reference).
        let m_val = self.lower_expr(m_expr)?.ok_or_else(|| {
            CodegenError::Internal("runReaderT/StateT: m has no value".to_string())
        })?;
        let r_val = self.lower_expr(r_expr)?.ok_or_else(|| {
            CodegenError::Internal("runReaderT/StateT: r has no value".to_string())
        })?;

        let ptr_type = self.type_mapper().ptr_type();
        let fn_name = "bhc_run_reader_t_over_state_t";

        let func = self.get_or_create_transformer_fn(fn_name);
        if func.count_basic_blocks() == 0 {
            let entry = self.llvm_ctx.append_basic_block(func, "entry");
            let saved_bb = self.builder().get_insert_block();
            self.builder().position_at_end(entry);

            // Parameters: (env, state) — this is a StateT closure
            // env contains: [m, reader_env]
            let env = func.get_nth_param(0).unwrap().into_pointer_value();
            let state = func.get_nth_param(1).unwrap();
            let m = self.extract_closure_env_elem(env, 2, 0)?;
            let reader_env = self.extract_closure_env_elem(env, 2, 1)?;

            // Call m with 3 args: m(m, reader_env, state) -> (value, final_state)
            let fn_type_3 =
                ptr_type.fn_type(&[ptr_type.into(), ptr_type.into(), ptr_type.into()], false);
            let m_fn = self.extract_closure_fn_ptr(m)?;
            let pair = self
                .builder()
                .build_indirect_call(
                    fn_type_3,
                    m_fn,
                    &[m.into(), reader_env.into(), state.into()],
                    "run_rt_st_result",
                )
                .map_err(|e| CodegenError::Internal(format!("runReaderT/StateT call m: {:?}", e)))?
                .try_as_basic_value()
                .basic()
                .ok_or_else(|| CodegenError::Internal("runReaderT/StateT m: void".to_string()))?;

            // Return the (value, final_state) pair directly — state is threaded through m
            self.builder().build_return(Some(&pair)).map_err(|e| {
                CodegenError::Internal(format!("runReaderT/StateT return: {:?}", e))
            })?;
            if let Some(bb) = saved_bb {
                self.builder().position_at_end(bb);
            }
        }

        // Create a StateT closure that captures m and reader_env
        let fn_ptr = func.as_global_value().as_pointer_value();
        let m_ptr = self.value_to_ptr(m_val)?;
        let r_ptr = self.value_to_ptr(r_val)?;
        let closure_ptr = self.alloc_closure(
            fn_ptr,
            &[
                (VarId::new(900000), m_ptr.into()),
                (VarId::new(900001), r_ptr.into()),
            ],
        )?;
        Ok(Some(closure_ptr.into()))
    }

    /// ask = closure \r -> r (returns env)
    ///
    /// When in StateT context (nested), ask is lifted:
    /// ask = closure \(env, s, reader_env) -> (reader_env, s)
    ///
    /// When in ReaderT-over-StateT context:
    /// ask = closure \(env, reader_env, state) -> (reader_env, state)
    fn lower_builtin_ask(&mut self) -> CodegenResult<Option<BasicValueEnum<'ctx>>> {
        let _ptr_type = self.type_mapper().ptr_type();
        // Check if we're in a ReaderT-over-StateT context
        if self.transformer_stack.is_reader_t_over_state_t() {
            // Nested: \(_env, reader_env, state) -> (reader_env, state)
            let fn_name = "bhc_ask_in_reader_t_over_state_t";
            let func = self.get_or_create_nested_transformer_fn(fn_name);
            if func.count_basic_blocks() == 0 {
                let entry = self.llvm_ctx.append_basic_block(func, "entry");
                let saved_bb = self.builder().get_insert_block();
                self.builder().position_at_end(entry);
                // param 0 = env, param 1 = reader_env, param 2 = state
                let reader_env = func.get_nth_param(1).unwrap();
                let state = func.get_nth_param(2).unwrap();
                let pair = self.alloc_pair(reader_env, state)?;
                self.builder().build_return(Some(&pair)).map_err(|e| {
                    CodegenError::Internal(format!("ask_rt_over_st return: {:?}", e))
                })?;
                if let Some(bb) = saved_bb {
                    self.builder().position_at_end(bb);
                }
            }
            let fn_ptr = func.as_global_value().as_pointer_value();
            let closure_ptr = self.alloc_closure(fn_ptr, &[])?;
            return Ok(Some(closure_ptr.into()));
        }

        // Check if we're in a StateT context - if so, lift ask
        if self.transformer_stack.is_state_t_over_reader_t() {
            // Lifted ask: produces a StateT closure that returns reader_env
            let fn_name = "bhc_lifted_ask_in_state_t";
            let func = self.get_or_create_nested_transformer_fn(fn_name);
            if func.count_basic_blocks() == 0 {
                let entry = self.llvm_ctx.append_basic_block(func, "entry");
                let saved_bb = self.builder().get_insert_block();
                self.builder().position_at_end(entry);
                // \(_env, s, reader_env) -> (reader_env, s)
                let s = func.get_nth_param(1).unwrap();
                let reader_env = func.get_nth_param(2).unwrap();
                let pair = self.alloc_pair(reader_env, s)?;
                self.builder()
                    .build_return(Some(&pair))
                    .map_err(|e| CodegenError::Internal(format!("lifted_ask return: {:?}", e)))?;
                if let Some(bb) = saved_bb {
                    self.builder().position_at_end(bb);
                }
            }
            let fn_ptr = func.as_global_value().as_pointer_value();
            let closure_ptr = self.alloc_closure(fn_ptr, &[])?;
            return Ok(Some(closure_ptr.into()));
        }

        // Normal ReaderT ask
        let fn_name = "bhc_reader_t_ask";
        let func = self.get_or_create_transformer_fn(fn_name);
        if func.count_basic_blocks() == 0 {
            let entry = self.llvm_ctx.append_basic_block(func, "entry");
            let saved_bb = self.builder().get_insert_block();
            self.builder().position_at_end(entry);
            // \(env, r) -> r
            let r = func.get_nth_param(1).unwrap();
            self.builder()
                .build_return(Some(&r))
                .map_err(|e| CodegenError::Internal(format!("ask return: {:?}", e)))?;
            if let Some(bb) = saved_bb {
                self.builder().position_at_end(bb);
            }
        }

        let fn_ptr = func.as_global_value().as_pointer_value();
        let closure_ptr = self.alloc_closure(fn_ptr, &[])?;
        Ok(Some(closure_ptr.into()))
    }

    /// asks f = closure \r -> f(r)
    fn lower_builtin_asks(&mut self, f_expr: &Expr) -> CodegenResult<Option<BasicValueEnum<'ctx>>> {
        let f_val = self
            .lower_expr(f_expr)?
            .ok_or_else(|| CodegenError::Internal("asks: f has no value".to_string()))?;

        let ptr_type = self.type_mapper().ptr_type();
        let fn_name = "bhc_reader_t_asks";

        let func = self.get_or_create_transformer_fn(fn_name);
        if func.count_basic_blocks() == 0 {
            let entry = self.llvm_ctx.append_basic_block(func, "entry");
            let saved_bb = self.builder().get_insert_block();
            self.builder().position_at_end(entry);
            // \(env, r) -> f(r) where f = env[0]
            let env = func.get_nth_param(0).unwrap().into_pointer_value();
            let r = func.get_nth_param(1).unwrap();
            let f = self.extract_closure_env_elem(env, 1, 0)?;
            let f_fn = self.extract_closure_fn_ptr(f)?;
            let r_ptr = self.value_to_ptr(r)?;
            let fn_type = ptr_type.fn_type(&[ptr_type.into(), ptr_type.into()], false);
            let result = self
                .builder()
                .build_indirect_call(fn_type, f_fn, &[f.into(), r_ptr.into()], "asks_result")
                .map_err(|e| CodegenError::Internal(format!("asks call: {:?}", e)))?
                .try_as_basic_value()
                .basic()
                .ok_or_else(|| CodegenError::Internal("asks: void".to_string()))?;
            self.builder()
                .build_return(Some(&result))
                .map_err(|e| CodegenError::Internal(format!("asks return: {:?}", e)))?;
            if let Some(bb) = saved_bb {
                self.builder().position_at_end(bb);
            }
        }

        let fn_ptr = func.as_global_value().as_pointer_value();
        let f_ptr = self.value_to_ptr(f_val)?;
        let closure_ptr = self.alloc_closure(fn_ptr, &[(VarId::new(900000), f_ptr.into())])?;
        Ok(Some(closure_ptr.into()))
    }

    /// local f m = closure \r -> m(f(r))
    fn lower_builtin_local(
        &mut self,
        f_expr: &Expr,
        m_expr: &Expr,
    ) -> CodegenResult<Option<BasicValueEnum<'ctx>>> {
        let f_val = self
            .lower_expr(f_expr)?
            .ok_or_else(|| CodegenError::Internal("local: f has no value".to_string()))?;
        let m_val = self
            .lower_expr(m_expr)?
            .ok_or_else(|| CodegenError::Internal("local: m has no value".to_string()))?;

        let ptr_type = self.type_mapper().ptr_type();
        let fn_name = "bhc_reader_t_local";

        let func = self.get_or_create_transformer_fn(fn_name);
        if func.count_basic_blocks() == 0 {
            let entry = self.llvm_ctx.append_basic_block(func, "entry");
            let saved_bb = self.builder().get_insert_block();
            self.builder().position_at_end(entry);
            // \(env, r) -> m(f(r)) where f = env[0], m = env[1]
            let env = func.get_nth_param(0).unwrap().into_pointer_value();
            let r = func.get_nth_param(1).unwrap();
            let f = self.extract_closure_env_elem(env, 2, 0)?;
            let m = self.extract_closure_env_elem(env, 2, 1)?;

            let fn_type = ptr_type.fn_type(&[ptr_type.into(), ptr_type.into()], false);

            // r' = f(r)
            let f_fn = self.extract_closure_fn_ptr(f)?;
            let r_ptr = self.value_to_ptr(r)?;
            let r_prime = self
                .builder()
                .build_indirect_call(fn_type, f_fn, &[f.into(), r_ptr.into()], "local_r")
                .map_err(|e| CodegenError::Internal(format!("local f call: {:?}", e)))?
                .try_as_basic_value()
                .basic()
                .ok_or_else(|| CodegenError::Internal("local f: void".to_string()))?;

            // result = m(r')
            let m_fn = self.extract_closure_fn_ptr(m)?;
            let result = self
                .builder()
                .build_indirect_call(fn_type, m_fn, &[m.into(), r_prime.into()], "local_result")
                .map_err(|e| CodegenError::Internal(format!("local m call: {:?}", e)))?
                .try_as_basic_value()
                .basic()
                .ok_or_else(|| CodegenError::Internal("local m: void".to_string()))?;

            self.builder()
                .build_return(Some(&result))
                .map_err(|e| CodegenError::Internal(format!("local return: {:?}", e)))?;
            if let Some(bb) = saved_bb {
                self.builder().position_at_end(bb);
            }
        }

        let fn_ptr = func.as_global_value().as_pointer_value();
        let f_ptr = self.value_to_ptr(f_val)?;
        let m_ptr = self.value_to_ptr(m_val)?;
        let closure_ptr = self.alloc_closure(
            fn_ptr,
            &[
                (VarId::new(900000), f_ptr.into()),
                (VarId::new(900001), m_ptr.into()),
            ],
        )?;
        Ok(Some(closure_ptr.into()))
    }

    /// ReaderT.pure x = closure \r -> x
    ///
    /// For RtOvSt: \(env, _r, s) -> (x, s) where x = env[0]
    fn lower_builtin_reader_t_pure(
        &mut self,
        x_expr: &Expr,
    ) -> CodegenResult<Option<BasicValueEnum<'ctx>>> {
        let x_val = self
            .lower_expr(x_expr)?
            .ok_or_else(|| CodegenError::Internal("ReaderT.pure: x has no value".to_string()))?;

        let _ptr_type = self.type_mapper().ptr_type();

        // ReaderT-over-StateT: return (x, s) pair
        if self.transformer_stack.is_reader_t_over_state_t() {
            let fn_name = "bhc_reader_t_pure_rt_over_st";
            let func = self.get_or_create_nested_transformer_fn(fn_name);
            if func.count_basic_blocks() == 0 {
                let entry = self.llvm_ctx.append_basic_block(func, "entry");
                let saved_bb = self.builder().get_insert_block();
                self.builder().position_at_end(entry);
                // \(env, _r, s) -> (x, s) where x = env[0]
                let env = func.get_nth_param(0).unwrap().into_pointer_value();
                let s = func.get_nth_param(2).unwrap();
                let x = self.extract_closure_env_elem(env, 1, 0)?;
                let pair = self.alloc_pair(x.into(), s)?;
                self.builder().build_return(Some(&pair)).map_err(|e| {
                    CodegenError::Internal(format!("reader_t_pure_rt_over_st return: {:?}", e))
                })?;
                if let Some(bb) = saved_bb {
                    self.builder().position_at_end(bb);
                }
            }
            let fn_ptr = func.as_global_value().as_pointer_value();
            let x_ptr = self.value_to_ptr(x_val)?;
            let closure_ptr = self.alloc_closure(fn_ptr, &[(VarId::new(900000), x_ptr.into())])?;
            return Ok(Some(closure_ptr.into()));
        }

        let fn_name = "bhc_reader_t_pure";

        let func = self.get_or_create_transformer_fn(fn_name);
        if func.count_basic_blocks() == 0 {
            let entry = self.llvm_ctx.append_basic_block(func, "entry");
            let saved_bb = self.builder().get_insert_block();
            self.builder().position_at_end(entry);
            // \(env, _r) -> x where x = env[0]
            let env = func.get_nth_param(0).unwrap().into_pointer_value();
            let x = self.extract_closure_env_elem(env, 1, 0)?;
            self.builder()
                .build_return(Some(&x))
                .map_err(|e| CodegenError::Internal(format!("reader_t_pure return: {:?}", e)))?;
            if let Some(bb) = saved_bb {
                self.builder().position_at_end(bb);
            }
        }

        let fn_ptr = func.as_global_value().as_pointer_value();
        let x_ptr = self.value_to_ptr(x_val)?;
        let closure_ptr = self.alloc_closure(fn_ptr, &[(VarId::new(900000), x_ptr.into())])?;
        Ok(Some(closure_ptr.into()))
    }

    /// ReaderT.>>= m k = closure \r -> let a = m(r) in k(a)(r)
    ///
    /// For RtOvSt: \(env, r, s) ->
    ///   let (a, s') = m(m, r, s)
    ///   let kr = k(k, a)
    ///   kr(kr, r, s')
    fn lower_builtin_reader_t_bind(
        &mut self,
        m_expr: &Expr,
        k_expr: &Expr,
    ) -> CodegenResult<Option<BasicValueEnum<'ctx>>> {
        let m_val = self
            .lower_expr(m_expr)?
            .ok_or_else(|| CodegenError::Internal("ReaderT.>>=: m has no value".to_string()))?;
        let k_val = self
            .lower_expr(k_expr)?
            .ok_or_else(|| CodegenError::Internal("ReaderT.>>=: k has no value".to_string()))?;

        let ptr_type = self.type_mapper().ptr_type();

        // ReaderT-over-StateT: 3-arg closures threading state
        if self.transformer_stack.is_reader_t_over_state_t() {
            let fn_name = "bhc_reader_t_bind_rt_over_st";
            let func = self.get_or_create_nested_transformer_fn(fn_name);
            if func.count_basic_blocks() == 0 {
                let entry = self.llvm_ctx.append_basic_block(func, "entry");
                let saved_bb = self.builder().get_insert_block();
                self.builder().position_at_end(entry);
                // \(env, r, s) -> ...
                let env = func.get_nth_param(0).unwrap().into_pointer_value();
                let r = func.get_nth_param(1).unwrap();
                let s = func.get_nth_param(2).unwrap();
                let m = self.extract_closure_env_elem(env, 2, 0)?;
                let k = self.extract_closure_env_elem(env, 2, 1)?;

                let fn_type_3 =
                    ptr_type.fn_type(&[ptr_type.into(), ptr_type.into(), ptr_type.into()], false);
                let fn_type_2 = ptr_type.fn_type(&[ptr_type.into(), ptr_type.into()], false);

                // (a, s') = m(m, r, s)
                let m_fn = self.extract_closure_fn_ptr(m)?;
                let pair = self
                    .builder()
                    .build_indirect_call(
                        fn_type_3,
                        m_fn,
                        &[m.into(), r.into(), s.into()],
                        "rt_st_bind_pair",
                    )
                    .map_err(|e| CodegenError::Internal(format!("ReaderT/StateT bind m: {:?}", e)))?
                    .try_as_basic_value()
                    .basic()
                    .ok_or_else(|| {
                        CodegenError::Internal("ReaderT/StateT bind m: void".to_string())
                    })?
                    .into_pointer_value();

                let a = self.extract_pair_fst(pair)?;
                let s_prime = self.extract_pair_snd(pair)?;

                // kr = k(k, a) — produces a new 3-arg closure
                let k_fn = self.extract_closure_fn_ptr(k)?;
                let kr = self
                    .builder()
                    .build_indirect_call(fn_type_2, k_fn, &[k.into(), a.into()], "rt_st_bind_kr")
                    .map_err(|e| CodegenError::Internal(format!("ReaderT/StateT bind k: {:?}", e)))?
                    .try_as_basic_value()
                    .basic()
                    .ok_or_else(|| {
                        CodegenError::Internal("ReaderT/StateT bind k: void".to_string())
                    })?;

                // result = kr(kr, r, s')
                let kr_ptr = kr.into_pointer_value();
                let kr_fn = self.extract_closure_fn_ptr(kr_ptr)?;
                let result = self
                    .builder()
                    .build_indirect_call(
                        fn_type_3,
                        kr_fn,
                        &[kr_ptr.into(), r.into(), s_prime.into()],
                        "rt_st_bind_result",
                    )
                    .map_err(|e| {
                        CodegenError::Internal(format!("ReaderT/StateT bind kr: {:?}", e))
                    })?
                    .try_as_basic_value()
                    .basic()
                    .ok_or_else(|| {
                        CodegenError::Internal("ReaderT/StateT bind kr: void".to_string())
                    })?;

                self.builder().build_return(Some(&result)).map_err(|e| {
                    CodegenError::Internal(format!("reader_t_bind_rt_over_st return: {:?}", e))
                })?;
                if let Some(bb) = saved_bb {
                    self.builder().position_at_end(bb);
                }
            }

            let fn_ptr = func.as_global_value().as_pointer_value();
            let m_ptr = self.value_to_ptr(m_val)?;
            let k_ptr = self.value_to_ptr(k_val)?;
            let closure_ptr = self.alloc_closure(
                fn_ptr,
                &[
                    (VarId::new(900000), m_ptr.into()),
                    (VarId::new(900001), k_ptr.into()),
                ],
            )?;
            return Ok(Some(closure_ptr.into()));
        }

        let fn_name = "bhc_reader_t_bind";

        let func = self.get_or_create_transformer_fn(fn_name);
        if func.count_basic_blocks() == 0 {
            let entry = self.llvm_ctx.append_basic_block(func, "entry");
            let saved_bb = self.builder().get_insert_block();
            self.builder().position_at_end(entry);
            // \(env, r) -> let a = m(r); kr = k(a); kr(r)
            // where m = env[0], k = env[1]
            let env = func.get_nth_param(0).unwrap().into_pointer_value();
            let r = func.get_nth_param(1).unwrap();
            let m = self.extract_closure_env_elem(env, 2, 0)?;
            let k = self.extract_closure_env_elem(env, 2, 1)?;

            let fn_type = ptr_type.fn_type(&[ptr_type.into(), ptr_type.into()], false);

            // a = m(r)
            let m_fn = self.extract_closure_fn_ptr(m)?;
            let a = self
                .builder()
                .build_indirect_call(fn_type, m_fn, &[m.into(), r.into()], "bind_a")
                .map_err(|e| CodegenError::Internal(format!("ReaderT bind m: {:?}", e)))?
                .try_as_basic_value()
                .basic()
                .ok_or_else(|| CodegenError::Internal("ReaderT bind m: void".to_string()))?;

            // kr = k(a)
            let k_fn = self.extract_closure_fn_ptr(k)?;
            let kr = self
                .builder()
                .build_indirect_call(fn_type, k_fn, &[k.into(), a.into()], "bind_kr")
                .map_err(|e| CodegenError::Internal(format!("ReaderT bind k: {:?}", e)))?
                .try_as_basic_value()
                .basic()
                .ok_or_else(|| CodegenError::Internal("ReaderT bind k: void".to_string()))?;

            // result = kr(r)
            let kr_ptr = kr.into_pointer_value();
            let kr_fn = self.extract_closure_fn_ptr(kr_ptr)?;
            let result = self
                .builder()
                .build_indirect_call(fn_type, kr_fn, &[kr_ptr.into(), r.into()], "bind_result")
                .map_err(|e| CodegenError::Internal(format!("ReaderT bind kr: {:?}", e)))?
                .try_as_basic_value()
                .basic()
                .ok_or_else(|| CodegenError::Internal("ReaderT bind kr: void".to_string()))?;

            self.builder()
                .build_return(Some(&result))
                .map_err(|e| CodegenError::Internal(format!("reader_t_bind return: {:?}", e)))?;
            if let Some(bb) = saved_bb {
                self.builder().position_at_end(bb);
            }
        }

        let fn_ptr = func.as_global_value().as_pointer_value();
        let m_ptr = self.value_to_ptr(m_val)?;
        let k_ptr = self.value_to_ptr(k_val)?;
        let closure_ptr = self.alloc_closure(
            fn_ptr,
            &[
                (VarId::new(900000), m_ptr.into()),
                (VarId::new(900001), k_ptr.into()),
            ],
        )?;
        Ok(Some(closure_ptr.into()))
    }

    /// ReaderT.>> m1 m2 = closure \r -> m1(r); m2(r)
    ///
    /// For RtOvSt: \(env, r, s) ->
    ///   let (_, s') = m1(m1, r, s)
    ///   m2(m2, r, s')
    fn lower_builtin_reader_t_then(
        &mut self,
        m1_expr: &Expr,
        m2_expr: &Expr,
    ) -> CodegenResult<Option<BasicValueEnum<'ctx>>> {
        let m1_val = self
            .lower_expr(m1_expr)?
            .ok_or_else(|| CodegenError::Internal("ReaderT.>>: m1 has no value".to_string()))?;
        let m2_val = self
            .lower_expr(m2_expr)?
            .ok_or_else(|| CodegenError::Internal("ReaderT.>>: m2 has no value".to_string()))?;

        let ptr_type = self.type_mapper().ptr_type();

        // ReaderT-over-StateT: 3-arg closures threading state
        if self.transformer_stack.is_reader_t_over_state_t() {
            let fn_name = "bhc_reader_t_then_rt_over_st";
            let func = self.get_or_create_nested_transformer_fn(fn_name);
            if func.count_basic_blocks() == 0 {
                let entry = self.llvm_ctx.append_basic_block(func, "entry");
                let saved_bb = self.builder().get_insert_block();
                self.builder().position_at_end(entry);
                // \(env, r, s) -> ...
                let env = func.get_nth_param(0).unwrap().into_pointer_value();
                let r = func.get_nth_param(1).unwrap();
                let s = func.get_nth_param(2).unwrap();
                let m1 = self.extract_closure_env_elem(env, 2, 0)?;
                let m2 = self.extract_closure_env_elem(env, 2, 1)?;

                let fn_type_3 =
                    ptr_type.fn_type(&[ptr_type.into(), ptr_type.into(), ptr_type.into()], false);

                // (_, s') = m1(m1, r, s)
                let m1_fn = self.extract_closure_fn_ptr(m1)?;
                let pair1 = self
                    .builder()
                    .build_indirect_call(
                        fn_type_3,
                        m1_fn,
                        &[m1.into(), r.into(), s.into()],
                        "rt_st_then_pair",
                    )
                    .map_err(|e| {
                        CodegenError::Internal(format!("ReaderT/StateT then m1: {:?}", e))
                    })?
                    .try_as_basic_value()
                    .basic()
                    .ok_or_else(|| {
                        CodegenError::Internal("ReaderT/StateT then m1: void".to_string())
                    })?
                    .into_pointer_value();

                let s_prime = self.extract_pair_snd(pair1)?;

                // result = m2(m2, r, s')
                let m2_fn = self.extract_closure_fn_ptr(m2)?;
                let result = self
                    .builder()
                    .build_indirect_call(
                        fn_type_3,
                        m2_fn,
                        &[m2.into(), r.into(), s_prime.into()],
                        "rt_st_then_result",
                    )
                    .map_err(|e| {
                        CodegenError::Internal(format!("ReaderT/StateT then m2: {:?}", e))
                    })?
                    .try_as_basic_value()
                    .basic()
                    .ok_or_else(|| {
                        CodegenError::Internal("ReaderT/StateT then m2: void".to_string())
                    })?;

                self.builder().build_return(Some(&result)).map_err(|e| {
                    CodegenError::Internal(format!("reader_t_then_rt_over_st return: {:?}", e))
                })?;
                if let Some(bb) = saved_bb {
                    self.builder().position_at_end(bb);
                }
            }

            let fn_ptr = func.as_global_value().as_pointer_value();
            let m1_ptr = self.value_to_ptr(m1_val)?;
            let m2_ptr = self.value_to_ptr(m2_val)?;
            let closure_ptr = self.alloc_closure(
                fn_ptr,
                &[
                    (VarId::new(900000), m1_ptr.into()),
                    (VarId::new(900001), m2_ptr.into()),
                ],
            )?;
            return Ok(Some(closure_ptr.into()));
        }

        let fn_name = "bhc_reader_t_then";

        let func = self.get_or_create_transformer_fn(fn_name);
        if func.count_basic_blocks() == 0 {
            let entry = self.llvm_ctx.append_basic_block(func, "entry");
            let saved_bb = self.builder().get_insert_block();
            self.builder().position_at_end(entry);
            // \(env, r) -> m1(r); m2(r) where m1 = env[0], m2 = env[1]
            let env = func.get_nth_param(0).unwrap().into_pointer_value();
            let r = func.get_nth_param(1).unwrap();
            let m1 = self.extract_closure_env_elem(env, 2, 0)?;
            let m2 = self.extract_closure_env_elem(env, 2, 1)?;

            let fn_type = ptr_type.fn_type(&[ptr_type.into(), ptr_type.into()], false);

            // m1(r) - discard result
            let m1_fn = self.extract_closure_fn_ptr(m1)?;
            self.builder()
                .build_indirect_call(fn_type, m1_fn, &[m1.into(), r.into()], "then_m1")
                .map_err(|e| CodegenError::Internal(format!("ReaderT then m1: {:?}", e)))?;

            // result = m2(r)
            let m2_fn = self.extract_closure_fn_ptr(m2)?;
            let result = self
                .builder()
                .build_indirect_call(fn_type, m2_fn, &[m2.into(), r.into()], "then_result")
                .map_err(|e| CodegenError::Internal(format!("ReaderT then m2: {:?}", e)))?
                .try_as_basic_value()
                .basic()
                .ok_or_else(|| CodegenError::Internal("ReaderT then m2: void".to_string()))?;

            self.builder()
                .build_return(Some(&result))
                .map_err(|e| CodegenError::Internal(format!("reader_t_then return: {:?}", e)))?;
            if let Some(bb) = saved_bb {
                self.builder().position_at_end(bb);
            }
        }

        let fn_ptr = func.as_global_value().as_pointer_value();
        let m1_ptr = self.value_to_ptr(m1_val)?;
        let m2_ptr = self.value_to_ptr(m2_val)?;
        let closure_ptr = self.alloc_closure(
            fn_ptr,
            &[
                (VarId::new(900000), m1_ptr.into()),
                (VarId::new(900001), m2_ptr.into()),
            ],
        )?;
        Ok(Some(closure_ptr.into()))
    }

    /// ReaderT.fmap f m = closure \r -> f(m(r))
    fn lower_builtin_reader_t_fmap(
        &mut self,
        f_expr: &Expr,
        m_expr: &Expr,
    ) -> CodegenResult<Option<BasicValueEnum<'ctx>>> {
        let f_val = self
            .lower_expr(f_expr)?
            .ok_or_else(|| CodegenError::Internal("ReaderT.fmap: f has no value".to_string()))?;
        let m_val = self
            .lower_expr(m_expr)?
            .ok_or_else(|| CodegenError::Internal("ReaderT.fmap: m has no value".to_string()))?;

        let ptr_type = self.type_mapper().ptr_type();
        let fn_name = "bhc_reader_t_fmap";

        let func = self.get_or_create_transformer_fn(fn_name);
        if func.count_basic_blocks() == 0 {
            let entry = self.llvm_ctx.append_basic_block(func, "entry");
            let saved_bb = self.builder().get_insert_block();
            self.builder().position_at_end(entry);
            let env = func.get_nth_param(0).unwrap().into_pointer_value();
            let r = func.get_nth_param(1).unwrap();
            let f = self.extract_closure_env_elem(env, 2, 0)?;
            let m = self.extract_closure_env_elem(env, 2, 1)?;

            let fn_type = ptr_type.fn_type(&[ptr_type.into(), ptr_type.into()], false);

            // a = m(r)
            let m_fn = self.extract_closure_fn_ptr(m)?;
            let a = self
                .builder()
                .build_indirect_call(fn_type, m_fn, &[m.into(), r.into()], "fmap_a")
                .map_err(|e| CodegenError::Internal(format!("ReaderT fmap m: {:?}", e)))?
                .try_as_basic_value()
                .basic()
                .ok_or_else(|| CodegenError::Internal("ReaderT fmap m: void".to_string()))?;

            // result = f(a)
            let f_fn = self.extract_closure_fn_ptr(f)?;
            let result = self
                .builder()
                .build_indirect_call(fn_type, f_fn, &[f.into(), a.into()], "fmap_result")
                .map_err(|e| CodegenError::Internal(format!("ReaderT fmap f: {:?}", e)))?
                .try_as_basic_value()
                .basic()
                .ok_or_else(|| CodegenError::Internal("ReaderT fmap f: void".to_string()))?;

            self.builder()
                .build_return(Some(&result))
                .map_err(|e| CodegenError::Internal(format!("reader_t_fmap return: {:?}", e)))?;
            if let Some(bb) = saved_bb {
                self.builder().position_at_end(bb);
            }
        }

        let fn_ptr = func.as_global_value().as_pointer_value();
        let f_ptr = self.value_to_ptr(f_val)?;
        let m_ptr = self.value_to_ptr(m_val)?;
        let closure_ptr = self.alloc_closure(
            fn_ptr,
            &[
                (VarId::new(900000), f_ptr.into()),
                (VarId::new(900001), m_ptr.into()),
            ],
        )?;
        Ok(Some(closure_ptr.into()))
    }

    /// ReaderT.<*> mf mx = closure \r -> mf(r)(mx(r))
    fn lower_builtin_reader_t_ap(
        &mut self,
        mf_expr: &Expr,
        mx_expr: &Expr,
    ) -> CodegenResult<Option<BasicValueEnum<'ctx>>> {
        // Same structure as fmap but applied differently
        // For simplicity, implement as: mf >>= \f -> fmap f mx
        // Which compiles to same closure pattern
        let mf_val = self
            .lower_expr(mf_expr)?
            .ok_or_else(|| CodegenError::Internal("ReaderT.<*>: mf has no value".to_string()))?;
        let mx_val = self
            .lower_expr(mx_expr)?
            .ok_or_else(|| CodegenError::Internal("ReaderT.<*>: mx has no value".to_string()))?;

        let ptr_type = self.type_mapper().ptr_type();
        let fn_name = "bhc_reader_t_ap";

        let func = self.get_or_create_transformer_fn(fn_name);
        if func.count_basic_blocks() == 0 {
            let entry = self.llvm_ctx.append_basic_block(func, "entry");
            let saved_bb = self.builder().get_insert_block();
            self.builder().position_at_end(entry);
            let env = func.get_nth_param(0).unwrap().into_pointer_value();
            let r = func.get_nth_param(1).unwrap();
            let mf = self.extract_closure_env_elem(env, 2, 0)?;
            let mx = self.extract_closure_env_elem(env, 2, 1)?;

            let fn_type = ptr_type.fn_type(&[ptr_type.into(), ptr_type.into()], false);

            // f = mf(r)
            let mf_fn = self.extract_closure_fn_ptr(mf)?;
            let f = self
                .builder()
                .build_indirect_call(fn_type, mf_fn, &[mf.into(), r.into()], "ap_f")
                .map_err(|e| CodegenError::Internal(format!("ReaderT ap mf: {:?}", e)))?
                .try_as_basic_value()
                .basic()
                .ok_or_else(|| CodegenError::Internal("ReaderT ap mf: void".to_string()))?;

            // x = mx(r)
            let mx_fn = self.extract_closure_fn_ptr(mx)?;
            let x = self
                .builder()
                .build_indirect_call(fn_type, mx_fn, &[mx.into(), r.into()], "ap_x")
                .map_err(|e| CodegenError::Internal(format!("ReaderT ap mx: {:?}", e)))?
                .try_as_basic_value()
                .basic()
                .ok_or_else(|| CodegenError::Internal("ReaderT ap mx: void".to_string()))?;

            // result = f(x)
            let f_ptr = f.into_pointer_value();
            let f_fn = self.extract_closure_fn_ptr(f_ptr)?;
            let result = self
                .builder()
                .build_indirect_call(fn_type, f_fn, &[f_ptr.into(), x.into()], "ap_result")
                .map_err(|e| CodegenError::Internal(format!("ReaderT ap f: {:?}", e)))?
                .try_as_basic_value()
                .basic()
                .ok_or_else(|| CodegenError::Internal("ReaderT ap f: void".to_string()))?;

            self.builder()
                .build_return(Some(&result))
                .map_err(|e| CodegenError::Internal(format!("reader_t_ap return: {:?}", e)))?;
            if let Some(bb) = saved_bb {
                self.builder().position_at_end(bb);
            }
        }

        let fn_ptr = func.as_global_value().as_pointer_value();
        let mf_ptr = self.value_to_ptr(mf_val)?;
        let mx_ptr = self.value_to_ptr(mx_val)?;
        let closure_ptr = self.alloc_closure(
            fn_ptr,
            &[
                (VarId::new(900000), mf_ptr.into()),
                (VarId::new(900001), mx_ptr.into()),
            ],
        )?;
        Ok(Some(closure_ptr.into()))
    }

    /// ReaderT.lift action = closure \r -> action (ignore r, return action)
    fn lower_builtin_reader_t_lift(
        &mut self,
        action_expr: &Expr,
    ) -> CodegenResult<Option<BasicValueEnum<'ctx>>> {
        let action_val = self.lower_expr(action_expr)?.ok_or_else(|| {
            CodegenError::Internal("ReaderT.lift: action has no value".to_string())
        })?;

        let _ptr_type = self.type_mapper().ptr_type();
        let fn_name = "bhc_reader_t_lift";

        let func = self.get_or_create_transformer_fn(fn_name);
        if func.count_basic_blocks() == 0 {
            let entry = self.llvm_ctx.append_basic_block(func, "entry");
            let saved_bb = self.builder().get_insert_block();
            self.builder().position_at_end(entry);
            // \(env, _r) -> action where action = env[0]
            let env = func.get_nth_param(0).unwrap().into_pointer_value();
            let action = self.extract_closure_env_elem(env, 1, 0)?;
            self.builder()
                .build_return(Some(&action))
                .map_err(|e| CodegenError::Internal(format!("reader_t_lift return: {:?}", e)))?;
            if let Some(bb) = saved_bb {
                self.builder().position_at_end(bb);
            }
        }

        let fn_ptr = func.as_global_value().as_pointer_value();
        let action_ptr = self.value_to_ptr(action_val)?;
        let closure_ptr = self.alloc_closure(fn_ptr, &[(VarId::new(900000), action_ptr.into())])?;
        Ok(Some(closure_ptr.into()))
    }

    // ========================================================================
    // StateT Operations
    // ========================================================================

    /// runStateT m s = m(s)
    ///
    /// For nested transformers like `StateT s (ReaderT r IO)`, this returns
    /// a ReaderT closure that produces `(a, s)` instead of executing directly.
    fn lower_builtin_run_state_t(
        &mut self,
        m_expr: &Expr,
        s_expr: &Expr,
    ) -> CodegenResult<Option<BasicValueEnum<'ctx>>> {
        // Check if this is a nested transformer
        let m_ty = m_expr.ty();
        let inner_monad = self.extract_inner_monad_from_state_t(&m_ty);

        match inner_monad {
            Some(TransformerLayer::ReaderT) => {
                // StateT s (ReaderT r IO) - return a ReaderT closure that produces (a, s)
                self.lower_run_state_t_over_reader_t(m_expr, s_expr)
            }
            Some(TransformerLayer::IO) | None => {
                // StateT s IO - direct execution
                self.lower_run_state_t_direct(m_expr, s_expr)
            }
            Some(_other) => Err(CodegenError::Internal(
                "runStateT over non-IO/ReaderT inner monad not yet supported".to_string(),
            )),
        }
    }

    /// Direct runStateT for StateT s IO - the current behavior.
    fn lower_run_state_t_direct(
        &mut self,
        m_expr: &Expr,
        s_expr: &Expr,
    ) -> CodegenResult<Option<BasicValueEnum<'ctx>>> {
        self.push_transformer_layer(TransformerLayer::StateT);
        let m_val = self
            .lower_expr(m_expr)?
            .ok_or_else(|| CodegenError::Internal("runStateT: m has no value".to_string()))?;
        self.pop_transformer_layer();
        let s_val = self
            .lower_expr(s_expr)?
            .ok_or_else(|| CodegenError::Internal("runStateT: s has no value".to_string()))?;

        let m_ptr = m_val.into_pointer_value();
        let s_ptr = self.value_to_ptr(s_val)?;
        let fn_ptr = self.extract_closure_fn_ptr(m_ptr)?;

        let ptr_type = self.type_mapper().ptr_type();
        let fn_type = ptr_type.fn_type(&[ptr_type.into(), ptr_type.into()], false);
        let result = self
            .builder()
            .build_indirect_call(
                fn_type,
                fn_ptr,
                &[m_ptr.into(), s_ptr.into()],
                "run_state_t",
            )
            .map_err(|e| CodegenError::Internal(format!("runStateT call failed: {:?}", e)))?
            .try_as_basic_value()
            .basic()
            .ok_or_else(|| CodegenError::Internal("runStateT: void".to_string()))?;
        Ok(Some(result))
    }

    /// runStateT for StateT s (ReaderT r IO) - returns a ReaderT closure that produces (a, s).
    fn lower_run_state_t_over_reader_t(
        &mut self,
        m_expr: &Expr,
        s_expr: &Expr,
    ) -> CodegenResult<Option<BasicValueEnum<'ctx>>> {
        self.push_transformer_layer(TransformerLayer::StateT);
        let m_val = self.lower_expr(m_expr)?.ok_or_else(|| {
            CodegenError::Internal("runStateT/ReaderT: m has no value".to_string())
        })?;
        self.pop_transformer_layer();
        let s_val = self.lower_expr(s_expr)?.ok_or_else(|| {
            CodegenError::Internal("runStateT/ReaderT: s has no value".to_string())
        })?;

        let ptr_type = self.type_mapper().ptr_type();
        let fn_name = "bhc_run_state_t_over_reader_t";

        let func = self.get_or_create_transformer_fn(fn_name);
        if func.count_basic_blocks() == 0 {
            let entry = self.llvm_ctx.append_basic_block(func, "entry");
            let saved_bb = self.builder().get_insert_block();
            self.builder().position_at_end(entry);

            // Parameters: (env, reader_env)
            // env contains: [m, init_state]
            let env = func.get_nth_param(0).unwrap().into_pointer_value();
            let reader_env = func.get_nth_param(1).unwrap();
            let m = self.extract_closure_env_elem(env, 2, 0)?;
            let init_state = self.extract_closure_env_elem(env, 2, 1)?;

            // Call m with state - returns (result, final_state)
            let fn_type = ptr_type.fn_type(&[ptr_type.into(), ptr_type.into()], false);
            let m_fn = self.extract_closure_fn_ptr(m)?;
            let pair = self
                .builder()
                .build_indirect_call(fn_type, m_fn, &[m.into(), init_state.into()], "run_st_pair")
                .map_err(|e| CodegenError::Internal(format!("runStateT/ReaderT call m: {:?}", e)))?
                .try_as_basic_value()
                .basic()
                .ok_or_else(|| CodegenError::Internal("runStateT/ReaderT m: void".to_string()))?;

            // Return the pair (reader_env unused for pure StateT computations)
            let _ = reader_env;
            self.builder().build_return(Some(&pair)).map_err(|e| {
                CodegenError::Internal(format!("runStateT/ReaderT return: {:?}", e))
            })?;
            if let Some(bb) = saved_bb {
                self.builder().position_at_end(bb);
            }
        }

        // Create a ReaderT closure that captures m and init_state
        let fn_ptr = func.as_global_value().as_pointer_value();
        let m_ptr = self.value_to_ptr(m_val)?;
        let s_ptr = self.value_to_ptr(s_val)?;
        let closure_ptr = self.alloc_closure(
            fn_ptr,
            &[
                (VarId::new(900000), m_ptr.into()),
                (VarId::new(900001), s_ptr.into()),
            ],
        )?;
        Ok(Some(closure_ptr.into()))
    }

    /// Helper: allocate a pair (a, s) as a 2-element ADT (tag=0, field0=a, field1=s)
    fn alloc_pair(
        &mut self,
        fst_val: BasicValueEnum<'ctx>,
        snd_val: BasicValueEnum<'ctx>,
    ) -> CodegenResult<PointerValue<'ctx>> {
        let adt_ty = self.adt_type(2);
        let i64_type = self.type_mapper().i64_type();

        // Allocate: tag(i64) + 2 fields (ptr each) = 24 bytes
        let alloc_fn = *self
            .functions
            .get(&VarId::new(1000005))
            .ok_or_else(|| CodegenError::Internal("bhc_alloc not declared".to_string()))?;
        let size = i64_type.const_int(24, false);
        let raw = self
            .builder()
            .build_call(alloc_fn, &[size.into()], "pair_alloc")
            .map_err(|e| CodegenError::Internal(format!("pair alloc: {:?}", e)))?
            .try_as_basic_value()
            .basic()
            .ok_or_else(|| CodegenError::Internal("pair alloc: void".to_string()))?
            .into_pointer_value();

        // Tag = 0 (tuple constructor)
        let tag_ptr = self
            .builder()
            .build_struct_gep(adt_ty, raw, 0, "pair_tag")
            .map_err(|e| CodegenError::Internal(format!("pair tag gep: {:?}", e)))?;
        self.builder()
            .build_store(tag_ptr, i64_type.const_zero())
            .map_err(|e| CodegenError::Internal(format!("pair tag store: {:?}", e)))?;

        // Field 0 - use store_adt_field pattern
        self.store_adt_field(raw, 2, 0, fst_val)?;

        // Field 1
        self.store_adt_field(raw, 2, 1, snd_val)?;

        Ok(raw)
    }

    /// Helper: extract fst from pair (tag + 2 fields)
    fn extract_pair_fst(&self, pair: PointerValue<'ctx>) -> CodegenResult<PointerValue<'ctx>> {
        self.extract_adt_field(pair, 2, 0)
    }

    /// Helper: extract snd from pair (tag + 2 fields)
    fn extract_pair_snd(&self, pair: PointerValue<'ctx>) -> CodegenResult<PointerValue<'ctx>> {
        self.extract_adt_field(pair, 2, 1)
    }

    /// get = closure \s -> (s, s)
    ///
    /// For nested transformers (StOvRt): \(_env, s, _reader_env) -> (s, s)
    /// For nested transformers (RtOvSt): \(_env, _reader_env, s) -> (s, s)
    fn lower_builtin_get(&mut self) -> CodegenResult<Option<BasicValueEnum<'ctx>>> {
        let _ptr_type = self.type_mapper().ptr_type();

        // ReaderT-over-StateT: state is at param 2
        if self.transformer_stack.is_reader_t_over_state_t() {
            let fn_name = "bhc_state_t_get_rt_over_st";
            let func = self.get_or_create_nested_transformer_fn(fn_name);
            if func.count_basic_blocks() == 0 {
                let entry = self.llvm_ctx.append_basic_block(func, "entry");
                let saved_bb = self.builder().get_insert_block();
                self.builder().position_at_end(entry);
                // \(_env, _reader_env, s) -> (s, s)
                let s = func.get_nth_param(2).unwrap();
                let pair = self.alloc_pair(s, s)?;
                self.builder().build_return(Some(&pair)).map_err(|e| {
                    CodegenError::Internal(format!("get_rt_over_st return: {:?}", e))
                })?;
                if let Some(bb) = saved_bb {
                    self.builder().position_at_end(bb);
                }
            }
            let fn_ptr = func.as_global_value().as_pointer_value();
            let closure_ptr = self.alloc_closure(fn_ptr, &[])?;
            return Ok(Some(closure_ptr.into()));
        }

        let is_nested = self.transformer_stack.is_state_t_over_reader_t();
        let fn_name = if is_nested {
            "bhc_state_t_get_nested"
        } else {
            "bhc_state_t_get"
        };

        let func = if is_nested {
            self.get_or_create_nested_transformer_fn(fn_name)
        } else {
            self.get_or_create_transformer_fn(fn_name)
        };
        if func.count_basic_blocks() == 0 {
            let entry = self.llvm_ctx.append_basic_block(func, "entry");
            let saved_bb = self.builder().get_insert_block();
            self.builder().position_at_end(entry);
            // Both: \(_env, s, [_reader_env]) -> (s, s)
            let s = func.get_nth_param(1).unwrap();
            let pair = self.alloc_pair(s, s)?;
            self.builder()
                .build_return(Some(&pair))
                .map_err(|e| CodegenError::Internal(format!("state_t_get return: {:?}", e)))?;
            if let Some(bb) = saved_bb {
                self.builder().position_at_end(bb);
            }
        }

        let fn_ptr = func.as_global_value().as_pointer_value();
        let closure_ptr = self.alloc_closure(fn_ptr, &[])?;
        Ok(Some(closure_ptr.into()))
    }

    /// put s' = closure \_ -> ((), s')
    ///
    /// For RtOvSt: \(env, _reader_env, _s) -> ((), s') where s' = env[0]
    fn lower_builtin_put(&mut self, s_expr: &Expr) -> CodegenResult<Option<BasicValueEnum<'ctx>>> {
        let s_val = self
            .lower_expr(s_expr)?
            .ok_or_else(|| CodegenError::Internal("put: s has no value".to_string()))?;

        let ptr_type = self.type_mapper().ptr_type();

        // ReaderT-over-StateT: 3-arg closure
        if self.transformer_stack.is_reader_t_over_state_t() {
            let fn_name = "bhc_state_t_put_rt_over_st";
            let func = self.get_or_create_nested_transformer_fn(fn_name);
            if func.count_basic_blocks() == 0 {
                let entry = self.llvm_ctx.append_basic_block(func, "entry");
                let saved_bb = self.builder().get_insert_block();
                self.builder().position_at_end(entry);
                // \(env, _reader_env, _s) -> ((), s') where s' = env[0]
                let env = func.get_nth_param(0).unwrap().into_pointer_value();
                let s_new = self.extract_closure_env_elem(env, 1, 0)?;
                let unit = ptr_type.const_null();
                let pair = self.alloc_pair(unit.into(), s_new.into())?;
                self.builder().build_return(Some(&pair)).map_err(|e| {
                    CodegenError::Internal(format!("put_rt_over_st return: {:?}", e))
                })?;
                if let Some(bb) = saved_bb {
                    self.builder().position_at_end(bb);
                }
            }
            let fn_ptr = func.as_global_value().as_pointer_value();
            let s_ptr = self.value_to_ptr(s_val)?;
            let closure_ptr = self.alloc_closure(fn_ptr, &[(VarId::new(900000), s_ptr.into())])?;
            return Ok(Some(closure_ptr.into()));
        }

        let fn_name = "bhc_state_t_put";

        let func = self.get_or_create_transformer_fn(fn_name);
        if func.count_basic_blocks() == 0 {
            let entry = self.llvm_ctx.append_basic_block(func, "entry");
            let saved_bb = self.builder().get_insert_block();
            self.builder().position_at_end(entry);
            // \(env, _s) -> ((), s') where s' = env[0]
            let env = func.get_nth_param(0).unwrap().into_pointer_value();
            let s_new = self.extract_closure_env_elem(env, 1, 0)?;
            let unit = ptr_type.const_null();
            let pair = self.alloc_pair(unit.into(), s_new.into())?;
            self.builder()
                .build_return(Some(&pair))
                .map_err(|e| CodegenError::Internal(format!("state_t_put return: {:?}", e)))?;
            if let Some(bb) = saved_bb {
                self.builder().position_at_end(bb);
            }
        }

        let fn_ptr = func.as_global_value().as_pointer_value();
        let s_ptr = self.value_to_ptr(s_val)?;
        let closure_ptr = self.alloc_closure(fn_ptr, &[(VarId::new(900000), s_ptr.into())])?;
        Ok(Some(closure_ptr.into()))
    }

    /// modify f = closure \s -> ((), f(s))
    ///
    /// For RtOvSt: \(env, _reader_env, s) -> ((), f(s)) where f = env[0]
    fn lower_builtin_modify(
        &mut self,
        f_expr: &Expr,
    ) -> CodegenResult<Option<BasicValueEnum<'ctx>>> {
        let f_val = self
            .lower_expr(f_expr)?
            .ok_or_else(|| CodegenError::Internal("modify: f has no value".to_string()))?;

        let ptr_type = self.type_mapper().ptr_type();

        // ReaderT-over-StateT: 3-arg closure
        if self.transformer_stack.is_reader_t_over_state_t() {
            let fn_name = "bhc_state_t_modify_rt_over_st";
            let func = self.get_or_create_nested_transformer_fn(fn_name);
            if func.count_basic_blocks() == 0 {
                let entry = self.llvm_ctx.append_basic_block(func, "entry");
                let saved_bb = self.builder().get_insert_block();
                self.builder().position_at_end(entry);
                // \(env, _reader_env, s) -> ((), f(s)) where f = env[0]
                let env = func.get_nth_param(0).unwrap().into_pointer_value();
                let s = func.get_nth_param(2).unwrap();
                let f = self.extract_closure_env_elem(env, 1, 0)?;

                let fn_type = ptr_type.fn_type(&[ptr_type.into(), ptr_type.into()], false);
                let f_fn = self.extract_closure_fn_ptr(f)?;
                let s_ptr = self.value_to_ptr(s)?;
                let s_new = self
                    .builder()
                    .build_indirect_call(fn_type, f_fn, &[f.into(), s_ptr.into()], "modify_rt_st_s")
                    .map_err(|e| CodegenError::Internal(format!("modify_rt_over_st f: {:?}", e)))?
                    .try_as_basic_value()
                    .basic()
                    .ok_or_else(|| {
                        CodegenError::Internal("modify_rt_over_st f: void".to_string())
                    })?;

                let unit = ptr_type.const_null();
                let pair = self.alloc_pair(unit.into(), s_new)?;
                self.builder().build_return(Some(&pair)).map_err(|e| {
                    CodegenError::Internal(format!("modify_rt_over_st return: {:?}", e))
                })?;
                if let Some(bb) = saved_bb {
                    self.builder().position_at_end(bb);
                }
            }
            let fn_ptr = func.as_global_value().as_pointer_value();
            let f_ptr = self.value_to_ptr(f_val)?;
            let closure_ptr = self.alloc_closure(fn_ptr, &[(VarId::new(900000), f_ptr.into())])?;
            return Ok(Some(closure_ptr.into()));
        }

        let fn_name = "bhc_state_t_modify";

        let func = self.get_or_create_transformer_fn(fn_name);
        if func.count_basic_blocks() == 0 {
            let entry = self.llvm_ctx.append_basic_block(func, "entry");
            let saved_bb = self.builder().get_insert_block();
            self.builder().position_at_end(entry);
            // \(env, s) -> ((), f(s)) where f = env[0]
            let env = func.get_nth_param(0).unwrap().into_pointer_value();
            let s = func.get_nth_param(1).unwrap();
            let f = self.extract_closure_env_elem(env, 1, 0)?;

            let fn_type = ptr_type.fn_type(&[ptr_type.into(), ptr_type.into()], false);
            let f_fn = self.extract_closure_fn_ptr(f)?;
            let s_ptr = self.value_to_ptr(s)?;
            let s_new = self
                .builder()
                .build_indirect_call(fn_type, f_fn, &[f.into(), s_ptr.into()], "modify_s")
                .map_err(|e| CodegenError::Internal(format!("modify f: {:?}", e)))?
                .try_as_basic_value()
                .basic()
                .ok_or_else(|| CodegenError::Internal("modify f: void".to_string()))?;

            let unit = ptr_type.const_null();
            let pair = self.alloc_pair(unit.into(), s_new)?;
            self.builder()
                .build_return(Some(&pair))
                .map_err(|e| CodegenError::Internal(format!("state_t_modify return: {:?}", e)))?;
            if let Some(bb) = saved_bb {
                self.builder().position_at_end(bb);
            }
        }

        let fn_ptr = func.as_global_value().as_pointer_value();
        let f_ptr = self.value_to_ptr(f_val)?;
        let closure_ptr = self.alloc_closure(fn_ptr, &[(VarId::new(900000), f_ptr.into())])?;
        Ok(Some(closure_ptr.into()))
    }

    /// gets f = closure \s -> (f(s), s)
    fn lower_builtin_gets(&mut self, f_expr: &Expr) -> CodegenResult<Option<BasicValueEnum<'ctx>>> {
        let f_val = self
            .lower_expr(f_expr)?
            .ok_or_else(|| CodegenError::Internal("gets: f has no value".to_string()))?;

        let ptr_type = self.type_mapper().ptr_type();
        let fn_name = "bhc_state_t_gets";

        let func = self.get_or_create_transformer_fn(fn_name);
        if func.count_basic_blocks() == 0 {
            let entry = self.llvm_ctx.append_basic_block(func, "entry");
            let saved_bb = self.builder().get_insert_block();
            self.builder().position_at_end(entry);
            let env = func.get_nth_param(0).unwrap().into_pointer_value();
            let s = func.get_nth_param(1).unwrap();
            let f = self.extract_closure_env_elem(env, 1, 0)?;

            let fn_type = ptr_type.fn_type(&[ptr_type.into(), ptr_type.into()], false);
            let f_fn = self.extract_closure_fn_ptr(f)?;
            let s_ptr = self.value_to_ptr(s)?;
            let result = self
                .builder()
                .build_indirect_call(fn_type, f_fn, &[f.into(), s_ptr.into()], "gets_result")
                .map_err(|e| CodegenError::Internal(format!("gets f: {:?}", e)))?
                .try_as_basic_value()
                .basic()
                .ok_or_else(|| CodegenError::Internal("gets f: void".to_string()))?;

            let pair = self.alloc_pair(result, s)?;
            self.builder()
                .build_return(Some(&pair))
                .map_err(|e| CodegenError::Internal(format!("state_t_gets return: {:?}", e)))?;
            if let Some(bb) = saved_bb {
                self.builder().position_at_end(bb);
            }
        }

        let fn_ptr = func.as_global_value().as_pointer_value();
        let f_ptr = self.value_to_ptr(f_val)?;
        let closure_ptr = self.alloc_closure(fn_ptr, &[(VarId::new(900000), f_ptr.into())])?;
        Ok(Some(closure_ptr.into()))
    }

    /// StateT.pure x = closure \s -> (x, s)
    ///
    /// For nested transformers (StateT over ReaderT), the closure takes 3 args:
    /// \(env, s, reader_env) -> (x, s)
    fn lower_builtin_state_t_pure(
        &mut self,
        x_expr: &Expr,
    ) -> CodegenResult<Option<BasicValueEnum<'ctx>>> {
        let x_val = self
            .lower_expr(x_expr)?
            .ok_or_else(|| CodegenError::Internal("StateT.pure: x has no value".to_string()))?;

        let _ptr_type = self.type_mapper().ptr_type();
        let is_nested = self.transformer_stack.is_state_t_over_reader_t();
        let fn_name = if is_nested {
            "bhc_state_t_pure_nested"
        } else {
            "bhc_state_t_pure"
        };

        let func = if is_nested {
            self.get_or_create_nested_transformer_fn(fn_name)
        } else {
            self.get_or_create_transformer_fn(fn_name)
        };
        if func.count_basic_blocks() == 0 {
            let entry = self.llvm_ctx.append_basic_block(func, "entry");
            let saved_bb = self.builder().get_insert_block();
            self.builder().position_at_end(entry);
            // For nested: \(env, s, reader_env) -> (x, s) where x = env[0]
            // For non-nested: \(env, s) -> (x, s) where x = env[0]
            let env = func.get_nth_param(0).unwrap().into_pointer_value();
            let s = func.get_nth_param(1).unwrap();
            // reader_env is param 2 for nested, but unused for pure
            let x = self.extract_closure_env_elem(env, 1, 0)?;
            let pair = self.alloc_pair(x.into(), s)?;
            self.builder()
                .build_return(Some(&pair))
                .map_err(|e| CodegenError::Internal(format!("state_t_pure return: {:?}", e)))?;
            if let Some(bb) = saved_bb {
                self.builder().position_at_end(bb);
            }
        }

        let fn_ptr = func.as_global_value().as_pointer_value();
        let x_ptr = self.value_to_ptr(x_val)?;
        let closure_ptr = self.alloc_closure(fn_ptr, &[(VarId::new(900000), x_ptr.into())])?;
        Ok(Some(closure_ptr.into()))
    }

    /// StateT.>>= m k = closure \s -> let (a, s') = m(s) in k(a)(s')
    ///
    /// For nested transformers (StateT over ReaderT), the closure takes 3 args:
    /// \(env, s, reader_env) -> let (a, s') = m(s, reader_env) in k(a)(s')(reader_env)
    fn lower_builtin_state_t_bind(
        &mut self,
        m_expr: &Expr,
        k_expr: &Expr,
    ) -> CodegenResult<Option<BasicValueEnum<'ctx>>> {
        let m_val = self
            .lower_expr(m_expr)?
            .ok_or_else(|| CodegenError::Internal("StateT.>>=: m has no value".to_string()))?;
        let k_val = self
            .lower_expr(k_expr)?
            .ok_or_else(|| CodegenError::Internal("StateT.>>=: k has no value".to_string()))?;

        let ptr_type = self.type_mapper().ptr_type();
        let is_nested = self.transformer_stack.is_state_t_over_reader_t();
        let fn_name = if is_nested {
            "bhc_state_t_bind_nested"
        } else {
            "bhc_state_t_bind"
        };

        let func = if is_nested {
            self.get_or_create_nested_transformer_fn(fn_name)
        } else {
            self.get_or_create_transformer_fn(fn_name)
        };
        if func.count_basic_blocks() == 0 {
            let entry = self.llvm_ctx.append_basic_block(func, "entry");
            let saved_bb = self.builder().get_insert_block();
            self.builder().position_at_end(entry);
            let env = func.get_nth_param(0).unwrap().into_pointer_value();
            let s = func.get_nth_param(1).unwrap();
            let m = self.extract_closure_env_elem(env, 2, 0)?;
            let k = self.extract_closure_env_elem(env, 2, 1)?;

            if is_nested {
                // Nested: 3-arg closures with reader_env threading
                let reader_env = func.get_nth_param(2).unwrap();
                let fn_type =
                    ptr_type.fn_type(&[ptr_type.into(), ptr_type.into(), ptr_type.into()], false);

                // pair = m(s, reader_env)
                let m_fn = self.extract_closure_fn_ptr(m)?;
                let pair = self
                    .builder()
                    .build_indirect_call(
                        fn_type,
                        m_fn,
                        &[m.into(), s.into(), reader_env.into()],
                        "st_bind_pair",
                    )
                    .map_err(|e| CodegenError::Internal(format!("StateT bind m nested: {:?}", e)))?
                    .try_as_basic_value()
                    .basic()
                    .ok_or_else(|| {
                        CodegenError::Internal("StateT bind m nested: void".to_string())
                    })?
                    .into_pointer_value();

                // a = fst(pair), s' = snd(pair)
                let a = self.extract_pair_fst(pair)?;
                let s_prime = self.extract_pair_snd(pair)?;

                // kr = k(a) -- k is a non-nested closure that produces a nested closure
                let k_fn_type = ptr_type.fn_type(&[ptr_type.into(), ptr_type.into()], false);
                let k_fn = self.extract_closure_fn_ptr(k)?;
                let kr = self
                    .builder()
                    .build_indirect_call(k_fn_type, k_fn, &[k.into(), a.into()], "st_bind_kr")
                    .map_err(|e| CodegenError::Internal(format!("StateT bind k nested: {:?}", e)))?
                    .try_as_basic_value()
                    .basic()
                    .ok_or_else(|| {
                        CodegenError::Internal("StateT bind k nested: void".to_string())
                    })?;

                // result = kr(s', reader_env)
                let kr_ptr = kr.into_pointer_value();
                let kr_fn = self.extract_closure_fn_ptr(kr_ptr)?;
                let result = self
                    .builder()
                    .build_indirect_call(
                        fn_type,
                        kr_fn,
                        &[kr_ptr.into(), s_prime.into(), reader_env.into()],
                        "st_bind_result",
                    )
                    .map_err(|e| CodegenError::Internal(format!("StateT bind kr nested: {:?}", e)))?
                    .try_as_basic_value()
                    .basic()
                    .ok_or_else(|| {
                        CodegenError::Internal("StateT bind kr nested: void".to_string())
                    })?;

                self.builder().build_return(Some(&result)).map_err(|e| {
                    CodegenError::Internal(format!("state_t_bind nested return: {:?}", e))
                })?;
            } else {
                // Non-nested: original 2-arg implementation
                let fn_type = ptr_type.fn_type(&[ptr_type.into(), ptr_type.into()], false);

                // pair = m(s)
                let m_fn = self.extract_closure_fn_ptr(m)?;
                let pair = self
                    .builder()
                    .build_indirect_call(fn_type, m_fn, &[m.into(), s.into()], "st_bind_pair")
                    .map_err(|e| CodegenError::Internal(format!("StateT bind m: {:?}", e)))?
                    .try_as_basic_value()
                    .basic()
                    .ok_or_else(|| CodegenError::Internal("StateT bind m: void".to_string()))?
                    .into_pointer_value();

                // a = fst(pair), s' = snd(pair)
                let a = self.extract_pair_fst(pair)?;
                let s_prime = self.extract_pair_snd(pair)?;

                // kr = k(a)
                let k_fn = self.extract_closure_fn_ptr(k)?;
                let kr = self
                    .builder()
                    .build_indirect_call(fn_type, k_fn, &[k.into(), a.into()], "st_bind_kr")
                    .map_err(|e| CodegenError::Internal(format!("StateT bind k: {:?}", e)))?
                    .try_as_basic_value()
                    .basic()
                    .ok_or_else(|| CodegenError::Internal("StateT bind k: void".to_string()))?;

                // result = kr(s')
                let kr_ptr = kr.into_pointer_value();
                let kr_fn = self.extract_closure_fn_ptr(kr_ptr)?;
                let result = self
                    .builder()
                    .build_indirect_call(
                        fn_type,
                        kr_fn,
                        &[kr_ptr.into(), s_prime.into()],
                        "st_bind_result",
                    )
                    .map_err(|e| CodegenError::Internal(format!("StateT bind kr: {:?}", e)))?
                    .try_as_basic_value()
                    .basic()
                    .ok_or_else(|| CodegenError::Internal("StateT bind kr: void".to_string()))?;

                self.builder()
                    .build_return(Some(&result))
                    .map_err(|e| CodegenError::Internal(format!("state_t_bind return: {:?}", e)))?;
            }
            if let Some(bb) = saved_bb {
                self.builder().position_at_end(bb);
            }
        }

        let fn_ptr = func.as_global_value().as_pointer_value();
        let m_ptr = self.value_to_ptr(m_val)?;
        let k_ptr = self.value_to_ptr(k_val)?;
        let closure_ptr = self.alloc_closure(
            fn_ptr,
            &[
                (VarId::new(900000), m_ptr.into()),
                (VarId::new(900001), k_ptr.into()),
            ],
        )?;
        Ok(Some(closure_ptr.into()))
    }

    /// StateT.>> m1 m2 = closure \s -> let (_, s') = m1(s) in m2(s')
    fn lower_builtin_state_t_then(
        &mut self,
        m1_expr: &Expr,
        m2_expr: &Expr,
    ) -> CodegenResult<Option<BasicValueEnum<'ctx>>> {
        let m1_val = self
            .lower_expr(m1_expr)?
            .ok_or_else(|| CodegenError::Internal("StateT.>>: m1 has no value".to_string()))?;
        let m2_val = self
            .lower_expr(m2_expr)?
            .ok_or_else(|| CodegenError::Internal("StateT.>>: m2 has no value".to_string()))?;

        let ptr_type = self.type_mapper().ptr_type();
        let fn_name = "bhc_state_t_then";

        let func = self.get_or_create_transformer_fn(fn_name);
        if func.count_basic_blocks() == 0 {
            let entry = self.llvm_ctx.append_basic_block(func, "entry");
            let saved_bb = self.builder().get_insert_block();
            self.builder().position_at_end(entry);
            let env = func.get_nth_param(0).unwrap().into_pointer_value();
            let s = func.get_nth_param(1).unwrap();
            let m1 = self.extract_closure_env_elem(env, 2, 0)?;
            let m2 = self.extract_closure_env_elem(env, 2, 1)?;

            let fn_type = ptr_type.fn_type(&[ptr_type.into(), ptr_type.into()], false);

            // pair = m1(s)
            let m1_fn = self.extract_closure_fn_ptr(m1)?;
            let pair = self
                .builder()
                .build_indirect_call(fn_type, m1_fn, &[m1.into(), s.into()], "st_then_pair")
                .map_err(|e| CodegenError::Internal(format!("StateT then m1: {:?}", e)))?
                .try_as_basic_value()
                .basic()
                .ok_or_else(|| CodegenError::Internal("StateT then m1: void".to_string()))?
                .into_pointer_value();

            // s' = snd(pair)
            let s_prime = self.extract_pair_snd(pair)?;

            // result = m2(s')
            let m2_fn = self.extract_closure_fn_ptr(m2)?;
            let result = self
                .builder()
                .build_indirect_call(
                    fn_type,
                    m2_fn,
                    &[m2.into(), s_prime.into()],
                    "st_then_result",
                )
                .map_err(|e| CodegenError::Internal(format!("StateT then m2: {:?}", e)))?
                .try_as_basic_value()
                .basic()
                .ok_or_else(|| CodegenError::Internal("StateT then m2: void".to_string()))?;

            self.builder()
                .build_return(Some(&result))
                .map_err(|e| CodegenError::Internal(format!("state_t_then return: {:?}", e)))?;
            if let Some(bb) = saved_bb {
                self.builder().position_at_end(bb);
            }
        }

        let fn_ptr = func.as_global_value().as_pointer_value();
        let m1_ptr = self.value_to_ptr(m1_val)?;
        let m2_ptr = self.value_to_ptr(m2_val)?;
        let closure_ptr = self.alloc_closure(
            fn_ptr,
            &[
                (VarId::new(900000), m1_ptr.into()),
                (VarId::new(900001), m2_ptr.into()),
            ],
        )?;
        Ok(Some(closure_ptr.into()))
    }

    /// StateT.fmap f m = closure \s -> let (a, s') = m(s) in (f(a), s')
    fn lower_builtin_state_t_fmap(
        &mut self,
        f_expr: &Expr,
        m_expr: &Expr,
    ) -> CodegenResult<Option<BasicValueEnum<'ctx>>> {
        let f_val = self
            .lower_expr(f_expr)?
            .ok_or_else(|| CodegenError::Internal("StateT.fmap: f has no value".to_string()))?;
        let m_val = self
            .lower_expr(m_expr)?
            .ok_or_else(|| CodegenError::Internal("StateT.fmap: m has no value".to_string()))?;

        let ptr_type = self.type_mapper().ptr_type();
        let fn_name = "bhc_state_t_fmap";

        let func = self.get_or_create_transformer_fn(fn_name);
        if func.count_basic_blocks() == 0 {
            let entry = self.llvm_ctx.append_basic_block(func, "entry");
            let saved_bb = self.builder().get_insert_block();
            self.builder().position_at_end(entry);
            let env = func.get_nth_param(0).unwrap().into_pointer_value();
            let s = func.get_nth_param(1).unwrap();
            let f = self.extract_closure_env_elem(env, 2, 0)?;
            let m = self.extract_closure_env_elem(env, 2, 1)?;

            let fn_type = ptr_type.fn_type(&[ptr_type.into(), ptr_type.into()], false);

            let m_fn = self.extract_closure_fn_ptr(m)?;
            let pair = self
                .builder()
                .build_indirect_call(fn_type, m_fn, &[m.into(), s.into()], "st_fmap_pair")
                .map_err(|e| CodegenError::Internal(format!("StateT fmap m: {:?}", e)))?
                .try_as_basic_value()
                .basic()
                .ok_or_else(|| CodegenError::Internal("StateT fmap m: void".to_string()))?
                .into_pointer_value();

            let a = self.extract_pair_fst(pair)?;
            let s_prime = self.extract_pair_snd(pair)?;

            let f_fn = self.extract_closure_fn_ptr(f)?;
            let b = self
                .builder()
                .build_indirect_call(fn_type, f_fn, &[f.into(), a.into()], "st_fmap_b")
                .map_err(|e| CodegenError::Internal(format!("StateT fmap f: {:?}", e)))?
                .try_as_basic_value()
                .basic()
                .ok_or_else(|| CodegenError::Internal("StateT fmap f: void".to_string()))?;

            let result_pair = self.alloc_pair(b, s_prime.into())?;
            self.builder()
                .build_return(Some(&result_pair))
                .map_err(|e| CodegenError::Internal(format!("state_t_fmap return: {:?}", e)))?;
            if let Some(bb) = saved_bb {
                self.builder().position_at_end(bb);
            }
        }

        let fn_ptr = func.as_global_value().as_pointer_value();
        let f_ptr = self.value_to_ptr(f_val)?;
        let m_ptr = self.value_to_ptr(m_val)?;
        let closure_ptr = self.alloc_closure(
            fn_ptr,
            &[
                (VarId::new(900000), f_ptr.into()),
                (VarId::new(900001), m_ptr.into()),
            ],
        )?;
        Ok(Some(closure_ptr.into()))
    }

    /// StateT.<*> = use bind and fmap composition
    fn lower_builtin_state_t_ap(
        &mut self,
        mf_expr: &Expr,
        mx_expr: &Expr,
    ) -> CodegenResult<Option<BasicValueEnum<'ctx>>> {
        // ap mf mx = mf >>= \f -> fmap f mx
        // For simplicity, implement directly like reader_t_ap but with state threading
        let mf_val = self
            .lower_expr(mf_expr)?
            .ok_or_else(|| CodegenError::Internal("StateT.<*>: mf has no value".to_string()))?;
        let mx_val = self
            .lower_expr(mx_expr)?
            .ok_or_else(|| CodegenError::Internal("StateT.<*>: mx has no value".to_string()))?;

        let ptr_type = self.type_mapper().ptr_type();
        let fn_name = "bhc_state_t_ap";

        let func = self.get_or_create_transformer_fn(fn_name);
        if func.count_basic_blocks() == 0 {
            let entry = self.llvm_ctx.append_basic_block(func, "entry");
            let saved_bb = self.builder().get_insert_block();
            self.builder().position_at_end(entry);
            let env = func.get_nth_param(0).unwrap().into_pointer_value();
            let s = func.get_nth_param(1).unwrap();
            let mf = self.extract_closure_env_elem(env, 2, 0)?;
            let mx = self.extract_closure_env_elem(env, 2, 1)?;

            let fn_type = ptr_type.fn_type(&[ptr_type.into(), ptr_type.into()], false);

            // (f, s') = mf(s)
            let mf_fn = self.extract_closure_fn_ptr(mf)?;
            let pair1 = self
                .builder()
                .build_indirect_call(fn_type, mf_fn, &[mf.into(), s.into()], "st_ap_pair1")
                .map_err(|e| CodegenError::Internal(format!("StateT ap mf: {:?}", e)))?
                .try_as_basic_value()
                .basic()
                .ok_or_else(|| CodegenError::Internal("StateT ap mf: void".to_string()))?
                .into_pointer_value();
            let f = self.extract_pair_fst(pair1)?;
            let s_prime = self.extract_pair_snd(pair1)?;

            // (x, s'') = mx(s')
            let mx_fn = self.extract_closure_fn_ptr(mx)?;
            let pair2 = self
                .builder()
                .build_indirect_call(fn_type, mx_fn, &[mx.into(), s_prime.into()], "st_ap_pair2")
                .map_err(|e| CodegenError::Internal(format!("StateT ap mx: {:?}", e)))?
                .try_as_basic_value()
                .basic()
                .ok_or_else(|| CodegenError::Internal("StateT ap mx: void".to_string()))?
                .into_pointer_value();
            let x = self.extract_pair_fst(pair2)?;
            let s_double_prime = self.extract_pair_snd(pair2)?;

            // result = f(x)
            let f_fn = self.extract_closure_fn_ptr(f)?;
            let b = self
                .builder()
                .build_indirect_call(fn_type, f_fn, &[f.into(), x.into()], "st_ap_result")
                .map_err(|e| CodegenError::Internal(format!("StateT ap f: {:?}", e)))?
                .try_as_basic_value()
                .basic()
                .ok_or_else(|| CodegenError::Internal("StateT ap f: void".to_string()))?;

            let result_pair = self.alloc_pair(b, s_double_prime.into())?;
            self.builder()
                .build_return(Some(&result_pair))
                .map_err(|e| CodegenError::Internal(format!("state_t_ap return: {:?}", e)))?;
            if let Some(bb) = saved_bb {
                self.builder().position_at_end(bb);
            }
        }

        let fn_ptr = func.as_global_value().as_pointer_value();
        let mf_ptr = self.value_to_ptr(mf_val)?;
        let mx_ptr = self.value_to_ptr(mx_val)?;
        let closure_ptr = self.alloc_closure(
            fn_ptr,
            &[
                (VarId::new(900000), mf_ptr.into()),
                (VarId::new(900001), mx_ptr.into()),
            ],
        )?;
        Ok(Some(closure_ptr.into()))
    }

    /// StateT.lift action = closure \s -> (action, s)
    fn lower_builtin_state_t_lift(
        &mut self,
        action_expr: &Expr,
    ) -> CodegenResult<Option<BasicValueEnum<'ctx>>> {
        let action_val = self.lower_expr(action_expr)?.ok_or_else(|| {
            CodegenError::Internal("StateT.lift: action has no value".to_string())
        })?;

        let _ptr_type = self.type_mapper().ptr_type();
        let fn_name = "bhc_state_t_lift";

        let func = self.get_or_create_transformer_fn(fn_name);
        if func.count_basic_blocks() == 0 {
            let entry = self.llvm_ctx.append_basic_block(func, "entry");
            let saved_bb = self.builder().get_insert_block();
            self.builder().position_at_end(entry);
            // \(env, s) -> (action, s) where action = env[0]
            let env = func.get_nth_param(0).unwrap().into_pointer_value();
            let s = func.get_nth_param(1).unwrap();
            let action = self.extract_closure_env_elem(env, 1, 0)?;
            let pair = self.alloc_pair(action.into(), s)?;
            self.builder()
                .build_return(Some(&pair))
                .map_err(|e| CodegenError::Internal(format!("state_t_lift return: {:?}", e)))?;
            if let Some(bb) = saved_bb {
                self.builder().position_at_end(bb);
            }
        }

        let fn_ptr = func.as_global_value().as_pointer_value();
        let action_ptr = self.value_to_ptr(action_val)?;
        let closure_ptr = self.alloc_closure(fn_ptr, &[(VarId::new(900000), action_ptr.into())])?;
        Ok(Some(closure_ptr.into()))
    }

    /// evalStateT m s = fst(m(s))
    ///
    /// For nested transformers like `StateT s (ReaderT r IO)`, this returns
    /// a ReaderT closure instead of executing directly.
    fn lower_builtin_eval_state_t(
        &mut self,
        m_expr: &Expr,
        s_expr: &Expr,
    ) -> CodegenResult<Option<BasicValueEnum<'ctx>>> {
        // Check if this is a nested transformer by examining the type
        let m_ty = m_expr.ty();
        let inner_monad = self.extract_inner_monad_from_state_t(&m_ty);

        match inner_monad {
            Some(TransformerLayer::ReaderT) => {
                // StateT s (ReaderT r IO) - return a ReaderT closure
                self.lower_eval_state_t_over_reader_t(m_expr, s_expr)
            }
            Some(TransformerLayer::IO) | None => {
                // StateT s IO - direct execution (current behavior)
                let result = self.lower_builtin_run_state_t(m_expr, s_expr)?;
                let pair = result
                    .ok_or_else(|| CodegenError::Internal("evalStateT: no result".to_string()))?
                    .into_pointer_value();
                let fst = self.extract_pair_fst(pair)?;
                Ok(Some(fst.into()))
            }
            Some(_other) => {
                // Other nested transformers - not yet supported
                Err(CodegenError::Internal(
                    "evalStateT over non-IO/ReaderT inner monad not yet supported".to_string(),
                ))
            }
        }
    }

    /// Extract the inner monad from a StateT type.
    ///
    /// For `StateT s m a`, returns the transformer layer of `m`.
    fn extract_inner_monad_from_state_t(&self, ty: &Ty) -> Option<TransformerLayer> {
        // StateT s m a = App(App(App(Con(StateT), s), m), a)
        // We need to get `m` and determine its transformer layer
        if let Ty::App(inner, _result_type) = ty {
            // inner = App(App(Con(StateT), s), m)
            if let Ty::App(inner2, monad_arg) = inner.as_ref() {
                // inner2 = App(Con(StateT), s)
                if let Ty::App(con, _param) = inner2.as_ref() {
                    if let Ty::Con(tycon) = con.as_ref() {
                        if tycon.name.as_str() == "StateT" {
                            // monad_arg is the inner monad `m`
                            return self.get_transformer_layer_from_type(monad_arg);
                        }
                    }
                }
            }
        }
        None
    }

    /// Extract the inner monad from a ReaderT type.
    ///
    /// For `ReaderT r m a`, returns the transformer layer of `m`.
    fn extract_inner_monad_from_reader_t(&self, ty: &Ty) -> Option<TransformerLayer> {
        // ReaderT r m a = App(App(App(Con(ReaderT), r), m), a)
        if let Ty::App(inner, _result_type) = ty {
            if let Ty::App(inner2, monad_arg) = inner.as_ref() {
                if let Ty::App(con, _param) = inner2.as_ref() {
                    if let Ty::Con(tycon) = con.as_ref() {
                        if tycon.name.as_str() == "ReaderT" {
                            return self.get_transformer_layer_from_type(monad_arg);
                        }
                    }
                }
            }
        }
        None
    }

    /// Extract the return type from a function type, stripping away function arrows.
    ///
    /// For `Int -> StateT Int IO Int`, returns `StateT Int IO Int`.
    /// For `StateT Int IO Int` (no arrows), returns the type itself.
    fn get_return_type_from_function_type<'a>(&self, ty: &'a Ty) -> &'a Ty {
        match ty {
            Ty::Fun(_, ret) => self.get_return_type_from_function_type(ret),
            _ => ty,
        }
    }

    /// Get the transformer layer from a monad type.
    ///
    /// Returns IO for `IO`, StateT for `StateT s m`, ReaderT for `ReaderT r m`, etc.
    fn get_transformer_layer_from_type(&self, ty: &Ty) -> Option<TransformerLayer> {
        // Detect transformer types. The structure depends on whether it's:
        // - IO: Con(IO)
        // - IO a: App(Con(IO), a)
        // - T x m (partial, 2 params): App(App(Con(T), x), m)
        // - T x m a (fully applied): App(App(App(Con(T), x), m), a)
        match ty {
            Ty::Con(tycon) if tycon.name.as_str() == "IO" => Some(TransformerLayer::IO),
            Ty::App(inner, _arg) => {
                // Check if inner is IO (for IO a)
                if let Ty::Con(tycon) = inner.as_ref() {
                    if tycon.name.as_str() == "IO" {
                        return Some(TransformerLayer::IO);
                    }
                }

                // For T x m (two-param transformers), inner = App(Con(T), x)
                // This handles partial application: StateT s m
                if let Ty::App(con, _param) = inner.as_ref() {
                    if let Ty::Con(tycon) = con.as_ref() {
                        let result = match tycon.name.as_str() {
                            "StateT" => Some(TransformerLayer::StateT),
                            "ReaderT" => Some(TransformerLayer::ReaderT),
                            "ExceptT" => Some(TransformerLayer::ExceptT),
                            "WriterT" => Some(TransformerLayer::WriterT),
                            _ => None,
                        };
                        if result.is_some() {
                            return result;
                        }
                    }

                    // For T x m a (fully applied), go one level deeper
                    // inner = App(App(Con(T), x), m), so we look at inner.inner
                    if let Ty::App(con2, _param2) = con.as_ref() {
                        if let Ty::Con(tycon) = con2.as_ref() {
                            return match tycon.name.as_str() {
                                "StateT" => Some(TransformerLayer::StateT),
                                "ReaderT" => Some(TransformerLayer::ReaderT),
                                "ExceptT" => Some(TransformerLayer::ExceptT),
                                "WriterT" => Some(TransformerLayer::WriterT),
                                _ => None,
                            };
                        }
                    }
                }
                None
            }
            _ => None,
        }
    }

    /// evalStateT for StateT s (ReaderT r IO) - returns a ReaderT closure.
    ///
    /// The returned closure takes reader_env and executes the StateT computation
    /// with combined (state, reader_env) context.
    fn lower_eval_state_t_over_reader_t(
        &mut self,
        m_expr: &Expr,
        s_expr: &Expr,
    ) -> CodegenResult<Option<BasicValueEnum<'ctx>>> {
        // Lower m — m is typically a top-level function reference whose body
        // is already lowered with the correct transformer stack by its own handler.
        // Do NOT push extra layers here — they would stack on top of the function's own
        // layers if m's body is lowered inline (forward reference).
        let m_val = self.lower_expr(m_expr)?.ok_or_else(|| {
            CodegenError::Internal("evalStateT/ReaderT: m has no value".to_string())
        })?;
        let s_val = self.lower_expr(s_expr)?.ok_or_else(|| {
            CodegenError::Internal("evalStateT/ReaderT: s has no value".to_string())
        })?;

        let ptr_type = self.type_mapper().ptr_type();
        let fn_name = "bhc_eval_state_t_over_reader_t";

        // Create the wrapper function that takes reader_env and runs the StateT computation
        let func = self.get_or_create_transformer_fn(fn_name);
        if func.count_basic_blocks() == 0 {
            let entry = self.llvm_ctx.append_basic_block(func, "entry");
            let saved_bb = self.builder().get_insert_block();
            self.builder().position_at_end(entry);

            // Parameters: (env, reader_env)
            // env contains: [m, init_state]
            let env = func.get_nth_param(0).unwrap().into_pointer_value();
            let reader_env = func.get_nth_param(1).unwrap();
            let m = self.extract_closure_env_elem(env, 2, 0)?;
            let init_state = self.extract_closure_env_elem(env, 2, 1)?;

            // Call m with (m, init_state, reader_env) - 3-arg nested calling convention
            // m :: StateT s (ReaderT r IO) a uses nested closures that need reader_env
            let fn_type =
                ptr_type.fn_type(&[ptr_type.into(), ptr_type.into(), ptr_type.into()], false);
            let m_fn = self.extract_closure_fn_ptr(m)?;
            let pair = self
                .builder()
                .build_indirect_call(
                    fn_type,
                    m_fn,
                    &[m.into(), init_state.into(), reader_env.into()],
                    "eval_st_pair",
                )
                .map_err(|e| CodegenError::Internal(format!("evalStateT/ReaderT call m: {:?}", e)))?
                .try_as_basic_value()
                .basic()
                .ok_or_else(|| CodegenError::Internal("evalStateT/ReaderT m: void".to_string()))?
                .into_pointer_value();

            // Extract fst (the result)
            let result = self.extract_pair_fst(pair)?;

            self.builder().build_return(Some(&result)).map_err(|e| {
                CodegenError::Internal(format!("evalStateT/ReaderT return: {:?}", e))
            })?;
            if let Some(bb) = saved_bb {
                self.builder().position_at_end(bb);
            }
        }

        // Create a ReaderT closure that captures m and init_state
        let fn_ptr = func.as_global_value().as_pointer_value();
        let m_ptr = self.value_to_ptr(m_val)?;
        let s_ptr = self.value_to_ptr(s_val)?;
        let closure_ptr = self.alloc_closure(
            fn_ptr,
            &[
                (VarId::new(900000), m_ptr.into()),
                (VarId::new(900001), s_ptr.into()),
            ],
        )?;
        Ok(Some(closure_ptr.into()))
    }

    /// execStateT m s = snd(m(s))
    /// execStateT m s = snd(m(s))
    ///
    /// For nested transformers like `StateT s (ReaderT r IO)`, this returns
    /// a ReaderT closure that produces just the final state.
    fn lower_builtin_exec_state_t(
        &mut self,
        m_expr: &Expr,
        s_expr: &Expr,
    ) -> CodegenResult<Option<BasicValueEnum<'ctx>>> {
        // Check if this is a nested transformer
        let m_ty = m_expr.ty();
        let inner_monad = self.extract_inner_monad_from_state_t(&m_ty);

        match inner_monad {
            Some(TransformerLayer::ReaderT) => {
                // StateT s (ReaderT r IO) - return a ReaderT closure that produces final state
                self.lower_exec_state_t_over_reader_t(m_expr, s_expr)
            }
            Some(TransformerLayer::IO) | None => {
                // StateT s IO - direct execution (current behavior)
                let result = self.lower_run_state_t_direct(m_expr, s_expr)?;
                let pair = result
                    .ok_or_else(|| CodegenError::Internal("execStateT: no result".to_string()))?
                    .into_pointer_value();
                let snd = self.extract_pair_snd(pair)?;
                Ok(Some(snd.into()))
            }
            Some(_other) => Err(CodegenError::Internal(
                "execStateT over non-IO/ReaderT inner monad not yet supported".to_string(),
            )),
        }
    }

    /// execStateT for StateT s (ReaderT r IO) - returns a ReaderT closure that produces final state.
    fn lower_exec_state_t_over_reader_t(
        &mut self,
        m_expr: &Expr,
        s_expr: &Expr,
    ) -> CodegenResult<Option<BasicValueEnum<'ctx>>> {
        self.push_transformer_layer(TransformerLayer::StateT);
        let m_val = self.lower_expr(m_expr)?.ok_or_else(|| {
            CodegenError::Internal("execStateT/ReaderT: m has no value".to_string())
        })?;
        self.pop_transformer_layer();
        let s_val = self.lower_expr(s_expr)?.ok_or_else(|| {
            CodegenError::Internal("execStateT/ReaderT: s has no value".to_string())
        })?;

        let ptr_type = self.type_mapper().ptr_type();
        let fn_name = "bhc_exec_state_t_over_reader_t";

        let func = self.get_or_create_transformer_fn(fn_name);
        if func.count_basic_blocks() == 0 {
            let entry = self.llvm_ctx.append_basic_block(func, "entry");
            let saved_bb = self.builder().get_insert_block();
            self.builder().position_at_end(entry);

            let env = func.get_nth_param(0).unwrap().into_pointer_value();
            let reader_env = func.get_nth_param(1).unwrap();
            let m = self.extract_closure_env_elem(env, 2, 0)?;
            let init_state = self.extract_closure_env_elem(env, 2, 1)?;

            let fn_type = ptr_type.fn_type(&[ptr_type.into(), ptr_type.into()], false);
            let m_fn = self.extract_closure_fn_ptr(m)?;
            let pair = self
                .builder()
                .build_indirect_call(
                    fn_type,
                    m_fn,
                    &[m.into(), init_state.into()],
                    "exec_st_pair",
                )
                .map_err(|e| CodegenError::Internal(format!("execStateT/ReaderT call m: {:?}", e)))?
                .try_as_basic_value()
                .basic()
                .ok_or_else(|| CodegenError::Internal("execStateT/ReaderT m: void".to_string()))?
                .into_pointer_value();

            // Extract snd (the final state)
            let final_state = self.extract_pair_snd(pair)?;

            let _ = reader_env;
            self.builder()
                .build_return(Some(&final_state))
                .map_err(|e| {
                    CodegenError::Internal(format!("execStateT/ReaderT return: {:?}", e))
                })?;
            if let Some(bb) = saved_bb {
                self.builder().position_at_end(bb);
            }
        }

        let fn_ptr = func.as_global_value().as_pointer_value();
        let m_ptr = self.value_to_ptr(m_val)?;
        let s_ptr = self.value_to_ptr(s_val)?;
        let closure_ptr = self.alloc_closure(
            fn_ptr,
            &[
                (VarId::new(900000), m_ptr.into()),
                (VarId::new(900001), s_ptr.into()),
            ],
        )?;
        Ok(Some(closure_ptr.into()))
    }

    // ========================================================================
    // ExceptT Transformer Operations
    // ========================================================================

    /// runExceptT m = m(_) — runs the computation, returns Either e a
    fn lower_builtin_run_except_t(
        &mut self,
        m_expr: &Expr,
    ) -> CodegenResult<Option<BasicValueEnum<'ctx>>> {
        self.push_transformer_layer(TransformerLayer::ExceptT);
        let m_val = self
            .lower_expr(m_expr)?
            .ok_or_else(|| CodegenError::Internal("runExceptT: m has no value".to_string()))?;
        self.pop_transformer_layer();

        // In nested context (ExceptT over StateT/ReaderT), the m_val IS the transformer
        // closure that expects state/reader-env. Return it as-is — the outer
        // runStateT/runReaderT will call it with the appropriate argument.
        let current = self.current_transformer_layer();
        if current == TransformerLayer::StateT || current == TransformerLayer::ReaderT {
            return Ok(Some(m_val));
        }

        let m_ptr = match m_val {
            BasicValueEnum::PointerValue(p) => p,
            _ => {
                return Err(CodegenError::TypeError(
                    "runExceptT: expected closure".to_string(),
                ))
            }
        };

        let ptr_type = self.type_mapper().ptr_type();
        let fn_type = ptr_type.fn_type(&[ptr_type.into(), ptr_type.into()], false);
        let m_fn = self.extract_closure_fn_ptr(m_ptr)?;
        let null_arg = ptr_type.const_null();
        let result = self
            .builder()
            .build_indirect_call(
                fn_type,
                m_fn,
                &[m_ptr.into(), null_arg.into()],
                "run_except_t",
            )
            .map_err(|e| CodegenError::Internal(format!("runExceptT call: {:?}", e)))?
            .try_as_basic_value()
            .basic()
            .ok_or_else(|| CodegenError::Internal("runExceptT: void".to_string()))?;
        Ok(Some(result))
    }

    /// ExceptT.pure x = closure \_ -> Right x
    fn lower_builtin_except_t_pure(
        &mut self,
        x_expr: &Expr,
    ) -> CodegenResult<Option<BasicValueEnum<'ctx>>> {
        // ExceptT over StateT: \(env, s) -> (Right x, s)
        if self.transformer_stack.is_except_t_over_state_t() {
            return self.lower_builtin_except_t_pure_over_st(x_expr);
        }
        // ExceptT over ReaderT: same as plain ExceptT (r is ignored in pure)

        let x_val = self
            .lower_expr(x_expr)?
            .ok_or_else(|| CodegenError::Internal("ExceptT.pure: x has no value".to_string()))?;

        let _ptr_type = self.type_mapper().ptr_type();
        let fn_name = "bhc_except_t_pure";

        let func = self.get_or_create_transformer_fn(fn_name);
        if func.count_basic_blocks() == 0 {
            let entry = self.llvm_ctx.append_basic_block(func, "entry");
            let saved_bb = self.builder().get_insert_block();
            self.builder().position_at_end(entry);
            // \(env, _) -> Right(x) where x = env[0]
            let env = func.get_nth_param(0).unwrap().into_pointer_value();
            let x = self.extract_closure_env_elem(env, 1, 0)?;
            // Allocate Right ADT: tag=1, arity=1
            let right_adt = self.alloc_adt(1, 1)?;
            self.store_adt_field(right_adt, 1, 0, x.into())?;
            self.builder()
                .build_return(Some(&right_adt))
                .map_err(|e| CodegenError::Internal(format!("except_t_pure return: {:?}", e)))?;
            if let Some(bb) = saved_bb {
                self.builder().position_at_end(bb);
            }
        }

        let fn_ptr = func.as_global_value().as_pointer_value();
        let x_ptr = self.value_to_ptr(x_val)?;
        let closure_ptr = self.alloc_closure(fn_ptr, &[(VarId::new(900000), x_ptr.into())])?;
        Ok(Some(closure_ptr.into()))
    }

    /// throwE e = closure \_ -> Left e
    fn lower_builtin_throw_e(
        &mut self,
        e_expr: &Expr,
    ) -> CodegenResult<Option<BasicValueEnum<'ctx>>> {
        // ExceptT over StateT: \(env, s) -> (Left e, s)
        if self.transformer_stack.is_except_t_over_state_t() {
            return self.lower_builtin_throw_e_over_st(e_expr);
        }
        // ExceptT over ReaderT: same as plain ExceptT (r is ignored in throwE)

        let e_val = self
            .lower_expr(e_expr)?
            .ok_or_else(|| CodegenError::Internal("throwE: e has no value".to_string()))?;

        let _ptr_type = self.type_mapper().ptr_type();
        let fn_name = "bhc_except_t_throw";

        let func = self.get_or_create_transformer_fn(fn_name);
        if func.count_basic_blocks() == 0 {
            let entry = self.llvm_ctx.append_basic_block(func, "entry");
            let saved_bb = self.builder().get_insert_block();
            self.builder().position_at_end(entry);
            // \(env, _) -> Left(e) where e = env[0]
            let env = func.get_nth_param(0).unwrap().into_pointer_value();
            let e = self.extract_closure_env_elem(env, 1, 0)?;
            // Allocate Left ADT: tag=0, arity=1
            let left_adt = self.alloc_adt(0, 1)?;
            self.store_adt_field(left_adt, 1, 0, e.into())?;
            self.builder()
                .build_return(Some(&left_adt))
                .map_err(|e| CodegenError::Internal(format!("throw_e return: {:?}", e)))?;
            if let Some(bb) = saved_bb {
                self.builder().position_at_end(bb);
            }
        }

        let fn_ptr = func.as_global_value().as_pointer_value();
        let e_ptr = self.value_to_ptr(e_val)?;
        let closure_ptr = self.alloc_closure(fn_ptr, &[(VarId::new(900000), e_ptr.into())])?;
        Ok(Some(closure_ptr.into()))
    }

    /// ExceptT.>>= m k = closure \_ -> let either = m(_) in case tag of
    ///   0 (Left) -> either (short-circuit)
    ///   1 (Right) -> let a = field0(either); kr = k(a) in kr(_)
    fn lower_builtin_except_t_bind(
        &mut self,
        m_expr: &Expr,
        k_expr: &Expr,
    ) -> CodegenResult<Option<BasicValueEnum<'ctx>>> {
        // ExceptT over StateT: \(env, s) -> ...threads state...
        if self.transformer_stack.is_except_t_over_state_t() {
            return self.lower_builtin_except_t_bind_over_st(m_expr, k_expr);
        }
        // ExceptT over ReaderT: \(env, r) -> ...threads reader-env...
        if self.transformer_stack.is_except_t_over_reader_t() {
            return self.lower_builtin_except_t_bind_over_rt(m_expr, k_expr);
        }

        let m_val = self
            .lower_expr(m_expr)?
            .ok_or_else(|| CodegenError::Internal("ExceptT.>>=: m has no value".to_string()))?;
        let k_val = self
            .lower_expr(k_expr)?
            .ok_or_else(|| CodegenError::Internal("ExceptT.>>=: k has no value".to_string()))?;

        let ptr_type = self.type_mapper().ptr_type();
        let fn_name = "bhc_except_t_bind";

        let func = self.get_or_create_transformer_fn(fn_name);
        if func.count_basic_blocks() == 0 {
            let entry = self.llvm_ctx.append_basic_block(func, "entry");
            let right_bb = self.llvm_ctx.append_basic_block(func, "right");
            let left_bb = self.llvm_ctx.append_basic_block(func, "left");
            let saved_bb = self.builder().get_insert_block();
            self.builder().position_at_end(entry);
            let env = func.get_nth_param(0).unwrap().into_pointer_value();
            let m = self.extract_closure_env_elem(env, 2, 0)?;
            let k = self.extract_closure_env_elem(env, 2, 1)?;

            let fn_type = ptr_type.fn_type(&[ptr_type.into(), ptr_type.into()], false);
            let null_arg = ptr_type.const_null();

            // either = m(_)
            let m_fn = self.extract_closure_fn_ptr(m)?;
            let either = self
                .builder()
                .build_indirect_call(
                    fn_type,
                    m_fn,
                    &[m.into(), null_arg.into()],
                    "except_bind_either",
                )
                .map_err(|e| CodegenError::Internal(format!("ExceptT bind m: {:?}", e)))?
                .try_as_basic_value()
                .basic()
                .ok_or_else(|| CodegenError::Internal("ExceptT bind m: void".to_string()))?
                .into_pointer_value();

            // Check tag (0 = Left, 1 = Right)
            let tag = self.extract_adt_tag(either)?;
            let tm = self.type_mapper();
            let is_right = self
                .builder()
                .build_int_compare(
                    inkwell::IntPredicate::EQ,
                    tag,
                    tm.i64_type().const_int(1, false),
                    "is_right",
                )
                .map_err(|e| CodegenError::Internal(format!("ExceptT tag cmp: {:?}", e)))?;
            self.builder()
                .build_conditional_branch(is_right, right_bb, left_bb)
                .map_err(|e| CodegenError::Internal(format!("ExceptT branch: {:?}", e)))?;

            // Left case: short-circuit, return either
            self.builder().position_at_end(left_bb);
            self.builder().build_return(Some(&either)).map_err(|e| {
                CodegenError::Internal(format!("except_t_bind left return: {:?}", e))
            })?;

            // Right case: extract a, call k(a), call result(_)
            self.builder().position_at_end(right_bb);
            let a = self.extract_adt_field(either, 1, 0)?;
            let k_fn = self.extract_closure_fn_ptr(k)?;
            let kr = self
                .builder()
                .build_indirect_call(fn_type, k_fn, &[k.into(), a.into()], "except_bind_kr")
                .map_err(|e| CodegenError::Internal(format!("ExceptT bind k: {:?}", e)))?
                .try_as_basic_value()
                .basic()
                .ok_or_else(|| CodegenError::Internal("ExceptT bind k: void".to_string()))?
                .into_pointer_value();
            let kr_fn = self.extract_closure_fn_ptr(kr)?;
            let result = self
                .builder()
                .build_indirect_call(
                    fn_type,
                    kr_fn,
                    &[kr.into(), null_arg.into()],
                    "except_bind_result",
                )
                .map_err(|e| CodegenError::Internal(format!("ExceptT bind kr: {:?}", e)))?
                .try_as_basic_value()
                .basic()
                .ok_or_else(|| CodegenError::Internal("ExceptT bind kr: void".to_string()))?;
            self.builder().build_return(Some(&result)).map_err(|e| {
                CodegenError::Internal(format!("except_t_bind right return: {:?}", e))
            })?;

            if let Some(bb) = saved_bb {
                self.builder().position_at_end(bb);
            }
        }

        let fn_ptr = func.as_global_value().as_pointer_value();
        let m_ptr = self.value_to_ptr(m_val)?;
        let k_ptr = self.value_to_ptr(k_val)?;
        let closure_ptr = self.alloc_closure(
            fn_ptr,
            &[
                (VarId::new(900000), m_ptr.into()),
                (VarId::new(900001), k_ptr.into()),
            ],
        )?;
        Ok(Some(closure_ptr.into()))
    }

    /// ExceptT.>> m1 m2 = m1 >>= \_ -> m2
    fn lower_builtin_except_t_then(
        &mut self,
        m1_expr: &Expr,
        m2_expr: &Expr,
    ) -> CodegenResult<Option<BasicValueEnum<'ctx>>> {
        // ExceptT over StateT: \(env, s) -> ...threads state...
        if self.transformer_stack.is_except_t_over_state_t() {
            return self.lower_builtin_except_t_then_over_st(m1_expr, m2_expr);
        }
        // ExceptT over ReaderT: \(env, r) -> ...threads reader-env...
        if self.transformer_stack.is_except_t_over_reader_t() {
            return self.lower_builtin_except_t_then_over_rt(m1_expr, m2_expr);
        }

        let m1_val = self
            .lower_expr(m1_expr)?
            .ok_or_else(|| CodegenError::Internal("ExceptT.>>: m1 has no value".to_string()))?;
        let m2_val = self
            .lower_expr(m2_expr)?
            .ok_or_else(|| CodegenError::Internal("ExceptT.>>: m2 has no value".to_string()))?;

        let ptr_type = self.type_mapper().ptr_type();
        let fn_name = "bhc_except_t_then";

        let func = self.get_or_create_transformer_fn(fn_name);
        if func.count_basic_blocks() == 0 {
            let entry = self.llvm_ctx.append_basic_block(func, "entry");
            let right_bb = self.llvm_ctx.append_basic_block(func, "right");
            let left_bb = self.llvm_ctx.append_basic_block(func, "left");
            let saved_bb = self.builder().get_insert_block();
            self.builder().position_at_end(entry);
            let env = func.get_nth_param(0).unwrap().into_pointer_value();
            let m1 = self.extract_closure_env_elem(env, 2, 0)?;
            let m2 = self.extract_closure_env_elem(env, 2, 1)?;

            let fn_type = ptr_type.fn_type(&[ptr_type.into(), ptr_type.into()], false);
            let null_arg = ptr_type.const_null();

            // either = m1(_)
            let m1_fn = self.extract_closure_fn_ptr(m1)?;
            let either = self
                .builder()
                .build_indirect_call(
                    fn_type,
                    m1_fn,
                    &[m1.into(), null_arg.into()],
                    "except_then_either",
                )
                .map_err(|e| CodegenError::Internal(format!("ExceptT then m1: {:?}", e)))?
                .try_as_basic_value()
                .basic()
                .ok_or_else(|| CodegenError::Internal("ExceptT then m1: void".to_string()))?
                .into_pointer_value();

            let tag = self.extract_adt_tag(either)?;
            let tm = self.type_mapper();
            let is_right = self
                .builder()
                .build_int_compare(
                    inkwell::IntPredicate::EQ,
                    tag,
                    tm.i64_type().const_int(1, false),
                    "is_right",
                )
                .map_err(|e| CodegenError::Internal(format!("ExceptT then cmp: {:?}", e)))?;
            self.builder()
                .build_conditional_branch(is_right, right_bb, left_bb)
                .map_err(|e| CodegenError::Internal(format!("ExceptT then branch: {:?}", e)))?;

            // Left case: return either
            self.builder().position_at_end(left_bb);
            self.builder().build_return(Some(&either)).map_err(|e| {
                CodegenError::Internal(format!("except_t_then left return: {:?}", e))
            })?;

            // Right case: call m2(_)
            self.builder().position_at_end(right_bb);
            let m2_fn = self.extract_closure_fn_ptr(m2)?;
            let result = self
                .builder()
                .build_indirect_call(
                    fn_type,
                    m2_fn,
                    &[m2.into(), null_arg.into()],
                    "except_then_result",
                )
                .map_err(|e| CodegenError::Internal(format!("ExceptT then m2: {:?}", e)))?
                .try_as_basic_value()
                .basic()
                .ok_or_else(|| CodegenError::Internal("ExceptT then m2: void".to_string()))?;
            self.builder().build_return(Some(&result)).map_err(|e| {
                CodegenError::Internal(format!("except_t_then right return: {:?}", e))
            })?;

            if let Some(bb) = saved_bb {
                self.builder().position_at_end(bb);
            }
        }

        let fn_ptr = func.as_global_value().as_pointer_value();
        let m1_ptr = self.value_to_ptr(m1_val)?;
        let m2_ptr = self.value_to_ptr(m2_val)?;
        let closure_ptr = self.alloc_closure(
            fn_ptr,
            &[
                (VarId::new(900000), m1_ptr.into()),
                (VarId::new(900001), m2_ptr.into()),
            ],
        )?;
        Ok(Some(closure_ptr.into()))
    }

    /// catchE m handler = closure \_ -> let either = m(_) in case tag of
    ///   0 (Left) -> let e = field0; h = handler(e) in h(_)
    ///   1 (Right) -> either
    fn lower_builtin_catch_e(
        &mut self,
        m_expr: &Expr,
        handler_expr: &Expr,
    ) -> CodegenResult<Option<BasicValueEnum<'ctx>>> {
        // ExceptT over StateT: \(env, s) -> ...threads state...
        if self.transformer_stack.is_except_t_over_state_t() {
            return self.lower_builtin_catch_e_over_st(m_expr, handler_expr);
        }
        // ExceptT over ReaderT: \(env, r) -> ...threads reader-env...
        if self.transformer_stack.is_except_t_over_reader_t() {
            return self.lower_builtin_catch_e_over_rt(m_expr, handler_expr);
        }

        let m_val = self
            .lower_expr(m_expr)?
            .ok_or_else(|| CodegenError::Internal("catchE: m has no value".to_string()))?;
        let handler_val = self
            .lower_expr(handler_expr)?
            .ok_or_else(|| CodegenError::Internal("catchE: handler has no value".to_string()))?;

        let ptr_type = self.type_mapper().ptr_type();
        let fn_name = "bhc_except_t_catch";

        let func = self.get_or_create_transformer_fn(fn_name);
        if func.count_basic_blocks() == 0 {
            let entry = self.llvm_ctx.append_basic_block(func, "entry");
            let right_bb = self.llvm_ctx.append_basic_block(func, "right");
            let left_bb = self.llvm_ctx.append_basic_block(func, "left");
            let saved_bb = self.builder().get_insert_block();
            self.builder().position_at_end(entry);
            let env = func.get_nth_param(0).unwrap().into_pointer_value();
            let m = self.extract_closure_env_elem(env, 2, 0)?;
            let handler = self.extract_closure_env_elem(env, 2, 1)?;

            let fn_type = ptr_type.fn_type(&[ptr_type.into(), ptr_type.into()], false);
            let null_arg = ptr_type.const_null();

            // either = m(_)
            let m_fn = self.extract_closure_fn_ptr(m)?;
            let either = self
                .builder()
                .build_indirect_call(fn_type, m_fn, &[m.into(), null_arg.into()], "catch_either")
                .map_err(|e| CodegenError::Internal(format!("catchE m: {:?}", e)))?
                .try_as_basic_value()
                .basic()
                .ok_or_else(|| CodegenError::Internal("catchE m: void".to_string()))?
                .into_pointer_value();

            let tag = self.extract_adt_tag(either)?;
            let tm = self.type_mapper();
            let is_right = self
                .builder()
                .build_int_compare(
                    inkwell::IntPredicate::EQ,
                    tag,
                    tm.i64_type().const_int(1, false),
                    "is_right",
                )
                .map_err(|e| CodegenError::Internal(format!("catchE tag cmp: {:?}", e)))?;
            self.builder()
                .build_conditional_branch(is_right, right_bb, left_bb)
                .map_err(|e| CodegenError::Internal(format!("catchE branch: {:?}", e)))?;

            // Right case: pass through
            self.builder().position_at_end(right_bb);
            self.builder()
                .build_return(Some(&either))
                .map_err(|e| CodegenError::Internal(format!("catch_e right return: {:?}", e)))?;

            // Left case: run handler
            self.builder().position_at_end(left_bb);
            let e = self.extract_adt_field(either, 1, 0)?;
            let handler_fn = self.extract_closure_fn_ptr(handler)?;
            let h = self
                .builder()
                .build_indirect_call(
                    fn_type,
                    handler_fn,
                    &[handler.into(), e.into()],
                    "catch_handler",
                )
                .map_err(|e| CodegenError::Internal(format!("catchE handler: {:?}", e)))?
                .try_as_basic_value()
                .basic()
                .ok_or_else(|| CodegenError::Internal("catchE handler: void".to_string()))?
                .into_pointer_value();
            let h_fn = self.extract_closure_fn_ptr(h)?;
            let result = self
                .builder()
                .build_indirect_call(fn_type, h_fn, &[h.into(), null_arg.into()], "catch_result")
                .map_err(|e| CodegenError::Internal(format!("catchE h: {:?}", e)))?
                .try_as_basic_value()
                .basic()
                .ok_or_else(|| CodegenError::Internal("catchE h: void".to_string()))?;
            self.builder()
                .build_return(Some(&result))
                .map_err(|e| CodegenError::Internal(format!("catch_e left return: {:?}", e)))?;

            if let Some(bb) = saved_bb {
                self.builder().position_at_end(bb);
            }
        }

        let fn_ptr = func.as_global_value().as_pointer_value();
        let m_ptr = self.value_to_ptr(m_val)?;
        let handler_ptr = self.value_to_ptr(handler_val)?;
        let closure_ptr = self.alloc_closure(
            fn_ptr,
            &[
                (VarId::new(900000), m_ptr.into()),
                (VarId::new(900001), handler_ptr.into()),
            ],
        )?;
        Ok(Some(closure_ptr.into()))
    }

    /// ExceptT.lift io = closure \_ -> let a = io in Right(a)
    fn lower_builtin_except_t_lift(
        &mut self,
        io_expr: &Expr,
    ) -> CodegenResult<Option<BasicValueEnum<'ctx>>> {
        // In nested contexts, the inner action is a closure that needs
        // state/reader-env threaded through. Route to nested lift functions.
        if self.transformer_stack.is_except_t_over_state_t() {
            let io_val = self.lower_expr(io_expr)?.ok_or_else(|| {
                CodegenError::Internal("ExceptT.lift/st: io has no value".to_string())
            })?;
            return Ok(Some(self.apply_except_t_lift_to_value_over_st(io_val)?));
        }
        if self.transformer_stack.is_except_t_over_reader_t() {
            let io_val = self.lower_expr(io_expr)?.ok_or_else(|| {
                CodegenError::Internal("ExceptT.lift/rt: io has no value".to_string())
            })?;
            return Ok(Some(self.apply_except_t_lift_to_value_over_rt(io_val)?));
        }

        let io_val = self
            .lower_expr(io_expr)?
            .ok_or_else(|| CodegenError::Internal("ExceptT.lift: io has no value".to_string()))?;

        let _ptr_type = self.type_mapper().ptr_type();
        let fn_name = "bhc_except_t_lift";

        let func = self.get_or_create_transformer_fn(fn_name);
        if func.count_basic_blocks() == 0 {
            let entry = self.llvm_ctx.append_basic_block(func, "entry");
            let saved_bb = self.builder().get_insert_block();
            self.builder().position_at_end(entry);
            // \(env, _) -> Right(io_result) where io = env[0]
            let env = func.get_nth_param(0).unwrap().into_pointer_value();
            let io = self.extract_closure_env_elem(env, 1, 0)?;
            // For now, IO actions are already executed, so just wrap the value
            // Allocate Right ADT: tag=1, arity=1
            let right_adt = self.alloc_adt(1, 1)?;
            self.store_adt_field(right_adt, 1, 0, io.into())?;
            self.builder()
                .build_return(Some(&right_adt))
                .map_err(|e| CodegenError::Internal(format!("except_t_lift return: {:?}", e)))?;
            if let Some(bb) = saved_bb {
                self.builder().position_at_end(bb);
            }
        }

        let fn_ptr = func.as_global_value().as_pointer_value();
        let io_ptr = self.value_to_ptr(io_val)?;
        let closure_ptr = self.alloc_closure(fn_ptr, &[(VarId::new(900000), io_ptr.into())])?;
        Ok(Some(closure_ptr.into()))
    }

    /// ExceptT.fmap f m = closure \_ -> let either = m(_) in case tag of
    ///   0 (Left) -> either
    ///   1 (Right) -> Right(f(field0))
    fn lower_builtin_except_t_fmap(
        &mut self,
        f_expr: &Expr,
        m_expr: &Expr,
    ) -> CodegenResult<Option<BasicValueEnum<'ctx>>> {
        let f_val = self
            .lower_expr(f_expr)?
            .ok_or_else(|| CodegenError::Internal("ExceptT.fmap: f has no value".to_string()))?;
        let m_val = self
            .lower_expr(m_expr)?
            .ok_or_else(|| CodegenError::Internal("ExceptT.fmap: m has no value".to_string()))?;

        let ptr_type = self.type_mapper().ptr_type();
        let fn_name = "bhc_except_t_fmap";

        let func = self.get_or_create_transformer_fn(fn_name);
        if func.count_basic_blocks() == 0 {
            let entry = self.llvm_ctx.append_basic_block(func, "entry");
            let right_bb = self.llvm_ctx.append_basic_block(func, "right");
            let left_bb = self.llvm_ctx.append_basic_block(func, "left");
            let saved_bb = self.builder().get_insert_block();
            self.builder().position_at_end(entry);
            let env = func.get_nth_param(0).unwrap().into_pointer_value();
            let f = self.extract_closure_env_elem(env, 2, 0)?;
            let m = self.extract_closure_env_elem(env, 2, 1)?;

            let fn_type = ptr_type.fn_type(&[ptr_type.into(), ptr_type.into()], false);
            let null_arg = ptr_type.const_null();

            let m_fn = self.extract_closure_fn_ptr(m)?;
            let either = self
                .builder()
                .build_indirect_call(fn_type, m_fn, &[m.into(), null_arg.into()], "fmap_either")
                .map_err(|e| CodegenError::Internal(format!("ExceptT fmap m: {:?}", e)))?
                .try_as_basic_value()
                .basic()
                .ok_or_else(|| CodegenError::Internal("ExceptT fmap m: void".to_string()))?
                .into_pointer_value();

            let tag = self.extract_adt_tag(either)?;
            let tm = self.type_mapper();
            let is_right = self
                .builder()
                .build_int_compare(
                    inkwell::IntPredicate::EQ,
                    tag,
                    tm.i64_type().const_int(1, false),
                    "is_right",
                )
                .map_err(|e| CodegenError::Internal(format!("ExceptT fmap cmp: {:?}", e)))?;
            self.builder()
                .build_conditional_branch(is_right, right_bb, left_bb)
                .map_err(|e| CodegenError::Internal(format!("ExceptT fmap branch: {:?}", e)))?;

            // Left case: pass through
            self.builder().position_at_end(left_bb);
            self.builder().build_return(Some(&either)).map_err(|e| {
                CodegenError::Internal(format!("except_t_fmap left return: {:?}", e))
            })?;

            // Right case: apply f
            self.builder().position_at_end(right_bb);
            let a = self.extract_adt_field(either, 1, 0)?;
            let f_fn = self.extract_closure_fn_ptr(f)?;
            let b = self
                .builder()
                .build_indirect_call(fn_type, f_fn, &[f.into(), a.into()], "fmap_b")
                .map_err(|e| CodegenError::Internal(format!("ExceptT fmap f: {:?}", e)))?
                .try_as_basic_value()
                .basic()
                .ok_or_else(|| CodegenError::Internal("ExceptT fmap f: void".to_string()))?;
            let right_adt = self.alloc_adt(1, 1)?;
            self.store_adt_field(right_adt, 1, 0, b)?;
            self.builder().build_return(Some(&right_adt)).map_err(|e| {
                CodegenError::Internal(format!("except_t_fmap right return: {:?}", e))
            })?;

            if let Some(bb) = saved_bb {
                self.builder().position_at_end(bb);
            }
        }

        let fn_ptr = func.as_global_value().as_pointer_value();
        let f_ptr = self.value_to_ptr(f_val)?;
        let m_ptr = self.value_to_ptr(m_val)?;
        let closure_ptr = self.alloc_closure(
            fn_ptr,
            &[
                (VarId::new(900000), f_ptr.into()),
                (VarId::new(900001), m_ptr.into()),
            ],
        )?;
        Ok(Some(closure_ptr.into()))
    }

    /// ExceptT.<*> mf ma — applicative apply
    fn lower_builtin_except_t_ap(
        &mut self,
        mf_expr: &Expr,
        ma_expr: &Expr,
    ) -> CodegenResult<Option<BasicValueEnum<'ctx>>> {
        let mf_val = self
            .lower_expr(mf_expr)?
            .ok_or_else(|| CodegenError::Internal("ExceptT.<*>: mf has no value".to_string()))?;
        let ma_val = self
            .lower_expr(ma_expr)?
            .ok_or_else(|| CodegenError::Internal("ExceptT.<*>: ma has no value".to_string()))?;

        let ptr_type = self.type_mapper().ptr_type();
        let fn_name = "bhc_except_t_ap";

        let func = self.get_or_create_transformer_fn(fn_name);
        if func.count_basic_blocks() == 0 {
            let entry = self.llvm_ctx.append_basic_block(func, "entry");
            let mf_right_bb = self.llvm_ctx.append_basic_block(func, "mf_right");
            let mf_left_bb = self.llvm_ctx.append_basic_block(func, "mf_left");
            let ma_right_bb = self.llvm_ctx.append_basic_block(func, "ma_right");
            let ma_left_bb = self.llvm_ctx.append_basic_block(func, "ma_left");
            let saved_bb = self.builder().get_insert_block();
            self.builder().position_at_end(entry);
            let env = func.get_nth_param(0).unwrap().into_pointer_value();
            let mf = self.extract_closure_env_elem(env, 2, 0)?;
            let ma = self.extract_closure_env_elem(env, 2, 1)?;

            let fn_type = ptr_type.fn_type(&[ptr_type.into(), ptr_type.into()], false);
            let null_arg = ptr_type.const_null();
            let tm = self.type_mapper();

            // either_f = mf(_)
            let mf_fn = self.extract_closure_fn_ptr(mf)?;
            let either_f = self
                .builder()
                .build_indirect_call(fn_type, mf_fn, &[mf.into(), null_arg.into()], "ap_either_f")
                .map_err(|e| CodegenError::Internal(format!("ExceptT ap mf: {:?}", e)))?
                .try_as_basic_value()
                .basic()
                .ok_or_else(|| CodegenError::Internal("ExceptT ap mf: void".to_string()))?
                .into_pointer_value();

            let tag_f = self.extract_adt_tag(either_f)?;
            let is_right_f = self
                .builder()
                .build_int_compare(
                    inkwell::IntPredicate::EQ,
                    tag_f,
                    tm.i64_type().const_int(1, false),
                    "is_right_f",
                )
                .map_err(|e| CodegenError::Internal(format!("ExceptT ap tag_f cmp: {:?}", e)))?;
            self.builder()
                .build_conditional_branch(is_right_f, mf_right_bb, mf_left_bb)
                .map_err(|e| CodegenError::Internal(format!("ExceptT ap branch_f: {:?}", e)))?;

            // mf Left: return either_f
            self.builder().position_at_end(mf_left_bb);
            self.builder().build_return(Some(&either_f)).map_err(|e| {
                CodegenError::Internal(format!("except_t_ap mf_left return: {:?}", e))
            })?;

            // mf Right: run ma
            self.builder().position_at_end(mf_right_bb);
            let f = self.extract_adt_field(either_f, 1, 0)?;
            let ma_fn = self.extract_closure_fn_ptr(ma)?;
            let either_a = self
                .builder()
                .build_indirect_call(fn_type, ma_fn, &[ma.into(), null_arg.into()], "ap_either_a")
                .map_err(|e| CodegenError::Internal(format!("ExceptT ap ma: {:?}", e)))?
                .try_as_basic_value()
                .basic()
                .ok_or_else(|| CodegenError::Internal("ExceptT ap ma: void".to_string()))?
                .into_pointer_value();

            let tag_a = self.extract_adt_tag(either_a)?;
            let is_right_a = self
                .builder()
                .build_int_compare(
                    inkwell::IntPredicate::EQ,
                    tag_a,
                    tm.i64_type().const_int(1, false),
                    "is_right_a",
                )
                .map_err(|e| CodegenError::Internal(format!("ExceptT ap tag_a cmp: {:?}", e)))?;
            self.builder()
                .build_conditional_branch(is_right_a, ma_right_bb, ma_left_bb)
                .map_err(|e| CodegenError::Internal(format!("ExceptT ap branch_a: {:?}", e)))?;

            // ma Left: return either_a
            self.builder().position_at_end(ma_left_bb);
            self.builder().build_return(Some(&either_a)).map_err(|e| {
                CodegenError::Internal(format!("except_t_ap ma_left return: {:?}", e))
            })?;

            // Both Right: apply f to a
            self.builder().position_at_end(ma_right_bb);
            let a = self.extract_adt_field(either_a, 1, 0)?;
            let f_fn = self.extract_closure_fn_ptr(f)?;
            let b = self
                .builder()
                .build_indirect_call(fn_type, f_fn, &[f.into(), a.into()], "ap_b")
                .map_err(|e| CodegenError::Internal(format!("ExceptT ap f: {:?}", e)))?
                .try_as_basic_value()
                .basic()
                .ok_or_else(|| CodegenError::Internal("ExceptT ap f: void".to_string()))?;
            let right_adt = self.alloc_adt(1, 1)?;
            self.store_adt_field(right_adt, 1, 0, b)?;
            self.builder().build_return(Some(&right_adt)).map_err(|e| {
                CodegenError::Internal(format!("except_t_ap ma_right return: {:?}", e))
            })?;

            if let Some(bb) = saved_bb {
                self.builder().position_at_end(bb);
            }
        }

        let fn_ptr = func.as_global_value().as_pointer_value();
        let mf_ptr = self.value_to_ptr(mf_val)?;
        let ma_ptr = self.value_to_ptr(ma_val)?;
        let closure_ptr = self.alloc_closure(
            fn_ptr,
            &[
                (VarId::new(900000), mf_ptr.into()),
                (VarId::new(900001), ma_ptr.into()),
            ],
        )?;
        Ok(Some(closure_ptr.into()))
    }

    // ========================================================================
    // ExceptT over StateT — Nested Transformer Operations
    // All closures use (env, state) -> (Either, state') convention
    // ========================================================================

    /// ExceptT.pure over StateT: \(env, s) -> (Right x, s)
    fn lower_builtin_except_t_pure_over_st(
        &mut self,
        x_expr: &Expr,
    ) -> CodegenResult<Option<BasicValueEnum<'ctx>>> {
        let x_val = self
            .lower_expr(x_expr)?
            .ok_or_else(|| CodegenError::Internal("ExceptT.pure/st: x has no value".to_string()))?;

        let fn_name = "bhc_except_t_pure_over_st";
        let func = self.get_or_create_transformer_fn(fn_name);
        if func.count_basic_blocks() == 0 {
            let entry = self.llvm_ctx.append_basic_block(func, "entry");
            let saved_bb = self.builder().get_insert_block();
            self.builder().position_at_end(entry);
            // \(env, s) -> (Right(x), s) where x = env[0]
            let env = func.get_nth_param(0).unwrap().into_pointer_value();
            let s = func.get_nth_param(1).unwrap().into_pointer_value();
            let x = self.extract_closure_env_elem(env, 1, 0)?;
            let right_adt = self.alloc_adt(1, 1)?;
            self.store_adt_field(right_adt, 1, 0, x.into())?;
            let pair = self.alloc_pair(right_adt.into(), s.into())?;
            self.builder().build_return(Some(&pair)).map_err(|e| {
                CodegenError::Internal(format!("except_t_pure_over_st return: {:?}", e))
            })?;
            if let Some(bb) = saved_bb {
                self.builder().position_at_end(bb);
            }
        }

        let fn_ptr = func.as_global_value().as_pointer_value();
        let x_ptr = self.value_to_ptr(x_val)?;
        let closure_ptr = self.alloc_closure(fn_ptr, &[(VarId::new(900000), x_ptr.into())])?;
        Ok(Some(closure_ptr.into()))
    }

    /// throwE over StateT: \(env, s) -> (Left e, s)
    fn lower_builtin_throw_e_over_st(
        &mut self,
        e_expr: &Expr,
    ) -> CodegenResult<Option<BasicValueEnum<'ctx>>> {
        let e_val = self
            .lower_expr(e_expr)?
            .ok_or_else(|| CodegenError::Internal("throwE/st: e has no value".to_string()))?;

        let fn_name = "bhc_except_t_throw_over_st";
        let func = self.get_or_create_transformer_fn(fn_name);
        if func.count_basic_blocks() == 0 {
            let entry = self.llvm_ctx.append_basic_block(func, "entry");
            let saved_bb = self.builder().get_insert_block();
            self.builder().position_at_end(entry);
            // \(env, s) -> (Left(e), s) where e = env[0]
            let env = func.get_nth_param(0).unwrap().into_pointer_value();
            let s = func.get_nth_param(1).unwrap().into_pointer_value();
            let e = self.extract_closure_env_elem(env, 1, 0)?;
            let left_adt = self.alloc_adt(0, 1)?;
            self.store_adt_field(left_adt, 1, 0, e.into())?;
            let pair = self.alloc_pair(left_adt.into(), s.into())?;
            self.builder()
                .build_return(Some(&pair))
                .map_err(|e| CodegenError::Internal(format!("throw_e_over_st return: {:?}", e)))?;
            if let Some(bb) = saved_bb {
                self.builder().position_at_end(bb);
            }
        }

        let fn_ptr = func.as_global_value().as_pointer_value();
        let e_ptr = self.value_to_ptr(e_val)?;
        let closure_ptr = self.alloc_closure(fn_ptr, &[(VarId::new(900000), e_ptr.into())])?;
        Ok(Some(closure_ptr.into()))
    }

    /// ExceptT.>>= over StateT:
    /// \(env, s) ->
    ///   pair = m(m, s);  either = fst(pair), s' = snd(pair)
    ///   Left:  return (either, s')
    ///   Right: a = field0(either); kr = k(k, a); result_pair = kr(kr, s'); return result_pair
    fn lower_builtin_except_t_bind_over_st(
        &mut self,
        m_expr: &Expr,
        k_expr: &Expr,
    ) -> CodegenResult<Option<BasicValueEnum<'ctx>>> {
        let m_val = self
            .lower_expr(m_expr)?
            .ok_or_else(|| CodegenError::Internal("ExceptT.>>=/st: m has no value".to_string()))?;
        let k_val = self
            .lower_expr(k_expr)?
            .ok_or_else(|| CodegenError::Internal("ExceptT.>>=/st: k has no value".to_string()))?;

        let ptr_type = self.type_mapper().ptr_type();
        let fn_name = "bhc_except_t_bind_over_st";
        let func = self.get_or_create_transformer_fn(fn_name);
        if func.count_basic_blocks() == 0 {
            let entry = self.llvm_ctx.append_basic_block(func, "entry");
            let right_bb = self.llvm_ctx.append_basic_block(func, "right");
            let left_bb = self.llvm_ctx.append_basic_block(func, "left");
            let saved_bb = self.builder().get_insert_block();
            self.builder().position_at_end(entry);
            let env = func.get_nth_param(0).unwrap().into_pointer_value();
            let s = func.get_nth_param(1).unwrap().into_pointer_value();
            let m = self.extract_closure_env_elem(env, 2, 0)?;
            let k = self.extract_closure_env_elem(env, 2, 1)?;

            let fn_type2 = ptr_type.fn_type(&[ptr_type.into(), ptr_type.into()], false);

            // pair = m(m, s)
            let m_fn = self.extract_closure_fn_ptr(m)?;
            let pair = self
                .builder()
                .build_indirect_call(fn_type2, m_fn, &[m.into(), s.into()], "bind_st_pair")
                .map_err(|e| CodegenError::Internal(format!("ExceptT bind/st m: {:?}", e)))?
                .try_as_basic_value()
                .basic()
                .ok_or_else(|| CodegenError::Internal("ExceptT bind/st m: void".to_string()))?
                .into_pointer_value();

            let either = self.extract_pair_fst(pair)?;
            let s_prime = self.extract_pair_snd(pair)?;

            let tag = self.extract_adt_tag(either)?;
            let is_right = self
                .builder()
                .build_int_compare(
                    inkwell::IntPredicate::EQ,
                    tag,
                    self.type_mapper().i64_type().const_int(1, false),
                    "is_right",
                )
                .map_err(|e| CodegenError::Internal(format!("bind/st tag cmp: {:?}", e)))?;
            self.builder()
                .build_conditional_branch(is_right, right_bb, left_bb)
                .map_err(|e| CodegenError::Internal(format!("bind/st branch: {:?}", e)))?;

            // Left: return (either, s')
            self.builder().position_at_end(left_bb);
            let left_pair = self.alloc_pair(either.into(), s_prime.into())?;
            self.builder()
                .build_return(Some(&left_pair))
                .map_err(|e| CodegenError::Internal(format!("bind/st left return: {:?}", e)))?;

            // Right: a = field0(either); kr = k(k, a); result = kr(kr, s')
            self.builder().position_at_end(right_bb);
            let a = self.extract_adt_field(either, 1, 0)?;
            let k_fn = self.extract_closure_fn_ptr(k)?;
            let kr = self
                .builder()
                .build_indirect_call(fn_type2, k_fn, &[k.into(), a.into()], "bind_st_kr")
                .map_err(|e| CodegenError::Internal(format!("ExceptT bind/st k: {:?}", e)))?
                .try_as_basic_value()
                .basic()
                .ok_or_else(|| CodegenError::Internal("ExceptT bind/st k: void".to_string()))?
                .into_pointer_value();
            let kr_fn = self.extract_closure_fn_ptr(kr)?;
            let result = self
                .builder()
                .build_indirect_call(
                    fn_type2,
                    kr_fn,
                    &[kr.into(), s_prime.into()],
                    "bind_st_result",
                )
                .map_err(|e| CodegenError::Internal(format!("ExceptT bind/st kr: {:?}", e)))?
                .try_as_basic_value()
                .basic()
                .ok_or_else(|| CodegenError::Internal("ExceptT bind/st kr: void".to_string()))?;
            self.builder()
                .build_return(Some(&result))
                .map_err(|e| CodegenError::Internal(format!("bind/st right return: {:?}", e)))?;

            if let Some(bb) = saved_bb {
                self.builder().position_at_end(bb);
            }
        }

        let fn_ptr = func.as_global_value().as_pointer_value();
        let m_ptr = self.value_to_ptr(m_val)?;
        let k_ptr = self.value_to_ptr(k_val)?;
        let closure_ptr = self.alloc_closure(
            fn_ptr,
            &[
                (VarId::new(900000), m_ptr.into()),
                (VarId::new(900001), k_ptr.into()),
            ],
        )?;
        Ok(Some(closure_ptr.into()))
    }

    /// ExceptT.>> over StateT:
    /// \(env, s) ->
    ///   pair = m1(m1, s);  either = fst(pair), s' = snd(pair)
    ///   Left:  return (either, s')
    ///   Right: return m2(m2, s')
    fn lower_builtin_except_t_then_over_st(
        &mut self,
        m1_expr: &Expr,
        m2_expr: &Expr,
    ) -> CodegenResult<Option<BasicValueEnum<'ctx>>> {
        let m1_val = self
            .lower_expr(m1_expr)?
            .ok_or_else(|| CodegenError::Internal("ExceptT.>>/st: m1 has no value".to_string()))?;
        let m2_val = self
            .lower_expr(m2_expr)?
            .ok_or_else(|| CodegenError::Internal("ExceptT.>>/st: m2 has no value".to_string()))?;

        let ptr_type = self.type_mapper().ptr_type();
        let fn_name = "bhc_except_t_then_over_st";
        let func = self.get_or_create_transformer_fn(fn_name);
        if func.count_basic_blocks() == 0 {
            let entry = self.llvm_ctx.append_basic_block(func, "entry");
            let right_bb = self.llvm_ctx.append_basic_block(func, "right");
            let left_bb = self.llvm_ctx.append_basic_block(func, "left");
            let saved_bb = self.builder().get_insert_block();
            self.builder().position_at_end(entry);
            let env = func.get_nth_param(0).unwrap().into_pointer_value();
            let s = func.get_nth_param(1).unwrap().into_pointer_value();
            let m1 = self.extract_closure_env_elem(env, 2, 0)?;
            let m2 = self.extract_closure_env_elem(env, 2, 1)?;

            let fn_type2 = ptr_type.fn_type(&[ptr_type.into(), ptr_type.into()], false);

            // pair = m1(m1, s)
            let m1_fn = self.extract_closure_fn_ptr(m1)?;
            let pair = self
                .builder()
                .build_indirect_call(fn_type2, m1_fn, &[m1.into(), s.into()], "then_st_pair")
                .map_err(|e| CodegenError::Internal(format!("ExceptT then/st m1: {:?}", e)))?
                .try_as_basic_value()
                .basic()
                .ok_or_else(|| CodegenError::Internal("ExceptT then/st m1: void".to_string()))?
                .into_pointer_value();

            let either = self.extract_pair_fst(pair)?;
            let s_prime = self.extract_pair_snd(pair)?;

            let tag = self.extract_adt_tag(either)?;
            let is_right = self
                .builder()
                .build_int_compare(
                    inkwell::IntPredicate::EQ,
                    tag,
                    self.type_mapper().i64_type().const_int(1, false),
                    "is_right",
                )
                .map_err(|e| CodegenError::Internal(format!("then/st tag cmp: {:?}", e)))?;
            self.builder()
                .build_conditional_branch(is_right, right_bb, left_bb)
                .map_err(|e| CodegenError::Internal(format!("then/st branch: {:?}", e)))?;

            // Left: return (either, s')
            self.builder().position_at_end(left_bb);
            let left_pair = self.alloc_pair(either.into(), s_prime.into())?;
            self.builder()
                .build_return(Some(&left_pair))
                .map_err(|e| CodegenError::Internal(format!("then/st left return: {:?}", e)))?;

            // Right: return m2(m2, s')
            self.builder().position_at_end(right_bb);
            let m2_fn = self.extract_closure_fn_ptr(m2)?;
            let result = self
                .builder()
                .build_indirect_call(
                    fn_type2,
                    m2_fn,
                    &[m2.into(), s_prime.into()],
                    "then_st_result",
                )
                .map_err(|e| CodegenError::Internal(format!("ExceptT then/st m2: {:?}", e)))?
                .try_as_basic_value()
                .basic()
                .ok_or_else(|| CodegenError::Internal("ExceptT then/st m2: void".to_string()))?;
            self.builder()
                .build_return(Some(&result))
                .map_err(|e| CodegenError::Internal(format!("then/st right return: {:?}", e)))?;

            if let Some(bb) = saved_bb {
                self.builder().position_at_end(bb);
            }
        }

        let fn_ptr = func.as_global_value().as_pointer_value();
        let m1_ptr = self.value_to_ptr(m1_val)?;
        let m2_ptr = self.value_to_ptr(m2_val)?;
        let closure_ptr = self.alloc_closure(
            fn_ptr,
            &[
                (VarId::new(900000), m1_ptr.into()),
                (VarId::new(900001), m2_ptr.into()),
            ],
        )?;
        Ok(Some(closure_ptr.into()))
    }

    /// catchE over StateT:
    /// \(env, s) ->
    ///   pair = m(m, s);  either = fst(pair), s' = snd(pair)
    ///   Right: return (either, s')
    ///   Left:  e = field0(either); hr = handler(handler, e); return hr(hr, s')
    fn lower_builtin_catch_e_over_st(
        &mut self,
        m_expr: &Expr,
        handler_expr: &Expr,
    ) -> CodegenResult<Option<BasicValueEnum<'ctx>>> {
        let m_val = self
            .lower_expr(m_expr)?
            .ok_or_else(|| CodegenError::Internal("catchE/st: m has no value".to_string()))?;
        let handler_val = self
            .lower_expr(handler_expr)?
            .ok_or_else(|| CodegenError::Internal("catchE/st: handler has no value".to_string()))?;

        let ptr_type = self.type_mapper().ptr_type();
        let fn_name = "bhc_except_t_catch_over_st";
        let func = self.get_or_create_transformer_fn(fn_name);
        if func.count_basic_blocks() == 0 {
            let entry = self.llvm_ctx.append_basic_block(func, "entry");
            let right_bb = self.llvm_ctx.append_basic_block(func, "right");
            let left_bb = self.llvm_ctx.append_basic_block(func, "left");
            let saved_bb = self.builder().get_insert_block();
            self.builder().position_at_end(entry);
            let env = func.get_nth_param(0).unwrap().into_pointer_value();
            let s = func.get_nth_param(1).unwrap().into_pointer_value();
            let m = self.extract_closure_env_elem(env, 2, 0)?;
            let handler = self.extract_closure_env_elem(env, 2, 1)?;

            let fn_type2 = ptr_type.fn_type(&[ptr_type.into(), ptr_type.into()], false);

            // pair = m(m, s)
            let m_fn = self.extract_closure_fn_ptr(m)?;
            let pair = self
                .builder()
                .build_indirect_call(fn_type2, m_fn, &[m.into(), s.into()], "catch_st_pair")
                .map_err(|e| CodegenError::Internal(format!("catchE/st m: {:?}", e)))?
                .try_as_basic_value()
                .basic()
                .ok_or_else(|| CodegenError::Internal("catchE/st m: void".to_string()))?
                .into_pointer_value();

            let either = self.extract_pair_fst(pair)?;
            let s_prime = self.extract_pair_snd(pair)?;

            let tag = self.extract_adt_tag(either)?;
            let is_right = self
                .builder()
                .build_int_compare(
                    inkwell::IntPredicate::EQ,
                    tag,
                    self.type_mapper().i64_type().const_int(1, false),
                    "is_right",
                )
                .map_err(|e| CodegenError::Internal(format!("catch/st tag cmp: {:?}", e)))?;
            self.builder()
                .build_conditional_branch(is_right, right_bb, left_bb)
                .map_err(|e| CodegenError::Internal(format!("catch/st branch: {:?}", e)))?;

            // Right: return (either, s')
            self.builder().position_at_end(right_bb);
            let right_pair = self.alloc_pair(either.into(), s_prime.into())?;
            self.builder()
                .build_return(Some(&right_pair))
                .map_err(|e| CodegenError::Internal(format!("catch/st right return: {:?}", e)))?;

            // Left: e = field0(either); hr = handler(handler, e); return hr(hr, s')
            self.builder().position_at_end(left_bb);
            let e = self.extract_adt_field(either, 1, 0)?;
            let handler_fn = self.extract_closure_fn_ptr(handler)?;
            let hr = self
                .builder()
                .build_indirect_call(
                    fn_type2,
                    handler_fn,
                    &[handler.into(), e.into()],
                    "catch_st_hr",
                )
                .map_err(|e| CodegenError::Internal(format!("catchE/st handler: {:?}", e)))?
                .try_as_basic_value()
                .basic()
                .ok_or_else(|| CodegenError::Internal("catchE/st handler: void".to_string()))?
                .into_pointer_value();
            let hr_fn = self.extract_closure_fn_ptr(hr)?;
            let result = self
                .builder()
                .build_indirect_call(
                    fn_type2,
                    hr_fn,
                    &[hr.into(), s_prime.into()],
                    "catch_st_result",
                )
                .map_err(|e| CodegenError::Internal(format!("catchE/st hr: {:?}", e)))?
                .try_as_basic_value()
                .basic()
                .ok_or_else(|| CodegenError::Internal("catchE/st hr: void".to_string()))?;
            self.builder()
                .build_return(Some(&result))
                .map_err(|e| CodegenError::Internal(format!("catch/st left return: {:?}", e)))?;

            if let Some(bb) = saved_bb {
                self.builder().position_at_end(bb);
            }
        }

        let fn_ptr = func.as_global_value().as_pointer_value();
        let m_ptr = self.value_to_ptr(m_val)?;
        let handler_ptr = self.value_to_ptr(handler_val)?;
        let closure_ptr = self.alloc_closure(
            fn_ptr,
            &[
                (VarId::new(900000), m_ptr.into()),
                (VarId::new(900001), handler_ptr.into()),
            ],
        )?;
        Ok(Some(closure_ptr.into()))
    }

    /// apply_except_t_lift over StateT: \(env, s) -> (Right(fst(action(action, s))), snd(action(action, s)))
    /// Lifts a StateT action into ExceptT-over-StateT by wrapping result in Right.
    fn apply_except_t_lift_to_value_over_st(
        &mut self,
        action_val: BasicValueEnum<'ctx>,
    ) -> CodegenResult<BasicValueEnum<'ctx>> {
        let fn_name = "bhc_except_t_lift_auto_over_st";
        let func = self.get_or_create_transformer_fn(fn_name);
        if func.count_basic_blocks() == 0 {
            let entry = self.llvm_ctx.append_basic_block(func, "entry");
            let saved_bb = self.builder().get_insert_block();
            self.builder().position_at_end(entry);
            let env = func.get_nth_param(0).unwrap().into_pointer_value();
            let s = func.get_nth_param(1).unwrap().into_pointer_value();
            let action = self.extract_closure_env_elem(env, 1, 0)?;

            let ptr_type = self.type_mapper().ptr_type();
            let fn_type2 = ptr_type.fn_type(&[ptr_type.into(), ptr_type.into()], false);

            // pair = action(action, s) — StateT action returns (value, s')
            let action_fn = self.extract_closure_fn_ptr(action)?;
            let pair = self
                .builder()
                .build_indirect_call(
                    fn_type2,
                    action_fn,
                    &[action.into(), s.into()],
                    "lift_st_pair",
                )
                .map_err(|e| CodegenError::Internal(format!("except_t_lift/st action: {:?}", e)))?
                .try_as_basic_value()
                .basic()
                .ok_or_else(|| CodegenError::Internal("except_t_lift/st: void".to_string()))?
                .into_pointer_value();

            let a = self.extract_pair_fst(pair)?;
            let s_prime = self.extract_pair_snd(pair)?;

            // Wrap a in Right
            let right_adt = self.alloc_adt(1, 1)?;
            self.store_adt_field(right_adt, 1, 0, a.into())?;

            let result = self.alloc_pair(right_adt.into(), s_prime.into())?;
            self.builder()
                .build_return(Some(&result))
                .map_err(|e| CodegenError::Internal(format!("except_t_lift/st return: {:?}", e)))?;
            if let Some(bb) = saved_bb {
                self.builder().position_at_end(bb);
            }
        }

        let fn_ptr = func.as_global_value().as_pointer_value();
        let action_ptr = self.value_to_ptr(action_val)?;
        let closure_ptr = self.alloc_closure(fn_ptr, &[(VarId::new(900102), action_ptr.into())])?;
        Ok(closure_ptr.into())
    }

    // ========================================================================
    // ExceptT over ReaderT — Nested Transformer Operations
    // All closures use (env, reader_env) -> Either convention
    // ========================================================================

    /// ExceptT.>>= over ReaderT:
    /// \(env, r) ->
    ///   either = m(m, r)
    ///   Left:  return either
    ///   Right: a = field0(either); kr = k(k, a); return kr(kr, r)
    fn lower_builtin_except_t_bind_over_rt(
        &mut self,
        m_expr: &Expr,
        k_expr: &Expr,
    ) -> CodegenResult<Option<BasicValueEnum<'ctx>>> {
        let m_val = self
            .lower_expr(m_expr)?
            .ok_or_else(|| CodegenError::Internal("ExceptT.>>=/rt: m has no value".to_string()))?;
        let k_val = self
            .lower_expr(k_expr)?
            .ok_or_else(|| CodegenError::Internal("ExceptT.>>=/rt: k has no value".to_string()))?;

        let ptr_type = self.type_mapper().ptr_type();
        let fn_name = "bhc_except_t_bind_over_rt";
        let func = self.get_or_create_transformer_fn(fn_name);
        if func.count_basic_blocks() == 0 {
            let entry = self.llvm_ctx.append_basic_block(func, "entry");
            let right_bb = self.llvm_ctx.append_basic_block(func, "right");
            let left_bb = self.llvm_ctx.append_basic_block(func, "left");
            let saved_bb = self.builder().get_insert_block();
            self.builder().position_at_end(entry);
            let env = func.get_nth_param(0).unwrap().into_pointer_value();
            let r = func.get_nth_param(1).unwrap().into_pointer_value();
            let m = self.extract_closure_env_elem(env, 2, 0)?;
            let k = self.extract_closure_env_elem(env, 2, 1)?;

            let fn_type2 = ptr_type.fn_type(&[ptr_type.into(), ptr_type.into()], false);

            // either = m(m, r)
            let m_fn = self.extract_closure_fn_ptr(m)?;
            let either = self
                .builder()
                .build_indirect_call(fn_type2, m_fn, &[m.into(), r.into()], "bind_rt_either")
                .map_err(|e| CodegenError::Internal(format!("ExceptT bind/rt m: {:?}", e)))?
                .try_as_basic_value()
                .basic()
                .ok_or_else(|| CodegenError::Internal("ExceptT bind/rt m: void".to_string()))?
                .into_pointer_value();

            let tag = self.extract_adt_tag(either)?;
            let is_right = self
                .builder()
                .build_int_compare(
                    inkwell::IntPredicate::EQ,
                    tag,
                    self.type_mapper().i64_type().const_int(1, false),
                    "is_right",
                )
                .map_err(|e| CodegenError::Internal(format!("bind/rt tag cmp: {:?}", e)))?;
            self.builder()
                .build_conditional_branch(is_right, right_bb, left_bb)
                .map_err(|e| CodegenError::Internal(format!("bind/rt branch: {:?}", e)))?;

            // Left: return either
            self.builder().position_at_end(left_bb);
            self.builder()
                .build_return(Some(&either))
                .map_err(|e| CodegenError::Internal(format!("bind/rt left return: {:?}", e)))?;

            // Right: a = field0(either); kr = k(k, a); return kr(kr, r)
            self.builder().position_at_end(right_bb);
            let a = self.extract_adt_field(either, 1, 0)?;
            let k_fn = self.extract_closure_fn_ptr(k)?;
            let kr = self
                .builder()
                .build_indirect_call(fn_type2, k_fn, &[k.into(), a.into()], "bind_rt_kr")
                .map_err(|e| CodegenError::Internal(format!("ExceptT bind/rt k: {:?}", e)))?
                .try_as_basic_value()
                .basic()
                .ok_or_else(|| CodegenError::Internal("ExceptT bind/rt k: void".to_string()))?
                .into_pointer_value();
            let kr_fn = self.extract_closure_fn_ptr(kr)?;
            let result = self
                .builder()
                .build_indirect_call(fn_type2, kr_fn, &[kr.into(), r.into()], "bind_rt_result")
                .map_err(|e| CodegenError::Internal(format!("ExceptT bind/rt kr: {:?}", e)))?
                .try_as_basic_value()
                .basic()
                .ok_or_else(|| CodegenError::Internal("ExceptT bind/rt kr: void".to_string()))?;
            self.builder()
                .build_return(Some(&result))
                .map_err(|e| CodegenError::Internal(format!("bind/rt right return: {:?}", e)))?;

            if let Some(bb) = saved_bb {
                self.builder().position_at_end(bb);
            }
        }

        let fn_ptr = func.as_global_value().as_pointer_value();
        let m_ptr = self.value_to_ptr(m_val)?;
        let k_ptr = self.value_to_ptr(k_val)?;
        let closure_ptr = self.alloc_closure(
            fn_ptr,
            &[
                (VarId::new(900000), m_ptr.into()),
                (VarId::new(900001), k_ptr.into()),
            ],
        )?;
        Ok(Some(closure_ptr.into()))
    }

    /// ExceptT.>> over ReaderT:
    /// \(env, r) ->
    ///   either = m1(m1, r)
    ///   Left:  return either
    ///   Right: return m2(m2, r)
    fn lower_builtin_except_t_then_over_rt(
        &mut self,
        m1_expr: &Expr,
        m2_expr: &Expr,
    ) -> CodegenResult<Option<BasicValueEnum<'ctx>>> {
        let m1_val = self
            .lower_expr(m1_expr)?
            .ok_or_else(|| CodegenError::Internal("ExceptT.>>/rt: m1 has no value".to_string()))?;
        let m2_val = self
            .lower_expr(m2_expr)?
            .ok_or_else(|| CodegenError::Internal("ExceptT.>>/rt: m2 has no value".to_string()))?;

        let ptr_type = self.type_mapper().ptr_type();
        let fn_name = "bhc_except_t_then_over_rt";
        let func = self.get_or_create_transformer_fn(fn_name);
        if func.count_basic_blocks() == 0 {
            let entry = self.llvm_ctx.append_basic_block(func, "entry");
            let right_bb = self.llvm_ctx.append_basic_block(func, "right");
            let left_bb = self.llvm_ctx.append_basic_block(func, "left");
            let saved_bb = self.builder().get_insert_block();
            self.builder().position_at_end(entry);
            let env = func.get_nth_param(0).unwrap().into_pointer_value();
            let r = func.get_nth_param(1).unwrap().into_pointer_value();
            let m1 = self.extract_closure_env_elem(env, 2, 0)?;
            let m2 = self.extract_closure_env_elem(env, 2, 1)?;

            let fn_type2 = ptr_type.fn_type(&[ptr_type.into(), ptr_type.into()], false);

            // either = m1(m1, r)
            let m1_fn = self.extract_closure_fn_ptr(m1)?;
            let either = self
                .builder()
                .build_indirect_call(fn_type2, m1_fn, &[m1.into(), r.into()], "then_rt_either")
                .map_err(|e| CodegenError::Internal(format!("ExceptT then/rt m1: {:?}", e)))?
                .try_as_basic_value()
                .basic()
                .ok_or_else(|| CodegenError::Internal("ExceptT then/rt m1: void".to_string()))?
                .into_pointer_value();

            let tag = self.extract_adt_tag(either)?;
            let is_right = self
                .builder()
                .build_int_compare(
                    inkwell::IntPredicate::EQ,
                    tag,
                    self.type_mapper().i64_type().const_int(1, false),
                    "is_right",
                )
                .map_err(|e| CodegenError::Internal(format!("then/rt tag cmp: {:?}", e)))?;
            self.builder()
                .build_conditional_branch(is_right, right_bb, left_bb)
                .map_err(|e| CodegenError::Internal(format!("then/rt branch: {:?}", e)))?;

            // Left: return either
            self.builder().position_at_end(left_bb);
            self.builder()
                .build_return(Some(&either))
                .map_err(|e| CodegenError::Internal(format!("then/rt left return: {:?}", e)))?;

            // Right: return m2(m2, r)
            self.builder().position_at_end(right_bb);
            let m2_fn = self.extract_closure_fn_ptr(m2)?;
            let result = self
                .builder()
                .build_indirect_call(fn_type2, m2_fn, &[m2.into(), r.into()], "then_rt_result")
                .map_err(|e| CodegenError::Internal(format!("ExceptT then/rt m2: {:?}", e)))?
                .try_as_basic_value()
                .basic()
                .ok_or_else(|| CodegenError::Internal("ExceptT then/rt m2: void".to_string()))?;
            self.builder()
                .build_return(Some(&result))
                .map_err(|e| CodegenError::Internal(format!("then/rt right return: {:?}", e)))?;

            if let Some(bb) = saved_bb {
                self.builder().position_at_end(bb);
            }
        }

        let fn_ptr = func.as_global_value().as_pointer_value();
        let m1_ptr = self.value_to_ptr(m1_val)?;
        let m2_ptr = self.value_to_ptr(m2_val)?;
        let closure_ptr = self.alloc_closure(
            fn_ptr,
            &[
                (VarId::new(900000), m1_ptr.into()),
                (VarId::new(900001), m2_ptr.into()),
            ],
        )?;
        Ok(Some(closure_ptr.into()))
    }

    /// catchE over ReaderT:
    /// \(env, r) ->
    ///   either = m(m, r)
    ///   Right: return either
    ///   Left:  e = field0(either); hr = handler(handler, e); return hr(hr, r)
    fn lower_builtin_catch_e_over_rt(
        &mut self,
        m_expr: &Expr,
        handler_expr: &Expr,
    ) -> CodegenResult<Option<BasicValueEnum<'ctx>>> {
        let m_val = self
            .lower_expr(m_expr)?
            .ok_or_else(|| CodegenError::Internal("catchE/rt: m has no value".to_string()))?;
        let handler_val = self
            .lower_expr(handler_expr)?
            .ok_or_else(|| CodegenError::Internal("catchE/rt: handler has no value".to_string()))?;

        let ptr_type = self.type_mapper().ptr_type();
        let fn_name = "bhc_except_t_catch_over_rt";
        let func = self.get_or_create_transformer_fn(fn_name);
        if func.count_basic_blocks() == 0 {
            let entry = self.llvm_ctx.append_basic_block(func, "entry");
            let right_bb = self.llvm_ctx.append_basic_block(func, "right");
            let left_bb = self.llvm_ctx.append_basic_block(func, "left");
            let saved_bb = self.builder().get_insert_block();
            self.builder().position_at_end(entry);
            let env = func.get_nth_param(0).unwrap().into_pointer_value();
            let r = func.get_nth_param(1).unwrap().into_pointer_value();
            let m = self.extract_closure_env_elem(env, 2, 0)?;
            let handler = self.extract_closure_env_elem(env, 2, 1)?;

            let fn_type2 = ptr_type.fn_type(&[ptr_type.into(), ptr_type.into()], false);

            // either = m(m, r)
            let m_fn = self.extract_closure_fn_ptr(m)?;
            let either = self
                .builder()
                .build_indirect_call(fn_type2, m_fn, &[m.into(), r.into()], "catch_rt_either")
                .map_err(|e| CodegenError::Internal(format!("catchE/rt m: {:?}", e)))?
                .try_as_basic_value()
                .basic()
                .ok_or_else(|| CodegenError::Internal("catchE/rt m: void".to_string()))?
                .into_pointer_value();

            let tag = self.extract_adt_tag(either)?;
            let is_right = self
                .builder()
                .build_int_compare(
                    inkwell::IntPredicate::EQ,
                    tag,
                    self.type_mapper().i64_type().const_int(1, false),
                    "is_right",
                )
                .map_err(|e| CodegenError::Internal(format!("catch/rt tag cmp: {:?}", e)))?;
            self.builder()
                .build_conditional_branch(is_right, right_bb, left_bb)
                .map_err(|e| CodegenError::Internal(format!("catch/rt branch: {:?}", e)))?;

            // Right: return either
            self.builder().position_at_end(right_bb);
            self.builder()
                .build_return(Some(&either))
                .map_err(|e| CodegenError::Internal(format!("catch/rt right return: {:?}", e)))?;

            // Left: e = field0(either); hr = handler(handler, e); return hr(hr, r)
            self.builder().position_at_end(left_bb);
            let e = self.extract_adt_field(either, 1, 0)?;
            let handler_fn = self.extract_closure_fn_ptr(handler)?;
            let hr = self
                .builder()
                .build_indirect_call(
                    fn_type2,
                    handler_fn,
                    &[handler.into(), e.into()],
                    "catch_rt_hr",
                )
                .map_err(|e| CodegenError::Internal(format!("catchE/rt handler: {:?}", e)))?
                .try_as_basic_value()
                .basic()
                .ok_or_else(|| CodegenError::Internal("catchE/rt handler: void".to_string()))?
                .into_pointer_value();
            let hr_fn = self.extract_closure_fn_ptr(hr)?;
            let result = self
                .builder()
                .build_indirect_call(fn_type2, hr_fn, &[hr.into(), r.into()], "catch_rt_result")
                .map_err(|e| CodegenError::Internal(format!("catchE/rt hr: {:?}", e)))?
                .try_as_basic_value()
                .basic()
                .ok_or_else(|| CodegenError::Internal("catchE/rt hr: void".to_string()))?;
            self.builder()
                .build_return(Some(&result))
                .map_err(|e| CodegenError::Internal(format!("catch/rt left return: {:?}", e)))?;

            if let Some(bb) = saved_bb {
                self.builder().position_at_end(bb);
            }
        }

        let fn_ptr = func.as_global_value().as_pointer_value();
        let m_ptr = self.value_to_ptr(m_val)?;
        let handler_ptr = self.value_to_ptr(handler_val)?;
        let closure_ptr = self.alloc_closure(
            fn_ptr,
            &[
                (VarId::new(900000), m_ptr.into()),
                (VarId::new(900001), handler_ptr.into()),
            ],
        )?;
        Ok(Some(closure_ptr.into()))
    }

    /// apply_except_t_lift over ReaderT: \(env, r) -> Right(action(action, r))
    fn apply_except_t_lift_to_value_over_rt(
        &mut self,
        action_val: BasicValueEnum<'ctx>,
    ) -> CodegenResult<BasicValueEnum<'ctx>> {
        let fn_name = "bhc_except_t_lift_auto_over_rt";
        let func = self.get_or_create_transformer_fn(fn_name);
        if func.count_basic_blocks() == 0 {
            let entry = self.llvm_ctx.append_basic_block(func, "entry");
            let saved_bb = self.builder().get_insert_block();
            self.builder().position_at_end(entry);
            let env = func.get_nth_param(0).unwrap().into_pointer_value();
            let r = func.get_nth_param(1).unwrap().into_pointer_value();
            let action = self.extract_closure_env_elem(env, 1, 0)?;

            let ptr_type = self.type_mapper().ptr_type();
            let fn_type2 = ptr_type.fn_type(&[ptr_type.into(), ptr_type.into()], false);

            // a = action(action, r) — ReaderT action returns value directly
            let action_fn = self.extract_closure_fn_ptr(action)?;
            let a = self
                .builder()
                .build_indirect_call(fn_type2, action_fn, &[action.into(), r.into()], "lift_rt_a")
                .map_err(|e| CodegenError::Internal(format!("except_t_lift/rt action: {:?}", e)))?
                .try_as_basic_value()
                .basic()
                .ok_or_else(|| CodegenError::Internal("except_t_lift/rt: void".to_string()))?;

            // Wrap in Right
            let right_adt = self.alloc_adt(1, 1)?;
            self.store_adt_field(right_adt, 1, 0, a)?;
            self.builder()
                .build_return(Some(&right_adt))
                .map_err(|e| CodegenError::Internal(format!("except_t_lift/rt return: {:?}", e)))?;
            if let Some(bb) = saved_bb {
                self.builder().position_at_end(bb);
            }
        }

        let fn_ptr = func.as_global_value().as_pointer_value();
        let action_ptr = self.value_to_ptr(action_val)?;
        let closure_ptr = self.alloc_closure(fn_ptr, &[(VarId::new(900102), action_ptr.into())])?;
        Ok(closure_ptr.into())
    }

    /// ExceptT bind implementation with pre-lowered values
    fn lower_except_t_bind_impl(
        &mut self,
        m_val: BasicValueEnum<'ctx>,
        k_val: BasicValueEnum<'ctx>,
    ) -> CodegenResult<Option<BasicValueEnum<'ctx>>> {
        let _ptr_type = self.type_mapper().ptr_type();
        let fn_name = "bhc_except_t_bind";

        let func = self.get_or_create_transformer_fn(fn_name);
        // Function body is generated by lower_builtin_except_t_bind if not already present

        let fn_ptr = func.as_global_value().as_pointer_value();
        let m_ptr = self.value_to_ptr(m_val)?;
        let k_ptr = self.value_to_ptr(k_val)?;
        let closure_ptr = self.alloc_closure(
            fn_ptr,
            &[
                (VarId::new(900000), m_ptr.into()),
                (VarId::new(900001), k_ptr.into()),
            ],
        )?;
        Ok(Some(closure_ptr.into()))
    }

    /// ExceptT then implementation with pre-lowered values
    fn lower_except_t_then_impl(
        &mut self,
        m1_val: BasicValueEnum<'ctx>,
        m2_val: BasicValueEnum<'ctx>,
    ) -> CodegenResult<Option<BasicValueEnum<'ctx>>> {
        let _ptr_type = self.type_mapper().ptr_type();
        let fn_name = "bhc_except_t_then";

        let func = self.get_or_create_transformer_fn(fn_name);
        // Function body is generated by lower_builtin_except_t_then if not already present

        let fn_ptr = func.as_global_value().as_pointer_value();
        let m1_ptr = self.value_to_ptr(m1_val)?;
        let m2_ptr = self.value_to_ptr(m2_val)?;
        let closure_ptr = self.alloc_closure(
            fn_ptr,
            &[
                (VarId::new(900000), m1_ptr.into()),
                (VarId::new(900001), m2_ptr.into()),
            ],
        )?;
        Ok(Some(closure_ptr.into()))
    }

    // ========================================================================
    // WriterT Transformer Operations
    // ========================================================================

    /// runWriterT m = m(_) — runs the computation, returns (a, w) pair
    fn lower_builtin_run_writer_t(
        &mut self,
        m_expr: &Expr,
    ) -> CodegenResult<Option<BasicValueEnum<'ctx>>> {
        self.push_transformer_layer(TransformerLayer::WriterT);
        let m_val = self
            .lower_expr(m_expr)?
            .ok_or_else(|| CodegenError::Internal("runWriterT: m has no value".to_string()))?;
        self.pop_transformer_layer();

        // In nested context (WriterT over StateT/ReaderT), the m_val IS the transformer
        // closure that expects state/reader-env. Return it as-is — the outer
        // runStateT/runReaderT will call it with the appropriate argument.
        let current = self.current_transformer_layer();
        if current == TransformerLayer::StateT || current == TransformerLayer::ReaderT {
            return Ok(Some(m_val));
        }

        let m_ptr = match m_val {
            BasicValueEnum::PointerValue(p) => p,
            _ => {
                return Err(CodegenError::TypeError(
                    "runWriterT: expected closure".to_string(),
                ))
            }
        };

        let ptr_type = self.type_mapper().ptr_type();
        let fn_type = ptr_type.fn_type(&[ptr_type.into(), ptr_type.into()], false);
        let m_fn = self.extract_closure_fn_ptr(m_ptr)?;
        let null_arg = ptr_type.const_null();
        let result = self
            .builder()
            .build_indirect_call(
                fn_type,
                m_fn,
                &[m_ptr.into(), null_arg.into()],
                "run_writer_t",
            )
            .map_err(|e| CodegenError::Internal(format!("runWriterT call: {:?}", e)))?
            .try_as_basic_value()
            .basic()
            .ok_or_else(|| CodegenError::Internal("runWriterT: void".to_string()))?;
        Ok(Some(result))
    }

    /// execWriterT m = snd(m(_)) — runs computation, returns just the output
    fn lower_builtin_exec_writer_t(
        &mut self,
        m_expr: &Expr,
    ) -> CodegenResult<Option<BasicValueEnum<'ctx>>> {
        let result = self.lower_builtin_run_writer_t(m_expr)?;
        let pair = result
            .ok_or_else(|| CodegenError::Internal("execWriterT: no result".to_string()))?
            .into_pointer_value();
        let snd = self.extract_pair_snd(pair)?;
        Ok(Some(snd.into()))
    }

    /// WriterT.pure x = closure \_ -> (x, "")
    fn lower_builtin_writer_t_pure(
        &mut self,
        x_expr: &Expr,
    ) -> CodegenResult<Option<BasicValueEnum<'ctx>>> {
        // WriterT over StateT: \(env, s) -> ((x, []), s)
        if self.transformer_stack.is_writer_t_over_state_t() {
            return self.lower_builtin_writer_t_pure_over_st(x_expr);
        }
        // WriterT over ReaderT: r is absorbed by unused second arg, same as plain

        let x_val = self
            .lower_expr(x_expr)?
            .ok_or_else(|| CodegenError::Internal("WriterT.pure: x has no value".to_string()))?;

        let _ptr_type = self.type_mapper().ptr_type();
        let fn_name = "bhc_writer_t_pure";

        let func = self.get_or_create_transformer_fn(fn_name);
        if func.count_basic_blocks() == 0 {
            let entry = self.llvm_ctx.append_basic_block(func, "entry");
            let saved_bb = self.builder().get_insert_block();
            self.builder().position_at_end(entry);
            // \(env, _) -> (x, "") where x = env[0]
            let env = func.get_nth_param(0).unwrap().into_pointer_value();
            let x = self.extract_closure_env_elem(env, 1, 0)?;
            // Empty string = nil list
            let empty = self.build_nil()?;
            let pair = self.alloc_pair(x.into(), empty.into())?;
            self.builder()
                .build_return(Some(&pair))
                .map_err(|e| CodegenError::Internal(format!("writer_t_pure return: {:?}", e)))?;
            if let Some(bb) = saved_bb {
                self.builder().position_at_end(bb);
            }
        }

        let fn_ptr = func.as_global_value().as_pointer_value();
        let x_ptr = self.value_to_ptr(x_val)?;
        let closure_ptr = self.alloc_closure(fn_ptr, &[(VarId::new(900000), x_ptr.into())])?;
        Ok(Some(closure_ptr.into()))
    }

    /// tell w = closure \_ -> ((), w)
    fn lower_builtin_tell(&mut self, w_expr: &Expr) -> CodegenResult<Option<BasicValueEnum<'ctx>>> {
        // WriterT over StateT: \(env, s) -> (((), w), s)
        if self.transformer_stack.is_writer_t_over_state_t() {
            return self.lower_builtin_tell_over_st(w_expr);
        }
        // WriterT over ReaderT: r is absorbed by unused second arg, same as plain

        let w_val = self
            .lower_expr(w_expr)?
            .ok_or_else(|| CodegenError::Internal("tell: w has no value".to_string()))?;

        let ptr_type = self.type_mapper().ptr_type();
        let fn_name = "bhc_writer_t_tell";

        let func = self.get_or_create_transformer_fn(fn_name);
        if func.count_basic_blocks() == 0 {
            let entry = self.llvm_ctx.append_basic_block(func, "entry");
            let saved_bb = self.builder().get_insert_block();
            self.builder().position_at_end(entry);
            // \(env, _) -> ((), w) where w = env[0]
            let env = func.get_nth_param(0).unwrap().into_pointer_value();
            let w = self.extract_closure_env_elem(env, 1, 0)?;
            let unit = ptr_type.const_null();
            let pair = self.alloc_pair(unit.into(), w.into())?;
            self.builder()
                .build_return(Some(&pair))
                .map_err(|e| CodegenError::Internal(format!("writer_t_tell return: {:?}", e)))?;
            if let Some(bb) = saved_bb {
                self.builder().position_at_end(bb);
            }
        }

        let fn_ptr = func.as_global_value().as_pointer_value();
        let w_ptr = self.value_to_ptr(w_val)?;
        let closure_ptr = self.alloc_closure(fn_ptr, &[(VarId::new(900000), w_ptr.into())])?;
        Ok(Some(closure_ptr.into()))
    }

    /// WriterT.>>= m k = closure \_ ->
    ///   let (a, w1) = m(_)
    ///   let (b, w2) = k(a)(_)
    ///   (b, w1 ++ w2)
    fn lower_builtin_writer_t_bind(
        &mut self,
        m_expr: &Expr,
        k_expr: &Expr,
    ) -> CodegenResult<Option<BasicValueEnum<'ctx>>> {
        // WriterT over StateT: threads state through closures
        if self.transformer_stack.is_writer_t_over_state_t() {
            return self.lower_builtin_writer_t_bind_over_st(m_expr, k_expr);
        }
        // WriterT over ReaderT: threads reader-env through closures
        if self.transformer_stack.is_writer_t_over_reader_t() {
            return self.lower_builtin_writer_t_bind_over_rt(m_expr, k_expr);
        }

        let m_val = self
            .lower_expr(m_expr)?
            .ok_or_else(|| CodegenError::Internal("WriterT.>>=: m has no value".to_string()))?;
        let k_val = self
            .lower_expr(k_expr)?
            .ok_or_else(|| CodegenError::Internal("WriterT.>>=: k has no value".to_string()))?;

        let ptr_type = self.type_mapper().ptr_type();
        let fn_name = "bhc_writer_t_bind";

        let func = self.get_or_create_transformer_fn(fn_name);
        if func.count_basic_blocks() == 0 {
            let entry = self.llvm_ctx.append_basic_block(func, "entry");
            let saved_bb = self.builder().get_insert_block();
            self.builder().position_at_end(entry);
            let env = func.get_nth_param(0).unwrap().into_pointer_value();
            let m = self.extract_closure_env_elem(env, 2, 0)?;
            let k = self.extract_closure_env_elem(env, 2, 1)?;

            let fn_type = ptr_type.fn_type(&[ptr_type.into(), ptr_type.into()], false);
            let null_arg = ptr_type.const_null();

            // pair1 = m(_)
            let m_fn = self.extract_closure_fn_ptr(m)?;
            let pair1 = self
                .builder()
                .build_indirect_call(fn_type, m_fn, &[m.into(), null_arg.into()], "wt_bind_pair1")
                .map_err(|e| CodegenError::Internal(format!("WriterT bind m: {:?}", e)))?
                .try_as_basic_value()
                .basic()
                .ok_or_else(|| CodegenError::Internal("WriterT bind m: void".to_string()))?
                .into_pointer_value();

            let a = self.extract_pair_fst(pair1)?;
            let w1 = self.extract_pair_snd(pair1)?;

            // kr = k(a)
            let k_fn = self.extract_closure_fn_ptr(k)?;
            let kr = self
                .builder()
                .build_indirect_call(fn_type, k_fn, &[k.into(), a.into()], "wt_bind_kr")
                .map_err(|e| CodegenError::Internal(format!("WriterT bind k: {:?}", e)))?
                .try_as_basic_value()
                .basic()
                .ok_or_else(|| CodegenError::Internal("WriterT bind k: void".to_string()))?
                .into_pointer_value();

            // pair2 = kr(_)
            let kr_fn = self.extract_closure_fn_ptr(kr)?;
            let pair2 = self
                .builder()
                .build_indirect_call(
                    fn_type,
                    kr_fn,
                    &[kr.into(), null_arg.into()],
                    "wt_bind_pair2",
                )
                .map_err(|e| CodegenError::Internal(format!("WriterT bind kr: {:?}", e)))?
                .try_as_basic_value()
                .basic()
                .ok_or_else(|| CodegenError::Internal("WriterT bind kr: void".to_string()))?
                .into_pointer_value();

            let b = self.extract_pair_fst(pair2)?;
            let w2 = self.extract_pair_snd(pair2)?;

            // w_combined = append(w1, w2) via list append
            let append_fn = self.get_or_create_list_append_fn()?;
            let w_combined = self
                .builder()
                .build_call(append_fn, &[w1.into(), w2.into()], "wt_bind_append")
                .map_err(|e| CodegenError::Internal(format!("WriterT append: {:?}", e)))?
                .try_as_basic_value()
                .basic()
                .ok_or_else(|| CodegenError::Internal("WriterT append: void".to_string()))?;

            let result_pair = self.alloc_pair(b.into(), w_combined)?;
            self.builder()
                .build_return(Some(&result_pair))
                .map_err(|e| CodegenError::Internal(format!("writer_t_bind return: {:?}", e)))?;
            if let Some(bb) = saved_bb {
                self.builder().position_at_end(bb);
            }
        }

        let fn_ptr = func.as_global_value().as_pointer_value();
        let m_ptr = self.value_to_ptr(m_val)?;
        let k_ptr = self.value_to_ptr(k_val)?;
        let closure_ptr = self.alloc_closure(
            fn_ptr,
            &[
                (VarId::new(900000), m_ptr.into()),
                (VarId::new(900001), k_ptr.into()),
            ],
        )?;
        Ok(Some(closure_ptr.into()))
    }

    /// WriterT.>> m1 m2 = m1 >>= \_ -> m2
    fn lower_builtin_writer_t_then(
        &mut self,
        m1_expr: &Expr,
        m2_expr: &Expr,
    ) -> CodegenResult<Option<BasicValueEnum<'ctx>>> {
        // WriterT over StateT: threads state through closures
        if self.transformer_stack.is_writer_t_over_state_t() {
            return self.lower_builtin_writer_t_then_over_st(m1_expr, m2_expr);
        }
        // WriterT over ReaderT: threads reader-env through closures
        if self.transformer_stack.is_writer_t_over_reader_t() {
            return self.lower_builtin_writer_t_then_over_rt(m1_expr, m2_expr);
        }

        let m1_val = self
            .lower_expr(m1_expr)?
            .ok_or_else(|| CodegenError::Internal("WriterT.>>: m1 has no value".to_string()))?;
        let m2_val = self
            .lower_expr(m2_expr)?
            .ok_or_else(|| CodegenError::Internal("WriterT.>>: m2 has no value".to_string()))?;

        let ptr_type = self.type_mapper().ptr_type();
        let fn_name = "bhc_writer_t_then";

        let func = self.get_or_create_transformer_fn(fn_name);
        if func.count_basic_blocks() == 0 {
            let entry = self.llvm_ctx.append_basic_block(func, "entry");
            let saved_bb = self.builder().get_insert_block();
            self.builder().position_at_end(entry);
            let env = func.get_nth_param(0).unwrap().into_pointer_value();
            let m1 = self.extract_closure_env_elem(env, 2, 0)?;
            let m2 = self.extract_closure_env_elem(env, 2, 1)?;

            let fn_type = ptr_type.fn_type(&[ptr_type.into(), ptr_type.into()], false);
            let null_arg = ptr_type.const_null();

            // pair1 = m1(_)
            let m1_fn = self.extract_closure_fn_ptr(m1)?;
            let pair1 = self
                .builder()
                .build_indirect_call(
                    fn_type,
                    m1_fn,
                    &[m1.into(), null_arg.into()],
                    "wt_then_pair1",
                )
                .map_err(|e| CodegenError::Internal(format!("WriterT then m1: {:?}", e)))?
                .try_as_basic_value()
                .basic()
                .ok_or_else(|| CodegenError::Internal("WriterT then m1: void".to_string()))?
                .into_pointer_value();

            let w1 = self.extract_pair_snd(pair1)?;

            // pair2 = m2(_)
            let m2_fn = self.extract_closure_fn_ptr(m2)?;
            let pair2 = self
                .builder()
                .build_indirect_call(
                    fn_type,
                    m2_fn,
                    &[m2.into(), null_arg.into()],
                    "wt_then_pair2",
                )
                .map_err(|e| CodegenError::Internal(format!("WriterT then m2: {:?}", e)))?
                .try_as_basic_value()
                .basic()
                .ok_or_else(|| CodegenError::Internal("WriterT then m2: void".to_string()))?
                .into_pointer_value();

            let b = self.extract_pair_fst(pair2)?;
            let w2 = self.extract_pair_snd(pair2)?;

            // w_combined = append(w1, w2) via list append
            let append_fn = self.get_or_create_list_append_fn()?;
            let w_combined = self
                .builder()
                .build_call(append_fn, &[w1.into(), w2.into()], "wt_then_append")
                .map_err(|e| CodegenError::Internal(format!("WriterT then append: {:?}", e)))?
                .try_as_basic_value()
                .basic()
                .ok_or_else(|| CodegenError::Internal("WriterT then append: void".to_string()))?;

            let result_pair = self.alloc_pair(b.into(), w_combined)?;
            self.builder()
                .build_return(Some(&result_pair))
                .map_err(|e| CodegenError::Internal(format!("writer_t_then return: {:?}", e)))?;
            if let Some(bb) = saved_bb {
                self.builder().position_at_end(bb);
            }
        }

        let fn_ptr = func.as_global_value().as_pointer_value();
        let m1_ptr = self.value_to_ptr(m1_val)?;
        let m2_ptr = self.value_to_ptr(m2_val)?;
        let closure_ptr = self.alloc_closure(
            fn_ptr,
            &[
                (VarId::new(900000), m1_ptr.into()),
                (VarId::new(900001), m2_ptr.into()),
            ],
        )?;
        Ok(Some(closure_ptr.into()))
    }

    /// WriterT bind implementation with pre-lowered values
    fn lower_writer_t_bind_impl(
        &mut self,
        m_val: BasicValueEnum<'ctx>,
        k_val: BasicValueEnum<'ctx>,
    ) -> CodegenResult<Option<BasicValueEnum<'ctx>>> {
        let fn_name = "bhc_writer_t_bind";
        let func = self.get_or_create_transformer_fn(fn_name);
        // Function body is generated by lower_builtin_writer_t_bind if not already present

        let fn_ptr = func.as_global_value().as_pointer_value();
        let m_ptr = self.value_to_ptr(m_val)?;
        let k_ptr = self.value_to_ptr(k_val)?;
        let closure_ptr = self.alloc_closure(
            fn_ptr,
            &[
                (VarId::new(900000), m_ptr.into()),
                (VarId::new(900001), k_ptr.into()),
            ],
        )?;
        Ok(Some(closure_ptr.into()))
    }

    /// WriterT then implementation with pre-lowered values
    fn lower_writer_t_then_impl(
        &mut self,
        m1_val: BasicValueEnum<'ctx>,
        m2_val: BasicValueEnum<'ctx>,
    ) -> CodegenResult<Option<BasicValueEnum<'ctx>>> {
        let fn_name = "bhc_writer_t_then";
        let func = self.get_or_create_transformer_fn(fn_name);
        // Function body is generated by lower_builtin_writer_t_then if not already present

        let fn_ptr = func.as_global_value().as_pointer_value();
        let m1_ptr = self.value_to_ptr(m1_val)?;
        let m2_ptr = self.value_to_ptr(m2_val)?;
        let closure_ptr = self.alloc_closure(
            fn_ptr,
            &[
                (VarId::new(900000), m1_ptr.into()),
                (VarId::new(900001), m2_ptr.into()),
            ],
        )?;
        Ok(Some(closure_ptr.into()))
    }

    /// WriterT.lift io = closure \_ -> (io_result, "")
    fn lower_builtin_writer_t_lift(
        &mut self,
        io_expr: &Expr,
    ) -> CodegenResult<Option<BasicValueEnum<'ctx>>> {
        // In nested contexts, the inner action is a closure that needs
        // state/reader-env threaded through. Route to nested lift functions.
        if self.transformer_stack.is_writer_t_over_state_t() {
            let io_val = self.lower_expr(io_expr)?.ok_or_else(|| {
                CodegenError::Internal("WriterT.lift/st: io has no value".to_string())
            })?;
            return Ok(Some(self.apply_writer_t_lift_to_value_over_st(io_val)?));
        }
        if self.transformer_stack.is_writer_t_over_reader_t() {
            let io_val = self.lower_expr(io_expr)?.ok_or_else(|| {
                CodegenError::Internal("WriterT.lift/rt: io has no value".to_string())
            })?;
            return Ok(Some(self.apply_writer_t_lift_to_value_over_rt(io_val)?));
        }

        let io_val = self
            .lower_expr(io_expr)?
            .ok_or_else(|| CodegenError::Internal("WriterT.lift: io has no value".to_string()))?;

        let _ptr_type = self.type_mapper().ptr_type();
        let fn_name = "bhc_writer_t_lift";

        let func = self.get_or_create_transformer_fn(fn_name);
        if func.count_basic_blocks() == 0 {
            let entry = self.llvm_ctx.append_basic_block(func, "entry");
            let saved_bb = self.builder().get_insert_block();
            self.builder().position_at_end(entry);
            // \(env, _) -> (io_result, "") where io = env[0]
            let env = func.get_nth_param(0).unwrap().into_pointer_value();
            let io_result = self.extract_closure_env_elem(env, 1, 0)?;
            let empty = self.build_nil()?;
            let pair = self.alloc_pair(io_result.into(), empty.into())?;
            self.builder()
                .build_return(Some(&pair))
                .map_err(|e| CodegenError::Internal(format!("writer_t_lift return: {:?}", e)))?;
            if let Some(bb) = saved_bb {
                self.builder().position_at_end(bb);
            }
        }

        let fn_ptr = func.as_global_value().as_pointer_value();
        let io_ptr = self.value_to_ptr(io_val)?;
        let closure_ptr = self.alloc_closure(fn_ptr, &[(VarId::new(900000), io_ptr.into())])?;
        Ok(Some(closure_ptr.into()))
    }

    /// WriterT.fmap f m = closure \_ -> let (a, w) = m(_) in (f(a), w)
    fn lower_builtin_writer_t_fmap(
        &mut self,
        f_expr: &Expr,
        m_expr: &Expr,
    ) -> CodegenResult<Option<BasicValueEnum<'ctx>>> {
        let f_val = self
            .lower_expr(f_expr)?
            .ok_or_else(|| CodegenError::Internal("WriterT.fmap: f has no value".to_string()))?;
        let m_val = self
            .lower_expr(m_expr)?
            .ok_or_else(|| CodegenError::Internal("WriterT.fmap: m has no value".to_string()))?;

        let ptr_type = self.type_mapper().ptr_type();
        let fn_name = "bhc_writer_t_fmap";

        let func = self.get_or_create_transformer_fn(fn_name);
        if func.count_basic_blocks() == 0 {
            let entry = self.llvm_ctx.append_basic_block(func, "entry");
            let saved_bb = self.builder().get_insert_block();
            self.builder().position_at_end(entry);
            let env = func.get_nth_param(0).unwrap().into_pointer_value();
            let f = self.extract_closure_env_elem(env, 2, 0)?;
            let m = self.extract_closure_env_elem(env, 2, 1)?;

            let fn_type = ptr_type.fn_type(&[ptr_type.into(), ptr_type.into()], false);
            let null_arg = ptr_type.const_null();

            let m_fn = self.extract_closure_fn_ptr(m)?;
            let pair = self
                .builder()
                .build_indirect_call(fn_type, m_fn, &[m.into(), null_arg.into()], "wt_fmap_pair")
                .map_err(|e| CodegenError::Internal(format!("WriterT fmap m: {:?}", e)))?
                .try_as_basic_value()
                .basic()
                .ok_or_else(|| CodegenError::Internal("WriterT fmap m: void".to_string()))?
                .into_pointer_value();

            let a = self.extract_pair_fst(pair)?;
            let w = self.extract_pair_snd(pair)?;

            let f_fn = self.extract_closure_fn_ptr(f)?;
            let b = self
                .builder()
                .build_indirect_call(fn_type, f_fn, &[f.into(), a.into()], "wt_fmap_b")
                .map_err(|e| CodegenError::Internal(format!("WriterT fmap f: {:?}", e)))?
                .try_as_basic_value()
                .basic()
                .ok_or_else(|| CodegenError::Internal("WriterT fmap f: void".to_string()))?;

            let result_pair = self.alloc_pair(b, w.into())?;
            self.builder()
                .build_return(Some(&result_pair))
                .map_err(|e| CodegenError::Internal(format!("writer_t_fmap return: {:?}", e)))?;
            if let Some(bb) = saved_bb {
                self.builder().position_at_end(bb);
            }
        }

        let fn_ptr = func.as_global_value().as_pointer_value();
        let f_ptr = self.value_to_ptr(f_val)?;
        let m_ptr = self.value_to_ptr(m_val)?;
        let closure_ptr = self.alloc_closure(
            fn_ptr,
            &[
                (VarId::new(900000), f_ptr.into()),
                (VarId::new(900001), m_ptr.into()),
            ],
        )?;
        Ok(Some(closure_ptr.into()))
    }

    /// WriterT.<*> mf ma — applicative apply
    fn lower_builtin_writer_t_ap(
        &mut self,
        mf_expr: &Expr,
        ma_expr: &Expr,
    ) -> CodegenResult<Option<BasicValueEnum<'ctx>>> {
        let mf_val = self
            .lower_expr(mf_expr)?
            .ok_or_else(|| CodegenError::Internal("WriterT.<*>: mf has no value".to_string()))?;
        let ma_val = self
            .lower_expr(ma_expr)?
            .ok_or_else(|| CodegenError::Internal("WriterT.<*>: ma has no value".to_string()))?;

        let ptr_type = self.type_mapper().ptr_type();
        let fn_name = "bhc_writer_t_ap";

        let func = self.get_or_create_transformer_fn(fn_name);
        if func.count_basic_blocks() == 0 {
            let entry = self.llvm_ctx.append_basic_block(func, "entry");
            let saved_bb = self.builder().get_insert_block();
            self.builder().position_at_end(entry);
            let env = func.get_nth_param(0).unwrap().into_pointer_value();
            let mf = self.extract_closure_env_elem(env, 2, 0)?;
            let ma = self.extract_closure_env_elem(env, 2, 1)?;

            let fn_type = ptr_type.fn_type(&[ptr_type.into(), ptr_type.into()], false);
            let null_arg = ptr_type.const_null();

            // (f, w1) = mf(_)
            let mf_fn = self.extract_closure_fn_ptr(mf)?;
            let pair1 = self
                .builder()
                .build_indirect_call(fn_type, mf_fn, &[mf.into(), null_arg.into()], "wt_ap_pair1")
                .map_err(|e| CodegenError::Internal(format!("WriterT ap mf: {:?}", e)))?
                .try_as_basic_value()
                .basic()
                .ok_or_else(|| CodegenError::Internal("WriterT ap mf: void".to_string()))?
                .into_pointer_value();

            let f = self.extract_pair_fst(pair1)?;
            let w1 = self.extract_pair_snd(pair1)?;

            // (a, w2) = ma(_)
            let ma_fn = self.extract_closure_fn_ptr(ma)?;
            let pair2 = self
                .builder()
                .build_indirect_call(fn_type, ma_fn, &[ma.into(), null_arg.into()], "wt_ap_pair2")
                .map_err(|e| CodegenError::Internal(format!("WriterT ap ma: {:?}", e)))?
                .try_as_basic_value()
                .basic()
                .ok_or_else(|| CodegenError::Internal("WriterT ap ma: void".to_string()))?
                .into_pointer_value();

            let a = self.extract_pair_fst(pair2)?;
            let w2 = self.extract_pair_snd(pair2)?;

            // b = f(a)
            let f_fn = self.extract_closure_fn_ptr(f)?;
            let b = self
                .builder()
                .build_indirect_call(fn_type, f_fn, &[f.into(), a.into()], "wt_ap_b")
                .map_err(|e| CodegenError::Internal(format!("WriterT ap f: {:?}", e)))?
                .try_as_basic_value()
                .basic()
                .ok_or_else(|| CodegenError::Internal("WriterT ap f: void".to_string()))?;

            // w_combined = append(w1, w2) via list append
            let append_fn = self.get_or_create_list_append_fn()?;
            let w_combined = self
                .builder()
                .build_call(append_fn, &[w1.into(), w2.into()], "wt_ap_append")
                .map_err(|e| CodegenError::Internal(format!("WriterT ap append: {:?}", e)))?
                .try_as_basic_value()
                .basic()
                .ok_or_else(|| CodegenError::Internal("WriterT ap append: void".to_string()))?;

            let result_pair = self.alloc_pair(b, w_combined)?;
            self.builder()
                .build_return(Some(&result_pair))
                .map_err(|e| CodegenError::Internal(format!("writer_t_ap return: {:?}", e)))?;
            if let Some(bb) = saved_bb {
                self.builder().position_at_end(bb);
            }
        }

        let fn_ptr = func.as_global_value().as_pointer_value();
        let mf_ptr = self.value_to_ptr(mf_val)?;
        let ma_ptr = self.value_to_ptr(ma_val)?;
        let closure_ptr = self.alloc_closure(
            fn_ptr,
            &[
                (VarId::new(900000), mf_ptr.into()),
                (VarId::new(900001), ma_ptr.into()),
            ],
        )?;
        Ok(Some(closure_ptr.into()))
    }

    // ========================================================================
    // WriterT Cross-Transformer Operations (WriterT over StateT / ReaderT)
    // ========================================================================

    /// WriterT.pure over StateT: \(env, s) -> ((x, []), s)
    fn lower_builtin_writer_t_pure_over_st(
        &mut self,
        x_expr: &Expr,
    ) -> CodegenResult<Option<BasicValueEnum<'ctx>>> {
        let x_val = self
            .lower_expr(x_expr)?
            .ok_or_else(|| CodegenError::Internal("WriterT.pure/st: x has no value".to_string()))?;

        let fn_name = "bhc_writer_t_pure_over_st";
        let func = self.get_or_create_transformer_fn(fn_name);
        if func.count_basic_blocks() == 0 {
            let entry = self.llvm_ctx.append_basic_block(func, "entry");
            let saved_bb = self.builder().get_insert_block();
            self.builder().position_at_end(entry);
            let env = func.get_nth_param(0).unwrap().into_pointer_value();
            let s = func.get_nth_param(1).unwrap().into_pointer_value();
            let x = self.extract_closure_env_elem(env, 1, 0)?;
            let empty = self.build_nil()?;
            let inner_pair = self.alloc_pair(x.into(), empty.into())?;
            let outer_pair = self.alloc_pair(inner_pair.into(), s.into())?;
            self.builder()
                .build_return(Some(&outer_pair))
                .map_err(|e| CodegenError::Internal(format!("writer_t_pure/st return: {:?}", e)))?;
            if let Some(bb) = saved_bb {
                self.builder().position_at_end(bb);
            }
        }

        let fn_ptr = func.as_global_value().as_pointer_value();
        let x_ptr = self.value_to_ptr(x_val)?;
        let closure_ptr = self.alloc_closure(fn_ptr, &[(VarId::new(900000), x_ptr.into())])?;
        Ok(Some(closure_ptr.into()))
    }

    /// tell over StateT: \(env, s) -> (((), w), s)
    fn lower_builtin_tell_over_st(
        &mut self,
        w_expr: &Expr,
    ) -> CodegenResult<Option<BasicValueEnum<'ctx>>> {
        let w_val = self
            .lower_expr(w_expr)?
            .ok_or_else(|| CodegenError::Internal("tell/st: w has no value".to_string()))?;

        let ptr_type = self.type_mapper().ptr_type();
        let fn_name = "bhc_writer_t_tell_over_st";
        let func = self.get_or_create_transformer_fn(fn_name);
        if func.count_basic_blocks() == 0 {
            let entry = self.llvm_ctx.append_basic_block(func, "entry");
            let saved_bb = self.builder().get_insert_block();
            self.builder().position_at_end(entry);
            let env = func.get_nth_param(0).unwrap().into_pointer_value();
            let s = func.get_nth_param(1).unwrap().into_pointer_value();
            let w = self.extract_closure_env_elem(env, 1, 0)?;
            let unit = ptr_type.const_null();
            let inner_pair = self.alloc_pair(unit.into(), w.into())?;
            let outer_pair = self.alloc_pair(inner_pair.into(), s.into())?;
            self.builder()
                .build_return(Some(&outer_pair))
                .map_err(|e| CodegenError::Internal(format!("writer_t_tell/st return: {:?}", e)))?;
            if let Some(bb) = saved_bb {
                self.builder().position_at_end(bb);
            }
        }

        let fn_ptr = func.as_global_value().as_pointer_value();
        let w_ptr = self.value_to_ptr(w_val)?;
        let closure_ptr = self.alloc_closure(fn_ptr, &[(VarId::new(900000), w_ptr.into())])?;
        Ok(Some(closure_ptr.into()))
    }

    /// WriterT.>>= over StateT: \(env, s) ->
    ///   outer1 = m(m, s)              → ((a, w1), s')
    ///   a = fst(fst(outer1)), w1 = snd(fst(outer1)), s' = snd(outer1)
    ///   kr = k(k, a)                  → new closure
    ///   outer2 = kr(kr, s')           → ((b, w2), s'')
    ///   b = fst(fst(outer2)), w2 = snd(fst(outer2)), s'' = snd(outer2)
    ///   return ((b, w1++w2), s'')
    fn lower_builtin_writer_t_bind_over_st(
        &mut self,
        m_expr: &Expr,
        k_expr: &Expr,
    ) -> CodegenResult<Option<BasicValueEnum<'ctx>>> {
        let m_val = self
            .lower_expr(m_expr)?
            .ok_or_else(|| CodegenError::Internal("WriterT.>>=/st: m has no value".to_string()))?;
        let k_val = self
            .lower_expr(k_expr)?
            .ok_or_else(|| CodegenError::Internal("WriterT.>>=/st: k has no value".to_string()))?;

        let ptr_type = self.type_mapper().ptr_type();
        let fn_name = "bhc_writer_t_bind_over_st";
        let func = self.get_or_create_transformer_fn(fn_name);
        if func.count_basic_blocks() == 0 {
            let entry = self.llvm_ctx.append_basic_block(func, "entry");
            let saved_bb = self.builder().get_insert_block();
            self.builder().position_at_end(entry);
            let env = func.get_nth_param(0).unwrap().into_pointer_value();
            let s = func.get_nth_param(1).unwrap().into_pointer_value();
            let m = self.extract_closure_env_elem(env, 2, 0)?;
            let k = self.extract_closure_env_elem(env, 2, 1)?;

            let fn_type2 = ptr_type.fn_type(&[ptr_type.into(), ptr_type.into()], false);

            // outer1 = m(m, s) → ((a, w1), s')
            let m_fn = self.extract_closure_fn_ptr(m)?;
            let outer1 = self
                .builder()
                .build_indirect_call(fn_type2, m_fn, &[m.into(), s.into()], "wt_bind_st_outer1")
                .map_err(|e| CodegenError::Internal(format!("WriterT bind/st m: {:?}", e)))?
                .try_as_basic_value()
                .basic()
                .ok_or_else(|| CodegenError::Internal("WriterT bind/st m: void".to_string()))?
                .into_pointer_value();

            let inner1 = self.extract_pair_fst(outer1)?;
            let s_prime = self.extract_pair_snd(outer1)?;
            let a = self.extract_pair_fst(inner1)?;
            let w1 = self.extract_pair_snd(inner1)?;

            // kr = k(k, a) → new closure
            let k_fn = self.extract_closure_fn_ptr(k)?;
            let kr = self
                .builder()
                .build_indirect_call(fn_type2, k_fn, &[k.into(), a.into()], "wt_bind_st_kr")
                .map_err(|e| CodegenError::Internal(format!("WriterT bind/st k: {:?}", e)))?
                .try_as_basic_value()
                .basic()
                .ok_or_else(|| CodegenError::Internal("WriterT bind/st k: void".to_string()))?
                .into_pointer_value();

            // outer2 = kr(kr, s') → ((b, w2), s'')
            let kr_fn = self.extract_closure_fn_ptr(kr)?;
            let outer2 = self
                .builder()
                .build_indirect_call(
                    fn_type2,
                    kr_fn,
                    &[kr.into(), s_prime.into()],
                    "wt_bind_st_outer2",
                )
                .map_err(|e| CodegenError::Internal(format!("WriterT bind/st kr: {:?}", e)))?
                .try_as_basic_value()
                .basic()
                .ok_or_else(|| CodegenError::Internal("WriterT bind/st kr: void".to_string()))?
                .into_pointer_value();

            let inner2 = self.extract_pair_fst(outer2)?;
            let s_double_prime = self.extract_pair_snd(outer2)?;
            let b = self.extract_pair_fst(inner2)?;
            let w2 = self.extract_pair_snd(inner2)?;

            // w_combined = w1 ++ w2
            let append_fn = self.get_or_create_list_append_fn()?;
            let w_combined = self
                .builder()
                .build_call(append_fn, &[w1.into(), w2.into()], "wt_bind_st_append")
                .map_err(|e| CodegenError::Internal(format!("WriterT bind/st append: {:?}", e)))?
                .try_as_basic_value()
                .basic()
                .ok_or_else(|| {
                    CodegenError::Internal("WriterT bind/st append: void".to_string())
                })?;

            let result_inner = self.alloc_pair(b.into(), w_combined)?;
            let result_outer = self.alloc_pair(result_inner.into(), s_double_prime.into())?;
            self.builder()
                .build_return(Some(&result_outer))
                .map_err(|e| CodegenError::Internal(format!("writer_t_bind/st return: {:?}", e)))?;
            if let Some(bb) = saved_bb {
                self.builder().position_at_end(bb);
            }
        }

        let fn_ptr = func.as_global_value().as_pointer_value();
        let m_ptr = self.value_to_ptr(m_val)?;
        let k_ptr = self.value_to_ptr(k_val)?;
        let closure_ptr = self.alloc_closure(
            fn_ptr,
            &[
                (VarId::new(900000), m_ptr.into()),
                (VarId::new(900001), k_ptr.into()),
            ],
        )?;
        Ok(Some(closure_ptr.into()))
    }

    /// WriterT.>>= over ReaderT: \(env, r) ->
    ///   pair1 = m(m, r) → (a, w1)
    ///   kr = k(k, a) → new closure
    ///   pair2 = kr(kr, r) → (b, w2)
    ///   return (b, w1++w2)
    fn lower_builtin_writer_t_bind_over_rt(
        &mut self,
        m_expr: &Expr,
        k_expr: &Expr,
    ) -> CodegenResult<Option<BasicValueEnum<'ctx>>> {
        let m_val = self
            .lower_expr(m_expr)?
            .ok_or_else(|| CodegenError::Internal("WriterT.>>=/rt: m has no value".to_string()))?;
        let k_val = self
            .lower_expr(k_expr)?
            .ok_or_else(|| CodegenError::Internal("WriterT.>>=/rt: k has no value".to_string()))?;

        let ptr_type = self.type_mapper().ptr_type();
        let fn_name = "bhc_writer_t_bind_over_rt";
        let func = self.get_or_create_transformer_fn(fn_name);
        if func.count_basic_blocks() == 0 {
            let entry = self.llvm_ctx.append_basic_block(func, "entry");
            let saved_bb = self.builder().get_insert_block();
            self.builder().position_at_end(entry);
            let env = func.get_nth_param(0).unwrap().into_pointer_value();
            let r = func.get_nth_param(1).unwrap().into_pointer_value();
            let m = self.extract_closure_env_elem(env, 2, 0)?;
            let k = self.extract_closure_env_elem(env, 2, 1)?;

            let fn_type2 = ptr_type.fn_type(&[ptr_type.into(), ptr_type.into()], false);

            // pair1 = m(m, r) → (a, w1)
            let m_fn = self.extract_closure_fn_ptr(m)?;
            let pair1 = self
                .builder()
                .build_indirect_call(fn_type2, m_fn, &[m.into(), r.into()], "wt_bind_rt_pair1")
                .map_err(|e| CodegenError::Internal(format!("WriterT bind/rt m: {:?}", e)))?
                .try_as_basic_value()
                .basic()
                .ok_or_else(|| CodegenError::Internal("WriterT bind/rt m: void".to_string()))?
                .into_pointer_value();

            let a = self.extract_pair_fst(pair1)?;
            let w1 = self.extract_pair_snd(pair1)?;

            // kr = k(k, a) → new closure
            let k_fn = self.extract_closure_fn_ptr(k)?;
            let kr = self
                .builder()
                .build_indirect_call(fn_type2, k_fn, &[k.into(), a.into()], "wt_bind_rt_kr")
                .map_err(|e| CodegenError::Internal(format!("WriterT bind/rt k: {:?}", e)))?
                .try_as_basic_value()
                .basic()
                .ok_or_else(|| CodegenError::Internal("WriterT bind/rt k: void".to_string()))?
                .into_pointer_value();

            // pair2 = kr(kr, r) → (b, w2)
            let kr_fn = self.extract_closure_fn_ptr(kr)?;
            let pair2 = self
                .builder()
                .build_indirect_call(fn_type2, kr_fn, &[kr.into(), r.into()], "wt_bind_rt_pair2")
                .map_err(|e| CodegenError::Internal(format!("WriterT bind/rt kr: {:?}", e)))?
                .try_as_basic_value()
                .basic()
                .ok_or_else(|| CodegenError::Internal("WriterT bind/rt kr: void".to_string()))?
                .into_pointer_value();

            let b = self.extract_pair_fst(pair2)?;
            let w2 = self.extract_pair_snd(pair2)?;

            // w_combined = w1 ++ w2
            let append_fn = self.get_or_create_list_append_fn()?;
            let w_combined = self
                .builder()
                .build_call(append_fn, &[w1.into(), w2.into()], "wt_bind_rt_append")
                .map_err(|e| CodegenError::Internal(format!("WriterT bind/rt append: {:?}", e)))?
                .try_as_basic_value()
                .basic()
                .ok_or_else(|| {
                    CodegenError::Internal("WriterT bind/rt append: void".to_string())
                })?;

            let result_pair = self.alloc_pair(b.into(), w_combined)?;
            self.builder()
                .build_return(Some(&result_pair))
                .map_err(|e| CodegenError::Internal(format!("writer_t_bind/rt return: {:?}", e)))?;
            if let Some(bb) = saved_bb {
                self.builder().position_at_end(bb);
            }
        }

        let fn_ptr = func.as_global_value().as_pointer_value();
        let m_ptr = self.value_to_ptr(m_val)?;
        let k_ptr = self.value_to_ptr(k_val)?;
        let closure_ptr = self.alloc_closure(
            fn_ptr,
            &[
                (VarId::new(900000), m_ptr.into()),
                (VarId::new(900001), k_ptr.into()),
            ],
        )?;
        Ok(Some(closure_ptr.into()))
    }

    /// WriterT.>> over StateT: \(env, s) ->
    ///   outer1 = m1(m1, s) → ((_, w1), s')
    ///   outer2 = m2(m2, s') → ((b, w2), s'')
    ///   return ((b, w1++w2), s'')
    fn lower_builtin_writer_t_then_over_st(
        &mut self,
        m1_expr: &Expr,
        m2_expr: &Expr,
    ) -> CodegenResult<Option<BasicValueEnum<'ctx>>> {
        let m1_val = self
            .lower_expr(m1_expr)?
            .ok_or_else(|| CodegenError::Internal("WriterT.>>/st: m1 has no value".to_string()))?;
        let m2_val = self
            .lower_expr(m2_expr)?
            .ok_or_else(|| CodegenError::Internal("WriterT.>>/st: m2 has no value".to_string()))?;

        let ptr_type = self.type_mapper().ptr_type();
        let fn_name = "bhc_writer_t_then_over_st";
        let func = self.get_or_create_transformer_fn(fn_name);
        if func.count_basic_blocks() == 0 {
            let entry = self.llvm_ctx.append_basic_block(func, "entry");
            let saved_bb = self.builder().get_insert_block();
            self.builder().position_at_end(entry);
            let env = func.get_nth_param(0).unwrap().into_pointer_value();
            let s = func.get_nth_param(1).unwrap().into_pointer_value();
            let m1 = self.extract_closure_env_elem(env, 2, 0)?;
            let m2 = self.extract_closure_env_elem(env, 2, 1)?;

            let fn_type2 = ptr_type.fn_type(&[ptr_type.into(), ptr_type.into()], false);

            // outer1 = m1(m1, s) → ((_, w1), s')
            let m1_fn = self.extract_closure_fn_ptr(m1)?;
            let outer1 = self
                .builder()
                .build_indirect_call(fn_type2, m1_fn, &[m1.into(), s.into()], "wt_then_st_outer1")
                .map_err(|e| CodegenError::Internal(format!("WriterT then/st m1: {:?}", e)))?
                .try_as_basic_value()
                .basic()
                .ok_or_else(|| CodegenError::Internal("WriterT then/st m1: void".to_string()))?
                .into_pointer_value();

            let inner1 = self.extract_pair_fst(outer1)?;
            let s_prime = self.extract_pair_snd(outer1)?;
            let w1 = self.extract_pair_snd(inner1)?;

            // outer2 = m2(m2, s') → ((b, w2), s'')
            let m2_fn = self.extract_closure_fn_ptr(m2)?;
            let outer2 = self
                .builder()
                .build_indirect_call(
                    fn_type2,
                    m2_fn,
                    &[m2.into(), s_prime.into()],
                    "wt_then_st_outer2",
                )
                .map_err(|e| CodegenError::Internal(format!("WriterT then/st m2: {:?}", e)))?
                .try_as_basic_value()
                .basic()
                .ok_or_else(|| CodegenError::Internal("WriterT then/st m2: void".to_string()))?
                .into_pointer_value();

            let inner2 = self.extract_pair_fst(outer2)?;
            let s_double_prime = self.extract_pair_snd(outer2)?;
            let b = self.extract_pair_fst(inner2)?;
            let w2 = self.extract_pair_snd(inner2)?;

            // w_combined = w1 ++ w2
            let append_fn = self.get_or_create_list_append_fn()?;
            let w_combined = self
                .builder()
                .build_call(append_fn, &[w1.into(), w2.into()], "wt_then_st_append")
                .map_err(|e| CodegenError::Internal(format!("WriterT then/st append: {:?}", e)))?
                .try_as_basic_value()
                .basic()
                .ok_or_else(|| {
                    CodegenError::Internal("WriterT then/st append: void".to_string())
                })?;

            let result_inner = self.alloc_pair(b.into(), w_combined)?;
            let result_outer = self.alloc_pair(result_inner.into(), s_double_prime.into())?;
            self.builder()
                .build_return(Some(&result_outer))
                .map_err(|e| CodegenError::Internal(format!("writer_t_then/st return: {:?}", e)))?;
            if let Some(bb) = saved_bb {
                self.builder().position_at_end(bb);
            }
        }

        let fn_ptr = func.as_global_value().as_pointer_value();
        let m1_ptr = self.value_to_ptr(m1_val)?;
        let m2_ptr = self.value_to_ptr(m2_val)?;
        let closure_ptr = self.alloc_closure(
            fn_ptr,
            &[
                (VarId::new(900000), m1_ptr.into()),
                (VarId::new(900001), m2_ptr.into()),
            ],
        )?;
        Ok(Some(closure_ptr.into()))
    }

    /// WriterT.>> over ReaderT: \(env, r) ->
    ///   pair1 = m1(m1, r) → (_, w1)
    ///   pair2 = m2(m2, r) → (b, w2)
    ///   return (b, w1++w2)
    fn lower_builtin_writer_t_then_over_rt(
        &mut self,
        m1_expr: &Expr,
        m2_expr: &Expr,
    ) -> CodegenResult<Option<BasicValueEnum<'ctx>>> {
        let m1_val = self
            .lower_expr(m1_expr)?
            .ok_or_else(|| CodegenError::Internal("WriterT.>>/rt: m1 has no value".to_string()))?;
        let m2_val = self
            .lower_expr(m2_expr)?
            .ok_or_else(|| CodegenError::Internal("WriterT.>>/rt: m2 has no value".to_string()))?;

        let ptr_type = self.type_mapper().ptr_type();
        let fn_name = "bhc_writer_t_then_over_rt";
        let func = self.get_or_create_transformer_fn(fn_name);
        if func.count_basic_blocks() == 0 {
            let entry = self.llvm_ctx.append_basic_block(func, "entry");
            let saved_bb = self.builder().get_insert_block();
            self.builder().position_at_end(entry);
            let env = func.get_nth_param(0).unwrap().into_pointer_value();
            let r = func.get_nth_param(1).unwrap().into_pointer_value();
            let m1 = self.extract_closure_env_elem(env, 2, 0)?;
            let m2 = self.extract_closure_env_elem(env, 2, 1)?;

            let fn_type2 = ptr_type.fn_type(&[ptr_type.into(), ptr_type.into()], false);

            // pair1 = m1(m1, r) → (_, w1)
            let m1_fn = self.extract_closure_fn_ptr(m1)?;
            let pair1 = self
                .builder()
                .build_indirect_call(fn_type2, m1_fn, &[m1.into(), r.into()], "wt_then_rt_pair1")
                .map_err(|e| CodegenError::Internal(format!("WriterT then/rt m1: {:?}", e)))?
                .try_as_basic_value()
                .basic()
                .ok_or_else(|| CodegenError::Internal("WriterT then/rt m1: void".to_string()))?
                .into_pointer_value();

            let w1 = self.extract_pair_snd(pair1)?;

            // pair2 = m2(m2, r) → (b, w2)
            let m2_fn = self.extract_closure_fn_ptr(m2)?;
            let pair2 = self
                .builder()
                .build_indirect_call(fn_type2, m2_fn, &[m2.into(), r.into()], "wt_then_rt_pair2")
                .map_err(|e| CodegenError::Internal(format!("WriterT then/rt m2: {:?}", e)))?
                .try_as_basic_value()
                .basic()
                .ok_or_else(|| CodegenError::Internal("WriterT then/rt m2: void".to_string()))?
                .into_pointer_value();

            let b = self.extract_pair_fst(pair2)?;
            let w2 = self.extract_pair_snd(pair2)?;

            // w_combined = w1 ++ w2
            let append_fn = self.get_or_create_list_append_fn()?;
            let w_combined = self
                .builder()
                .build_call(append_fn, &[w1.into(), w2.into()], "wt_then_rt_append")
                .map_err(|e| CodegenError::Internal(format!("WriterT then/rt append: {:?}", e)))?
                .try_as_basic_value()
                .basic()
                .ok_or_else(|| {
                    CodegenError::Internal("WriterT then/rt append: void".to_string())
                })?;

            let result_pair = self.alloc_pair(b.into(), w_combined)?;
            self.builder()
                .build_return(Some(&result_pair))
                .map_err(|e| CodegenError::Internal(format!("writer_t_then/rt return: {:?}", e)))?;
            if let Some(bb) = saved_bb {
                self.builder().position_at_end(bb);
            }
        }

        let fn_ptr = func.as_global_value().as_pointer_value();
        let m1_ptr = self.value_to_ptr(m1_val)?;
        let m2_ptr = self.value_to_ptr(m2_val)?;
        let closure_ptr = self.alloc_closure(
            fn_ptr,
            &[
                (VarId::new(900000), m1_ptr.into()),
                (VarId::new(900001), m2_ptr.into()),
            ],
        )?;
        Ok(Some(closure_ptr.into()))
    }

    /// WriterT.lift auto over StateT: \(env, s) -> ((action(action, s).fst, []), action(action, s).snd)
    fn apply_writer_t_lift_to_value_over_st(
        &mut self,
        action_val: BasicValueEnum<'ctx>,
    ) -> CodegenResult<BasicValueEnum<'ctx>> {
        let fn_name = "bhc_writer_t_lift_auto_over_st";
        let func = self.get_or_create_transformer_fn(fn_name);
        if func.count_basic_blocks() == 0 {
            let entry = self.llvm_ctx.append_basic_block(func, "entry");
            let saved_bb = self.builder().get_insert_block();
            self.builder().position_at_end(entry);
            let env = func.get_nth_param(0).unwrap().into_pointer_value();
            let s = func.get_nth_param(1).unwrap().into_pointer_value();
            let action = self.extract_closure_env_elem(env, 1, 0)?;

            let ptr_type = self.type_mapper().ptr_type();
            let fn_type2 = ptr_type.fn_type(&[ptr_type.into(), ptr_type.into()], false);

            // pair = action(action, s) → (value, s')
            let action_fn = self.extract_closure_fn_ptr(action)?;
            let pair = self
                .builder()
                .build_indirect_call(
                    fn_type2,
                    action_fn,
                    &[action.into(), s.into()],
                    "wt_lift_st_pair",
                )
                .map_err(|e| CodegenError::Internal(format!("writer_t_lift/st action: {:?}", e)))?
                .try_as_basic_value()
                .basic()
                .ok_or_else(|| CodegenError::Internal("writer_t_lift/st: void".to_string()))?
                .into_pointer_value();

            let a = self.extract_pair_fst(pair)?;
            let s_prime = self.extract_pair_snd(pair)?;

            let empty = self.build_nil()?;
            let inner_pair = self.alloc_pair(a.into(), empty.into())?;
            let outer_pair = self.alloc_pair(inner_pair.into(), s_prime.into())?;
            self.builder()
                .build_return(Some(&outer_pair))
                .map_err(|e| CodegenError::Internal(format!("writer_t_lift/st return: {:?}", e)))?;
            if let Some(bb) = saved_bb {
                self.builder().position_at_end(bb);
            }
        }

        let fn_ptr = func.as_global_value().as_pointer_value();
        let action_ptr = self.value_to_ptr(action_val)?;
        let closure_ptr = self.alloc_closure(fn_ptr, &[(VarId::new(900103), action_ptr.into())])?;
        Ok(closure_ptr.into())
    }

    /// WriterT.lift auto over ReaderT: \(env, r) -> (action(action, r), [])
    fn apply_writer_t_lift_to_value_over_rt(
        &mut self,
        action_val: BasicValueEnum<'ctx>,
    ) -> CodegenResult<BasicValueEnum<'ctx>> {
        let fn_name = "bhc_writer_t_lift_auto_over_rt";
        let func = self.get_or_create_transformer_fn(fn_name);
        if func.count_basic_blocks() == 0 {
            let entry = self.llvm_ctx.append_basic_block(func, "entry");
            let saved_bb = self.builder().get_insert_block();
            self.builder().position_at_end(entry);
            let env = func.get_nth_param(0).unwrap().into_pointer_value();
            let r = func.get_nth_param(1).unwrap().into_pointer_value();
            let action = self.extract_closure_env_elem(env, 1, 0)?;

            let ptr_type = self.type_mapper().ptr_type();
            let fn_type2 = ptr_type.fn_type(&[ptr_type.into(), ptr_type.into()], false);

            // a = action(action, r) — ReaderT action returns value directly
            let action_fn = self.extract_closure_fn_ptr(action)?;
            let a = self
                .builder()
                .build_indirect_call(
                    fn_type2,
                    action_fn,
                    &[action.into(), r.into()],
                    "wt_lift_rt_a",
                )
                .map_err(|e| CodegenError::Internal(format!("writer_t_lift/rt action: {:?}", e)))?
                .try_as_basic_value()
                .basic()
                .ok_or_else(|| CodegenError::Internal("writer_t_lift/rt: void".to_string()))?;

            let empty = self.build_nil()?;
            let pair = self.alloc_pair(a, empty.into())?;
            self.builder()
                .build_return(Some(&pair))
                .map_err(|e| CodegenError::Internal(format!("writer_t_lift/rt return: {:?}", e)))?;
            if let Some(bb) = saved_bb {
                self.builder().position_at_end(bb);
            }
        }

        let fn_ptr = func.as_global_value().as_pointer_value();
        let action_ptr = self.value_to_ptr(action_val)?;
        let closure_ptr = self.alloc_closure(fn_ptr, &[(VarId::new(900103), action_ptr.into())])?;
        Ok(closure_ptr.into())
    }

    // ========================================================================
    // Phase 1: Numeric & Character Operation Handlers
    // ========================================================================

    /// Lower `negate` - negate a numeric value.
    fn lower_builtin_negate(&mut self, expr: &Expr) -> CodegenResult<Option<BasicValueEnum<'ctx>>> {
        // Rational dispatch
        if self.is_rational_expr(expr) {
            return self.lower_builtin_rational_unary(expr, 1000907, "negate");
        }
        let val = self
            .lower_expr(expr)?
            .ok_or_else(|| CodegenError::Internal("negate: no value".to_string()))?;
        let int_val = self.coerce_to_int(val)?;
        let zero = self.type_mapper().i64_type().const_zero();
        let result = self
            .builder()
            .build_int_sub(zero, int_val, "negate")
            .map_err(|e| CodegenError::Internal(format!("negate failed: {:?}", e)))?;
        Ok(Some(self.int_to_ptr(result)?.into()))
    }

    /// Lower `abs` - absolute value.
    fn lower_builtin_abs(&mut self, expr: &Expr) -> CodegenResult<Option<BasicValueEnum<'ctx>>> {
        // Rational dispatch
        if self.is_rational_expr(expr) {
            return self.lower_builtin_rational_unary(expr, 1000908, "abs");
        }
        // Double dispatch: a `Double` value is a boxed f64, so integer abs on its
        // bits would corrupt it (clearing the sign of `-2.5`'s bit pattern gives
        // 1.75). Use floating-point abs instead.
        if self.expr_returns_double(expr) {
            let val = self
                .lower_expr(expr)?
                .ok_or_else(|| CodegenError::Internal("abs: no value".to_string()))?;
            let f = self.coerce_to_f64(val)?;
            let zero = self.type_mapper().f64_type().const_float(0.0);
            let is_neg = self
                .builder()
                .build_float_compare(inkwell::FloatPredicate::OLT, f, zero, "is_neg")
                .map_err(|e| CodegenError::Internal(format!("abs fcmp failed: {:?}", e)))?;
            let neg = self
                .builder()
                .build_float_neg(f, "fneg")
                .map_err(|e| CodegenError::Internal(format!("abs fneg failed: {:?}", e)))?;
            let result = self
                .builder()
                .build_select(is_neg, neg, f, "fabs")
                .map_err(|e| CodegenError::Internal(format!("abs select failed: {:?}", e)))?
                .into_float_value();
            return Ok(Some(self.f64_to_ptr(result)?.into()));
        }
        let val = self
            .lower_expr(expr)?
            .ok_or_else(|| CodegenError::Internal("abs: no value".to_string()))?;
        let int_val = self.coerce_to_int(val)?;
        let zero = self.type_mapper().i64_type().const_zero();
        let is_neg = self
            .builder()
            .build_int_compare(inkwell::IntPredicate::SLT, int_val, zero, "is_neg")
            .map_err(|e| CodegenError::Internal(format!("abs cmp failed: {:?}", e)))?;
        let neg_val = self
            .builder()
            .build_int_sub(zero, int_val, "neg")
            .map_err(|e| CodegenError::Internal(format!("abs neg failed: {:?}", e)))?;
        let result = self
            .builder()
            .build_select(is_neg, neg_val, int_val, "abs")
            .map_err(|e| CodegenError::Internal(format!("abs select failed: {:?}", e)))?
            .into_int_value();
        Ok(Some(self.int_to_ptr(result)?.into()))
    }

    /// Lower `signum` - sign of a value (-1, 0, or 1).
    fn lower_builtin_signum(&mut self, expr: &Expr) -> CodegenResult<Option<BasicValueEnum<'ctx>>> {
        // Rational dispatch
        if self.is_rational_expr(expr) {
            return self.lower_builtin_rational_unary(expr, 1000909, "signum");
        }
        let val = self
            .lower_expr(expr)?
            .ok_or_else(|| CodegenError::Internal("signum: no value".to_string()))?;
        let int_val = self.coerce_to_int(val)?;
        let tm = self.type_mapper();
        let zero = tm.i64_type().const_zero();
        let one = tm.i64_type().const_int(1, false);
        let neg_one = tm.i64_type().const_all_ones();
        let is_pos = self
            .builder()
            .build_int_compare(inkwell::IntPredicate::SGT, int_val, zero, "is_pos")
            .map_err(|e| CodegenError::Internal(format!("signum failed: {:?}", e)))?;
        let is_neg = self
            .builder()
            .build_int_compare(inkwell::IntPredicate::SLT, int_val, zero, "is_neg")
            .map_err(|e| CodegenError::Internal(format!("signum failed: {:?}", e)))?;
        let step1 = self
            .builder()
            .build_select(is_pos, one, zero, "signum_step1")
            .map_err(|e| CodegenError::Internal(format!("signum failed: {:?}", e)))?
            .into_int_value();
        let result = self
            .builder()
            .build_select(is_neg, neg_one, step1, "signum")
            .map_err(|e| CodegenError::Internal(format!("signum failed: {:?}", e)))?
            .into_int_value();
        Ok(Some(self.int_to_ptr(result)?.into()))
    }

    /// Lower a unary math function (sqrt, exp, log, sin, cos, etc.)
    fn lower_builtin_math_unary(
        &mut self,
        expr: &Expr,
        rts_id: usize,
        name: &str,
    ) -> CodegenResult<Option<BasicValueEnum<'ctx>>> {
        let val = self
            .lower_expr(expr)?
            .ok_or_else(|| CodegenError::Internal(format!("{}: no value", name)))?;
        let f64_val = self.coerce_to_f64(val)?;
        let rts_fn = self
            .functions
            .get(&VarId::new(rts_id))
            .ok_or_else(|| CodegenError::Internal(format!("RTS function {} not declared", name)))?;
        let result = self
            .builder()
            .build_call(*rts_fn, &[f64_val.into()], name)
            .map_err(|e| CodegenError::Internal(format!("{} call failed: {:?}", name, e)))?
            .try_as_basic_value()
            .basic()
            .ok_or_else(|| CodegenError::Internal(format!("{}: returned void", name)))?;
        Ok(Some(self.f64_to_ptr(result.into_float_value())?.into()))
    }

    /// Lower a binary math function (atan2).
    fn lower_builtin_math_binary(
        &mut self,
        expr1: &Expr,
        expr2: &Expr,
        rts_id: usize,
        name: &str,
    ) -> CodegenResult<Option<BasicValueEnum<'ctx>>> {
        let val1 = self
            .lower_expr(expr1)?
            .ok_or_else(|| CodegenError::Internal(format!("{}: no arg1", name)))?;
        let val2 = self
            .lower_expr(expr2)?
            .ok_or_else(|| CodegenError::Internal(format!("{}: no arg2", name)))?;
        let f1 = self.coerce_to_f64(val1)?;
        let f2 = self.coerce_to_f64(val2)?;
        let rts_fn = self
            .functions
            .get(&VarId::new(rts_id))
            .ok_or_else(|| CodegenError::Internal(format!("RTS function {} not declared", name)))?;
        let result = self
            .builder()
            .build_call(*rts_fn, &[f1.into(), f2.into()], name)
            .map_err(|e| CodegenError::Internal(format!("{} call failed: {:?}", name, e)))?
            .try_as_basic_value()
            .basic()
            .ok_or_else(|| CodegenError::Internal(format!("{}: returned void", name)))?;
        Ok(Some(self.f64_to_ptr(result.into_float_value())?.into()))
    }

    /// Lower float-to-int conversion (ceiling, floor, round, truncate).
    fn lower_builtin_float_to_int(
        &mut self,
        expr: &Expr,
        rts_id: usize,
        name: &str,
    ) -> CodegenResult<Option<BasicValueEnum<'ctx>>> {
        let val = self
            .lower_expr(expr)?
            .ok_or_else(|| CodegenError::Internal(format!("{}: no value", name)))?;
        let f64_val = self.coerce_to_f64(val)?;
        let rts_fn = self
            .functions
            .get(&VarId::new(rts_id))
            .ok_or_else(|| CodegenError::Internal(format!("RTS function {} not declared", name)))?;
        let result = self
            .builder()
            .build_call(*rts_fn, &[f64_val.into()], name)
            .map_err(|e| CodegenError::Internal(format!("{} call failed: {:?}", name, e)))?
            .try_as_basic_value()
            .basic()
            .ok_or_else(|| CodegenError::Internal(format!("{}: returned void", name)))?;
        Ok(Some(self.int_to_ptr(result.into_int_value())?.into()))
    }

    /// Lower `%` operator — construct a Rational from two Int args.
    fn lower_builtin_rational_make(
        &mut self,
        num_expr: &Expr,
        denom_expr: &Expr,
    ) -> CodegenResult<Option<BasicValueEnum<'ctx>>> {
        let num = self
            .lower_expr(num_expr)?
            .ok_or_else(|| CodegenError::Internal("%: no numerator".to_string()))?;
        let denom = self
            .lower_expr(denom_expr)?
            .ok_or_else(|| CodegenError::Internal("%: no denominator".to_string()))?;
        let num_int = self.coerce_to_int(num)?;
        let denom_int = self.coerce_to_int(denom)?;
        let rts_fn = *self
            .functions
            .get(&VarId::new(1000900))
            .ok_or_else(|| CodegenError::Internal("bhc_rational_make not declared".to_string()))?;
        let result = self
            .builder()
            .build_call(rts_fn, &[num_int.into(), denom_int.into()], "rational_make")
            .map_err(|e| CodegenError::Internal(format!("rational_make call: {:?}", e)))?;
        Ok(result.try_as_basic_value().basic())
    }

    /// Lower a unary Rational RTS operation (numerator, denominator, negate, abs, signum, recip).
    fn lower_builtin_rational_unary(
        &mut self,
        expr: &Expr,
        rts_id: usize,
        name: &str,
    ) -> CodegenResult<Option<BasicValueEnum<'ctx>>> {
        let val = self
            .lower_expr(expr)?
            .ok_or_else(|| CodegenError::Internal(format!("{}: no value", name)))?;
        let val_ptr = self.value_to_ptr(val)?;
        let rts_fn = *self.functions.get(&VarId::new(rts_id)).ok_or_else(|| {
            CodegenError::Internal(format!(
                "RTS function {} (VarId {}) not declared",
                name, rts_id
            ))
        })?;
        let result = self
            .builder()
            .build_call(rts_fn, &[val_ptr.into()], name)
            .map_err(|e| CodegenError::Internal(format!("{} call failed: {:?}", name, e)))?;
        Ok(result.try_as_basic_value().basic())
    }

    /// Lower `toRational` — convert Int to Rational via bhc_rational_from_int.
    fn lower_builtin_to_rational(
        &mut self,
        expr: &Expr,
    ) -> CodegenResult<Option<BasicValueEnum<'ctx>>> {
        // If the argument is already Rational, it's identity
        if self.is_rational_expr(expr) {
            return self.lower_expr(expr);
        }
        let val = self
            .lower_expr(expr)?
            .ok_or_else(|| CodegenError::Internal("toRational: no value".to_string()))?;
        let int_val = self.coerce_to_int(val)?;
        let rts_fn = *self.functions.get(&VarId::new(1000912)).ok_or_else(|| {
            CodegenError::Internal("bhc_rational_from_int not declared".to_string())
        })?;
        let result = self
            .builder()
            .build_call(rts_fn, &[int_val.into()], "to_rational")
            .map_err(|e| CodegenError::Internal(format!("toRational call: {:?}", e)))?;
        Ok(result.try_as_basic_value().basic())
    }

    /// Lower `fromRational` — convert Rational to target type.
    /// For Rational -> Rational: identity. For Rational -> Float/Double: call bhc_rational_to_double.
    fn lower_builtin_from_rational(
        &mut self,
        expr: &Expr,
    ) -> CodegenResult<Option<BasicValueEnum<'ctx>>> {
        let val = self
            .lower_expr(expr)?
            .ok_or_else(|| CodegenError::Internal("fromRational: no value".to_string()))?;
        // Default: identity (Rational -> Rational)
        Ok(Some(val))
    }

    /// Lower `ord` - character to integer (identity in our representation).
    fn lower_builtin_ord(&mut self, expr: &Expr) -> CodegenResult<Option<BasicValueEnum<'ctx>>> {
        self.lower_expr(expr)
    }

    /// Lower `chr` - integer to character (identity in our representation).
    fn lower_builtin_chr(&mut self, expr: &Expr) -> CodegenResult<Option<BasicValueEnum<'ctx>>> {
        self.lower_expr(expr)
    }

    /// Lower `even n` — returns Bool ADT (tag 0=False, 1=True).
    fn lower_builtin_even(&mut self, expr: &Expr) -> CodegenResult<Option<BasicValueEnum<'ctx>>> {
        let val = self
            .lower_expr(expr)?
            .ok_or_else(|| CodegenError::Internal("even: no value".to_string()))?;
        let int_val = self.coerce_to_int(val)?;
        let i64_type = self.type_mapper().i64_type();
        let two = i64_type.const_int(2, false);
        let rem = self
            .builder()
            .build_int_signed_rem(int_val, two, "even_rem")
            .map_err(|e| CodegenError::Internal(format!("even: srem failed: {:?}", e)))?;
        let is_even = self
            .builder()
            .build_int_compare(
                inkwell::IntPredicate::EQ,
                rem,
                i64_type.const_zero(),
                "is_even",
            )
            .map_err(|e| CodegenError::Internal(format!("even: cmp failed: {:?}", e)))?;
        let tag = self
            .builder()
            .build_int_z_extend(is_even, i64_type, "even_tag")
            .map_err(|e| CodegenError::Internal(format!("even: extend failed: {:?}", e)))?;
        self.allocate_bool_adt(tag, "even")
    }

    /// Lower `odd n` — returns Bool ADT (tag 0=False, 1=True).
    fn lower_builtin_odd(&mut self, expr: &Expr) -> CodegenResult<Option<BasicValueEnum<'ctx>>> {
        let val = self
            .lower_expr(expr)?
            .ok_or_else(|| CodegenError::Internal("odd: no value".to_string()))?;
        let int_val = self.coerce_to_int(val)?;
        let i64_type = self.type_mapper().i64_type();
        let two = i64_type.const_int(2, false);
        let rem = self
            .builder()
            .build_int_signed_rem(int_val, two, "odd_rem")
            .map_err(|e| CodegenError::Internal(format!("odd: srem failed: {:?}", e)))?;
        let is_odd = self
            .builder()
            .build_int_compare(
                inkwell::IntPredicate::NE,
                rem,
                i64_type.const_zero(),
                "is_odd",
            )
            .map_err(|e| CodegenError::Internal(format!("odd: cmp failed: {:?}", e)))?;
        let tag = self
            .builder()
            .build_int_z_extend(is_odd, i64_type, "odd_tag")
            .map_err(|e| CodegenError::Internal(format!("odd: extend failed: {:?}", e)))?;
        self.allocate_bool_adt(tag, "odd")
    }

    /// Allocate a Bool ADT with dynamic tag (0=False, 1=True).
    fn allocate_bool_adt(
        &mut self,
        tag: inkwell::values::IntValue<'ctx>,
        name: &str,
    ) -> CodegenResult<Option<BasicValueEnum<'ctx>>> {
        let adt_ty = self.adt_type(0);
        let size_val = self.type_mapper().i64_type().const_int(8, false);
        let alloc_fn = self
            .functions
            .get(&VarId::new(1000005))
            .ok_or_else(|| CodegenError::Internal("bhc_alloc not declared".to_string()))?;
        let raw_ptr = self
            .builder()
            .build_call(*alloc_fn, &[size_val.into()], &format!("{}_alloc", name))
            .map_err(|e| CodegenError::Internal(format!("{}: alloc failed: {:?}", name, e)))?
            .try_as_basic_value()
            .basic()
            .ok_or_else(|| CodegenError::Internal(format!("{}: alloc returned void", name)))?;
        let ptr = raw_ptr.into_pointer_value();
        let tag_ptr = self
            .builder()
            .build_struct_gep(adt_ty, ptr, 0, &format!("{}_tag_ptr", name))
            .map_err(|e| CodegenError::Internal(format!("{}: gep failed: {:?}", name, e)))?;
        self.builder()
            .build_store(tag_ptr, tag)
            .map_err(|e| CodegenError::Internal(format!("{}: store failed: {:?}", name, e)))?;
        Ok(Some(ptr.into()))
    }

    /// Allocate an Ordering ADT with dynamic tag (0=LT, 1=EQ, 2=GT).
    fn allocate_ordering_adt(
        &mut self,
        tag: inkwell::values::IntValue<'ctx>,
        name: &str,
    ) -> CodegenResult<Option<BasicValueEnum<'ctx>>> {
        // Identical layout to Bool ADT — 8-byte tag-only struct
        let adt_ty = self.adt_type(0);
        let size_val = self.type_mapper().i64_type().const_int(8, false);
        let alloc_fn = self
            .functions
            .get(&VarId::new(1000005))
            .ok_or_else(|| CodegenError::Internal("bhc_alloc not declared".to_string()))?;
        let raw_ptr = self
            .builder()
            .build_call(*alloc_fn, &[size_val.into()], &format!("{}_alloc", name))
            .map_err(|e| CodegenError::Internal(format!("{}: alloc failed: {:?}", name, e)))?
            .try_as_basic_value()
            .basic()
            .ok_or_else(|| CodegenError::Internal(format!("{}: alloc returned void", name)))?;
        let ptr = raw_ptr.into_pointer_value();
        let tag_ptr = self
            .builder()
            .build_struct_gep(adt_ty, ptr, 0, &format!("{}_tag_ptr", name))
            .map_err(|e| CodegenError::Internal(format!("{}: gep failed: {:?}", name, e)))?;
        self.builder()
            .build_store(tag_ptr, tag)
            .map_err(|e| CodegenError::Internal(format!("{}: store failed: {:?}", name, e)))?;
        Ok(Some(ptr.into()))
    }

    /// Lower `from x` (GHC.Generics) — dispatch to derived $derived_from_TypeName.
    fn lower_builtin_generic_from(
        &mut self,
        arg_expr: &Expr,
    ) -> CodegenResult<Option<BasicValueEnum<'ctx>>> {
        // Try to infer the ADT type from the argument expression
        let type_name = self.infer_adt_type_from_expr(arg_expr);

        // Fallback: if there's exactly one derived_from_fn, use it (common case)
        let type_name = type_name.or_else(|| {
            if self.derived_from_fns.len() == 1 {
                Some(self.derived_from_fns.keys().next().unwrap().clone())
            } else {
                None
            }
        });

        if let Some(type_name) = type_name {
            if let Some(from_var_id) = self.derived_from_fns.get(&type_name).copied() {
                if let Some(from_fn) = self.functions.get(&from_var_id).copied() {
                    let value = self.lower_expr(arg_expr)?.ok_or_else(|| {
                        CodegenError::Internal("from: failed to lower ADT value".to_string())
                    })?;
                    // Call derived from: fn(env_ptr, value) -> rep_ptr
                    let null_env = self.type_mapper().ptr_type().const_null();
                    let fn_ptr = from_fn.as_global_value().as_pointer_value();
                    let fn_type = self.type_mapper().ptr_type().fn_type(
                        &[
                            self.type_mapper().ptr_type().into(),
                            self.type_mapper().ptr_type().into(),
                        ],
                        false,
                    );
                    let result = self
                        .builder()
                        .build_indirect_call(
                            fn_type,
                            fn_ptr,
                            &[null_env.into(), value.into()],
                            "derived_from",
                        )
                        .map_err(|e| CodegenError::Internal(format!("from: call failed: {:?}", e)))?
                        .try_as_basic_value()
                        .basic()
                        .ok_or_else(|| CodegenError::Internal("from: void result".to_string()))?;
                    return Ok(Some(result));
                }
            }
        }
        // Fallback: identity (for types without Generic instance)
        self.lower_expr(arg_expr)
    }

    /// Lower `to rep` (GHC.Generics) — dispatch to derived $derived_to_TypeName.
    /// Note: `to` dispatches based on what the result type should be, which is
    /// determined from the expression context. Since we can't easily infer the
    /// result type, we look at the argument's rep structure to find the original type.
    fn lower_builtin_generic_to(
        &mut self,
        arg_expr: &Expr,
    ) -> CodegenResult<Option<BasicValueEnum<'ctx>>> {
        // For `to`, we need to find which type this is for.
        // Strategy: check if the argument was produced by `from` of a known type,
        // or look for derived_to_fns and try each one.
        // Simplest approach: if there's exactly one derived_to_fn, use it.
        // Otherwise, check if the argument expression gives us a hint.

        // Check if argument is (from x) — then we know the type from x
        if let Expr::App(f, inner_arg, _) = arg_expr {
            if let Expr::Var(fv, _) = f.as_ref() {
                if fv.name.as_str() == "from" {
                    if let Some(type_name) = self.infer_adt_type_from_expr(inner_arg) {
                        if let Some(to_var_id) = self.derived_to_fns.get(&type_name).copied() {
                            if let Some(to_fn) = self.functions.get(&to_var_id).copied() {
                                let value = self.lower_expr(arg_expr)?.ok_or_else(|| {
                                    CodegenError::Internal(
                                        "to: failed to lower rep value".to_string(),
                                    )
                                })?;
                                let null_env = self.type_mapper().ptr_type().const_null();
                                let fn_ptr = to_fn.as_global_value().as_pointer_value();
                                let fn_type = self.type_mapper().ptr_type().fn_type(
                                    &[
                                        self.type_mapper().ptr_type().into(),
                                        self.type_mapper().ptr_type().into(),
                                    ],
                                    false,
                                );
                                let result = self
                                    .builder()
                                    .build_indirect_call(
                                        fn_type,
                                        fn_ptr,
                                        &[null_env.into(), value.into()],
                                        "derived_to",
                                    )
                                    .map_err(|e| {
                                        CodegenError::Internal(format!("to: call failed: {:?}", e))
                                    })?
                                    .try_as_basic_value()
                                    .basic()
                                    .ok_or_else(|| {
                                        CodegenError::Internal("to: void result".to_string())
                                    })?;
                                return Ok(Some(result));
                            }
                        }
                    }
                }
            }
        }

        // If there's exactly one derived_to_fn, use it (common case in tests)
        if self.derived_to_fns.len() == 1 {
            let (_, to_var_id) = self.derived_to_fns.iter().next().unwrap();
            let to_var_id = *to_var_id;
            if let Some(to_fn) = self.functions.get(&to_var_id).copied() {
                let value = self.lower_expr(arg_expr)?.ok_or_else(|| {
                    CodegenError::Internal("to: failed to lower rep value".to_string())
                })?;
                let null_env = self.type_mapper().ptr_type().const_null();
                let fn_ptr = to_fn.as_global_value().as_pointer_value();
                let fn_type = self.type_mapper().ptr_type().fn_type(
                    &[
                        self.type_mapper().ptr_type().into(),
                        self.type_mapper().ptr_type().into(),
                    ],
                    false,
                );
                let result = self
                    .builder()
                    .build_indirect_call(
                        fn_type,
                        fn_ptr,
                        &[null_env.into(), value.into()],
                        "derived_to",
                    )
                    .map_err(|e| CodegenError::Internal(format!("to: call failed: {:?}", e)))?
                    .try_as_basic_value()
                    .basic()
                    .ok_or_else(|| CodegenError::Internal("to: void result".to_string()))?;
                return Ok(Some(result));
            }
        }

        // Fallback: identity
        self.lower_expr(arg_expr)
    }

    /// Lower `compare a b` — returns Ordering ADT (tag 0=LT, 1=EQ, 2=GT).
    fn lower_builtin_compare(
        &mut self,
        a_expr: &Expr,
        b_expr: &Expr,
    ) -> CodegenResult<Option<BasicValueEnum<'ctx>>> {
        // Check if comparing user ADTs with derived Ord
        let adt_type = Self::is_user_adt_expr(a_expr, &self.constructor_metadata)
            .or_else(|| Self::is_user_adt_expr(b_expr, &self.constructor_metadata));
        if let Some(type_name) = adt_type {
            if let Some(cmp_var_id) = self.derived_compare_fns.get(&type_name).copied() {
                if let Some(cmp_fn) = self.functions.get(&cmp_var_id).copied() {
                    let lhs = self
                        .lower_expr(a_expr)?
                        .ok_or_else(|| CodegenError::Internal("compare: no lhs".to_string()))?;
                    let rhs = self
                        .lower_expr(b_expr)?
                        .ok_or_else(|| CodegenError::Internal("compare: no rhs".to_string()))?;
                    let null_env = self.type_mapper().ptr_type().const_null();
                    let fn_ptr = cmp_fn.as_global_value().as_pointer_value();
                    let fn_type = self.type_mapper().ptr_type().fn_type(
                        &[
                            self.type_mapper().ptr_type().into(),
                            self.type_mapper().ptr_type().into(),
                            self.type_mapper().ptr_type().into(),
                        ],
                        false,
                    );
                    let result = self
                        .builder()
                        .build_indirect_call(
                            fn_type,
                            fn_ptr,
                            &[null_env.into(), lhs.into(), rhs.into()],
                            "derived_compare",
                        )
                        .map_err(|e| CodegenError::Internal(format!("derived compare: {:?}", e)))?;
                    let ordering_ptr = result.try_as_basic_value().basic().ok_or_else(|| {
                        CodegenError::Internal("derived compare returned void".to_string())
                    })?;
                    return Ok(Some(ordering_ptr));
                }
            }
        }

        // Rational comparison
        if self.is_rational_expr(a_expr) || self.is_rational_expr(b_expr) {
            let a_val = self
                .lower_expr(a_expr)?
                .ok_or_else(|| CodegenError::Internal("compare: no lhs".to_string()))?;
            let b_val = self
                .lower_expr(b_expr)?
                .ok_or_else(|| CodegenError::Internal("compare: no rhs".to_string()))?;
            let a_ptr = self.value_to_ptr(a_val)?;
            let b_ptr = self.value_to_ptr(b_val)?;
            let rts_fn = *self.functions.get(&VarId::new(1000911)).ok_or_else(|| {
                CodegenError::Internal("bhc_rational_compare not declared".to_string())
            })?;
            let cmp_result = self
                .builder()
                .build_call(rts_fn, &[a_ptr.into(), b_ptr.into()], "rational_compare")
                .map_err(|e| CodegenError::Internal(format!("rational compare: {:?}", e)))?;
            let cmp_val = cmp_result.try_as_basic_value().basic().ok_or_else(|| {
                CodegenError::Internal("rational compare returned void".to_string())
            })?;
            // bhc_rational_compare returns i32 (-1, 0, 1). Convert to Ordering ADT (tag 0=LT, 1=EQ, 2=GT).
            let cmp_int = if cmp_val.is_int_value() {
                cmp_val.into_int_value()
            } else {
                self.ptr_to_int(cmp_val.into_pointer_value())?
            };
            let i64_type = self.type_mapper().i64_type();
            let zero = i64_type.const_zero();
            let is_lt = self
                .builder()
                .build_int_compare(inkwell::IntPredicate::SLT, cmp_int, zero, "rat_cmp_lt")
                .map_err(|e| CodegenError::Internal(format!("rat cmp lt: {:?}", e)))?;
            let is_gt = self
                .builder()
                .build_int_compare(inkwell::IntPredicate::SGT, cmp_int, zero, "rat_cmp_gt")
                .map_err(|e| CodegenError::Internal(format!("rat cmp gt: {:?}", e)))?;
            let tag_gt_or_eq = self
                .builder()
                .build_select(
                    is_gt,
                    i64_type.const_int(2, false),
                    i64_type.const_int(1, false),
                    "gt_or_eq",
                )
                .map_err(|e| CodegenError::Internal(format!("rat compare select1: {:?}", e)))?
                .into_int_value();
            let tag = self
                .builder()
                .build_select(
                    is_lt,
                    i64_type.const_int(0, false),
                    tag_gt_or_eq,
                    "rat_cmp_tag",
                )
                .map_err(|e| CodegenError::Internal(format!("rat compare select2: {:?}", e)))?;
            return self.allocate_ordering_adt(tag.into_int_value(), "rational_compare");
        }

        // Integer comparison fallback
        let a_val = self
            .lower_expr(a_expr)?
            .ok_or_else(|| CodegenError::Internal("compare: no lhs".to_string()))?;
        let b_val = self
            .lower_expr(b_expr)?
            .ok_or_else(|| CodegenError::Internal("compare: no rhs".to_string()))?;
        let a_int = self.coerce_to_int(a_val)?;
        let b_int = self.coerce_to_int(b_val)?;
        let i64_type = self.type_mapper().i64_type();

        let is_lt = self
            .builder()
            .build_int_compare(inkwell::IntPredicate::SLT, a_int, b_int, "cmp_lt")
            .map_err(|e| CodegenError::Internal(format!("compare: slt: {:?}", e)))?;
        let is_gt = self
            .builder()
            .build_int_compare(inkwell::IntPredicate::SGT, a_int, b_int, "cmp_gt")
            .map_err(|e| CodegenError::Internal(format!("compare: sgt: {:?}", e)))?;

        // tag = is_lt ? 0 : (is_gt ? 2 : 1)
        let tag_gt_or_eq = self
            .builder()
            .build_select(
                is_gt,
                i64_type.const_int(2, false),
                i64_type.const_int(1, false),
                "gt_or_eq",
            )
            .map_err(|e| CodegenError::Internal(format!("compare: select1: {:?}", e)))?
            .into_int_value();
        let tag = self
            .builder()
            .build_select(is_lt, i64_type.const_int(0, false), tag_gt_or_eq, "cmp_tag")
            .map_err(|e| CodegenError::Internal(format!("compare: select2: {:?}", e)))?;

        self.allocate_ordering_adt(tag.into_int_value(), "compare")
    }

    /// Lower a binary integer RTS operation (gcd, lcm).
    fn lower_builtin_int_binop_rts(
        &mut self,
        a_expr: &Expr,
        b_expr: &Expr,
        rts_id: usize,
        name: &str,
    ) -> CodegenResult<Option<BasicValueEnum<'ctx>>> {
        let a_val = self
            .lower_expr(a_expr)?
            .ok_or_else(|| CodegenError::Internal(format!("{}: no a", name)))?;
        let b_val = self
            .lower_expr(b_expr)?
            .ok_or_else(|| CodegenError::Internal(format!("{}: no b", name)))?;
        let a_int = self.coerce_to_int(a_val)?;
        let b_int = self.coerce_to_int(b_val)?;
        let rts_fn = self
            .functions
            .get(&VarId::new(rts_id))
            .ok_or_else(|| CodegenError::Internal(format!("{} RTS function not declared", name)))?;
        let result = self
            .builder()
            .build_call(*rts_fn, &[a_int.into(), b_int.into()], name)
            .map_err(|e| CodegenError::Internal(format!("{} call failed: {:?}", name, e)))?
            .try_as_basic_value()
            .basic()
            .ok_or_else(|| CodegenError::Internal(format!("{}: returned void", name)))?;
        let result_int = result.into_int_value();
        let ptr = self.int_to_ptr(result_int)?;
        Ok(Some(ptr.into()))
    }

    /// Lower `divMod a b` — returns (Int, Int) tuple with floor-division semantics.
    fn lower_builtin_divmod(
        &mut self,
        a_expr: &Expr,
        b_expr: &Expr,
    ) -> CodegenResult<Option<BasicValueEnum<'ctx>>> {
        let a_val = self
            .lower_expr(a_expr)?
            .ok_or_else(|| CodegenError::Internal("divMod: no a".to_string()))?;
        let b_val = self
            .lower_expr(b_expr)?
            .ok_or_else(|| CodegenError::Internal("divMod: no b".to_string()))?;
        let a_int = self.coerce_to_int(a_val)?;
        let b_int = self.coerce_to_int(b_val)?;

        // Haskell divMod uses floor-division: div rounds toward negative infinity
        // d = floor(a/b), m = a - d*b
        let i64_type = self.type_mapper().i64_type();

        // Compute truncated quotient and remainder
        let q = self
            .builder()
            .build_int_signed_div(a_int, b_int, "divmod_q")
            .map_err(|e| CodegenError::Internal(format!("divMod: sdiv failed: {:?}", e)))?;
        let r = self
            .builder()
            .build_int_signed_rem(a_int, b_int, "divmod_r")
            .map_err(|e| CodegenError::Internal(format!("divMod: srem failed: {:?}", e)))?;

        // Adjust for floor division: if remainder != 0 and signs differ, subtract 1 from quotient and add b to remainder
        let r_nonzero = self
            .builder()
            .build_int_compare(inkwell::IntPredicate::NE, r, i64_type.const_zero(), "r_nz")
            .map_err(|e| CodegenError::Internal(format!("divMod: cmp1 failed: {:?}", e)))?;
        // XOR a and b, check sign bit (if signs differ, xor is negative)
        let xor_ab = self
            .builder()
            .build_xor(a_int, b_int, "xor_ab")
            .map_err(|e| CodegenError::Internal(format!("divMod: xor failed: {:?}", e)))?;
        let signs_differ = self
            .builder()
            .build_int_compare(
                inkwell::IntPredicate::SLT,
                xor_ab,
                i64_type.const_zero(),
                "signs_diff",
            )
            .map_err(|e| CodegenError::Internal(format!("divMod: cmp2 failed: {:?}", e)))?;
        let needs_adjust = self
            .builder()
            .build_and(r_nonzero, signs_differ, "needs_adj")
            .map_err(|e| CodegenError::Internal(format!("divMod: and failed: {:?}", e)))?;

        let one = i64_type.const_int(1, false);
        let q_minus_1 = self
            .builder()
            .build_int_sub(q, one, "q_m1")
            .map_err(|e| CodegenError::Internal(format!("divMod: sub failed: {:?}", e)))?;
        let r_plus_b = self
            .builder()
            .build_int_add(r, b_int, "r_pb")
            .map_err(|e| CodegenError::Internal(format!("divMod: add failed: {:?}", e)))?;

        let div_result = self
            .builder()
            .build_select(needs_adjust, q_minus_1, q, "div_res")
            .map_err(|e| CodegenError::Internal(format!("divMod: select1 failed: {:?}", e)))?
            .into_int_value();
        let mod_result = self
            .builder()
            .build_select(needs_adjust, r_plus_b, r, "mod_res")
            .map_err(|e| CodegenError::Internal(format!("divMod: select2 failed: {:?}", e)))?
            .into_int_value();

        self.allocate_int_pair_tuple(div_result, mod_result, "divmod")
    }

    /// Lower `quotRem a b` — returns (Int, Int) tuple with truncation-toward-zero semantics.
    fn lower_builtin_quotrem(
        &mut self,
        a_expr: &Expr,
        b_expr: &Expr,
    ) -> CodegenResult<Option<BasicValueEnum<'ctx>>> {
        let a_val = self
            .lower_expr(a_expr)?
            .ok_or_else(|| CodegenError::Internal("quotRem: no a".to_string()))?;
        let b_val = self
            .lower_expr(b_expr)?
            .ok_or_else(|| CodegenError::Internal("quotRem: no b".to_string()))?;
        let a_int = self.coerce_to_int(a_val)?;
        let b_int = self.coerce_to_int(b_val)?;

        let q = self
            .builder()
            .build_int_signed_div(a_int, b_int, "quotrem_q")
            .map_err(|e| CodegenError::Internal(format!("quotRem: sdiv failed: {:?}", e)))?;
        let r = self
            .builder()
            .build_int_signed_rem(a_int, b_int, "quotrem_r")
            .map_err(|e| CodegenError::Internal(format!("quotRem: srem failed: {:?}", e)))?;

        self.allocate_int_pair_tuple(q, r, "quotrem")
    }

    /// Allocate a (Int, Int) tuple: 24 bytes, tag=0 at offset 0, fst at offset 8, snd at offset 16.
    fn allocate_int_pair_tuple(
        &mut self,
        fst: inkwell::values::IntValue<'ctx>,
        snd: inkwell::values::IntValue<'ctx>,
        name: &str,
    ) -> CodegenResult<Option<BasicValueEnum<'ctx>>> {
        let i64_type = self.type_mapper().i64_type();
        let ptr_type = self.type_mapper().ptr_type();
        let size_val = i64_type.const_int(24, false);
        let alloc_fn = self
            .functions
            .get(&VarId::new(1000005))
            .ok_or_else(|| CodegenError::Internal("bhc_alloc not declared".to_string()))?;
        let raw_ptr = self
            .builder()
            .build_call(*alloc_fn, &[size_val.into()], &format!("{}_alloc", name))
            .map_err(|e| CodegenError::Internal(format!("{}: alloc failed: {:?}", name, e)))?
            .try_as_basic_value()
            .basic()
            .ok_or_else(|| CodegenError::Internal(format!("{}: alloc returned void", name)))?;
        let tuple_ptr = raw_ptr.into_pointer_value();

        // Store tag=0 at offset 0
        let adt_ty = self.adt_type(0);
        let tag_ptr = self
            .builder()
            .build_struct_gep(adt_ty, tuple_ptr, 0, &format!("{}_tag", name))
            .map_err(|e| CodegenError::Internal(format!("{}: tag gep failed: {:?}", name, e)))?;
        self.builder()
            .build_store(tag_ptr, i64_type.const_zero())
            .map_err(|e| CodegenError::Internal(format!("{}: tag store failed: {:?}", name, e)))?;

        // Store fst as ptr at offset 8
        let fst_ptr = self.int_to_ptr(fst)?;
        let field1_ptr = self
            .builder()
            .build_struct_gep(adt_ty, tuple_ptr, 1, &format!("{}_fst", name))
            .map_err(|e| CodegenError::Internal(format!("{}: fst gep failed: {:?}", name, e)))?;
        self.builder()
            .build_store(field1_ptr, fst_ptr)
            .map_err(|e| CodegenError::Internal(format!("{}: fst store failed: {:?}", name, e)))?;

        // Store snd as ptr at offset 16
        let snd_ptr = self.int_to_ptr(snd)?;
        let field2_gep = unsafe {
            self.builder()
                .build_gep(
                    ptr_type,
                    tuple_ptr,
                    &[i64_type.const_int(2, false)],
                    &format!("{}_snd_gep", name),
                )
                .map_err(|e| CodegenError::Internal(format!("{}: snd gep failed: {:?}", name, e)))?
        };
        self.builder()
            .build_store(field2_gep, snd_ptr)
            .map_err(|e| CodegenError::Internal(format!("{}: snd store failed: {:?}", name, e)))?;

        Ok(Some(tuple_ptr.into()))
    }

    /// Lower `newIORef val` — create a new mutable reference.
    fn lower_builtin_new_ioref(
        &mut self,
        val_expr: &Expr,
    ) -> CodegenResult<Option<BasicValueEnum<'ctx>>> {
        let val = self
            .lower_expr(val_expr)?
            .ok_or_else(|| CodegenError::Internal("newIORef: no value".to_string()))?;
        let val_ptr = self.value_to_ptr(val)?;
        let rts_fn = self
            .functions
            .get(&VarId::new(1000502))
            .ok_or_else(|| CodegenError::Internal("bhc_new_ioref not declared".to_string()))?;
        let result = self
            .builder()
            .build_call(*rts_fn, &[val_ptr.into()], "new_ioref")
            .map_err(|e| CodegenError::Internal(format!("newIORef call failed: {:?}", e)))?
            .try_as_basic_value()
            .basic()
            .ok_or_else(|| CodegenError::Internal("newIORef: returned void".to_string()))?;
        Ok(Some(result))
    }

    /// Lower `readIORef ref` — read the current value.
    fn lower_builtin_read_ioref(
        &mut self,
        ref_expr: &Expr,
    ) -> CodegenResult<Option<BasicValueEnum<'ctx>>> {
        let ref_val = self
            .lower_expr(ref_expr)?
            .ok_or_else(|| CodegenError::Internal("readIORef: no ref".to_string()))?;
        let ref_ptr = match ref_val {
            BasicValueEnum::PointerValue(p) => p,
            _ => {
                return Err(CodegenError::TypeError(
                    "readIORef expects pointer".to_string(),
                ))
            }
        };
        let rts_fn = self
            .functions
            .get(&VarId::new(1000503))
            .ok_or_else(|| CodegenError::Internal("bhc_read_ioref not declared".to_string()))?;
        let result = self
            .builder()
            .build_call(*rts_fn, &[ref_ptr.into()], "read_ioref")
            .map_err(|e| CodegenError::Internal(format!("readIORef call failed: {:?}", e)))?
            .try_as_basic_value()
            .basic()
            .ok_or_else(|| CodegenError::Internal("readIORef: returned void".to_string()))?;
        Ok(Some(result))
    }

    /// Lower `writeIORef ref val` — overwrite the value.
    fn lower_builtin_write_ioref(
        &mut self,
        ref_expr: &Expr,
        val_expr: &Expr,
    ) -> CodegenResult<Option<BasicValueEnum<'ctx>>> {
        let ref_val = self
            .lower_expr(ref_expr)?
            .ok_or_else(|| CodegenError::Internal("writeIORef: no ref".to_string()))?;
        let ref_ptr = match ref_val {
            BasicValueEnum::PointerValue(p) => p,
            _ => {
                return Err(CodegenError::TypeError(
                    "writeIORef expects pointer".to_string(),
                ))
            }
        };
        let val = self
            .lower_expr(val_expr)?
            .ok_or_else(|| CodegenError::Internal("writeIORef: no value".to_string()))?;
        let val_ptr = self.value_to_ptr(val)?;
        let rts_fn = self
            .functions
            .get(&VarId::new(1000504))
            .ok_or_else(|| CodegenError::Internal("bhc_write_ioref not declared".to_string()))?;
        self.builder()
            .build_call(*rts_fn, &[ref_ptr.into(), val_ptr.into()], "")
            .map_err(|e| CodegenError::Internal(format!("writeIORef call failed: {:?}", e)))?;
        Ok(Some(self.type_mapper().ptr_type().const_null().into()))
    }

    /// Lower `modifyIORef ref f` — read, apply f, write back.
    fn lower_builtin_modify_ioref(
        &mut self,
        ref_expr: &Expr,
        func_expr: &Expr,
    ) -> CodegenResult<Option<BasicValueEnum<'ctx>>> {
        let ref_val = self
            .lower_expr(ref_expr)?
            .ok_or_else(|| CodegenError::Internal("modifyIORef: no ref".to_string()))?;
        let ref_ptr = match ref_val {
            BasicValueEnum::PointerValue(p) => p,
            _ => {
                return Err(CodegenError::TypeError(
                    "modifyIORef expects pointer".to_string(),
                ))
            }
        };
        let func_val = self
            .lower_expr(func_expr)?
            .ok_or_else(|| CodegenError::Internal("modifyIORef: no function".to_string()))?;
        let func_ptr = match func_val {
            BasicValueEnum::PointerValue(p) => p,
            _ => {
                return Err(CodegenError::TypeError(
                    "modifyIORef: function must be closure".to_string(),
                ))
            }
        };

        // Read current value
        let read_fn = self
            .functions
            .get(&VarId::new(1000503))
            .ok_or_else(|| CodegenError::Internal("bhc_read_ioref not declared".to_string()))?;
        let current_val = self
            .builder()
            .build_call(*read_fn, &[ref_ptr.into()], "modify_read")
            .map_err(|e| CodegenError::Internal(format!("modifyIORef read failed: {:?}", e)))?
            .try_as_basic_value()
            .basic()
            .ok_or_else(|| CodegenError::Internal("modifyIORef: read returned void".to_string()))?;

        // Apply function: f(current_val)
        let ptr_type = self.type_mapper().ptr_type();
        let fn_ptr = self.extract_closure_fn_ptr(func_ptr)?;
        let fn_type = ptr_type.fn_type(&[ptr_type.into(), ptr_type.into()], false);
        let new_val = self
            .builder()
            .build_indirect_call(
                fn_type,
                fn_ptr,
                &[func_ptr.into(), current_val.into()],
                "modify_apply",
            )
            .map_err(|e| CodegenError::Internal(format!("modifyIORef apply failed: {:?}", e)))?
            .try_as_basic_value()
            .basic()
            .ok_or_else(|| {
                CodegenError::Internal("modifyIORef: apply returned void".to_string())
            })?;

        // Write new value
        let write_fn = self
            .functions
            .get(&VarId::new(1000504))
            .ok_or_else(|| CodegenError::Internal("bhc_write_ioref not declared".to_string()))?;
        let new_val_ptr = self.value_to_ptr(new_val)?;
        self.builder()
            .build_call(*write_fn, &[ref_ptr.into(), new_val_ptr.into()], "")
            .map_err(|e| CodegenError::Internal(format!("modifyIORef write failed: {:?}", e)))?;

        Ok(Some(ptr_type.const_null().into()))
    }

    /// Lower `atomicModifyIORef ref f` — read, apply f (returns tuple), write first, return second.
    /// Simplified: in our single-threaded RTS, atomic = non-atomic.
    /// f :: a -> (a, b), we apply f, treat result as tuple, extract fst to write back, return snd.
    fn lower_builtin_atomic_modify_ioref(
        &mut self,
        ref_expr: &Expr,
        func_expr: &Expr,
    ) -> CodegenResult<Option<BasicValueEnum<'ctx>>> {
        let ref_val = self
            .lower_expr(ref_expr)?
            .ok_or_else(|| CodegenError::Internal("atomicModifyIORef: no ref".to_string()))?;
        let ref_ptr = match ref_val {
            BasicValueEnum::PointerValue(p) => p,
            _ => {
                return Err(CodegenError::TypeError(
                    "atomicModifyIORef expects pointer".to_string(),
                ))
            }
        };
        let func_val = self
            .lower_expr(func_expr)?
            .ok_or_else(|| CodegenError::Internal("atomicModifyIORef: no function".to_string()))?;
        let func_ptr = match func_val {
            BasicValueEnum::PointerValue(p) => p,
            _ => {
                return Err(CodegenError::TypeError(
                    "atomicModifyIORef: function must be closure".to_string(),
                ))
            }
        };

        // Read current value
        let read_fn = self
            .functions
            .get(&VarId::new(1000503))
            .ok_or_else(|| CodegenError::Internal("bhc_read_ioref not declared".to_string()))?;
        let current_val = self
            .builder()
            .build_call(*read_fn, &[ref_ptr.into()], "atomic_read")
            .map_err(|e| CodegenError::Internal(format!("atomicModifyIORef read failed: {:?}", e)))?
            .try_as_basic_value()
            .basic()
            .ok_or_else(|| {
                CodegenError::Internal("atomicModifyIORef: read returned void".to_string())
            })?;

        // Apply function: f(current_val) -> (new_a, result_b)
        let ptr_type = self.type_mapper().ptr_type();
        let fn_ptr = self.extract_closure_fn_ptr(func_ptr)?;
        let fn_type = ptr_type.fn_type(&[ptr_type.into(), ptr_type.into()], false);
        let tuple_val = self
            .builder()
            .build_indirect_call(
                fn_type,
                fn_ptr,
                &[func_ptr.into(), current_val.into()],
                "atomic_apply",
            )
            .map_err(|e| {
                CodegenError::Internal(format!("atomicModifyIORef apply failed: {:?}", e))
            })?
            .try_as_basic_value()
            .basic()
            .ok_or_else(|| {
                CodegenError::Internal("atomicModifyIORef: apply returned void".to_string())
            })?;

        // The result is a tuple (a, b) represented as a heap object.
        // Extract field 0 (the new value) and field 1 (the result).
        // Tuples in BHC are allocated as: [tag, field0, field1, ...]
        let tuple_ptr = match tuple_val {
            BasicValueEnum::PointerValue(p) => p,
            _ => return Ok(Some(ptr_type.const_null().into())),
        };

        // Treat tuple as array of pointers: [tag, fst, snd, ...]
        // tag at offset 0, fst at offset 1, snd at offset 2 (each pointer-sized)
        let i64_type = self.type_mapper().i64_type();
        let fst_gep = unsafe {
            self.builder().build_gep(
                ptr_type,
                tuple_ptr,
                &[i64_type.const_int(1, false)],
                "fst_ptr",
            )
        }
        .map_err(|e| CodegenError::Internal(format!("atomicModifyIORef: fst gep: {:?}", e)))?;
        let fst_val = self
            .builder()
            .build_load(ptr_type, fst_gep, "fst_val")
            .map_err(|e| CodegenError::Internal(format!("atomicModifyIORef: load fst: {:?}", e)))?;

        let snd_gep = unsafe {
            self.builder().build_gep(
                ptr_type,
                tuple_ptr,
                &[i64_type.const_int(2, false)],
                "snd_ptr",
            )
        }
        .map_err(|e| CodegenError::Internal(format!("atomicModifyIORef: snd gep: {:?}", e)))?;
        let snd_val = self
            .builder()
            .build_load(ptr_type, snd_gep, "snd_val")
            .map_err(|e| CodegenError::Internal(format!("atomicModifyIORef: load snd: {:?}", e)))?;

        // Write fst (new value) back to the IORef
        let write_fn = self
            .functions
            .get(&VarId::new(1000504))
            .ok_or_else(|| CodegenError::Internal("bhc_write_ioref not declared".to_string()))?;
        let fst_ptr = self.value_to_ptr(fst_val)?;
        self.builder()
            .build_call(*write_fn, &[ref_ptr.into(), fst_ptr.into()], "")
            .map_err(|e| {
                CodegenError::Internal(format!("atomicModifyIORef write failed: {:?}", e))
            })?;

        // Return snd (the result b)
        Ok(Some(snd_val))
    }

    /// Lower a character predicate (isAlpha, isDigit, isSpace, etc.)
    /// Returns a proper Bool ADT (tag 0=False, 1=True) for consistency with True/False constructors.
    fn lower_builtin_char_pred(
        &mut self,
        expr: &Expr,
        rts_id: usize,
        name: &str,
    ) -> CodegenResult<Option<BasicValueEnum<'ctx>>> {
        let val = self
            .lower_expr(expr)?
            .ok_or_else(|| CodegenError::Internal(format!("{}: no value", name)))?;
        let int_val = self.coerce_to_int(val)?;
        let i32_type = self.type_mapper().i32_type();
        let char_val = self
            .builder()
            .build_int_truncate(int_val, i32_type, "char_val")
            .map_err(|e| CodegenError::Internal(format!("{}: truncate failed: {:?}", name, e)))?;
        let rts_fn = self
            .functions
            .get(&VarId::new(rts_id))
            .ok_or_else(|| CodegenError::Internal(format!("char pred {} not declared", name)))?;
        let result = self
            .builder()
            .build_call(*rts_fn, &[char_val.into()], name)
            .map_err(|e| CodegenError::Internal(format!("{} call failed: {:?}", name, e)))?
            .try_as_basic_value()
            .basic()
            .ok_or_else(|| CodegenError::Internal(format!("{}: returned void", name)))?;
        let bool_val = result.into_int_value();
        let tag = self
            .builder()
            .build_int_z_extend(bool_val, self.type_mapper().i64_type(), "bool_tag")
            .map_err(|e| CodegenError::Internal(format!("{}: extend failed: {:?}", name, e)))?;

        // Allocate a Bool ADT with dynamic tag (0=False, 1=True)
        let tm = self.type_mapper();
        let adt_ty = self.adt_type(0);
        let size_val = tm.i64_type().const_int(8, false); // Just tag, no fields
        let alloc_fn = self
            .functions
            .get(&VarId::new(1000005))
            .ok_or_else(|| CodegenError::Internal("bhc_alloc not declared".to_string()))?;
        let raw_ptr = self
            .builder()
            .build_call(*alloc_fn, &[size_val.into()], "bool_alloc")
            .map_err(|e| CodegenError::Internal(format!("{}: alloc failed: {:?}", name, e)))?
            .try_as_basic_value()
            .basic()
            .ok_or_else(|| CodegenError::Internal(format!("{}: alloc returned void", name)))?;
        let ptr = raw_ptr.into_pointer_value();
        let tag_ptr = self
            .builder()
            .build_struct_gep(adt_ty, ptr, 0, "tag_ptr")
            .map_err(|e| CodegenError::Internal(format!("{}: gep failed: {:?}", name, e)))?;
        self.builder()
            .build_store(tag_ptr, tag)
            .map_err(|e| CodegenError::Internal(format!("{}: store failed: {:?}", name, e)))?;
        Ok(Some(ptr.into()))
    }

    /// Lower a character conversion (toLower, toUpper).
    fn lower_builtin_char_conv(
        &mut self,
        expr: &Expr,
        rts_id: usize,
        name: &str,
    ) -> CodegenResult<Option<BasicValueEnum<'ctx>>> {
        let val = self
            .lower_expr(expr)?
            .ok_or_else(|| CodegenError::Internal(format!("{}: no value", name)))?;
        let int_val = self.coerce_to_int(val)?;
        let i32_type = self.type_mapper().i32_type();
        let char_val = self
            .builder()
            .build_int_truncate(int_val, i32_type, "char_val")
            .map_err(|e| CodegenError::Internal(format!("{}: truncate failed: {:?}", name, e)))?;
        let rts_fn = self
            .functions
            .get(&VarId::new(rts_id))
            .ok_or_else(|| CodegenError::Internal(format!("char conv {} not declared", name)))?;
        let result = self
            .builder()
            .build_call(*rts_fn, &[char_val.into()], name)
            .map_err(|e| CodegenError::Internal(format!("{} call failed: {:?}", name, e)))?
            .try_as_basic_value()
            .basic()
            .ok_or_else(|| CodegenError::Internal(format!("{}: returned void", name)))?;
        let u32_val = result.into_int_value();
        let extended = self
            .builder()
            .build_int_z_extend(u32_val, self.type_mapper().i64_type(), "char_ext")
            .map_err(|e| CodegenError::Internal(format!("{}: extend failed: {:?}", name, e)))?;
        Ok(Some(self.int_to_ptr(extended)?.into()))
    }

    /// Lower `digitToInt` - convert digit char to integer.
    fn lower_builtin_digit_to_int(
        &mut self,
        expr: &Expr,
    ) -> CodegenResult<Option<BasicValueEnum<'ctx>>> {
        let val = self
            .lower_expr(expr)?
            .ok_or_else(|| CodegenError::Internal("digitToInt: no value".to_string()))?;
        let int_val = self.coerce_to_int(val)?;
        let i32_type = self.type_mapper().i32_type();
        let char_val = self
            .builder()
            .build_int_truncate(int_val, i32_type, "char_val")
            .map_err(|e| CodegenError::Internal(format!("digitToInt: truncate failed: {:?}", e)))?;
        let rts_fn = self.functions.get(&VarId::new(1000046)).ok_or_else(|| {
            CodegenError::Internal("bhc_char_digit_to_int not declared".to_string())
        })?;
        let result = self
            .builder()
            .build_call(*rts_fn, &[char_val.into()], "digit_to_int")
            .map_err(|e| CodegenError::Internal(format!("digitToInt call failed: {:?}", e)))?
            .try_as_basic_value()
            .basic()
            .ok_or_else(|| CodegenError::Internal("digitToInt: returned void".to_string()))?;
        let i32_val = result.into_int_value();
        let extended = self
            .builder()
            .build_int_s_extend(i32_val, self.type_mapper().i64_type(), "digit_ext")
            .map_err(|e| CodegenError::Internal(format!("digitToInt: extend failed: {:?}", e)))?;
        Ok(Some(self.int_to_ptr(extended)?.into()))
    }

    /// Lower `intToDigit` - convert integer to digit char.
    fn lower_builtin_int_to_digit(
        &mut self,
        expr: &Expr,
    ) -> CodegenResult<Option<BasicValueEnum<'ctx>>> {
        let val = self
            .lower_expr(expr)?
            .ok_or_else(|| CodegenError::Internal("intToDigit: no value".to_string()))?;
        let int_val = self.coerce_to_int(val)?;
        let i32_type = self.type_mapper().i32_type();
        let truncated = self
            .builder()
            .build_int_truncate(int_val, i32_type, "int_val")
            .map_err(|e| CodegenError::Internal(format!("intToDigit: truncate failed: {:?}", e)))?;
        let rts_fn = self.functions.get(&VarId::new(1000047)).ok_or_else(|| {
            CodegenError::Internal("bhc_char_int_to_digit not declared".to_string())
        })?;
        let result = self
            .builder()
            .build_call(*rts_fn, &[truncated.into()], "int_to_digit")
            .map_err(|e| CodegenError::Internal(format!("intToDigit call failed: {:?}", e)))?
            .try_as_basic_value()
            .basic()
            .ok_or_else(|| CodegenError::Internal("intToDigit: returned void".to_string()))?;
        let u32_val = result.into_int_value();
        let extended = self
            .builder()
            .build_int_z_extend(u32_val, self.type_mapper().i64_type(), "digit_char_ext")
            .map_err(|e| CodegenError::Internal(format!("intToDigit: extend failed: {:?}", e)))?;
        Ok(Some(self.int_to_ptr(extended)?.into()))
    }

    // ========================================================================
    // Phase 4: IO & System Operation Handlers
    // ========================================================================

    /// Lower `readFile` - read file contents.
    fn lower_builtin_read_file(
        &mut self,
        path_expr: &Expr,
    ) -> CodegenResult<Option<BasicValueEnum<'ctx>>> {
        let path_val = self
            .lower_expr(path_expr)?
            .ok_or_else(|| CodegenError::Internal("readFile: no path".to_string()))?;
        // Convert [Char] path to C-string for RTS
        let path_ptr = self.value_to_ptr(path_val)?;
        let path_cstr = self.char_list_to_cstring(path_ptr)?;
        let rts_fn = self
            .functions
            .get(&VarId::new(1000050))
            .ok_or_else(|| CodegenError::Internal("bhc_readFile not declared".to_string()))?;
        let cstr_result = self
            .builder()
            .build_call(*rts_fn, &[path_cstr.into()], "read_file")
            .map_err(|e| CodegenError::Internal(format!("readFile call failed: {:?}", e)))?
            .try_as_basic_value()
            .basic()
            .ok_or_else(|| CodegenError::Internal("readFile: returned void".to_string()))?;

        // Check for exception sentinel (null return from bhc_throw).
        // If null, return null immediately from current function so bhc_catch
        // can detect the exception via TLS instead of continuing execution.
        let result_ptr = cstr_result.into_pointer_value();
        let tm = self.type_mapper();
        let current_fn = self
            .builder()
            .get_insert_block()
            .and_then(|b| b.get_parent())
            .ok_or_else(|| CodegenError::Internal("readFile: no current function".to_string()))?;
        let early_ret_block = self
            .llvm_context()
            .append_basic_block(current_fn, "readfile_exc");
        let ok_block = self
            .llvm_context()
            .append_basic_block(current_fn, "readfile_ok");

        let is_null = self
            .builder()
            .build_is_null(result_ptr, "readfile_null")
            .map_err(|e| CodegenError::Internal(format!("readFile null check: {:?}", e)))?;
        self.builder()
            .build_conditional_branch(is_null, early_ret_block, ok_block)
            .map_err(|e| CodegenError::Internal(format!("readFile branch: {:?}", e)))?;

        // Exception path: return null sentinel to propagate to bhc_catch
        self.builder().position_at_end(early_ret_block);
        self.builder()
            .build_return(Some(&tm.ptr_type().const_null()))
            .map_err(|e| CodegenError::Internal(format!("readFile exc return: {:?}", e)))?;

        // OK path: convert C-string result to [Char] linked list
        self.builder().position_at_end(ok_block);
        let char_list = self.cstring_to_char_list(result_ptr)?;
        Ok(Some(char_list.into()))
    }

    /// Lower `writeFile` - write string to file.
    fn lower_builtin_write_file(
        &mut self,
        path_expr: &Expr,
        content_expr: &Expr,
    ) -> CodegenResult<Option<BasicValueEnum<'ctx>>> {
        let path_val = self
            .lower_expr(path_expr)?
            .ok_or_else(|| CodegenError::Internal("writeFile: no path".to_string()))?;
        let content_val = self
            .lower_expr(content_expr)?
            .ok_or_else(|| CodegenError::Internal("writeFile: no content".to_string()))?;
        // Convert [Char] args to C-strings for RTS
        let path_ptr = self.value_to_ptr(path_val)?;
        let path_cstr = self.char_list_to_cstring(path_ptr)?;
        let content_ptr = self.value_to_ptr(content_val)?;
        let content_cstr = self.char_list_to_cstring(content_ptr)?;
        let rts_fn = self
            .functions
            .get(&VarId::new(1000051))
            .ok_or_else(|| CodegenError::Internal("bhc_writeFile not declared".to_string()))?;
        self.builder()
            .build_call(*rts_fn, &[path_cstr.into(), content_cstr.into()], "")
            .map_err(|e| CodegenError::Internal(format!("writeFile call failed: {:?}", e)))?;
        Ok(Some(self.type_mapper().ptr_type().const_null().into()))
    }

    /// Lower `appendFile` - append string to file.
    fn lower_builtin_append_file(
        &mut self,
        path_expr: &Expr,
        content_expr: &Expr,
    ) -> CodegenResult<Option<BasicValueEnum<'ctx>>> {
        let path_val = self
            .lower_expr(path_expr)?
            .ok_or_else(|| CodegenError::Internal("appendFile: no path".to_string()))?;
        let content_val = self
            .lower_expr(content_expr)?
            .ok_or_else(|| CodegenError::Internal("appendFile: no content".to_string()))?;
        // Convert [Char] args to C-strings for RTS
        let path_ptr = self.value_to_ptr(path_val)?;
        let path_cstr = self.char_list_to_cstring(path_ptr)?;
        let content_ptr = self.value_to_ptr(content_val)?;
        let content_cstr = self.char_list_to_cstring(content_ptr)?;
        let rts_fn = self
            .functions
            .get(&VarId::new(1000052))
            .ok_or_else(|| CodegenError::Internal("bhc_appendFile not declared".to_string()))?;
        self.builder()
            .build_call(*rts_fn, &[path_cstr.into(), content_cstr.into()], "")
            .map_err(|e| CodegenError::Internal(format!("appendFile call failed: {:?}", e)))?;
        Ok(Some(self.type_mapper().ptr_type().const_null().into()))
    }

    /// Lower `openFile` - open a file handle.
    fn lower_builtin_open_file(
        &mut self,
        path_expr: &Expr,
        mode_expr: &Expr,
    ) -> CodegenResult<Option<BasicValueEnum<'ctx>>> {
        let path_val = self
            .lower_expr(path_expr)?
            .ok_or_else(|| CodegenError::Internal("openFile: no path".to_string()))?;
        let mode_val = self
            .lower_expr(mode_expr)?
            .ok_or_else(|| CodegenError::Internal("openFile: no mode".to_string()))?;
        // Convert [Char] path to C-string for RTS
        let path_ptr = self.value_to_ptr(path_val)?;
        let path_cstr = self.char_list_to_cstring(path_ptr)?;
        // mode_val is an IOMode ADT pointer — load the tag (first i64) to get the mode integer
        let mode_int = match mode_val {
            BasicValueEnum::PointerValue(p) => self
                .builder()
                .build_load(self.type_mapper().i64_type(), p, "mode_tag")
                .map_err(|e| {
                    CodegenError::Internal(format!("openFile: load mode tag failed: {:?}", e))
                })?
                .into_int_value(),
            BasicValueEnum::IntValue(i) => i,
            _ => {
                return Err(CodegenError::TypeError(
                    "openFile: unexpected mode type".to_string(),
                ))
            }
        };
        let mode_i32 = self
            .builder()
            .build_int_truncate(mode_int, self.type_mapper().i32_type(), "mode")
            .map_err(|e| CodegenError::Internal(format!("openFile: truncate failed: {:?}", e)))?;
        let rts_fn = self
            .functions
            .get(&VarId::new(1000053))
            .ok_or_else(|| CodegenError::Internal("bhc_open_file not declared".to_string()))?;
        let result = self
            .builder()
            .build_call(*rts_fn, &[path_cstr.into(), mode_i32.into()], "handle")
            .map_err(|e| CodegenError::Internal(format!("openFile call failed: {:?}", e)))?
            .try_as_basic_value()
            .basic()
            .ok_or_else(|| CodegenError::Internal("openFile: returned void".to_string()))?;
        Ok(Some(result))
    }

    /// Lower `hClose` - close a file handle.
    fn lower_builtin_hclose(
        &mut self,
        handle_expr: &Expr,
    ) -> CodegenResult<Option<BasicValueEnum<'ctx>>> {
        let handle_val = self
            .lower_expr(handle_expr)?
            .ok_or_else(|| CodegenError::Internal("hClose: no handle".to_string()))?;
        let handle_ptr = match handle_val {
            BasicValueEnum::PointerValue(p) => p,
            _ => return Err(CodegenError::TypeError("hClose expects handle".to_string())),
        };
        let rts_fn = self
            .functions
            .get(&VarId::new(1000054))
            .ok_or_else(|| CodegenError::Internal("bhc_close_handle not declared".to_string()))?;
        self.builder()
            .build_call(*rts_fn, &[handle_ptr.into()], "")
            .map_err(|e| CodegenError::Internal(format!("hClose call failed: {:?}", e)))?;
        Ok(Some(self.type_mapper().ptr_type().const_null().into()))
    }

    /// Lower `hGetChar` - read a character from handle.
    fn lower_builtin_hgetchar(
        &mut self,
        handle_expr: &Expr,
    ) -> CodegenResult<Option<BasicValueEnum<'ctx>>> {
        let handle_val = self
            .lower_expr(handle_expr)?
            .ok_or_else(|| CodegenError::Internal("hGetChar: no handle".to_string()))?;
        let handle_ptr = match handle_val {
            BasicValueEnum::PointerValue(p) => p,
            _ => {
                return Err(CodegenError::TypeError(
                    "hGetChar expects handle".to_string(),
                ))
            }
        };
        let rts_fn = self
            .functions
            .get(&VarId::new(1000055))
            .ok_or_else(|| CodegenError::Internal("bhc_hGetChar not declared".to_string()))?;
        let result = self
            .builder()
            .build_call(*rts_fn, &[handle_ptr.into()], "hgetchar")
            .map_err(|e| CodegenError::Internal(format!("hGetChar call failed: {:?}", e)))?
            .try_as_basic_value()
            .basic()
            .ok_or_else(|| CodegenError::Internal("hGetChar: returned void".to_string()))?;
        let char_val = result.into_int_value();
        let extended = self
            .builder()
            .build_int_z_extend(char_val, self.type_mapper().i64_type(), "char_ext")
            .map_err(|e| CodegenError::Internal(format!("hGetChar: extend failed: {:?}", e)))?;
        Ok(Some(self.int_to_ptr(extended)?.into()))
    }

    /// Lower `hGetLine` - read a line from handle.
    fn lower_builtin_hgetline(
        &mut self,
        handle_expr: &Expr,
    ) -> CodegenResult<Option<BasicValueEnum<'ctx>>> {
        let handle_val = self
            .lower_expr(handle_expr)?
            .ok_or_else(|| CodegenError::Internal("hGetLine: no handle".to_string()))?;
        let handle_ptr = match handle_val {
            BasicValueEnum::PointerValue(p) => p,
            _ => {
                return Err(CodegenError::TypeError(
                    "hGetLine expects handle".to_string(),
                ))
            }
        };
        let rts_fn = self
            .functions
            .get(&VarId::new(1000056))
            .ok_or_else(|| CodegenError::Internal("bhc_hGetLine not declared".to_string()))?;
        let cstr_result = self
            .builder()
            .build_call(*rts_fn, &[handle_ptr.into()], "hgetline")
            .map_err(|e| CodegenError::Internal(format!("hGetLine call failed: {:?}", e)))?
            .try_as_basic_value()
            .basic()
            .ok_or_else(|| CodegenError::Internal("hGetLine: returned void".to_string()))?;
        // Convert C-string result to [Char] linked list
        let char_list = self.cstring_to_char_list(cstr_result.into_pointer_value())?;
        Ok(Some(char_list.into()))
    }

    /// Lower `hPutStr` - write string to handle.
    fn lower_builtin_hputstr(
        &mut self,
        handle_expr: &Expr,
        str_expr: &Expr,
    ) -> CodegenResult<Option<BasicValueEnum<'ctx>>> {
        let handle_val = self
            .lower_expr(handle_expr)?
            .ok_or_else(|| CodegenError::Internal("hPutStr: no handle".to_string()))?;
        let str_val = self
            .lower_expr(str_expr)?
            .ok_or_else(|| CodegenError::Internal("hPutStr: no string".to_string()))?;
        let handle_ptr = match handle_val {
            BasicValueEnum::PointerValue(p) => p,
            _ => {
                return Err(CodegenError::TypeError(
                    "hPutStr expects handle".to_string(),
                ))
            }
        };
        // Convert [Char] string to C-string for RTS
        let str_ptr = self.value_to_ptr(str_val)?;
        let str_cstr = self.char_list_to_cstring(str_ptr)?;
        let rts_fn = self
            .functions
            .get(&VarId::new(1000057))
            .ok_or_else(|| CodegenError::Internal("bhc_hPutStr not declared".to_string()))?;
        self.builder()
            .build_call(*rts_fn, &[handle_ptr.into(), str_cstr.into()], "")
            .map_err(|e| CodegenError::Internal(format!("hPutStr call failed: {:?}", e)))?;
        Ok(Some(self.type_mapper().ptr_type().const_null().into()))
    }

    /// Lower `hPutStrLn` - write string + newline to handle.
    fn lower_builtin_hputstrln(
        &mut self,
        handle_expr: &Expr,
        str_expr: &Expr,
    ) -> CodegenResult<Option<BasicValueEnum<'ctx>>> {
        let handle_val = self
            .lower_expr(handle_expr)?
            .ok_or_else(|| CodegenError::Internal("hPutStrLn: no handle".to_string()))?;
        let str_val = self
            .lower_expr(str_expr)?
            .ok_or_else(|| CodegenError::Internal("hPutStrLn: no string".to_string()))?;
        let handle_ptr = match handle_val {
            BasicValueEnum::PointerValue(p) => p,
            _ => {
                return Err(CodegenError::TypeError(
                    "hPutStrLn expects handle".to_string(),
                ))
            }
        };
        // Convert [Char] string to C-string for RTS
        let str_ptr = self.value_to_ptr(str_val)?;
        let str_cstr = self.char_list_to_cstring(str_ptr)?;
        let put_fn = self
            .functions
            .get(&VarId::new(1000057))
            .ok_or_else(|| CodegenError::Internal("bhc_hPutStr not declared".to_string()))?;
        self.builder()
            .build_call(*put_fn, &[handle_ptr.into(), str_cstr.into()], "")
            .map_err(|e| CodegenError::Internal(format!("hPutStrLn put failed: {:?}", e)))?;
        let nl = self.module.add_global_string("hputstrln_nl", "\n");
        self.builder()
            .build_call(*put_fn, &[handle_ptr.into(), nl.into()], "")
            .map_err(|e| CodegenError::Internal(format!("hPutStrLn nl failed: {:?}", e)))?;
        Ok(Some(self.type_mapper().ptr_type().const_null().into()))
    }

    /// Lower `hFlush`.
    fn lower_builtin_hflush(
        &mut self,
        handle_expr: &Expr,
    ) -> CodegenResult<Option<BasicValueEnum<'ctx>>> {
        let handle_val = self
            .lower_expr(handle_expr)?
            .ok_or_else(|| CodegenError::Internal("hFlush: no handle".to_string()))?;
        let handle_ptr = match handle_val {
            BasicValueEnum::PointerValue(p) => p,
            _ => return Err(CodegenError::TypeError("hFlush expects handle".to_string())),
        };
        let rts_fn = self
            .functions
            .get(&VarId::new(1000058))
            .ok_or_else(|| CodegenError::Internal("bhc_hFlush not declared".to_string()))?;
        self.builder()
            .build_call(*rts_fn, &[handle_ptr.into()], "")
            .map_err(|e| CodegenError::Internal(format!("hFlush call failed: {:?}", e)))?;
        Ok(Some(self.type_mapper().ptr_type().const_null().into()))
    }

    /// Lower `hIsEOF`.
    fn lower_builtin_hiseof(
        &mut self,
        handle_expr: &Expr,
    ) -> CodegenResult<Option<BasicValueEnum<'ctx>>> {
        let handle_val = self
            .lower_expr(handle_expr)?
            .ok_or_else(|| CodegenError::Internal("hIsEOF: no handle".to_string()))?;
        let handle_ptr = match handle_val {
            BasicValueEnum::PointerValue(p) => p,
            _ => return Err(CodegenError::TypeError("hIsEOF expects handle".to_string())),
        };
        let rts_fn = self
            .functions
            .get(&VarId::new(1000059))
            .ok_or_else(|| CodegenError::Internal("bhc_hIsEOF not declared".to_string()))?;
        let result = self
            .builder()
            .build_call(*rts_fn, &[handle_ptr.into()], "is_eof")
            .map_err(|e| CodegenError::Internal(format!("hIsEOF call failed: {:?}", e)))?
            .try_as_basic_value()
            .basic()
            .ok_or_else(|| CodegenError::Internal("hIsEOF: returned void".to_string()))?;
        let bool_val = result.into_int_value();
        let extended = self
            .builder()
            .build_int_z_extend(bool_val, self.type_mapper().i64_type(), "eof_ext")
            .map_err(|e| CodegenError::Internal(format!("hIsEOF: extend failed: {:?}", e)))?;
        Ok(Some(self.int_to_ptr(extended)?.into()))
    }

    /// Lower `hSetBuffering` (stub).
    fn lower_builtin_hset_buffering(
        &mut self,
        handle_expr: &Expr,
        mode_expr: &Expr,
    ) -> CodegenResult<Option<BasicValueEnum<'ctx>>> {
        let _ = self.lower_expr(handle_expr)?;
        let _ = self.lower_expr(mode_expr)?;
        Ok(Some(self.type_mapper().ptr_type().const_null().into()))
    }

    /// Lower stdin/stdout/stderr.
    fn lower_builtin_std_handle(
        &mut self,
        rts_id: usize,
        name: &str,
    ) -> CodegenResult<Option<BasicValueEnum<'ctx>>> {
        let rts_fn = self
            .functions
            .get(&VarId::new(rts_id))
            .ok_or_else(|| CodegenError::Internal(format!("bhc_{} not declared", name)))?;
        let result = self
            .builder()
            .build_call(*rts_fn, &[], name)
            .map_err(|e| CodegenError::Internal(format!("{} call failed: {:?}", name, e)))?
            .try_as_basic_value()
            .basic()
            .ok_or_else(|| CodegenError::Internal(format!("{}: returned void", name)))?;
        Ok(Some(result))
    }

    /// Lower file predicate (doesFileExist, doesDirectoryExist).
    fn lower_builtin_file_pred(
        &mut self,
        path_expr: &Expr,
        rts_id: usize,
        name: &str,
    ) -> CodegenResult<Option<BasicValueEnum<'ctx>>> {
        let path_val = self
            .lower_expr(path_expr)?
            .ok_or_else(|| CodegenError::Internal(format!("{}: no path", name)))?;
        // Convert [Char] path to C-string for RTS
        let path_ptr = self.value_to_ptr(path_val)?;
        let path_cstr = self.char_list_to_cstring(path_ptr)?;
        let rts_fn = self
            .functions
            .get(&VarId::new(rts_id))
            .ok_or_else(|| CodegenError::Internal(format!("{} not declared", name)))?;
        let result = self
            .builder()
            .build_call(*rts_fn, &[path_cstr.into()], name)
            .map_err(|e| CodegenError::Internal(format!("{} call failed: {:?}", name, e)))?
            .try_as_basic_value()
            .basic()
            .ok_or_else(|| CodegenError::Internal(format!("{}: returned void", name)))?;
        let bool_i32 = result.into_int_value();
        // Convert i32 result (0/1) to a proper Bool ADT constructor
        // True = tag 1, False = tag 0
        let i64_type = self.type_mapper().i64_type();
        let extended = self
            .builder()
            .build_int_z_extend(bool_i32, i64_type, "pred_ext")
            .map_err(|e| CodegenError::Internal(format!("{}: extend failed: {:?}", name, e)))?;

        // Allocate a Bool ADT: 8-byte tag followed by no fields
        let _ptr_type = self.type_mapper().ptr_type();
        let alloc_fn = self
            .functions
            .get(&VarId::new(1000005))
            .ok_or_else(|| CodegenError::Internal("bhc_alloc not declared".to_string()))?;
        let size = i64_type.const_int(8, false); // Just the tag
        let adt_ptr = self
            .builder()
            .build_call(*alloc_fn, &[size.into()], "bool_adt")
            .map_err(|e| CodegenError::Internal(format!("{}: alloc failed: {:?}", name, e)))?
            .try_as_basic_value()
            .basic()
            .ok_or_else(|| CodegenError::Internal(format!("{}: alloc returned void", name)))?
            .into_pointer_value();
        // Store the tag
        self.builder()
            .build_store(adt_ptr, extended)
            .map_err(|e| CodegenError::Internal(format!("{}: store tag failed: {:?}", name, e)))?;
        Ok(Some(adt_ptr.into()))
    }

    /// Lower `removeFile`.
    fn lower_builtin_remove_file(
        &mut self,
        path_expr: &Expr,
    ) -> CodegenResult<Option<BasicValueEnum<'ctx>>> {
        let path_val = self
            .lower_expr(path_expr)?
            .ok_or_else(|| CodegenError::Internal("removeFile: no path".to_string()))?;
        let path_ptr = self.value_to_ptr(path_val)?;
        let path_cstr = self.char_list_to_cstring(path_ptr)?;
        let rts_fn = self
            .functions
            .get(&VarId::new(1000312))
            .ok_or_else(|| CodegenError::Internal("bhc_remove_file not declared".to_string()))?;
        self.builder()
            .build_call(*rts_fn, &[path_cstr.into()], "")
            .map_err(|e| CodegenError::Internal(format!("removeFile call failed: {:?}", e)))?;
        Ok(Some(self.type_mapper().ptr_type().const_null().into()))
    }

    /// Lower `getArgs`: returns list of command-line arguments.
    fn lower_builtin_get_args(&mut self) -> CodegenResult<Option<BasicValueEnum<'ctx>>> {
        let rts_fn = self
            .functions
            .get(&VarId::new(1000315))
            .ok_or_else(|| CodegenError::Internal("bhc_get_args not declared".to_string()))?;
        let result = self
            .builder()
            .build_call(*rts_fn, &[], "get_args")
            .map_err(|e| CodegenError::Internal(format!("getArgs call failed: {:?}", e)))?
            .try_as_basic_value()
            .basic()
            .ok_or_else(|| CodegenError::Internal("getArgs: returned void".to_string()))?;
        Ok(Some(result))
    }

    /// Lower `getProgName`: returns program name.
    fn lower_builtin_get_prog_name(&mut self) -> CodegenResult<Option<BasicValueEnum<'ctx>>> {
        let rts_fn = self
            .functions
            .get(&VarId::new(1000316))
            .ok_or_else(|| CodegenError::Internal("bhc_get_prog_name not declared".to_string()))?;
        let result = self
            .builder()
            .build_call(*rts_fn, &[], "get_prog_name")
            .map_err(|e| CodegenError::Internal(format!("getProgName call failed: {:?}", e)))?
            .try_as_basic_value()
            .basic()
            .ok_or_else(|| CodegenError::Internal("getProgName: returned void".to_string()))?;
        Ok(Some(result))
    }

    /// Lower `getEnv`.
    fn lower_builtin_get_env(
        &mut self,
        name_expr: &Expr,
    ) -> CodegenResult<Option<BasicValueEnum<'ctx>>> {
        let name_val = self
            .lower_expr(name_expr)?
            .ok_or_else(|| CodegenError::Internal("getEnv: no name".to_string()))?;
        let name_ptr = match name_val {
            BasicValueEnum::PointerValue(p) => p,
            _ => return Err(CodegenError::TypeError("getEnv expects string".to_string())),
        };
        let rts_fn = self
            .functions
            .get(&VarId::new(1000068))
            .ok_or_else(|| CodegenError::Internal("bhc_get_env not declared".to_string()))?;
        let result = self
            .builder()
            .build_call(*rts_fn, &[name_ptr.into()], "get_env")
            .map_err(|e| CodegenError::Internal(format!("getEnv call failed: {:?}", e)))?
            .try_as_basic_value()
            .basic()
            .ok_or_else(|| CodegenError::Internal("getEnv: returned void".to_string()))?;
        Ok(Some(result))
    }

    /// Lower `lookupEnv` (simplified: wraps getEnv).
    fn lower_builtin_lookup_env(
        &mut self,
        name_expr: &Expr,
    ) -> CodegenResult<Option<BasicValueEnum<'ctx>>> {
        self.lower_builtin_get_env(name_expr)
    }

    /// Lower `exitSuccess`.
    fn lower_builtin_exit_success(&mut self) -> CodegenResult<Option<BasicValueEnum<'ctx>>> {
        let rts_fn = self
            .functions
            .get(&VarId::new(1000066))
            .ok_or_else(|| CodegenError::Internal("bhc_exit_success not declared".to_string()))?;
        self.builder()
            .build_call(*rts_fn, &[], "")
            .map_err(|e| CodegenError::Internal(format!("exitSuccess failed: {:?}", e)))?;
        self.builder()
            .build_unreachable()
            .map_err(|e| CodegenError::Internal(format!("unreachable failed: {:?}", e)))?;
        Ok(Some(self.type_mapper().ptr_type().const_null().into()))
    }

    /// Lower `exitFailure`.
    fn lower_builtin_exit_failure(&mut self) -> CodegenResult<Option<BasicValueEnum<'ctx>>> {
        let rts_fn = self
            .functions
            .get(&VarId::new(1000067))
            .ok_or_else(|| CodegenError::Internal("bhc_exit_failure not declared".to_string()))?;
        self.builder()
            .build_call(*rts_fn, &[], "")
            .map_err(|e| CodegenError::Internal(format!("exitFailure failed: {:?}", e)))?;
        self.builder()
            .build_unreachable()
            .map_err(|e| CodegenError::Internal(format!("unreachable failed: {:?}", e)))?;
        Ok(Some(self.type_mapper().ptr_type().const_null().into()))
    }

    /// Lower `exitWith`.
    fn lower_builtin_exit_with(
        &mut self,
        code_expr: &Expr,
    ) -> CodegenResult<Option<BasicValueEnum<'ctx>>> {
        let code_val = self
            .lower_expr(code_expr)?
            .ok_or_else(|| CodegenError::Internal("exitWith: no code".to_string()))?;
        let int_val = self.coerce_to_int(code_val)?;
        let code_i32 = self
            .builder()
            .build_int_truncate(int_val, self.type_mapper().i32_type(), "exit_code")
            .map_err(|e| CodegenError::Internal(format!("exitWith: truncate failed: {:?}", e)))?;
        let rts_fn = self
            .functions
            .get(&VarId::new(1000065))
            .ok_or_else(|| CodegenError::Internal("bhc_exit not declared".to_string()))?;
        self.builder()
            .build_call(*rts_fn, &[code_i32.into()], "")
            .map_err(|e| CodegenError::Internal(format!("exitWith failed: {:?}", e)))?;
        self.builder()
            .build_unreachable()
            .map_err(|e| CodegenError::Internal(format!("unreachable failed: {:?}", e)))?;
        Ok(Some(self.type_mapper().ptr_type().const_null().into()))
    }

    /// Lower `hGetContents`: read all remaining contents from a handle.
    fn lower_builtin_hgetcontents(
        &mut self,
        handle_expr: &Expr,
    ) -> CodegenResult<Option<BasicValueEnum<'ctx>>> {
        let handle_val = self
            .lower_expr(handle_expr)?
            .ok_or_else(|| CodegenError::Internal("hGetContents: no handle".to_string()))?;
        let handle_ptr = match handle_val {
            BasicValueEnum::PointerValue(p) => p,
            _ => {
                return Err(CodegenError::TypeError(
                    "hGetContents expects handle".to_string(),
                ))
            }
        };
        let rts_fn = self
            .functions
            .get(&VarId::new(1000300))
            .ok_or_else(|| CodegenError::Internal("bhc_hGetContents not declared".to_string()))?;
        let result = self
            .builder()
            .build_call(*rts_fn, &[handle_ptr.into()], "hgetcontents")
            .map_err(|e| CodegenError::Internal(format!("hGetContents call failed: {:?}", e)))?
            .try_as_basic_value()
            .basic()
            .ok_or_else(|| CodegenError::Internal("hGetContents: returned void".to_string()))?;
        Ok(Some(result))
    }

    /// Lower `getCurrentDirectory`.
    fn lower_builtin_get_current_directory(
        &mut self,
    ) -> CodegenResult<Option<BasicValueEnum<'ctx>>> {
        let rts_fn = self.functions.get(&VarId::new(1000317)).ok_or_else(|| {
            CodegenError::Internal("bhc_get_current_directory not declared".to_string())
        })?;
        let result = self
            .builder()
            .build_call(*rts_fn, &[], "getcwd")
            .map_err(|e| {
                CodegenError::Internal(format!("getCurrentDirectory call failed: {:?}", e))
            })?
            .try_as_basic_value()
            .basic()
            .ok_or_else(|| {
                CodegenError::Internal("getCurrentDirectory: returned void".to_string())
            })?;
        Ok(Some(result))
    }

    /// Lower `createDirectory`.
    fn lower_builtin_create_directory(
        &mut self,
        path_expr: &Expr,
    ) -> CodegenResult<Option<BasicValueEnum<'ctx>>> {
        let path_val = self
            .lower_expr(path_expr)?
            .ok_or_else(|| CodegenError::Internal("createDirectory: no path".to_string()))?;
        let path_ptr = self.value_to_ptr(path_val)?;
        let path_cstr = self.char_list_to_cstring(path_ptr)?;
        let rts_fn = self.functions.get(&VarId::new(1000311)).ok_or_else(|| {
            CodegenError::Internal("bhc_create_directory not declared".to_string())
        })?;
        self.builder()
            .build_call(*rts_fn, &[path_cstr.into()], "")
            .map_err(|e| CodegenError::Internal(format!("createDirectory call failed: {:?}", e)))?;
        Ok(Some(self.type_mapper().ptr_type().const_null().into()))
    }

    /// Lower `listDirectory`.
    fn lower_builtin_list_directory(
        &mut self,
        path_expr: &Expr,
    ) -> CodegenResult<Option<BasicValueEnum<'ctx>>> {
        let path_val = self
            .lower_expr(path_expr)?
            .ok_or_else(|| CodegenError::Internal("listDirectory: no path".to_string()))?;
        let path_ptr = match path_val {
            BasicValueEnum::PointerValue(p) => p,
            _ => {
                return Err(CodegenError::TypeError(
                    "listDirectory expects string".to_string(),
                ))
            }
        };
        let path_cstr = self.char_list_to_cstring(path_ptr)?;
        let rts_fn = self
            .functions
            .get(&VarId::new(1000313))
            .ok_or_else(|| CodegenError::Internal("bhc_list_directory not declared".to_string()))?;
        let result = self
            .builder()
            .build_call(*rts_fn, &[path_cstr.into()], "listdir")
            .map_err(|e| CodegenError::Internal(format!("listDirectory call failed: {:?}", e)))?
            .try_as_basic_value()
            .basic()
            .ok_or_else(|| CodegenError::Internal("listDirectory: returned void".to_string()))?;
        Ok(Some(result))
    }

    // ========================================================================
    // E.19: System.FilePath + System.Directory codegen
    // ========================================================================

    /// Lower a filepath function that takes String and returns String.
    /// Pattern: char_list_to_cstring -> call RTS -> cstring_to_char_list.
    fn lower_builtin_filepath_str_to_str(
        &mut self,
        path_expr: &Expr,
        rts_id: usize,
        name: &str,
    ) -> CodegenResult<Option<BasicValueEnum<'ctx>>> {
        let path_val = self
            .lower_expr(path_expr)?
            .ok_or_else(|| CodegenError::Internal(format!("{}: no path", name)))?;
        let path_ptr = self.value_to_ptr(path_val)?;
        let path_cstr = self.char_list_to_cstring(path_ptr)?;
        let rts_fn = self
            .functions
            .get(&VarId::new(rts_id))
            .ok_or_else(|| CodegenError::Internal(format!("{} RTS function not declared", name)))?;
        let cstr_result = self
            .builder()
            .build_call(*rts_fn, &[path_cstr.into()], name)
            .map_err(|e| CodegenError::Internal(format!("{} call failed: {:?}", name, e)))?
            .try_as_basic_value()
            .basic()
            .ok_or_else(|| CodegenError::Internal(format!("{}: returned void", name)))?;
        let result_ptr = cstr_result.into_pointer_value();
        let char_list = self.cstring_to_char_list(result_ptr)?;
        Ok(Some(char_list.into()))
    }

    /// Lower a filepath function that takes two Strings and returns String.
    /// Pattern: char_list_to_cstring on both -> call RTS -> cstring_to_char_list.
    fn lower_builtin_filepath_two_str_to_str(
        &mut self,
        arg1_expr: &Expr,
        arg2_expr: &Expr,
        rts_id: usize,
        name: &str,
    ) -> CodegenResult<Option<BasicValueEnum<'ctx>>> {
        let arg1_val = self
            .lower_expr(arg1_expr)?
            .ok_or_else(|| CodegenError::Internal(format!("{}: no arg1", name)))?;
        let arg2_val = self
            .lower_expr(arg2_expr)?
            .ok_or_else(|| CodegenError::Internal(format!("{}: no arg2", name)))?;
        let arg1_ptr = self.value_to_ptr(arg1_val)?;
        let arg1_cstr = self.char_list_to_cstring(arg1_ptr)?;
        let arg2_ptr = self.value_to_ptr(arg2_val)?;
        let arg2_cstr = self.char_list_to_cstring(arg2_ptr)?;
        let rts_fn = self
            .functions
            .get(&VarId::new(rts_id))
            .ok_or_else(|| CodegenError::Internal(format!("{} RTS function not declared", name)))?;
        let cstr_result = self
            .builder()
            .build_call(*rts_fn, &[arg1_cstr.into(), arg2_cstr.into()], name)
            .map_err(|e| CodegenError::Internal(format!("{} call failed: {:?}", name, e)))?
            .try_as_basic_value()
            .basic()
            .ok_or_else(|| CodegenError::Internal(format!("{}: returned void", name)))?;
        let result_ptr = cstr_result.into_pointer_value();
        let char_list = self.cstring_to_char_list(result_ptr)?;
        Ok(Some(char_list.into()))
    }

    /// Lower `splitExtension`: takes String, returns (String, String) tuple.
    /// Codegen-composed: calls dropExtension and takeExtension, packs into tuple.
    fn lower_builtin_split_extension(
        &mut self,
        path_expr: &Expr,
    ) -> CodegenResult<Option<BasicValueEnum<'ctx>>> {
        // Evaluate path once
        let path_val = self
            .lower_expr(path_expr)?
            .ok_or_else(|| CodegenError::Internal("splitExtension: no path".to_string()))?;
        let path_ptr = self.value_to_ptr(path_val)?;
        let path_cstr = self.char_list_to_cstring(path_ptr)?;

        // Call bhc_drop_extension for the base part
        let drop_ext_fn = self
            .functions
            .get(&VarId::new(1000523))
            .ok_or_else(|| CodegenError::Internal("bhc_drop_extension not declared".to_string()))?;
        let base_cstr = self
            .builder()
            .build_call(*drop_ext_fn, &[path_cstr.into()], "split_base")
            .map_err(|e| CodegenError::Internal(format!("splitExtension base: {:?}", e)))?
            .try_as_basic_value()
            .basic()
            .ok_or_else(|| CodegenError::Internal("splitExtension base: void".to_string()))?
            .into_pointer_value();
        let base_list = self.cstring_to_char_list(base_cstr)?;

        // Call bhc_take_extension for the extension part
        let take_ext_fn = self
            .functions
            .get(&VarId::new(1000522))
            .ok_or_else(|| CodegenError::Internal("bhc_take_extension not declared".to_string()))?;
        let ext_cstr = self
            .builder()
            .build_call(*take_ext_fn, &[path_cstr.into()], "split_ext")
            .map_err(|e| CodegenError::Internal(format!("splitExtension ext: {:?}", e)))?
            .try_as_basic_value()
            .basic()
            .ok_or_else(|| CodegenError::Internal("splitExtension ext: void".to_string()))?
            .into_pointer_value();
        let ext_list = self.cstring_to_char_list(ext_cstr)?;

        // Pack into a (String, String) tuple using alloc_pair
        let pair_ptr = self.alloc_pair(base_list.into(), ext_list.into())?;
        Ok(Some(pair_ptr.into()))
    }

    /// Lower a directory function that takes String and returns IO ().
    /// Pattern: char_list_to_cstring -> call void RTS -> return unit.
    fn lower_builtin_dir_str_to_io_unit(
        &mut self,
        path_expr: &Expr,
        rts_id: usize,
        name: &str,
    ) -> CodegenResult<Option<BasicValueEnum<'ctx>>> {
        let path_val = self
            .lower_expr(path_expr)?
            .ok_or_else(|| CodegenError::Internal(format!("{}: no path", name)))?;
        let path_ptr = self.value_to_ptr(path_val)?;
        let path_cstr = self.char_list_to_cstring(path_ptr)?;
        let rts_fn = self
            .functions
            .get(&VarId::new(rts_id))
            .ok_or_else(|| CodegenError::Internal(format!("{} RTS function not declared", name)))?;
        self.builder()
            .build_call(*rts_fn, &[path_cstr.into()], "")
            .map_err(|e| CodegenError::Internal(format!("{} call failed: {:?}", name, e)))?;
        Ok(Some(self.type_mapper().ptr_type().const_null().into()))
    }

    /// Lower a directory function that takes two Strings and returns IO ().
    /// Pattern: char_list_to_cstring on both -> call void RTS -> return unit.
    fn lower_builtin_dir_two_str_to_io_unit(
        &mut self,
        src_expr: &Expr,
        dst_expr: &Expr,
        rts_id: usize,
        name: &str,
    ) -> CodegenResult<Option<BasicValueEnum<'ctx>>> {
        let src_val = self
            .lower_expr(src_expr)?
            .ok_or_else(|| CodegenError::Internal(format!("{}: no src", name)))?;
        let dst_val = self
            .lower_expr(dst_expr)?
            .ok_or_else(|| CodegenError::Internal(format!("{}: no dst", name)))?;
        let src_ptr = self.value_to_ptr(src_val)?;
        let src_cstr = self.char_list_to_cstring(src_ptr)?;
        let dst_ptr = self.value_to_ptr(dst_val)?;
        let dst_cstr = self.char_list_to_cstring(dst_ptr)?;
        let rts_fn = self
            .functions
            .get(&VarId::new(rts_id))
            .ok_or_else(|| CodegenError::Internal(format!("{} RTS function not declared", name)))?;
        self.builder()
            .build_call(*rts_fn, &[src_cstr.into(), dst_cstr.into()], "")
            .map_err(|e| CodegenError::Internal(format!("{} call failed: {:?}", name, e)))?;
        Ok(Some(self.type_mapper().ptr_type().const_null().into()))
    }

    /// Lower `show` — type-aware dispatch to the correct show function.
    /// First tries expr.ty(), falls back to expression structure analysis.
    fn lower_builtin_show(&mut self, expr: &Expr) -> CodegenResult<Option<BasicValueEnum<'ctx>>> {
        let ty = expr.ty();

        // First try type-based dispatch (works when type info is available)
        if !matches!(ty, Ty::Error) && !matches!(ty, Ty::Var(_)) {
            if self.is_bool_type(&ty) {
                return self.lower_builtin_show_typed(expr, 1000091, "show_bool", ShowCoerce::Bool);
            }
            if self.is_char_type(&ty) {
                return self.lower_builtin_show_typed(expr, 1000074, "show_char", ShowCoerce::Char);
            }
            if self.is_double_type(&ty) {
                return self.lower_builtin_show_typed(
                    expr,
                    1000073,
                    "show_double",
                    ShowCoerce::Double,
                );
            }
            if self.is_float_type(&ty) {
                return self.lower_builtin_show_typed(
                    expr,
                    1000090,
                    "show_float",
                    ShowCoerce::Float,
                );
            }
            if self.is_string_type(&ty) {
                return self.lower_builtin_show_typed(
                    expr,
                    1000092,
                    "show_string",
                    ShowCoerce::StringList,
                );
            }
            if self.is_list_type(&ty) {
                return self.lower_builtin_show_typed(expr, 1000093, "show_list", ShowCoerce::List);
            }
            if self.is_maybe_type(&ty).is_some() {
                return self.lower_builtin_show_typed(
                    expr,
                    1000094,
                    "show_maybe",
                    ShowCoerce::MaybeOf,
                );
            }
            if self.is_either_type(&ty).is_some() {
                return self.lower_builtin_show_typed(
                    expr,
                    1000095,
                    "show_either",
                    ShowCoerce::EitherOf,
                );
            }
            if self.is_tuple_type(&ty).is_some() {
                return self.lower_builtin_show_typed(
                    expr,
                    1000096,
                    "show_tuple2",
                    ShowCoerce::Tuple2Of,
                );
            }
            if self.is_unit_type(&ty) {
                return self.lower_builtin_show_typed(expr, 1000097, "show_unit", ShowCoerce::Unit);
            }
            if self.is_rational_type(&ty) {
                // Call bhc_rational_show (VarId 1000915) which returns a C string,
                // then convert to BHC char list for putStrLn
                let val = self
                    .lower_expr(expr)?
                    .ok_or_else(|| CodegenError::Internal("show rational: no value".to_string()))?;
                let val_ptr = self.value_to_ptr(val)?;
                let rts_fn = *self.functions.get(&VarId::new(1000915)).ok_or_else(|| {
                    CodegenError::Internal("bhc_rational_show not declared".to_string())
                })?;
                let cstr_result = self
                    .builder()
                    .build_call(rts_fn, &[val_ptr.into()], "show_rational")
                    .map_err(|e| CodegenError::Internal(format!("show_rational call: {:?}", e)))?
                    .try_as_basic_value()
                    .basic()
                    .ok_or_else(|| {
                        CodegenError::Internal("show_rational: returned void".to_string())
                    })?;
                let char_list = self.cstring_to_char_list(cstr_result.into_pointer_value())?;
                return Ok(Some(char_list.into()));
            }
            if self.is_integer_type(&ty) {
                return self.lower_builtin_show_typed(
                    expr,
                    1000618,
                    "show_integer",
                    ShowCoerce::Integer,
                );
            }
            if self.is_int_type(&ty) {
                return self.lower_builtin_show_typed(expr, 1000072, "show_int", ShowCoerce::Int);
            }
        }

        // Check if the expression is Rational (when type info unavailable)
        if self.is_rational_expr(expr) {
            let val = self
                .lower_expr(expr)?
                .ok_or_else(|| CodegenError::Internal("show rational: no value".to_string()))?;
            let val_ptr = self.value_to_ptr(val)?;
            let rts_fn = *self.functions.get(&VarId::new(1000915)).ok_or_else(|| {
                CodegenError::Internal("bhc_rational_show not declared".to_string())
            })?;
            let cstr_result = self
                .builder()
                .build_call(rts_fn, &[val_ptr.into()], "show_rational")
                .map_err(|e| CodegenError::Internal(format!("show_rational call: {:?}", e)))?
                .try_as_basic_value()
                .basic()
                .ok_or_else(|| {
                    CodegenError::Internal("show_rational: returned void".to_string())
                })?;
            let char_list = self.cstring_to_char_list(cstr_result.into_pointer_value())?;
            return Ok(Some(char_list.into()));
        }

        // Fall back to expression structure analysis when type is Error/Var
        if let Some((coerce, var_id, label)) = self.infer_show_from_expr(expr) {
            return self.lower_builtin_show_typed(expr, var_id, label, coerce);
        }

        // Check if the expression is a user-defined ADT with derived Show
        if let Some(type_name) = self.infer_adt_type_from_expr(expr) {
            if let Some(show_var_id) = self.derived_show_fns.get(&type_name).copied() {
                if let Some(show_fn) = self.functions.get(&show_var_id).copied() {
                    let value = self.lower_expr(expr)?.ok_or_else(|| {
                        CodegenError::Internal("show: failed to lower ADT value".to_string())
                    })?;
                    // Call derived show: fn(env_ptr, value) -> string_ptr
                    // All BHC functions use (env, args...) calling convention
                    let null_env = self.type_mapper().ptr_type().const_null();
                    let fn_ptr = show_fn.as_global_value().as_pointer_value();
                    let fn_type = self.type_mapper().ptr_type().fn_type(
                        &[
                            self.type_mapper().ptr_type().into(),
                            self.type_mapper().ptr_type().into(),
                        ],
                        false,
                    );
                    let result = self
                        .builder()
                        .build_indirect_call(
                            fn_type,
                            fn_ptr,
                            &[null_env.into(), value.into()],
                            "derived_show",
                        )
                        .map_err(|e| {
                            CodegenError::Internal(format!("derived show call: {:?}", e))
                        })?;
                    return Ok(result.try_as_basic_value().basic());
                }
            }
        }

        // Default: Int
        self.lower_builtin_show_typed(expr, 1000072, "show_int", ShowCoerce::Int)
    }

    /// Infer the show coercion from the expression structure when type info is unavailable.
    fn infer_show_from_expr(&self, expr: &Expr) -> Option<(ShowCoerce, usize, &'static str)> {
        match expr {
            // String literal
            Expr::Lit(Literal::String(_), _, _) => {
                Some((ShowCoerce::StringList, 1000092, "show_string"))
            }
            // Int literal
            Expr::Lit(Literal::Int(_), _, _) => Some((ShowCoerce::Int, 1000072, "show_int")),
            // Integer literal (arbitrary precision)
            Expr::Lit(Literal::Integer(_), _, _) => {
                Some((ShowCoerce::Integer, 1000618, "show_integer"))
            }
            // Float/Double literal
            Expr::Lit(Literal::Float(_), _, _) | Expr::Lit(Literal::Double(_), _, _) => {
                Some((ShowCoerce::Double, 1000073, "show_double"))
            }
            // Char literal
            Expr::Lit(Literal::Char(_), _, _) => Some((ShowCoerce::Char, 1000074, "show_char")),
            // Unit "()" or constructor named "()"
            Expr::Var(var, _) if var.name.as_str() == "()" => {
                Some((ShowCoerce::Unit, 1000097, "show_unit"))
            }
            // Bool constructors
            Expr::Var(var, _) if var.name.as_str() == "True" || var.name.as_str() == "False" => {
                Some((ShowCoerce::Bool, 1000091, "show_bool"))
            }
            // Ordering constructors
            Expr::Var(var, _) if matches!(var.name.as_str(), "LT" | "EQ" | "GT") => {
                Some((ShowCoerce::Ordering, 1000098, "show_ordering"))
            }
            // Nothing
            Expr::Var(var, _) if var.name.as_str() == "Nothing" => {
                Some((ShowCoerce::MaybeOf, 1000094, "show_maybe"))
            }
            // E.45: Integer variable (tracked from let-binding or by type)
            Expr::Var(var, _)
                if self.integer_vars.contains(&var.id) || self.is_integer_type(&var.ty) =>
            {
                Some((ShowCoerce::Integer, 1000618, "show_integer"))
            }
            // Empty list []
            Expr::Var(var, _) if var.name.as_str() == "[]" => {
                // Could be [a] for any a - use List with Int default
                Some((ShowCoerce::List, 1000093, "show_list"))
            }
            // Function applications returning known types
            Expr::App(f, _arg, _) => {
                // E.63: force/id are identity functions — infer from argument.
                // The polymorphic `Num`/`Fractional` unary ops preserve their
                // argument's type (`abs`/`negate`/`signum`/`recip`), so render
                // them by the argument too — otherwise a `Double` argument would
                // be misrendered as `Int`.
                if let Expr::Var(fv, _) = f.as_ref() {
                    if matches!(
                        fv.name.as_str(),
                        "force" | "id" | "to" | "abs" | "negate" | "signum" | "recip"
                    ) {
                        return self.infer_show_from_expr(_arg);
                    }
                }
                // E.51: fmap/(<$>) preserves the functor type — infer from the second arg
                if let Expr::App(ff, _, _) = f.as_ref() {
                    if let Expr::Var(fv, _) = ff.as_ref() {
                        if fv.name.as_str() == "fmap" || fv.name.as_str() == "<$>" {
                            return self.infer_show_from_expr(_arg);
                        }
                    }
                }
                // E.52: foldr f z x — result type equals type of z (the init value)
                // Expression structure: App(App(App(foldr, step), init), container)
                // At this level: f = App(App(foldr, step), init), _arg = container
                if let Expr::App(ff, init_val, _) = f.as_ref() {
                    if let Expr::App(fff, _, _) = ff.as_ref() {
                        if let Expr::Var(fv, _) = fff.as_ref() {
                            if fv.name.as_str() == "foldr" {
                                return self.infer_show_from_expr(init_val);
                            }
                        }
                    }
                }
                // E.45: Check if it's a function that returns Integer
                if self.is_integer_expr(expr) {
                    return Some((ShowCoerce::Integer, 1000618, "show_integer"));
                }
                // Check if it's a function that returns Bool
                if self.expr_returns_bool(f) {
                    return Some((ShowCoerce::Bool, 1000091, "show_bool"));
                }
                // Check if it's a function that returns Ordering (e.g., compare x y)
                if self.expr_returns_ordering(f) {
                    return Some((ShowCoerce::Ordering, 1000098, "show_ordering"));
                }
                // Check if it's a function that returns Int (fully applied binary int op)
                if self.expr_returns_int(expr) {
                    return Some((ShowCoerce::Int, 1000072, "show_int"));
                }
                // Check if it's a function/expression that returns Double/Float
                if self.expr_returns_double(expr) {
                    return Some((ShowCoerce::Double, 1000073, "show_double"));
                }
                // Check if it's a function that returns a list
                if self.expr_looks_like_list(f) {
                    return Some((ShowCoerce::List, 1000093, "show_list"));
                }
                match f.as_ref() {
                    // Just x
                    Expr::Var(var, _) if var.name.as_str() == "Just" => {
                        Some((ShowCoerce::MaybeOf, 1000094, "show_maybe"))
                    }
                    // Functions that return Maybe (readMaybe, lookupEnv, find, lookup, elemIndex, findIndex, listToMaybe)
                    Expr::Var(var, _)
                        if matches!(
                            var.name.as_str(),
                            "readMaybe"
                                | "lookupEnv"
                                | "find"
                                | "lookup"
                                | "elemIndex"
                                | "findIndex"
                                | "listToMaybe"
                                | "Data.Map.lookup"
                                | "stripPrefix"
                        ) =>
                    {
                        Some((ShowCoerce::MaybeOf, 1000094, "show_maybe"))
                    }
                    // Left x or Right x
                    Expr::Var(var, _)
                        if var.name.as_str() == "Left" || var.name.as_str() == "Right" =>
                    {
                        Some((ShowCoerce::EitherOf, 1000095, "show_either"))
                    }
                    // Cons application: (:) x xs  — it's a two-arg app, so this is partially applied
                    // The full Cons is App(App((:), head), tail)
                    Expr::App(ff, _head, _) => {
                        match ff.as_ref() {
                            Expr::Var(var, _) if var.name.as_str() == ":" => {
                                // This is a list. Check head to determine element type.
                                Some((ShowCoerce::List, 1000093, "show_list"))
                            }
                            // Tuple constructor: App(App((,), fst), snd)
                            Expr::Var(var, _)
                                if var.name.as_str() == "(,)" || var.name.as_str() == "Tuple2" =>
                            {
                                Some((ShowCoerce::Tuple2Of, 1000096, "show_tuple2"))
                            }
                            _ => None,
                        }
                    }
                    _ => None,
                }
            }
            // Let binding: check body
            Expr::Let(_, body, _) => self.infer_show_from_expr(body),
            // Type application: check inner
            Expr::TyApp(inner, _, _) => self.infer_show_from_expr(inner),
            _ => None,
        }
    }

    /// Infer the show type tag for an element from expression structure.
    /// Returns: 0=Int, 1=Double, 2=Float, 3=Bool, 4=Char, 5=String
    // Show-tag inference helper retained for container show lowering.
    #[allow(dead_code)]
    fn infer_elem_tag_from_expr(&self, expr: &Expr) -> i64 {
        // First try type info
        let ty = expr.ty();
        if !matches!(ty, Ty::Error) && !matches!(ty, Ty::Var(_)) {
            return self.type_to_show_tag(&ty);
        }
        // Fall back to expression structure
        match expr {
            Expr::Lit(Literal::Int(_), _, _) => 0,
            Expr::Lit(Literal::Double(_), _, _) => 1,
            Expr::Lit(Literal::Float(_), _, _) => 2,
            Expr::Lit(Literal::Char(_), _, _) => 4,
            Expr::Lit(Literal::String(_), _, _) => 5,
            Expr::Var(var, _) => match var.name.as_str() {
                "True" | "False" => 3,
                _ => 0,
            },
            _ => 0, // default Int
        }
    }

    /// Get the first element of a Cons-list expression for type inference.
    // Retained for list show-tag inference.
    #[allow(dead_code)]
    fn get_list_head_expr<'e>(&self, expr: &'e Expr) -> Option<&'e Expr> {
        match expr {
            // App(App((:), head), tail)
            Expr::App(f, _tail, _) => {
                if let Expr::App(ff, head, _) = f.as_ref() {
                    if let Expr::Var(var, _) = ff.as_ref() {
                        if var.name.as_str() == ":" {
                            return Some(head);
                        }
                    }
                }
                None
            }
            Expr::Let(_, body, _) => self.get_list_head_expr(body),
            Expr::TyApp(inner, _, _) => self.get_list_head_expr(inner),
            _ => None,
        }
    }

    /// Infer element type tag for a list expression.
    // Retained for list show-tag inference.
    #[allow(dead_code)]
    fn infer_list_elem_tag(&self, list_expr: &Expr) -> i64 {
        // Try to get the head element and infer its type
        if let Some(head) = self.get_list_head_expr(list_expr) {
            return self.infer_elem_tag_from_expr(head);
        }
        0 // default to Int for empty lists
    }

    /// Infer the value type tag for a Maybe/Just expression.
    // Retained for Maybe show-tag inference.
    #[allow(dead_code)]
    fn infer_maybe_elem_tag(&self, maybe_expr: &Expr) -> i64 {
        // Just x → infer from x
        if let Expr::App(f, arg, _) = maybe_expr {
            if let Expr::Var(var, _) = f.as_ref() {
                if var.name.as_str() == "Just" {
                    return self.infer_elem_tag_from_expr(arg);
                }
            }
        }
        0 // Nothing defaults to Int
    }

    /// Infer the left/right type tags for an Either expression.
    // Retained for Either show-tag inference.
    #[allow(dead_code)]
    fn infer_either_tags(&self, either_expr: &Expr) -> (i64, i64) {
        if let Expr::App(f, arg, _) = either_expr {
            if let Expr::Var(var, _) = f.as_ref() {
                match var.name.as_str() {
                    "Left" => return (self.infer_elem_tag_from_expr(arg), 0),
                    "Right" => return (0, self.infer_elem_tag_from_expr(arg)),
                    _ => {}
                }
            }
        }
        (0, 0)
    }

    /// Infer the fst/snd type tags for a tuple expression.
    // Retained for tuple show-tag inference.
    #[allow(dead_code)]
    fn infer_tuple_tags(&self, tuple_expr: &Expr) -> (i64, i64) {
        // App(App((,), fst), snd)
        if let Expr::App(f, snd, _) = tuple_expr {
            if let Expr::App(ff, fst, _) = f.as_ref() {
                if let Expr::Var(var, _) = ff.as_ref() {
                    if var.name.as_str() == "(,)" || var.name.as_str() == "Tuple2" {
                        return (
                            self.infer_elem_tag_from_expr(fst),
                            self.infer_elem_tag_from_expr(snd),
                        );
                    }
                }
            }
        }
        (0, 0)
    }

    /// Create a ShowTypeDesc global constant in the LLVM module.
    /// Returns a pointer to the global.
    fn create_show_desc_global(
        &mut self,
        tag: i64,
        child1: Option<PointerValue<'ctx>>,
        child2: Option<PointerValue<'ctx>>,
    ) -> PointerValue<'ctx> {
        let i64_type = self.type_mapper().i64_type();
        let ptr_type = self.type_mapper().ptr_type();
        let struct_type = self
            .llvm_ctx
            .struct_type(&[i64_type.into(), ptr_type.into(), ptr_type.into()], false);
        let struct_val = struct_type.const_named_struct(&[
            i64_type.const_int(tag as u64, false).into(),
            child1.unwrap_or(ptr_type.const_null()).into(),
            child2.unwrap_or(ptr_type.const_null()).into(),
        ]);
        let name = format!("show_desc_{}", self.show_desc_counter);
        self.show_desc_counter += 1;
        let global = self
            .module
            .llvm_module()
            .add_global(struct_type, None, &name);
        global.set_initializer(&struct_val);
        global.set_constant(true);
        global.as_pointer_value()
    }

    /// Get or create a primitive show descriptor global (leaf types with no children).
    fn get_primitive_show_desc(&mut self, tag: i64) -> PointerValue<'ctx> {
        self.create_show_desc_global(tag, None, None)
    }

    /// Build a recursive show descriptor from a ShowCoerce + expression.
    fn build_show_descriptor(&mut self, expr: &Expr) -> PointerValue<'ctx> {
        // First determine the coerce type
        let coerce = if let Some((c, _, _)) = self.infer_show_from_expr(expr) {
            c
        } else {
            // Try type-based detection
            let ty = expr.ty();
            if !matches!(ty, Ty::Error) && !matches!(ty, Ty::Var(_)) {
                if self.is_bool_type(&ty) {
                    ShowCoerce::Bool
                } else if self.is_char_type(&ty) {
                    ShowCoerce::Char
                } else if self.is_double_type(&ty) {
                    ShowCoerce::Double
                } else if self.is_float_type(&ty) {
                    ShowCoerce::Float
                } else if self.is_string_type(&ty) {
                    ShowCoerce::StringList
                } else if self.is_list_type(&ty) {
                    ShowCoerce::List
                } else if self.is_maybe_type(&ty).is_some() {
                    ShowCoerce::MaybeOf
                } else if self.is_either_type(&ty).is_some() {
                    ShowCoerce::EitherOf
                } else if self.is_tuple_type(&ty).is_some() {
                    ShowCoerce::Tuple2Of
                } else if self.is_unit_type(&ty) {
                    ShowCoerce::Unit
                } else {
                    ShowCoerce::Int
                }
            } else {
                ShowCoerce::Int
            }
        };

        match coerce {
            ShowCoerce::Int => {
                // A user-defined ADT (with derived Show) used as an element of a
                // compound (e.g. `[Red, Green]`, `Just (Circle 5)`) falls through
                // to the Int default here; build an Adt descriptor carrying its
                // derived-show function pointer so the RTS shows it properly
                // instead of printing the element pointer.
                if let Some(desc) = self.try_build_adt_show_desc(expr) {
                    desc
                } else {
                    self.get_primitive_show_desc(0)
                }
            }
            ShowCoerce::Double => self.get_primitive_show_desc(1),
            ShowCoerce::Float => self.get_primitive_show_desc(2),
            ShowCoerce::Bool => self.get_primitive_show_desc(3),
            ShowCoerce::Char => self.get_primitive_show_desc(4),
            ShowCoerce::StringList => self.get_primitive_show_desc(5),
            ShowCoerce::Unit => self.get_primitive_show_desc(6),
            ShowCoerce::Ordering => self.get_primitive_show_desc(7),
            ShowCoerce::Integer => self.get_primitive_show_desc(0), // Use Int tag (0) — Integer show is handled separately
            ShowCoerce::List => {
                let elem_desc = self.build_list_elem_descriptor(expr);
                self.create_show_desc_global(10, Some(elem_desc), None)
            }
            ShowCoerce::MaybeOf => {
                let elem_desc = self.build_maybe_elem_descriptor(expr);
                self.create_show_desc_global(11, Some(elem_desc), None)
            }
            ShowCoerce::Tuple2Of => {
                let (fst_d, snd_d) = self.build_tuple_elem_descriptors(expr);
                self.create_show_desc_global(12, Some(fst_d), Some(snd_d))
            }
            ShowCoerce::EitherOf => {
                let (l_d, r_d) = self.build_either_elem_descriptors(expr);
                self.create_show_desc_global(13, Some(l_d), Some(r_d))
            }
        }
    }

    /// Build an `Adt` show descriptor (tag 14) for a user-defined type with
    /// derived Show, carrying its derived-show function pointer in `child1`.
    /// Returns `None` if `expr` isn't such an ADT.
    fn try_build_adt_show_desc(&mut self, expr: &Expr) -> Option<PointerValue<'ctx>> {
        let type_name = self.infer_adt_type_from_expr(expr)?;
        let show_var_id = *self.derived_show_fns.get(&type_name)?;
        let show_fn = *self.functions.get(&show_var_id)?;
        let fn_ptr = show_fn.as_global_value().as_pointer_value();
        Some(self.create_show_desc_global(14, Some(fn_ptr), None))
    }

    /// Build a descriptor for the element type of a list expression.
    fn build_list_elem_descriptor(&mut self, list_expr: &Expr) -> PointerValue<'ctx> {
        // Try to get the head element and build its descriptor recursively
        if let Some(head) = self.get_list_head_expr_cloned(list_expr) {
            return self.build_show_descriptor(&head);
        }
        self.get_primitive_show_desc(0) // default to Int
    }

    /// Clone the head expression from a Cons-list (needed because build_show_descriptor takes &mut self).
    fn get_list_head_expr_cloned(&self, expr: &Expr) -> Option<Expr> {
        match expr {
            // App(App((:), head), tail)
            Expr::App(f, _tail, _) => {
                if let Expr::App(ff, head, _) = f.as_ref() {
                    if let Expr::Var(var, _) = ff.as_ref() {
                        if var.name.as_str() == ":" {
                            return Some(head.as_ref().clone());
                        }
                    }
                }
                None
            }
            Expr::Let(_, body, _) => self.get_list_head_expr_cloned(body),
            Expr::TyApp(inner, _, _) => self.get_list_head_expr_cloned(inner),
            _ => None,
        }
    }

    /// Build a descriptor for the element type of a Maybe expression.
    fn build_maybe_elem_descriptor(&mut self, maybe_expr: &Expr) -> PointerValue<'ctx> {
        // Just x → build descriptor from x
        if let Expr::App(f, arg, _) = maybe_expr {
            if let Expr::Var(var, _) = f.as_ref() {
                if var.name.as_str() == "Just" {
                    return self.build_show_descriptor(arg);
                }
            }
        }
        self.get_primitive_show_desc(0) // Nothing defaults to Int
    }

    /// Build descriptors for the fst/snd types of a tuple expression.
    fn build_tuple_elem_descriptors(
        &mut self,
        tuple_expr: &Expr,
    ) -> (PointerValue<'ctx>, PointerValue<'ctx>) {
        // App(App((,), fst), snd)
        if let Expr::App(f, snd_expr, _) = tuple_expr {
            if let Expr::App(ff, fst_expr, _) = f.as_ref() {
                if let Expr::Var(var, _) = ff.as_ref() {
                    if var.name.as_str() == "(,)" || var.name.as_str() == "Tuple2" {
                        let fst_cloned = fst_expr.as_ref().clone();
                        let snd_cloned = snd_expr.as_ref().clone();
                        let fst_d = self.build_show_descriptor(&fst_cloned);
                        let snd_d = self.build_show_descriptor(&snd_cloned);
                        return (fst_d, snd_d);
                    }
                }
            }
        }
        let d0 = self.get_primitive_show_desc(0);
        let d0b = self.get_primitive_show_desc(0);
        (d0, d0b)
    }

    /// Build descriptors for the left/right types of an Either expression.
    fn build_either_elem_descriptors(
        &mut self,
        either_expr: &Expr,
    ) -> (PointerValue<'ctx>, PointerValue<'ctx>) {
        if let Expr::App(f, arg, _) = either_expr {
            if let Expr::Var(var, _) = f.as_ref() {
                let arg_cloned = arg.as_ref().clone();
                match var.name.as_str() {
                    "Left" => {
                        let l_d = self.build_show_descriptor(&arg_cloned);
                        let r_d = self.get_primitive_show_desc(0);
                        return (l_d, r_d);
                    }
                    "Right" => {
                        let l_d = self.get_primitive_show_desc(0);
                        let r_d = self.build_show_descriptor(&arg_cloned);
                        return (l_d, r_d);
                    }
                    _ => {}
                }
            }
        }
        let d0 = self.get_primitive_show_desc(0);
        let d0b = self.get_primitive_show_desc(0);
        (d0, d0b)
    }

    /// Lower type-specialized show functions.
    fn lower_builtin_show_typed(
        &mut self,
        expr: &Expr,
        var_id: usize,
        label: &str,
        coerce: ShowCoerce,
    ) -> CodegenResult<Option<BasicValueEnum<'ctx>>> {
        let val = self
            .lower_expr(expr)?
            .ok_or_else(|| CodegenError::Internal(format!("{}: no value", label)))?;

        // For compound types, use bhc_show_with_desc with recursive descriptors
        match coerce {
            ShowCoerce::StringList | ShowCoerce::Unit | ShowCoerce::Integer => {
                // Pass pointer directly: fn(ptr) -> ptr (simple, no nesting)
                let rts_fn = *self
                    .functions
                    .get(&VarId::new(var_id))
                    .ok_or_else(|| CodegenError::Internal(format!("bhc_{} not declared", label)))?;
                let ptr = self.value_to_ptr(val)?;
                let cstr_result = self
                    .builder()
                    .build_call(rts_fn, &[ptr.into()], label)
                    .map_err(|e| CodegenError::Internal(format!("{} call failed: {:?}", label, e)))?
                    .try_as_basic_value()
                    .basic()
                    .ok_or_else(|| CodegenError::Internal(format!("{}: returned void", label)))?;
                let char_list = self.cstring_to_char_list(cstr_result.into_pointer_value())?;
                return Ok(Some(char_list.into()));
            }
            ShowCoerce::List
            | ShowCoerce::MaybeOf
            | ShowCoerce::EitherOf
            | ShowCoerce::Tuple2Of => {
                // Use bhc_show_with_desc with recursive type descriptor
                let ptr = self.value_to_ptr(val)?;
                let desc = self.build_show_descriptor(expr);
                let show_fn = *self.functions.get(&VarId::new(1000099)).ok_or_else(|| {
                    CodegenError::Internal("bhc_show_with_desc not declared".to_string())
                })?;
                let cstr_result = self
                    .builder()
                    .build_call(show_fn, &[ptr.into(), desc.into()], "show_with_desc")
                    .map_err(|e| {
                        CodegenError::Internal(format!("show_with_desc call failed: {:?}", e))
                    })?
                    .try_as_basic_value()
                    .basic()
                    .ok_or_else(|| {
                        CodegenError::Internal("show_with_desc: returned void".to_string())
                    })?;
                let char_list = self.cstring_to_char_list(cstr_result.into_pointer_value())?;
                return Ok(Some(char_list.into()));
            }
            _ => {} // Fall through to primitive coercion handling below
        }

        // For primitive types, use the specific RTS function
        let rts_fn = *self
            .functions
            .get(&VarId::new(var_id))
            .ok_or_else(|| CodegenError::Internal(format!("bhc_{} not declared", label)))?;

        // Primitive coercions (Int, Double, Float, Char, Bool)
        let call_arg: inkwell::values::BasicMetadataValueEnum = match coerce {
            ShowCoerce::Int => {
                let int_val = self.coerce_to_int(val)?;
                int_val.into()
            }
            ShowCoerce::Double => {
                // Handle float values directly (e.g. from Float/Double literals)
                match val {
                    BasicValueEnum::FloatValue(fv) => {
                        // Check if it's already f64
                        if fv.get_type() == self.type_mapper().f64_type() {
                            fv.into()
                        } else {
                            // f32 -> f64 extension
                            let f64_val = self
                                .builder()
                                .build_float_ext(fv, self.type_mapper().f64_type(), "f32_to_f64")
                                .map_err(|e| {
                                    CodegenError::Internal(format!(
                                        "{}: float ext failed: {:?}",
                                        label, e
                                    ))
                                })?;
                            f64_val.into()
                        }
                    }
                    _ => {
                        let int_val = self.coerce_to_int(val)?;
                        let f64_val = self
                            .builder()
                            .build_bit_cast(int_val, self.type_mapper().f64_type(), "to_f64")
                            .map_err(|e| {
                                CodegenError::Internal(format!(
                                    "{}: bitcast failed: {:?}",
                                    label, e
                                ))
                            })?;
                        f64_val.into()
                    }
                }
            }
            ShowCoerce::Float => {
                let int_val = self.coerce_to_int(val)?;
                let truncated = self
                    .builder()
                    .build_int_truncate(int_val, self.llvm_context().i32_type(), "trunc32")
                    .map_err(|e| {
                        CodegenError::Internal(format!("{}: truncate failed: {:?}", label, e))
                    })?;
                let f32_val = self
                    .builder()
                    .build_bit_cast(truncated, self.type_mapper().f32_type(), "to_f32")
                    .map_err(|e| {
                        CodegenError::Internal(format!("{}: bitcast failed: {:?}", label, e))
                    })?;
                f32_val.into()
            }
            ShowCoerce::Char => {
                let int_val = self.coerce_to_int(val)?;
                let i32_val = self
                    .builder()
                    .build_int_truncate(int_val, self.llvm_context().i32_type(), "to_i32")
                    .map_err(|e| {
                        CodegenError::Internal(format!("{}: truncate failed: {:?}", label, e))
                    })?;
                i32_val.into()
            }
            ShowCoerce::Bool => {
                // Bool is an ADT with tag 0=False, 1=True - extract the tag
                let ptr = match val {
                    BasicValueEnum::PointerValue(p) => p,
                    BasicValueEnum::IntValue(i) => {
                        // Already an int (e.g. from case branch), use directly
                        return {
                            let cstr_result = self
                                .builder()
                                .build_call(rts_fn, &[i.into()], label)
                                .map_err(|e| {
                                    CodegenError::Internal(format!(
                                        "{} call failed: {:?}",
                                        label, e
                                    ))
                                })?
                                .try_as_basic_value()
                                .basic()
                                .ok_or_else(|| {
                                    CodegenError::Internal(format!("{}: returned void", label))
                                })?;
                            let char_list =
                                self.cstring_to_char_list(cstr_result.into_pointer_value())?;
                            Ok(Some(char_list.into()))
                        };
                    }
                    _ => {
                        return Err(CodegenError::Internal(format!(
                            "{}: expected pointer or int for Bool",
                            label
                        )))
                    }
                };
                let tag = self.extract_adt_tag(ptr)?;
                tag.into()
            }
            ShowCoerce::Ordering => {
                // Ordering is an ADT with tag 0=LT, 1=EQ, 2=GT - extract the tag
                let ptr = match val {
                    BasicValueEnum::PointerValue(p) => p,
                    BasicValueEnum::IntValue(i) => {
                        return {
                            let cstr_result = self
                                .builder()
                                .build_call(rts_fn, &[i.into()], label)
                                .map_err(|e| {
                                    CodegenError::Internal(format!(
                                        "{} call failed: {:?}",
                                        label, e
                                    ))
                                })?
                                .try_as_basic_value()
                                .basic()
                                .ok_or_else(|| {
                                    CodegenError::Internal(format!("{}: returned void", label))
                                })?;
                            let char_list =
                                self.cstring_to_char_list(cstr_result.into_pointer_value())?;
                            Ok(Some(char_list.into()))
                        };
                    }
                    _ => {
                        return Err(CodegenError::Internal(format!(
                            "{}: expected pointer or int for Ordering",
                            label
                        )))
                    }
                };
                let tag = self.extract_adt_tag(ptr)?;
                tag.into()
            }
            _ => unreachable!("compound coercions handled above"),
        };
        let cstr_result = self
            .builder()
            .build_call(rts_fn, &[call_arg], label)
            .map_err(|e| CodegenError::Internal(format!("{} call failed: {:?}", label, e)))?
            .try_as_basic_value()
            .basic()
            .ok_or_else(|| CodegenError::Internal(format!("{}: returned void", label)))?;
        let char_list = self.cstring_to_char_list(cstr_result.into_pointer_value())?;
        Ok(Some(char_list.into()))
    }

    // ========================================================================
    // Phase 5: Monadic & Higher-Order Operation Handlers
    // ========================================================================

    /// Lower `fmap` / `<$>` with type dispatch.
    /// Dispatches to: user ADT (derived Functor), Maybe, List, or IO (fallback).
    fn lower_builtin_fmap(
        &mut self,
        func_expr: &Expr,
        action_expr: &Expr,
    ) -> CodegenResult<Option<BasicValueEnum<'ctx>>> {
        // Check if action_expr is a user ADT with derived Functor
        if let Some(type_name) = self.infer_adt_type_from_expr(action_expr) {
            if self.derived_functor_fns.contains_key(&type_name) {
                return self.lower_fmap_derived(func_expr, action_expr, &type_name);
            }
        }

        // Check if action_expr looks like a Maybe value
        if self.expr_looks_like_maybe(action_expr) {
            return self.lower_fmap_maybe(func_expr, action_expr);
        }

        // Check if action_expr looks like a List value
        if self.expr_looks_like_list(action_expr) {
            return self.lower_builtin_map(func_expr, action_expr);
        }

        // Fallback: IO fmap (apply function to action result)
        self.lower_fmap_io(func_expr, action_expr)
    }

    /// Lower fmap for a user-defined ADT with derived Functor.
    /// Calls the derived fmap function: fn(env, f, x) -> result
    fn lower_fmap_derived(
        &mut self,
        func_expr: &Expr,
        value_expr: &Expr,
        type_name: &str,
    ) -> CodegenResult<Option<BasicValueEnum<'ctx>>> {
        let fmap_var_id = self
            .derived_functor_fns
            .get(type_name)
            .copied()
            .ok_or_else(|| {
                CodegenError::Internal(format!("fmap: no derived fmap for {}", type_name))
            })?;
        let fmap_fn = self.functions.get(&fmap_var_id).copied().ok_or_else(|| {
            CodegenError::Internal(format!(
                "fmap: derived fmap function not found for {}",
                type_name
            ))
        })?;

        let func_val = self
            .lower_expr(func_expr)?
            .ok_or_else(|| CodegenError::Internal("fmap: no function value".to_string()))?;
        let value_val = self
            .lower_expr(value_expr)?
            .ok_or_else(|| CodegenError::Internal("fmap: no value".to_string()))?;

        let ptr_type = self.type_mapper().ptr_type();
        let null_env = ptr_type.const_null();
        let func_ptr = self.value_to_ptr(func_val)?;
        let value_ptr = self.value_to_ptr(value_val)?;

        // Flat 3-arg call: fn(env, f, x) -> result
        let fn_ptr = fmap_fn.as_global_value().as_pointer_value();
        let fn_type = ptr_type.fn_type(&[ptr_type.into(), ptr_type.into(), ptr_type.into()], false);
        let result = self
            .builder()
            .build_indirect_call(
                fn_type,
                fn_ptr,
                &[null_env.into(), func_ptr.into(), value_ptr.into()],
                "derived_fmap",
            )
            .map_err(|e| CodegenError::Internal(format!("derived fmap call: {:?}", e)))?
            .try_as_basic_value()
            .basic()
            .ok_or_else(|| CodegenError::Internal("derived fmap: returned void".to_string()))?;
        Ok(Some(result))
    }

    /// Lower foldr for a user-defined ADT with derived Foldable.
    /// Calls the derived foldr function: fn(env, f, z, x) -> result
    fn lower_foldr_derived(
        &mut self,
        func_expr: &Expr,
        init_expr: &Expr,
        value_expr: &Expr,
        type_name: &str,
    ) -> CodegenResult<Option<BasicValueEnum<'ctx>>> {
        let foldr_var_id = self
            .derived_foldable_fns
            .get(type_name)
            .copied()
            .ok_or_else(|| {
                CodegenError::Internal(format!("foldr: no derived foldr for {}", type_name))
            })?;
        let foldr_fn = self.functions.get(&foldr_var_id).copied().ok_or_else(|| {
            CodegenError::Internal(format!(
                "foldr: derived foldr function not found for {}",
                type_name
            ))
        })?;

        let func_val = self.lower_expr(func_expr)?.ok_or_else(|| {
            CodegenError::Internal("foldr derived: no function value".to_string())
        })?;
        let init_val = self
            .lower_expr(init_expr)?
            .ok_or_else(|| CodegenError::Internal("foldr derived: no init value".to_string()))?;
        let value_val = self.lower_expr(value_expr)?.ok_or_else(|| {
            CodegenError::Internal("foldr derived: no container value".to_string())
        })?;

        let ptr_type = self.type_mapper().ptr_type();
        let null_env = ptr_type.const_null();
        let func_ptr = self.value_to_ptr(func_val)?;
        let init_ptr = self.value_to_ptr(init_val)?;
        let value_ptr = self.value_to_ptr(value_val)?;

        // Flat 4-arg call: fn(env, f, z, x) -> result
        let fn_ptr = foldr_fn.as_global_value().as_pointer_value();
        let fn_type = ptr_type.fn_type(
            &[
                ptr_type.into(),
                ptr_type.into(),
                ptr_type.into(),
                ptr_type.into(),
            ],
            false,
        );
        let result = self
            .builder()
            .build_indirect_call(
                fn_type,
                fn_ptr,
                &[
                    null_env.into(),
                    func_ptr.into(),
                    init_ptr.into(),
                    value_ptr.into(),
                ],
                "derived_foldr",
            )
            .map_err(|e| CodegenError::Internal(format!("derived foldr call: {:?}", e)))?
            .try_as_basic_value()
            .basic()
            .ok_or_else(|| CodegenError::Internal("derived foldr: returned void".to_string()))?;
        Ok(Some(result))
    }

    /// Lower fmap for Maybe: fmap f Nothing = Nothing, fmap f (Just x) = Just (f x)
    fn lower_fmap_maybe(
        &mut self,
        func_expr: &Expr,
        maybe_expr: &Expr,
    ) -> CodegenResult<Option<BasicValueEnum<'ctx>>> {
        let maybe_val = self
            .lower_expr(maybe_expr)?
            .ok_or_else(|| CodegenError::Internal("fmap Maybe: no value".to_string()))?;
        let func_val = self
            .lower_expr(func_expr)?
            .ok_or_else(|| CodegenError::Internal("fmap Maybe: no function".to_string()))?;

        let ptr_type = self.type_mapper().ptr_type();
        let i64_type = self.type_mapper().i64_type();
        let maybe_ptr = self.value_to_ptr(maybe_val)?;

        // Extract tag: Nothing=0, Just=1
        let tag = self.extract_adt_tag(maybe_ptr)?;
        let is_just = self
            .builder()
            .build_int_compare(
                inkwell::IntPredicate::NE,
                tag,
                i64_type.const_zero(),
                "is_just",
            )
            .map_err(|e| CodegenError::Internal(format!("fmap Maybe cmp: {:?}", e)))?;

        let current_fn = self
            .builder()
            .get_insert_block()
            .and_then(|b| b.get_parent())
            .ok_or_else(|| CodegenError::Internal("fmap Maybe: no current function".to_string()))?;
        let just_bb = self.llvm_ctx.append_basic_block(current_fn, "fmap_just");
        let nothing_bb = self.llvm_ctx.append_basic_block(current_fn, "fmap_nothing");
        let merge_bb = self.llvm_ctx.append_basic_block(current_fn, "fmap_merge");

        self.builder()
            .build_conditional_branch(is_just, just_bb, nothing_bb)
            .map_err(|e| CodegenError::Internal(format!("fmap Maybe branch: {:?}", e)))?;

        // Just branch: extract payload, apply f, wrap in new Just
        self.builder().position_at_end(just_bb);
        let adt_type = self.adt_type(1);
        let payload_gep = self
            .builder()
            .build_struct_gep(adt_type, maybe_ptr, 1, "just_payload_ptr")
            .map_err(|e| CodegenError::Internal(format!("fmap Maybe gep: {:?}", e)))?;
        let payload = self
            .builder()
            .build_load(ptr_type, payload_gep, "just_payload")
            .map_err(|e| CodegenError::Internal(format!("fmap Maybe load: {:?}", e)))?;

        // Apply f to payload
        let func_ptr_val = match func_val {
            BasicValueEnum::PointerValue(p) => p,
            _ => {
                return Err(CodegenError::TypeError(
                    "fmap: function must be closure".to_string(),
                ))
            }
        };
        let fn_ptr = self.extract_closure_fn_ptr(func_ptr_val)?;
        let payload_ptr = self.value_to_ptr(payload)?;
        let fn_type = ptr_type.fn_type(&[ptr_type.into(), ptr_type.into()], false);
        let mapped = self
            .builder()
            .build_indirect_call(
                fn_type,
                fn_ptr,
                &[func_ptr_val.into(), payload_ptr.into()],
                "fmap_applied",
            )
            .map_err(|e| CodegenError::Internal(format!("fmap Maybe apply: {:?}", e)))?
            .try_as_basic_value()
            .basic()
            .ok_or_else(|| CodegenError::Internal("fmap Maybe: apply returned void".to_string()))?;

        // Allocate new Just(mapped): tag=1, arity=1
        let alloc_fn = self
            .functions
            .get(&VarId::new(1000005))
            .copied()
            .ok_or_else(|| CodegenError::Internal("fmap: bhc_alloc not found".to_string()))?;
        let size_val = i64_type.const_int(16, false); // 8 tag + 8 payload
        let raw_just = self
            .builder()
            .build_call(alloc_fn, &[size_val.into()], "just_alloc")
            .map_err(|e| CodegenError::Internal(format!("fmap Maybe alloc: {:?}", e)))?
            .try_as_basic_value()
            .basic()
            .ok_or_else(|| CodegenError::Internal("fmap: alloc returned void".to_string()))?;
        let just_ptr = raw_just.into_pointer_value();

        // Store tag = 1
        let tag_gep = self
            .builder()
            .build_struct_gep(adt_type, just_ptr, 0, "just_tag_ptr")
            .map_err(|e| CodegenError::Internal(format!("fmap Maybe tag gep: {:?}", e)))?;
        self.builder()
            .build_store(tag_gep, i64_type.const_int(1, false))
            .map_err(|e| CodegenError::Internal(format!("fmap Maybe tag store: {:?}", e)))?;

        // Store payload
        let payload_out_gep = self
            .builder()
            .build_struct_gep(adt_type, just_ptr, 1, "just_payload_out_ptr")
            .map_err(|e| CodegenError::Internal(format!("fmap Maybe payload gep: {:?}", e)))?;
        let mapped_ptr = self.value_to_ptr(mapped)?;
        self.builder()
            .build_store(payload_out_gep, mapped_ptr)
            .map_err(|e| CodegenError::Internal(format!("fmap Maybe payload store: {:?}", e)))?;

        let just_result: BasicValueEnum<'ctx> = just_ptr.into();
        self.builder()
            .build_unconditional_branch(merge_bb)
            .map_err(|e| CodegenError::Internal(format!("fmap Maybe just br: {:?}", e)))?;
        let just_end_bb = self.builder().get_insert_block().unwrap();

        // Nothing branch: return Nothing as-is
        self.builder().position_at_end(nothing_bb);
        let nothing_result: BasicValueEnum<'ctx> = maybe_ptr.into();
        self.builder()
            .build_unconditional_branch(merge_bb)
            .map_err(|e| CodegenError::Internal(format!("fmap Maybe nothing br: {:?}", e)))?;
        let nothing_end_bb = self.builder().get_insert_block().unwrap();

        // Merge
        self.builder().position_at_end(merge_bb);
        let phi = self
            .builder()
            .build_phi(ptr_type, "fmap_maybe_result")
            .map_err(|e| CodegenError::Internal(format!("fmap Maybe phi: {:?}", e)))?;
        phi.add_incoming(&[
            (&just_result, just_end_bb),
            (&nothing_result, nothing_end_bb),
        ]);

        Ok(Some(phi.as_basic_value()))
    }

    /// Lower fmap for IO: apply function to action result (original behavior).
    fn lower_fmap_io(
        &mut self,
        func_expr: &Expr,
        action_expr: &Expr,
    ) -> CodegenResult<Option<BasicValueEnum<'ctx>>> {
        let action_result = self
            .lower_expr(action_expr)?
            .ok_or_else(|| CodegenError::Internal("fmap: no value".to_string()))?;
        let func_val = self
            .lower_expr(func_expr)?
            .ok_or_else(|| CodegenError::Internal("fmap: no function".to_string()))?;
        let func_ptr = match func_val {
            BasicValueEnum::PointerValue(p) => p,
            _ => {
                return Err(CodegenError::TypeError(
                    "fmap: function must be closure".to_string(),
                ))
            }
        };
        let ptr_type = self.type_mapper().ptr_type();
        let action_ptr = self.value_to_ptr(action_result)?;
        let fn_ptr = self.extract_closure_fn_ptr(func_ptr)?;
        let fn_type = ptr_type.fn_type(&[ptr_type.into(), ptr_type.into()], false);
        let result = self
            .builder()
            .build_indirect_call(
                fn_type,
                fn_ptr,
                &[func_ptr.into(), action_ptr.into()],
                "fmap_result",
            )
            .map_err(|e| CodegenError::Internal(format!("fmap call failed: {:?}", e)))?
            .try_as_basic_value()
            .basic()
            .ok_or_else(|| CodegenError::Internal("fmap: returned void".to_string()))?;
        Ok(Some(result))
    }

    /// Check if an expression looks like a Maybe value based on its structure.
    fn expr_looks_like_maybe(&self, expr: &Expr) -> bool {
        match expr {
            Expr::App(f, _, _) => self.expr_looks_like_maybe(f),
            Expr::TyApp(e, _, _) => self.expr_looks_like_maybe(e),
            Expr::Let(_, body, _) => self.expr_looks_like_maybe(body),
            Expr::Var(var, _) => {
                let name = var.name.as_str();
                matches!(
                    name,
                    "Just"
                        | "Nothing"
                        | "readMaybe"
                        | "lookupEnv"
                        | "find"
                        | "lookup"
                        | "elemIndex"
                        | "findIndex"
                        | "listToMaybe"
                        | "Data.Map.lookup"
                        | "stripPrefix"
                )
            }
            _ => false,
        }
    }

    /// Lower `<*>`.
    fn lower_builtin_ap(
        &mut self,
        func_expr: &Expr,
        val_expr: &Expr,
    ) -> CodegenResult<Option<BasicValueEnum<'ctx>>> {
        let func_result = self
            .lower_expr(func_expr)?
            .ok_or_else(|| CodegenError::Internal("<*>: no func".to_string()))?;
        let val_result = self
            .lower_expr(val_expr)?
            .ok_or_else(|| CodegenError::Internal("<*>: no val".to_string()))?;
        let func_ptr = match func_result {
            BasicValueEnum::PointerValue(p) => p,
            _ => {
                return Err(CodegenError::TypeError(
                    "<*>: function must be closure".to_string(),
                ))
            }
        };
        let ptr_type = self.type_mapper().ptr_type();
        let val_ptr = self.value_to_ptr(val_result)?;
        let fn_ptr = self.extract_closure_fn_ptr(func_ptr)?;
        let fn_type = ptr_type.fn_type(&[ptr_type.into(), ptr_type.into()], false);
        let result = self
            .builder()
            .build_indirect_call(
                fn_type,
                fn_ptr,
                &[func_ptr.into(), val_ptr.into()],
                "ap_result",
            )
            .map_err(|e| CodegenError::Internal(format!("<*> call failed: {:?}", e)))?
            .try_as_basic_value()
            .basic()
            .ok_or_else(|| CodegenError::Internal("<*>: returned void".to_string()))?;
        Ok(Some(result))
    }

    /// Lower `join`.
    fn lower_builtin_join(&mut self, expr: &Expr) -> CodegenResult<Option<BasicValueEnum<'ctx>>> {
        self.lower_expr(expr)
    }

    /// Lower `=<<` (reverse bind).
    fn lower_builtin_bind_flipped(
        &mut self,
        func_expr: &Expr,
        action_expr: &Expr,
    ) -> CodegenResult<Option<BasicValueEnum<'ctx>>> {
        self.lower_builtin_bind(action_expr, func_expr)
    }

    /// Lower `when`.
    fn lower_builtin_when(
        &mut self,
        cond_expr: &Expr,
        action_expr: &Expr,
    ) -> CodegenResult<Option<BasicValueEnum<'ctx>>> {
        let cond_val = self
            .lower_expr(cond_expr)?
            .ok_or_else(|| CodegenError::Internal("when: no cond".to_string()))?;
        // Bool is an ADT with tag 0=False, 1=True — extract the tag
        let bool_int = match cond_val {
            BasicValueEnum::PointerValue(p) => self.extract_adt_tag(p)?,
            BasicValueEnum::IntValue(i) => i,
            _ => return Err(CodegenError::TypeError("when expects Bool".to_string())),
        };
        let is_true = self
            .builder()
            .build_int_compare(
                inkwell::IntPredicate::NE,
                bool_int,
                self.type_mapper().i64_type().const_zero(),
                "when_cond",
            )
            .map_err(|e| CodegenError::Internal(format!("when: cmp failed: {:?}", e)))?;
        let current_fn = self
            .builder()
            .get_insert_block()
            .and_then(|b| b.get_parent())
            .ok_or_else(|| CodegenError::Internal("no current function".to_string()))?;
        let then_block = self
            .llvm_context()
            .append_basic_block(current_fn, "when_then");
        let merge_block = self
            .llvm_context()
            .append_basic_block(current_fn, "when_merge");
        self.builder()
            .build_conditional_branch(is_true, then_block, merge_block)
            .map_err(|e| CodegenError::Internal(format!("when: branch failed: {:?}", e)))?;
        self.builder().position_at_end(then_block);
        let _ = self.lower_expr(action_expr)?;
        self.builder()
            .build_unconditional_branch(merge_block)
            .map_err(|e| CodegenError::Internal(format!("when: branch failed: {:?}", e)))?;
        self.builder().position_at_end(merge_block);
        Ok(Some(self.type_mapper().ptr_type().const_null().into()))
    }

    /// Lower `unless`.
    fn lower_builtin_unless(
        &mut self,
        cond_expr: &Expr,
        action_expr: &Expr,
    ) -> CodegenResult<Option<BasicValueEnum<'ctx>>> {
        let cond_val = self
            .lower_expr(cond_expr)?
            .ok_or_else(|| CodegenError::Internal("unless: no cond".to_string()))?;
        // Bool is an ADT with tag 0=False, 1=True — extract the tag
        let bool_int = match cond_val {
            BasicValueEnum::PointerValue(p) => self.extract_adt_tag(p)?,
            BasicValueEnum::IntValue(i) => i,
            _ => return Err(CodegenError::TypeError("unless expects Bool".to_string())),
        };
        let is_false = self
            .builder()
            .build_int_compare(
                inkwell::IntPredicate::EQ,
                bool_int,
                self.type_mapper().i64_type().const_zero(),
                "unless_cond",
            )
            .map_err(|e| CodegenError::Internal(format!("unless: cmp failed: {:?}", e)))?;
        let current_fn = self
            .builder()
            .get_insert_block()
            .and_then(|b| b.get_parent())
            .ok_or_else(|| CodegenError::Internal("no current function".to_string()))?;
        let then_block = self
            .llvm_context()
            .append_basic_block(current_fn, "unless_then");
        let merge_block = self
            .llvm_context()
            .append_basic_block(current_fn, "unless_merge");
        self.builder()
            .build_conditional_branch(is_false, then_block, merge_block)
            .map_err(|e| CodegenError::Internal(format!("unless: branch failed: {:?}", e)))?;
        self.builder().position_at_end(then_block);
        let _ = self.lower_expr(action_expr)?;
        self.builder()
            .build_unconditional_branch(merge_block)
            .map_err(|e| CodegenError::Internal(format!("unless: branch failed: {:?}", e)))?;
        self.builder().position_at_end(merge_block);
        Ok(Some(self.type_mapper().ptr_type().const_null().into()))
    }

    /// Lower `void`.
    fn lower_builtin_void(
        &mut self,
        action_expr: &Expr,
    ) -> CodegenResult<Option<BasicValueEnum<'ctx>>> {
        let _ = self.lower_expr(action_expr)?;
        Ok(Some(self.type_mapper().ptr_type().const_null().into()))
    }

    /// Lower `mapM`.
    fn lower_builtin_mapm(
        &mut self,
        func_expr: &Expr,
        list_expr: &Expr,
    ) -> CodegenResult<Option<BasicValueEnum<'ctx>>> {
        self.lower_builtin_map(func_expr, list_expr)
    }

    /// Lower `mapM_`.
    fn lower_builtin_mapm_(
        &mut self,
        func_expr: &Expr,
        list_expr: &Expr,
    ) -> CodegenResult<Option<BasicValueEnum<'ctx>>> {
        let _ = self.lower_builtin_map(func_expr, list_expr)?;
        Ok(Some(self.type_mapper().ptr_type().const_null().into()))
    }

    /// Lower `sequence`.
    fn lower_builtin_sequence(
        &mut self,
        list_expr: &Expr,
    ) -> CodegenResult<Option<BasicValueEnum<'ctx>>> {
        self.lower_expr(list_expr)
    }

    /// Lower `sequence_`.
    fn lower_builtin_sequence_(
        &mut self,
        list_expr: &Expr,
    ) -> CodegenResult<Option<BasicValueEnum<'ctx>>> {
        let _ = self.lower_expr(list_expr)?;
        Ok(Some(self.type_mapper().ptr_type().const_null().into()))
    }

    /// Lower `traverse`. Dispatches to derived Traversable for user ADTs, or falls back to mapM for lists.
    fn lower_builtin_traverse(
        &mut self,
        func_expr: &Expr,
        container_expr: &Expr,
    ) -> CodegenResult<Option<BasicValueEnum<'ctx>>> {
        // Check if container is a user ADT with derived Traversable
        if let Some(type_name) = self.infer_adt_type_from_expr(container_expr) {
            if self.derived_traversable_fns.contains_key(&type_name) {
                return self.lower_traverse_derived(func_expr, container_expr, &type_name);
            }
        }
        // Fall back to list traverse (= mapM = map for BHC's eager IO)
        self.lower_builtin_mapm(func_expr, container_expr)
    }

    /// Lower traverse for a user-defined ADT with derived Traversable.
    /// Calls the derived traverse function: fn(env, f, x) -> result
    fn lower_traverse_derived(
        &mut self,
        func_expr: &Expr,
        container_expr: &Expr,
        type_name: &str,
    ) -> CodegenResult<Option<BasicValueEnum<'ctx>>> {
        let traverse_var_id = self
            .derived_traversable_fns
            .get(type_name)
            .copied()
            .ok_or_else(|| {
                CodegenError::Internal(format!("traverse: no derived traverse for {}", type_name))
            })?;
        let traverse_fn = self
            .functions
            .get(&traverse_var_id)
            .copied()
            .ok_or_else(|| {
                CodegenError::Internal(format!(
                    "traverse: derived traverse function not found for {}",
                    type_name
                ))
            })?;

        let func_val = self
            .lower_expr(func_expr)?
            .ok_or_else(|| CodegenError::Internal("traverse: no function value".to_string()))?;
        let container_val = self
            .lower_expr(container_expr)?
            .ok_or_else(|| CodegenError::Internal("traverse: no container value".to_string()))?;

        let ptr_type = self.type_mapper().ptr_type();
        let null_env = ptr_type.const_null();
        let func_ptr = self.value_to_ptr(func_val)?;
        let container_ptr = self.value_to_ptr(container_val)?;

        // Flat 3-arg call: fn(env, f, x) -> result
        let fn_ptr = traverse_fn.as_global_value().as_pointer_value();
        let fn_type = ptr_type.fn_type(&[ptr_type.into(), ptr_type.into(), ptr_type.into()], false);
        let result = self
            .builder()
            .build_indirect_call(
                fn_type,
                fn_ptr,
                &[null_env.into(), func_ptr.into(), container_ptr.into()],
                "derived_traverse",
            )
            .map_err(|e| CodegenError::Internal(format!("derived traverse call: {:?}", e)))?
            .try_as_basic_value()
            .basic()
            .ok_or_else(|| CodegenError::Internal("derived traverse: returned void".to_string()))?;
        Ok(Some(result))
    }

    /// Lower `traverse_`. Calls traverse and discards the result.
    fn lower_builtin_traverse_(
        &mut self,
        func_expr: &Expr,
        container_expr: &Expr,
    ) -> CodegenResult<Option<BasicValueEnum<'ctx>>> {
        let _ = self.lower_builtin_traverse(func_expr, container_expr)?;
        Ok(Some(self.type_mapper().ptr_type().const_null().into()))
    }

    /// Lower `sequenceA`. Delegates to sequence for lists, or traverse id for user ADTs.
    fn lower_builtin_sequence_a(
        &mut self,
        container_expr: &Expr,
    ) -> CodegenResult<Option<BasicValueEnum<'ctx>>> {
        // For user ADTs, we'd need to traverse with identity, but sequenceA on user ADTs
        // is uncommon. Fall back to evaluating the expression (same as sequence for lists).
        self.lower_expr(container_expr)
    }

    /// Lower `sequenceA_`. Evaluates and discards.
    fn lower_builtin_sequence_a_(
        &mut self,
        container_expr: &Expr,
    ) -> CodegenResult<Option<BasicValueEnum<'ctx>>> {
        let _ = self.lower_expr(container_expr)?;
        Ok(Some(self.type_mapper().ptr_type().const_null().into()))
    }

    /// Lower `forever`.
    fn lower_builtin_forever(
        &mut self,
        action_expr: &Expr,
    ) -> CodegenResult<Option<BasicValueEnum<'ctx>>> {
        let current_fn = self
            .builder()
            .get_insert_block()
            .and_then(|b| b.get_parent())
            .ok_or_else(|| CodegenError::Internal("no current function".to_string()))?;
        let loop_block = self
            .llvm_context()
            .append_basic_block(current_fn, "forever_loop");
        self.builder()
            .build_unconditional_branch(loop_block)
            .map_err(|e| CodegenError::Internal(format!("forever: branch failed: {:?}", e)))?;
        self.builder().position_at_end(loop_block);
        let _ = self.lower_expr(action_expr)?;
        self.builder()
            .build_unconditional_branch(loop_block)
            .map_err(|e| CodegenError::Internal(format!("forever: branch failed: {:?}", e)))?;
        let after_block = self
            .llvm_context()
            .append_basic_block(current_fn, "forever_after");
        self.builder().position_at_end(after_block);
        Ok(Some(self.type_mapper().ptr_type().const_null().into()))
    }

    // E.18: Monadic combinators

    /// Lower `filterM pred list` — monadic filter.
    /// Walk list, call pred on each element (returns IO Bool), keep elements where True.
    fn lower_builtin_filter_m(
        &mut self,
        pred_expr: &Expr,
        list_expr: &Expr,
    ) -> CodegenResult<Option<BasicValueEnum<'ctx>>> {
        let pred_val = self
            .lower_expr(pred_expr)?
            .ok_or_else(|| CodegenError::Internal("filterM: no predicate".to_string()))?;
        let pred_ptr = match pred_val {
            BasicValueEnum::PointerValue(p) => p,
            _ => {
                return Err(CodegenError::TypeError(
                    "filterM: predicate must be closure".to_string(),
                ))
            }
        };
        let list_val = self
            .lower_expr(list_expr)?
            .ok_or_else(|| CodegenError::Internal("filterM: no list".to_string()))?;
        let list_ptr = match list_val {
            BasicValueEnum::PointerValue(p) => p,
            _ => return Err(CodegenError::TypeError("filterM: expects list".to_string())),
        };

        let ptr_type = self.type_mapper().ptr_type();
        let i64_type = self.type_mapper().i64_type();
        let current_fn = self
            .builder()
            .get_insert_block()
            .and_then(|b| b.get_parent())
            .ok_or_else(|| CodegenError::Internal("no current function".to_string()))?;
        let entry_block = self
            .builder()
            .get_insert_block()
            .ok_or_else(|| CodegenError::Internal("no current block".to_string()))?;

        let loop_header = self
            .llvm_context()
            .append_basic_block(current_fn, "filterm_header");
        let loop_body = self
            .llvm_context()
            .append_basic_block(current_fn, "filterm_body");
        let loop_keep = self
            .llvm_context()
            .append_basic_block(current_fn, "filterm_keep");
        let loop_skip = self
            .llvm_context()
            .append_basic_block(current_fn, "filterm_skip");
        let loop_exit = self
            .llvm_context()
            .append_basic_block(current_fn, "filterm_exit");

        let nil = self.build_nil()?;
        self.builder()
            .build_unconditional_branch(loop_header)
            .map_err(|e| CodegenError::Internal(format!("filterM: branch failed: {:?}", e)))?;

        // Loop header
        self.builder().position_at_end(loop_header);
        let result_phi = self
            .builder()
            .build_phi(ptr_type, "filterm_result")
            .map_err(|e| CodegenError::Internal(format!("filterM: phi failed: {:?}", e)))?;
        let list_phi = self
            .builder()
            .build_phi(ptr_type, "filterm_list")
            .map_err(|e| CodegenError::Internal(format!("filterM: phi failed: {:?}", e)))?;

        // Check if list is empty (tag == 0 means Nil)
        let tag = self.extract_adt_tag(list_phi.as_basic_value().into_pointer_value())?;
        let is_empty = self
            .builder()
            .build_int_compare(
                inkwell::IntPredicate::EQ,
                tag,
                i64_type.const_zero(),
                "is_empty",
            )
            .map_err(|e| CodegenError::Internal(format!("filterM: cmp failed: {:?}", e)))?;
        self.builder()
            .build_conditional_branch(is_empty, loop_exit, loop_body)
            .map_err(|e| CodegenError::Internal(format!("filterM: branch failed: {:?}", e)))?;

        // Loop body: extract head/tail, call predicate
        self.builder().position_at_end(loop_body);
        let head_ptr =
            self.extract_adt_field(list_phi.as_basic_value().into_pointer_value(), 2, 0)?;
        let tail_ptr =
            self.extract_adt_field(list_phi.as_basic_value().into_pointer_value(), 2, 1)?;

        // Call predicate: pred_ptr(pred_ptr, head) -> IO Bool (in strict codegen = Bool)
        let pred_fn_ptr = self.extract_closure_fn_ptr(pred_ptr)?;
        let fn_type = ptr_type.fn_type(&[ptr_type.into(), ptr_type.into()], false);
        let pred_result = self
            .builder()
            .build_indirect_call(
                fn_type,
                pred_fn_ptr,
                &[pred_ptr.into(), head_ptr.into()],
                "pred_result",
            )
            .map_err(|e| CodegenError::Internal(format!("filterM: call failed: {:?}", e)))?
            .try_as_basic_value()
            .basic()
            .ok_or_else(|| {
                CodegenError::Internal("filterM: predicate returned void".to_string())
            })?;

        // Bool is an ADT with tag 0=False, 1=True — use extract_adt_tag (not ptr_to_int!)
        let bool_int = match pred_result {
            BasicValueEnum::PointerValue(p) => self.extract_adt_tag(p)?,
            BasicValueEnum::IntValue(i) => i,
            _ => {
                return Err(CodegenError::TypeError(
                    "filterM: predicate must return Bool".to_string(),
                ))
            }
        };
        let is_true = self
            .builder()
            .build_int_compare(
                inkwell::IntPredicate::NE,
                bool_int,
                i64_type.const_zero(),
                "is_true",
            )
            .map_err(|e| CodegenError::Internal(format!("filterM: cmp failed: {:?}", e)))?;
        self.builder()
            .build_conditional_branch(is_true, loop_keep, loop_skip)
            .map_err(|e| CodegenError::Internal(format!("filterM: branch failed: {:?}", e)))?;

        // Keep: cons head onto result
        self.builder().position_at_end(loop_keep);
        let new_cons = self.build_cons(head_ptr.into(), result_phi.as_basic_value())?;
        self.builder()
            .build_unconditional_branch(loop_header)
            .map_err(|e| CodegenError::Internal(format!("filterM: branch failed: {:?}", e)))?;

        // Skip: continue without adding
        self.builder().position_at_end(loop_skip);
        self.builder()
            .build_unconditional_branch(loop_header)
            .map_err(|e| CodegenError::Internal(format!("filterM: branch failed: {:?}", e)))?;

        // Phi incoming
        result_phi.add_incoming(&[
            (&nil, entry_block),
            (&new_cons, loop_keep),
            (&result_phi.as_basic_value(), loop_skip),
        ]);
        list_phi.add_incoming(&[
            (&list_ptr, entry_block),
            (&tail_ptr, loop_keep),
            (&tail_ptr, loop_skip),
        ]);

        // Exit: reverse the accumulated result
        self.builder().position_at_end(loop_exit);
        self.build_inline_reverse(result_phi.as_basic_value().into_pointer_value(), current_fn)
    }

    /// Lower `foldM f acc list` — monadic left fold.
    fn lower_builtin_fold_m(
        &mut self,
        func_expr: &Expr,
        init_expr: &Expr,
        list_expr: &Expr,
    ) -> CodegenResult<Option<BasicValueEnum<'ctx>>> {
        let func_val = self
            .lower_expr(func_expr)?
            .ok_or_else(|| CodegenError::Internal("foldM: no function".to_string()))?;
        let func_ptr = match func_val {
            BasicValueEnum::PointerValue(p) => p,
            _ => {
                return Err(CodegenError::TypeError(
                    "foldM: function must be closure".to_string(),
                ))
            }
        };
        let init_val = self
            .lower_expr(init_expr)?
            .ok_or_else(|| CodegenError::Internal("foldM: no init".to_string()))?;
        let init_ptr = self.value_to_ptr(init_val)?;
        let list_val = self
            .lower_expr(list_expr)?
            .ok_or_else(|| CodegenError::Internal("foldM: no list".to_string()))?;
        let list_ptr = match list_val {
            BasicValueEnum::PointerValue(p) => p,
            _ => return Err(CodegenError::TypeError("foldM: expects list".to_string())),
        };

        let ptr_type = self.type_mapper().ptr_type();
        let i64_type = self.type_mapper().i64_type();
        let current_fn = self
            .builder()
            .get_insert_block()
            .and_then(|b| b.get_parent())
            .ok_or_else(|| CodegenError::Internal("no current function".to_string()))?;
        let entry_block = self
            .builder()
            .get_insert_block()
            .ok_or_else(|| CodegenError::Internal("no current block".to_string()))?;

        let loop_header = self
            .llvm_context()
            .append_basic_block(current_fn, "foldm_header");
        let loop_body = self
            .llvm_context()
            .append_basic_block(current_fn, "foldm_body");
        let loop_exit = self
            .llvm_context()
            .append_basic_block(current_fn, "foldm_exit");

        self.builder()
            .build_unconditional_branch(loop_header)
            .map_err(|e| CodegenError::Internal(format!("foldM: branch failed: {:?}", e)))?;

        // Loop header
        self.builder().position_at_end(loop_header);
        let acc_phi = self
            .builder()
            .build_phi(ptr_type, "foldm_acc")
            .map_err(|e| CodegenError::Internal(format!("foldM: phi failed: {:?}", e)))?;
        let list_phi = self
            .builder()
            .build_phi(ptr_type, "foldm_list")
            .map_err(|e| CodegenError::Internal(format!("foldM: phi failed: {:?}", e)))?;

        let tag = self.extract_adt_tag(list_phi.as_basic_value().into_pointer_value())?;
        let is_empty = self
            .builder()
            .build_int_compare(
                inkwell::IntPredicate::EQ,
                tag,
                i64_type.const_zero(),
                "is_empty",
            )
            .map_err(|e| CodegenError::Internal(format!("foldM: cmp failed: {:?}", e)))?;
        self.builder()
            .build_conditional_branch(is_empty, loop_exit, loop_body)
            .map_err(|e| CodegenError::Internal(format!("foldM: branch failed: {:?}", e)))?;

        // Loop body: call f(acc, head)
        self.builder().position_at_end(loop_body);
        let head_ptr =
            self.extract_adt_field(list_phi.as_basic_value().into_pointer_value(), 2, 0)?;
        let tail_ptr =
            self.extract_adt_field(list_phi.as_basic_value().into_pointer_value(), 2, 1)?;

        // Flat 2-arg closure call: fn_ptr(closure_ptr, acc, head) -> result
        let closure_fn_ptr = self.extract_closure_fn_ptr(func_ptr)?;
        let fn_type = ptr_type.fn_type(&[ptr_type.into(), ptr_type.into(), ptr_type.into()], false);
        let new_acc = self
            .builder()
            .build_indirect_call(
                fn_type,
                closure_fn_ptr,
                &[
                    func_ptr.into(),
                    acc_phi.as_basic_value().into_pointer_value().into(),
                    head_ptr.into(),
                ],
                "new_acc",
            )
            .map_err(|e| CodegenError::Internal(format!("foldM: call failed: {:?}", e)))?
            .try_as_basic_value()
            .basic()
            .ok_or_else(|| CodegenError::Internal("foldM: function returned void".to_string()))?;
        let new_acc_ptr = self.value_to_ptr(new_acc)?;

        self.builder()
            .build_unconditional_branch(loop_header)
            .map_err(|e| CodegenError::Internal(format!("foldM: branch failed: {:?}", e)))?;

        // Phi incoming
        acc_phi.add_incoming(&[(&init_ptr, entry_block), (&new_acc_ptr, loop_body)]);
        list_phi.add_incoming(&[(&list_ptr, entry_block), (&tail_ptr, loop_body)]);

        // Exit: return final accumulator
        self.builder().position_at_end(loop_exit);
        Ok(Some(acc_phi.as_basic_value()))
    }

    /// Lower `foldM_ f acc list` — monadic left fold, discards result.
    fn lower_builtin_fold_m_(
        &mut self,
        func_expr: &Expr,
        init_expr: &Expr,
        list_expr: &Expr,
    ) -> CodegenResult<Option<BasicValueEnum<'ctx>>> {
        let _ = self.lower_builtin_fold_m(func_expr, init_expr, list_expr)?;
        Ok(Some(self.type_mapper().ptr_type().const_null().into()))
    }

    /// Lower `replicateM n action` — run action n times, collect results.
    fn lower_builtin_replicate_m(
        &mut self,
        count_expr: &Expr,
        action_expr: &Expr,
    ) -> CodegenResult<Option<BasicValueEnum<'ctx>>> {
        let count_val = self
            .lower_expr(count_expr)?
            .ok_or_else(|| CodegenError::Internal("replicateM: no count".to_string()))?;
        // Coerce count to i64
        let count_i64 = match count_val {
            BasicValueEnum::IntValue(i) => i,
            BasicValueEnum::PointerValue(p) => self
                .builder()
                .build_ptr_to_int(p, self.type_mapper().i64_type(), "count_int")
                .map_err(|e| {
                    CodegenError::Internal(format!("replicateM: ptr_to_int failed: {:?}", e))
                })?,
            _ => {
                return Err(CodegenError::TypeError(
                    "replicateM: count must be Int".to_string(),
                ))
            }
        };

        let ptr_type = self.type_mapper().ptr_type();
        let i64_type = self.type_mapper().i64_type();
        let current_fn = self
            .builder()
            .get_insert_block()
            .and_then(|b| b.get_parent())
            .ok_or_else(|| CodegenError::Internal("no current function".to_string()))?;
        let entry_block = self
            .builder()
            .get_insert_block()
            .ok_or_else(|| CodegenError::Internal("no current block".to_string()))?;

        let loop_header = self
            .llvm_context()
            .append_basic_block(current_fn, "repm_header");
        let loop_body = self
            .llvm_context()
            .append_basic_block(current_fn, "repm_body");
        let loop_exit = self
            .llvm_context()
            .append_basic_block(current_fn, "repm_exit");

        let nil = self.build_nil()?;
        self.builder()
            .build_unconditional_branch(loop_header)
            .map_err(|e| CodegenError::Internal(format!("replicateM: branch failed: {:?}", e)))?;

        // Loop header
        self.builder().position_at_end(loop_header);
        let counter_phi = self
            .builder()
            .build_phi(i64_type, "repm_counter")
            .map_err(|e| CodegenError::Internal(format!("replicateM: phi failed: {:?}", e)))?;
        let result_phi = self
            .builder()
            .build_phi(ptr_type, "repm_result")
            .map_err(|e| CodegenError::Internal(format!("replicateM: phi failed: {:?}", e)))?;

        let counter_done = self
            .builder()
            .build_int_compare(
                inkwell::IntPredicate::SLE,
                counter_phi.as_basic_value().into_int_value(),
                i64_type.const_zero(),
                "counter_done",
            )
            .map_err(|e| CodegenError::Internal(format!("replicateM: cmp failed: {:?}", e)))?;
        self.builder()
            .build_conditional_branch(counter_done, loop_exit, loop_body)
            .map_err(|e| CodegenError::Internal(format!("replicateM: branch failed: {:?}", e)))?;

        // Loop body: re-lower the action expression each iteration
        self.builder().position_at_end(loop_body);
        let action_result = self.lower_expr(action_expr)?.ok_or_else(|| {
            CodegenError::Internal("replicateM: action returned nothing".to_string())
        })?;
        let new_cons = self.build_cons(action_result, result_phi.as_basic_value())?;
        let decremented = self
            .builder()
            .build_int_sub(
                counter_phi.as_basic_value().into_int_value(),
                i64_type.const_int(1, false),
                "dec",
            )
            .map_err(|e| CodegenError::Internal(format!("replicateM: sub failed: {:?}", e)))?;
        // Get the actual current block — lower_expr may have created new blocks
        let body_end_block = self
            .builder()
            .get_insert_block()
            .ok_or_else(|| CodegenError::Internal("replicateM: no block".to_string()))?;

        self.builder()
            .build_unconditional_branch(loop_header)
            .map_err(|e| CodegenError::Internal(format!("replicateM: branch failed: {:?}", e)))?;

        // Phi incoming — use body_end_block (may differ from loop_body if lower_expr created blocks)
        counter_phi.add_incoming(&[(&count_i64, entry_block), (&decremented, body_end_block)]);
        result_phi.add_incoming(&[(&nil, entry_block), (&new_cons, body_end_block)]);

        // Exit: reverse accumulated result
        self.builder().position_at_end(loop_exit);
        self.build_inline_reverse(result_phi.as_basic_value().into_pointer_value(), current_fn)
    }

    /// Lower `replicateM_ n action` — run action n times, discard results.
    fn lower_builtin_replicate_m_(
        &mut self,
        count_expr: &Expr,
        action_expr: &Expr,
    ) -> CodegenResult<Option<BasicValueEnum<'ctx>>> {
        let count_val = self
            .lower_expr(count_expr)?
            .ok_or_else(|| CodegenError::Internal("replicateM_: no count".to_string()))?;
        let count_i64 = match count_val {
            BasicValueEnum::IntValue(i) => i,
            BasicValueEnum::PointerValue(p) => self
                .builder()
                .build_ptr_to_int(p, self.type_mapper().i64_type(), "count_int")
                .map_err(|e| {
                    CodegenError::Internal(format!("replicateM_: ptr_to_int failed: {:?}", e))
                })?,
            _ => {
                return Err(CodegenError::TypeError(
                    "replicateM_: count must be Int".to_string(),
                ))
            }
        };

        let i64_type = self.type_mapper().i64_type();
        let current_fn = self
            .builder()
            .get_insert_block()
            .and_then(|b| b.get_parent())
            .ok_or_else(|| CodegenError::Internal("no current function".to_string()))?;
        let entry_block = self
            .builder()
            .get_insert_block()
            .ok_or_else(|| CodegenError::Internal("no current block".to_string()))?;

        let loop_header = self
            .llvm_context()
            .append_basic_block(current_fn, "repm__header");
        let loop_body = self
            .llvm_context()
            .append_basic_block(current_fn, "repm__body");
        let loop_exit = self
            .llvm_context()
            .append_basic_block(current_fn, "repm__exit");

        self.builder()
            .build_unconditional_branch(loop_header)
            .map_err(|e| CodegenError::Internal(format!("replicateM_: branch failed: {:?}", e)))?;

        // Loop header
        self.builder().position_at_end(loop_header);
        let counter_phi = self
            .builder()
            .build_phi(i64_type, "repm__counter")
            .map_err(|e| CodegenError::Internal(format!("replicateM_: phi failed: {:?}", e)))?;

        let counter_done = self
            .builder()
            .build_int_compare(
                inkwell::IntPredicate::SLE,
                counter_phi.as_basic_value().into_int_value(),
                i64_type.const_zero(),
                "counter_done",
            )
            .map_err(|e| CodegenError::Internal(format!("replicateM_: cmp failed: {:?}", e)))?;
        self.builder()
            .build_conditional_branch(counter_done, loop_exit, loop_body)
            .map_err(|e| CodegenError::Internal(format!("replicateM_: branch failed: {:?}", e)))?;

        // Loop body: run action, discard result
        self.builder().position_at_end(loop_body);
        let _ = self.lower_expr(action_expr)?;
        // lower_expr may have created new blocks (e.g., for putStrLn), get the current block
        let body_end_block = self.builder().get_insert_block().ok_or_else(|| {
            CodegenError::Internal("replicateM_: no block after action".to_string())
        })?;
        let decremented = self
            .builder()
            .build_int_sub(
                counter_phi.as_basic_value().into_int_value(),
                i64_type.const_int(1, false),
                "dec",
            )
            .map_err(|e| CodegenError::Internal(format!("replicateM_: sub failed: {:?}", e)))?;

        self.builder()
            .build_unconditional_branch(loop_header)
            .map_err(|e| CodegenError::Internal(format!("replicateM_: branch failed: {:?}", e)))?;

        // Phi incoming — use body_end_block, not loop_body
        counter_phi.add_incoming(&[(&count_i64, entry_block), (&decremented, body_end_block)]);

        // Exit: return unit
        self.builder().position_at_end(loop_exit);
        Ok(Some(self.type_mapper().ptr_type().const_null().into()))
    }

    /// Lower `zipWithM f xs ys` — monadic zipWith, collecting results.
    fn lower_builtin_zipwith_m(
        &mut self,
        func_expr: &Expr,
        list1_expr: &Expr,
        list2_expr: &Expr,
    ) -> CodegenResult<Option<BasicValueEnum<'ctx>>> {
        let func_val = self
            .lower_expr(func_expr)?
            .ok_or_else(|| CodegenError::Internal("zipWithM: no function".to_string()))?;
        let func_ptr = match func_val {
            BasicValueEnum::PointerValue(p) => p,
            _ => {
                return Err(CodegenError::TypeError(
                    "zipWithM: function must be closure".to_string(),
                ))
            }
        };
        let list1_val = self
            .lower_expr(list1_expr)?
            .ok_or_else(|| CodegenError::Internal("zipWithM: no list1".to_string()))?;
        let list1_ptr = match list1_val {
            BasicValueEnum::PointerValue(p) => p,
            _ => {
                return Err(CodegenError::TypeError(
                    "zipWithM: expects list".to_string(),
                ))
            }
        };
        let list2_val = self
            .lower_expr(list2_expr)?
            .ok_or_else(|| CodegenError::Internal("zipWithM: no list2".to_string()))?;
        let list2_ptr = match list2_val {
            BasicValueEnum::PointerValue(p) => p,
            _ => {
                return Err(CodegenError::TypeError(
                    "zipWithM: expects list".to_string(),
                ))
            }
        };

        let ptr_type = self.type_mapper().ptr_type();
        let i64_type = self.type_mapper().i64_type();
        let current_fn = self
            .builder()
            .get_insert_block()
            .and_then(|b| b.get_parent())
            .ok_or_else(|| CodegenError::Internal("no current function".to_string()))?;
        let entry_block = self
            .builder()
            .get_insert_block()
            .ok_or_else(|| CodegenError::Internal("no current block".to_string()))?;

        let loop_header = self
            .llvm_context()
            .append_basic_block(current_fn, "zwm_header");
        let loop_body = self
            .llvm_context()
            .append_basic_block(current_fn, "zwm_body");
        let loop_exit = self
            .llvm_context()
            .append_basic_block(current_fn, "zwm_exit");

        let nil = self.build_nil()?;
        self.builder()
            .build_unconditional_branch(loop_header)
            .map_err(|e| CodegenError::Internal(format!("zipWithM: branch failed: {:?}", e)))?;

        // Loop header
        self.builder().position_at_end(loop_header);
        let result_phi = self
            .builder()
            .build_phi(ptr_type, "zwm_result")
            .map_err(|e| CodegenError::Internal(format!("zipWithM: phi failed: {:?}", e)))?;
        let list1_phi = self
            .builder()
            .build_phi(ptr_type, "zwm_list1")
            .map_err(|e| CodegenError::Internal(format!("zipWithM: phi failed: {:?}", e)))?;
        let list2_phi = self
            .builder()
            .build_phi(ptr_type, "zwm_list2")
            .map_err(|e| CodegenError::Internal(format!("zipWithM: phi failed: {:?}", e)))?;

        // Check if either list is empty
        let tag1 = self.extract_adt_tag(list1_phi.as_basic_value().into_pointer_value())?;
        let tag2 = self.extract_adt_tag(list2_phi.as_basic_value().into_pointer_value())?;
        let is_empty1 = self
            .builder()
            .build_int_compare(
                inkwell::IntPredicate::EQ,
                tag1,
                i64_type.const_zero(),
                "is_empty1",
            )
            .map_err(|e| CodegenError::Internal(format!("zipWithM: cmp failed: {:?}", e)))?;
        let is_empty2 = self
            .builder()
            .build_int_compare(
                inkwell::IntPredicate::EQ,
                tag2,
                i64_type.const_zero(),
                "is_empty2",
            )
            .map_err(|e| CodegenError::Internal(format!("zipWithM: cmp failed: {:?}", e)))?;
        let is_empty = self
            .builder()
            .build_or(is_empty1, is_empty2, "is_empty")
            .map_err(|e| CodegenError::Internal(format!("zipWithM: or failed: {:?}", e)))?;
        self.builder()
            .build_conditional_branch(is_empty, loop_exit, loop_body)
            .map_err(|e| CodegenError::Internal(format!("zipWithM: branch failed: {:?}", e)))?;

        // Loop body: extract heads/tails, call function
        self.builder().position_at_end(loop_body);
        let head1 =
            self.extract_adt_field(list1_phi.as_basic_value().into_pointer_value(), 2, 0)?;
        let tail1 =
            self.extract_adt_field(list1_phi.as_basic_value().into_pointer_value(), 2, 1)?;
        let head2 =
            self.extract_adt_field(list2_phi.as_basic_value().into_pointer_value(), 2, 0)?;
        let tail2 =
            self.extract_adt_field(list2_phi.as_basic_value().into_pointer_value(), 2, 1)?;

        // Flat 2-arg closure call: fn_ptr(closure_ptr, head1, head2) -> result
        let closure_fn_ptr = self.extract_closure_fn_ptr(func_ptr)?;
        let fn_type = ptr_type.fn_type(&[ptr_type.into(), ptr_type.into(), ptr_type.into()], false);
        let mapped_val = self
            .builder()
            .build_indirect_call(
                fn_type,
                closure_fn_ptr,
                &[func_ptr.into(), head1.into(), head2.into()],
                "zwm_mapped",
            )
            .map_err(|e| CodegenError::Internal(format!("zipWithM: call failed: {:?}", e)))?
            .try_as_basic_value()
            .basic()
            .ok_or_else(|| {
                CodegenError::Internal("zipWithM: function returned void".to_string())
            })?;

        let new_cons = self.build_cons(mapped_val, result_phi.as_basic_value())?;
        self.builder()
            .build_unconditional_branch(loop_header)
            .map_err(|e| CodegenError::Internal(format!("zipWithM: branch failed: {:?}", e)))?;

        // Phi incoming
        result_phi.add_incoming(&[(&nil, entry_block), (&new_cons, loop_body)]);
        list1_phi.add_incoming(&[(&list1_ptr, entry_block), (&tail1, loop_body)]);
        list2_phi.add_incoming(&[(&list2_ptr, entry_block), (&tail2, loop_body)]);

        // Exit: reverse accumulated result
        self.builder().position_at_end(loop_exit);
        self.build_inline_reverse(result_phi.as_basic_value().into_pointer_value(), current_fn)
    }

    /// Lower `zipWithM_ f xs ys` — monadic zipWith, discards results.
    fn lower_builtin_zipwith_m_(
        &mut self,
        func_expr: &Expr,
        list1_expr: &Expr,
        list2_expr: &Expr,
    ) -> CodegenResult<Option<BasicValueEnum<'ctx>>> {
        let func_val = self
            .lower_expr(func_expr)?
            .ok_or_else(|| CodegenError::Internal("zipWithM_: no function".to_string()))?;
        let func_ptr = match func_val {
            BasicValueEnum::PointerValue(p) => p,
            _ => {
                return Err(CodegenError::TypeError(
                    "zipWithM_: function must be closure".to_string(),
                ))
            }
        };
        let list1_val = self
            .lower_expr(list1_expr)?
            .ok_or_else(|| CodegenError::Internal("zipWithM_: no list1".to_string()))?;
        let list1_ptr = match list1_val {
            BasicValueEnum::PointerValue(p) => p,
            _ => {
                return Err(CodegenError::TypeError(
                    "zipWithM_: expects list".to_string(),
                ))
            }
        };
        let list2_val = self
            .lower_expr(list2_expr)?
            .ok_or_else(|| CodegenError::Internal("zipWithM_: no list2".to_string()))?;
        let list2_ptr = match list2_val {
            BasicValueEnum::PointerValue(p) => p,
            _ => {
                return Err(CodegenError::TypeError(
                    "zipWithM_: expects list".to_string(),
                ))
            }
        };

        let ptr_type = self.type_mapper().ptr_type();
        let i64_type = self.type_mapper().i64_type();
        let current_fn = self
            .builder()
            .get_insert_block()
            .and_then(|b| b.get_parent())
            .ok_or_else(|| CodegenError::Internal("no current function".to_string()))?;
        let entry_block = self
            .builder()
            .get_insert_block()
            .ok_or_else(|| CodegenError::Internal("no current block".to_string()))?;

        let loop_header = self
            .llvm_context()
            .append_basic_block(current_fn, "zwm__header");
        let loop_body = self
            .llvm_context()
            .append_basic_block(current_fn, "zwm__body");
        let loop_exit = self
            .llvm_context()
            .append_basic_block(current_fn, "zwm__exit");

        self.builder()
            .build_unconditional_branch(loop_header)
            .map_err(|e| CodegenError::Internal(format!("zipWithM_: branch failed: {:?}", e)))?;

        // Loop header
        self.builder().position_at_end(loop_header);
        let list1_phi = self
            .builder()
            .build_phi(ptr_type, "zwm__list1")
            .map_err(|e| CodegenError::Internal(format!("zipWithM_: phi failed: {:?}", e)))?;
        let list2_phi = self
            .builder()
            .build_phi(ptr_type, "zwm__list2")
            .map_err(|e| CodegenError::Internal(format!("zipWithM_: phi failed: {:?}", e)))?;

        // Check if either list is empty
        let tag1 = self.extract_adt_tag(list1_phi.as_basic_value().into_pointer_value())?;
        let tag2 = self.extract_adt_tag(list2_phi.as_basic_value().into_pointer_value())?;
        let is_empty1 = self
            .builder()
            .build_int_compare(
                inkwell::IntPredicate::EQ,
                tag1,
                i64_type.const_zero(),
                "is_empty1",
            )
            .map_err(|e| CodegenError::Internal(format!("zipWithM_: cmp failed: {:?}", e)))?;
        let is_empty2 = self
            .builder()
            .build_int_compare(
                inkwell::IntPredicate::EQ,
                tag2,
                i64_type.const_zero(),
                "is_empty2",
            )
            .map_err(|e| CodegenError::Internal(format!("zipWithM_: cmp failed: {:?}", e)))?;
        let is_empty = self
            .builder()
            .build_or(is_empty1, is_empty2, "is_empty")
            .map_err(|e| CodegenError::Internal(format!("zipWithM_: or failed: {:?}", e)))?;
        self.builder()
            .build_conditional_branch(is_empty, loop_exit, loop_body)
            .map_err(|e| CodegenError::Internal(format!("zipWithM_: branch failed: {:?}", e)))?;

        // Loop body: extract heads/tails, call function, discard result
        self.builder().position_at_end(loop_body);
        let head1 =
            self.extract_adt_field(list1_phi.as_basic_value().into_pointer_value(), 2, 0)?;
        let tail1 =
            self.extract_adt_field(list1_phi.as_basic_value().into_pointer_value(), 2, 1)?;
        let head2 =
            self.extract_adt_field(list2_phi.as_basic_value().into_pointer_value(), 2, 0)?;
        let tail2 =
            self.extract_adt_field(list2_phi.as_basic_value().into_pointer_value(), 2, 1)?;

        // Flat 2-arg closure call: fn_ptr(closure_ptr, head1, head2) -> result (discarded)
        let closure_fn_ptr = self.extract_closure_fn_ptr(func_ptr)?;
        let fn_type = ptr_type.fn_type(&[ptr_type.into(), ptr_type.into(), ptr_type.into()], false);
        let _ = self
            .builder()
            .build_indirect_call(
                fn_type,
                closure_fn_ptr,
                &[func_ptr.into(), head1.into(), head2.into()],
                "zwm__result",
            )
            .map_err(|e| CodegenError::Internal(format!("zipWithM_: call failed: {:?}", e)))?;

        self.builder()
            .build_unconditional_branch(loop_header)
            .map_err(|e| CodegenError::Internal(format!("zipWithM_: branch failed: {:?}", e)))?;

        // Phi incoming
        list1_phi.add_incoming(&[(&list1_ptr, entry_block), (&tail1, loop_body)]);
        list2_phi.add_incoming(&[(&list2_ptr, entry_block), (&tail2, loop_body)]);

        // Exit: return unit
        self.builder().position_at_end(loop_exit);
        Ok(Some(self.type_mapper().ptr_type().const_null().into()))
    }

    // Data.Function handlers

    /// Lower `flip f x y = f y x`.
    /// Uses flat 3-arg call: fn(closure_env, arg2, arg1) — BHC calling convention.
    fn lower_builtin_flip(
        &mut self,
        func_expr: &Expr,
        arg1_expr: &Expr,
        arg2_expr: &Expr,
    ) -> CodegenResult<Option<BasicValueEnum<'ctx>>> {
        let func_val = self
            .lower_expr(func_expr)?
            .ok_or_else(|| CodegenError::Internal("flip: no function".to_string()))?;
        let arg1_val = self
            .lower_expr(arg1_expr)?
            .ok_or_else(|| CodegenError::Internal("flip: no arg1".to_string()))?;
        let arg2_val = self
            .lower_expr(arg2_expr)?
            .ok_or_else(|| CodegenError::Internal("flip: no arg2".to_string()))?;
        let func_ptr = match func_val {
            BasicValueEnum::PointerValue(p) => p,
            _ => {
                return Err(CodegenError::TypeError(
                    "flip: function must be closure".to_string(),
                ))
            }
        };
        let ptr_type = self.type_mapper().ptr_type();
        let arg1_ptr = self.value_to_ptr(arg1_val)?;
        let arg2_ptr = self.value_to_ptr(arg2_val)?;
        let fn_ptr = self.extract_closure_fn_ptr(func_ptr)?;
        // Flat 3-arg call: fn(closure_env, arg2, arg1) — args swapped
        let fn_type = ptr_type.fn_type(&[ptr_type.into(), ptr_type.into(), ptr_type.into()], false);
        let result = self
            .builder()
            .build_indirect_call(
                fn_type,
                fn_ptr,
                &[func_ptr.into(), arg2_ptr.into(), arg1_ptr.into()],
                "flip_result",
            )
            .map_err(|e| CodegenError::Internal(format!("flip call failed: {:?}", e)))?
            .try_as_basic_value()
            .basic()
            .ok_or_else(|| CodegenError::Internal("flip: result returned void".to_string()))?;
        Ok(Some(result))
    }

    /// Lower `on f g x y = f (g x) (g y)`.
    fn lower_builtin_on(
        &mut self,
        f_expr: &Expr,
        g_expr: &Expr,
        x_expr: &Expr,
        y_expr: &Expr,
    ) -> CodegenResult<Option<BasicValueEnum<'ctx>>> {
        let f_val = self
            .lower_expr(f_expr)?
            .ok_or_else(|| CodegenError::Internal("on: no f".to_string()))?;
        let g_val = self
            .lower_expr(g_expr)?
            .ok_or_else(|| CodegenError::Internal("on: no g".to_string()))?;
        let x_val = self
            .lower_expr(x_expr)?
            .ok_or_else(|| CodegenError::Internal("on: no x".to_string()))?;
        let y_val = self
            .lower_expr(y_expr)?
            .ok_or_else(|| CodegenError::Internal("on: no y".to_string()))?;
        let g_ptr = match g_val {
            BasicValueEnum::PointerValue(p) => p,
            _ => return Err(CodegenError::TypeError("on: g must be closure".to_string())),
        };
        let f_ptr = match f_val {
            BasicValueEnum::PointerValue(p) => p,
            _ => return Err(CodegenError::TypeError("on: f must be closure".to_string())),
        };
        let ptr_type = self.type_mapper().ptr_type();
        let fn_type = ptr_type.fn_type(&[ptr_type.into(), ptr_type.into()], false);
        let x_ptr = self.value_to_ptr(x_val)?;
        let g_fn_ptr = self.extract_closure_fn_ptr(g_ptr)?;
        let gx = self
            .builder()
            .build_indirect_call(fn_type, g_fn_ptr, &[g_ptr.into(), x_ptr.into()], "gx")
            .map_err(|e| CodegenError::Internal(format!("on: g(x) failed: {:?}", e)))?
            .try_as_basic_value()
            .basic()
            .ok_or_else(|| CodegenError::Internal("on: g(x) void".to_string()))?;
        let y_ptr = self.value_to_ptr(y_val)?;
        let gy = self
            .builder()
            .build_indirect_call(fn_type, g_fn_ptr, &[g_ptr.into(), y_ptr.into()], "gy")
            .map_err(|e| CodegenError::Internal(format!("on: g(y) failed: {:?}", e)))?
            .try_as_basic_value()
            .basic()
            .ok_or_else(|| CodegenError::Internal("on: g(y) void".to_string()))?;
        let f_fn_ptr = self.extract_closure_fn_ptr(f_ptr)?;
        let gx_ptr = self.value_to_ptr(gx)?;
        let partial = self
            .builder()
            .build_indirect_call(fn_type, f_fn_ptr, &[f_ptr.into(), gx_ptr.into()], "f_gx")
            .map_err(|e| CodegenError::Internal(format!("on: f(gx) failed: {:?}", e)))?
            .try_as_basic_value()
            .basic()
            .ok_or_else(|| CodegenError::Internal("on: f(gx) void".to_string()))?;
        let partial_ptr = match partial {
            BasicValueEnum::PointerValue(p) => p,
            _ => return Ok(Some(partial)),
        };
        let gy_ptr = self.value_to_ptr(gy)?;
        let p_fn = self.extract_closure_fn_ptr(partial_ptr)?;
        let result = self
            .builder()
            .build_indirect_call(
                fn_type,
                p_fn,
                &[partial_ptr.into(), gy_ptr.into()],
                "on_result",
            )
            .map_err(|e| CodegenError::Internal(format!("on: result failed: {:?}", e)))?
            .try_as_basic_value()
            .basic()
            .ok_or_else(|| CodegenError::Internal("on: result void".to_string()))?;
        Ok(Some(result))
    }

    /// Lower `fix` (simplified).
    fn lower_builtin_fix(
        &mut self,
        func_expr: &Expr,
    ) -> CodegenResult<Option<BasicValueEnum<'ctx>>> {
        self.lower_expr(func_expr)
    }

    /// Lower `$` - function application.
    fn lower_builtin_apply(
        &mut self,
        func_expr: &Expr,
        arg_expr: &Expr,
    ) -> CodegenResult<Option<BasicValueEnum<'ctx>>> {
        let func_val = self
            .lower_expr(func_expr)?
            .ok_or_else(|| CodegenError::Internal("$: no function".to_string()))?;
        let arg_val = self
            .lower_expr(arg_expr)?
            .ok_or_else(|| CodegenError::Internal("$: no argument".to_string()))?;
        let func_ptr = match func_val {
            BasicValueEnum::PointerValue(p) => p,
            _ => {
                return Err(CodegenError::TypeError(
                    "$: function must be closure".to_string(),
                ))
            }
        };
        let ptr_type = self.type_mapper().ptr_type();
        let arg_ptr = self.value_to_ptr(arg_val)?;
        let fn_ptr = self.extract_closure_fn_ptr(func_ptr)?;
        let fn_type = ptr_type.fn_type(&[ptr_type.into(), ptr_type.into()], false);
        let result = self
            .builder()
            .build_indirect_call(
                fn_type,
                fn_ptr,
                &[func_ptr.into(), arg_ptr.into()],
                "apply_result",
            )
            .map_err(|e| CodegenError::Internal(format!("$ call failed: {:?}", e)))?
            .try_as_basic_value()
            .basic()
            .ok_or_else(|| CodegenError::Internal("$: returned void".to_string()))?;
        Ok(Some(result))
    }

    /// Lower `.` - function composition: (f . g) x = f (g x).
    fn lower_builtin_compose(
        &mut self,
        f_expr: &Expr,
        g_expr: &Expr,
        x_expr: &Expr,
    ) -> CodegenResult<Option<BasicValueEnum<'ctx>>> {
        let f_val = self
            .lower_expr(f_expr)?
            .ok_or_else(|| CodegenError::Internal(".: no f".to_string()))?;
        let g_val = self
            .lower_expr(g_expr)?
            .ok_or_else(|| CodegenError::Internal(".: no g".to_string()))?;
        let x_val = self
            .lower_expr(x_expr)?
            .ok_or_else(|| CodegenError::Internal(".: no x".to_string()))?;
        let g_ptr = match g_val {
            BasicValueEnum::PointerValue(p) => p,
            _ => return Err(CodegenError::TypeError(".: g must be closure".to_string())),
        };
        let f_ptr = match f_val {
            BasicValueEnum::PointerValue(p) => p,
            _ => return Err(CodegenError::TypeError(".: f must be closure".to_string())),
        };
        let ptr_type = self.type_mapper().ptr_type();
        let fn_type = ptr_type.fn_type(&[ptr_type.into(), ptr_type.into()], false);
        let x_ptr = self.value_to_ptr(x_val)?;
        let g_fn = self.extract_closure_fn_ptr(g_ptr)?;
        let gx = self
            .builder()
            .build_indirect_call(fn_type, g_fn, &[g_ptr.into(), x_ptr.into()], "gx")
            .map_err(|e| CodegenError::Internal(format!(".: g(x) failed: {:?}", e)))?
            .try_as_basic_value()
            .basic()
            .ok_or_else(|| CodegenError::Internal(".: g(x) void".to_string()))?;
        let gx_ptr = self.value_to_ptr(gx)?;
        let f_fn = self.extract_closure_fn_ptr(f_ptr)?;
        let result = self
            .builder()
            .build_indirect_call(fn_type, f_fn, &[f_ptr.into(), gx_ptr.into()], "compose")
            .map_err(|e| CodegenError::Internal(format!(".: f(g(x)) failed: {:?}", e)))?
            .try_as_basic_value()
            .basic()
            .ok_or_else(|| CodegenError::Internal(".: f(g(x)) void".to_string()))?;
        Ok(Some(result))
    }

    // ========================================================================
    // E.27: succ, pred, (&), swap, curry, uncurry
    // ========================================================================

    /// Lower `succ` - successor function (n + 1), or user enum succ via fromEnum/toEnum.
    fn lower_builtin_succ(&mut self, expr: &Expr) -> CodegenResult<Option<BasicValueEnum<'ctx>>> {
        // Check if argument is a user enum type
        if let Some(type_name) = self.infer_adt_type_from_expr(expr) {
            if self.derived_from_enum_fns.contains_key(&type_name)
                && self.derived_to_enum_fns.contains_key(&type_name)
            {
                // succ x = toEnum(fromEnum(x) + 1)
                let val = self
                    .lower_expr(expr)?
                    .ok_or_else(|| CodegenError::Internal("succ: no value".to_string()))?;
                let tag = self.call_derived_from_enum(&type_name, val)?;
                let one = self.type_mapper().i64_type().const_int(1, false);
                let tag_plus_1 = self
                    .builder()
                    .build_int_add(tag, one, "succ_tag")
                    .map_err(|e| CodegenError::Internal(format!("succ add: {:?}", e)))?;
                let result = self.call_derived_to_enum(&type_name, tag_plus_1)?;
                return Ok(Some(result));
            }
        }
        // Fallback: Int succ (n + 1)
        let val = self
            .lower_expr(expr)?
            .ok_or_else(|| CodegenError::Internal("succ: no value".to_string()))?;
        let int_val = self.coerce_to_int(val)?;
        let one = self.type_mapper().i64_type().const_int(1, false);
        let result = self
            .builder()
            .build_int_add(int_val, one, "succ")
            .map_err(|e| CodegenError::Internal(format!("succ failed: {:?}", e)))?;
        Ok(Some(self.int_to_ptr(result)?.into()))
    }

    /// Lower `pred` - predecessor function (n - 1), or user enum pred via fromEnum/toEnum.
    fn lower_builtin_pred(&mut self, expr: &Expr) -> CodegenResult<Option<BasicValueEnum<'ctx>>> {
        // Check if argument is a user enum type
        if let Some(type_name) = self.infer_adt_type_from_expr(expr) {
            if self.derived_from_enum_fns.contains_key(&type_name)
                && self.derived_to_enum_fns.contains_key(&type_name)
            {
                // pred x = toEnum(fromEnum(x) - 1)
                let val = self
                    .lower_expr(expr)?
                    .ok_or_else(|| CodegenError::Internal("pred: no value".to_string()))?;
                let tag = self.call_derived_from_enum(&type_name, val)?;
                let one = self.type_mapper().i64_type().const_int(1, false);
                let tag_minus_1 = self
                    .builder()
                    .build_int_sub(tag, one, "pred_tag")
                    .map_err(|e| CodegenError::Internal(format!("pred sub: {:?}", e)))?;
                let result = self.call_derived_to_enum(&type_name, tag_minus_1)?;
                return Ok(Some(result));
            }
        }
        // Fallback: Int pred (n - 1)
        let val = self
            .lower_expr(expr)?
            .ok_or_else(|| CodegenError::Internal("pred: no value".to_string()))?;
        let int_val = self.coerce_to_int(val)?;
        let one = self.type_mapper().i64_type().const_int(1, false);
        let result = self
            .builder()
            .build_int_sub(int_val, one, "pred")
            .map_err(|e| CodegenError::Internal(format!("pred failed: {:?}", e)))?;
        Ok(Some(self.int_to_ptr(result)?.into()))
    }

    /// Lower `fromEnum` - convert enum value to Int.
    fn lower_builtin_from_enum(
        &mut self,
        expr: &Expr,
    ) -> CodegenResult<Option<BasicValueEnum<'ctx>>> {
        // Check if argument is a user enum type
        if let Some(type_name) = self.infer_adt_type_from_expr(expr) {
            if let Some(&var_id) = self.derived_from_enum_fns.get(&type_name) {
                if let Some(fn_val) = self.functions.get(&var_id).copied() {
                    let val = self
                        .lower_expr(expr)?
                        .ok_or_else(|| CodegenError::Internal("fromEnum: no value".to_string()))?;
                    // Call derived fromEnum: fn(env, x) -> Int (as tagged int pointer)
                    let null_env = self.type_mapper().ptr_type().const_null();
                    let fn_ptr = fn_val.as_global_value().as_pointer_value();
                    let fn_type = self.type_mapper().ptr_type().fn_type(
                        &[
                            self.type_mapper().ptr_type().into(),
                            self.type_mapper().ptr_type().into(),
                        ],
                        false,
                    );
                    let result = self
                        .builder()
                        .build_indirect_call(
                            fn_type,
                            fn_ptr,
                            &[null_env.into(), val.into()],
                            "derived_from_enum",
                        )
                        .map_err(|e| CodegenError::Internal(format!("fromEnum call: {:?}", e)))?;
                    return Ok(result.try_as_basic_value().basic());
                }
            }
        }
        // Fallback: identity (fromEnum on Int = identity)
        self.lower_expr(expr)
    }

    /// Lower `toEnum` - convert Int to enum value.
    fn lower_builtin_to_enum(
        &mut self,
        expr: &Expr,
    ) -> CodegenResult<Option<BasicValueEnum<'ctx>>> {
        // If there's exactly one user enum with derived Enum, use it
        if self.derived_to_enum_fns.len() == 1 {
            let (type_name, &var_id) = self.derived_to_enum_fns.iter().next().unwrap();
            let type_name = type_name.clone();
            if let Some(fn_val) = self.functions.get(&var_id).copied() {
                let val = self
                    .lower_expr(expr)?
                    .ok_or_else(|| CodegenError::Internal("toEnum: no value".to_string()))?;
                // Convert Int value to pointer for the call (derived functions use ptr calling convention)
                let val_ptr = self.value_to_ptr(val)?;
                // Call derived toEnum: fn(env, n) -> Constructor pointer
                let null_env = self.type_mapper().ptr_type().const_null();
                let fn_ptr = fn_val.as_global_value().as_pointer_value();
                let fn_type = self.type_mapper().ptr_type().fn_type(
                    &[
                        self.type_mapper().ptr_type().into(),
                        self.type_mapper().ptr_type().into(),
                    ],
                    false,
                );
                let result = self
                    .builder()
                    .build_indirect_call(
                        fn_type,
                        fn_ptr,
                        &[null_env.into(), val_ptr.into()],
                        "derived_to_enum",
                    )
                    .map_err(|e| CodegenError::Internal(format!("toEnum call: {:?}", e)))?;
                let _ = type_name;
                return Ok(result.try_as_basic_value().basic());
            }
        }
        // Fallback: identity (toEnum on Int = identity)
        self.lower_expr(expr)
    }

    /// Lower `minBound` - return the minimum value of a Bounded type.
    fn lower_builtin_min_bound(&mut self) -> CodegenResult<Option<BasicValueEnum<'ctx>>> {
        // If there's exactly one user enum with derived Bounded, use it
        if self.derived_min_bound_fns.len() == 1 {
            let (type_name, &var_id) = self.derived_min_bound_fns.iter().next().unwrap();
            let type_name = type_name.clone();
            if let Some(fn_val) = self.functions.get(&var_id).copied() {
                // Call derived minBound: fn(env) -> Constructor pointer
                let null_env = self.type_mapper().ptr_type().const_null();
                let fn_ptr = fn_val.as_global_value().as_pointer_value();
                let fn_type = self
                    .type_mapper()
                    .ptr_type()
                    .fn_type(&[self.type_mapper().ptr_type().into()], false);
                let result = self
                    .builder()
                    .build_indirect_call(fn_type, fn_ptr, &[null_env.into()], "derived_min_bound")
                    .map_err(|e| CodegenError::Internal(format!("minBound call: {:?}", e)))?;
                let _ = type_name;
                return Ok(result.try_as_basic_value().basic());
            }
        }
        // Fallback: Int.MIN_VALUE
        let min_val = self
            .type_mapper()
            .i64_type()
            .const_int(i64::MIN as u64, true);
        Ok(Some(self.int_to_ptr(min_val)?.into()))
    }

    /// Lower `maxBound` - return the maximum value of a Bounded type.
    fn lower_builtin_max_bound(&mut self) -> CodegenResult<Option<BasicValueEnum<'ctx>>> {
        // If there's exactly one user enum with derived Bounded, use it
        if self.derived_max_bound_fns.len() == 1 {
            let (type_name, &var_id) = self.derived_max_bound_fns.iter().next().unwrap();
            let type_name = type_name.clone();
            if let Some(fn_val) = self.functions.get(&var_id).copied() {
                // Call derived maxBound: fn(env) -> Constructor pointer
                let null_env = self.type_mapper().ptr_type().const_null();
                let fn_ptr = fn_val.as_global_value().as_pointer_value();
                let fn_type = self
                    .type_mapper()
                    .ptr_type()
                    .fn_type(&[self.type_mapper().ptr_type().into()], false);
                let result = self
                    .builder()
                    .build_indirect_call(fn_type, fn_ptr, &[null_env.into()], "derived_max_bound")
                    .map_err(|e| CodegenError::Internal(format!("maxBound call: {:?}", e)))?;
                let _ = type_name;
                return Ok(result.try_as_basic_value().basic());
            }
        }
        // Fallback: Int.MAX_VALUE
        let max_val = self
            .type_mapper()
            .i64_type()
            .const_int(i64::MAX as u64, true);
        Ok(Some(self.int_to_ptr(max_val)?.into()))
    }

    /// Helper: Call a derived fromEnum function on a value, returning the Int tag.
    fn call_derived_from_enum(
        &mut self,
        type_name: &str,
        val: BasicValueEnum<'ctx>,
    ) -> CodegenResult<IntValue<'ctx>> {
        let var_id = self
            .derived_from_enum_fns
            .get(type_name)
            .copied()
            .ok_or_else(|| {
                CodegenError::Internal(format!("no derived fromEnum for {}", type_name))
            })?;
        let fn_val = self.functions.get(&var_id).copied().ok_or_else(|| {
            CodegenError::Internal(format!("fromEnum function not found for {}", type_name))
        })?;
        let null_env = self.type_mapper().ptr_type().const_null();
        let fn_ptr = fn_val.as_global_value().as_pointer_value();
        let fn_type = self.type_mapper().ptr_type().fn_type(
            &[
                self.type_mapper().ptr_type().into(),
                self.type_mapper().ptr_type().into(),
            ],
            false,
        );
        let result = self
            .builder()
            .build_indirect_call(
                fn_type,
                fn_ptr,
                &[null_env.into(), val.into()],
                "from_enum_call",
            )
            .map_err(|e| CodegenError::Internal(format!("fromEnum call: {:?}", e)))?;
        // The derived fromEnum returns an Int value as a tagged pointer
        let result_ptr = result
            .try_as_basic_value()
            .basic()
            .ok_or_else(|| CodegenError::Internal("fromEnum: no return value".to_string()))?;
        self.coerce_to_int(result_ptr)
    }

    /// Helper: Call a derived toEnum function with an Int tag, returning the constructor pointer.
    fn call_derived_to_enum(
        &mut self,
        type_name: &str,
        tag: IntValue<'ctx>,
    ) -> CodegenResult<BasicValueEnum<'ctx>> {
        let var_id = self
            .derived_to_enum_fns
            .get(type_name)
            .copied()
            .ok_or_else(|| {
                CodegenError::Internal(format!("no derived toEnum for {}", type_name))
            })?;
        let fn_val = self.functions.get(&var_id).copied().ok_or_else(|| {
            CodegenError::Internal(format!("toEnum function not found for {}", type_name))
        })?;
        let null_env = self.type_mapper().ptr_type().const_null();
        let fn_ptr = fn_val.as_global_value().as_pointer_value();
        let fn_type = self.type_mapper().ptr_type().fn_type(
            &[
                self.type_mapper().ptr_type().into(),
                self.type_mapper().ptr_type().into(),
            ],
            false,
        );
        // Convert Int tag to tagged pointer for the call
        let tag_ptr = self.int_to_ptr(tag)?;
        let result = self
            .builder()
            .build_indirect_call(
                fn_type,
                fn_ptr,
                &[null_env.into(), tag_ptr.into()],
                "to_enum_call",
            )
            .map_err(|e| CodegenError::Internal(format!("toEnum call: {:?}", e)))?;
        result
            .try_as_basic_value()
            .basic()
            .ok_or_else(|| CodegenError::Internal("toEnum: no return value".to_string()))
    }

    // ====================================================================
    // E.28: Arithmetic, enum, folds, higher-order, IO input
    // ====================================================================

    /// Lower `min` - returns the smaller of two Ints.
    fn lower_builtin_min(
        &mut self,
        a_expr: &Expr,
        b_expr: &Expr,
    ) -> CodegenResult<Option<BasicValueEnum<'ctx>>> {
        let a_val = self
            .lower_expr(a_expr)?
            .ok_or_else(|| CodegenError::Internal("min: a has no value".to_string()))?;
        let b_val = self
            .lower_expr(b_expr)?
            .ok_or_else(|| CodegenError::Internal("min: b has no value".to_string()))?;
        let a_int = self.coerce_to_int(a_val)?;
        let b_int = self.coerce_to_int(b_val)?;
        let cmp = self
            .builder()
            .build_int_compare(inkwell::IntPredicate::SLT, a_int, b_int, "min_cmp")
            .map_err(|e| CodegenError::Internal(format!("min cmp: {:?}", e)))?;
        let result = self
            .builder()
            .build_select(cmp, a_int, b_int, "min_sel")
            .map_err(|e| CodegenError::Internal(format!("min sel: {:?}", e)))?;
        Ok(Some(self.int_to_ptr(result.into_int_value())?.into()))
    }

    /// Lower `max` - returns the larger of two Ints.
    fn lower_builtin_max(
        &mut self,
        a_expr: &Expr,
        b_expr: &Expr,
    ) -> CodegenResult<Option<BasicValueEnum<'ctx>>> {
        let a_val = self
            .lower_expr(a_expr)?
            .ok_or_else(|| CodegenError::Internal("max: a has no value".to_string()))?;
        let b_val = self
            .lower_expr(b_expr)?
            .ok_or_else(|| CodegenError::Internal("max: b has no value".to_string()))?;
        let a_int = self.coerce_to_int(a_val)?;
        let b_int = self.coerce_to_int(b_val)?;
        let cmp = self
            .builder()
            .build_int_compare(inkwell::IntPredicate::SGT, a_int, b_int, "max_cmp")
            .map_err(|e| CodegenError::Internal(format!("max cmp: {:?}", e)))?;
        let result = self
            .builder()
            .build_select(cmp, a_int, b_int, "max_sel")
            .map_err(|e| CodegenError::Internal(format!("max sel: {:?}", e)))?;
        Ok(Some(self.int_to_ptr(result.into_int_value())?.into()))
    }

    /// Lower `subtract` - subtract x y = y - x (flipped subtraction).
    fn lower_builtin_subtract(
        &mut self,
        x_expr: &Expr,
        y_expr: &Expr,
    ) -> CodegenResult<Option<BasicValueEnum<'ctx>>> {
        let x_val = self
            .lower_expr(x_expr)?
            .ok_or_else(|| CodegenError::Internal("subtract: x has no value".to_string()))?;
        let y_val = self
            .lower_expr(y_expr)?
            .ok_or_else(|| CodegenError::Internal("subtract: y has no value".to_string()))?;
        let x_int = self.coerce_to_int(x_val)?;
        let y_int = self.coerce_to_int(y_val)?;
        // subtract x y = y - x
        let result = self
            .builder()
            .build_int_sub(y_int, x_int, "subtract")
            .map_err(|e| CodegenError::Internal(format!("subtract: {:?}", e)))?;
        Ok(Some(self.int_to_ptr(result)?.into()))
    }

    /// Lower `enumFrom` - [n..] (infinite list, capped at 10000 elements as fallback).
    fn lower_builtin_enum_from(
        &mut self,
        from_expr: &Expr,
    ) -> CodegenResult<Option<BasicValueEnum<'ctx>>> {
        // Build a finite list of 10000 elements as fallback
        // Real programs use `take n (enumFrom x)` which the optimizer can handle
        let from_val = self
            .lower_expr(from_expr)?
            .ok_or_else(|| CodegenError::Internal("enumFrom: from has no value".to_string()))?;
        let from = self.to_int_value(from_val)?;

        let tm = self.type_mapper();
        let i64_type = tm.i64_type();
        let current_fn = self
            .builder()
            .get_insert_block()
            .and_then(|b| b.get_parent())
            .ok_or_else(|| CodegenError::Internal("no current function".to_string()))?;

        // to = from + 9999
        let limit = self
            .builder()
            .build_int_add(from, i64_type.const_int(9999, false), "enum_limit")
            .map_err(|e| CodegenError::Internal(format!("enumFrom: add: {:?}", e)))?;

        // Build list backwards from limit down to from (same pattern as enumFromTo)
        let loop_header = self.llvm_ctx.append_basic_block(current_fn, "enumf_header");
        let loop_body = self.llvm_ctx.append_basic_block(current_fn, "enumf_body");
        let loop_exit = self.llvm_ctx.append_basic_block(current_fn, "enumf_exit");

        let nil = self.build_nil()?;
        let entry_block = self.builder().get_insert_block().unwrap();
        self.builder()
            .build_unconditional_branch(loop_header)
            .map_err(|e| CodegenError::Internal(format!("enumFrom: branch: {:?}", e)))?;

        self.builder().position_at_end(loop_header);
        let acc_phi = self
            .builder()
            .build_phi(tm.ptr_type(), "acc")
            .map_err(|e| CodegenError::Internal(format!("enumFrom: phi: {:?}", e)))?;
        let current_phi = self
            .builder()
            .build_phi(i64_type, "current")
            .map_err(|e| CodegenError::Internal(format!("enumFrom: phi: {:?}", e)))?;

        let current = current_phi.as_basic_value().into_int_value();
        let done = self
            .builder()
            .build_int_compare(inkwell::IntPredicate::SLT, current, from, "done")
            .map_err(|e| CodegenError::Internal(format!("enumFrom: cmp: {:?}", e)))?;
        self.builder()
            .build_conditional_branch(done, loop_exit, loop_body)
            .map_err(|e| CodegenError::Internal(format!("enumFrom: branch: {:?}", e)))?;

        self.builder().position_at_end(loop_body);
        let boxed = self.box_int(current)?;
        let new_acc = self.build_cons(boxed.into(), acc_phi.as_basic_value())?;
        let prev = self
            .builder()
            .build_int_sub(current, i64_type.const_int(1, false), "prev")
            .map_err(|e| CodegenError::Internal(format!("enumFrom: sub: {:?}", e)))?;
        self.builder()
            .build_unconditional_branch(loop_header)
            .map_err(|e| CodegenError::Internal(format!("enumFrom: branch: {:?}", e)))?;

        acc_phi.add_incoming(&[(&nil, entry_block), (&new_acc, loop_body)]);
        current_phi.add_incoming(&[(&limit, entry_block), (&prev, loop_body)]);

        self.builder().position_at_end(loop_exit);
        Ok(Some(acc_phi.as_basic_value()))
    }

    /// Lower `enumFromThen` - [n, m..] (infinite list with step, capped at 10000).
    fn lower_builtin_enum_from_then(
        &mut self,
        from_expr: &Expr,
        then_expr: &Expr,
    ) -> CodegenResult<Option<BasicValueEnum<'ctx>>> {
        let from_val = self
            .lower_expr(from_expr)?
            .ok_or_else(|| CodegenError::Internal("enumFromThen: from has no value".to_string()))?;
        let then_val = self
            .lower_expr(then_expr)?
            .ok_or_else(|| CodegenError::Internal("enumFromThen: then has no value".to_string()))?;
        let from = self.to_int_value(from_val)?;
        let then_v = self.to_int_value(then_val)?;

        let tm = self.type_mapper();
        let i64_type = tm.i64_type();
        let current_fn = self
            .builder()
            .get_insert_block()
            .and_then(|b| b.get_parent())
            .ok_or_else(|| CodegenError::Internal("no current function".to_string()))?;

        // step = then - from
        let step = self
            .builder()
            .build_int_sub(then_v, from, "step")
            .map_err(|e| CodegenError::Internal(format!("enumFromThen: sub: {:?}", e)))?;

        // Build 10000 elements: last_val = from + step * 9999
        // Build backwards from element 9999 down to 0
        let loop_header = self
            .llvm_ctx
            .append_basic_block(current_fn, "enumft_header");
        let loop_body = self.llvm_ctx.append_basic_block(current_fn, "enumft_body");
        let loop_exit = self.llvm_ctx.append_basic_block(current_fn, "enumft_exit");

        let nil = self.build_nil()?;
        let count_limit = i64_type.const_int(10000, false);
        let entry_block = self.builder().get_insert_block().unwrap();
        self.builder()
            .build_unconditional_branch(loop_header)
            .map_err(|e| CodegenError::Internal(format!("enumFromThen: branch: {:?}", e)))?;

        self.builder().position_at_end(loop_header);
        let acc_phi = self
            .builder()
            .build_phi(tm.ptr_type(), "acc")
            .map_err(|e| CodegenError::Internal(format!("enumFromThen: phi: {:?}", e)))?;
        let idx_phi = self
            .builder()
            .build_phi(i64_type, "idx")
            .map_err(|e| CodegenError::Internal(format!("enumFromThen: phi: {:?}", e)))?;

        let idx = idx_phi.as_basic_value().into_int_value();
        let done = self
            .builder()
            .build_int_compare(
                inkwell::IntPredicate::SLT,
                idx,
                i64_type.const_zero(),
                "done",
            )
            .map_err(|e| CodegenError::Internal(format!("enumFromThen: cmp: {:?}", e)))?;
        self.builder()
            .build_conditional_branch(done, loop_exit, loop_body)
            .map_err(|e| CodegenError::Internal(format!("enumFromThen: branch: {:?}", e)))?;

        self.builder().position_at_end(loop_body);
        // current_val = from + step * idx
        let step_times_idx = self
            .builder()
            .build_int_mul(step, idx, "step_idx")
            .map_err(|e| CodegenError::Internal(format!("enumFromThen: mul: {:?}", e)))?;
        let current_val = self
            .builder()
            .build_int_add(from, step_times_idx, "cur_val")
            .map_err(|e| CodegenError::Internal(format!("enumFromThen: add: {:?}", e)))?;
        let boxed = self.box_int(current_val)?;
        let new_acc = self.build_cons(boxed.into(), acc_phi.as_basic_value())?;
        let prev_idx = self
            .builder()
            .build_int_sub(idx, i64_type.const_int(1, false), "prev_idx")
            .map_err(|e| CodegenError::Internal(format!("enumFromThen: sub: {:?}", e)))?;
        self.builder()
            .build_unconditional_branch(loop_header)
            .map_err(|e| CodegenError::Internal(format!("enumFromThen: branch: {:?}", e)))?;

        let start_idx = self
            .builder()
            .build_int_sub(count_limit, i64_type.const_int(1, false), "start_idx")
            .map_err(|e| CodegenError::Internal(format!("enumFromThen: sub: {:?}", e)))?;
        acc_phi.add_incoming(&[(&nil, entry_block), (&new_acc, loop_body)]);
        idx_phi.add_incoming(&[(&start_idx, entry_block), (&prev_idx, loop_body)]);

        self.builder().position_at_end(loop_exit);
        Ok(Some(acc_phi.as_basic_value()))
    }

    /// Lower `enumFromThenTo` - [n, m..p] (finite list with step).
    fn lower_builtin_enum_from_then_to(
        &mut self,
        from_expr: &Expr,
        then_expr: &Expr,
        to_expr: &Expr,
    ) -> CodegenResult<Option<BasicValueEnum<'ctx>>> {
        let from_val = self.lower_expr(from_expr)?.ok_or_else(|| {
            CodegenError::Internal("enumFromThenTo: from has no value".to_string())
        })?;
        let then_val = self.lower_expr(then_expr)?.ok_or_else(|| {
            CodegenError::Internal("enumFromThenTo: then has no value".to_string())
        })?;
        let to_val = self
            .lower_expr(to_expr)?
            .ok_or_else(|| CodegenError::Internal("enumFromThenTo: to has no value".to_string()))?;
        let from = self.to_int_value(from_val)?;
        let then_v = self.to_int_value(then_val)?;
        let to = self.to_int_value(to_val)?;

        let tm = self.type_mapper();
        let i64_type = tm.i64_type();
        let current_fn = self
            .builder()
            .get_insert_block()
            .and_then(|b| b.get_parent())
            .ok_or_else(|| CodegenError::Internal("no current function".to_string()))?;

        // step = then - from
        let step = self
            .builder()
            .build_int_sub(then_v, from, "step")
            .map_err(|e| CodegenError::Internal(format!("enumFromThenTo: sub: {:?}", e)))?;

        // We need to handle both positive and negative steps.
        // For positive step: iterate while current <= to
        // For negative step: iterate while current >= to
        // We'll compute the number of elements, then build the list backwards.

        // Check step direction
        let step_positive = self
            .builder()
            .build_int_compare(
                inkwell::IntPredicate::SGT,
                step,
                i64_type.const_zero(),
                "step_pos",
            )
            .map_err(|e| CodegenError::Internal(format!("enumFromThenTo: cmp: {:?}", e)))?;

        // Build blocks
        let loop_header = self.llvm_ctx.append_basic_block(current_fn, "eftt_header");
        let loop_body = self.llvm_ctx.append_basic_block(current_fn, "eftt_body");
        let loop_exit = self.llvm_ctx.append_basic_block(current_fn, "eftt_exit");

        let nil = self.build_nil()?;
        let entry_block = self.builder().get_insert_block().unwrap();
        self.builder()
            .build_unconditional_branch(loop_header)
            .map_err(|e| CodegenError::Internal(format!("enumFromThenTo: branch: {:?}", e)))?;

        self.builder().position_at_end(loop_header);
        let acc_phi = self
            .builder()
            .build_phi(tm.ptr_type(), "acc")
            .map_err(|e| CodegenError::Internal(format!("enumFromThenTo: phi: {:?}", e)))?;
        let current_phi = self
            .builder()
            .build_phi(i64_type, "current")
            .map_err(|e| CodegenError::Internal(format!("enumFromThenTo: phi: {:?}", e)))?;

        let current = current_phi.as_basic_value().into_int_value();

        // For positive step: done when current > to
        let over_pos = self
            .builder()
            .build_int_compare(inkwell::IntPredicate::SGT, current, to, "over_pos")
            .map_err(|e| CodegenError::Internal(format!("enumFromThenTo: cmp: {:?}", e)))?;
        // For negative step: done when current < to
        let over_neg = self
            .builder()
            .build_int_compare(inkwell::IntPredicate::SLT, current, to, "over_neg")
            .map_err(|e| CodegenError::Internal(format!("enumFromThenTo: cmp: {:?}", e)))?;
        // Select based on step direction
        let done = self
            .builder()
            .build_select(step_positive, over_pos, over_neg, "done")
            .map_err(|e| CodegenError::Internal(format!("enumFromThenTo: sel: {:?}", e)))?;
        self.builder()
            .build_conditional_branch(done.into_int_value(), loop_exit, loop_body)
            .map_err(|e| CodegenError::Internal(format!("enumFromThenTo: branch: {:?}", e)))?;

        self.builder().position_at_end(loop_body);
        let boxed = self.box_int(current)?;
        // Build list in forward order: cons current onto end, but we need reverse.
        // Actually, build forward by consing and then reversing. Or build in reverse:
        // Walk forward, consing each element. The list will be in reverse order.
        // Then reverse at the end.
        let new_acc = self.build_cons(boxed.into(), acc_phi.as_basic_value())?;
        let next = self
            .builder()
            .build_int_add(current, step, "next")
            .map_err(|e| CodegenError::Internal(format!("enumFromThenTo: add: {:?}", e)))?;
        self.builder()
            .build_unconditional_branch(loop_header)
            .map_err(|e| CodegenError::Internal(format!("enumFromThenTo: branch: {:?}", e)))?;

        acc_phi.add_incoming(&[(&nil, entry_block), (&new_acc, loop_body)]);
        current_phi.add_incoming(&[(&from, entry_block), (&next, loop_body)]);

        self.builder().position_at_end(loop_exit);
        // The list is reversed (we consed in forward order), so reverse it
        let reversed =
            self.build_inline_reverse(acc_phi.as_basic_value().into_pointer_value(), current_fn)?;
        Ok(reversed)
    }

    /// Lower `foldl1` - left fold with no initial value (uses head of list).
    fn lower_builtin_foldl1(
        &mut self,
        func_expr: &Expr,
        list_expr: &Expr,
    ) -> CodegenResult<Option<BasicValueEnum<'ctx>>> {
        let func_val = self
            .lower_expr(func_expr)?
            .ok_or_else(|| CodegenError::Internal("foldl1: function has no value".to_string()))?;
        let func_ptr = match func_val {
            BasicValueEnum::PointerValue(p) => p,
            _ => {
                return Err(CodegenError::TypeError(
                    "foldl1: function must be a closure".to_string(),
                ))
            }
        };
        let list_val = self
            .lower_expr(list_expr)?
            .ok_or_else(|| CodegenError::Internal("foldl1: list has no value".to_string()))?;
        let list_ptr = match list_val {
            BasicValueEnum::PointerValue(p) => p,
            _ => return Err(CodegenError::TypeError("foldl1 expects a list".to_string())),
        };

        let tm = self.type_mapper();
        let ptr_type = tm.ptr_type();
        let current_fn = self
            .builder()
            .get_insert_block()
            .and_then(|b| b.get_parent())
            .ok_or_else(|| CodegenError::Internal("no current function".to_string()))?;

        // Extract head as initial accumulator, tail as list to fold
        let head = self.extract_adt_field(list_ptr, 2, 0)?;
        let tail = self.extract_adt_field(list_ptr, 2, 1)?;

        // Now do standard foldl loop on tail with head as init
        let entry_block = self.builder().get_insert_block().unwrap();
        let loop_header = self
            .llvm_ctx
            .append_basic_block(current_fn, "foldl1_header");
        let loop_body = self.llvm_ctx.append_basic_block(current_fn, "foldl1_body");
        let loop_exit = self.llvm_ctx.append_basic_block(current_fn, "foldl1_exit");

        self.builder()
            .build_unconditional_branch(loop_header)
            .map_err(|e| CodegenError::Internal(format!("foldl1: branch: {:?}", e)))?;

        self.builder().position_at_end(loop_header);
        let acc_phi = self
            .builder()
            .build_phi(ptr_type, "foldl1_acc")
            .map_err(|e| CodegenError::Internal(format!("foldl1: phi: {:?}", e)))?;
        let list_phi = self
            .builder()
            .build_phi(ptr_type, "foldl1_list")
            .map_err(|e| CodegenError::Internal(format!("foldl1: phi: {:?}", e)))?;

        let tag = self.extract_adt_tag(list_phi.as_basic_value().into_pointer_value())?;
        let is_empty = self
            .builder()
            .build_int_compare(
                inkwell::IntPredicate::EQ,
                tag,
                tm.i64_type().const_zero(),
                "is_empty",
            )
            .map_err(|e| CodegenError::Internal(format!("foldl1: cmp: {:?}", e)))?;
        self.builder()
            .build_conditional_branch(is_empty, loop_exit, loop_body)
            .map_err(|e| CodegenError::Internal(format!("foldl1: branch: {:?}", e)))?;

        self.builder().position_at_end(loop_body);
        let head_ptr =
            self.extract_adt_field(list_phi.as_basic_value().into_pointer_value(), 2, 0)?;
        let tail_ptr =
            self.extract_adt_field(list_phi.as_basic_value().into_pointer_value(), 2, 1)?;

        // Call f acc head - flat 3-arg call: fn_ptr(closure_ptr, acc, head)
        let fn_ptr = self.extract_closure_fn_ptr(func_ptr)?;
        let fn_type = ptr_type.fn_type(&[ptr_type.into(), ptr_type.into(), ptr_type.into()], false);
        let new_acc = self
            .builder()
            .build_indirect_call(
                fn_type,
                fn_ptr,
                &[
                    func_ptr.into(),
                    acc_phi.as_basic_value().into(),
                    head_ptr.into(),
                ],
                "foldl1_new_acc",
            )
            .map_err(|e| CodegenError::Internal(format!("foldl1: call: {:?}", e)))?
            .try_as_basic_value()
            .basic()
            .ok_or_else(|| CodegenError::Internal("foldl1: function returned void".to_string()))?;

        self.builder()
            .build_unconditional_branch(loop_header)
            .map_err(|e| CodegenError::Internal(format!("foldl1: branch: {:?}", e)))?;

        acc_phi.add_incoming(&[(&head, entry_block), (&new_acc, loop_body)]);
        list_phi.add_incoming(&[(&tail, entry_block), (&tail_ptr, loop_body)]);

        self.builder().position_at_end(loop_exit);
        Ok(Some(acc_phi.as_basic_value()))
    }

    /// Lower `foldr1` - right fold with no initial value.
    /// Implementation: reverse list, then foldl1.
    fn lower_builtin_foldr1(
        &mut self,
        func_expr: &Expr,
        list_expr: &Expr,
    ) -> CodegenResult<Option<BasicValueEnum<'ctx>>> {
        let func_val = self
            .lower_expr(func_expr)?
            .ok_or_else(|| CodegenError::Internal("foldr1: function has no value".to_string()))?;
        let func_ptr = match func_val {
            BasicValueEnum::PointerValue(p) => p,
            _ => {
                return Err(CodegenError::TypeError(
                    "foldr1: function must be a closure".to_string(),
                ))
            }
        };
        let list_val = self
            .lower_expr(list_expr)?
            .ok_or_else(|| CodegenError::Internal("foldr1: list has no value".to_string()))?;
        let list_ptr = match list_val {
            BasicValueEnum::PointerValue(p) => p,
            _ => return Err(CodegenError::TypeError("foldr1 expects a list".to_string())),
        };

        let ptr_type = self.type_mapper().ptr_type();
        let i64_zero = self.type_mapper().i64_type().const_zero();
        let current_fn = self
            .builder()
            .get_insert_block()
            .and_then(|b| b.get_parent())
            .ok_or_else(|| CodegenError::Internal("no current function".to_string()))?;

        // Reverse the list first
        let reversed = self
            .build_inline_reverse(list_ptr, current_fn)?
            .ok_or_else(|| CodegenError::Internal("foldr1: reverse returned None".to_string()))?;
        let reversed_ptr = reversed.into_pointer_value();

        // Extract head of reversed list as init, tail as list to fold
        let head = self.extract_adt_field(reversed_ptr, 2, 0)?;
        let tail = self.extract_adt_field(reversed_ptr, 2, 1)?;

        // foldl on reversed list with flipped function args
        let entry_block = self.builder().get_insert_block().unwrap();
        let loop_header = self
            .llvm_ctx
            .append_basic_block(current_fn, "foldr1_header");
        let loop_body = self.llvm_ctx.append_basic_block(current_fn, "foldr1_body");
        let loop_exit = self.llvm_ctx.append_basic_block(current_fn, "foldr1_exit");

        self.builder()
            .build_unconditional_branch(loop_header)
            .map_err(|e| CodegenError::Internal(format!("foldr1: branch: {:?}", e)))?;

        self.builder().position_at_end(loop_header);
        let acc_phi = self
            .builder()
            .build_phi(ptr_type, "foldr1_acc")
            .map_err(|e| CodegenError::Internal(format!("foldr1: phi: {:?}", e)))?;
        let list_phi = self
            .builder()
            .build_phi(ptr_type, "foldr1_list")
            .map_err(|e| CodegenError::Internal(format!("foldr1: phi: {:?}", e)))?;

        let tag = self.extract_adt_tag(list_phi.as_basic_value().into_pointer_value())?;
        let is_empty = self
            .builder()
            .build_int_compare(inkwell::IntPredicate::EQ, tag, i64_zero, "is_empty")
            .map_err(|e| CodegenError::Internal(format!("foldr1: cmp: {:?}", e)))?;
        self.builder()
            .build_conditional_branch(is_empty, loop_exit, loop_body)
            .map_err(|e| CodegenError::Internal(format!("foldr1: branch: {:?}", e)))?;

        self.builder().position_at_end(loop_body);
        let head_ptr =
            self.extract_adt_field(list_phi.as_basic_value().into_pointer_value(), 2, 0)?;
        let tail_ptr =
            self.extract_adt_field(list_phi.as_basic_value().into_pointer_value(), 2, 1)?;

        // For foldr1: f elem acc (not f acc elem like foldl)
        let fn_ptr = self.extract_closure_fn_ptr(func_ptr)?;
        let fn_type = ptr_type.fn_type(&[ptr_type.into(), ptr_type.into(), ptr_type.into()], false);
        let new_acc = self
            .builder()
            .build_indirect_call(
                fn_type,
                fn_ptr,
                &[
                    func_ptr.into(),
                    head_ptr.into(),
                    acc_phi.as_basic_value().into(),
                ],
                "foldr1_new_acc",
            )
            .map_err(|e| CodegenError::Internal(format!("foldr1: call: {:?}", e)))?
            .try_as_basic_value()
            .basic()
            .ok_or_else(|| CodegenError::Internal("foldr1: function returned void".to_string()))?;

        self.builder()
            .build_unconditional_branch(loop_header)
            .map_err(|e| CodegenError::Internal(format!("foldr1: branch: {:?}", e)))?;

        acc_phi.add_incoming(&[(&head, entry_block), (&new_acc, loop_body)]);
        list_phi.add_incoming(&[(&tail, entry_block), (&tail_ptr, loop_body)]);

        self.builder().position_at_end(loop_exit);
        Ok(Some(acc_phi.as_basic_value()))
    }

    /// Lower `comparing` - comparing f x y = compare (f x) (f y).
    fn lower_builtin_comparing(
        &mut self,
        f_expr: &Expr,
        x_expr: &Expr,
        y_expr: &Expr,
    ) -> CodegenResult<Option<BasicValueEnum<'ctx>>> {
        let f_val = self
            .lower_expr(f_expr)?
            .ok_or_else(|| CodegenError::Internal("comparing: f has no value".to_string()))?;
        let f_ptr = match f_val {
            BasicValueEnum::PointerValue(p) => p,
            _ => {
                return Err(CodegenError::TypeError(
                    "comparing: f must be a closure".to_string(),
                ))
            }
        };
        let x_val = self
            .lower_expr(x_expr)?
            .ok_or_else(|| CodegenError::Internal("comparing: x has no value".to_string()))?;
        let y_val = self
            .lower_expr(y_expr)?
            .ok_or_else(|| CodegenError::Internal("comparing: y has no value".to_string()))?;

        let ptr_type = self.type_mapper().ptr_type();
        let i64_type = self.type_mapper().i64_type();
        let x_ptr = self.value_to_ptr(x_val)?;
        let y_ptr = self.value_to_ptr(y_val)?;

        // Call f(x) and f(y)
        let fn_ptr = self.extract_closure_fn_ptr(f_ptr)?;
        let fn_type = ptr_type.fn_type(&[ptr_type.into(), ptr_type.into()], false);

        let fx = self
            .builder()
            .build_indirect_call(fn_type, fn_ptr, &[f_ptr.into(), x_ptr.into()], "fx")
            .map_err(|e| CodegenError::Internal(format!("comparing: fx call: {:?}", e)))?
            .try_as_basic_value()
            .basic()
            .ok_or_else(|| CodegenError::Internal("comparing: fx returned void".to_string()))?;

        let fy = self
            .builder()
            .build_indirect_call(fn_type, fn_ptr, &[f_ptr.into(), y_ptr.into()], "fy")
            .map_err(|e| CodegenError::Internal(format!("comparing: fy call: {:?}", e)))?
            .try_as_basic_value()
            .basic()
            .ok_or_else(|| CodegenError::Internal("comparing: fy returned void".to_string()))?;

        // Inline compare: coerce to int, then SLT/SGT chain -> Ordering ADT
        let fx_int = self.coerce_to_int(fx)?;
        let fy_int = self.coerce_to_int(fy)?;

        let is_lt = self
            .builder()
            .build_int_compare(inkwell::IntPredicate::SLT, fx_int, fy_int, "is_lt")
            .map_err(|e| CodegenError::Internal(format!("comparing: lt: {:?}", e)))?;
        let is_gt = self
            .builder()
            .build_int_compare(inkwell::IntPredicate::SGT, fx_int, fy_int, "is_gt")
            .map_err(|e| CodegenError::Internal(format!("comparing: gt: {:?}", e)))?;
        let tag_gt_or_eq = self
            .builder()
            .build_select(
                is_gt,
                i64_type.const_int(2, false),
                i64_type.const_int(1, false),
                "gt_or_eq",
            )
            .map_err(|e| CodegenError::Internal(format!("comparing: sel1: {:?}", e)))?;
        let tag = self
            .builder()
            .build_select(
                is_lt,
                i64_type.const_int(0, false),
                tag_gt_or_eq.into_int_value(),
                "cmp_tag",
            )
            .map_err(|e| CodegenError::Internal(format!("comparing: sel2: {:?}", e)))?;
        self.allocate_ordering_adt(tag.into_int_value(), "comparing")
    }

    /// Lower `until` - until p f x = if p x then x else until p f (f x).
    fn lower_builtin_until(
        &mut self,
        pred_expr: &Expr,
        func_expr: &Expr,
        init_expr: &Expr,
    ) -> CodegenResult<Option<BasicValueEnum<'ctx>>> {
        let pred_val = self
            .lower_expr(pred_expr)?
            .ok_or_else(|| CodegenError::Internal("until: pred has no value".to_string()))?;
        let pred_ptr = match pred_val {
            BasicValueEnum::PointerValue(p) => p,
            _ => {
                return Err(CodegenError::TypeError(
                    "until: pred must be a closure".to_string(),
                ))
            }
        };
        let func_val = self
            .lower_expr(func_expr)?
            .ok_or_else(|| CodegenError::Internal("until: func has no value".to_string()))?;
        let func_ptr = match func_val {
            BasicValueEnum::PointerValue(p) => p,
            _ => {
                return Err(CodegenError::TypeError(
                    "until: func must be a closure".to_string(),
                ))
            }
        };
        let init_val = self
            .lower_expr(init_expr)?
            .ok_or_else(|| CodegenError::Internal("until: init has no value".to_string()))?;
        let init_ptr = self.value_to_ptr(init_val)?;

        let tm = self.type_mapper();
        let ptr_type = tm.ptr_type();
        let current_fn = self
            .builder()
            .get_insert_block()
            .and_then(|b| b.get_parent())
            .ok_or_else(|| CodegenError::Internal("no current function".to_string()))?;

        let entry_block = self.builder().get_insert_block().unwrap();
        let loop_header = self.llvm_ctx.append_basic_block(current_fn, "until_header");
        let loop_body = self.llvm_ctx.append_basic_block(current_fn, "until_body");
        let loop_exit = self.llvm_ctx.append_basic_block(current_fn, "until_exit");

        self.builder()
            .build_unconditional_branch(loop_header)
            .map_err(|e| CodegenError::Internal(format!("until: branch: {:?}", e)))?;

        self.builder().position_at_end(loop_header);
        let val_phi = self
            .builder()
            .build_phi(ptr_type, "until_val")
            .map_err(|e| CodegenError::Internal(format!("until: phi: {:?}", e)))?;

        // Call p(val) -> Bool ADT
        let pred_fn_ptr = self.extract_closure_fn_ptr(pred_ptr)?;
        let pred_fn_type = ptr_type.fn_type(&[ptr_type.into(), ptr_type.into()], false);
        let pred_result = self
            .builder()
            .build_indirect_call(
                pred_fn_type,
                pred_fn_ptr,
                &[pred_ptr.into(), val_phi.as_basic_value().into()],
                "pred_result",
            )
            .map_err(|e| CodegenError::Internal(format!("until: pred call: {:?}", e)))?
            .try_as_basic_value()
            .basic()
            .ok_or_else(|| CodegenError::Internal("until: pred returned void".to_string()))?;

        // Check if predicate returned True (non-zero value)
        // Comparison operators return tagged-int-as-pointer, so use ptr_to_int
        let pred_bool = self
            .builder()
            .build_ptr_to_int(pred_result.into_pointer_value(), tm.i64_type(), "pred_bool")
            .map_err(|e| CodegenError::Internal(format!("until: ptr_to_int: {:?}", e)))?;
        let is_true = self
            .builder()
            .build_int_compare(
                inkwell::IntPredicate::NE,
                pred_bool,
                tm.i64_type().const_zero(),
                "is_true",
            )
            .map_err(|e| CodegenError::Internal(format!("until: cmp: {:?}", e)))?;
        self.builder()
            .build_conditional_branch(is_true, loop_exit, loop_body)
            .map_err(|e| CodegenError::Internal(format!("until: branch: {:?}", e)))?;

        // Loop body: apply f to current val
        self.builder().position_at_end(loop_body);
        let func_fn_ptr = self.extract_closure_fn_ptr(func_ptr)?;
        let func_fn_type = ptr_type.fn_type(&[ptr_type.into(), ptr_type.into()], false);
        let new_val = self
            .builder()
            .build_indirect_call(
                func_fn_type,
                func_fn_ptr,
                &[func_ptr.into(), val_phi.as_basic_value().into()],
                "new_val",
            )
            .map_err(|e| CodegenError::Internal(format!("until: func call: {:?}", e)))?
            .try_as_basic_value()
            .basic()
            .ok_or_else(|| CodegenError::Internal("until: func returned void".to_string()))?;

        self.builder()
            .build_unconditional_branch(loop_header)
            .map_err(|e| CodegenError::Internal(format!("until: branch: {:?}", e)))?;

        val_phi.add_incoming(&[(&init_ptr, entry_block), (&new_val, loop_body)]);

        self.builder().position_at_end(loop_exit);
        Ok(Some(val_phi.as_basic_value()))
    }

    /// Lower `getChar` - read a single character from stdin.
    fn lower_builtin_get_char(&mut self) -> CodegenResult<Option<BasicValueEnum<'ctx>>> {
        let rts_fn = self
            .functions
            .get(&VarId::new(1000560))
            .ok_or_else(|| CodegenError::Internal("bhc_getChar not declared".to_string()))?;
        let result = self
            .builder()
            .build_call(*rts_fn, &[], "getchar_result")
            .map_err(|e| CodegenError::Internal(format!("getChar call failed: {:?}", e)))?
            .try_as_basic_value()
            .basic()
            .ok_or_else(|| CodegenError::Internal("getChar: returned void".to_string()))?;
        // Result is i64 char code, convert to pointer (Char representation)
        Ok(Some(self.int_to_ptr(result.into_int_value())?.into()))
    }

    /// Lower `isEOF` - check if stdin is at EOF.
    fn lower_builtin_is_eof(&mut self) -> CodegenResult<Option<BasicValueEnum<'ctx>>> {
        let rts_fn = self
            .functions
            .get(&VarId::new(1000561))
            .ok_or_else(|| CodegenError::Internal("bhc_isEOF not declared".to_string()))?;
        let result = self
            .builder()
            .build_call(*rts_fn, &[], "iseof_result")
            .map_err(|e| CodegenError::Internal(format!("isEOF call failed: {:?}", e)))?
            .try_as_basic_value()
            .basic()
            .ok_or_else(|| CodegenError::Internal("isEOF: returned void".to_string()))?;
        // Result is i64 (0 or 1), wrap as Bool ADT
        self.allocate_bool_adt(result.into_int_value(), "iseof")
    }

    /// Lower `getContents` - read all of stdin.
    fn lower_builtin_get_contents(&mut self) -> CodegenResult<Option<BasicValueEnum<'ctx>>> {
        let rts_fn = self
            .functions
            .get(&VarId::new(1000562))
            .ok_or_else(|| CodegenError::Internal("bhc_getContents not declared".to_string()))?;
        let result = self
            .builder()
            .build_call(*rts_fn, &[], "getcontents_result")
            .map_err(|e| CodegenError::Internal(format!("getContents call failed: {:?}", e)))?
            .try_as_basic_value()
            .basic()
            .ok_or_else(|| CodegenError::Internal("getContents: returned void".to_string()))?;
        // Result is *mut c_char, convert to [Char] list
        let char_list = self.cstring_to_char_list(result.into_pointer_value())?;
        Ok(Some(char_list.into()))
    }

    /// Lower `interact` - interact f = getContents >>= putStr . f
    fn lower_builtin_interact(
        &mut self,
        func_expr: &Expr,
    ) -> CodegenResult<Option<BasicValueEnum<'ctx>>> {
        let func_val = self
            .lower_expr(func_expr)?
            .ok_or_else(|| CodegenError::Internal("interact: func has no value".to_string()))?;
        let func_ptr = match func_val {
            BasicValueEnum::PointerValue(p) => p,
            _ => {
                return Err(CodegenError::TypeError(
                    "interact: func must be a closure".to_string(),
                ))
            }
        };

        let ptr_type = self.type_mapper().ptr_type();

        // Step 1: Call getContents to get input as [Char]
        let rts_fn = self
            .functions
            .get(&VarId::new(1000562))
            .ok_or_else(|| CodegenError::Internal("bhc_getContents not declared".to_string()))?;
        let input_cstr = self
            .builder()
            .build_call(*rts_fn, &[], "interact_input")
            .map_err(|e| CodegenError::Internal(format!("interact: getContents: {:?}", e)))?
            .try_as_basic_value()
            .basic()
            .ok_or_else(|| {
                CodegenError::Internal("interact: getContents returned void".to_string())
            })?;
        let input_list = self.cstring_to_char_list(input_cstr.into_pointer_value())?;

        // Step 2: Call f(input) via closure call
        let fn_ptr = self.extract_closure_fn_ptr(func_ptr)?;
        let fn_type = ptr_type.fn_type(&[ptr_type.into(), ptr_type.into()], false);
        let output = self
            .builder()
            .build_indirect_call(
                fn_type,
                fn_ptr,
                &[func_ptr.into(), input_list.into()],
                "interact_output",
            )
            .map_err(|e| CodegenError::Internal(format!("interact: f call: {:?}", e)))?
            .try_as_basic_value()
            .basic()
            .ok_or_else(|| CodegenError::Internal("interact: f returned void".to_string()))?;

        // Step 3: Print the output string (putStr pattern)
        let output_ptr = match output {
            BasicValueEnum::PointerValue(p) => p,
            _ => {
                return Err(CodegenError::TypeError(
                    "interact: output must be string".to_string(),
                ))
            }
        };
        self.lower_print_char_list_ptr(output_ptr)?;

        // Return unit
        Ok(Some(ptr_type.const_null().into()))
    }

    /// Emit a loop that walks a [Char] linked list and prints each character.
    /// Takes a pre-evaluated PointerValue instead of an Expr.
    fn lower_print_char_list_ptr(
        &mut self,
        list_ptr: inkwell::values::PointerValue<'ctx>,
    ) -> CodegenResult<()> {
        let tm = self.type_mapper();
        let current_fn = self
            .builder()
            .get_insert_block()
            .and_then(|b| b.get_parent())
            .ok_or_else(|| CodegenError::Internal("no current function".to_string()))?;

        let print_char_fn = self
            .functions
            .get(&VarId::new(1000009))
            .ok_or_else(|| CodegenError::Internal("bhc_print_char not declared".to_string()))?;

        let loop_header = self.llvm_ctx.append_basic_block(current_fn, "iprint_loop");
        let loop_body = self.llvm_ctx.append_basic_block(current_fn, "iprint_body");
        let loop_end = self.llvm_ctx.append_basic_block(current_fn, "iprint_done");

        let pre_loop_block = self.builder().get_insert_block().unwrap();
        self.builder()
            .build_unconditional_branch(loop_header)
            .map_err(|e| CodegenError::Internal(format!("iprint: branch: {:?}", e)))?;

        self.builder().position_at_end(loop_header);
        let current_node = self
            .builder()
            .build_phi(tm.ptr_type(), "cur_node")
            .map_err(|e| CodegenError::Internal(format!("iprint: phi: {:?}", e)))?;
        current_node.add_incoming(&[(&list_ptr, pre_loop_block)]);

        let tag = self.extract_adt_tag(current_node.as_basic_value().into_pointer_value())?;
        let is_nil = self
            .builder()
            .build_int_compare(
                inkwell::IntPredicate::EQ,
                tag,
                tm.i64_type().const_zero(),
                "is_nil",
            )
            .map_err(|e| CodegenError::Internal(format!("iprint: cmp: {:?}", e)))?;
        self.builder()
            .build_conditional_branch(is_nil, loop_end, loop_body)
            .map_err(|e| CodegenError::Internal(format!("iprint: branch: {:?}", e)))?;

        self.builder().position_at_end(loop_body);
        // Extract head (Char) at field 0. The char is stored boxed (via
        // int_to_ptr), so convert back to an int and truncate to i32 — which is
        // what bhc_print_char expects. (Passing the i64 directly fails LLVM
        // verification.)
        let head =
            self.extract_adt_field(current_node.as_basic_value().into_pointer_value(), 2, 0)?;
        let char_val = self
            .builder()
            .build_ptr_to_int(head, tm.i64_type(), "char_val")
            .map_err(|e| CodegenError::Internal(format!("iprint: char ptr_to_int: {:?}", e)))?;
        let char_i32 = self
            .builder()
            .build_int_truncate(char_val, tm.i32_type(), "char_i32")
            .map_err(|e| CodegenError::Internal(format!("iprint: char truncate: {:?}", e)))?;
        self.builder()
            .build_call(*print_char_fn, &[char_i32.into()], "")
            .map_err(|e| CodegenError::Internal(format!("iprint: call: {:?}", e)))?;
        let next =
            self.extract_adt_field(current_node.as_basic_value().into_pointer_value(), 2, 1)?;
        current_node.add_incoming(&[(&next, loop_body)]);
        self.builder()
            .build_unconditional_branch(loop_header)
            .map_err(|e| CodegenError::Internal(format!("iprint: branch: {:?}", e)))?;

        self.builder().position_at_end(loop_end);
        Ok(())
    }

    /// Lower `(&)` - reverse function application: (&) x f = f x.
    fn lower_builtin_reverse_apply(
        &mut self,
        val_expr: &Expr,
        func_expr: &Expr,
    ) -> CodegenResult<Option<BasicValueEnum<'ctx>>> {
        let val = self
            .lower_expr(val_expr)?
            .ok_or_else(|| CodegenError::Internal("&: no value".to_string()))?;
        let func_val = self
            .lower_expr(func_expr)?
            .ok_or_else(|| CodegenError::Internal("&: no function".to_string()))?;
        let func_ptr = match func_val {
            BasicValueEnum::PointerValue(p) => p,
            _ => {
                return Err(CodegenError::TypeError(
                    "&: function must be closure".to_string(),
                ))
            }
        };
        let ptr_type = self.type_mapper().ptr_type();
        let val_ptr = self.value_to_ptr(val)?;
        let fn_ptr = self.extract_closure_fn_ptr(func_ptr)?;
        let fn_type = ptr_type.fn_type(&[ptr_type.into(), ptr_type.into()], false);
        let result = self
            .builder()
            .build_indirect_call(
                fn_type,
                fn_ptr,
                &[func_ptr.into(), val_ptr.into()],
                "rev_apply_result",
            )
            .map_err(|e| CodegenError::Internal(format!("& call failed: {:?}", e)))?
            .try_as_basic_value()
            .basic()
            .ok_or_else(|| CodegenError::Internal("&: returned void".to_string()))?;
        Ok(Some(result))
    }

    /// Lower `swap` - swap elements of a pair: swap (a, b) = (b, a).
    fn lower_builtin_swap(
        &mut self,
        pair_expr: &Expr,
    ) -> CodegenResult<Option<BasicValueEnum<'ctx>>> {
        let pair_val = self
            .lower_expr(pair_expr)?
            .ok_or_else(|| CodegenError::Internal("swap: pair has no value".to_string()))?;
        let pair_ptr = match pair_val {
            BasicValueEnum::PointerValue(p) => p,
            _ => return Err(CodegenError::TypeError("swap expects a tuple".to_string())),
        };

        // Extract field 0 (fst) and field 1 (snd)
        let fst_val = self.extract_adt_field(pair_ptr, 2, 0)?;
        let snd_val = self.extract_adt_field(pair_ptr, 2, 1)?;

        // Allocate new tuple with swapped fields: (snd, fst)
        self.allocate_ptr_pair_tuple(snd_val, fst_val, "swap")
    }

    /// Allocate a (ptr, ptr) tuple: 24 bytes, tag=0, fst at offset 8, snd at offset 16.
    /// Like allocate_int_pair_tuple but takes pointer values instead of int values.
    fn allocate_ptr_pair_tuple(
        &mut self,
        fst: PointerValue<'ctx>,
        snd: PointerValue<'ctx>,
        name: &str,
    ) -> CodegenResult<Option<BasicValueEnum<'ctx>>> {
        let i64_type = self.type_mapper().i64_type();
        let ptr_type = self.type_mapper().ptr_type();
        let size_val = i64_type.const_int(24, false);
        let alloc_fn = self
            .functions
            .get(&VarId::new(1000005))
            .ok_or_else(|| CodegenError::Internal("bhc_alloc not declared".to_string()))?;
        let raw_ptr = self
            .builder()
            .build_call(*alloc_fn, &[size_val.into()], &format!("{}_alloc", name))
            .map_err(|e| CodegenError::Internal(format!("{}: alloc failed: {:?}", name, e)))?
            .try_as_basic_value()
            .basic()
            .ok_or_else(|| CodegenError::Internal(format!("{}: alloc returned void", name)))?;
        let tuple_ptr = raw_ptr.into_pointer_value();

        // Store tag=0 at offset 0
        let adt_ty = self.adt_type(0);
        let tag_ptr = self
            .builder()
            .build_struct_gep(adt_ty, tuple_ptr, 0, &format!("{}_tag", name))
            .map_err(|e| CodegenError::Internal(format!("{}: tag gep: {:?}", name, e)))?;
        self.builder()
            .build_store(tag_ptr, i64_type.const_zero())
            .map_err(|e| CodegenError::Internal(format!("{}: tag store: {:?}", name, e)))?;

        // Store fst at offset 8
        let field1_ptr = self
            .builder()
            .build_struct_gep(adt_ty, tuple_ptr, 1, &format!("{}_fst", name))
            .map_err(|e| CodegenError::Internal(format!("{}: fst gep: {:?}", name, e)))?;
        self.builder()
            .build_store(field1_ptr, fst)
            .map_err(|e| CodegenError::Internal(format!("{}: fst store: {:?}", name, e)))?;

        // Store snd at offset 16 (raw GEP, same pattern as allocate_int_pair_tuple)
        let field2_gep = unsafe {
            self.builder()
                .build_gep(
                    ptr_type,
                    tuple_ptr,
                    &[i64_type.const_int(2, false)],
                    &format!("{}_snd_gep", name),
                )
                .map_err(|e| CodegenError::Internal(format!("{}: snd gep: {:?}", name, e)))?
        };
        self.builder()
            .build_store(field2_gep, snd)
            .map_err(|e| CodegenError::Internal(format!("{}: snd store: {:?}", name, e)))?;

        Ok(Some(tuple_ptr.into()))
    }

    /// Lower `curry f x y = f (x, y)`.
    fn lower_builtin_curry(
        &mut self,
        f_expr: &Expr,
        x_expr: &Expr,
        y_expr: &Expr,
    ) -> CodegenResult<Option<BasicValueEnum<'ctx>>> {
        let f_val = self
            .lower_expr(f_expr)?
            .ok_or_else(|| CodegenError::Internal("curry: no f".to_string()))?;
        let x_val = self
            .lower_expr(x_expr)?
            .ok_or_else(|| CodegenError::Internal("curry: no x".to_string()))?;
        let y_val = self
            .lower_expr(y_expr)?
            .ok_or_else(|| CodegenError::Internal("curry: no y".to_string()))?;
        let f_ptr = match f_val {
            BasicValueEnum::PointerValue(p) => p,
            _ => {
                return Err(CodegenError::TypeError(
                    "curry: f must be closure".to_string(),
                ))
            }
        };
        let ptr_type = self.type_mapper().ptr_type();

        // Allocate tuple (x, y)
        let x_ptr = self.value_to_ptr(x_val)?;
        let y_ptr = self.value_to_ptr(y_val)?;
        let tuple_val = self
            .allocate_ptr_pair_tuple(x_ptr, y_ptr, "curry")?
            .ok_or_else(|| CodegenError::Internal("curry: tuple alloc void".to_string()))?;
        let tuple_ptr = tuple_val.into_pointer_value();

        // Call f(tuple)
        let fn_ptr = self.extract_closure_fn_ptr(f_ptr)?;
        let fn_type = ptr_type.fn_type(&[ptr_type.into(), ptr_type.into()], false);
        let result = self
            .builder()
            .build_indirect_call(
                fn_type,
                fn_ptr,
                &[f_ptr.into(), tuple_ptr.into()],
                "curry_result",
            )
            .map_err(|e| CodegenError::Internal(format!("curry: call failed: {:?}", e)))?
            .try_as_basic_value()
            .basic()
            .ok_or_else(|| CodegenError::Internal("curry: returned void".to_string()))?;
        Ok(Some(result))
    }

    /// Lower `uncurry f (x, y) = f x y`.
    fn lower_builtin_uncurry(
        &mut self,
        f_expr: &Expr,
        pair_expr: &Expr,
    ) -> CodegenResult<Option<BasicValueEnum<'ctx>>> {
        let f_val = self
            .lower_expr(f_expr)?
            .ok_or_else(|| CodegenError::Internal("uncurry: no f".to_string()))?;
        let pair_val = self
            .lower_expr(pair_expr)?
            .ok_or_else(|| CodegenError::Internal("uncurry: no pair".to_string()))?;
        let f_ptr = match f_val {
            BasicValueEnum::PointerValue(p) => p,
            _ => {
                return Err(CodegenError::TypeError(
                    "uncurry: f must be closure".to_string(),
                ))
            }
        };
        let pair_ptr = match pair_val {
            BasicValueEnum::PointerValue(p) => p,
            _ => {
                return Err(CodegenError::TypeError(
                    "uncurry: pair must be tuple".to_string(),
                ))
            }
        };

        // Extract fst and snd from pair
        let fst_val = self.extract_adt_field(pair_ptr, 2, 0)?;
        let snd_val = self.extract_adt_field(pair_ptr, 2, 1)?;

        // Call f(fst, snd) — flat 3-arg call: (closure_env, arg1, arg2)
        // BHC compiles 2-arg functions as flat fn(env, x, y) -> result
        let ptr_type = self.type_mapper().ptr_type();
        let fn_type = ptr_type.fn_type(&[ptr_type.into(), ptr_type.into(), ptr_type.into()], false);
        let fn_ptr = self.extract_closure_fn_ptr(f_ptr)?;
        let result = self
            .builder()
            .build_indirect_call(
                fn_type,
                fn_ptr,
                &[f_ptr.into(), fst_val.into(), snd_val.into()],
                "uncurry_result",
            )
            .map_err(|e| CodegenError::Internal(format!("uncurry: call failed: {:?}", e)))?
            .try_as_basic_value()
            .basic()
            .ok_or_else(|| CodegenError::Internal("uncurry: result void".to_string()))?;
        Ok(Some(result))
    }

    // ========================================================================
    // Phase 2: Advanced List Operation Handlers (stubs and implementations)
    // ========================================================================

    /// Lower `any`.
    fn lower_builtin_any(
        &mut self,
        pred_expr: &Expr,
        list_expr: &Expr,
    ) -> CodegenResult<Option<BasicValueEnum<'ctx>>> {
        // Lower predicate closure and list
        let pred_val = self
            .lower_expr(pred_expr)?
            .ok_or_else(|| CodegenError::Internal("any: pred has no value".to_string()))?;
        let pred_ptr = match pred_val {
            BasicValueEnum::PointerValue(p) => p,
            _ => {
                return Err(CodegenError::TypeError(
                    "any: pred must be a closure".to_string(),
                ))
            }
        };
        let list_val = self
            .lower_expr(list_expr)?
            .ok_or_else(|| CodegenError::Internal("any: list has no value".to_string()))?;
        let list_ptr = match list_val {
            BasicValueEnum::PointerValue(p) => p,
            _ => return Err(CodegenError::TypeError("any expects a list".to_string())),
        };

        let ptr_type = self.type_mapper().ptr_type();
        let i64_zero = self.type_mapper().i64_type().const_zero();
        let current_fn = self
            .builder()
            .get_insert_block()
            .and_then(|b| b.get_parent())
            .ok_or_else(|| CodegenError::Internal("no current function".to_string()))?;
        let entry_block = self
            .builder()
            .get_insert_block()
            .ok_or_else(|| CodegenError::Internal("no current block".to_string()))?;

        let loop_header = self.llvm_ctx.append_basic_block(current_fn, "any_header");
        let loop_body = self.llvm_ctx.append_basic_block(current_fn, "any_body");
        let return_true = self.llvm_ctx.append_basic_block(current_fn, "any_true");
        let return_false = self.llvm_ctx.append_basic_block(current_fn, "any_false");
        let merge_block = self.llvm_ctx.append_basic_block(current_fn, "any_merge");

        self.builder()
            .build_unconditional_branch(loop_header)
            .map_err(|e| CodegenError::Internal(format!("any: branch: {:?}", e)))?;

        // Loop header: check if list is empty
        self.builder().position_at_end(loop_header);
        let list_phi = self
            .builder()
            .build_phi(ptr_type, "any_list")
            .map_err(|e| CodegenError::Internal(format!("any: phi: {:?}", e)))?;
        let tag = self.extract_adt_tag(list_phi.as_basic_value().into_pointer_value())?;
        let is_empty = self
            .builder()
            .build_int_compare(inkwell::IntPredicate::EQ, tag, i64_zero, "is_nil")
            .map_err(|e| CodegenError::Internal(format!("any: cmp: {:?}", e)))?;
        self.builder()
            .build_conditional_branch(is_empty, return_false, loop_body)
            .map_err(|e| CodegenError::Internal(format!("any: branch: {:?}", e)))?;

        // Loop body: extract head, call predicate, check result
        self.builder().position_at_end(loop_body);
        let head = self.extract_adt_field(list_phi.as_basic_value().into_pointer_value(), 2, 0)?;
        let tail = self.extract_adt_field(list_phi.as_basic_value().into_pointer_value(), 2, 1)?;

        // Call pred(head)
        let fn_ptr = self.extract_closure_fn_ptr(pred_ptr)?;
        let fn_type = ptr_type.fn_type(&[ptr_type.into(), ptr_type.into()], false);
        let result = self
            .builder()
            .build_indirect_call(fn_type, fn_ptr, &[pred_ptr.into(), head.into()], "any_call")
            .map_err(|e| CodegenError::Internal(format!("any: call: {:?}", e)))?
            .try_as_basic_value()
            .basic()
            .ok_or_else(|| CodegenError::Internal("any: pred returned void".to_string()))?;

        // Check if result is True (Bool tag != 0). Use the discriminating
        // `extract_bool_tag`: the predicate may return a tagged-int-as-pointer
        // (0/1) or a heap Bool ADT, and `extract_adt_tag` would dereference a
        // tagged value as a heap pointer and segfault.
        let result_int = match result {
            BasicValueEnum::PointerValue(p) => self.extract_bool_tag(p)?,
            BasicValueEnum::IntValue(i) => i,
            _ => {
                return Err(CodegenError::TypeError(
                    "any: pred should return Bool".to_string(),
                ))
            }
        };
        let is_true = self
            .builder()
            .build_int_compare(inkwell::IntPredicate::NE, result_int, i64_zero, "pred_true")
            .map_err(|e| CodegenError::Internal(format!("any: cmp: {:?}", e)))?;
        // `extract_bool_tag` may have split the body, so the loop back-edge's
        // actual predecessor is the current block, not `loop_body`.
        let body_end = self
            .builder()
            .get_insert_block()
            .ok_or_else(|| CodegenError::Internal("any: no current block".to_string()))?;
        self.builder()
            .build_conditional_branch(is_true, return_true, loop_header)
            .map_err(|e| CodegenError::Internal(format!("any: branch: {:?}", e)))?;

        list_phi.add_incoming(&[(&list_ptr, entry_block), (&tail, body_end)]);

        let true_tag = self.type_mapper().i64_type().const_int(1, false);
        let false_tag = self.type_mapper().i64_type().const_zero();

        // Return True
        self.builder().position_at_end(return_true);
        let true_bool = self
            .allocate_bool_adt(true_tag, "any_true")?
            .ok_or_else(|| CodegenError::Internal("any: alloc true failed".to_string()))?;
        self.builder()
            .build_unconditional_branch(merge_block)
            .map_err(|e| CodegenError::Internal(format!("any: branch: {:?}", e)))?;

        // Return False
        self.builder().position_at_end(return_false);
        let false_bool = self
            .allocate_bool_adt(false_tag, "any_false")?
            .ok_or_else(|| CodegenError::Internal("any: alloc false failed".to_string()))?;
        self.builder()
            .build_unconditional_branch(merge_block)
            .map_err(|e| CodegenError::Internal(format!("any: branch: {:?}", e)))?;

        // Merge
        self.builder().position_at_end(merge_block);
        let result_phi = self
            .builder()
            .build_phi(ptr_type, "any_result")
            .map_err(|e| CodegenError::Internal(format!("any: phi: {:?}", e)))?;
        result_phi.add_incoming(&[
            (&true_bool.into_pointer_value(), return_true),
            (&false_bool.into_pointer_value(), return_false),
        ]);

        Ok(Some(result_phi.as_basic_value()))
    }

    /// Lower `all`.
    fn lower_builtin_all(
        &mut self,
        pred_expr: &Expr,
        list_expr: &Expr,
    ) -> CodegenResult<Option<BasicValueEnum<'ctx>>> {
        // Lower predicate closure and list
        let pred_val = self
            .lower_expr(pred_expr)?
            .ok_or_else(|| CodegenError::Internal("all: pred has no value".to_string()))?;
        let pred_ptr = match pred_val {
            BasicValueEnum::PointerValue(p) => p,
            _ => {
                return Err(CodegenError::TypeError(
                    "all: pred must be a closure".to_string(),
                ))
            }
        };
        let list_val = self
            .lower_expr(list_expr)?
            .ok_or_else(|| CodegenError::Internal("all: list has no value".to_string()))?;
        let list_ptr = match list_val {
            BasicValueEnum::PointerValue(p) => p,
            _ => return Err(CodegenError::TypeError("all expects a list".to_string())),
        };

        let ptr_type = self.type_mapper().ptr_type();
        let i64_zero = self.type_mapper().i64_type().const_zero();
        let current_fn = self
            .builder()
            .get_insert_block()
            .and_then(|b| b.get_parent())
            .ok_or_else(|| CodegenError::Internal("no current function".to_string()))?;
        let entry_block = self
            .builder()
            .get_insert_block()
            .ok_or_else(|| CodegenError::Internal("no current block".to_string()))?;

        let loop_header = self.llvm_ctx.append_basic_block(current_fn, "all_header");
        let loop_body = self.llvm_ctx.append_basic_block(current_fn, "all_body");
        let return_true = self.llvm_ctx.append_basic_block(current_fn, "all_true");
        let return_false = self.llvm_ctx.append_basic_block(current_fn, "all_false");
        let merge_block = self.llvm_ctx.append_basic_block(current_fn, "all_merge");

        self.builder()
            .build_unconditional_branch(loop_header)
            .map_err(|e| CodegenError::Internal(format!("all: branch: {:?}", e)))?;

        // Loop header: check if list is empty
        self.builder().position_at_end(loop_header);
        let list_phi = self
            .builder()
            .build_phi(ptr_type, "all_list")
            .map_err(|e| CodegenError::Internal(format!("all: phi: {:?}", e)))?;
        let tag = self.extract_adt_tag(list_phi.as_basic_value().into_pointer_value())?;
        let is_empty = self
            .builder()
            .build_int_compare(inkwell::IntPredicate::EQ, tag, i64_zero, "is_nil")
            .map_err(|e| CodegenError::Internal(format!("all: cmp: {:?}", e)))?;
        self.builder()
            .build_conditional_branch(is_empty, return_true, loop_body)
            .map_err(|e| CodegenError::Internal(format!("all: branch: {:?}", e)))?;

        // Loop body: extract head, call predicate, check result
        self.builder().position_at_end(loop_body);
        let head = self.extract_adt_field(list_phi.as_basic_value().into_pointer_value(), 2, 0)?;
        let tail = self.extract_adt_field(list_phi.as_basic_value().into_pointer_value(), 2, 1)?;

        // Call pred(head)
        let fn_ptr = self.extract_closure_fn_ptr(pred_ptr)?;
        let fn_type = ptr_type.fn_type(&[ptr_type.into(), ptr_type.into()], false);
        let result = self
            .builder()
            .build_indirect_call(fn_type, fn_ptr, &[pred_ptr.into(), head.into()], "all_call")
            .map_err(|e| CodegenError::Internal(format!("all: call: {:?}", e)))?
            .try_as_basic_value()
            .basic()
            .ok_or_else(|| CodegenError::Internal("all: pred returned void".to_string()))?;

        // Check if result is False (Bool tag == 0) → short-circuit. The
        // predicate may return a tagged-int-as-pointer (0/1, e.g. a `==`
        // comparison) or a heap Bool ADT, so use the discriminating
        // `extract_bool_tag` — `extract_adt_tag` would dereference a small
        // tagged value as a heap pointer and segfault.
        let result_int = match result {
            BasicValueEnum::PointerValue(p) => self.extract_bool_tag(p)?,
            BasicValueEnum::IntValue(i) => i,
            _ => {
                return Err(CodegenError::TypeError(
                    "all: pred should return Bool".to_string(),
                ))
            }
        };
        let is_false = self
            .builder()
            .build_int_compare(
                inkwell::IntPredicate::EQ,
                result_int,
                i64_zero,
                "pred_false",
            )
            .map_err(|e| CodegenError::Internal(format!("all: cmp: {:?}", e)))?;
        // `extract_bool_tag` may have split the body into extra blocks, so the
        // loop back-edge's actual predecessor is the current block, not
        // `loop_body`.
        let body_end = self
            .builder()
            .get_insert_block()
            .ok_or_else(|| CodegenError::Internal("all: no current block".to_string()))?;
        self.builder()
            .build_conditional_branch(is_false, return_false, loop_header)
            .map_err(|e| CodegenError::Internal(format!("all: branch: {:?}", e)))?;

        list_phi.add_incoming(&[(&list_ptr, entry_block), (&tail, body_end)]);

        let true_tag = self.type_mapper().i64_type().const_int(1, false);
        let false_tag = self.type_mapper().i64_type().const_zero();

        // Return True (all elements passed)
        self.builder().position_at_end(return_true);
        let true_bool = self
            .allocate_bool_adt(true_tag, "all_true")?
            .ok_or_else(|| CodegenError::Internal("all: alloc true failed".to_string()))?;
        self.builder()
            .build_unconditional_branch(merge_block)
            .map_err(|e| CodegenError::Internal(format!("all: branch: {:?}", e)))?;

        // Return False (short-circuited)
        self.builder().position_at_end(return_false);
        let false_bool = self
            .allocate_bool_adt(false_tag, "all_false")?
            .ok_or_else(|| CodegenError::Internal("all: alloc false failed".to_string()))?;
        self.builder()
            .build_unconditional_branch(merge_block)
            .map_err(|e| CodegenError::Internal(format!("all: branch: {:?}", e)))?;

        // Merge
        self.builder().position_at_end(merge_block);
        let result_phi = self
            .builder()
            .build_phi(ptr_type, "all_result")
            .map_err(|e| CodegenError::Internal(format!("all: phi: {:?}", e)))?;
        result_phi.add_incoming(&[
            (&true_bool.into_pointer_value(), return_true),
            (&false_bool.into_pointer_value(), return_false),
        ]);

        Ok(Some(result_phi.as_basic_value()))
    }

    /// Lower `and` - conjunction of a list of Bools. Short-circuits on False.
    fn lower_builtin_and(
        &mut self,
        list_expr: &Expr,
    ) -> CodegenResult<Option<BasicValueEnum<'ctx>>> {
        // and [] = True; and (x:xs) = if x then and xs else False
        let list_val = self
            .lower_expr(list_expr)?
            .ok_or_else(|| CodegenError::Internal("and: list has no value".to_string()))?;
        let list_ptr = match list_val {
            BasicValueEnum::PointerValue(p) => p,
            _ => return Err(CodegenError::TypeError("and expects a list".to_string())),
        };

        let ptr_type = self.type_mapper().ptr_type();
        let i64_type = self.type_mapper().i64_type();
        let i64_zero = i64_type.const_zero();
        let current_fn = self
            .builder()
            .get_insert_block()
            .and_then(|b| b.get_parent())
            .ok_or_else(|| CodegenError::Internal("no current function".to_string()))?;
        let entry_block = self
            .builder()
            .get_insert_block()
            .ok_or_else(|| CodegenError::Internal("no current block".to_string()))?;

        let loop_header = self.llvm_ctx.append_basic_block(current_fn, "and_header");
        let loop_body = self.llvm_ctx.append_basic_block(current_fn, "and_body");
        let return_true = self.llvm_ctx.append_basic_block(current_fn, "and_true");
        let return_false = self.llvm_ctx.append_basic_block(current_fn, "and_false");
        let merge_block = self.llvm_ctx.append_basic_block(current_fn, "and_merge");

        self.builder()
            .build_unconditional_branch(loop_header)
            .map_err(|e| CodegenError::Internal(format!("and: branch: {:?}", e)))?;

        // Loop header: check if list is empty
        self.builder().position_at_end(loop_header);
        let list_phi = self
            .builder()
            .build_phi(ptr_type, "and_list")
            .map_err(|e| CodegenError::Internal(format!("and: phi: {:?}", e)))?;
        let tag = self.extract_adt_tag(list_phi.as_basic_value().into_pointer_value())?;
        let is_empty = self
            .builder()
            .build_int_compare(inkwell::IntPredicate::EQ, tag, i64_zero, "is_nil")
            .map_err(|e| CodegenError::Internal(format!("and: cmp: {:?}", e)))?;
        self.builder()
            .build_conditional_branch(is_empty, return_true, loop_body)
            .map_err(|e| CodegenError::Internal(format!("and: branch: {:?}", e)))?;

        // Loop body: extract head Bool, check its tag
        self.builder().position_at_end(loop_body);
        let head = self.extract_adt_field(list_phi.as_basic_value().into_pointer_value(), 2, 0)?;
        let tail = self.extract_adt_field(list_phi.as_basic_value().into_pointer_value(), 2, 1)?;
        // head is a Bool ADT - extract its tag (0=False, 1=True)
        let head_tag = self.extract_adt_tag(head)?;
        let is_false = self
            .builder()
            .build_int_compare(inkwell::IntPredicate::EQ, head_tag, i64_zero, "is_false")
            .map_err(|e| CodegenError::Internal(format!("and: cmp: {:?}", e)))?;
        self.builder()
            .build_conditional_branch(is_false, return_false, loop_header)
            .map_err(|e| CodegenError::Internal(format!("and: branch: {:?}", e)))?;

        list_phi.add_incoming(&[(&list_ptr, entry_block), (&tail, loop_body)]);

        let true_tag = i64_type.const_int(1, false);
        let false_tag = i64_type.const_zero();

        // Return True (all elements were True)
        self.builder().position_at_end(return_true);
        let true_bool = self
            .allocate_bool_adt(true_tag, "and_true")?
            .ok_or_else(|| CodegenError::Internal("and: alloc true failed".to_string()))?;
        self.builder()
            .build_unconditional_branch(merge_block)
            .map_err(|e| CodegenError::Internal(format!("and: branch: {:?}", e)))?;

        // Return False (found a False element)
        self.builder().position_at_end(return_false);
        let false_bool = self
            .allocate_bool_adt(false_tag, "and_false")?
            .ok_or_else(|| CodegenError::Internal("and: alloc false failed".to_string()))?;
        self.builder()
            .build_unconditional_branch(merge_block)
            .map_err(|e| CodegenError::Internal(format!("and: branch: {:?}", e)))?;

        // Merge
        self.builder().position_at_end(merge_block);
        let result_phi = self
            .builder()
            .build_phi(ptr_type, "and_result")
            .map_err(|e| CodegenError::Internal(format!("and: phi: {:?}", e)))?;
        result_phi.add_incoming(&[
            (&true_bool.into_pointer_value(), return_true),
            (&false_bool.into_pointer_value(), return_false),
        ]);

        Ok(Some(result_phi.as_basic_value()))
    }

    /// Lower `or` - disjunction of a list of Bools. Short-circuits on True.
    fn lower_builtin_or(
        &mut self,
        list_expr: &Expr,
    ) -> CodegenResult<Option<BasicValueEnum<'ctx>>> {
        // or [] = False; or (x:xs) = if x then True else or xs
        let list_val = self
            .lower_expr(list_expr)?
            .ok_or_else(|| CodegenError::Internal("or: list has no value".to_string()))?;
        let list_ptr = match list_val {
            BasicValueEnum::PointerValue(p) => p,
            _ => return Err(CodegenError::TypeError("or expects a list".to_string())),
        };

        let ptr_type = self.type_mapper().ptr_type();
        let i64_type = self.type_mapper().i64_type();
        let i64_zero = i64_type.const_zero();
        let current_fn = self
            .builder()
            .get_insert_block()
            .and_then(|b| b.get_parent())
            .ok_or_else(|| CodegenError::Internal("no current function".to_string()))?;
        let entry_block = self
            .builder()
            .get_insert_block()
            .ok_or_else(|| CodegenError::Internal("no current block".to_string()))?;

        let loop_header = self.llvm_ctx.append_basic_block(current_fn, "or_header");
        let loop_body = self.llvm_ctx.append_basic_block(current_fn, "or_body");
        let return_true = self.llvm_ctx.append_basic_block(current_fn, "or_true");
        let return_false = self.llvm_ctx.append_basic_block(current_fn, "or_false");
        let merge_block = self.llvm_ctx.append_basic_block(current_fn, "or_merge");

        self.builder()
            .build_unconditional_branch(loop_header)
            .map_err(|e| CodegenError::Internal(format!("or: branch: {:?}", e)))?;

        // Loop header: check if list is empty
        self.builder().position_at_end(loop_header);
        let list_phi = self
            .builder()
            .build_phi(ptr_type, "or_list")
            .map_err(|e| CodegenError::Internal(format!("or: phi: {:?}", e)))?;
        let tag = self.extract_adt_tag(list_phi.as_basic_value().into_pointer_value())?;
        let is_empty = self
            .builder()
            .build_int_compare(inkwell::IntPredicate::EQ, tag, i64_zero, "is_nil")
            .map_err(|e| CodegenError::Internal(format!("or: cmp: {:?}", e)))?;
        self.builder()
            .build_conditional_branch(is_empty, return_false, loop_body)
            .map_err(|e| CodegenError::Internal(format!("or: branch: {:?}", e)))?;

        // Loop body: extract head Bool, check its tag
        self.builder().position_at_end(loop_body);
        let head = self.extract_adt_field(list_phi.as_basic_value().into_pointer_value(), 2, 0)?;
        let tail = self.extract_adt_field(list_phi.as_basic_value().into_pointer_value(), 2, 1)?;
        // head is a Bool ADT - extract its tag (0=False, 1=True)
        let head_tag = self.extract_adt_tag(head)?;
        let is_true = self
            .builder()
            .build_int_compare(inkwell::IntPredicate::NE, head_tag, i64_zero, "is_true")
            .map_err(|e| CodegenError::Internal(format!("or: cmp: {:?}", e)))?;
        self.builder()
            .build_conditional_branch(is_true, return_true, loop_header)
            .map_err(|e| CodegenError::Internal(format!("or: branch: {:?}", e)))?;

        list_phi.add_incoming(&[(&list_ptr, entry_block), (&tail, loop_body)]);

        let true_tag = i64_type.const_int(1, false);
        let false_tag = i64_type.const_zero();

        // Return True (found a True element)
        self.builder().position_at_end(return_true);
        let true_bool = self
            .allocate_bool_adt(true_tag, "or_true")?
            .ok_or_else(|| CodegenError::Internal("or: alloc true failed".to_string()))?;
        self.builder()
            .build_unconditional_branch(merge_block)
            .map_err(|e| CodegenError::Internal(format!("or: branch: {:?}", e)))?;

        // Return False (all elements were False)
        self.builder().position_at_end(return_false);
        let false_bool = self
            .allocate_bool_adt(false_tag, "or_false")?
            .ok_or_else(|| CodegenError::Internal("or: alloc false failed".to_string()))?;
        self.builder()
            .build_unconditional_branch(merge_block)
            .map_err(|e| CodegenError::Internal(format!("or: branch: {:?}", e)))?;

        // Merge
        self.builder().position_at_end(merge_block);
        let result_phi = self
            .builder()
            .build_phi(ptr_type, "or_result")
            .map_err(|e| CodegenError::Internal(format!("or: phi: {:?}", e)))?;
        result_phi.add_incoming(&[
            (&true_bool.into_pointer_value(), return_true),
            (&false_bool.into_pointer_value(), return_false),
        ]);

        Ok(Some(result_phi.as_basic_value()))
    }

    /// Lower `maximum` - find the maximum element in a non-empty list.
    fn lower_builtin_maximum(
        &mut self,
        list_expr: &Expr,
    ) -> CodegenResult<Option<BasicValueEnum<'ctx>>> {
        self.lower_builtin_max_min_impl(list_expr, inkwell::IntPredicate::SGT, "maximum")
    }

    /// Lower `minimum` - find the minimum element in a non-empty list.
    fn lower_builtin_minimum(
        &mut self,
        list_expr: &Expr,
    ) -> CodegenResult<Option<BasicValueEnum<'ctx>>> {
        self.lower_builtin_max_min_impl(list_expr, inkwell::IntPredicate::SLT, "minimum")
    }

    /// Shared implementation for `maximum` and `minimum`.
    fn lower_builtin_max_min_impl(
        &mut self,
        list_expr: &Expr,
        cmp_pred: inkwell::IntPredicate,
        label: &str,
    ) -> CodegenResult<Option<BasicValueEnum<'ctx>>> {
        let list_val = self
            .lower_expr(list_expr)?
            .ok_or_else(|| CodegenError::Internal(format!("{}: list has no value", label)))?;
        let list_ptr = match list_val {
            BasicValueEnum::PointerValue(p) => p,
            _ => return Err(CodegenError::TypeError(format!("{} expects a list", label))),
        };

        let tm = self.type_mapper();
        let ptr_type = tm.ptr_type();
        let i64_type = tm.i64_type();
        let current_fn = self
            .builder()
            .get_insert_block()
            .and_then(|b| b.get_parent())
            .ok_or_else(|| CodegenError::Internal("no current function".to_string()))?;
        let _entry_block = self
            .builder()
            .get_insert_block()
            .ok_or_else(|| CodegenError::Internal("no current block".to_string()))?;

        // Blocks: extract head as initial best, then loop over tail
        let check_nonempty = self
            .llvm_ctx
            .append_basic_block(current_fn, &format!("{}_check", label));
        let loop_header = self
            .llvm_ctx
            .append_basic_block(current_fn, &format!("{}_header", label));
        let loop_body = self
            .llvm_ctx
            .append_basic_block(current_fn, &format!("{}_body", label));
        let loop_exit = self
            .llvm_ctx
            .append_basic_block(current_fn, &format!("{}_exit", label));

        self.builder()
            .build_unconditional_branch(check_nonempty)
            .map_err(|e| CodegenError::Internal(format!("{}: branch: {:?}", label, e)))?;

        // Check non-empty: extract head as initial best, tail as rest
        self.builder().position_at_end(check_nonempty);
        let head = self.extract_adt_field(list_ptr, 2, 0)?;
        let tail = self.extract_adt_field(list_ptr, 2, 1)?;
        self.builder()
            .build_unconditional_branch(loop_header)
            .map_err(|e| CodegenError::Internal(format!("{}: branch: {:?}", label, e)))?;

        // Loop header: phi for best and current list
        self.builder().position_at_end(loop_header);
        let best_phi = self
            .builder()
            .build_phi(ptr_type, &format!("{}_best", label))
            .map_err(|e| CodegenError::Internal(format!("{}: phi: {:?}", label, e)))?;
        let list_phi = self
            .builder()
            .build_phi(ptr_type, &format!("{}_list", label))
            .map_err(|e| CodegenError::Internal(format!("{}: phi: {:?}", label, e)))?;

        let tag = self.extract_adt_tag(list_phi.as_basic_value().into_pointer_value())?;
        let is_empty = self
            .builder()
            .build_int_compare(
                inkwell::IntPredicate::EQ,
                tag,
                i64_type.const_zero(),
                "is_nil",
            )
            .map_err(|e| CodegenError::Internal(format!("{}: cmp: {:?}", label, e)))?;
        self.builder()
            .build_conditional_branch(is_empty, loop_exit, loop_body)
            .map_err(|e| CodegenError::Internal(format!("{}: branch: {:?}", label, e)))?;

        // Loop body: extract head, compare with best
        self.builder().position_at_end(loop_body);
        let cur_head =
            self.extract_adt_field(list_phi.as_basic_value().into_pointer_value(), 2, 0)?;
        let cur_tail =
            self.extract_adt_field(list_phi.as_basic_value().into_pointer_value(), 2, 1)?;

        let head_int = self
            .builder()
            .build_ptr_to_int(cur_head, i64_type, "head_int")
            .map_err(|e| CodegenError::Internal(format!("{}: p2i: {:?}", label, e)))?;
        let best_int = self
            .builder()
            .build_ptr_to_int(
                best_phi.as_basic_value().into_pointer_value(),
                i64_type,
                "best_int",
            )
            .map_err(|e| CodegenError::Internal(format!("{}: p2i: {:?}", label, e)))?;
        let is_better = self
            .builder()
            .build_int_compare(cmp_pred, head_int, best_int, "is_better")
            .map_err(|e| CodegenError::Internal(format!("{}: cmp: {:?}", label, e)))?;

        // Select new best
        let new_best = self
            .builder()
            .build_select(
                is_better,
                cur_head,
                best_phi.as_basic_value().into_pointer_value(),
                "new_best",
            )
            .map_err(|e| CodegenError::Internal(format!("{}: select: {:?}", label, e)))?;
        self.builder()
            .build_unconditional_branch(loop_header)
            .map_err(|e| CodegenError::Internal(format!("{}: branch: {:?}", label, e)))?;

        // Phi incoming
        best_phi.add_incoming(&[
            (&head, check_nonempty),
            (&new_best.into_pointer_value(), loop_body),
        ]);
        list_phi.add_incoming(&[(&tail, check_nonempty), (&cur_tail, loop_body)]);

        // Exit: return best
        self.builder().position_at_end(loop_exit);
        Ok(Some(best_phi.as_basic_value()))
    }

    /// Lower `elemIndex val xs` - return Just index if found, Nothing otherwise.
    fn lower_builtin_elem_index(
        &mut self,
        val_expr: &Expr,
        list_expr: &Expr,
    ) -> CodegenResult<Option<BasicValueEnum<'ctx>>> {
        let val_val = self
            .lower_expr(val_expr)?
            .ok_or_else(|| CodegenError::Internal("elemIndex: value has no value".to_string()))?;
        let val_ptr = self.value_to_ptr(val_val)?;
        let list_val = self
            .lower_expr(list_expr)?
            .ok_or_else(|| CodegenError::Internal("elemIndex: list has no value".to_string()))?;
        let list_ptr = match list_val {
            BasicValueEnum::PointerValue(p) => p,
            _ => {
                return Err(CodegenError::TypeError(
                    "elemIndex expects a list".to_string(),
                ))
            }
        };

        let tm = self.type_mapper();
        let ptr_type = tm.ptr_type();
        let i64_type = tm.i64_type();
        let current_fn = self
            .builder()
            .get_insert_block()
            .and_then(|b| b.get_parent())
            .ok_or_else(|| CodegenError::Internal("no current function".to_string()))?;
        let entry_block = self
            .builder()
            .get_insert_block()
            .ok_or_else(|| CodegenError::Internal("no current block".to_string()))?;

        let loop_header = self.llvm_ctx.append_basic_block(current_fn, "ei_header");
        let loop_body = self.llvm_ctx.append_basic_block(current_fn, "ei_body");
        let found_block = self.llvm_ctx.append_basic_block(current_fn, "ei_found");
        let not_found_block = self.llvm_ctx.append_basic_block(current_fn, "ei_not_found");
        let merge_block = self.llvm_ctx.append_basic_block(current_fn, "ei_merge");

        let val_int = self
            .builder()
            .build_ptr_to_int(val_ptr, i64_type, "val_int")
            .map_err(|e| CodegenError::Internal(format!("elemIndex: p2i: {:?}", e)))?;

        self.builder()
            .build_unconditional_branch(loop_header)
            .map_err(|e| CodegenError::Internal(format!("elemIndex: branch: {:?}", e)))?;

        // Loop header: phi for list and counter
        self.builder().position_at_end(loop_header);
        let list_phi = self
            .builder()
            .build_phi(ptr_type, "ei_list")
            .map_err(|e| CodegenError::Internal(format!("elemIndex: phi: {:?}", e)))?;
        let counter_phi = self
            .builder()
            .build_phi(i64_type, "ei_counter")
            .map_err(|e| CodegenError::Internal(format!("elemIndex: phi: {:?}", e)))?;

        let tag = self.extract_adt_tag(list_phi.as_basic_value().into_pointer_value())?;
        let is_empty = self
            .builder()
            .build_int_compare(
                inkwell::IntPredicate::EQ,
                tag,
                i64_type.const_zero(),
                "is_nil",
            )
            .map_err(|e| CodegenError::Internal(format!("elemIndex: cmp: {:?}", e)))?;
        self.builder()
            .build_conditional_branch(is_empty, not_found_block, loop_body)
            .map_err(|e| CodegenError::Internal(format!("elemIndex: branch: {:?}", e)))?;

        // Loop body: compare head with val
        self.builder().position_at_end(loop_body);
        let head = self.extract_adt_field(list_phi.as_basic_value().into_pointer_value(), 2, 0)?;
        let tail = self.extract_adt_field(list_phi.as_basic_value().into_pointer_value(), 2, 1)?;
        let head_int = self
            .builder()
            .build_ptr_to_int(head, i64_type, "head_int")
            .map_err(|e| CodegenError::Internal(format!("elemIndex: p2i: {:?}", e)))?;
        let is_equal = self
            .builder()
            .build_int_compare(inkwell::IntPredicate::EQ, head_int, val_int, "is_eq")
            .map_err(|e| CodegenError::Internal(format!("elemIndex: cmp: {:?}", e)))?;
        let next_counter = self
            .builder()
            .build_int_add(
                counter_phi.as_basic_value().into_int_value(),
                i64_type.const_int(1, false),
                "next_idx",
            )
            .map_err(|e| CodegenError::Internal(format!("elemIndex: add: {:?}", e)))?;
        self.builder()
            .build_conditional_branch(is_equal, found_block, loop_header)
            .map_err(|e| CodegenError::Internal(format!("elemIndex: branch: {:?}", e)))?;

        list_phi.add_incoming(&[(&list_ptr, entry_block), (&tail, loop_body)]);
        counter_phi.add_incoming(&[
            (&i64_type.const_zero(), entry_block),
            (&next_counter, loop_body),
        ]);

        // Found: return Just counter (box counter as pointer, wrap in Just ADT)
        self.builder().position_at_end(found_block);
        let idx_ptr = self.int_to_ptr(counter_phi.as_basic_value().into_int_value())?;
        let just = self.alloc_adt(1, 1)?;
        self.store_adt_field(just, 1, 0, idx_ptr.into())?;
        self.builder()
            .build_unconditional_branch(merge_block)
            .map_err(|e| CodegenError::Internal(format!("elemIndex: branch: {:?}", e)))?;

        // Not found: return Nothing
        self.builder().position_at_end(not_found_block);
        let nothing = self.alloc_adt(0, 0)?;
        self.builder()
            .build_unconditional_branch(merge_block)
            .map_err(|e| CodegenError::Internal(format!("elemIndex: branch: {:?}", e)))?;

        // Merge
        self.builder().position_at_end(merge_block);
        let result_phi = self
            .builder()
            .build_phi(ptr_type, "ei_result")
            .map_err(|e| CodegenError::Internal(format!("elemIndex: phi: {:?}", e)))?;
        result_phi.add_incoming(&[(&just, found_block), (&nothing, not_found_block)]);

        Ok(Some(result_phi.as_basic_value()))
    }

    /// Lower `findIndex pred xs` - return Just index where pred is true, Nothing otherwise.
    fn lower_builtin_find_index(
        &mut self,
        pred_expr: &Expr,
        list_expr: &Expr,
    ) -> CodegenResult<Option<BasicValueEnum<'ctx>>> {
        let pred_val = self
            .lower_expr(pred_expr)?
            .ok_or_else(|| CodegenError::Internal("findIndex: pred has no value".to_string()))?;
        let pred_ptr = match pred_val {
            BasicValueEnum::PointerValue(p) => p,
            _ => {
                return Err(CodegenError::TypeError(
                    "findIndex: pred must be a closure".to_string(),
                ))
            }
        };
        let list_val = self
            .lower_expr(list_expr)?
            .ok_or_else(|| CodegenError::Internal("findIndex: list has no value".to_string()))?;
        let list_ptr = match list_val {
            BasicValueEnum::PointerValue(p) => p,
            _ => {
                return Err(CodegenError::TypeError(
                    "findIndex expects a list".to_string(),
                ))
            }
        };

        let tm = self.type_mapper();
        let ptr_type = tm.ptr_type();
        let i64_type = tm.i64_type();
        let current_fn = self
            .builder()
            .get_insert_block()
            .and_then(|b| b.get_parent())
            .ok_or_else(|| CodegenError::Internal("no current function".to_string()))?;
        let entry_block = self
            .builder()
            .get_insert_block()
            .ok_or_else(|| CodegenError::Internal("no current block".to_string()))?;

        let loop_header = self.llvm_ctx.append_basic_block(current_fn, "fi_header");
        let loop_body = self.llvm_ctx.append_basic_block(current_fn, "fi_body");
        let found_block = self.llvm_ctx.append_basic_block(current_fn, "fi_found");
        let not_found_block = self.llvm_ctx.append_basic_block(current_fn, "fi_not_found");
        let merge_block = self.llvm_ctx.append_basic_block(current_fn, "fi_merge");

        self.builder()
            .build_unconditional_branch(loop_header)
            .map_err(|e| CodegenError::Internal(format!("findIndex: branch: {:?}", e)))?;

        // Loop header: phi for list and counter
        self.builder().position_at_end(loop_header);
        let list_phi = self
            .builder()
            .build_phi(ptr_type, "fi_list")
            .map_err(|e| CodegenError::Internal(format!("findIndex: phi: {:?}", e)))?;
        let counter_phi = self
            .builder()
            .build_phi(i64_type, "fi_counter")
            .map_err(|e| CodegenError::Internal(format!("findIndex: phi: {:?}", e)))?;

        let tag = self.extract_adt_tag(list_phi.as_basic_value().into_pointer_value())?;
        let is_empty = self
            .builder()
            .build_int_compare(
                inkwell::IntPredicate::EQ,
                tag,
                i64_type.const_zero(),
                "is_nil",
            )
            .map_err(|e| CodegenError::Internal(format!("findIndex: cmp: {:?}", e)))?;
        self.builder()
            .build_conditional_branch(is_empty, not_found_block, loop_body)
            .map_err(|e| CodegenError::Internal(format!("findIndex: branch: {:?}", e)))?;

        // Loop body: call predicate on head
        self.builder().position_at_end(loop_body);
        let head = self.extract_adt_field(list_phi.as_basic_value().into_pointer_value(), 2, 0)?;
        let tail = self.extract_adt_field(list_phi.as_basic_value().into_pointer_value(), 2, 1)?;

        let fn_ptr = self.extract_closure_fn_ptr(pred_ptr)?;
        let fn_type = ptr_type.fn_type(&[ptr_type.into(), ptr_type.into()], false);
        let pred_result = self
            .builder()
            .build_indirect_call(fn_type, fn_ptr, &[pred_ptr.into(), head.into()], "fi_call")
            .map_err(|e| CodegenError::Internal(format!("findIndex: call: {:?}", e)))?
            .try_as_basic_value()
            .basic()
            .ok_or_else(|| CodegenError::Internal("findIndex: pred returned void".to_string()))?;

        // Check Bool result tag
        let result_tag = match pred_result {
            BasicValueEnum::PointerValue(p) => self.extract_adt_tag(p)?,
            BasicValueEnum::IntValue(i) => i,
            _ => {
                return Err(CodegenError::TypeError(
                    "findIndex: pred should return Bool".to_string(),
                ))
            }
        };
        let is_true = self
            .builder()
            .build_int_compare(
                inkwell::IntPredicate::NE,
                result_tag,
                i64_type.const_zero(),
                "pred_true",
            )
            .map_err(|e| CodegenError::Internal(format!("findIndex: cmp: {:?}", e)))?;
        let next_counter = self
            .builder()
            .build_int_add(
                counter_phi.as_basic_value().into_int_value(),
                i64_type.const_int(1, false),
                "next_idx",
            )
            .map_err(|e| CodegenError::Internal(format!("findIndex: add: {:?}", e)))?;
        self.builder()
            .build_conditional_branch(is_true, found_block, loop_header)
            .map_err(|e| CodegenError::Internal(format!("findIndex: branch: {:?}", e)))?;

        list_phi.add_incoming(&[(&list_ptr, entry_block), (&tail, loop_body)]);
        counter_phi.add_incoming(&[
            (&i64_type.const_zero(), entry_block),
            (&next_counter, loop_body),
        ]);

        // Found: return Just counter
        self.builder().position_at_end(found_block);
        let idx_ptr = self.int_to_ptr(counter_phi.as_basic_value().into_int_value())?;
        let just = self.alloc_adt(1, 1)?;
        self.store_adt_field(just, 1, 0, idx_ptr.into())?;
        self.builder()
            .build_unconditional_branch(merge_block)
            .map_err(|e| CodegenError::Internal(format!("findIndex: branch: {:?}", e)))?;

        // Not found: return Nothing
        self.builder().position_at_end(not_found_block);
        let nothing = self.alloc_adt(0, 0)?;
        self.builder()
            .build_unconditional_branch(merge_block)
            .map_err(|e| CodegenError::Internal(format!("findIndex: branch: {:?}", e)))?;

        // Merge
        self.builder().position_at_end(merge_block);
        let result_phi = self
            .builder()
            .build_phi(ptr_type, "fi_result")
            .map_err(|e| CodegenError::Internal(format!("findIndex: phi: {:?}", e)))?;
        result_phi.add_incoming(&[(&just, found_block), (&nothing, not_found_block)]);

        Ok(Some(result_phi.as_basic_value()))
    }

    /// Lower `isPrefixOf xs ys` - check if xs is a prefix of ys.
    fn lower_builtin_is_prefix_of(
        &mut self,
        prefix_expr: &Expr,
        list_expr: &Expr,
    ) -> CodegenResult<Option<BasicValueEnum<'ctx>>> {
        let prefix_val = self
            .lower_expr(prefix_expr)?
            .ok_or_else(|| CodegenError::Internal("isPrefixOf: prefix has no value".to_string()))?;
        let prefix_ptr = match prefix_val {
            BasicValueEnum::PointerValue(p) => p,
            _ => {
                return Err(CodegenError::TypeError(
                    "isPrefixOf expects a list".to_string(),
                ))
            }
        };
        let list_val = self
            .lower_expr(list_expr)?
            .ok_or_else(|| CodegenError::Internal("isPrefixOf: list has no value".to_string()))?;
        let list_ptr = match list_val {
            BasicValueEnum::PointerValue(p) => p,
            _ => {
                return Err(CodegenError::TypeError(
                    "isPrefixOf expects a list".to_string(),
                ))
            }
        };

        let ptr_type = self.type_mapper().ptr_type();
        let i64_type = self.type_mapper().i64_type();
        let i64_zero = i64_type.const_zero();
        let current_fn = self
            .builder()
            .get_insert_block()
            .and_then(|b| b.get_parent())
            .ok_or_else(|| CodegenError::Internal("no current function".to_string()))?;
        let entry_block = self
            .builder()
            .get_insert_block()
            .ok_or_else(|| CodegenError::Internal("no current block".to_string()))?;

        let loop_header = self.llvm_ctx.append_basic_block(current_fn, "ipf_header");
        let loop_body = self.llvm_ctx.append_basic_block(current_fn, "ipf_body");
        let return_true = self.llvm_ctx.append_basic_block(current_fn, "ipf_true");
        let return_false = self.llvm_ctx.append_basic_block(current_fn, "ipf_false");
        let merge_block = self.llvm_ctx.append_basic_block(current_fn, "ipf_merge");

        self.builder()
            .build_unconditional_branch(loop_header)
            .map_err(|e| CodegenError::Internal(format!("isPrefixOf: branch: {:?}", e)))?;

        // Loop header: phi for both lists
        self.builder().position_at_end(loop_header);
        let prefix_phi = self
            .builder()
            .build_phi(ptr_type, "ipf_prefix")
            .map_err(|e| CodegenError::Internal(format!("isPrefixOf: phi: {:?}", e)))?;
        let list_phi = self
            .builder()
            .build_phi(ptr_type, "ipf_list")
            .map_err(|e| CodegenError::Internal(format!("isPrefixOf: phi: {:?}", e)))?;

        // If prefix is exhausted, return True
        let prefix_tag = self.extract_adt_tag(prefix_phi.as_basic_value().into_pointer_value())?;
        let prefix_empty = self
            .builder()
            .build_int_compare(
                inkwell::IntPredicate::EQ,
                prefix_tag,
                i64_zero,
                "prefix_nil",
            )
            .map_err(|e| CodegenError::Internal(format!("isPrefixOf: cmp: {:?}", e)))?;
        self.builder()
            .build_conditional_branch(prefix_empty, return_true, loop_body)
            .map_err(|e| CodegenError::Internal(format!("isPrefixOf: branch: {:?}", e)))?;

        // Loop body: check if list is exhausted, then compare heads
        self.builder().position_at_end(loop_body);
        let list_tag = self.extract_adt_tag(list_phi.as_basic_value().into_pointer_value())?;
        let list_empty = self
            .builder()
            .build_int_compare(inkwell::IntPredicate::EQ, list_tag, i64_zero, "list_nil")
            .map_err(|e| CodegenError::Internal(format!("isPrefixOf: cmp: {:?}", e)))?;

        let compare_block = self.llvm_ctx.append_basic_block(current_fn, "ipf_compare");
        self.builder()
            .build_conditional_branch(list_empty, return_false, compare_block)
            .map_err(|e| CodegenError::Internal(format!("isPrefixOf: branch: {:?}", e)))?;

        // Compare heads
        self.builder().position_at_end(compare_block);
        let p_head =
            self.extract_adt_field(prefix_phi.as_basic_value().into_pointer_value(), 2, 0)?;
        let p_tail =
            self.extract_adt_field(prefix_phi.as_basic_value().into_pointer_value(), 2, 1)?;
        let l_head =
            self.extract_adt_field(list_phi.as_basic_value().into_pointer_value(), 2, 0)?;
        let l_tail =
            self.extract_adt_field(list_phi.as_basic_value().into_pointer_value(), 2, 1)?;

        let p_int = self
            .builder()
            .build_ptr_to_int(p_head, i64_type, "p_int")
            .map_err(|e| CodegenError::Internal(format!("isPrefixOf: p2i: {:?}", e)))?;
        let l_int = self
            .builder()
            .build_ptr_to_int(l_head, i64_type, "l_int")
            .map_err(|e| CodegenError::Internal(format!("isPrefixOf: p2i: {:?}", e)))?;
        let heads_eq = self
            .builder()
            .build_int_compare(inkwell::IntPredicate::EQ, p_int, l_int, "heads_eq")
            .map_err(|e| CodegenError::Internal(format!("isPrefixOf: cmp: {:?}", e)))?;
        self.builder()
            .build_conditional_branch(heads_eq, loop_header, return_false)
            .map_err(|e| CodegenError::Internal(format!("isPrefixOf: branch: {:?}", e)))?;

        prefix_phi.add_incoming(&[(&prefix_ptr, entry_block), (&p_tail, compare_block)]);
        list_phi.add_incoming(&[(&list_ptr, entry_block), (&l_tail, compare_block)]);

        let true_tag = i64_type.const_int(1, false);
        let false_tag = i64_type.const_zero();

        // Return True
        self.builder().position_at_end(return_true);
        let true_bool = self
            .allocate_bool_adt(true_tag, "ipf_true")?
            .ok_or_else(|| CodegenError::Internal("isPrefixOf: alloc true failed".to_string()))?;
        self.builder()
            .build_unconditional_branch(merge_block)
            .map_err(|e| CodegenError::Internal(format!("isPrefixOf: branch: {:?}", e)))?;

        // Return False
        self.builder().position_at_end(return_false);
        let false_bool = self
            .allocate_bool_adt(false_tag, "ipf_false")?
            .ok_or_else(|| CodegenError::Internal("isPrefixOf: alloc false failed".to_string()))?;
        self.builder()
            .build_unconditional_branch(merge_block)
            .map_err(|e| CodegenError::Internal(format!("isPrefixOf: branch: {:?}", e)))?;

        // Merge
        self.builder().position_at_end(merge_block);
        let result_phi = self
            .builder()
            .build_phi(ptr_type, "ipf_result")
            .map_err(|e| CodegenError::Internal(format!("isPrefixOf: phi: {:?}", e)))?;
        result_phi.add_incoming(&[
            (&true_bool.into_pointer_value(), return_true),
            (&false_bool.into_pointer_value(), return_false),
        ]);

        Ok(Some(result_phi.as_basic_value()))
    }

    /// Lower `isSuffixOf xs ys` - reverse both lists, then check isPrefixOf.
    fn lower_builtin_is_suffix_of(
        &mut self,
        suffix_expr: &Expr,
        list_expr: &Expr,
    ) -> CodegenResult<Option<BasicValueEnum<'ctx>>> {
        let suffix_val = self
            .lower_expr(suffix_expr)?
            .ok_or_else(|| CodegenError::Internal("isSuffixOf: suffix has no value".to_string()))?;
        let suffix_ptr = match suffix_val {
            BasicValueEnum::PointerValue(p) => p,
            _ => {
                return Err(CodegenError::TypeError(
                    "isSuffixOf expects a list".to_string(),
                ))
            }
        };
        let list_val = self
            .lower_expr(list_expr)?
            .ok_or_else(|| CodegenError::Internal("isSuffixOf: list has no value".to_string()))?;
        let list_ptr = match list_val {
            BasicValueEnum::PointerValue(p) => p,
            _ => {
                return Err(CodegenError::TypeError(
                    "isSuffixOf expects a list".to_string(),
                ))
            }
        };

        let current_fn = self
            .builder()
            .get_insert_block()
            .and_then(|b| b.get_parent())
            .ok_or_else(|| CodegenError::Internal("no current function".to_string()))?;

        // Reverse both lists
        let rev_suffix = self
            .build_inline_reverse(suffix_ptr, current_fn)?
            .ok_or_else(|| CodegenError::Internal("isSuffixOf: reverse suffix failed".to_string()))?
            .into_pointer_value();
        let rev_list = self
            .build_inline_reverse(list_ptr, current_fn)?
            .ok_or_else(|| CodegenError::Internal("isSuffixOf: reverse list failed".to_string()))?
            .into_pointer_value();

        // Now do isPrefixOf logic inline on reversed lists
        let ptr_type = self.type_mapper().ptr_type();
        let i64_type = self.type_mapper().i64_type();
        let i64_zero = i64_type.const_zero();
        let pre_block = self
            .builder()
            .get_insert_block()
            .ok_or_else(|| CodegenError::Internal("no current block".to_string()))?;

        let loop_header = self.llvm_ctx.append_basic_block(current_fn, "isf_header");
        let loop_body = self.llvm_ctx.append_basic_block(current_fn, "isf_body");
        let compare_block = self.llvm_ctx.append_basic_block(current_fn, "isf_compare");
        let return_true = self.llvm_ctx.append_basic_block(current_fn, "isf_true");
        let return_false = self.llvm_ctx.append_basic_block(current_fn, "isf_false");
        let merge_block = self.llvm_ctx.append_basic_block(current_fn, "isf_merge");

        self.builder()
            .build_unconditional_branch(loop_header)
            .map_err(|e| CodegenError::Internal(format!("isSuffixOf: branch: {:?}", e)))?;

        self.builder().position_at_end(loop_header);
        let prefix_phi = self
            .builder()
            .build_phi(ptr_type, "isf_prefix")
            .map_err(|e| CodegenError::Internal(format!("isSuffixOf: phi: {:?}", e)))?;
        let list_phi = self
            .builder()
            .build_phi(ptr_type, "isf_list")
            .map_err(|e| CodegenError::Internal(format!("isSuffixOf: phi: {:?}", e)))?;

        let prefix_tag = self.extract_adt_tag(prefix_phi.as_basic_value().into_pointer_value())?;
        let prefix_empty = self
            .builder()
            .build_int_compare(
                inkwell::IntPredicate::EQ,
                prefix_tag,
                i64_zero,
                "prefix_nil",
            )
            .map_err(|e| CodegenError::Internal(format!("isSuffixOf: cmp: {:?}", e)))?;
        self.builder()
            .build_conditional_branch(prefix_empty, return_true, loop_body)
            .map_err(|e| CodegenError::Internal(format!("isSuffixOf: branch: {:?}", e)))?;

        self.builder().position_at_end(loop_body);
        let list_tag = self.extract_adt_tag(list_phi.as_basic_value().into_pointer_value())?;
        let list_empty = self
            .builder()
            .build_int_compare(inkwell::IntPredicate::EQ, list_tag, i64_zero, "list_nil")
            .map_err(|e| CodegenError::Internal(format!("isSuffixOf: cmp: {:?}", e)))?;
        self.builder()
            .build_conditional_branch(list_empty, return_false, compare_block)
            .map_err(|e| CodegenError::Internal(format!("isSuffixOf: branch: {:?}", e)))?;

        self.builder().position_at_end(compare_block);
        let p_head =
            self.extract_adt_field(prefix_phi.as_basic_value().into_pointer_value(), 2, 0)?;
        let p_tail =
            self.extract_adt_field(prefix_phi.as_basic_value().into_pointer_value(), 2, 1)?;
        let l_head =
            self.extract_adt_field(list_phi.as_basic_value().into_pointer_value(), 2, 0)?;
        let l_tail =
            self.extract_adt_field(list_phi.as_basic_value().into_pointer_value(), 2, 1)?;

        let p_int = self
            .builder()
            .build_ptr_to_int(p_head, i64_type, "p_int")
            .map_err(|e| CodegenError::Internal(format!("isSuffixOf: p2i: {:?}", e)))?;
        let l_int = self
            .builder()
            .build_ptr_to_int(l_head, i64_type, "l_int")
            .map_err(|e| CodegenError::Internal(format!("isSuffixOf: p2i: {:?}", e)))?;
        let heads_eq = self
            .builder()
            .build_int_compare(inkwell::IntPredicate::EQ, p_int, l_int, "heads_eq")
            .map_err(|e| CodegenError::Internal(format!("isSuffixOf: cmp: {:?}", e)))?;
        self.builder()
            .build_conditional_branch(heads_eq, loop_header, return_false)
            .map_err(|e| CodegenError::Internal(format!("isSuffixOf: branch: {:?}", e)))?;

        prefix_phi.add_incoming(&[(&rev_suffix, pre_block), (&p_tail, compare_block)]);
        list_phi.add_incoming(&[(&rev_list, pre_block), (&l_tail, compare_block)]);

        let true_tag = i64_type.const_int(1, false);
        let false_tag = i64_type.const_zero();

        self.builder().position_at_end(return_true);
        let true_bool = self
            .allocate_bool_adt(true_tag, "isf_true")?
            .ok_or_else(|| CodegenError::Internal("isSuffixOf: alloc true failed".to_string()))?;
        self.builder()
            .build_unconditional_branch(merge_block)
            .map_err(|e| CodegenError::Internal(format!("isSuffixOf: branch: {:?}", e)))?;

        self.builder().position_at_end(return_false);
        let false_bool = self
            .allocate_bool_adt(false_tag, "isf_false")?
            .ok_or_else(|| CodegenError::Internal("isSuffixOf: alloc false failed".to_string()))?;
        self.builder()
            .build_unconditional_branch(merge_block)
            .map_err(|e| CodegenError::Internal(format!("isSuffixOf: branch: {:?}", e)))?;

        self.builder().position_at_end(merge_block);
        let result_phi = self
            .builder()
            .build_phi(ptr_type, "isf_result")
            .map_err(|e| CodegenError::Internal(format!("isSuffixOf: phi: {:?}", e)))?;
        result_phi.add_incoming(&[
            (&true_bool.into_pointer_value(), return_true),
            (&false_bool.into_pointer_value(), return_false),
        ]);

        Ok(Some(result_phi.as_basic_value()))
    }

    /// Lower `isInfixOf needle haystack` - check if needle appears anywhere in haystack.
    fn lower_builtin_is_infix_of(
        &mut self,
        needle_expr: &Expr,
        haystack_expr: &Expr,
    ) -> CodegenResult<Option<BasicValueEnum<'ctx>>> {
        let needle_val = self
            .lower_expr(needle_expr)?
            .ok_or_else(|| CodegenError::Internal("isInfixOf: needle has no value".to_string()))?;
        let needle_ptr = match needle_val {
            BasicValueEnum::PointerValue(p) => p,
            _ => {
                return Err(CodegenError::TypeError(
                    "isInfixOf expects a list".to_string(),
                ))
            }
        };
        let haystack_val = self.lower_expr(haystack_expr)?.ok_or_else(|| {
            CodegenError::Internal("isInfixOf: haystack has no value".to_string())
        })?;
        let haystack_ptr = match haystack_val {
            BasicValueEnum::PointerValue(p) => p,
            _ => {
                return Err(CodegenError::TypeError(
                    "isInfixOf expects a list".to_string(),
                ))
            }
        };

        let ptr_type = self.type_mapper().ptr_type();
        let i64_type = self.type_mapper().i64_type();
        let i64_zero = i64_type.const_zero();
        let current_fn = self
            .builder()
            .get_insert_block()
            .and_then(|b| b.get_parent())
            .ok_or_else(|| CodegenError::Internal("no current function".to_string()))?;
        let _entry_block = self
            .builder()
            .get_insert_block()
            .ok_or_else(|| CodegenError::Internal("no current block".to_string()))?;

        // First check: if needle is empty, return True immediately
        let needle_tag = self.extract_adt_tag(needle_ptr)?;
        let needle_empty = self
            .builder()
            .build_int_compare(
                inkwell::IntPredicate::EQ,
                needle_tag,
                i64_zero,
                "needle_nil",
            )
            .map_err(|e| CodegenError::Internal(format!("isInfixOf: cmp: {:?}", e)))?;

        let needle_ok = self
            .llvm_ctx
            .append_basic_block(current_fn, "iif_needle_ok");
        let return_true_early = self
            .llvm_ctx
            .append_basic_block(current_fn, "iif_true_early");
        let outer_header = self.llvm_ctx.append_basic_block(current_fn, "iif_outer");
        let inner_header = self.llvm_ctx.append_basic_block(current_fn, "iif_inner");
        let inner_body = self
            .llvm_ctx
            .append_basic_block(current_fn, "iif_inner_body");
        let inner_compare = self
            .llvm_ctx
            .append_basic_block(current_fn, "iif_inner_cmp");
        let return_true = self.llvm_ctx.append_basic_block(current_fn, "iif_true");
        let return_false = self.llvm_ctx.append_basic_block(current_fn, "iif_false");
        let next_outer = self
            .llvm_ctx
            .append_basic_block(current_fn, "iif_next_outer");
        let merge_block = self.llvm_ctx.append_basic_block(current_fn, "iif_merge");

        self.builder()
            .build_conditional_branch(needle_empty, return_true_early, needle_ok)
            .map_err(|e| CodegenError::Internal(format!("isInfixOf: branch: {:?}", e)))?;

        // Return True early for empty needle
        self.builder().position_at_end(return_true_early);
        let true_tag = i64_type.const_int(1, false);
        let true_early = self
            .allocate_bool_adt(true_tag, "iif_true_early")?
            .ok_or_else(|| CodegenError::Internal("isInfixOf: alloc true failed".to_string()))?;
        self.builder()
            .build_unconditional_branch(merge_block)
            .map_err(|e| CodegenError::Internal(format!("isInfixOf: branch: {:?}", e)))?;

        // Non-empty needle: outer loop over haystack
        self.builder().position_at_end(needle_ok);
        self.builder()
            .build_unconditional_branch(outer_header)
            .map_err(|e| CodegenError::Internal(format!("isInfixOf: branch: {:?}", e)))?;

        self.builder().position_at_end(outer_header);
        let hay_phi = self
            .builder()
            .build_phi(ptr_type, "iif_hay")
            .map_err(|e| CodegenError::Internal(format!("isInfixOf: phi: {:?}", e)))?;

        // Check if haystack exhausted
        let hay_tag = self.extract_adt_tag(hay_phi.as_basic_value().into_pointer_value())?;
        let hay_empty = self
            .builder()
            .build_int_compare(inkwell::IntPredicate::EQ, hay_tag, i64_zero, "hay_nil")
            .map_err(|e| CodegenError::Internal(format!("isInfixOf: cmp: {:?}", e)))?;
        self.builder()
            .build_conditional_branch(hay_empty, return_false, inner_header)
            .map_err(|e| CodegenError::Internal(format!("isInfixOf: branch: {:?}", e)))?;

        // Inner loop: isPrefixOf at current haystack position
        self.builder().position_at_end(inner_header);
        let n_phi = self
            .builder()
            .build_phi(ptr_type, "iif_n")
            .map_err(|e| CodegenError::Internal(format!("isInfixOf: phi: {:?}", e)))?;
        let h_phi = self
            .builder()
            .build_phi(ptr_type, "iif_h")
            .map_err(|e| CodegenError::Internal(format!("isInfixOf: phi: {:?}", e)))?;

        // If needle exhausted at this position, we found a match
        let n_tag = self.extract_adt_tag(n_phi.as_basic_value().into_pointer_value())?;
        let n_empty = self
            .builder()
            .build_int_compare(inkwell::IntPredicate::EQ, n_tag, i64_zero, "n_nil")
            .map_err(|e| CodegenError::Internal(format!("isInfixOf: cmp: {:?}", e)))?;
        self.builder()
            .build_conditional_branch(n_empty, return_true, inner_body)
            .map_err(|e| CodegenError::Internal(format!("isInfixOf: branch: {:?}", e)))?;

        // Inner body: check if haystack portion exhausted
        self.builder().position_at_end(inner_body);
        let h_tag = self.extract_adt_tag(h_phi.as_basic_value().into_pointer_value())?;
        let h_empty = self
            .builder()
            .build_int_compare(inkwell::IntPredicate::EQ, h_tag, i64_zero, "h_nil")
            .map_err(|e| CodegenError::Internal(format!("isInfixOf: cmp: {:?}", e)))?;
        self.builder()
            .build_conditional_branch(h_empty, next_outer, inner_compare)
            .map_err(|e| CodegenError::Internal(format!("isInfixOf: branch: {:?}", e)))?;

        // Inner compare: compare heads
        self.builder().position_at_end(inner_compare);
        let n_head = self.extract_adt_field(n_phi.as_basic_value().into_pointer_value(), 2, 0)?;
        let n_tail = self.extract_adt_field(n_phi.as_basic_value().into_pointer_value(), 2, 1)?;
        let h_head = self.extract_adt_field(h_phi.as_basic_value().into_pointer_value(), 2, 0)?;
        let h_tail = self.extract_adt_field(h_phi.as_basic_value().into_pointer_value(), 2, 1)?;

        let n_int = self
            .builder()
            .build_ptr_to_int(n_head, i64_type, "n_int")
            .map_err(|e| CodegenError::Internal(format!("isInfixOf: p2i: {:?}", e)))?;
        let h_int = self
            .builder()
            .build_ptr_to_int(h_head, i64_type, "h_int")
            .map_err(|e| CodegenError::Internal(format!("isInfixOf: p2i: {:?}", e)))?;
        let heads_eq = self
            .builder()
            .build_int_compare(inkwell::IntPredicate::EQ, n_int, h_int, "heads_eq")
            .map_err(|e| CodegenError::Internal(format!("isInfixOf: cmp: {:?}", e)))?;
        self.builder()
            .build_conditional_branch(heads_eq, inner_header, next_outer)
            .map_err(|e| CodegenError::Internal(format!("isInfixOf: branch: {:?}", e)))?;

        n_phi.add_incoming(&[(&needle_ptr, outer_header), (&n_tail, inner_compare)]);
        h_phi.add_incoming(&[
            (&hay_phi.as_basic_value().into_pointer_value(), outer_header),
            (&h_tail, inner_compare),
        ]);

        // Next outer: advance haystack by one
        self.builder().position_at_end(next_outer);
        let hay_tail =
            self.extract_adt_field(hay_phi.as_basic_value().into_pointer_value(), 2, 1)?;
        self.builder()
            .build_unconditional_branch(outer_header)
            .map_err(|e| CodegenError::Internal(format!("isInfixOf: branch: {:?}", e)))?;

        hay_phi.add_incoming(&[(&haystack_ptr, needle_ok), (&hay_tail, next_outer)]);

        // Return True
        self.builder().position_at_end(return_true);
        let true_bool = self
            .allocate_bool_adt(true_tag, "iif_true")?
            .ok_or_else(|| CodegenError::Internal("isInfixOf: alloc true failed".to_string()))?;
        self.builder()
            .build_unconditional_branch(merge_block)
            .map_err(|e| CodegenError::Internal(format!("isInfixOf: branch: {:?}", e)))?;

        // Return False
        self.builder().position_at_end(return_false);
        let false_tag = i64_type.const_zero();
        let false_bool = self
            .allocate_bool_adt(false_tag, "iif_false")?
            .ok_or_else(|| CodegenError::Internal("isInfixOf: alloc false failed".to_string()))?;
        self.builder()
            .build_unconditional_branch(merge_block)
            .map_err(|e| CodegenError::Internal(format!("isInfixOf: branch: {:?}", e)))?;

        // Merge
        self.builder().position_at_end(merge_block);
        let result_phi = self
            .builder()
            .build_phi(ptr_type, "iif_result")
            .map_err(|e| CodegenError::Internal(format!("isInfixOf: phi: {:?}", e)))?;
        result_phi.add_incoming(&[
            (&true_early.into_pointer_value(), return_true_early),
            (&true_bool.into_pointer_value(), return_true),
            (&false_bool.into_pointer_value(), return_false),
        ]);

        Ok(Some(result_phi.as_basic_value()))
    }

    /// Lower `tails xs` - return all suffixes of a list, including the empty list.
    fn lower_builtin_tails(
        &mut self,
        list_expr: &Expr,
    ) -> CodegenResult<Option<BasicValueEnum<'ctx>>> {
        let list_val = self
            .lower_expr(list_expr)?
            .ok_or_else(|| CodegenError::Internal("tails: list has no value".to_string()))?;
        let list_ptr = match list_val {
            BasicValueEnum::PointerValue(p) => p,
            _ => return Err(CodegenError::TypeError("tails expects a list".to_string())),
        };

        let ptr_type = self.type_mapper().ptr_type();
        let i64_type = self.type_mapper().i64_type();
        let current_fn = self
            .builder()
            .get_insert_block()
            .and_then(|b| b.get_parent())
            .ok_or_else(|| CodegenError::Internal("no current function".to_string()))?;
        let entry_block = self
            .builder()
            .get_insert_block()
            .ok_or_else(|| CodegenError::Internal("no current block".to_string()))?;

        // Build result list in reverse: cons each suffix onto accumulator, then reverse
        let nil = self.build_nil()?;
        // First cons the empty list at the very end: we accumulate [xs, tail xs, ..., [], current_suffixes_reversed]
        // Actually: walk list, at each position cons the current pointer onto acc. Then cons nil. Then reverse.
        let loop_header = self.llvm_ctx.append_basic_block(current_fn, "tails_header");
        let loop_body = self.llvm_ctx.append_basic_block(current_fn, "tails_body");
        let loop_exit = self.llvm_ctx.append_basic_block(current_fn, "tails_exit");

        self.builder()
            .build_unconditional_branch(loop_header)
            .map_err(|e| CodegenError::Internal(format!("tails: branch: {:?}", e)))?;

        self.builder().position_at_end(loop_header);
        let list_phi = self
            .builder()
            .build_phi(ptr_type, "tails_list")
            .map_err(|e| CodegenError::Internal(format!("tails: phi: {:?}", e)))?;
        let acc_phi = self
            .builder()
            .build_phi(ptr_type, "tails_acc")
            .map_err(|e| CodegenError::Internal(format!("tails: phi: {:?}", e)))?;

        let tag = self.extract_adt_tag(list_phi.as_basic_value().into_pointer_value())?;
        let is_empty = self
            .builder()
            .build_int_compare(
                inkwell::IntPredicate::EQ,
                tag,
                i64_type.const_zero(),
                "is_nil",
            )
            .map_err(|e| CodegenError::Internal(format!("tails: cmp: {:?}", e)))?;
        self.builder()
            .build_conditional_branch(is_empty, loop_exit, loop_body)
            .map_err(|e| CodegenError::Internal(format!("tails: branch: {:?}", e)))?;

        // Loop body: cons current list pointer onto acc, advance to tail
        self.builder().position_at_end(loop_body);
        let tail = self.extract_adt_field(list_phi.as_basic_value().into_pointer_value(), 2, 1)?;
        let new_cons = self.build_cons(
            list_phi.as_basic_value().into_pointer_value().into(),
            acc_phi.as_basic_value(),
        )?;
        self.builder()
            .build_unconditional_branch(loop_header)
            .map_err(|e| CodegenError::Internal(format!("tails: branch: {:?}", e)))?;

        list_phi.add_incoming(&[(&list_ptr, entry_block), (&tail, loop_body)]);
        acc_phi.add_incoming(&[(&nil, entry_block), (&new_cons, loop_body)]);

        // Exit: cons the empty list (nil) onto acc, then reverse
        self.builder().position_at_end(loop_exit);
        let final_cons = self.build_cons(nil.into(), acc_phi.as_basic_value())?;

        // Reverse the accumulated list
        self.build_inline_reverse(final_cons, current_fn)
    }

    /// Lower `inits xs` - return all prefixes of a list, including the empty list.
    fn lower_builtin_inits(
        &mut self,
        list_expr: &Expr,
    ) -> CodegenResult<Option<BasicValueEnum<'ctx>>> {
        let list_val = self
            .lower_expr(list_expr)?
            .ok_or_else(|| CodegenError::Internal("inits: list has no value".to_string()))?;
        let list_ptr = match list_val {
            BasicValueEnum::PointerValue(p) => p,
            _ => return Err(CodegenError::TypeError("inits expects a list".to_string())),
        };

        let ptr_type = self.type_mapper().ptr_type();
        let i64_type = self.type_mapper().i64_type();
        let current_fn = self
            .builder()
            .get_insert_block()
            .and_then(|b| b.get_parent())
            .ok_or_else(|| CodegenError::Internal("no current function".to_string()))?;
        let entry_block = self
            .builder()
            .get_insert_block()
            .ok_or_else(|| CodegenError::Internal("no current block".to_string()))?;

        // Strategy: walk list with a counter. For each position i (0..=length),
        // build the prefix of length i by taking i elements from the original list.
        // Accumulate results in reverse, then reverse at the end.
        //
        // Simpler approach: accumulate elements seen into a reversed list.
        // At each step, reverse the reversed-elements to get the prefix, and cons onto result acc.
        // This is O(n^2) but correct and simple.

        let nil = self.build_nil()?;

        // First, cons the empty list (nil) as the first init
        let init_acc_start = self.build_cons(nil.into(), nil.into())?;

        let loop_header = self.llvm_ctx.append_basic_block(current_fn, "inits_header");
        let loop_body = self.llvm_ctx.append_basic_block(current_fn, "inits_body");
        let loop_exit = self.llvm_ctx.append_basic_block(current_fn, "inits_exit");

        self.builder()
            .build_unconditional_branch(loop_header)
            .map_err(|e| CodegenError::Internal(format!("inits: branch: {:?}", e)))?;

        self.builder().position_at_end(loop_header);
        let list_phi = self
            .builder()
            .build_phi(ptr_type, "inits_list")
            .map_err(|e| CodegenError::Internal(format!("inits: phi: {:?}", e)))?;
        let elems_phi = self
            .builder()
            .build_phi(ptr_type, "inits_elems")
            .map_err(|e| CodegenError::Internal(format!("inits: phi: {:?}", e)))?;
        let acc_phi = self
            .builder()
            .build_phi(ptr_type, "inits_acc")
            .map_err(|e| CodegenError::Internal(format!("inits: phi: {:?}", e)))?;

        let tag = self.extract_adt_tag(list_phi.as_basic_value().into_pointer_value())?;
        let is_empty = self
            .builder()
            .build_int_compare(
                inkwell::IntPredicate::EQ,
                tag,
                i64_type.const_zero(),
                "is_nil",
            )
            .map_err(|e| CodegenError::Internal(format!("inits: cmp: {:?}", e)))?;
        self.builder()
            .build_conditional_branch(is_empty, loop_exit, loop_body)
            .map_err(|e| CodegenError::Internal(format!("inits: branch: {:?}", e)))?;

        // Loop body: add current head to reversed elements, reverse to get prefix, cons prefix onto acc
        self.builder().position_at_end(loop_body);
        let head = self.extract_adt_field(list_phi.as_basic_value().into_pointer_value(), 2, 0)?;
        let tail = self.extract_adt_field(list_phi.as_basic_value().into_pointer_value(), 2, 1)?;

        // cons head onto reversed-elements (building reversed prefix)
        let new_elems = self.build_cons(head.into(), elems_phi.as_basic_value())?;
        let new_elems_ptr = new_elems; // build_cons returns PointerValue directly

        // Reverse the reversed-elements to get the actual prefix
        let prefix = self
            .build_inline_reverse(new_elems_ptr, current_fn)?
            .ok_or_else(|| CodegenError::Internal("inits: reverse failed".to_string()))?;

        // Cons prefix onto accumulator
        let new_acc = self.build_cons(prefix, acc_phi.as_basic_value())?;

        // Get the current block before branching (build_inline_reverse changed it)
        let loop_body_end = self
            .builder()
            .get_insert_block()
            .ok_or_else(|| CodegenError::Internal("no current block".to_string()))?;

        self.builder()
            .build_unconditional_branch(loop_header)
            .map_err(|e| CodegenError::Internal(format!("inits: branch: {:?}", e)))?;

        list_phi.add_incoming(&[(&list_ptr, entry_block), (&tail, loop_body_end)]);
        elems_phi.add_incoming(&[(&nil, entry_block), (&new_elems_ptr, loop_body_end)]);
        acc_phi.add_incoming(&[(&init_acc_start, entry_block), (&new_acc, loop_body_end)]);

        // Exit: reverse the accumulated result
        self.builder().position_at_end(loop_exit);
        self.build_inline_reverse(acc_phi.as_basic_value().into_pointer_value(), current_fn)
    }

    /// Lower `maximumBy cmp xs` - find maximum element using comparison function.
    fn lower_builtin_maximum_by(
        &mut self,
        cmp_expr: &Expr,
        list_expr: &Expr,
    ) -> CodegenResult<Option<BasicValueEnum<'ctx>>> {
        self.lower_builtin_extremum_by(cmp_expr, list_expr, 2, "maximumBy")
    }

    /// Lower `minimumBy cmp xs` - find minimum element using comparison function.
    fn lower_builtin_minimum_by(
        &mut self,
        cmp_expr: &Expr,
        list_expr: &Expr,
    ) -> CodegenResult<Option<BasicValueEnum<'ctx>>> {
        self.lower_builtin_extremum_by(cmp_expr, list_expr, 0, "minimumBy")
    }

    /// Shared implementation for `maximumBy` and `minimumBy`.
    /// `update_tag` is 2 for GT (maximumBy) or 0 for LT (minimumBy).
    fn lower_builtin_extremum_by(
        &mut self,
        cmp_expr: &Expr,
        list_expr: &Expr,
        update_tag: u64,
        label: &str,
    ) -> CodegenResult<Option<BasicValueEnum<'ctx>>> {
        let cmp_val = self
            .lower_expr(cmp_expr)?
            .ok_or_else(|| CodegenError::Internal(format!("{}: cmp has no value", label)))?;
        let cmp_ptr = match cmp_val {
            BasicValueEnum::PointerValue(p) => p,
            _ => {
                return Err(CodegenError::TypeError(format!(
                    "{}: cmp must be a closure",
                    label
                )))
            }
        };
        let list_val = self
            .lower_expr(list_expr)?
            .ok_or_else(|| CodegenError::Internal(format!("{}: list has no value", label)))?;
        let list_ptr = match list_val {
            BasicValueEnum::PointerValue(p) => p,
            _ => return Err(CodegenError::TypeError(format!("{} expects a list", label))),
        };

        let ptr_type = self.type_mapper().ptr_type();
        let i64_type = self.type_mapper().i64_type();
        let current_fn = self
            .builder()
            .get_insert_block()
            .and_then(|b| b.get_parent())
            .ok_or_else(|| CodegenError::Internal("no current function".to_string()))?;

        // Extract head as initial best
        let check_block = self
            .llvm_ctx
            .append_basic_block(current_fn, &format!("{}_check", label));
        let loop_header = self
            .llvm_ctx
            .append_basic_block(current_fn, &format!("{}_header", label));
        let loop_body = self
            .llvm_ctx
            .append_basic_block(current_fn, &format!("{}_body", label));
        let loop_exit = self
            .llvm_ctx
            .append_basic_block(current_fn, &format!("{}_exit", label));

        self.builder()
            .build_unconditional_branch(check_block)
            .map_err(|e| CodegenError::Internal(format!("{}: branch: {:?}", label, e)))?;

        self.builder().position_at_end(check_block);
        let head = self.extract_adt_field(list_ptr, 2, 0)?;
        let tail = self.extract_adt_field(list_ptr, 2, 1)?;
        self.builder()
            .build_unconditional_branch(loop_header)
            .map_err(|e| CodegenError::Internal(format!("{}: branch: {:?}", label, e)))?;

        // Loop header
        self.builder().position_at_end(loop_header);
        let best_phi = self
            .builder()
            .build_phi(ptr_type, &format!("{}_best", label))
            .map_err(|e| CodegenError::Internal(format!("{}: phi: {:?}", label, e)))?;
        let list_phi = self
            .builder()
            .build_phi(ptr_type, &format!("{}_list", label))
            .map_err(|e| CodegenError::Internal(format!("{}: phi: {:?}", label, e)))?;

        let tag = self.extract_adt_tag(list_phi.as_basic_value().into_pointer_value())?;
        let is_empty = self
            .builder()
            .build_int_compare(
                inkwell::IntPredicate::EQ,
                tag,
                i64_type.const_zero(),
                "is_nil",
            )
            .map_err(|e| CodegenError::Internal(format!("{}: cmp: {:?}", label, e)))?;
        self.builder()
            .build_conditional_branch(is_empty, loop_exit, loop_body)
            .map_err(|e| CodegenError::Internal(format!("{}: branch: {:?}", label, e)))?;

        // Loop body: extract head, call cmp(head, best), check ordering
        self.builder().position_at_end(loop_body);
        let cur_head =
            self.extract_adt_field(list_phi.as_basic_value().into_pointer_value(), 2, 0)?;
        let cur_tail =
            self.extract_adt_field(list_phi.as_basic_value().into_pointer_value(), 2, 1)?;

        // Call cmp(cur_head, best) - cmp is a flat 2-arg closure: (env, arg1, arg2) -> Ordering
        let cmp_fn_ptr = self.extract_closure_fn_ptr(cmp_ptr)?;
        let fn_type = ptr_type.fn_type(&[ptr_type.into(), ptr_type.into(), ptr_type.into()], false);
        let ordering_result = self
            .builder()
            .build_indirect_call(
                fn_type,
                cmp_fn_ptr,
                &[
                    cmp_ptr.into(),
                    cur_head.into(),
                    best_phi.as_basic_value().into_pointer_value().into(),
                ],
                &format!("{}_ord", label),
            )
            .map_err(|e| CodegenError::Internal(format!("{}: call: {:?}", label, e)))?
            .try_as_basic_value()
            .basic()
            .ok_or_else(|| CodegenError::Internal(format!("{}: cmp returned void", label)))?;

        // Check Ordering ADT tag: 0=LT, 1=EQ, 2=GT
        let ord_tag = self.extract_adt_tag(ordering_result.into_pointer_value())?;
        let should_update = self
            .builder()
            .build_int_compare(
                inkwell::IntPredicate::EQ,
                ord_tag,
                i64_type.const_int(update_tag, false),
                "should_update",
            )
            .map_err(|e| CodegenError::Internal(format!("{}: cmp: {:?}", label, e)))?;

        let new_best = self
            .builder()
            .build_select(
                should_update,
                cur_head,
                best_phi.as_basic_value().into_pointer_value(),
                "new_best",
            )
            .map_err(|e| CodegenError::Internal(format!("{}: select: {:?}", label, e)))?;
        self.builder()
            .build_unconditional_branch(loop_header)
            .map_err(|e| CodegenError::Internal(format!("{}: branch: {:?}", label, e)))?;

        best_phi.add_incoming(&[
            (&head, check_block),
            (&new_best.into_pointer_value(), loop_body),
        ]);
        list_phi.add_incoming(&[(&tail, check_block), (&cur_tail, loop_body)]);

        // Exit: return best
        self.builder().position_at_end(loop_exit);
        Ok(Some(best_phi.as_basic_value()))
    }

    /// Lower `elem`.
    fn lower_builtin_elem(
        &mut self,
        val_expr: &Expr,
        list_expr: &Expr,
    ) -> CodegenResult<Option<BasicValueEnum<'ctx>>> {
        // Lower the value to search for
        let val_val = self
            .lower_expr(val_expr)?
            .ok_or_else(|| CodegenError::Internal("elem: value has no value".to_string()))?;
        let val_ptr = self.value_to_ptr(val_val)?;

        // Lower the list
        let list_val = self
            .lower_expr(list_expr)?
            .ok_or_else(|| CodegenError::Internal("elem: list has no value".to_string()))?;
        let list_ptr = match list_val {
            BasicValueEnum::PointerValue(p) => p,
            _ => return Err(CodegenError::TypeError("elem expects a list".to_string())),
        };

        let tm = self.type_mapper();
        let ptr_type = tm.ptr_type();
        let current_fn = self
            .builder()
            .get_insert_block()
            .and_then(|b| b.get_parent())
            .ok_or_else(|| CodegenError::Internal("no current function".to_string()))?;
        let entry_block = self
            .builder()
            .get_insert_block()
            .ok_or_else(|| CodegenError::Internal("no current block".to_string()))?;

        let loop_header = self.llvm_ctx.append_basic_block(current_fn, "elem_header");
        let loop_body = self.llvm_ctx.append_basic_block(current_fn, "elem_body");
        let found_block = self.llvm_ctx.append_basic_block(current_fn, "elem_found");
        let not_found_block = self
            .llvm_ctx
            .append_basic_block(current_fn, "elem_not_found");
        let merge_block = self.llvm_ctx.append_basic_block(current_fn, "elem_merge");

        // Convert search value to i64 for comparison
        let val_int = self
            .builder()
            .build_ptr_to_int(val_ptr, tm.i64_type(), "val_int")
            .map_err(|e| CodegenError::Internal(format!("ptr_to_int: {:?}", e)))?;

        self.builder()
            .build_unconditional_branch(loop_header)
            .map_err(|e| CodegenError::Internal(format!("branch: {:?}", e)))?;

        // Loop header
        self.builder().position_at_end(loop_header);
        let list_phi = self
            .builder()
            .build_phi(ptr_type, "elem_list")
            .map_err(|e| CodegenError::Internal(format!("phi: {:?}", e)))?;

        let tag = self.extract_adt_tag(list_phi.as_basic_value().into_pointer_value())?;
        let is_empty = self
            .builder()
            .build_int_compare(
                inkwell::IntPredicate::EQ,
                tag,
                tm.i64_type().const_zero(),
                "is_empty",
            )
            .map_err(|e| CodegenError::Internal(format!("cmp: {:?}", e)))?;

        self.builder()
            .build_conditional_branch(is_empty, not_found_block, loop_body)
            .map_err(|e| CodegenError::Internal(format!("branch: {:?}", e)))?;

        // Loop body: extract head, compare via ptr_to_int
        self.builder().position_at_end(loop_body);
        let head_ptr =
            self.extract_adt_field(list_phi.as_basic_value().into_pointer_value(), 2, 0)?;
        let tail_ptr =
            self.extract_adt_field(list_phi.as_basic_value().into_pointer_value(), 2, 1)?;

        let head_int = self
            .builder()
            .build_ptr_to_int(head_ptr, tm.i64_type(), "head_int")
            .map_err(|e| CodegenError::Internal(format!("ptr_to_int: {:?}", e)))?;
        let is_equal = self
            .builder()
            .build_int_compare(inkwell::IntPredicate::EQ, head_int, val_int, "is_equal")
            .map_err(|e| CodegenError::Internal(format!("cmp: {:?}", e)))?;

        self.builder()
            .build_conditional_branch(is_equal, found_block, loop_header)
            .map_err(|e| CodegenError::Internal(format!("branch: {:?}", e)))?;

        list_phi.add_incoming(&[(&list_ptr, entry_block), (&tail_ptr, loop_body)]);

        // Found: return True = int_to_ptr(1)
        self.builder().position_at_end(found_block);
        let true_val = self.int_to_ptr(tm.i64_type().const_int(1, false))?;
        self.builder()
            .build_unconditional_branch(merge_block)
            .map_err(|e| CodegenError::Internal(format!("branch: {:?}", e)))?;

        // Not found: return False = int_to_ptr(0)
        self.builder().position_at_end(not_found_block);
        let false_val = self.int_to_ptr(tm.i64_type().const_zero())?;
        self.builder()
            .build_unconditional_branch(merge_block)
            .map_err(|e| CodegenError::Internal(format!("branch: {:?}", e)))?;

        // Merge
        self.builder().position_at_end(merge_block);
        let result_phi = self
            .builder()
            .build_phi(ptr_type, "elem_result")
            .map_err(|e| CodegenError::Internal(format!("phi: {:?}", e)))?;
        result_phi.add_incoming(&[(&true_val, found_block), (&false_val, not_found_block)]);

        Ok(Some(result_phi.as_basic_value()))
    }

    /// Lower `notElem :: Eq a => a -> [a] -> Bool`.
    /// Inverse of elem: returns True if element is NOT in the list.
    fn lower_builtin_not_elem(
        &mut self,
        val_expr: &Expr,
        list_expr: &Expr,
    ) -> CodegenResult<Option<BasicValueEnum<'ctx>>> {
        // Lower the value to search for
        let val_val = self
            .lower_expr(val_expr)?
            .ok_or_else(|| CodegenError::Internal("notElem: value has no value".to_string()))?;
        let val_ptr = self.value_to_ptr(val_val)?;

        // Lower the list
        let list_val = self
            .lower_expr(list_expr)?
            .ok_or_else(|| CodegenError::Internal("notElem: list has no value".to_string()))?;
        let list_ptr = match list_val {
            BasicValueEnum::PointerValue(p) => p,
            _ => {
                return Err(CodegenError::TypeError(
                    "notElem expects a list".to_string(),
                ))
            }
        };

        let tm = self.type_mapper();
        let ptr_type = tm.ptr_type();
        let current_fn = self
            .builder()
            .get_insert_block()
            .and_then(|b| b.get_parent())
            .ok_or_else(|| CodegenError::Internal("no current function".to_string()))?;
        let entry_block = self
            .builder()
            .get_insert_block()
            .ok_or_else(|| CodegenError::Internal("no current block".to_string()))?;

        let loop_header = self
            .llvm_ctx
            .append_basic_block(current_fn, "notelem_header");
        let loop_body = self.llvm_ctx.append_basic_block(current_fn, "notelem_body");
        let found_block = self
            .llvm_ctx
            .append_basic_block(current_fn, "notelem_found");
        let not_found_block = self
            .llvm_ctx
            .append_basic_block(current_fn, "notelem_not_found");
        let merge_block = self
            .llvm_ctx
            .append_basic_block(current_fn, "notelem_merge");

        // Convert search value to i64 for comparison
        let val_int = self
            .builder()
            .build_ptr_to_int(val_ptr, tm.i64_type(), "val_int")
            .map_err(|e| CodegenError::Internal(format!("ptr_to_int: {:?}", e)))?;

        self.builder()
            .build_unconditional_branch(loop_header)
            .map_err(|e| CodegenError::Internal(format!("branch: {:?}", e)))?;

        // Loop header
        self.builder().position_at_end(loop_header);
        let list_phi = self
            .builder()
            .build_phi(ptr_type, "notelem_list")
            .map_err(|e| CodegenError::Internal(format!("phi: {:?}", e)))?;

        let tag = self.extract_adt_tag(list_phi.as_basic_value().into_pointer_value())?;
        let is_empty = self
            .builder()
            .build_int_compare(
                inkwell::IntPredicate::EQ,
                tag,
                tm.i64_type().const_zero(),
                "is_empty",
            )
            .map_err(|e| CodegenError::Internal(format!("cmp: {:?}", e)))?;

        self.builder()
            .build_conditional_branch(is_empty, not_found_block, loop_body)
            .map_err(|e| CodegenError::Internal(format!("branch: {:?}", e)))?;

        // Loop body: extract head, compare via ptr_to_int
        self.builder().position_at_end(loop_body);
        let head_ptr =
            self.extract_adt_field(list_phi.as_basic_value().into_pointer_value(), 2, 0)?;
        let tail_ptr =
            self.extract_adt_field(list_phi.as_basic_value().into_pointer_value(), 2, 1)?;

        let head_int = self
            .builder()
            .build_ptr_to_int(head_ptr, tm.i64_type(), "head_int")
            .map_err(|e| CodegenError::Internal(format!("ptr_to_int: {:?}", e)))?;
        let is_equal = self
            .builder()
            .build_int_compare(inkwell::IntPredicate::EQ, head_int, val_int, "is_equal")
            .map_err(|e| CodegenError::Internal(format!("cmp: {:?}", e)))?;

        self.builder()
            .build_conditional_branch(is_equal, found_block, loop_header)
            .map_err(|e| CodegenError::Internal(format!("branch: {:?}", e)))?;

        list_phi.add_incoming(&[(&list_ptr, entry_block), (&tail_ptr, loop_body)]);

        // Found: element IS in list, so notElem returns False = int_to_ptr(0)
        self.builder().position_at_end(found_block);
        let false_val = self.int_to_ptr(tm.i64_type().const_zero())?;
        self.builder()
            .build_unconditional_branch(merge_block)
            .map_err(|e| CodegenError::Internal(format!("branch: {:?}", e)))?;

        // Not found: element is NOT in list, so notElem returns True = int_to_ptr(1)
        self.builder().position_at_end(not_found_block);
        let true_val = self.int_to_ptr(tm.i64_type().const_int(1, false))?;
        self.builder()
            .build_unconditional_branch(merge_block)
            .map_err(|e| CodegenError::Internal(format!("branch: {:?}", e)))?;

        // Merge
        self.builder().position_at_end(merge_block);
        let result_phi = self
            .builder()
            .build_phi(ptr_type, "notelem_result")
            .map_err(|e| CodegenError::Internal(format!("phi: {:?}", e)))?;
        result_phi.add_incoming(&[(&false_val, found_block), (&true_val, not_found_block)]);

        Ok(Some(result_phi.as_basic_value()))
    }

    /// Lower `iterate` (stub: single-element list).
    fn lower_builtin_iterate(
        &mut self,
        _func_expr: &Expr,
        seed_expr: &Expr,
    ) -> CodegenResult<Option<BasicValueEnum<'ctx>>> {
        let _ = self.lower_expr(_func_expr)?;
        let seed_val = self
            .lower_expr(seed_expr)?
            .ok_or_else(|| CodegenError::Internal("iterate: no seed".to_string()))?;
        let seed_ptr = self.value_to_ptr(seed_val)?;
        let nil = self.alloc_adt(0, 0)?;
        let cons = self.alloc_adt(1, 2)?;
        self.store_adt_field(cons, 2, 0, seed_ptr.into())?;
        self.store_adt_field(cons, 2, 1, nil.into())?;
        Ok(Some(cons.into()))
    }

    /// Lower `repeat` (stub).
    fn lower_builtin_repeat(
        &mut self,
        val_expr: &Expr,
    ) -> CodegenResult<Option<BasicValueEnum<'ctx>>> {
        self.lower_builtin_iterate(val_expr, val_expr)
    }

    /// Lower `cycle` (stub).
    fn lower_builtin_cycle(
        &mut self,
        list_expr: &Expr,
    ) -> CodegenResult<Option<BasicValueEnum<'ctx>>> {
        self.lower_expr(list_expr)
    }

    /// Lower `takeWhile` — take longest prefix where predicate holds.
    fn lower_builtin_take_while(
        &mut self,
        pred_expr: &Expr,
        list_expr: &Expr,
    ) -> CodegenResult<Option<BasicValueEnum<'ctx>>> {
        let pred_val = self
            .lower_expr(pred_expr)?
            .ok_or_else(|| CodegenError::Internal("takeWhile: no predicate".to_string()))?;
        let pred_ptr = match pred_val {
            BasicValueEnum::PointerValue(p) => p,
            _ => {
                return Err(CodegenError::TypeError(
                    "takeWhile: predicate must be a closure".to_string(),
                ))
            }
        };
        let list_val = self
            .lower_expr(list_expr)?
            .ok_or_else(|| CodegenError::Internal("takeWhile: no list".to_string()))?;
        let list_ptr = match list_val {
            BasicValueEnum::PointerValue(p) => p,
            _ => {
                return Err(CodegenError::TypeError(
                    "takeWhile expects a list".to_string(),
                ))
            }
        };

        let tm = self.type_mapper();
        let ptr_type = tm.ptr_type();
        let current_fn = self
            .builder()
            .get_insert_block()
            .and_then(|b| b.get_parent())
            .ok_or_else(|| CodegenError::Internal("no current function".to_string()))?;
        let entry_block = self
            .builder()
            .get_insert_block()
            .ok_or_else(|| CodegenError::Internal("no current block".to_string()))?;

        let loop_header = self.llvm_ctx.append_basic_block(current_fn, "tw_header");
        let loop_body = self.llvm_ctx.append_basic_block(current_fn, "tw_body");
        let tw_keep = self.llvm_ctx.append_basic_block(current_fn, "tw_keep");
        let loop_exit = self.llvm_ctx.append_basic_block(current_fn, "tw_exit");

        let nil = self.build_nil()?;
        self.builder()
            .build_unconditional_branch(loop_header)
            .map_err(|e| CodegenError::Internal(format!("branch: {:?}", e)))?;

        self.builder().position_at_end(loop_header);
        let acc_phi = self
            .builder()
            .build_phi(ptr_type, "tw_acc")
            .map_err(|e| CodegenError::Internal(format!("phi: {:?}", e)))?;
        let list_phi = self
            .builder()
            .build_phi(ptr_type, "tw_list")
            .map_err(|e| CodegenError::Internal(format!("phi: {:?}", e)))?;

        let tag = self.extract_adt_tag(list_phi.as_basic_value().into_pointer_value())?;
        let is_empty = self
            .builder()
            .build_int_compare(
                inkwell::IntPredicate::EQ,
                tag,
                tm.i64_type().const_zero(),
                "is_empty",
            )
            .map_err(|e| CodegenError::Internal(format!("cmp: {:?}", e)))?;
        self.builder()
            .build_conditional_branch(is_empty, loop_exit, loop_body)
            .map_err(|e| CodegenError::Internal(format!("branch: {:?}", e)))?;

        self.builder().position_at_end(loop_body);
        let head_ptr =
            self.extract_adt_field(list_phi.as_basic_value().into_pointer_value(), 2, 0)?;
        let tail_ptr =
            self.extract_adt_field(list_phi.as_basic_value().into_pointer_value(), 2, 1)?;

        let pred_fn_ptr = self.extract_closure_fn_ptr(pred_ptr)?;
        let fn_type = ptr_type.fn_type(&[ptr_type.into(), ptr_type.into()], false);
        let pred_result = self
            .builder()
            .build_indirect_call(
                fn_type,
                pred_fn_ptr,
                &[pred_ptr.into(), head_ptr.into()],
                "pred_result",
            )
            .map_err(|e| CodegenError::Internal(format!("call predicate: {:?}", e)))?
            .try_as_basic_value()
            .basic()
            .ok_or_else(|| CodegenError::Internal("predicate returned void".to_string()))?;
        // Handle both tagged-int-as-pointer (0/1) and Bool ADT (heap struct with tag)
        let pred_tag = self.extract_bool_tag(pred_result.into_pointer_value())?;
        // Capture actual block after extract_bool_tag (may have created new blocks)
        let pred_check_block = self.builder().get_insert_block().ok_or_else(|| {
            CodegenError::Internal("takeWhile: no block after bool check".to_string())
        })?;
        let is_true = self
            .builder()
            .build_int_compare(
                inkwell::IntPredicate::NE,
                pred_tag,
                tm.i64_type().const_zero(),
                "is_true",
            )
            .map_err(|e| CodegenError::Internal(format!("cmp: {:?}", e)))?;

        // If pred is true, keep; if false, stop
        self.builder()
            .build_conditional_branch(is_true, tw_keep, loop_exit)
            .map_err(|e| CodegenError::Internal(format!("branch: {:?}", e)))?;

        self.builder().position_at_end(tw_keep);
        let new_cons = self.build_cons(head_ptr.into(), acc_phi.as_basic_value())?;
        self.builder()
            .build_unconditional_branch(loop_header)
            .map_err(|e| CodegenError::Internal(format!("branch: {:?}", e)))?;

        acc_phi.add_incoming(&[(&nil, entry_block), (&new_cons, tw_keep)]);
        list_phi.add_incoming(&[(&list_ptr, entry_block), (&tail_ptr, tw_keep)]);

        // Exit: reverse accumulated prefix
        self.builder().position_at_end(loop_exit);
        let acc_at_exit = self
            .builder()
            .build_phi(ptr_type, "tw_acc_exit")
            .map_err(|e| CodegenError::Internal(format!("phi: {:?}", e)))?;
        acc_at_exit.add_incoming(&[
            (&acc_phi.as_basic_value(), loop_header),
            (&acc_phi.as_basic_value(), pred_check_block),
        ]);

        // Inline reverse
        let rev_header = self
            .llvm_ctx
            .append_basic_block(current_fn, "tw_rev_header");
        let rev_body = self.llvm_ctx.append_basic_block(current_fn, "tw_rev_body");
        let rev_exit = self.llvm_ctx.append_basic_block(current_fn, "tw_rev_exit");
        let nil2 = self.build_nil()?;
        self.builder()
            .build_unconditional_branch(rev_header)
            .map_err(|e| CodegenError::Internal(format!("branch: {:?}", e)))?;

        self.builder().position_at_end(rev_header);
        let rev_acc = self
            .builder()
            .build_phi(ptr_type, "rev_acc")
            .map_err(|e| CodegenError::Internal(format!("phi: {:?}", e)))?;
        let rev_list = self
            .builder()
            .build_phi(ptr_type, "rev_list")
            .map_err(|e| CodegenError::Internal(format!("phi: {:?}", e)))?;
        let rev_tag = self.extract_adt_tag(rev_list.as_basic_value().into_pointer_value())?;
        let rev_is_empty = self
            .builder()
            .build_int_compare(
                inkwell::IntPredicate::EQ,
                rev_tag,
                tm.i64_type().const_zero(),
                "rev_is_empty",
            )
            .map_err(|e| CodegenError::Internal(format!("cmp: {:?}", e)))?;
        self.builder()
            .build_conditional_branch(rev_is_empty, rev_exit, rev_body)
            .map_err(|e| CodegenError::Internal(format!("branch: {:?}", e)))?;

        self.builder().position_at_end(rev_body);
        let rev_head =
            self.extract_adt_field(rev_list.as_basic_value().into_pointer_value(), 2, 0)?;
        let rev_tail =
            self.extract_adt_field(rev_list.as_basic_value().into_pointer_value(), 2, 1)?;
        let rev_new_cons = self.build_cons(rev_head.into(), rev_acc.as_basic_value())?;
        self.builder()
            .build_unconditional_branch(rev_header)
            .map_err(|e| CodegenError::Internal(format!("branch: {:?}", e)))?;

        rev_acc.add_incoming(&[(&nil2, loop_exit), (&rev_new_cons, rev_body)]);
        rev_list.add_incoming(&[
            (&acc_at_exit.as_basic_value(), loop_exit),
            (&rev_tail, rev_body),
        ]);

        self.builder().position_at_end(rev_exit);
        Ok(Some(rev_acc.as_basic_value()))
    }

    /// Lower `dropWhile` — drop longest prefix where predicate holds.
    fn lower_builtin_drop_while(
        &mut self,
        pred_expr: &Expr,
        list_expr: &Expr,
    ) -> CodegenResult<Option<BasicValueEnum<'ctx>>> {
        let pred_val = self
            .lower_expr(pred_expr)?
            .ok_or_else(|| CodegenError::Internal("dropWhile: no predicate".to_string()))?;
        let pred_ptr = match pred_val {
            BasicValueEnum::PointerValue(p) => p,
            _ => {
                return Err(CodegenError::TypeError(
                    "dropWhile: predicate must be a closure".to_string(),
                ))
            }
        };
        let list_val = self
            .lower_expr(list_expr)?
            .ok_or_else(|| CodegenError::Internal("dropWhile: no list".to_string()))?;
        let list_ptr = match list_val {
            BasicValueEnum::PointerValue(p) => p,
            _ => {
                return Err(CodegenError::TypeError(
                    "dropWhile expects a list".to_string(),
                ))
            }
        };

        let tm = self.type_mapper();
        let ptr_type = tm.ptr_type();
        let current_fn = self
            .builder()
            .get_insert_block()
            .and_then(|b| b.get_parent())
            .ok_or_else(|| CodegenError::Internal("no current function".to_string()))?;
        let entry_block = self
            .builder()
            .get_insert_block()
            .ok_or_else(|| CodegenError::Internal("no current block".to_string()))?;

        let loop_header = self.llvm_ctx.append_basic_block(current_fn, "dw_header");
        let loop_body = self.llvm_ctx.append_basic_block(current_fn, "dw_body");
        let loop_exit = self.llvm_ctx.append_basic_block(current_fn, "dw_exit");

        self.builder()
            .build_unconditional_branch(loop_header)
            .map_err(|e| CodegenError::Internal(format!("branch: {:?}", e)))?;

        self.builder().position_at_end(loop_header);
        let list_phi = self
            .builder()
            .build_phi(ptr_type, "dw_list")
            .map_err(|e| CodegenError::Internal(format!("phi: {:?}", e)))?;

        let tag = self.extract_adt_tag(list_phi.as_basic_value().into_pointer_value())?;
        let is_empty = self
            .builder()
            .build_int_compare(
                inkwell::IntPredicate::EQ,
                tag,
                tm.i64_type().const_zero(),
                "is_empty",
            )
            .map_err(|e| CodegenError::Internal(format!("cmp: {:?}", e)))?;
        self.builder()
            .build_conditional_branch(is_empty, loop_exit, loop_body)
            .map_err(|e| CodegenError::Internal(format!("branch: {:?}", e)))?;

        self.builder().position_at_end(loop_body);
        let head_ptr =
            self.extract_adt_field(list_phi.as_basic_value().into_pointer_value(), 2, 0)?;
        let tail_ptr =
            self.extract_adt_field(list_phi.as_basic_value().into_pointer_value(), 2, 1)?;

        let pred_fn_ptr = self.extract_closure_fn_ptr(pred_ptr)?;
        let fn_type = ptr_type.fn_type(&[ptr_type.into(), ptr_type.into()], false);
        let pred_result = self
            .builder()
            .build_indirect_call(
                fn_type,
                pred_fn_ptr,
                &[pred_ptr.into(), head_ptr.into()],
                "pred_result",
            )
            .map_err(|e| CodegenError::Internal(format!("call predicate: {:?}", e)))?
            .try_as_basic_value()
            .basic()
            .ok_or_else(|| CodegenError::Internal("predicate returned void".to_string()))?;
        // Handle both tagged-int-as-pointer (0/1) and Bool ADT (heap struct with tag)
        let pred_tag = self.extract_bool_tag(pred_result.into_pointer_value())?;
        // Capture actual block after extract_bool_tag (may have created new blocks)
        let pred_check_block = self.builder().get_insert_block().ok_or_else(|| {
            CodegenError::Internal("dropWhile: no block after bool check".to_string())
        })?;
        let is_true = self
            .builder()
            .build_int_compare(
                inkwell::IntPredicate::NE,
                pred_tag,
                tm.i64_type().const_zero(),
                "is_true",
            )
            .map_err(|e| CodegenError::Internal(format!("cmp: {:?}", e)))?;

        // If pred true, continue dropping (advance to tail); if false, stop
        self.builder()
            .build_conditional_branch(is_true, loop_header, loop_exit)
            .map_err(|e| CodegenError::Internal(format!("branch: {:?}", e)))?;

        // Note: when branching back from body, we use tail_ptr
        list_phi.add_incoming(&[(&list_ptr, entry_block), (&tail_ptr, pred_check_block)]);

        // Exit: return the remaining list
        self.builder().position_at_end(loop_exit);
        let result_phi = self
            .builder()
            .build_phi(ptr_type, "dw_result")
            .map_err(|e| CodegenError::Internal(format!("phi: {:?}", e)))?;
        result_phi.add_incoming(&[
            (&list_phi.as_basic_value(), loop_header), // empty list case
            (&list_phi.as_basic_value(), pred_check_block), // pred became false case
        ]);

        Ok(Some(result_phi.as_basic_value()))
    }

    /// Lower `span :: (a -> Bool) -> [a] -> ([a], [a])`.
    /// Takes the longest prefix of elements satisfying the predicate.
    /// Returns (prefix, rest) as a pair ADT (tag=0, arity=2).
    fn lower_builtin_span(
        &mut self,
        pred_expr: &Expr,
        list_expr: &Expr,
    ) -> CodegenResult<Option<BasicValueEnum<'ctx>>> {
        // Lower predicate closure
        let pred_val = self
            .lower_expr(pred_expr)?
            .ok_or_else(|| CodegenError::Internal("span: predicate has no value".to_string()))?;
        let pred_ptr = match pred_val {
            BasicValueEnum::PointerValue(p) => p,
            _ => {
                return Err(CodegenError::TypeError(
                    "span: predicate must be a closure".to_string(),
                ))
            }
        };

        // Lower the list
        let list_val = self
            .lower_expr(list_expr)?
            .ok_or_else(|| CodegenError::Internal("span: list has no value".to_string()))?;
        let list_ptr = match list_val {
            BasicValueEnum::PointerValue(p) => p,
            _ => return Err(CodegenError::TypeError("span expects a list".to_string())),
        };

        let tm = self.type_mapper();
        let ptr_type = tm.ptr_type();
        let current_fn = self
            .builder()
            .get_insert_block()
            .and_then(|b| b.get_parent())
            .ok_or_else(|| CodegenError::Internal("no current function".to_string()))?;
        let entry_block = self
            .builder()
            .get_insert_block()
            .ok_or_else(|| CodegenError::Internal("no current block".to_string()))?;

        let loop_header = self.llvm_ctx.append_basic_block(current_fn, "span_header");
        let loop_body = self.llvm_ctx.append_basic_block(current_fn, "span_body");
        let span_keep = self.llvm_ctx.append_basic_block(current_fn, "span_keep");
        let loop_exit = self.llvm_ctx.append_basic_block(current_fn, "span_exit");

        let nil = self.build_nil()?;

        self.builder()
            .build_unconditional_branch(loop_header)
            .map_err(|e| CodegenError::Internal(format!("branch: {:?}", e)))?;

        // Loop header: phi for reversed prefix and current list
        self.builder().position_at_end(loop_header);
        let prefix_phi = self
            .builder()
            .build_phi(ptr_type, "span_prefix")
            .map_err(|e| CodegenError::Internal(format!("phi: {:?}", e)))?;
        let list_phi = self
            .builder()
            .build_phi(ptr_type, "span_list")
            .map_err(|e| CodegenError::Internal(format!("phi: {:?}", e)))?;

        // Check if list is empty
        let tag = self.extract_adt_tag(list_phi.as_basic_value().into_pointer_value())?;
        let is_empty = self
            .builder()
            .build_int_compare(
                inkwell::IntPredicate::EQ,
                tag,
                tm.i64_type().const_zero(),
                "is_empty",
            )
            .map_err(|e| CodegenError::Internal(format!("cmp: {:?}", e)))?;

        self.builder()
            .build_conditional_branch(is_empty, loop_exit, loop_body)
            .map_err(|e| CodegenError::Internal(format!("branch: {:?}", e)))?;

        // Loop body: test predicate on head
        self.builder().position_at_end(loop_body);
        let head_ptr =
            self.extract_adt_field(list_phi.as_basic_value().into_pointer_value(), 2, 0)?;
        let tail_ptr =
            self.extract_adt_field(list_phi.as_basic_value().into_pointer_value(), 2, 1)?;

        // Call predicate
        let pred_fn_ptr = self.extract_closure_fn_ptr(pred_ptr)?;
        let fn_type = ptr_type.fn_type(&[ptr_type.into(), ptr_type.into()], false);
        let pred_result = self
            .builder()
            .build_indirect_call(
                fn_type,
                pred_fn_ptr,
                &[pred_ptr.into(), head_ptr.into()],
                "pred_result",
            )
            .map_err(|e| CodegenError::Internal(format!("call predicate: {:?}", e)))?
            .try_as_basic_value()
            .basic()
            .ok_or_else(|| CodegenError::Internal("predicate returned void".to_string()))?;

        // Handle both tagged-int-as-pointer (0/1) and Bool ADT (heap struct with tag)
        let pred_tag = self.extract_bool_tag(pred_result.into_pointer_value())?;
        // Capture actual block after extract_bool_tag (may have created new blocks)
        let pred_check_block = self
            .builder()
            .get_insert_block()
            .ok_or_else(|| CodegenError::Internal("span: no block after bool check".to_string()))?;
        let is_true = self
            .builder()
            .build_int_compare(
                inkwell::IntPredicate::NE,
                pred_tag,
                tm.i64_type().const_zero(),
                "is_true",
            )
            .map_err(|e| CodegenError::Internal(format!("cmp: {:?}", e)))?;

        self.builder()
            .build_conditional_branch(is_true, span_keep, loop_exit)
            .map_err(|e| CodegenError::Internal(format!("branch: {:?}", e)))?;

        // Keep block: cons head onto prefix, continue with tail
        self.builder().position_at_end(span_keep);
        let new_cons = self.build_cons(head_ptr.into(), prefix_phi.as_basic_value())?;
        self.builder()
            .build_unconditional_branch(loop_header)
            .map_err(|e| CodegenError::Internal(format!("branch: {:?}", e)))?;

        // Add phi incoming values
        prefix_phi.add_incoming(&[(&nil, entry_block), (&new_cons, span_keep)]);
        list_phi.add_incoming(&[(&list_ptr, entry_block), (&tail_ptr, span_keep)]);

        // Exit: we have reversed prefix and rest = current list_phi
        self.builder().position_at_end(loop_exit);

        // Phi nodes for values arriving at exit from header (empty list) or pred_check_block (pred false)
        let rest_phi = self
            .builder()
            .build_phi(ptr_type, "span_rest")
            .map_err(|e| CodegenError::Internal(format!("phi: {:?}", e)))?;
        rest_phi.add_incoming(&[
            (&list_phi.as_basic_value(), loop_header),
            (&list_phi.as_basic_value(), pred_check_block),
        ]);

        let prefix_at_exit = self
            .builder()
            .build_phi(ptr_type, "span_prefix_exit")
            .map_err(|e| CodegenError::Internal(format!("phi: {:?}", e)))?;
        prefix_at_exit.add_incoming(&[
            (&prefix_phi.as_basic_value(), loop_header),
            (&prefix_phi.as_basic_value(), pred_check_block),
        ]);

        // Inline reverse of prefix
        let rev_header = self
            .llvm_ctx
            .append_basic_block(current_fn, "span_rev_header");
        let rev_body = self
            .llvm_ctx
            .append_basic_block(current_fn, "span_rev_body");
        let rev_exit = self
            .llvm_ctx
            .append_basic_block(current_fn, "span_rev_exit");

        let nil2 = self.build_nil()?;

        self.builder()
            .build_unconditional_branch(rev_header)
            .map_err(|e| CodegenError::Internal(format!("branch: {:?}", e)))?;

        self.builder().position_at_end(rev_header);
        let rev_acc = self
            .builder()
            .build_phi(ptr_type, "rev_acc")
            .map_err(|e| CodegenError::Internal(format!("phi: {:?}", e)))?;
        let rev_list = self
            .builder()
            .build_phi(ptr_type, "rev_list")
            .map_err(|e| CodegenError::Internal(format!("phi: {:?}", e)))?;

        let rev_tag = self.extract_adt_tag(rev_list.as_basic_value().into_pointer_value())?;
        let rev_is_empty = self
            .builder()
            .build_int_compare(
                inkwell::IntPredicate::EQ,
                rev_tag,
                tm.i64_type().const_zero(),
                "rev_is_empty",
            )
            .map_err(|e| CodegenError::Internal(format!("cmp: {:?}", e)))?;

        self.builder()
            .build_conditional_branch(rev_is_empty, rev_exit, rev_body)
            .map_err(|e| CodegenError::Internal(format!("branch: {:?}", e)))?;

        self.builder().position_at_end(rev_body);
        let rev_head =
            self.extract_adt_field(rev_list.as_basic_value().into_pointer_value(), 2, 0)?;
        let rev_tail =
            self.extract_adt_field(rev_list.as_basic_value().into_pointer_value(), 2, 1)?;
        let rev_new_cons = self.build_cons(rev_head.into(), rev_acc.as_basic_value())?;

        self.builder()
            .build_unconditional_branch(rev_header)
            .map_err(|e| CodegenError::Internal(format!("branch: {:?}", e)))?;

        rev_acc.add_incoming(&[(&nil2, loop_exit), (&rev_new_cons, rev_body)]);
        rev_list.add_incoming(&[
            (&prefix_at_exit.as_basic_value(), loop_exit),
            (&rev_tail, rev_body),
        ]);

        // Build pair (reversed_prefix, rest) as ADT (tag=0, arity=2)
        self.builder().position_at_end(rev_exit);
        let pair = self.alloc_adt(0, 2)?;
        self.store_adt_field(pair, 2, 0, rev_acc.as_basic_value())?;
        self.store_adt_field(pair, 2, 1, rest_phi.as_basic_value())?;

        Ok(Some(pair.into()))
    }

    /// Lower `break` — split list at first element where predicate is True.
    /// break p xs = span (not . p) xs
    /// Takes prefix where pred is False, rest starts where pred becomes True.
    fn lower_builtin_break(
        &mut self,
        pred_expr: &Expr,
        list_expr: &Expr,
    ) -> CodegenResult<Option<BasicValueEnum<'ctx>>> {
        let pred_val = self
            .lower_expr(pred_expr)?
            .ok_or_else(|| CodegenError::Internal("break: no predicate".to_string()))?;
        let pred_ptr = match pred_val {
            BasicValueEnum::PointerValue(p) => p,
            _ => {
                return Err(CodegenError::TypeError(
                    "break: predicate must be a closure".to_string(),
                ))
            }
        };
        let list_val = self
            .lower_expr(list_expr)?
            .ok_or_else(|| CodegenError::Internal("break: no list".to_string()))?;
        let list_ptr = match list_val {
            BasicValueEnum::PointerValue(p) => p,
            _ => return Err(CodegenError::TypeError("break expects a list".to_string())),
        };

        let tm = self.type_mapper();
        let ptr_type = tm.ptr_type();
        let current_fn = self
            .builder()
            .get_insert_block()
            .and_then(|b| b.get_parent())
            .ok_or_else(|| CodegenError::Internal("no current function".to_string()))?;
        let entry_block = self
            .builder()
            .get_insert_block()
            .ok_or_else(|| CodegenError::Internal("no current block".to_string()))?;

        let loop_header = self.llvm_ctx.append_basic_block(current_fn, "break_header");
        let loop_body = self.llvm_ctx.append_basic_block(current_fn, "break_body");
        let break_keep = self.llvm_ctx.append_basic_block(current_fn, "break_keep");
        let loop_exit = self.llvm_ctx.append_basic_block(current_fn, "break_exit");

        let nil = self.build_nil()?;

        self.builder()
            .build_unconditional_branch(loop_header)
            .map_err(|e| CodegenError::Internal(format!("branch: {:?}", e)))?;

        self.builder().position_at_end(loop_header);
        let prefix_phi = self
            .builder()
            .build_phi(ptr_type, "break_prefix")
            .map_err(|e| CodegenError::Internal(format!("phi: {:?}", e)))?;
        let list_phi = self
            .builder()
            .build_phi(ptr_type, "break_list")
            .map_err(|e| CodegenError::Internal(format!("phi: {:?}", e)))?;

        let tag = self.extract_adt_tag(list_phi.as_basic_value().into_pointer_value())?;
        let is_empty = self
            .builder()
            .build_int_compare(
                inkwell::IntPredicate::EQ,
                tag,
                tm.i64_type().const_zero(),
                "is_empty",
            )
            .map_err(|e| CodegenError::Internal(format!("cmp: {:?}", e)))?;

        self.builder()
            .build_conditional_branch(is_empty, loop_exit, loop_body)
            .map_err(|e| CodegenError::Internal(format!("branch: {:?}", e)))?;

        // Loop body: test predicate
        self.builder().position_at_end(loop_body);
        let head_ptr =
            self.extract_adt_field(list_phi.as_basic_value().into_pointer_value(), 2, 0)?;
        let tail_ptr =
            self.extract_adt_field(list_phi.as_basic_value().into_pointer_value(), 2, 1)?;

        let pred_fn_ptr = self.extract_closure_fn_ptr(pred_ptr)?;
        let fn_type = ptr_type.fn_type(&[ptr_type.into(), ptr_type.into()], false);
        let pred_result = self
            .builder()
            .build_indirect_call(
                fn_type,
                pred_fn_ptr,
                &[pred_ptr.into(), head_ptr.into()],
                "pred_result",
            )
            .map_err(|e| CodegenError::Internal(format!("call predicate: {:?}", e)))?
            .try_as_basic_value()
            .basic()
            .ok_or_else(|| CodegenError::Internal("predicate returned void".to_string()))?;

        // Handle both tagged-int-as-pointer (0/1) and Bool ADT (heap struct with tag)
        let pred_tag = self.extract_bool_tag(pred_result.into_pointer_value())?;
        // Capture actual block after extract_bool_tag (may have created new blocks)
        let pred_check_block = self.builder().get_insert_block().ok_or_else(|| {
            CodegenError::Internal("break: no block after bool check".to_string())
        })?;
        // break stops when pred is TRUE (opposite of span)
        let is_true = self
            .builder()
            .build_int_compare(
                inkwell::IntPredicate::NE,
                pred_tag,
                tm.i64_type().const_zero(),
                "is_true",
            )
            .map_err(|e| CodegenError::Internal(format!("cmp: {:?}", e)))?;

        // If pred is true, exit (break found). If false, keep going.
        self.builder()
            .build_conditional_branch(is_true, loop_exit, break_keep)
            .map_err(|e| CodegenError::Internal(format!("branch: {:?}", e)))?;

        // Keep block: pred is false, cons head onto prefix
        self.builder().position_at_end(break_keep);
        let new_cons = self.build_cons(head_ptr.into(), prefix_phi.as_basic_value())?;
        self.builder()
            .build_unconditional_branch(loop_header)
            .map_err(|e| CodegenError::Internal(format!("branch: {:?}", e)))?;

        prefix_phi.add_incoming(&[(&nil, entry_block), (&new_cons, break_keep)]);
        list_phi.add_incoming(&[(&list_ptr, entry_block), (&tail_ptr, break_keep)]);

        // Exit
        self.builder().position_at_end(loop_exit);

        let rest_phi = self
            .builder()
            .build_phi(ptr_type, "break_rest")
            .map_err(|e| CodegenError::Internal(format!("phi: {:?}", e)))?;
        rest_phi.add_incoming(&[
            (&list_phi.as_basic_value(), loop_header),
            (&list_phi.as_basic_value(), pred_check_block),
        ]);

        let prefix_at_exit = self
            .builder()
            .build_phi(ptr_type, "break_prefix_exit")
            .map_err(|e| CodegenError::Internal(format!("phi: {:?}", e)))?;
        prefix_at_exit.add_incoming(&[
            (&prefix_phi.as_basic_value(), loop_header),
            (&prefix_phi.as_basic_value(), pred_check_block),
        ]);

        // Inline reverse prefix
        let rev_header = self
            .llvm_ctx
            .append_basic_block(current_fn, "brk_rev_header");
        let rev_body = self.llvm_ctx.append_basic_block(current_fn, "brk_rev_body");
        let rev_exit = self.llvm_ctx.append_basic_block(current_fn, "brk_rev_exit");
        let nil2 = self.build_nil()?;
        self.builder()
            .build_unconditional_branch(rev_header)
            .map_err(|e| CodegenError::Internal(format!("branch: {:?}", e)))?;

        self.builder().position_at_end(rev_header);
        let rev_acc = self
            .builder()
            .build_phi(ptr_type, "rev_acc")
            .map_err(|e| CodegenError::Internal(format!("phi: {:?}", e)))?;
        let rev_list = self
            .builder()
            .build_phi(ptr_type, "rev_list")
            .map_err(|e| CodegenError::Internal(format!("phi: {:?}", e)))?;
        let rev_tag = self.extract_adt_tag(rev_list.as_basic_value().into_pointer_value())?;
        let rev_is_empty = self
            .builder()
            .build_int_compare(
                inkwell::IntPredicate::EQ,
                rev_tag,
                tm.i64_type().const_zero(),
                "rev_is_empty",
            )
            .map_err(|e| CodegenError::Internal(format!("cmp: {:?}", e)))?;
        self.builder()
            .build_conditional_branch(rev_is_empty, rev_exit, rev_body)
            .map_err(|e| CodegenError::Internal(format!("branch: {:?}", e)))?;

        self.builder().position_at_end(rev_body);
        let rev_head =
            self.extract_adt_field(rev_list.as_basic_value().into_pointer_value(), 2, 0)?;
        let rev_tail =
            self.extract_adt_field(rev_list.as_basic_value().into_pointer_value(), 2, 1)?;
        let rev_new_cons = self.build_cons(rev_head.into(), rev_acc.as_basic_value())?;
        self.builder()
            .build_unconditional_branch(rev_header)
            .map_err(|e| CodegenError::Internal(format!("branch: {:?}", e)))?;

        rev_acc.add_incoming(&[(&nil2, loop_exit), (&rev_new_cons, rev_body)]);
        rev_list.add_incoming(&[
            (&prefix_at_exit.as_basic_value(), loop_exit),
            (&rev_tail, rev_body),
        ]);

        self.builder().position_at_end(rev_exit);
        let pair = self.alloc_adt(0, 2)?;
        self.store_adt_field(pair, 2, 0, rev_acc.as_basic_value())?;
        self.store_adt_field(pair, 2, 1, rest_phi.as_basic_value())?;

        Ok(Some(pair.into()))
    }

    /// Lower `splitAt` — split list at position n.
    /// splitAt n xs = (take n xs, drop n xs)
    fn lower_builtin_split_at(
        &mut self,
        n_expr: &Expr,
        list_expr: &Expr,
    ) -> CodegenResult<Option<BasicValueEnum<'ctx>>> {
        let n_val = self
            .lower_expr(n_expr)?
            .ok_or_else(|| CodegenError::Internal("splitAt: no n".to_string()))?;
        let n_int = self.coerce_to_int(n_val)?;
        let list_val = self
            .lower_expr(list_expr)?
            .ok_or_else(|| CodegenError::Internal("splitAt: no list".to_string()))?;
        let list_ptr = match list_val {
            BasicValueEnum::PointerValue(p) => p,
            _ => {
                return Err(CodegenError::TypeError(
                    "splitAt expects a list".to_string(),
                ))
            }
        };

        let tm = self.type_mapper();
        let ptr_type = tm.ptr_type();
        let i64_type = tm.i64_type();
        let current_fn = self
            .builder()
            .get_insert_block()
            .and_then(|b| b.get_parent())
            .ok_or_else(|| CodegenError::Internal("no current function".to_string()))?;
        let entry_block = self
            .builder()
            .get_insert_block()
            .ok_or_else(|| CodegenError::Internal("no current block".to_string()))?;

        let loop_header = self
            .llvm_ctx
            .append_basic_block(current_fn, "splitat_header");
        let loop_body = self.llvm_ctx.append_basic_block(current_fn, "splitat_body");
        let loop_exit = self.llvm_ctx.append_basic_block(current_fn, "splitat_exit");

        let nil = self.build_nil()?;

        self.builder()
            .build_unconditional_branch(loop_header)
            .map_err(|e| CodegenError::Internal(format!("branch: {:?}", e)))?;

        self.builder().position_at_end(loop_header);
        let prefix_phi = self
            .builder()
            .build_phi(ptr_type, "splitat_prefix")
            .map_err(|e| CodegenError::Internal(format!("phi: {:?}", e)))?;
        let list_phi = self
            .builder()
            .build_phi(ptr_type, "splitat_list")
            .map_err(|e| CodegenError::Internal(format!("phi: {:?}", e)))?;
        let counter_phi = self
            .builder()
            .build_phi(i64_type, "splitat_counter")
            .map_err(|e| CodegenError::Internal(format!("phi: {:?}", e)))?;

        // Check if counter reached n or list is empty
        let counter_done = self
            .builder()
            .build_int_compare(
                inkwell::IntPredicate::SGE,
                counter_phi.as_basic_value().into_int_value(),
                n_int,
                "counter_done",
            )
            .map_err(|e| CodegenError::Internal(format!("cmp: {:?}", e)))?;

        let tag = self.extract_adt_tag(list_phi.as_basic_value().into_pointer_value())?;
        let is_empty = self
            .builder()
            .build_int_compare(
                inkwell::IntPredicate::EQ,
                tag,
                i64_type.const_zero(),
                "is_empty",
            )
            .map_err(|e| CodegenError::Internal(format!("cmp: {:?}", e)))?;

        let should_stop = self
            .builder()
            .build_or(counter_done, is_empty, "should_stop")
            .map_err(|e| CodegenError::Internal(format!("or: {:?}", e)))?;

        self.builder()
            .build_conditional_branch(should_stop, loop_exit, loop_body)
            .map_err(|e| CodegenError::Internal(format!("branch: {:?}", e)))?;

        // Loop body: take one element
        self.builder().position_at_end(loop_body);
        let head_ptr =
            self.extract_adt_field(list_phi.as_basic_value().into_pointer_value(), 2, 0)?;
        let tail_ptr =
            self.extract_adt_field(list_phi.as_basic_value().into_pointer_value(), 2, 1)?;
        let new_cons = self.build_cons(head_ptr.into(), prefix_phi.as_basic_value())?;
        let next_counter = self
            .builder()
            .build_int_add(
                counter_phi.as_basic_value().into_int_value(),
                i64_type.const_int(1, false),
                "next_counter",
            )
            .map_err(|e| CodegenError::Internal(format!("add: {:?}", e)))?;

        self.builder()
            .build_unconditional_branch(loop_header)
            .map_err(|e| CodegenError::Internal(format!("branch: {:?}", e)))?;

        prefix_phi.add_incoming(&[(&nil, entry_block), (&new_cons, loop_body)]);
        list_phi.add_incoming(&[(&list_ptr, entry_block), (&tail_ptr, loop_body)]);
        counter_phi.add_incoming(&[
            (&i64_type.const_zero(), entry_block),
            (&next_counter, loop_body),
        ]);

        // Exit: reverse prefix, rest = current list
        self.builder().position_at_end(loop_exit);

        // Inline reverse
        let rev_header = self
            .llvm_ctx
            .append_basic_block(current_fn, "sa_rev_header");
        let rev_body = self.llvm_ctx.append_basic_block(current_fn, "sa_rev_body");
        let rev_exit = self.llvm_ctx.append_basic_block(current_fn, "sa_rev_exit");
        let nil2 = self.build_nil()?;
        self.builder()
            .build_unconditional_branch(rev_header)
            .map_err(|e| CodegenError::Internal(format!("branch: {:?}", e)))?;

        self.builder().position_at_end(rev_header);
        let rev_acc = self
            .builder()
            .build_phi(ptr_type, "rev_acc")
            .map_err(|e| CodegenError::Internal(format!("phi: {:?}", e)))?;
        let rev_list = self
            .builder()
            .build_phi(ptr_type, "rev_list")
            .map_err(|e| CodegenError::Internal(format!("phi: {:?}", e)))?;
        let rev_tag = self.extract_adt_tag(rev_list.as_basic_value().into_pointer_value())?;
        let rev_is_empty = self
            .builder()
            .build_int_compare(
                inkwell::IntPredicate::EQ,
                rev_tag,
                i64_type.const_zero(),
                "rev_is_empty",
            )
            .map_err(|e| CodegenError::Internal(format!("cmp: {:?}", e)))?;
        self.builder()
            .build_conditional_branch(rev_is_empty, rev_exit, rev_body)
            .map_err(|e| CodegenError::Internal(format!("branch: {:?}", e)))?;

        self.builder().position_at_end(rev_body);
        let rev_head =
            self.extract_adt_field(rev_list.as_basic_value().into_pointer_value(), 2, 0)?;
        let rev_tail =
            self.extract_adt_field(rev_list.as_basic_value().into_pointer_value(), 2, 1)?;
        let rev_new_cons = self.build_cons(rev_head.into(), rev_acc.as_basic_value())?;
        self.builder()
            .build_unconditional_branch(rev_header)
            .map_err(|e| CodegenError::Internal(format!("branch: {:?}", e)))?;

        rev_acc.add_incoming(&[(&nil2, loop_exit), (&rev_new_cons, rev_body)]);
        rev_list.add_incoming(&[
            (&prefix_phi.as_basic_value(), loop_exit),
            (&rev_tail, rev_body),
        ]);

        self.builder().position_at_end(rev_exit);
        let pair = self.alloc_adt(0, 2)?;
        self.store_adt_field(pair, 2, 0, rev_acc.as_basic_value())?;
        self.store_adt_field(pair, 2, 1, list_phi.as_basic_value())?;

        Ok(Some(pair.into()))
    }

    /// Lower `find :: (a -> Bool) -> [a] -> Maybe a`.
    /// Traverses the list, applying predicate to each element.
    /// Returns Just element for the first match, or Nothing if none found.
    fn lower_builtin_find(
        &mut self,
        pred_expr: &Expr,
        list_expr: &Expr,
    ) -> CodegenResult<Option<BasicValueEnum<'ctx>>> {
        // Lower predicate closure
        let pred_val = self
            .lower_expr(pred_expr)?
            .ok_or_else(|| CodegenError::Internal("find: predicate has no value".to_string()))?;
        let pred_ptr = match pred_val {
            BasicValueEnum::PointerValue(p) => p,
            _ => {
                return Err(CodegenError::TypeError(
                    "find: predicate must be a closure".to_string(),
                ))
            }
        };

        // Lower the list
        let list_val = self
            .lower_expr(list_expr)?
            .ok_or_else(|| CodegenError::Internal("find: list has no value".to_string()))?;
        let list_ptr = match list_val {
            BasicValueEnum::PointerValue(p) => p,
            _ => return Err(CodegenError::TypeError("find expects a list".to_string())),
        };

        let tm = self.type_mapper();
        let ptr_type = tm.ptr_type();
        let current_fn = self
            .builder()
            .get_insert_block()
            .and_then(|b| b.get_parent())
            .ok_or_else(|| CodegenError::Internal("no current function".to_string()))?;
        let entry_block = self
            .builder()
            .get_insert_block()
            .ok_or_else(|| CodegenError::Internal("no current block".to_string()))?;

        let loop_header = self.llvm_ctx.append_basic_block(current_fn, "find_header");
        let loop_body = self.llvm_ctx.append_basic_block(current_fn, "find_body");
        let found_block = self.llvm_ctx.append_basic_block(current_fn, "find_found");
        let not_found_block = self
            .llvm_ctx
            .append_basic_block(current_fn, "find_not_found");
        let merge_block = self.llvm_ctx.append_basic_block(current_fn, "find_merge");

        self.builder()
            .build_unconditional_branch(loop_header)
            .map_err(|e| CodegenError::Internal(format!("branch: {:?}", e)))?;

        // Loop header: phi for current list pointer
        self.builder().position_at_end(loop_header);
        let list_phi = self
            .builder()
            .build_phi(ptr_type, "find_list")
            .map_err(|e| CodegenError::Internal(format!("phi: {:?}", e)))?;

        // Check if list is empty (tag == 0 means nil)
        let tag = self.extract_adt_tag(list_phi.as_basic_value().into_pointer_value())?;
        let is_empty = self
            .builder()
            .build_int_compare(
                inkwell::IntPredicate::EQ,
                tag,
                tm.i64_type().const_zero(),
                "is_empty",
            )
            .map_err(|e| CodegenError::Internal(format!("cmp: {:?}", e)))?;

        self.builder()
            .build_conditional_branch(is_empty, not_found_block, loop_body)
            .map_err(|e| CodegenError::Internal(format!("branch: {:?}", e)))?;

        // Loop body: extract head, apply predicate
        self.builder().position_at_end(loop_body);
        let head_ptr =
            self.extract_adt_field(list_phi.as_basic_value().into_pointer_value(), 2, 0)?;
        let tail_ptr =
            self.extract_adt_field(list_phi.as_basic_value().into_pointer_value(), 2, 1)?;

        // Call predicate: pred(pred_ptr, head)
        let pred_fn_ptr = self.extract_closure_fn_ptr(pred_ptr)?;
        let fn_type = ptr_type.fn_type(&[ptr_type.into(), ptr_type.into()], false);
        let pred_result = self
            .builder()
            .build_indirect_call(
                fn_type,
                pred_fn_ptr,
                &[pred_ptr.into(), head_ptr.into()],
                "pred_result",
            )
            .map_err(|e| CodegenError::Internal(format!("call predicate: {:?}", e)))?
            .try_as_basic_value()
            .basic()
            .ok_or_else(|| CodegenError::Internal("predicate returned void".to_string()))?;

        // Check boolean result
        // Handle both tagged-int-as-pointer (0/1) and Bool ADT (heap struct with tag)
        let pred_bool = self.extract_bool_tag(pred_result.into_pointer_value())?;
        // Capture actual block after extract_bool_tag (may have created new blocks)
        let pred_check_block = self
            .builder()
            .get_insert_block()
            .ok_or_else(|| CodegenError::Internal("find: no block after bool check".to_string()))?;
        let is_true = self
            .builder()
            .build_int_compare(
                inkwell::IntPredicate::NE,
                pred_bool,
                tm.i64_type().const_zero(),
                "is_true",
            )
            .map_err(|e| CodegenError::Internal(format!("cmp: {:?}", e)))?;

        self.builder()
            .build_conditional_branch(is_true, found_block, loop_header)
            .map_err(|e| CodegenError::Internal(format!("branch: {:?}", e)))?;

        // Add phi incoming for list_phi
        list_phi.add_incoming(&[(&list_ptr, entry_block), (&tail_ptr, pred_check_block)]);

        // Found block: return Just element (tag=1, arity=1)
        self.builder().position_at_end(found_block);
        let just = self.alloc_adt(1, 1)?;
        self.store_adt_field(just, 1, 0, head_ptr.into())?;
        self.builder()
            .build_unconditional_branch(merge_block)
            .map_err(|e| CodegenError::Internal(format!("branch: {:?}", e)))?;

        // Not found block: return Nothing (tag=0, arity=0)
        self.builder().position_at_end(not_found_block);
        let nothing = self.alloc_adt(0, 0)?;
        self.builder()
            .build_unconditional_branch(merge_block)
            .map_err(|e| CodegenError::Internal(format!("branch: {:?}", e)))?;

        // Merge block: phi to select Just or Nothing
        self.builder().position_at_end(merge_block);
        let result_phi = self
            .builder()
            .build_phi(ptr_type, "find_result")
            .map_err(|e| CodegenError::Internal(format!("phi: {:?}", e)))?;
        result_phi.add_incoming(&[(&just, found_block), (&nothing, not_found_block)]);

        Ok(Some(result_phi.as_basic_value()))
    }

    /// Lower `lookup :: Eq k => k -> [(k,v)] -> Maybe v`.
    /// Traverses an association list (list of pairs). Each head element is a pair
    /// ADT (tag=0, arity=2) with field 0 = key, field 1 = value.
    /// Compares key via ptr_to_int equality.
    fn lower_builtin_lookup(
        &mut self,
        key_expr: &Expr,
        list_expr: &Expr,
    ) -> CodegenResult<Option<BasicValueEnum<'ctx>>> {
        // Lower the key
        let key_val = self
            .lower_expr(key_expr)?
            .ok_or_else(|| CodegenError::Internal("lookup: key has no value".to_string()))?;
        let key_ptr = self.value_to_ptr(key_val)?;

        // Lower the list
        let list_val = self
            .lower_expr(list_expr)?
            .ok_or_else(|| CodegenError::Internal("lookup: list has no value".to_string()))?;
        let list_ptr = match list_val {
            BasicValueEnum::PointerValue(p) => p,
            _ => return Err(CodegenError::TypeError("lookup expects a list".to_string())),
        };

        let tm = self.type_mapper();
        let ptr_type = tm.ptr_type();
        let current_fn = self
            .builder()
            .get_insert_block()
            .and_then(|b| b.get_parent())
            .ok_or_else(|| CodegenError::Internal("no current function".to_string()))?;
        let entry_block = self
            .builder()
            .get_insert_block()
            .ok_or_else(|| CodegenError::Internal("no current block".to_string()))?;

        let loop_header = self
            .llvm_ctx
            .append_basic_block(current_fn, "lookup_header");
        let loop_body = self.llvm_ctx.append_basic_block(current_fn, "lookup_body");
        let found_block = self.llvm_ctx.append_basic_block(current_fn, "lookup_found");
        let not_found_block = self
            .llvm_ctx
            .append_basic_block(current_fn, "lookup_not_found");
        let merge_block = self.llvm_ctx.append_basic_block(current_fn, "lookup_merge");

        // Convert search key to i64 for comparison
        let key_int = self
            .builder()
            .build_ptr_to_int(key_ptr, tm.i64_type(), "key_int")
            .map_err(|e| CodegenError::Internal(format!("ptr_to_int: {:?}", e)))?;

        self.builder()
            .build_unconditional_branch(loop_header)
            .map_err(|e| CodegenError::Internal(format!("branch: {:?}", e)))?;

        // Loop header
        self.builder().position_at_end(loop_header);
        let list_phi = self
            .builder()
            .build_phi(ptr_type, "lookup_list")
            .map_err(|e| CodegenError::Internal(format!("phi: {:?}", e)))?;

        let tag = self.extract_adt_tag(list_phi.as_basic_value().into_pointer_value())?;
        let is_empty = self
            .builder()
            .build_int_compare(
                inkwell::IntPredicate::EQ,
                tag,
                tm.i64_type().const_zero(),
                "is_empty",
            )
            .map_err(|e| CodegenError::Internal(format!("cmp: {:?}", e)))?;

        self.builder()
            .build_conditional_branch(is_empty, not_found_block, loop_body)
            .map_err(|e| CodegenError::Internal(format!("branch: {:?}", e)))?;

        // Loop body: extract head (a pair), then extract key and value from it
        self.builder().position_at_end(loop_body);
        let head_ptr =
            self.extract_adt_field(list_phi.as_basic_value().into_pointer_value(), 2, 0)?;
        let tail_ptr =
            self.extract_adt_field(list_phi.as_basic_value().into_pointer_value(), 2, 1)?;

        // head_ptr is a pair (tag=0, arity=2): extract field 0 (key) and field 1 (value)
        let pair_key = self.extract_adt_field(head_ptr, 2, 0)?;
        let pair_val = self.extract_adt_field(head_ptr, 2, 1)?;

        // Compare pair key with search key
        let pair_key_int = self
            .builder()
            .build_ptr_to_int(pair_key, tm.i64_type(), "pair_key_int")
            .map_err(|e| CodegenError::Internal(format!("ptr_to_int: {:?}", e)))?;
        let is_match = self
            .builder()
            .build_int_compare(inkwell::IntPredicate::EQ, pair_key_int, key_int, "is_match")
            .map_err(|e| CodegenError::Internal(format!("cmp: {:?}", e)))?;

        self.builder()
            .build_conditional_branch(is_match, found_block, loop_header)
            .map_err(|e| CodegenError::Internal(format!("branch: {:?}", e)))?;

        // Add phi incoming
        list_phi.add_incoming(&[(&list_ptr, entry_block), (&tail_ptr, loop_body)]);

        // Found: return Just value (tag=1, arity=1)
        self.builder().position_at_end(found_block);
        let just = self.alloc_adt(1, 1)?;
        self.store_adt_field(just, 1, 0, pair_val.into())?;
        self.builder()
            .build_unconditional_branch(merge_block)
            .map_err(|e| CodegenError::Internal(format!("branch: {:?}", e)))?;

        // Not found: return Nothing (tag=0, arity=0)
        self.builder().position_at_end(not_found_block);
        let nothing = self.alloc_adt(0, 0)?;
        self.builder()
            .build_unconditional_branch(merge_block)
            .map_err(|e| CodegenError::Internal(format!("branch: {:?}", e)))?;

        // Merge
        self.builder().position_at_end(merge_block);
        let result_phi = self
            .builder()
            .build_phi(ptr_type, "lookup_result")
            .map_err(|e| CodegenError::Internal(format!("phi: {:?}", e)))?;
        result_phi.add_incoming(&[(&just, found_block), (&nothing, not_found_block)]);

        Ok(Some(result_phi.as_basic_value()))
    }

    /// Lower `partition :: (a -> Bool) -> [a] -> ([a], [a])`.
    /// Splits the list into (elements satisfying pred, elements not satisfying).
    /// Both sublists built in reverse during traversal, then reversed.
    fn lower_builtin_partition(
        &mut self,
        pred_expr: &Expr,
        list_expr: &Expr,
    ) -> CodegenResult<Option<BasicValueEnum<'ctx>>> {
        // Lower predicate closure
        let pred_val = self.lower_expr(pred_expr)?.ok_or_else(|| {
            CodegenError::Internal("partition: predicate has no value".to_string())
        })?;
        let pred_ptr = match pred_val {
            BasicValueEnum::PointerValue(p) => p,
            _ => {
                return Err(CodegenError::TypeError(
                    "partition: predicate must be a closure".to_string(),
                ))
            }
        };

        // Lower the list
        let list_val = self
            .lower_expr(list_expr)?
            .ok_or_else(|| CodegenError::Internal("partition: list has no value".to_string()))?;
        let list_ptr = match list_val {
            BasicValueEnum::PointerValue(p) => p,
            _ => {
                return Err(CodegenError::TypeError(
                    "partition expects a list".to_string(),
                ))
            }
        };

        let tm = self.type_mapper();
        let ptr_type = tm.ptr_type();
        let current_fn = self
            .builder()
            .get_insert_block()
            .and_then(|b| b.get_parent())
            .ok_or_else(|| CodegenError::Internal("no current function".to_string()))?;
        let entry_block = self
            .builder()
            .get_insert_block()
            .ok_or_else(|| CodegenError::Internal("no current block".to_string()))?;

        let loop_header = self.llvm_ctx.append_basic_block(current_fn, "part_header");
        let loop_body = self.llvm_ctx.append_basic_block(current_fn, "part_body");
        let part_yes = self.llvm_ctx.append_basic_block(current_fn, "part_yes");
        let part_no = self.llvm_ctx.append_basic_block(current_fn, "part_no");
        let loop_exit = self.llvm_ctx.append_basic_block(current_fn, "part_exit");

        let nil = self.build_nil()?;

        self.builder()
            .build_unconditional_branch(loop_header)
            .map_err(|e| CodegenError::Internal(format!("branch: {:?}", e)))?;

        // Loop header: phi for yes_list (reversed), no_list (reversed), current list
        self.builder().position_at_end(loop_header);
        let yes_phi = self
            .builder()
            .build_phi(ptr_type, "part_yes_list")
            .map_err(|e| CodegenError::Internal(format!("phi: {:?}", e)))?;
        let no_phi = self
            .builder()
            .build_phi(ptr_type, "part_no_list")
            .map_err(|e| CodegenError::Internal(format!("phi: {:?}", e)))?;
        let list_phi = self
            .builder()
            .build_phi(ptr_type, "part_list")
            .map_err(|e| CodegenError::Internal(format!("phi: {:?}", e)))?;

        // Check if list is empty
        let tag = self.extract_adt_tag(list_phi.as_basic_value().into_pointer_value())?;
        let is_empty = self
            .builder()
            .build_int_compare(
                inkwell::IntPredicate::EQ,
                tag,
                tm.i64_type().const_zero(),
                "is_empty",
            )
            .map_err(|e| CodegenError::Internal(format!("cmp: {:?}", e)))?;

        self.builder()
            .build_conditional_branch(is_empty, loop_exit, loop_body)
            .map_err(|e| CodegenError::Internal(format!("branch: {:?}", e)))?;

        // Loop body: test predicate
        self.builder().position_at_end(loop_body);
        let head_ptr =
            self.extract_adt_field(list_phi.as_basic_value().into_pointer_value(), 2, 0)?;
        let tail_ptr =
            self.extract_adt_field(list_phi.as_basic_value().into_pointer_value(), 2, 1)?;

        // Call predicate
        let pred_fn_ptr = self.extract_closure_fn_ptr(pred_ptr)?;
        let fn_type = ptr_type.fn_type(&[ptr_type.into(), ptr_type.into()], false);
        let pred_result = self
            .builder()
            .build_indirect_call(
                fn_type,
                pred_fn_ptr,
                &[pred_ptr.into(), head_ptr.into()],
                "pred_result",
            )
            .map_err(|e| CodegenError::Internal(format!("call predicate: {:?}", e)))?
            .try_as_basic_value()
            .basic()
            .ok_or_else(|| CodegenError::Internal("predicate returned void".to_string()))?;

        let pred_bool = self
            // Handle both tagged-int-as-pointer (0/1) and Bool ADT (heap struct with tag)
            .extract_bool_tag(pred_result.into_pointer_value())?;
        let is_true = self
            .builder()
            .build_int_compare(
                inkwell::IntPredicate::NE,
                pred_bool,
                tm.i64_type().const_zero(),
                "is_true",
            )
            .map_err(|e| CodegenError::Internal(format!("cmp: {:?}", e)))?;

        self.builder()
            .build_conditional_branch(is_true, part_yes, part_no)
            .map_err(|e| CodegenError::Internal(format!("branch: {:?}", e)))?;

        // Yes block: cons head onto yes_list
        self.builder().position_at_end(part_yes);
        let yes_new_cons = self.build_cons(head_ptr.into(), yes_phi.as_basic_value())?;
        self.builder()
            .build_unconditional_branch(loop_header)
            .map_err(|e| CodegenError::Internal(format!("branch: {:?}", e)))?;

        // No block: cons head onto no_list
        self.builder().position_at_end(part_no);
        let no_new_cons = self.build_cons(head_ptr.into(), no_phi.as_basic_value())?;
        self.builder()
            .build_unconditional_branch(loop_header)
            .map_err(|e| CodegenError::Internal(format!("branch: {:?}", e)))?;

        // Add phi incoming values
        yes_phi.add_incoming(&[
            (&nil, entry_block),
            (&yes_new_cons, part_yes),
            (&yes_phi.as_basic_value(), part_no),
        ]);
        no_phi.add_incoming(&[
            (&nil, entry_block),
            (&no_phi.as_basic_value(), part_yes),
            (&no_new_cons, part_no),
        ]);
        list_phi.add_incoming(&[
            (&list_ptr, entry_block),
            (&tail_ptr, part_yes),
            (&tail_ptr, part_no),
        ]);

        // Exit: reverse both lists
        self.builder().position_at_end(loop_exit);

        // --- Reverse yes_list ---
        let rev1_header = self.llvm_ctx.append_basic_block(current_fn, "rev1_header");
        let rev1_body = self.llvm_ctx.append_basic_block(current_fn, "rev1_body");
        let rev1_exit = self.llvm_ctx.append_basic_block(current_fn, "rev1_exit");

        let nil2 = self.build_nil()?;

        self.builder()
            .build_unconditional_branch(rev1_header)
            .map_err(|e| CodegenError::Internal(format!("branch: {:?}", e)))?;

        self.builder().position_at_end(rev1_header);
        let rev1_acc = self
            .builder()
            .build_phi(ptr_type, "rev1_acc")
            .map_err(|e| CodegenError::Internal(format!("phi: {:?}", e)))?;
        let rev1_list = self
            .builder()
            .build_phi(ptr_type, "rev1_list")
            .map_err(|e| CodegenError::Internal(format!("phi: {:?}", e)))?;

        let rev1_tag = self.extract_adt_tag(rev1_list.as_basic_value().into_pointer_value())?;
        let rev1_is_empty = self
            .builder()
            .build_int_compare(
                inkwell::IntPredicate::EQ,
                rev1_tag,
                tm.i64_type().const_zero(),
                "rev1_is_empty",
            )
            .map_err(|e| CodegenError::Internal(format!("cmp: {:?}", e)))?;

        self.builder()
            .build_conditional_branch(rev1_is_empty, rev1_exit, rev1_body)
            .map_err(|e| CodegenError::Internal(format!("branch: {:?}", e)))?;

        self.builder().position_at_end(rev1_body);
        let rev1_head =
            self.extract_adt_field(rev1_list.as_basic_value().into_pointer_value(), 2, 0)?;
        let rev1_tail =
            self.extract_adt_field(rev1_list.as_basic_value().into_pointer_value(), 2, 1)?;
        let rev1_new_cons = self.build_cons(rev1_head.into(), rev1_acc.as_basic_value())?;

        self.builder()
            .build_unconditional_branch(rev1_header)
            .map_err(|e| CodegenError::Internal(format!("branch: {:?}", e)))?;

        rev1_acc.add_incoming(&[(&nil2, loop_exit), (&rev1_new_cons, rev1_body)]);
        rev1_list.add_incoming(&[
            (&yes_phi.as_basic_value(), loop_exit),
            (&rev1_tail, rev1_body),
        ]);

        // --- Reverse no_list ---
        self.builder().position_at_end(rev1_exit);

        let rev2_header = self.llvm_ctx.append_basic_block(current_fn, "rev2_header");
        let rev2_body = self.llvm_ctx.append_basic_block(current_fn, "rev2_body");
        let rev2_exit = self.llvm_ctx.append_basic_block(current_fn, "rev2_exit");

        let nil3 = self.build_nil()?;

        self.builder()
            .build_unconditional_branch(rev2_header)
            .map_err(|e| CodegenError::Internal(format!("branch: {:?}", e)))?;

        self.builder().position_at_end(rev2_header);
        let rev2_acc = self
            .builder()
            .build_phi(ptr_type, "rev2_acc")
            .map_err(|e| CodegenError::Internal(format!("phi: {:?}", e)))?;
        let rev2_list = self
            .builder()
            .build_phi(ptr_type, "rev2_list")
            .map_err(|e| CodegenError::Internal(format!("phi: {:?}", e)))?;

        let rev2_tag = self.extract_adt_tag(rev2_list.as_basic_value().into_pointer_value())?;
        let rev2_is_empty = self
            .builder()
            .build_int_compare(
                inkwell::IntPredicate::EQ,
                rev2_tag,
                tm.i64_type().const_zero(),
                "rev2_is_empty",
            )
            .map_err(|e| CodegenError::Internal(format!("cmp: {:?}", e)))?;

        self.builder()
            .build_conditional_branch(rev2_is_empty, rev2_exit, rev2_body)
            .map_err(|e| CodegenError::Internal(format!("branch: {:?}", e)))?;

        self.builder().position_at_end(rev2_body);
        let rev2_head =
            self.extract_adt_field(rev2_list.as_basic_value().into_pointer_value(), 2, 0)?;
        let rev2_tail =
            self.extract_adt_field(rev2_list.as_basic_value().into_pointer_value(), 2, 1)?;
        let rev2_new_cons = self.build_cons(rev2_head.into(), rev2_acc.as_basic_value())?;

        self.builder()
            .build_unconditional_branch(rev2_header)
            .map_err(|e| CodegenError::Internal(format!("branch: {:?}", e)))?;

        rev2_acc.add_incoming(&[(&nil3, rev1_exit), (&rev2_new_cons, rev2_body)]);
        rev2_list.add_incoming(&[
            (&no_phi.as_basic_value(), rev1_exit),
            (&rev2_tail, rev2_body),
        ]);

        // Build pair (reversed_yes, reversed_no) as ADT (tag=0, arity=2)
        self.builder().position_at_end(rev2_exit);
        let pair = self.alloc_adt(0, 2)?;
        self.store_adt_field(pair, 2, 0, rev1_acc.as_basic_value())?;
        self.store_adt_field(pair, 2, 1, rev2_acc.as_basic_value())?;

        Ok(Some(pair.into()))
    }

    /// Lower `intersperse :: a -> [a] -> [a]`.
    /// Inserts separator between each element. Empty/singleton lists unchanged.
    fn lower_builtin_intersperse(
        &mut self,
        sep_expr: &Expr,
        list_expr: &Expr,
    ) -> CodegenResult<Option<BasicValueEnum<'ctx>>> {
        // Lower the separator
        let sep_val = self.lower_expr(sep_expr)?.ok_or_else(|| {
            CodegenError::Internal("intersperse: separator has no value".to_string())
        })?;
        let sep_ptr = self.value_to_ptr(sep_val)?;

        // Lower the list
        let list_val = self
            .lower_expr(list_expr)?
            .ok_or_else(|| CodegenError::Internal("intersperse: list has no value".to_string()))?;
        let list_ptr = match list_val {
            BasicValueEnum::PointerValue(p) => p,
            _ => {
                return Err(CodegenError::TypeError(
                    "intersperse expects a list".to_string(),
                ))
            }
        };

        let tm = self.type_mapper();
        let ptr_type = tm.ptr_type();
        let current_fn = self
            .builder()
            .get_insert_block()
            .and_then(|b| b.get_parent())
            .ok_or_else(|| CodegenError::Internal("no current function".to_string()))?;

        // Check if list is empty first
        let empty_block = self.llvm_ctx.append_basic_block(current_fn, "inter_empty");
        let first_block = self.llvm_ctx.append_basic_block(current_fn, "inter_first");
        let loop_header = self.llvm_ctx.append_basic_block(current_fn, "inter_header");
        let loop_body = self.llvm_ctx.append_basic_block(current_fn, "inter_body");
        let loop_exit = self.llvm_ctx.append_basic_block(current_fn, "inter_exit");
        let final_merge = self.llvm_ctx.append_basic_block(current_fn, "inter_merge");

        let tag = self.extract_adt_tag(list_ptr)?;
        let is_empty = self
            .builder()
            .build_int_compare(
                inkwell::IntPredicate::EQ,
                tag,
                tm.i64_type().const_zero(),
                "is_empty",
            )
            .map_err(|e| CodegenError::Internal(format!("cmp: {:?}", e)))?;

        self.builder()
            .build_conditional_branch(is_empty, empty_block, first_block)
            .map_err(|e| CodegenError::Internal(format!("branch: {:?}", e)))?;

        // Empty list: return nil
        self.builder().position_at_end(empty_block);
        let empty_nil = self.build_nil()?;
        self.builder()
            .build_unconditional_branch(final_merge)
            .map_err(|e| CodegenError::Internal(format!("branch: {:?}", e)))?;

        // First element: extract head, start reversed result with just head
        self.builder().position_at_end(first_block);
        let first_head = self.extract_adt_field(list_ptr, 2, 0)?;
        let first_tail = self.extract_adt_field(list_ptr, 2, 1)?;

        let nil = self.build_nil()?;
        let first_cons = self.build_cons(first_head.into(), nil.into())?;

        self.builder()
            .build_unconditional_branch(loop_header)
            .map_err(|e| CodegenError::Internal(format!("branch: {:?}", e)))?;

        // Loop header: process remaining elements, prepending sep then element
        self.builder().position_at_end(loop_header);
        let result_phi = self
            .builder()
            .build_phi(ptr_type, "inter_result")
            .map_err(|e| CodegenError::Internal(format!("phi: {:?}", e)))?;
        let list_phi = self
            .builder()
            .build_phi(ptr_type, "inter_list")
            .map_err(|e| CodegenError::Internal(format!("phi: {:?}", e)))?;

        let loop_tag = self.extract_adt_tag(list_phi.as_basic_value().into_pointer_value())?;
        let loop_is_empty = self
            .builder()
            .build_int_compare(
                inkwell::IntPredicate::EQ,
                loop_tag,
                tm.i64_type().const_zero(),
                "loop_is_empty",
            )
            .map_err(|e| CodegenError::Internal(format!("cmp: {:?}", e)))?;

        self.builder()
            .build_conditional_branch(loop_is_empty, loop_exit, loop_body)
            .map_err(|e| CodegenError::Internal(format!("branch: {:?}", e)))?;

        // Loop body: cons element, then cons sep (building reversed)
        self.builder().position_at_end(loop_body);
        let head_ptr =
            self.extract_adt_field(list_phi.as_basic_value().into_pointer_value(), 2, 0)?;
        let tail_ptr =
            self.extract_adt_field(list_phi.as_basic_value().into_pointer_value(), 2, 1)?;

        // In reverse: cons element first, then sep on top
        let with_elem = self.build_cons(head_ptr.into(), result_phi.as_basic_value())?;
        let with_sep = self.build_cons(sep_ptr.into(), with_elem.into())?;

        self.builder()
            .build_unconditional_branch(loop_header)
            .map_err(|e| CodegenError::Internal(format!("branch: {:?}", e)))?;

        result_phi.add_incoming(&[(&first_cons, first_block), (&with_sep, loop_body)]);
        list_phi.add_incoming(&[(&first_tail, first_block), (&tail_ptr, loop_body)]);

        // Loop exit: reverse the result
        self.builder().position_at_end(loop_exit);

        let rev_header = self.llvm_ctx.append_basic_block(current_fn, "rev_header");
        let rev_body = self.llvm_ctx.append_basic_block(current_fn, "rev_body");
        let rev_exit = self.llvm_ctx.append_basic_block(current_fn, "rev_exit");

        let nil2 = self.build_nil()?;

        self.builder()
            .build_unconditional_branch(rev_header)
            .map_err(|e| CodegenError::Internal(format!("branch: {:?}", e)))?;

        self.builder().position_at_end(rev_header);
        let rev_acc = self
            .builder()
            .build_phi(ptr_type, "rev_acc")
            .map_err(|e| CodegenError::Internal(format!("phi: {:?}", e)))?;
        let rev_list = self
            .builder()
            .build_phi(ptr_type, "rev_list")
            .map_err(|e| CodegenError::Internal(format!("phi: {:?}", e)))?;

        let rev_tag = self.extract_adt_tag(rev_list.as_basic_value().into_pointer_value())?;
        let rev_is_empty = self
            .builder()
            .build_int_compare(
                inkwell::IntPredicate::EQ,
                rev_tag,
                tm.i64_type().const_zero(),
                "rev_is_empty",
            )
            .map_err(|e| CodegenError::Internal(format!("cmp: {:?}", e)))?;

        self.builder()
            .build_conditional_branch(rev_is_empty, rev_exit, rev_body)
            .map_err(|e| CodegenError::Internal(format!("branch: {:?}", e)))?;

        self.builder().position_at_end(rev_body);
        let rev_head =
            self.extract_adt_field(rev_list.as_basic_value().into_pointer_value(), 2, 0)?;
        let rev_tail =
            self.extract_adt_field(rev_list.as_basic_value().into_pointer_value(), 2, 1)?;
        let rev_new_cons = self.build_cons(rev_head.into(), rev_acc.as_basic_value())?;

        self.builder()
            .build_unconditional_branch(rev_header)
            .map_err(|e| CodegenError::Internal(format!("branch: {:?}", e)))?;

        rev_acc.add_incoming(&[(&nil2, loop_exit), (&rev_new_cons, rev_body)]);
        rev_list.add_incoming(&[
            (&result_phi.as_basic_value(), loop_exit),
            (&rev_tail, rev_body),
        ]);

        // After reverse, branch to final merge
        self.builder().position_at_end(rev_exit);
        self.builder()
            .build_unconditional_branch(final_merge)
            .map_err(|e| CodegenError::Internal(format!("branch: {:?}", e)))?;

        // Final merge: either empty nil or reversed result
        self.builder().position_at_end(final_merge);
        let final_phi = self
            .builder()
            .build_phi(ptr_type, "inter_final")
            .map_err(|e| CodegenError::Internal(format!("phi: {:?}", e)))?;
        final_phi.add_incoming(&[
            (&empty_nil, empty_block),
            (&rev_acc.as_basic_value(), rev_exit),
        ]);

        Ok(Some(final_phi.as_basic_value()))
    }

    /// Lower `intercalate` — call RTS bhc_list_intercalate.
    fn lower_builtin_intercalate(
        &mut self,
        sep_expr: &Expr,
        list_expr: &Expr,
    ) -> CodegenResult<Option<BasicValueEnum<'ctx>>> {
        let sep_val = self
            .lower_expr(sep_expr)?
            .ok_or_else(|| CodegenError::Internal("intercalate: no sep".to_string()))?;
        let sep_ptr = self.value_to_ptr(sep_val)?;
        let list_val = self
            .lower_expr(list_expr)?
            .ok_or_else(|| CodegenError::Internal("intercalate: no list".to_string()))?;
        let list_ptr = self.value_to_ptr(list_val)?;
        let rts_fn = self.functions.get(&VarId::new(1000174)).ok_or_else(|| {
            CodegenError::Internal("bhc_list_intercalate not declared".to_string())
        })?;
        let result = self
            .builder()
            .build_call(*rts_fn, &[sep_ptr.into(), list_ptr.into()], "intercalate")
            .map_err(|e| CodegenError::Internal(format!("intercalate call failed: {:?}", e)))?
            .try_as_basic_value()
            .basic()
            .ok_or_else(|| CodegenError::Internal("intercalate: returned void".to_string()))?;
        Ok(Some(result))
    }

    /// Lower `transpose` — call RTS bhc_list_transpose.
    fn lower_builtin_transpose(
        &mut self,
        list_expr: &Expr,
    ) -> CodegenResult<Option<BasicValueEnum<'ctx>>> {
        let list_val = self
            .lower_expr(list_expr)?
            .ok_or_else(|| CodegenError::Internal("transpose: no list".to_string()))?;
        let list_ptr = self.value_to_ptr(list_val)?;
        let rts_fn = self
            .functions
            .get(&VarId::new(1000175))
            .ok_or_else(|| CodegenError::Internal("bhc_list_transpose not declared".to_string()))?;
        let result = self
            .builder()
            .build_call(*rts_fn, &[list_ptr.into()], "transpose")
            .map_err(|e| CodegenError::Internal(format!("transpose call failed: {:?}", e)))?
            .try_as_basic_value()
            .basic()
            .ok_or_else(|| CodegenError::Internal("transpose: returned void".to_string()))?;
        Ok(Some(result))
    }

    /// Lower `nub` — call RTS bhc_list_nub.
    fn lower_builtin_nub(
        &mut self,
        list_expr: &Expr,
    ) -> CodegenResult<Option<BasicValueEnum<'ctx>>> {
        let list_val = self
            .lower_expr(list_expr)?
            .ok_or_else(|| CodegenError::Internal("nub: no list".to_string()))?;
        let list_ptr = self.value_to_ptr(list_val)?;
        let rts_fn = self
            .functions
            .get(&VarId::new(1000172))
            .ok_or_else(|| CodegenError::Internal("bhc_list_nub not declared".to_string()))?;
        let result = self
            .builder()
            .build_call(*rts_fn, &[list_ptr.into()], "nub")
            .map_err(|e| CodegenError::Internal(format!("nub call failed: {:?}", e)))?
            .try_as_basic_value()
            .basic()
            .ok_or_else(|| CodegenError::Internal("nub: returned void".to_string()))?;
        Ok(Some(result))
    }

    /// Lower `sort` — call RTS bhc_list_sort.
    fn lower_builtin_sort(
        &mut self,
        list_expr: &Expr,
    ) -> CodegenResult<Option<BasicValueEnum<'ctx>>> {
        let list_val = self
            .lower_expr(list_expr)?
            .ok_or_else(|| CodegenError::Internal("sort: no list".to_string()))?;
        let list_ptr = self.value_to_ptr(list_val)?;
        let rts_fn = self
            .functions
            .get(&VarId::new(1000170))
            .ok_or_else(|| CodegenError::Internal("bhc_list_sort not declared".to_string()))?;
        let result = self
            .builder()
            .build_call(*rts_fn, &[list_ptr.into()], "sort")
            .map_err(|e| CodegenError::Internal(format!("sort call failed: {:?}", e)))?
            .try_as_basic_value()
            .basic()
            .ok_or_else(|| CodegenError::Internal("sort: returned void".to_string()))?;
        Ok(Some(result))
    }

    /// Lower `sortBy` — call RTS bhc_list_sort_by.
    fn lower_builtin_sort_by(
        &mut self,
        cmp_expr: &Expr,
        list_expr: &Expr,
    ) -> CodegenResult<Option<BasicValueEnum<'ctx>>> {
        let cmp_val = self
            .lower_expr(cmp_expr)?
            .ok_or_else(|| CodegenError::Internal("sortBy: no cmp".to_string()))?;
        let cmp_ptr = self.value_to_ptr(cmp_val)?;
        let list_val = self
            .lower_expr(list_expr)?
            .ok_or_else(|| CodegenError::Internal("sortBy: no list".to_string()))?;
        let list_ptr = self.value_to_ptr(list_val)?;
        let rts_fn = self
            .functions
            .get(&VarId::new(1000171))
            .ok_or_else(|| CodegenError::Internal("bhc_list_sort_by not declared".to_string()))?;
        let result = self
            .builder()
            .build_call(*rts_fn, &[cmp_ptr.into(), list_ptr.into()], "sort_by")
            .map_err(|e| CodegenError::Internal(format!("sort_by call failed: {:?}", e)))?
            .try_as_basic_value()
            .basic()
            .ok_or_else(|| CodegenError::Internal("sort_by: returned void".to_string()))?;
        Ok(Some(result))
    }

    /// Lower `group` — call RTS bhc_list_group.
    fn lower_builtin_group(
        &mut self,
        list_expr: &Expr,
    ) -> CodegenResult<Option<BasicValueEnum<'ctx>>> {
        let list_val = self
            .lower_expr(list_expr)?
            .ok_or_else(|| CodegenError::Internal("group: no list".to_string()))?;
        let list_ptr = self.value_to_ptr(list_val)?;
        let rts_fn = self
            .functions
            .get(&VarId::new(1000173))
            .ok_or_else(|| CodegenError::Internal("bhc_list_group not declared".to_string()))?;
        let result = self
            .builder()
            .build_call(*rts_fn, &[list_ptr.into()], "group")
            .map_err(|e| CodegenError::Internal(format!("group call failed: {:?}", e)))?
            .try_as_basic_value()
            .basic()
            .ok_or_else(|| CodegenError::Internal("group: returned void".to_string()))?;
        Ok(Some(result))
    }

    // ---- E.26: *By variants, sortOn, stripPrefix, insert, mapAccumL/R ----

    /// Lower `sortOn` — call RTS bhc_list_sort_on.
    fn lower_builtin_sort_on(
        &mut self,
        key_expr: &Expr,
        list_expr: &Expr,
    ) -> CodegenResult<Option<BasicValueEnum<'ctx>>> {
        let key_val = self
            .lower_expr(key_expr)?
            .ok_or_else(|| CodegenError::Internal("sortOn: no key fn".to_string()))?;
        let key_ptr = self.value_to_ptr(key_val)?;
        let list_val = self
            .lower_expr(list_expr)?
            .ok_or_else(|| CodegenError::Internal("sortOn: no list".to_string()))?;
        let list_ptr = self.value_to_ptr(list_val)?;
        let rts_fn = self
            .functions
            .get(&VarId::new(1000550))
            .ok_or_else(|| CodegenError::Internal("bhc_list_sort_on not declared".to_string()))?;
        let result = self
            .builder()
            .build_call(*rts_fn, &[key_ptr.into(), list_ptr.into()], "sort_on")
            .map_err(|e| CodegenError::Internal(format!("sort_on call failed: {:?}", e)))?
            .try_as_basic_value()
            .basic()
            .ok_or_else(|| CodegenError::Internal("sort_on: returned void".to_string()))?;
        Ok(Some(result))
    }

    /// Lower `nubBy` — call RTS bhc_list_nub_by.
    fn lower_builtin_nub_by(
        &mut self,
        eq_expr: &Expr,
        list_expr: &Expr,
    ) -> CodegenResult<Option<BasicValueEnum<'ctx>>> {
        let eq_val = self
            .lower_expr(eq_expr)?
            .ok_or_else(|| CodegenError::Internal("nubBy: no eq fn".to_string()))?;
        let eq_ptr = self.value_to_ptr(eq_val)?;
        let list_val = self
            .lower_expr(list_expr)?
            .ok_or_else(|| CodegenError::Internal("nubBy: no list".to_string()))?;
        let list_ptr = self.value_to_ptr(list_val)?;
        let rts_fn = self
            .functions
            .get(&VarId::new(1000551))
            .ok_or_else(|| CodegenError::Internal("bhc_list_nub_by not declared".to_string()))?;
        let result = self
            .builder()
            .build_call(*rts_fn, &[eq_ptr.into(), list_ptr.into()], "nub_by")
            .map_err(|e| CodegenError::Internal(format!("nub_by call failed: {:?}", e)))?
            .try_as_basic_value()
            .basic()
            .ok_or_else(|| CodegenError::Internal("nub_by: returned void".to_string()))?;
        Ok(Some(result))
    }

    /// Lower `groupBy` — call RTS bhc_list_group_by.
    fn lower_builtin_group_by(
        &mut self,
        eq_expr: &Expr,
        list_expr: &Expr,
    ) -> CodegenResult<Option<BasicValueEnum<'ctx>>> {
        let eq_val = self
            .lower_expr(eq_expr)?
            .ok_or_else(|| CodegenError::Internal("groupBy: no eq fn".to_string()))?;
        let eq_ptr = self.value_to_ptr(eq_val)?;
        let list_val = self
            .lower_expr(list_expr)?
            .ok_or_else(|| CodegenError::Internal("groupBy: no list".to_string()))?;
        let list_ptr = self.value_to_ptr(list_val)?;
        let rts_fn = self
            .functions
            .get(&VarId::new(1000552))
            .ok_or_else(|| CodegenError::Internal("bhc_list_group_by not declared".to_string()))?;
        let result = self
            .builder()
            .build_call(*rts_fn, &[eq_ptr.into(), list_ptr.into()], "group_by")
            .map_err(|e| CodegenError::Internal(format!("group_by call failed: {:?}", e)))?
            .try_as_basic_value()
            .basic()
            .ok_or_else(|| CodegenError::Internal("group_by: returned void".to_string()))?;
        Ok(Some(result))
    }

    /// Lower `deleteBy` — call RTS bhc_list_delete_by.
    fn lower_builtin_delete_by(
        &mut self,
        eq_expr: &Expr,
        val_expr: &Expr,
        list_expr: &Expr,
    ) -> CodegenResult<Option<BasicValueEnum<'ctx>>> {
        let eq_val = self
            .lower_expr(eq_expr)?
            .ok_or_else(|| CodegenError::Internal("deleteBy: no eq fn".to_string()))?;
        let eq_ptr = self.value_to_ptr(eq_val)?;
        let val = self
            .lower_expr(val_expr)?
            .ok_or_else(|| CodegenError::Internal("deleteBy: no val".to_string()))?;
        let val_ptr = self.value_to_ptr(val)?;
        let list_val = self
            .lower_expr(list_expr)?
            .ok_or_else(|| CodegenError::Internal("deleteBy: no list".to_string()))?;
        let list_ptr = self.value_to_ptr(list_val)?;
        let rts_fn = self
            .functions
            .get(&VarId::new(1000553))
            .ok_or_else(|| CodegenError::Internal("bhc_list_delete_by not declared".to_string()))?;
        let result = self
            .builder()
            .build_call(
                *rts_fn,
                &[eq_ptr.into(), val_ptr.into(), list_ptr.into()],
                "delete_by",
            )
            .map_err(|e| CodegenError::Internal(format!("delete_by call failed: {:?}", e)))?
            .try_as_basic_value()
            .basic()
            .ok_or_else(|| CodegenError::Internal("delete_by: returned void".to_string()))?;
        Ok(Some(result))
    }

    /// Lower `unionBy` — call RTS bhc_list_union_by.
    fn lower_builtin_union_by(
        &mut self,
        eq_expr: &Expr,
        xs_expr: &Expr,
        ys_expr: &Expr,
    ) -> CodegenResult<Option<BasicValueEnum<'ctx>>> {
        let eq_val = self
            .lower_expr(eq_expr)?
            .ok_or_else(|| CodegenError::Internal("unionBy: no eq fn".to_string()))?;
        let eq_ptr = self.value_to_ptr(eq_val)?;
        let xs_val = self
            .lower_expr(xs_expr)?
            .ok_or_else(|| CodegenError::Internal("unionBy: no xs".to_string()))?;
        let xs_ptr = self.value_to_ptr(xs_val)?;
        let ys_val = self
            .lower_expr(ys_expr)?
            .ok_or_else(|| CodegenError::Internal("unionBy: no ys".to_string()))?;
        let ys_ptr = self.value_to_ptr(ys_val)?;
        let rts_fn = self
            .functions
            .get(&VarId::new(1000554))
            .ok_or_else(|| CodegenError::Internal("bhc_list_union_by not declared".to_string()))?;
        let result = self
            .builder()
            .build_call(
                *rts_fn,
                &[eq_ptr.into(), xs_ptr.into(), ys_ptr.into()],
                "union_by",
            )
            .map_err(|e| CodegenError::Internal(format!("union_by call failed: {:?}", e)))?
            .try_as_basic_value()
            .basic()
            .ok_or_else(|| CodegenError::Internal("union_by: returned void".to_string()))?;
        Ok(Some(result))
    }

    /// Lower `intersectBy` — call RTS bhc_list_intersect_by.
    fn lower_builtin_intersect_by(
        &mut self,
        eq_expr: &Expr,
        xs_expr: &Expr,
        ys_expr: &Expr,
    ) -> CodegenResult<Option<BasicValueEnum<'ctx>>> {
        let eq_val = self
            .lower_expr(eq_expr)?
            .ok_or_else(|| CodegenError::Internal("intersectBy: no eq fn".to_string()))?;
        let eq_ptr = self.value_to_ptr(eq_val)?;
        let xs_val = self
            .lower_expr(xs_expr)?
            .ok_or_else(|| CodegenError::Internal("intersectBy: no xs".to_string()))?;
        let xs_ptr = self.value_to_ptr(xs_val)?;
        let ys_val = self
            .lower_expr(ys_expr)?
            .ok_or_else(|| CodegenError::Internal("intersectBy: no ys".to_string()))?;
        let ys_ptr = self.value_to_ptr(ys_val)?;
        let rts_fn = self.functions.get(&VarId::new(1000555)).ok_or_else(|| {
            CodegenError::Internal("bhc_list_intersect_by not declared".to_string())
        })?;
        let result = self
            .builder()
            .build_call(
                *rts_fn,
                &[eq_ptr.into(), xs_ptr.into(), ys_ptr.into()],
                "intersect_by",
            )
            .map_err(|e| CodegenError::Internal(format!("intersect_by call failed: {:?}", e)))?
            .try_as_basic_value()
            .basic()
            .ok_or_else(|| CodegenError::Internal("intersect_by: returned void".to_string()))?;
        Ok(Some(result))
    }

    /// Lower `stripPrefix` — call RTS bhc_list_strip_prefix.
    fn lower_builtin_strip_prefix(
        &mut self,
        prefix_expr: &Expr,
        list_expr: &Expr,
    ) -> CodegenResult<Option<BasicValueEnum<'ctx>>> {
        let prefix_val = self
            .lower_expr(prefix_expr)?
            .ok_or_else(|| CodegenError::Internal("stripPrefix: no prefix".to_string()))?;
        let prefix_ptr = self.value_to_ptr(prefix_val)?;
        let list_val = self
            .lower_expr(list_expr)?
            .ok_or_else(|| CodegenError::Internal("stripPrefix: no list".to_string()))?;
        let list_ptr = self.value_to_ptr(list_val)?;
        let rts_fn = self.functions.get(&VarId::new(1000556)).ok_or_else(|| {
            CodegenError::Internal("bhc_list_strip_prefix not declared".to_string())
        })?;
        let result = self
            .builder()
            .build_call(
                *rts_fn,
                &[prefix_ptr.into(), list_ptr.into()],
                "strip_prefix",
            )
            .map_err(|e| CodegenError::Internal(format!("strip_prefix call failed: {:?}", e)))?
            .try_as_basic_value()
            .basic()
            .ok_or_else(|| CodegenError::Internal("strip_prefix: returned void".to_string()))?;
        Ok(Some(result))
    }

    /// Lower `insert` — call RTS bhc_list_insert.
    fn lower_builtin_insert(
        &mut self,
        val_expr: &Expr,
        list_expr: &Expr,
    ) -> CodegenResult<Option<BasicValueEnum<'ctx>>> {
        let val = self
            .lower_expr(val_expr)?
            .ok_or_else(|| CodegenError::Internal("insert: no val".to_string()))?;
        let val_ptr = self.value_to_ptr(val)?;
        let list_val = self
            .lower_expr(list_expr)?
            .ok_or_else(|| CodegenError::Internal("insert: no list".to_string()))?;
        let list_ptr = self.value_to_ptr(list_val)?;
        let rts_fn = self
            .functions
            .get(&VarId::new(1000557))
            .ok_or_else(|| CodegenError::Internal("bhc_list_insert not declared".to_string()))?;
        let result = self
            .builder()
            .build_call(*rts_fn, &[val_ptr.into(), list_ptr.into()], "insert")
            .map_err(|e| CodegenError::Internal(format!("insert call failed: {:?}", e)))?
            .try_as_basic_value()
            .basic()
            .ok_or_else(|| CodegenError::Internal("insert: returned void".to_string()))?;
        Ok(Some(result))
    }

    /// Lower `mapAccumL` — call RTS bhc_list_map_accum_l.
    fn lower_builtin_map_accum_l(
        &mut self,
        f_expr: &Expr,
        acc_expr: &Expr,
        list_expr: &Expr,
    ) -> CodegenResult<Option<BasicValueEnum<'ctx>>> {
        let f_val = self
            .lower_expr(f_expr)?
            .ok_or_else(|| CodegenError::Internal("mapAccumL: no fn".to_string()))?;
        let f_ptr = self.value_to_ptr(f_val)?;
        let acc_val = self
            .lower_expr(acc_expr)?
            .ok_or_else(|| CodegenError::Internal("mapAccumL: no acc".to_string()))?;
        let acc_ptr = self.value_to_ptr(acc_val)?;
        let list_val = self
            .lower_expr(list_expr)?
            .ok_or_else(|| CodegenError::Internal("mapAccumL: no list".to_string()))?;
        let list_ptr = self.value_to_ptr(list_val)?;
        let rts_fn = self.functions.get(&VarId::new(1000558)).ok_or_else(|| {
            CodegenError::Internal("bhc_list_map_accum_l not declared".to_string())
        })?;
        let result = self
            .builder()
            .build_call(
                *rts_fn,
                &[f_ptr.into(), acc_ptr.into(), list_ptr.into()],
                "map_accum_l",
            )
            .map_err(|e| CodegenError::Internal(format!("mapAccumL call failed: {:?}", e)))?
            .try_as_basic_value()
            .basic()
            .ok_or_else(|| CodegenError::Internal("mapAccumL: returned void".to_string()))?;
        Ok(Some(result))
    }

    /// Lower `mapAccumR` — call RTS bhc_list_map_accum_r.
    fn lower_builtin_map_accum_r(
        &mut self,
        f_expr: &Expr,
        acc_expr: &Expr,
        list_expr: &Expr,
    ) -> CodegenResult<Option<BasicValueEnum<'ctx>>> {
        let f_val = self
            .lower_expr(f_expr)?
            .ok_or_else(|| CodegenError::Internal("mapAccumR: no fn".to_string()))?;
        let f_ptr = self.value_to_ptr(f_val)?;
        let acc_val = self
            .lower_expr(acc_expr)?
            .ok_or_else(|| CodegenError::Internal("mapAccumR: no acc".to_string()))?;
        let acc_ptr = self.value_to_ptr(acc_val)?;
        let list_val = self
            .lower_expr(list_expr)?
            .ok_or_else(|| CodegenError::Internal("mapAccumR: no list".to_string()))?;
        let list_ptr = self.value_to_ptr(list_val)?;
        let rts_fn = self.functions.get(&VarId::new(1000559)).ok_or_else(|| {
            CodegenError::Internal("bhc_list_map_accum_r not declared".to_string())
        })?;
        let result = self
            .builder()
            .build_call(
                *rts_fn,
                &[f_ptr.into(), acc_ptr.into(), list_ptr.into()],
                "map_accum_r",
            )
            .map_err(|e| CodegenError::Internal(format!("mapAccumR call failed: {:?}", e)))?
            .try_as_basic_value()
            .basic()
            .ok_or_else(|| CodegenError::Internal("mapAccumR: returned void".to_string()))?;
        Ok(Some(result))
    }

    /// Lower `delete` — remove first occurrence of value from list.
    /// delete x [] = []
    /// delete x (y:ys) = if x == y then ys else y : delete x ys
    fn lower_builtin_delete(
        &mut self,
        val_expr: &Expr,
        list_expr: &Expr,
    ) -> CodegenResult<Option<BasicValueEnum<'ctx>>> {
        let val = self
            .lower_expr(val_expr)?
            .ok_or_else(|| CodegenError::Internal("delete: no value".to_string()))?;
        let val_int = self.coerce_to_int(val)?;
        let list_val = self
            .lower_expr(list_expr)?
            .ok_or_else(|| CodegenError::Internal("delete: no list".to_string()))?;
        let list_ptr = match list_val {
            BasicValueEnum::PointerValue(p) => p,
            _ => return Err(CodegenError::TypeError("delete expects a list".to_string())),
        };

        let tm = self.type_mapper();
        let ptr_type = tm.ptr_type();
        let current_fn = self
            .builder()
            .get_insert_block()
            .and_then(|b| b.get_parent())
            .ok_or_else(|| CodegenError::Internal("no current function".to_string()))?;
        let entry_block = self
            .builder()
            .get_insert_block()
            .ok_or_else(|| CodegenError::Internal("no current block".to_string()))?;

        // Phase 1: scan for element, collecting prefix in reverse
        let loop_header = self.llvm_ctx.append_basic_block(current_fn, "del_header");
        let loop_body = self.llvm_ctx.append_basic_block(current_fn, "del_body");
        let del_found = self.llvm_ctx.append_basic_block(current_fn, "del_found");
        let del_notfound = self.llvm_ctx.append_basic_block(current_fn, "del_notfound");
        let del_merge = self.llvm_ctx.append_basic_block(current_fn, "del_merge");

        let nil = self.build_nil()?;
        self.builder()
            .build_unconditional_branch(loop_header)
            .map_err(|e| CodegenError::Internal(format!("branch: {:?}", e)))?;

        self.builder().position_at_end(loop_header);
        let prefix_phi = self
            .builder()
            .build_phi(ptr_type, "del_prefix")
            .map_err(|e| CodegenError::Internal(format!("phi: {:?}", e)))?;
        let list_phi = self
            .builder()
            .build_phi(ptr_type, "del_list")
            .map_err(|e| CodegenError::Internal(format!("phi: {:?}", e)))?;

        let tag = self.extract_adt_tag(list_phi.as_basic_value().into_pointer_value())?;
        let is_empty = self
            .builder()
            .build_int_compare(
                inkwell::IntPredicate::EQ,
                tag,
                tm.i64_type().const_zero(),
                "is_empty",
            )
            .map_err(|e| CodegenError::Internal(format!("cmp: {:?}", e)))?;
        // Empty list → element not in list, go to merge with nil suffix
        self.builder()
            .build_conditional_branch(is_empty, del_merge, loop_body)
            .map_err(|e| CodegenError::Internal(format!("branch: {:?}", e)))?;

        // Body: compare element
        self.builder().position_at_end(loop_body);
        let head_ptr =
            self.extract_adt_field(list_phi.as_basic_value().into_pointer_value(), 2, 0)?;
        let tail_ptr =
            self.extract_adt_field(list_phi.as_basic_value().into_pointer_value(), 2, 1)?;

        let head_int = self
            .builder()
            .build_ptr_to_int(head_ptr, tm.i64_type(), "head_int")
            .map_err(|e| CodegenError::Internal(format!("ptr_to_int: {:?}", e)))?;
        let is_match = self
            .builder()
            .build_int_compare(inkwell::IntPredicate::EQ, head_int, val_int, "is_match")
            .map_err(|e| CodegenError::Internal(format!("cmp: {:?}", e)))?;

        self.builder()
            .build_conditional_branch(is_match, del_found, del_notfound)
            .map_err(|e| CodegenError::Internal(format!("branch: {:?}", e)))?;

        // Found: result = reverse(prefix) ++ tail (skip matched element)
        self.builder().position_at_end(del_found);
        self.builder()
            .build_unconditional_branch(del_merge)
            .map_err(|e| CodegenError::Internal(format!("branch: {:?}", e)))?;

        // Not found in this element: cons head onto prefix, continue with tail
        self.builder().position_at_end(del_notfound);
        let new_cons = self.build_cons(head_ptr.into(), prefix_phi.as_basic_value())?;
        self.builder()
            .build_unconditional_branch(loop_header)
            .map_err(|e| CodegenError::Internal(format!("branch: {:?}", e)))?;

        prefix_phi.add_incoming(&[(&nil, entry_block), (&new_cons, del_notfound)]);
        list_phi.add_incoming(&[(&list_ptr, entry_block), (&tail_ptr, del_notfound)]);

        // Merge: reverse prefix onto suffix
        // Reached from: loop_header (empty, suffix=nil) or del_found (suffix=tail)
        self.builder().position_at_end(del_merge);
        let suffix_phi = self
            .builder()
            .build_phi(ptr_type, "del_suffix")
            .map_err(|e| CodegenError::Internal(format!("phi: {:?}", e)))?;
        suffix_phi.add_incoming(&[
            (&nil, loop_header),    // empty list: no suffix
            (&tail_ptr, del_found), // found: skip matched, suffix = tail
        ]);

        let prefix_at_merge = self
            .builder()
            .build_phi(ptr_type, "del_prefix_merge")
            .map_err(|e| CodegenError::Internal(format!("phi: {:?}", e)))?;
        prefix_at_merge.add_incoming(&[
            (&prefix_phi.as_basic_value(), loop_header),
            (&prefix_phi.as_basic_value(), del_found),
        ]);

        // Reverse prefix onto suffix
        let rev_header = self
            .llvm_ctx
            .append_basic_block(current_fn, "del_rev_header");
        let rev_body = self.llvm_ctx.append_basic_block(current_fn, "del_rev_body");
        let rev_exit = self.llvm_ctx.append_basic_block(current_fn, "del_rev_exit");
        self.builder()
            .build_unconditional_branch(rev_header)
            .map_err(|e| CodegenError::Internal(format!("branch: {:?}", e)))?;

        self.builder().position_at_end(rev_header);
        let rev_acc = self
            .builder()
            .build_phi(ptr_type, "rev_acc")
            .map_err(|e| CodegenError::Internal(format!("phi: {:?}", e)))?;
        let rev_list = self
            .builder()
            .build_phi(ptr_type, "rev_list")
            .map_err(|e| CodegenError::Internal(format!("phi: {:?}", e)))?;
        let rev_tag = self.extract_adt_tag(rev_list.as_basic_value().into_pointer_value())?;
        let rev_empty = self
            .builder()
            .build_int_compare(
                inkwell::IntPredicate::EQ,
                rev_tag,
                tm.i64_type().const_zero(),
                "rev_empty",
            )
            .map_err(|e| CodegenError::Internal(format!("cmp: {:?}", e)))?;
        self.builder()
            .build_conditional_branch(rev_empty, rev_exit, rev_body)
            .map_err(|e| CodegenError::Internal(format!("branch: {:?}", e)))?;

        self.builder().position_at_end(rev_body);
        let r_head =
            self.extract_adt_field(rev_list.as_basic_value().into_pointer_value(), 2, 0)?;
        let r_tail =
            self.extract_adt_field(rev_list.as_basic_value().into_pointer_value(), 2, 1)?;
        let r_cons = self.build_cons(r_head.into(), rev_acc.as_basic_value())?;
        self.builder()
            .build_unconditional_branch(rev_header)
            .map_err(|e| CodegenError::Internal(format!("branch: {:?}", e)))?;

        // Note: initial acc is the suffix (we reverse-prepend prefix onto it)
        rev_acc.add_incoming(&[
            (&suffix_phi.as_basic_value(), del_merge),
            (&r_cons, rev_body),
        ]);
        rev_list.add_incoming(&[
            (&prefix_at_merge.as_basic_value(), del_merge),
            (&r_tail, rev_body),
        ]);

        self.builder().position_at_end(rev_exit);
        Ok(Some(rev_acc.as_basic_value()))
    }

    /// Lower `union` (stub: append).
    fn lower_builtin_union(
        &mut self,
        list1_expr: &Expr,
        list2_expr: &Expr,
    ) -> CodegenResult<Option<BasicValueEnum<'ctx>>> {
        self.lower_builtin_append(list1_expr, list2_expr)
    }

    /// Lower `intersect :: Eq a => [a] -> [a] -> [a]`.
    /// For each element in xs, scan ys for a match (pointer equality on Int tags).
    fn lower_builtin_intersect(
        &mut self,
        list1_expr: &Expr,
        list2_expr: &Expr,
    ) -> CodegenResult<Option<BasicValueEnum<'ctx>>> {
        let list1_val = self
            .lower_expr(list1_expr)?
            .ok_or_else(|| CodegenError::Internal("intersect: list1 has no value".to_string()))?;
        let list1_ptr = match list1_val {
            BasicValueEnum::PointerValue(p) => p,
            _ => {
                return Err(CodegenError::TypeError(
                    "intersect expects lists".to_string(),
                ))
            }
        };

        let list2_val = self
            .lower_expr(list2_expr)?
            .ok_or_else(|| CodegenError::Internal("intersect: list2 has no value".to_string()))?;
        let list2_ptr = match list2_val {
            BasicValueEnum::PointerValue(p) => p,
            _ => {
                return Err(CodegenError::TypeError(
                    "intersect expects lists".to_string(),
                ))
            }
        };

        let tm = self.type_mapper();
        let ptr_type = tm.ptr_type();
        let current_fn = self
            .builder()
            .get_insert_block()
            .and_then(|b| b.get_parent())
            .ok_or_else(|| CodegenError::Internal("no current function".to_string()))?;
        let entry_block = self
            .builder()
            .get_insert_block()
            .ok_or_else(|| CodegenError::Internal("no current block".to_string()))?;

        let nil = self.build_nil()?;

        // Outer loop: iterate through xs
        let outer_header = self
            .llvm_ctx
            .append_basic_block(current_fn, "isect_outer_hdr");
        let outer_body = self
            .llvm_ctx
            .append_basic_block(current_fn, "isect_outer_body");
        let outer_exit = self
            .llvm_ctx
            .append_basic_block(current_fn, "isect_outer_exit");
        // Inner loop: scan ys for match
        let inner_header = self
            .llvm_ctx
            .append_basic_block(current_fn, "isect_inner_hdr");
        let inner_body = self
            .llvm_ctx
            .append_basic_block(current_fn, "isect_inner_body");
        let inner_found = self.llvm_ctx.append_basic_block(current_fn, "isect_found");
        let inner_next = self
            .llvm_ctx
            .append_basic_block(current_fn, "isect_not_found");

        self.builder()
            .build_unconditional_branch(outer_header)
            .map_err(|e| CodegenError::Internal(format!("branch: {:?}", e)))?;

        // Outer header
        self.builder().position_at_end(outer_header);
        let xs_phi = self
            .builder()
            .build_phi(ptr_type, "isect_xs")
            .map_err(|e| CodegenError::Internal(format!("phi: {:?}", e)))?;
        let result_phi = self
            .builder()
            .build_phi(ptr_type, "isect_result")
            .map_err(|e| CodegenError::Internal(format!("phi: {:?}", e)))?;

        let xs_tag = self.extract_adt_tag(xs_phi.as_basic_value().into_pointer_value())?;
        let xs_empty = self
            .builder()
            .build_int_compare(
                inkwell::IntPredicate::EQ,
                xs_tag,
                tm.i64_type().const_zero(),
                "xs_empty",
            )
            .map_err(|e| CodegenError::Internal(format!("cmp: {:?}", e)))?;
        self.builder()
            .build_conditional_branch(xs_empty, outer_exit, outer_body)
            .map_err(|e| CodegenError::Internal(format!("branch: {:?}", e)))?;

        // Outer body: get head of xs
        self.builder().position_at_end(outer_body);
        let x_head = self.extract_adt_field(xs_phi.as_basic_value().into_pointer_value(), 2, 0)?;
        let x_tail = self.extract_adt_field(xs_phi.as_basic_value().into_pointer_value(), 2, 1)?;

        // Convert x_head to int for comparison (works for Int values)
        let x_int = self
            .builder()
            .build_ptr_to_int(x_head, tm.i64_type(), "x_int")
            .map_err(|e| CodegenError::Internal(format!("ptr_to_int: {:?}", e)))?;

        self.builder()
            .build_unconditional_branch(inner_header)
            .map_err(|e| CodegenError::Internal(format!("branch: {:?}", e)))?;

        // Inner header: scan ys
        self.builder().position_at_end(inner_header);
        let ys_phi = self
            .builder()
            .build_phi(ptr_type, "isect_ys")
            .map_err(|e| CodegenError::Internal(format!("phi: {:?}", e)))?;

        let ys_tag = self.extract_adt_tag(ys_phi.as_basic_value().into_pointer_value())?;
        let ys_empty = self
            .builder()
            .build_int_compare(
                inkwell::IntPredicate::EQ,
                ys_tag,
                tm.i64_type().const_zero(),
                "ys_empty",
            )
            .map_err(|e| CodegenError::Internal(format!("cmp: {:?}", e)))?;
        self.builder()
            .build_conditional_branch(ys_empty, inner_next, inner_body)
            .map_err(|e| CodegenError::Internal(format!("branch: {:?}", e)))?;

        // Inner body: compare x_head with y_head
        self.builder().position_at_end(inner_body);
        let y_head = self.extract_adt_field(ys_phi.as_basic_value().into_pointer_value(), 2, 0)?;
        let y_tail = self.extract_adt_field(ys_phi.as_basic_value().into_pointer_value(), 2, 1)?;
        let y_int = self
            .builder()
            .build_ptr_to_int(y_head, tm.i64_type(), "y_int")
            .map_err(|e| CodegenError::Internal(format!("ptr_to_int: {:?}", e)))?;

        let eq = self
            .builder()
            .build_int_compare(inkwell::IntPredicate::EQ, x_int, y_int, "eq")
            .map_err(|e| CodegenError::Internal(format!("cmp: {:?}", e)))?;
        self.builder()
            .build_conditional_branch(eq, inner_found, inner_header)
            .map_err(|e| CodegenError::Internal(format!("branch: {:?}", e)))?;

        ys_phi.add_incoming(&[(&list2_ptr, outer_body), (&y_tail, inner_body)]);

        // Found: cons x_head onto result, continue outer
        self.builder().position_at_end(inner_found);
        let found_cons = self.build_cons(x_head.into(), result_phi.as_basic_value())?;
        self.builder()
            .build_unconditional_branch(outer_header)
            .map_err(|e| CodegenError::Internal(format!("branch: {:?}", e)))?;

        // Not found: skip, continue outer
        self.builder().position_at_end(inner_next);
        self.builder()
            .build_unconditional_branch(outer_header)
            .map_err(|e| CodegenError::Internal(format!("branch: {:?}", e)))?;

        xs_phi.add_incoming(&[
            (&list1_ptr, entry_block),
            (&x_tail, inner_found),
            (&x_tail, inner_next),
        ]);
        result_phi.add_incoming(&[
            (&nil, entry_block),
            (&found_cons, inner_found),
            (&result_phi.as_basic_value(), inner_next),
        ]);

        // Reverse result
        self.builder().position_at_end(outer_exit);
        let reversed = self
            .build_inline_reverse(result_phi.as_basic_value().into_pointer_value(), current_fn)?;
        Ok(reversed)
    }

    /// Lower `scanl :: (b -> a -> b) -> b -> [a] -> [b]`.
    /// Produces a list starting with init, then applying f to accumulator and each element.
    /// Output always starts with init even if input is empty.
    /// Builds result in reverse, then reverses at end.
    fn lower_builtin_scanl(
        &mut self,
        func_expr: &Expr,
        init_expr: &Expr,
        list_expr: &Expr,
    ) -> CodegenResult<Option<BasicValueEnum<'ctx>>> {
        // Lower the function closure
        let func_val = self
            .lower_expr(func_expr)?
            .ok_or_else(|| CodegenError::Internal("scanl: function has no value".to_string()))?;
        let func_ptr = match func_val {
            BasicValueEnum::PointerValue(p) => p,
            _ => {
                return Err(CodegenError::TypeError(
                    "scanl: function must be a closure".to_string(),
                ))
            }
        };

        // Lower the initial value
        let init_val = self
            .lower_expr(init_expr)?
            .ok_or_else(|| CodegenError::Internal("scanl: init has no value".to_string()))?;
        let init_ptr = self.value_to_ptr(init_val)?;

        // Lower the list
        let list_val = self
            .lower_expr(list_expr)?
            .ok_or_else(|| CodegenError::Internal("scanl: list has no value".to_string()))?;
        let list_ptr = match list_val {
            BasicValueEnum::PointerValue(p) => p,
            _ => return Err(CodegenError::TypeError("scanl expects a list".to_string())),
        };

        let tm = self.type_mapper();
        let ptr_type = tm.ptr_type();
        let current_fn = self
            .builder()
            .get_insert_block()
            .and_then(|b| b.get_parent())
            .ok_or_else(|| CodegenError::Internal("no current function".to_string()))?;
        let entry_block = self
            .builder()
            .get_insert_block()
            .ok_or_else(|| CodegenError::Internal("no current block".to_string()))?;

        let loop_header = self.llvm_ctx.append_basic_block(current_fn, "scanl_header");
        let loop_body = self.llvm_ctx.append_basic_block(current_fn, "scanl_body");
        let loop_exit = self.llvm_ctx.append_basic_block(current_fn, "scanl_exit");

        // Build the initial reversed result: cons init onto nil
        // (scanl always starts with init, even for empty input)
        let nil = self.build_nil()?;
        let init_cons = self.build_cons(init_ptr.into(), nil.into())?;

        self.builder()
            .build_unconditional_branch(loop_header)
            .map_err(|e| CodegenError::Internal(format!("branch: {:?}", e)))?;

        // Loop header: phi for acc, list, and reversed result
        self.builder().position_at_end(loop_header);
        let acc_phi = self
            .builder()
            .build_phi(ptr_type, "scanl_acc")
            .map_err(|e| CodegenError::Internal(format!("phi: {:?}", e)))?;
        let list_phi = self
            .builder()
            .build_phi(ptr_type, "scanl_list")
            .map_err(|e| CodegenError::Internal(format!("phi: {:?}", e)))?;
        let result_phi = self
            .builder()
            .build_phi(ptr_type, "scanl_result")
            .map_err(|e| CodegenError::Internal(format!("phi: {:?}", e)))?;

        // Check if list is empty
        let tag = self.extract_adt_tag(list_phi.as_basic_value().into_pointer_value())?;
        let is_empty = self
            .builder()
            .build_int_compare(
                inkwell::IntPredicate::EQ,
                tag,
                tm.i64_type().const_zero(),
                "is_empty",
            )
            .map_err(|e| CodegenError::Internal(format!("cmp: {:?}", e)))?;

        self.builder()
            .build_conditional_branch(is_empty, loop_exit, loop_body)
            .map_err(|e| CodegenError::Internal(format!("branch: {:?}", e)))?;

        // Loop body: new_acc = f(acc, head); cons new_acc onto result
        self.builder().position_at_end(loop_body);
        let head_ptr =
            self.extract_adt_field(list_phi.as_basic_value().into_pointer_value(), 2, 0)?;
        let tail_ptr =
            self.extract_adt_field(list_phi.as_basic_value().into_pointer_value(), 2, 1)?;

        // Call f(func_ptr, acc, head) - 2-arg closure call
        let fn_ptr = self.extract_closure_fn_ptr(func_ptr)?;
        let fn_type = ptr_type.fn_type(&[ptr_type.into(), ptr_type.into(), ptr_type.into()], false);
        let new_acc = self
            .builder()
            .build_indirect_call(
                fn_type,
                fn_ptr,
                &[
                    func_ptr.into(),
                    acc_phi.as_basic_value().into(),
                    head_ptr.into(),
                ],
                "scanl_new_acc",
            )
            .map_err(|e| CodegenError::Internal(format!("call function: {:?}", e)))?
            .try_as_basic_value()
            .basic()
            .ok_or_else(|| CodegenError::Internal("scanl: function returned void".to_string()))?;

        let new_acc_ptr = self.value_to_ptr(new_acc)?;

        // Cons new_acc onto the reversed result
        let new_cons = self.build_cons(new_acc_ptr.into(), result_phi.as_basic_value())?;

        self.builder()
            .build_unconditional_branch(loop_header)
            .map_err(|e| CodegenError::Internal(format!("branch: {:?}", e)))?;

        // Add phi incoming values
        acc_phi.add_incoming(&[(&init_ptr, entry_block), (&new_acc_ptr, loop_body)]);
        list_phi.add_incoming(&[(&list_ptr, entry_block), (&tail_ptr, loop_body)]);
        result_phi.add_incoming(&[(&init_cons, entry_block), (&new_cons, loop_body)]);

        // Exit: reverse the accumulated result
        self.builder().position_at_end(loop_exit);

        // Inline reverse
        let rev_header = self.llvm_ctx.append_basic_block(current_fn, "rev_header");
        let rev_body = self.llvm_ctx.append_basic_block(current_fn, "rev_body");
        let rev_exit = self.llvm_ctx.append_basic_block(current_fn, "rev_exit");

        let nil2 = self.build_nil()?;

        self.builder()
            .build_unconditional_branch(rev_header)
            .map_err(|e| CodegenError::Internal(format!("branch: {:?}", e)))?;

        self.builder().position_at_end(rev_header);
        let rev_acc = self
            .builder()
            .build_phi(ptr_type, "rev_acc")
            .map_err(|e| CodegenError::Internal(format!("phi: {:?}", e)))?;
        let rev_list = self
            .builder()
            .build_phi(ptr_type, "rev_list")
            .map_err(|e| CodegenError::Internal(format!("phi: {:?}", e)))?;

        let rev_tag = self.extract_adt_tag(rev_list.as_basic_value().into_pointer_value())?;
        let rev_is_empty = self
            .builder()
            .build_int_compare(
                inkwell::IntPredicate::EQ,
                rev_tag,
                tm.i64_type().const_zero(),
                "rev_is_empty",
            )
            .map_err(|e| CodegenError::Internal(format!("cmp: {:?}", e)))?;

        self.builder()
            .build_conditional_branch(rev_is_empty, rev_exit, rev_body)
            .map_err(|e| CodegenError::Internal(format!("branch: {:?}", e)))?;

        self.builder().position_at_end(rev_body);
        let rev_head =
            self.extract_adt_field(rev_list.as_basic_value().into_pointer_value(), 2, 0)?;
        let rev_tail =
            self.extract_adt_field(rev_list.as_basic_value().into_pointer_value(), 2, 1)?;
        let rev_new_cons = self.build_cons(rev_head.into(), rev_acc.as_basic_value())?;

        self.builder()
            .build_unconditional_branch(rev_header)
            .map_err(|e| CodegenError::Internal(format!("branch: {:?}", e)))?;

        rev_acc.add_incoming(&[(&nil2, loop_exit), (&rev_new_cons, rev_body)]);
        rev_list.add_incoming(&[
            (&result_phi.as_basic_value(), loop_exit),
            (&rev_tail, rev_body),
        ]);

        self.builder().position_at_end(rev_exit);
        Ok(Some(rev_acc.as_basic_value()))
    }

    /// Lower `scanr :: (a -> b -> b) -> b -> [a] -> [b]`.
    /// scanr f z [x1,x2,...,xn] = [f x1 (f x2 (...(f xn z)...)), ..., f xn z, z]
    /// Strategy: reverse input, then left-accumulate with f(elem, acc), result comes out in order.
    fn lower_builtin_scanr(
        &mut self,
        func_expr: &Expr,
        init_expr: &Expr,
        list_expr: &Expr,
    ) -> CodegenResult<Option<BasicValueEnum<'ctx>>> {
        // Lower the function closure
        let func_val = self
            .lower_expr(func_expr)?
            .ok_or_else(|| CodegenError::Internal("scanr: function has no value".to_string()))?;
        let func_ptr = match func_val {
            BasicValueEnum::PointerValue(p) => p,
            _ => {
                return Err(CodegenError::TypeError(
                    "scanr: function must be a closure".to_string(),
                ))
            }
        };

        // Lower the initial value
        let init_val = self
            .lower_expr(init_expr)?
            .ok_or_else(|| CodegenError::Internal("scanr: init has no value".to_string()))?;
        let init_ptr = self.value_to_ptr(init_val)?;

        // Lower the list
        let list_val = self
            .lower_expr(list_expr)?
            .ok_or_else(|| CodegenError::Internal("scanr: list has no value".to_string()))?;
        let list_ptr = match list_val {
            BasicValueEnum::PointerValue(p) => p,
            _ => return Err(CodegenError::TypeError("scanr expects a list".to_string())),
        };

        let ptr_type = self.type_mapper().ptr_type();
        let i64_zero = self.type_mapper().i64_type().const_zero();
        let current_fn = self
            .builder()
            .get_insert_block()
            .and_then(|b| b.get_parent())
            .ok_or_else(|| CodegenError::Internal("no current function".to_string()))?;

        // Step 1: Reverse the input list
        let reversed = self
            .build_inline_reverse(list_ptr, current_fn)?
            .ok_or_else(|| CodegenError::Internal("scanr: reverse failed".to_string()))?;
        let reversed_ptr = reversed.into_pointer_value();

        let entry_block = self
            .builder()
            .get_insert_block()
            .ok_or_else(|| CodegenError::Internal("no current block".to_string()))?;

        // Step 2: Left-accumulate over reversed list, building result in forward order.
        // Start with result = [z], acc = z
        let nil = self.build_nil()?;
        let init_cons = self.build_cons(init_ptr.into(), nil.into())?;

        let loop_header = self.llvm_ctx.append_basic_block(current_fn, "scanr_header");
        let loop_body = self.llvm_ctx.append_basic_block(current_fn, "scanr_body");
        let loop_exit = self.llvm_ctx.append_basic_block(current_fn, "scanr_exit");

        self.builder()
            .build_unconditional_branch(loop_header)
            .map_err(|e| CodegenError::Internal(format!("branch: {:?}", e)))?;

        self.builder().position_at_end(loop_header);
        let acc_phi = self
            .builder()
            .build_phi(ptr_type, "scanr_acc")
            .map_err(|e| CodegenError::Internal(format!("phi: {:?}", e)))?;
        let list_phi = self
            .builder()
            .build_phi(ptr_type, "scanr_list")
            .map_err(|e| CodegenError::Internal(format!("phi: {:?}", e)))?;
        let result_phi = self
            .builder()
            .build_phi(ptr_type, "scanr_result")
            .map_err(|e| CodegenError::Internal(format!("phi: {:?}", e)))?;

        let tag = self.extract_adt_tag(list_phi.as_basic_value().into_pointer_value())?;
        let is_empty = self
            .builder()
            .build_int_compare(inkwell::IntPredicate::EQ, tag, i64_zero, "is_empty")
            .map_err(|e| CodegenError::Internal(format!("cmp: {:?}", e)))?;

        self.builder()
            .build_conditional_branch(is_empty, loop_exit, loop_body)
            .map_err(|e| CodegenError::Internal(format!("branch: {:?}", e)))?;

        // Loop body: new_acc = f(head, acc), cons new_acc onto result
        self.builder().position_at_end(loop_body);
        let head_ptr =
            self.extract_adt_field(list_phi.as_basic_value().into_pointer_value(), 2, 0)?;
        let tail_ptr =
            self.extract_adt_field(list_phi.as_basic_value().into_pointer_value(), 2, 1)?;

        // scanr applies f(elem, acc) — note the arg order
        let fn_ptr = self.extract_closure_fn_ptr(func_ptr)?;
        let fn_type = ptr_type.fn_type(&[ptr_type.into(), ptr_type.into(), ptr_type.into()], false);
        let new_acc = self
            .builder()
            .build_indirect_call(
                fn_type,
                fn_ptr,
                &[
                    func_ptr.into(),
                    head_ptr.into(),
                    acc_phi.as_basic_value().into(),
                ],
                "scanr_new_acc",
            )
            .map_err(|e| CodegenError::Internal(format!("call: {:?}", e)))?
            .try_as_basic_value()
            .basic()
            .ok_or_else(|| CodegenError::Internal("scanr: function returned void".to_string()))?;

        let new_acc_ptr = self.value_to_ptr(new_acc)?;
        let new_cons = self.build_cons(new_acc_ptr.into(), result_phi.as_basic_value())?;

        self.builder()
            .build_unconditional_branch(loop_header)
            .map_err(|e| CodegenError::Internal(format!("branch: {:?}", e)))?;

        acc_phi.add_incoming(&[(&init_ptr, entry_block), (&new_acc_ptr, loop_body)]);
        list_phi.add_incoming(&[(&reversed_ptr, entry_block), (&tail_ptr, loop_body)]);
        result_phi.add_incoming(&[(&init_cons, entry_block), (&new_cons, loop_body)]);

        self.builder().position_at_end(loop_exit);
        Ok(Some(result_phi.as_basic_value()))
    }

    /// Lower `scanl1 :: (a -> a -> a) -> [a] -> [a]`.
    /// scanl1 f [x1,x2,...] = scanl f x1 [x2,...]
    /// Extract head as init, then run scanl loop on tail.
    fn lower_builtin_scanl1(
        &mut self,
        func_expr: &Expr,
        list_expr: &Expr,
    ) -> CodegenResult<Option<BasicValueEnum<'ctx>>> {
        let func_val = self
            .lower_expr(func_expr)?
            .ok_or_else(|| CodegenError::Internal("scanl1: function has no value".to_string()))?;
        let func_ptr = match func_val {
            BasicValueEnum::PointerValue(p) => p,
            _ => {
                return Err(CodegenError::TypeError(
                    "scanl1: function must be a closure".to_string(),
                ))
            }
        };

        let list_val = self
            .lower_expr(list_expr)?
            .ok_or_else(|| CodegenError::Internal("scanl1: list has no value".to_string()))?;
        let list_ptr = match list_val {
            BasicValueEnum::PointerValue(p) => p,
            _ => return Err(CodegenError::TypeError("scanl1 expects a list".to_string())),
        };

        let ptr_type = self.type_mapper().ptr_type();
        let i64_zero = self.type_mapper().i64_type().const_zero();
        let current_fn = self
            .builder()
            .get_insert_block()
            .and_then(|b| b.get_parent())
            .ok_or_else(|| CodegenError::Internal("no current function".to_string()))?;

        // Check if list is empty — if so, return empty list
        let tag = self.extract_adt_tag(list_ptr)?;
        let is_empty = self
            .builder()
            .build_int_compare(inkwell::IntPredicate::EQ, tag, i64_zero, "is_empty")
            .map_err(|e| CodegenError::Internal(format!("cmp: {:?}", e)))?;

        let nonempty_block = self
            .llvm_ctx
            .append_basic_block(current_fn, "scanl1_nonempty");
        let empty_block = self.llvm_ctx.append_basic_block(current_fn, "scanl1_empty");
        let merge_block = self.llvm_ctx.append_basic_block(current_fn, "scanl1_merge");

        self.builder()
            .build_conditional_branch(is_empty, empty_block, nonempty_block)
            .map_err(|e| CodegenError::Internal(format!("branch: {:?}", e)))?;

        // Empty case: return nil
        self.builder().position_at_end(empty_block);
        let nil = self.build_nil()?;
        self.builder()
            .build_unconditional_branch(merge_block)
            .map_err(|e| CodegenError::Internal(format!("branch: {:?}", e)))?;

        // Non-empty case: extract head as init, scanl on tail
        self.builder().position_at_end(nonempty_block);
        let head_ptr = self.extract_adt_field(list_ptr, 2, 0)?;
        let tail_ptr = self.extract_adt_field(list_ptr, 2, 1)?;

        // Build reversed result starting with head (the init value)
        let nil2 = self.build_nil()?;
        let init_cons = self.build_cons(head_ptr.into(), nil2.into())?;

        let ne_entry_block = self
            .builder()
            .get_insert_block()
            .ok_or_else(|| CodegenError::Internal("no current block".to_string()))?;

        let loop_header = self
            .llvm_ctx
            .append_basic_block(current_fn, "scanl1_header");
        let loop_body = self.llvm_ctx.append_basic_block(current_fn, "scanl1_body");
        let loop_exit = self.llvm_ctx.append_basic_block(current_fn, "scanl1_exit");

        self.builder()
            .build_unconditional_branch(loop_header)
            .map_err(|e| CodegenError::Internal(format!("branch: {:?}", e)))?;

        self.builder().position_at_end(loop_header);
        let acc_phi = self
            .builder()
            .build_phi(ptr_type, "scanl1_acc")
            .map_err(|e| CodegenError::Internal(format!("phi: {:?}", e)))?;
        let l_phi = self
            .builder()
            .build_phi(ptr_type, "scanl1_list")
            .map_err(|e| CodegenError::Internal(format!("phi: {:?}", e)))?;
        let result_phi = self
            .builder()
            .build_phi(ptr_type, "scanl1_result")
            .map_err(|e| CodegenError::Internal(format!("phi: {:?}", e)))?;

        let l_tag = self.extract_adt_tag(l_phi.as_basic_value().into_pointer_value())?;
        let l_empty = self
            .builder()
            .build_int_compare(inkwell::IntPredicate::EQ, l_tag, i64_zero, "l_empty")
            .map_err(|e| CodegenError::Internal(format!("cmp: {:?}", e)))?;
        self.builder()
            .build_conditional_branch(l_empty, loop_exit, loop_body)
            .map_err(|e| CodegenError::Internal(format!("branch: {:?}", e)))?;

        // Loop body: new_acc = f(acc, head); cons new_acc onto result
        self.builder().position_at_end(loop_body);
        let lh = self.extract_adt_field(l_phi.as_basic_value().into_pointer_value(), 2, 0)?;
        let lt = self.extract_adt_field(l_phi.as_basic_value().into_pointer_value(), 2, 1)?;

        let fn_ptr = self.extract_closure_fn_ptr(func_ptr)?;
        let fn_type = ptr_type.fn_type(&[ptr_type.into(), ptr_type.into(), ptr_type.into()], false);
        let new_acc = self
            .builder()
            .build_indirect_call(
                fn_type,
                fn_ptr,
                &[func_ptr.into(), acc_phi.as_basic_value().into(), lh.into()],
                "scanl1_new_acc",
            )
            .map_err(|e| CodegenError::Internal(format!("call: {:?}", e)))?
            .try_as_basic_value()
            .basic()
            .ok_or_else(|| CodegenError::Internal("scanl1: function returned void".to_string()))?;
        let new_acc_ptr = self.value_to_ptr(new_acc)?;
        let new_cons = self.build_cons(new_acc_ptr.into(), result_phi.as_basic_value())?;

        self.builder()
            .build_unconditional_branch(loop_header)
            .map_err(|e| CodegenError::Internal(format!("branch: {:?}", e)))?;

        acc_phi.add_incoming(&[(&head_ptr, ne_entry_block), (&new_acc_ptr, loop_body)]);
        l_phi.add_incoming(&[(&tail_ptr, ne_entry_block), (&lt, loop_body)]);
        result_phi.add_incoming(&[(&init_cons, ne_entry_block), (&new_cons, loop_body)]);

        // Reverse the accumulated result
        self.builder().position_at_end(loop_exit);
        let reversed = self
            .build_inline_reverse(result_phi.as_basic_value().into_pointer_value(), current_fn)?
            .ok_or_else(|| CodegenError::Internal("scanl1: reverse failed".to_string()))?;

        self.builder()
            .build_unconditional_branch(merge_block)
            .map_err(|e| CodegenError::Internal(format!("branch: {:?}", e)))?;
        let rev_exit_block = self
            .builder()
            .get_insert_block()
            .ok_or_else(|| CodegenError::Internal("no block after reverse".to_string()))?;

        // Merge
        self.builder().position_at_end(merge_block);
        let final_phi = self
            .builder()
            .build_phi(ptr_type, "scanl1_final")
            .map_err(|e| CodegenError::Internal(format!("phi: {:?}", e)))?;
        final_phi.add_incoming(&[(&nil, empty_block), (&reversed, rev_exit_block)]);

        Ok(Some(final_phi.as_basic_value()))
    }

    /// Lower `scanr1 :: (a -> a -> a) -> [a] -> [a]`.
    /// scanr1 f [x1,...,xn] = scanr f xn [x1,...,x(n-1)]
    /// Strategy: reverse input, extract head of reversed (= original last) as init,
    /// then left-accumulate with f(elem, acc) on rest. Result is already in order.
    fn lower_builtin_scanr1(
        &mut self,
        func_expr: &Expr,
        list_expr: &Expr,
    ) -> CodegenResult<Option<BasicValueEnum<'ctx>>> {
        let func_val = self
            .lower_expr(func_expr)?
            .ok_or_else(|| CodegenError::Internal("scanr1: function has no value".to_string()))?;
        let func_ptr = match func_val {
            BasicValueEnum::PointerValue(p) => p,
            _ => {
                return Err(CodegenError::TypeError(
                    "scanr1: function must be a closure".to_string(),
                ))
            }
        };

        let list_val = self
            .lower_expr(list_expr)?
            .ok_or_else(|| CodegenError::Internal("scanr1: list has no value".to_string()))?;
        let list_ptr = match list_val {
            BasicValueEnum::PointerValue(p) => p,
            _ => return Err(CodegenError::TypeError("scanr1 expects a list".to_string())),
        };

        let ptr_type = self.type_mapper().ptr_type();
        let i64_zero = self.type_mapper().i64_type().const_zero();
        let current_fn = self
            .builder()
            .get_insert_block()
            .and_then(|b| b.get_parent())
            .ok_or_else(|| CodegenError::Internal("no current function".to_string()))?;

        // Check if empty
        let tag = self.extract_adt_tag(list_ptr)?;
        let is_empty = self
            .builder()
            .build_int_compare(inkwell::IntPredicate::EQ, tag, i64_zero, "is_empty")
            .map_err(|e| CodegenError::Internal(format!("cmp: {:?}", e)))?;

        let nonempty_block = self
            .llvm_ctx
            .append_basic_block(current_fn, "scanr1_nonempty");
        let empty_block = self.llvm_ctx.append_basic_block(current_fn, "scanr1_empty");
        let merge_block = self.llvm_ctx.append_basic_block(current_fn, "scanr1_merge");

        self.builder()
            .build_conditional_branch(is_empty, empty_block, nonempty_block)
            .map_err(|e| CodegenError::Internal(format!("branch: {:?}", e)))?;

        // Empty case
        self.builder().position_at_end(empty_block);
        let nil = self.build_nil()?;
        self.builder()
            .build_unconditional_branch(merge_block)
            .map_err(|e| CodegenError::Internal(format!("branch: {:?}", e)))?;

        // Non-empty: reverse input, extract head of reversed as init
        self.builder().position_at_end(nonempty_block);
        let reversed = self
            .build_inline_reverse(list_ptr, current_fn)?
            .ok_or_else(|| CodegenError::Internal("scanr1: reverse failed".to_string()))?;
        let reversed_ptr = reversed.into_pointer_value();

        let rev_head = self.extract_adt_field(reversed_ptr, 2, 0)?;
        let rev_tail = self.extract_adt_field(reversed_ptr, 2, 1)?;

        // Build initial result: cons init_val onto nil
        let nil2 = self.build_nil()?;
        let init_cons = self.build_cons(rev_head.into(), nil2.into())?;

        let ne_entry_block = self
            .builder()
            .get_insert_block()
            .ok_or_else(|| CodegenError::Internal("no block".to_string()))?;

        let loop_header = self
            .llvm_ctx
            .append_basic_block(current_fn, "scanr1_header");
        let loop_body = self.llvm_ctx.append_basic_block(current_fn, "scanr1_body");
        let loop_exit = self.llvm_ctx.append_basic_block(current_fn, "scanr1_exit");

        self.builder()
            .build_unconditional_branch(loop_header)
            .map_err(|e| CodegenError::Internal(format!("branch: {:?}", e)))?;

        self.builder().position_at_end(loop_header);
        let acc_phi = self
            .builder()
            .build_phi(ptr_type, "scanr1_acc")
            .map_err(|e| CodegenError::Internal(format!("phi: {:?}", e)))?;
        let l_phi = self
            .builder()
            .build_phi(ptr_type, "scanr1_list")
            .map_err(|e| CodegenError::Internal(format!("phi: {:?}", e)))?;
        let result_phi = self
            .builder()
            .build_phi(ptr_type, "scanr1_result")
            .map_err(|e| CodegenError::Internal(format!("phi: {:?}", e)))?;

        let l_tag = self.extract_adt_tag(l_phi.as_basic_value().into_pointer_value())?;
        let l_empty = self
            .builder()
            .build_int_compare(inkwell::IntPredicate::EQ, l_tag, i64_zero, "l_empty")
            .map_err(|e| CodegenError::Internal(format!("cmp: {:?}", e)))?;
        self.builder()
            .build_conditional_branch(l_empty, loop_exit, loop_body)
            .map_err(|e| CodegenError::Internal(format!("branch: {:?}", e)))?;

        // Loop body: new_acc = f(head, acc), cons onto result
        self.builder().position_at_end(loop_body);
        let lh = self.extract_adt_field(l_phi.as_basic_value().into_pointer_value(), 2, 0)?;
        let lt = self.extract_adt_field(l_phi.as_basic_value().into_pointer_value(), 2, 1)?;

        let fn_ptr = self.extract_closure_fn_ptr(func_ptr)?;
        let fn_type = ptr_type.fn_type(&[ptr_type.into(), ptr_type.into(), ptr_type.into()], false);
        let new_acc = self
            .builder()
            .build_indirect_call(
                fn_type,
                fn_ptr,
                &[func_ptr.into(), lh.into(), acc_phi.as_basic_value().into()],
                "scanr1_new_acc",
            )
            .map_err(|e| CodegenError::Internal(format!("call: {:?}", e)))?
            .try_as_basic_value()
            .basic()
            .ok_or_else(|| CodegenError::Internal("scanr1: function returned void".to_string()))?;
        let new_acc_ptr = self.value_to_ptr(new_acc)?;
        let new_cons = self.build_cons(new_acc_ptr.into(), result_phi.as_basic_value())?;

        self.builder()
            .build_unconditional_branch(loop_header)
            .map_err(|e| CodegenError::Internal(format!("branch: {:?}", e)))?;

        acc_phi.add_incoming(&[(&rev_head, ne_entry_block), (&new_acc_ptr, loop_body)]);
        l_phi.add_incoming(&[(&rev_tail, ne_entry_block), (&lt, loop_body)]);
        result_phi.add_incoming(&[(&init_cons, ne_entry_block), (&new_cons, loop_body)]);

        // Result is already in the correct order (built forward from rightmost element)
        self.builder().position_at_end(loop_exit);
        self.builder()
            .build_unconditional_branch(merge_block)
            .map_err(|e| CodegenError::Internal(format!("branch: {:?}", e)))?;

        self.builder().position_at_end(merge_block);
        let final_phi = self
            .builder()
            .build_phi(ptr_type, "scanr1_final")
            .map_err(|e| CodegenError::Internal(format!("phi: {:?}", e)))?;
        final_phi.add_incoming(&[
            (&nil, empty_block),
            (&result_phi.as_basic_value(), loop_exit),
        ]);

        Ok(Some(final_phi.as_basic_value()))
    }

    /// Lower `unfoldr :: (b -> Maybe (a, b)) -> b -> [a]`.
    /// Loop: call f(seed), if Nothing stop, if Just (val, newSeed) cons val and continue.
    fn lower_builtin_unfoldr(
        &mut self,
        func_expr: &Expr,
        seed_expr: &Expr,
    ) -> CodegenResult<Option<BasicValueEnum<'ctx>>> {
        let func_val = self
            .lower_expr(func_expr)?
            .ok_or_else(|| CodegenError::Internal("unfoldr: function has no value".to_string()))?;
        let func_ptr = match func_val {
            BasicValueEnum::PointerValue(p) => p,
            _ => {
                return Err(CodegenError::TypeError(
                    "unfoldr: function must be a closure".to_string(),
                ))
            }
        };

        let seed_val = self
            .lower_expr(seed_expr)?
            .ok_or_else(|| CodegenError::Internal("unfoldr: seed has no value".to_string()))?;
        let seed_ptr = self.value_to_ptr(seed_val)?;

        let tm = self.type_mapper();
        let ptr_type = tm.ptr_type();
        let current_fn = self
            .builder()
            .get_insert_block()
            .and_then(|b| b.get_parent())
            .ok_or_else(|| CodegenError::Internal("no current function".to_string()))?;
        let entry_block = self
            .builder()
            .get_insert_block()
            .ok_or_else(|| CodegenError::Internal("no current block".to_string()))?;

        let nil = self.build_nil()?;

        let loop_header = self
            .llvm_ctx
            .append_basic_block(current_fn, "unfoldr_header");
        let loop_body = self.llvm_ctx.append_basic_block(current_fn, "unfoldr_body");
        let loop_exit = self.llvm_ctx.append_basic_block(current_fn, "unfoldr_exit");

        self.builder()
            .build_unconditional_branch(loop_header)
            .map_err(|e| CodegenError::Internal(format!("branch: {:?}", e)))?;

        self.builder().position_at_end(loop_header);
        let seed_phi = self
            .builder()
            .build_phi(ptr_type, "unfoldr_seed")
            .map_err(|e| CodegenError::Internal(format!("phi: {:?}", e)))?;
        let result_phi = self
            .builder()
            .build_phi(ptr_type, "unfoldr_result")
            .map_err(|e| CodegenError::Internal(format!("phi: {:?}", e)))?;

        // Call f(seed) -> Maybe (a, b)
        let fn_ptr = self.extract_closure_fn_ptr(func_ptr)?;
        let fn_type = ptr_type.fn_type(&[ptr_type.into(), ptr_type.into()], false);
        let maybe_val = self
            .builder()
            .build_indirect_call(
                fn_type,
                fn_ptr,
                &[func_ptr.into(), seed_phi.as_basic_value().into()],
                "unfoldr_maybe",
            )
            .map_err(|e| CodegenError::Internal(format!("call: {:?}", e)))?
            .try_as_basic_value()
            .basic()
            .ok_or_else(|| CodegenError::Internal("unfoldr: function returned void".to_string()))?;
        let maybe_ptr = self.value_to_ptr(maybe_val)?;

        // Check Maybe tag: 0 = Nothing, 1 = Just
        let maybe_tag = self.extract_adt_tag(maybe_ptr)?;
        let is_nothing = self
            .builder()
            .build_int_compare(
                inkwell::IntPredicate::EQ,
                maybe_tag,
                tm.i64_type().const_zero(),
                "is_nothing",
            )
            .map_err(|e| CodegenError::Internal(format!("cmp: {:?}", e)))?;

        self.builder()
            .build_conditional_branch(is_nothing, loop_exit, loop_body)
            .map_err(|e| CodegenError::Internal(format!("branch: {:?}", e)))?;

        // Loop body: extract (val, newSeed) from Just payload
        self.builder().position_at_end(loop_body);
        // Just has 1 field: the payload, which is a tuple (a, b)
        let tuple_ptr = self.extract_adt_field(maybe_ptr, 1, 0)?;
        // Extract fst (value) and snd (new seed) from the tuple
        let val_ptr = self.extract_adt_field(tuple_ptr, 2, 0)?;
        let new_seed_ptr = self.extract_adt_field(tuple_ptr, 2, 1)?;

        let new_cons = self.build_cons(val_ptr.into(), result_phi.as_basic_value())?;

        self.builder()
            .build_unconditional_branch(loop_header)
            .map_err(|e| CodegenError::Internal(format!("branch: {:?}", e)))?;

        seed_phi.add_incoming(&[(&seed_ptr, entry_block), (&new_seed_ptr, loop_body)]);
        result_phi.add_incoming(&[(&nil, entry_block), (&new_cons, loop_body)]);

        // Reverse the accumulated result
        self.builder().position_at_end(loop_exit);
        let reversed = self
            .build_inline_reverse(result_phi.as_basic_value().into_pointer_value(), current_fn)?;
        Ok(reversed)
    }

    /// Lower `lines` — call RTS bhc_string_lines.
    fn lower_builtin_lines(
        &mut self,
        str_expr: &Expr,
    ) -> CodegenResult<Option<BasicValueEnum<'ctx>>> {
        let str_val = self
            .lower_expr(str_expr)?
            .ok_or_else(|| CodegenError::Internal("lines: no string".to_string()))?;
        // Convert [Char] input to C-string for RTS
        let str_ptr = self.value_to_ptr(str_val)?;
        let str_cstr = self.char_list_to_cstring(str_ptr)?;
        let rts_fn = self
            .functions
            .get(&VarId::new(1000176))
            .ok_or_else(|| CodegenError::Internal("bhc_string_lines not declared".to_string()))?;
        let cstr_list = self
            .builder()
            .build_call(*rts_fn, &[str_cstr.into()], "lines")
            .map_err(|e| CodegenError::Internal(format!("lines call failed: {:?}", e)))?
            .try_as_basic_value()
            .basic()
            .ok_or_else(|| CodegenError::Internal("lines: returned void".to_string()))?;
        // Convert list of C-strings to list of [Char] lists
        let result = self.cstring_list_to_char_list_list(cstr_list.into_pointer_value())?;
        Ok(Some(result.into()))
    }

    /// Lower `unlines` — call RTS bhc_string_unlines.
    fn lower_builtin_unlines(
        &mut self,
        list_expr: &Expr,
    ) -> CodegenResult<Option<BasicValueEnum<'ctx>>> {
        let list_val = self
            .lower_expr(list_expr)?
            .ok_or_else(|| CodegenError::Internal("unlines: no list".to_string()))?;
        // Convert list of [Char] lists to list of C-strings for RTS
        let list_ptr = self.value_to_ptr(list_val)?;
        let cstr_list = self.char_list_list_to_cstring_list(list_ptr)?;
        let rts_fn = self
            .functions
            .get(&VarId::new(1000177))
            .ok_or_else(|| CodegenError::Internal("bhc_string_unlines not declared".to_string()))?;
        let cstr_result = self
            .builder()
            .build_call(*rts_fn, &[cstr_list.into()], "unlines")
            .map_err(|e| CodegenError::Internal(format!("unlines call failed: {:?}", e)))?
            .try_as_basic_value()
            .basic()
            .ok_or_else(|| CodegenError::Internal("unlines: returned void".to_string()))?;
        // Convert C-string result to [Char] linked list
        let char_list = self.cstring_to_char_list(cstr_result.into_pointer_value())?;
        Ok(Some(char_list.into()))
    }

    /// Lower `words` — call RTS bhc_string_words.
    fn lower_builtin_words(
        &mut self,
        str_expr: &Expr,
    ) -> CodegenResult<Option<BasicValueEnum<'ctx>>> {
        let str_val = self
            .lower_expr(str_expr)?
            .ok_or_else(|| CodegenError::Internal("words: no string".to_string()))?;
        // Convert [Char] input to C-string for RTS
        let str_ptr = self.value_to_ptr(str_val)?;
        let str_cstr = self.char_list_to_cstring(str_ptr)?;
        let rts_fn = self
            .functions
            .get(&VarId::new(1000178))
            .ok_or_else(|| CodegenError::Internal("bhc_string_words not declared".to_string()))?;
        let cstr_list = self
            .builder()
            .build_call(*rts_fn, &[str_cstr.into()], "words")
            .map_err(|e| CodegenError::Internal(format!("words call failed: {:?}", e)))?
            .try_as_basic_value()
            .basic()
            .ok_or_else(|| CodegenError::Internal("words: returned void".to_string()))?;
        // Convert list of C-strings to list of [Char] lists
        let result = self.cstring_list_to_char_list_list(cstr_list.into_pointer_value())?;
        Ok(Some(result.into()))
    }

    /// Lower `unwords` — call RTS bhc_string_unwords.
    fn lower_builtin_unwords(
        &mut self,
        list_expr: &Expr,
    ) -> CodegenResult<Option<BasicValueEnum<'ctx>>> {
        let list_val = self
            .lower_expr(list_expr)?
            .ok_or_else(|| CodegenError::Internal("unwords: no list".to_string()))?;
        // Convert list of [Char] lists to list of C-strings for RTS
        let list_ptr = self.value_to_ptr(list_val)?;
        let cstr_list = self.char_list_list_to_cstring_list(list_ptr)?;
        let rts_fn = self
            .functions
            .get(&VarId::new(1000179))
            .ok_or_else(|| CodegenError::Internal("bhc_string_unwords not declared".to_string()))?;
        let cstr_result = self
            .builder()
            .build_call(*rts_fn, &[cstr_list.into()], "unwords")
            .map_err(|e| CodegenError::Internal(format!("unwords call failed: {:?}", e)))?
            .try_as_basic_value()
            .basic()
            .ok_or_else(|| CodegenError::Internal("unwords: returned void".to_string()))?;
        // Convert C-string result to [Char] linked list
        let char_list = self.cstring_to_char_list(cstr_result.into_pointer_value())?;
        Ok(Some(char_list.into()))
    }

    /// Lower `read :: String -> a`.
    ///
    /// For user-defined ADTs with derived Read, generates inline char-list
    /// comparison against constructor names. For Int, falls back to RTS.
    fn lower_builtin_read(
        &mut self,
        str_expr: &Expr,
    ) -> CodegenResult<Option<BasicValueEnum<'ctx>>> {
        // Check for derived Read instance on the result type
        if let Some(type_name) = self.infer_read_target_type(str_expr) {
            if self.derived_read_fns.contains_key(&type_name) {
                // Collect constructor names and tags for this type
                let constructors: Vec<(String, u32)> = self
                    .constructor_metadata
                    .iter()
                    .filter_map(|(name, meta)| {
                        if meta.type_name.as_deref() == Some(&type_name) {
                            Some((name.clone(), meta.tag))
                        } else {
                            None
                        }
                    })
                    .collect();

                if !constructors.is_empty() {
                    return self.lower_read_adt_inline(str_expr, &constructors);
                }
            }
        }

        // Default: read as Int via RTS
        let str_val = self
            .lower_expr(str_expr)?
            .ok_or_else(|| CodegenError::Internal("read: no string".to_string()))?;
        let str_ptr = self.value_to_ptr(str_val)?;
        let rts_fn = self
            .functions
            .get(&VarId::new(1000540))
            .ok_or_else(|| CodegenError::Internal("bhc_read_int not declared".to_string()))?;
        let result = self
            .builder()
            .build_call(*rts_fn, &[str_ptr.into()], "read_int")
            .map_err(|e| CodegenError::Internal(format!("read call failed: {:?}", e)))?
            .try_as_basic_value()
            .basic()
            .ok_or_else(|| CodegenError::Internal("read: returned void".to_string()))?;
        // Convert i64 to int-as-pointer
        let i64_val = result.into_int_value();
        let ptr_val = self
            .builder()
            .build_int_to_ptr(i64_val, self.type_mapper().ptr_type(), "read_as_ptr")
            .map_err(|e| CodegenError::Internal(format!("read int_to_ptr: {:?}", e)))?;
        Ok(Some(ptr_val.into()))
    }

    /// Generate inline ADT read: compare char list against constructor names
    /// character by character, return matching tag-as-pointer.
    fn lower_read_adt_inline(
        &mut self,
        str_expr: &Expr,
        constructors: &[(String, u32)],
    ) -> CodegenResult<Option<BasicValueEnum<'ctx>>> {
        let str_val = self
            .lower_expr(str_expr)?
            .ok_or_else(|| CodegenError::Internal("read: no string".to_string()))?;
        let list_ptr = self.value_to_ptr(str_val)?;

        let tm = self.type_mapper();
        let ptr_type = tm.ptr_type();
        let i64_type = tm.i64_type();
        let current_fn = self
            .builder()
            .get_insert_block()
            .and_then(|b| b.get_parent())
            .ok_or_else(|| CodegenError::Internal("no current function".to_string()))?;

        // Create merge block where the result will be collected
        let merge_block = self
            .llvm_context()
            .append_basic_block(current_fn, "read_merge");

        // Build phi in merge block to collect results
        let mut incoming: Vec<(BasicValueEnum<'ctx>, inkwell::basic_block::BasicBlock<'ctx>)> =
            Vec::new();

        for (con_name, tag) in constructors {
            // For each constructor, generate a comparison chain:
            // Walk the char list and compare each character against the constructor name.
            // If all chars match and list ends, this is a match.

            let check_block = self
                .llvm_context()
                .append_basic_block(current_fn, &format!("read_check_{}", con_name));
            let match_block = self
                .llvm_context()
                .append_basic_block(current_fn, &format!("read_match_{}", con_name));
            let next_block = self
                .llvm_context()
                .append_basic_block(current_fn, &format!("read_next_{}", con_name));

            // Branch to check block
            self.builder()
                .build_unconditional_branch(check_block)
                .map_err(|e| CodegenError::Internal(format!("branch: {:?}", e)))?;

            self.builder().position_at_end(check_block);

            // Walk the list, comparing each char
            let mut current_list = list_ptr;
            let _all_matched = true;
            let chars: Vec<char> = con_name.chars().collect();

            for (i, ch) in chars.iter().enumerate() {
                let char_check = self
                    .llvm_context()
                    .append_basic_block(current_fn, &format!("read_{}_ch{}", con_name, i));
                let char_mismatch = self
                    .llvm_context()
                    .append_basic_block(current_fn, &format!("read_{}_miss{}", con_name, i));

                // Check if list is non-empty (tag != 0)
                let tag_val = self.extract_adt_tag(current_list)?;
                let is_cons = self
                    .builder()
                    .build_int_compare(
                        inkwell::IntPredicate::NE,
                        tag_val,
                        i64_type.const_zero(),
                        "is_cons",
                    )
                    .map_err(|e| CodegenError::Internal(format!("cmp: {:?}", e)))?;
                self.builder()
                    .build_conditional_branch(is_cons, char_check, char_mismatch)
                    .map_err(|e| CodegenError::Internal(format!("branch: {:?}", e)))?;

                // char_mismatch: list too short, go to next constructor
                self.builder().position_at_end(char_mismatch);
                self.builder()
                    .build_unconditional_branch(next_block)
                    .map_err(|e| CodegenError::Internal(format!("branch: {:?}", e)))?;

                // char_check: extract head char and compare
                self.builder().position_at_end(char_check);
                let head_ptr = self.extract_adt_field(current_list, 2, 0)?;
                let char_val = self
                    .builder()
                    .build_ptr_to_int(head_ptr, i64_type, "char_val")
                    .map_err(|e| CodegenError::Internal(format!("ptr_to_int: {:?}", e)))?;
                let expected = i64_type.const_int(*ch as u64, false);
                let chars_eq = self
                    .builder()
                    .build_int_compare(inkwell::IntPredicate::EQ, char_val, expected, "char_eq")
                    .map_err(|e| CodegenError::Internal(format!("cmp: {:?}", e)))?;

                let next_char = self
                    .llvm_context()
                    .append_basic_block(current_fn, &format!("read_{}_next{}", con_name, i));

                self.builder()
                    .build_conditional_branch(chars_eq, next_char, next_block)
                    .map_err(|e| CodegenError::Internal(format!("branch: {:?}", e)))?;

                // next_char: advance to tail
                self.builder().position_at_end(next_char);
                let tail = self.extract_adt_field(current_list, 2, 1)?;
                current_list = tail;
            }

            // After all chars matched, check that list is empty (tag == 0)
            let end_tag = self.extract_adt_tag(current_list)?;
            let is_nil = self
                .builder()
                .build_int_compare(
                    inkwell::IntPredicate::EQ,
                    end_tag,
                    i64_type.const_zero(),
                    "is_nil",
                )
                .map_err(|e| CodegenError::Internal(format!("cmp: {:?}", e)))?;
            self.builder()
                .build_conditional_branch(is_nil, match_block, next_block)
                .map_err(|e| CodegenError::Internal(format!("branch: {:?}", e)))?;

            // match_block: construct the ADT value (heap-allocated with tag)
            self.builder().position_at_end(match_block);
            let result_ptr = self.alloc_adt(*tag, 0)?;
            incoming.push((result_ptr.into(), match_block));
            self.builder()
                .build_unconditional_branch(merge_block)
                .map_err(|e| CodegenError::Internal(format!("branch: {:?}", e)))?;

            // next_block: continue to next constructor
            self.builder().position_at_end(next_block);
        }

        // After all constructors checked without match: return null (error)
        let null_ptr = ptr_type.const_null();
        let final_block = self.builder().get_insert_block().unwrap();
        incoming.push((null_ptr.into(), final_block));
        self.builder()
            .build_unconditional_branch(merge_block)
            .map_err(|e| CodegenError::Internal(format!("branch: {:?}", e)))?;

        // Merge block: phi to select the result
        self.builder().position_at_end(merge_block);
        let phi = self
            .builder()
            .build_phi(ptr_type, "read_result")
            .map_err(|e| CodegenError::Internal(format!("phi: {:?}", e)))?;
        for (val, block) in &incoming {
            phi.add_incoming(&[(&val.into_pointer_value(), *block)]);
        }

        Ok(Some(phi.as_basic_value()))
    }

    /// Lower `readMaybe :: String -> Maybe Int` (monomorphic).
    ///
    /// Passes the char list to the RTS `bhc_try_read_int` which returns a
    /// Maybe ADT pointer (Nothing on parse failure, Just n on success).
    fn lower_builtin_read_maybe(
        &mut self,
        str_expr: &Expr,
    ) -> CodegenResult<Option<BasicValueEnum<'ctx>>> {
        let str_val = self
            .lower_expr(str_expr)?
            .ok_or_else(|| CodegenError::Internal("readMaybe: no string".to_string()))?;
        let str_ptr = self.value_to_ptr(str_val)?;
        let rts_fn = self
            .functions
            .get(&VarId::new(1000541))
            .ok_or_else(|| CodegenError::Internal("bhc_try_read_int not declared".to_string()))?;
        let result = self
            .builder()
            .build_call(*rts_fn, &[str_ptr.into()], "try_read_int")
            .map_err(|e| CodegenError::Internal(format!("readMaybe call failed: {:?}", e)))?
            .try_as_basic_value()
            .basic()
            .ok_or_else(|| CodegenError::Internal("readMaybe: returned void".to_string()))?;
        Ok(Some(result))
    }

    /// Lower `zip3 :: [a] -> [b] -> [c] -> [(a,b,c)]`.
    /// 3-list parallel iteration, build 3-tuples.
    fn lower_builtin_zip3(
        &mut self,
        list1_expr: &Expr,
        list2_expr: &Expr,
        list3_expr: &Expr,
    ) -> CodegenResult<Option<BasicValueEnum<'ctx>>> {
        let list1_val = self
            .lower_expr(list1_expr)?
            .ok_or_else(|| CodegenError::Internal("zip3: list1 no value".to_string()))?;
        let list1_ptr = match list1_val {
            BasicValueEnum::PointerValue(p) => p,
            _ => return Err(CodegenError::TypeError("zip3 expects lists".to_string())),
        };
        let list2_val = self
            .lower_expr(list2_expr)?
            .ok_or_else(|| CodegenError::Internal("zip3: list2 no value".to_string()))?;
        let list2_ptr = match list2_val {
            BasicValueEnum::PointerValue(p) => p,
            _ => return Err(CodegenError::TypeError("zip3 expects lists".to_string())),
        };
        let list3_val = self
            .lower_expr(list3_expr)?
            .ok_or_else(|| CodegenError::Internal("zip3: list3 no value".to_string()))?;
        let list3_ptr = match list3_val {
            BasicValueEnum::PointerValue(p) => p,
            _ => return Err(CodegenError::TypeError("zip3 expects lists".to_string())),
        };

        let tm = self.type_mapper();
        let ptr_type = tm.ptr_type();
        let current_fn = self
            .builder()
            .get_insert_block()
            .and_then(|b| b.get_parent())
            .ok_or_else(|| CodegenError::Internal("no current function".to_string()))?;
        let entry_block = self
            .builder()
            .get_insert_block()
            .ok_or_else(|| CodegenError::Internal("no current block".to_string()))?;

        let nil = self.build_nil()?;
        let loop_header = self.llvm_ctx.append_basic_block(current_fn, "zip3_header");
        let loop_body = self.llvm_ctx.append_basic_block(current_fn, "zip3_body");
        let loop_exit = self.llvm_ctx.append_basic_block(current_fn, "zip3_exit");

        self.builder()
            .build_unconditional_branch(loop_header)
            .map_err(|e| CodegenError::Internal(format!("branch: {:?}", e)))?;

        self.builder().position_at_end(loop_header);
        let result_phi = self
            .builder()
            .build_phi(ptr_type, "zip3_result")
            .map_err(|e| CodegenError::Internal(format!("phi: {:?}", e)))?;
        let l1_phi = self
            .builder()
            .build_phi(ptr_type, "zip3_l1")
            .map_err(|e| CodegenError::Internal(format!("phi: {:?}", e)))?;
        let l2_phi = self
            .builder()
            .build_phi(ptr_type, "zip3_l2")
            .map_err(|e| CodegenError::Internal(format!("phi: {:?}", e)))?;
        let l3_phi = self
            .builder()
            .build_phi(ptr_type, "zip3_l3")
            .map_err(|e| CodegenError::Internal(format!("phi: {:?}", e)))?;

        // Check if any list is empty
        let tag1 = self.extract_adt_tag(l1_phi.as_basic_value().into_pointer_value())?;
        let tag2 = self.extract_adt_tag(l2_phi.as_basic_value().into_pointer_value())?;
        let tag3 = self.extract_adt_tag(l3_phi.as_basic_value().into_pointer_value())?;
        let empty1 = self
            .builder()
            .build_int_compare(
                inkwell::IntPredicate::EQ,
                tag1,
                tm.i64_type().const_zero(),
                "e1",
            )
            .map_err(|e| CodegenError::Internal(format!("cmp: {:?}", e)))?;
        let empty2 = self
            .builder()
            .build_int_compare(
                inkwell::IntPredicate::EQ,
                tag2,
                tm.i64_type().const_zero(),
                "e2",
            )
            .map_err(|e| CodegenError::Internal(format!("cmp: {:?}", e)))?;
        let empty3 = self
            .builder()
            .build_int_compare(
                inkwell::IntPredicate::EQ,
                tag3,
                tm.i64_type().const_zero(),
                "e3",
            )
            .map_err(|e| CodegenError::Internal(format!("cmp: {:?}", e)))?;
        let any_empty = self
            .builder()
            .build_or(empty1, empty2, "e12")
            .map_err(|e| CodegenError::Internal(format!("or: {:?}", e)))?;
        let any_empty = self
            .builder()
            .build_or(any_empty, empty3, "e123")
            .map_err(|e| CodegenError::Internal(format!("or: {:?}", e)))?;

        self.builder()
            .build_conditional_branch(any_empty, loop_exit, loop_body)
            .map_err(|e| CodegenError::Internal(format!("branch: {:?}", e)))?;

        // Loop body: extract heads, build triple, cons
        self.builder().position_at_end(loop_body);
        let h1 = self.extract_adt_field(l1_phi.as_basic_value().into_pointer_value(), 2, 0)?;
        let t1 = self.extract_adt_field(l1_phi.as_basic_value().into_pointer_value(), 2, 1)?;
        let h2 = self.extract_adt_field(l2_phi.as_basic_value().into_pointer_value(), 2, 0)?;
        let t2 = self.extract_adt_field(l2_phi.as_basic_value().into_pointer_value(), 2, 1)?;
        let h3 = self.extract_adt_field(l3_phi.as_basic_value().into_pointer_value(), 2, 0)?;
        let t3 = self.extract_adt_field(l3_phi.as_basic_value().into_pointer_value(), 2, 1)?;

        let triple = self.build_triple(h1.into(), h2.into(), h3.into())?;
        let new_cons = self.build_cons(triple.into(), result_phi.as_basic_value())?;

        self.builder()
            .build_unconditional_branch(loop_header)
            .map_err(|e| CodegenError::Internal(format!("branch: {:?}", e)))?;

        result_phi.add_incoming(&[(&nil, entry_block), (&new_cons, loop_body)]);
        l1_phi.add_incoming(&[(&list1_ptr, entry_block), (&t1, loop_body)]);
        l2_phi.add_incoming(&[(&list2_ptr, entry_block), (&t2, loop_body)]);
        l3_phi.add_incoming(&[(&list3_ptr, entry_block), (&t3, loop_body)]);

        // Reverse result
        self.builder().position_at_end(loop_exit);
        let reversed = self
            .build_inline_reverse(result_phi.as_basic_value().into_pointer_value(), current_fn)?;
        Ok(reversed)
    }

    /// Lower `zipWith3 :: (a -> b -> c -> d) -> [a] -> [b] -> [c] -> [d]`.
    /// 3-list parallel iteration, call 3-arg function.
    fn lower_builtin_zipwith3(
        &mut self,
        func_expr: &Expr,
        list1_expr: &Expr,
        list2_expr: &Expr,
        list3_expr: &Expr,
    ) -> CodegenResult<Option<BasicValueEnum<'ctx>>> {
        let func_val = self
            .lower_expr(func_expr)?
            .ok_or_else(|| CodegenError::Internal("zipWith3: func no value".to_string()))?;
        let func_ptr = match func_val {
            BasicValueEnum::PointerValue(p) => p,
            _ => {
                return Err(CodegenError::TypeError(
                    "zipWith3: function must be a closure".to_string(),
                ))
            }
        };
        let list1_val = self
            .lower_expr(list1_expr)?
            .ok_or_else(|| CodegenError::Internal("zipWith3: list1 no value".to_string()))?;
        let list1_ptr = match list1_val {
            BasicValueEnum::PointerValue(p) => p,
            _ => {
                return Err(CodegenError::TypeError(
                    "zipWith3 expects lists".to_string(),
                ))
            }
        };
        let list2_val = self
            .lower_expr(list2_expr)?
            .ok_or_else(|| CodegenError::Internal("zipWith3: list2 no value".to_string()))?;
        let list2_ptr = match list2_val {
            BasicValueEnum::PointerValue(p) => p,
            _ => {
                return Err(CodegenError::TypeError(
                    "zipWith3 expects lists".to_string(),
                ))
            }
        };
        let list3_val = self
            .lower_expr(list3_expr)?
            .ok_or_else(|| CodegenError::Internal("zipWith3: list3 no value".to_string()))?;
        let list3_ptr = match list3_val {
            BasicValueEnum::PointerValue(p) => p,
            _ => {
                return Err(CodegenError::TypeError(
                    "zipWith3 expects lists".to_string(),
                ))
            }
        };

        let tm = self.type_mapper();
        let ptr_type = tm.ptr_type();
        let current_fn = self
            .builder()
            .get_insert_block()
            .and_then(|b| b.get_parent())
            .ok_or_else(|| CodegenError::Internal("no current function".to_string()))?;
        let entry_block = self
            .builder()
            .get_insert_block()
            .ok_or_else(|| CodegenError::Internal("no current block".to_string()))?;

        let nil = self.build_nil()?;
        let loop_header = self.llvm_ctx.append_basic_block(current_fn, "zw3_header");
        let loop_body = self.llvm_ctx.append_basic_block(current_fn, "zw3_body");
        let loop_exit = self.llvm_ctx.append_basic_block(current_fn, "zw3_exit");

        self.builder()
            .build_unconditional_branch(loop_header)
            .map_err(|e| CodegenError::Internal(format!("branch: {:?}", e)))?;

        self.builder().position_at_end(loop_header);
        let result_phi = self
            .builder()
            .build_phi(ptr_type, "zw3_result")
            .map_err(|e| CodegenError::Internal(format!("phi: {:?}", e)))?;
        let l1_phi = self
            .builder()
            .build_phi(ptr_type, "zw3_l1")
            .map_err(|e| CodegenError::Internal(format!("phi: {:?}", e)))?;
        let l2_phi = self
            .builder()
            .build_phi(ptr_type, "zw3_l2")
            .map_err(|e| CodegenError::Internal(format!("phi: {:?}", e)))?;
        let l3_phi = self
            .builder()
            .build_phi(ptr_type, "zw3_l3")
            .map_err(|e| CodegenError::Internal(format!("phi: {:?}", e)))?;

        let tag1 = self.extract_adt_tag(l1_phi.as_basic_value().into_pointer_value())?;
        let tag2 = self.extract_adt_tag(l2_phi.as_basic_value().into_pointer_value())?;
        let tag3 = self.extract_adt_tag(l3_phi.as_basic_value().into_pointer_value())?;
        let empty1 = self
            .builder()
            .build_int_compare(
                inkwell::IntPredicate::EQ,
                tag1,
                tm.i64_type().const_zero(),
                "e1",
            )
            .map_err(|e| CodegenError::Internal(format!("cmp: {:?}", e)))?;
        let empty2 = self
            .builder()
            .build_int_compare(
                inkwell::IntPredicate::EQ,
                tag2,
                tm.i64_type().const_zero(),
                "e2",
            )
            .map_err(|e| CodegenError::Internal(format!("cmp: {:?}", e)))?;
        let empty3 = self
            .builder()
            .build_int_compare(
                inkwell::IntPredicate::EQ,
                tag3,
                tm.i64_type().const_zero(),
                "e3",
            )
            .map_err(|e| CodegenError::Internal(format!("cmp: {:?}", e)))?;
        let any_empty = self
            .builder()
            .build_or(empty1, empty2, "e12")
            .map_err(|e| CodegenError::Internal(format!("or: {:?}", e)))?;
        let any_empty = self
            .builder()
            .build_or(any_empty, empty3, "e123")
            .map_err(|e| CodegenError::Internal(format!("or: {:?}", e)))?;

        self.builder()
            .build_conditional_branch(any_empty, loop_exit, loop_body)
            .map_err(|e| CodegenError::Internal(format!("branch: {:?}", e)))?;

        // Loop body: extract heads, call f(h1, h2, h3), cons result
        self.builder().position_at_end(loop_body);
        let h1 = self.extract_adt_field(l1_phi.as_basic_value().into_pointer_value(), 2, 0)?;
        let t1 = self.extract_adt_field(l1_phi.as_basic_value().into_pointer_value(), 2, 1)?;
        let h2 = self.extract_adt_field(l2_phi.as_basic_value().into_pointer_value(), 2, 0)?;
        let t2 = self.extract_adt_field(l2_phi.as_basic_value().into_pointer_value(), 2, 1)?;
        let h3 = self.extract_adt_field(l3_phi.as_basic_value().into_pointer_value(), 2, 0)?;
        let t3 = self.extract_adt_field(l3_phi.as_basic_value().into_pointer_value(), 2, 1)?;

        // Call f as 4-arg closure: fn(closure_ptr, h1, h2, h3) -> result
        let closure_fn_ptr = self.extract_closure_fn_ptr(func_ptr)?;
        let fn_type = ptr_type.fn_type(
            &[
                ptr_type.into(),
                ptr_type.into(),
                ptr_type.into(),
                ptr_type.into(),
            ],
            false,
        );
        let mapped_val = self
            .builder()
            .build_indirect_call(
                fn_type,
                closure_fn_ptr,
                &[func_ptr.into(), h1.into(), h2.into(), h3.into()],
                "zw3_val",
            )
            .map_err(|e| CodegenError::Internal(format!("call: {:?}", e)))?
            .try_as_basic_value()
            .basic()
            .ok_or_else(|| {
                CodegenError::Internal("zipWith3: function returned void".to_string())
            })?;

        let new_cons = self.build_cons(mapped_val, result_phi.as_basic_value())?;

        self.builder()
            .build_unconditional_branch(loop_header)
            .map_err(|e| CodegenError::Internal(format!("branch: {:?}", e)))?;

        result_phi.add_incoming(&[(&nil, entry_block), (&new_cons, loop_body)]);
        l1_phi.add_incoming(&[(&list1_ptr, entry_block), (&t1, loop_body)]);
        l2_phi.add_incoming(&[(&list2_ptr, entry_block), (&t2, loop_body)]);
        l3_phi.add_incoming(&[(&list3_ptr, entry_block), (&t3, loop_body)]);

        // Reverse result
        self.builder().position_at_end(loop_exit);
        let reversed = self
            .build_inline_reverse(result_phi.as_basic_value().into_pointer_value(), current_fn)?;
        Ok(reversed)
    }

    /// Lower `unzip` — split list of pairs into pair of lists.
    /// unzip [(a1,b1), (a2,b2), ...] = ([a1,a2,...], [b1,b2,...])
    fn lower_builtin_unzip(
        &mut self,
        list_expr: &Expr,
    ) -> CodegenResult<Option<BasicValueEnum<'ctx>>> {
        let list_val = self
            .lower_expr(list_expr)?
            .ok_or_else(|| CodegenError::Internal("unzip: no list".to_string()))?;
        let list_ptr = match list_val {
            BasicValueEnum::PointerValue(p) => p,
            _ => return Err(CodegenError::TypeError("unzip expects a list".to_string())),
        };

        let tm = self.type_mapper();
        let ptr_type = tm.ptr_type();
        let current_fn = self
            .builder()
            .get_insert_block()
            .and_then(|b| b.get_parent())
            .ok_or_else(|| CodegenError::Internal("no current function".to_string()))?;
        let entry_block = self
            .builder()
            .get_insert_block()
            .ok_or_else(|| CodegenError::Internal("no current block".to_string()))?;

        let loop_header = self.llvm_ctx.append_basic_block(current_fn, "unzip_header");
        let loop_body = self.llvm_ctx.append_basic_block(current_fn, "unzip_body");
        let loop_exit = self.llvm_ctx.append_basic_block(current_fn, "unzip_exit");

        let nil = self.build_nil()?;
        self.builder()
            .build_unconditional_branch(loop_header)
            .map_err(|e| CodegenError::Internal(format!("branch: {:?}", e)))?;

        self.builder().position_at_end(loop_header);
        let fsts_phi = self
            .builder()
            .build_phi(ptr_type, "unzip_fsts")
            .map_err(|e| CodegenError::Internal(format!("phi: {:?}", e)))?;
        let snds_phi = self
            .builder()
            .build_phi(ptr_type, "unzip_snds")
            .map_err(|e| CodegenError::Internal(format!("phi: {:?}", e)))?;
        let list_phi = self
            .builder()
            .build_phi(ptr_type, "unzip_list")
            .map_err(|e| CodegenError::Internal(format!("phi: {:?}", e)))?;

        let tag = self.extract_adt_tag(list_phi.as_basic_value().into_pointer_value())?;
        let is_empty = self
            .builder()
            .build_int_compare(
                inkwell::IntPredicate::EQ,
                tag,
                tm.i64_type().const_zero(),
                "is_empty",
            )
            .map_err(|e| CodegenError::Internal(format!("cmp: {:?}", e)))?;
        self.builder()
            .build_conditional_branch(is_empty, loop_exit, loop_body)
            .map_err(|e| CodegenError::Internal(format!("branch: {:?}", e)))?;

        // Loop body: extract pair from head, split into fst and snd
        self.builder().position_at_end(loop_body);
        let head_ptr =
            self.extract_adt_field(list_phi.as_basic_value().into_pointer_value(), 2, 0)?;
        let tail_ptr =
            self.extract_adt_field(list_phi.as_basic_value().into_pointer_value(), 2, 1)?;

        // head is a pair ADT (tag=0, arity=2)
        let fst_val = self.extract_adt_field(head_ptr, 2, 0)?;
        let snd_val = self.extract_adt_field(head_ptr, 2, 1)?;

        let fst_cons = self.build_cons(fst_val.into(), fsts_phi.as_basic_value())?;
        let snd_cons = self.build_cons(snd_val.into(), snds_phi.as_basic_value())?;

        self.builder()
            .build_unconditional_branch(loop_header)
            .map_err(|e| CodegenError::Internal(format!("branch: {:?}", e)))?;

        fsts_phi.add_incoming(&[(&nil, entry_block), (&fst_cons, loop_body)]);
        snds_phi.add_incoming(&[(&nil, entry_block), (&snd_cons, loop_body)]);
        list_phi.add_incoming(&[(&list_ptr, entry_block), (&tail_ptr, loop_body)]);

        // Exit: reverse both lists, then build pair
        self.builder().position_at_end(loop_exit);

        // Reverse fsts
        let rev1_header = self.llvm_ctx.append_basic_block(current_fn, "rev1_header");
        let rev1_body = self.llvm_ctx.append_basic_block(current_fn, "rev1_body");
        let rev1_exit = self.llvm_ctx.append_basic_block(current_fn, "rev1_exit");
        let nil2 = self.build_nil()?;
        self.builder()
            .build_unconditional_branch(rev1_header)
            .map_err(|e| CodegenError::Internal(format!("branch: {:?}", e)))?;

        self.builder().position_at_end(rev1_header);
        let rev1_acc = self
            .builder()
            .build_phi(ptr_type, "rev1_acc")
            .map_err(|e| CodegenError::Internal(format!("phi: {:?}", e)))?;
        let rev1_list = self
            .builder()
            .build_phi(ptr_type, "rev1_list")
            .map_err(|e| CodegenError::Internal(format!("phi: {:?}", e)))?;
        let rev1_tag = self.extract_adt_tag(rev1_list.as_basic_value().into_pointer_value())?;
        let rev1_empty = self
            .builder()
            .build_int_compare(
                inkwell::IntPredicate::EQ,
                rev1_tag,
                tm.i64_type().const_zero(),
                "rev1_empty",
            )
            .map_err(|e| CodegenError::Internal(format!("cmp: {:?}", e)))?;
        self.builder()
            .build_conditional_branch(rev1_empty, rev1_exit, rev1_body)
            .map_err(|e| CodegenError::Internal(format!("branch: {:?}", e)))?;

        self.builder().position_at_end(rev1_body);
        let r1_head =
            self.extract_adt_field(rev1_list.as_basic_value().into_pointer_value(), 2, 0)?;
        let r1_tail =
            self.extract_adt_field(rev1_list.as_basic_value().into_pointer_value(), 2, 1)?;
        let r1_cons = self.build_cons(r1_head.into(), rev1_acc.as_basic_value())?;
        self.builder()
            .build_unconditional_branch(rev1_header)
            .map_err(|e| CodegenError::Internal(format!("branch: {:?}", e)))?;

        rev1_acc.add_incoming(&[(&nil2, loop_exit), (&r1_cons, rev1_body)]);
        rev1_list.add_incoming(&[
            (&fsts_phi.as_basic_value(), loop_exit),
            (&r1_tail, rev1_body),
        ]);

        // Reverse snds
        self.builder().position_at_end(rev1_exit);
        let rev2_header = self.llvm_ctx.append_basic_block(current_fn, "rev2_header");
        let rev2_body = self.llvm_ctx.append_basic_block(current_fn, "rev2_body");
        let rev2_exit = self.llvm_ctx.append_basic_block(current_fn, "rev2_exit");
        let nil3 = self.build_nil()?;
        self.builder()
            .build_unconditional_branch(rev2_header)
            .map_err(|e| CodegenError::Internal(format!("branch: {:?}", e)))?;

        self.builder().position_at_end(rev2_header);
        let rev2_acc = self
            .builder()
            .build_phi(ptr_type, "rev2_acc")
            .map_err(|e| CodegenError::Internal(format!("phi: {:?}", e)))?;
        let rev2_list = self
            .builder()
            .build_phi(ptr_type, "rev2_list")
            .map_err(|e| CodegenError::Internal(format!("phi: {:?}", e)))?;
        let rev2_tag = self.extract_adt_tag(rev2_list.as_basic_value().into_pointer_value())?;
        let rev2_empty = self
            .builder()
            .build_int_compare(
                inkwell::IntPredicate::EQ,
                rev2_tag,
                tm.i64_type().const_zero(),
                "rev2_empty",
            )
            .map_err(|e| CodegenError::Internal(format!("cmp: {:?}", e)))?;
        self.builder()
            .build_conditional_branch(rev2_empty, rev2_exit, rev2_body)
            .map_err(|e| CodegenError::Internal(format!("branch: {:?}", e)))?;

        self.builder().position_at_end(rev2_body);
        let r2_head =
            self.extract_adt_field(rev2_list.as_basic_value().into_pointer_value(), 2, 0)?;
        let r2_tail =
            self.extract_adt_field(rev2_list.as_basic_value().into_pointer_value(), 2, 1)?;
        let r2_cons = self.build_cons(r2_head.into(), rev2_acc.as_basic_value())?;
        self.builder()
            .build_unconditional_branch(rev2_header)
            .map_err(|e| CodegenError::Internal(format!("branch: {:?}", e)))?;

        rev2_acc.add_incoming(&[(&nil3, rev1_exit), (&r2_cons, rev2_body)]);
        rev2_list.add_incoming(&[
            (&snds_phi.as_basic_value(), rev1_exit),
            (&r2_tail, rev2_body),
        ]);

        // Build result pair
        self.builder().position_at_end(rev2_exit);
        let pair = self.alloc_adt(0, 2)?;
        self.store_adt_field(pair, 2, 0, rev1_acc.as_basic_value())?;
        self.store_adt_field(pair, 2, 1, rev2_acc.as_basic_value())?;

        Ok(Some(pair.into()))
    }

    // ========================================================================
    // Phase 3: Container Operation Handlers (Data.Map, Data.Set, Data.IntMap, Data.IntSet)
    // ========================================================================

    // --- Data.Map handlers ---

    /// Lower `Data.Map.empty` - create empty map.
    fn lower_builtin_map_empty(&mut self) -> CodegenResult<Option<BasicValueEnum<'ctx>>> {
        let rts_fn = self
            .functions
            .get(&VarId::new(1000100))
            .ok_or_else(|| CodegenError::Internal("bhc_map_empty not declared".to_string()))?;
        let result = self
            .builder()
            .build_call(*rts_fn, &[], "map_empty")
            .map_err(|e| CodegenError::Internal(format!("map_empty call failed: {:?}", e)))?
            .try_as_basic_value()
            .basic()
            .ok_or_else(|| CodegenError::Internal("map_empty: returned void".to_string()))?;
        Ok(Some(result))
    }

    /// Lower `Data.Map.singleton` - create singleton map.
    fn lower_builtin_map_singleton(
        &mut self,
        key_expr: &Expr,
        val_expr: &Expr,
    ) -> CodegenResult<Option<BasicValueEnum<'ctx>>> {
        let key = self
            .lower_expr(key_expr)?
            .ok_or_else(|| CodegenError::Internal("map_singleton: no key".to_string()))?;
        let val = self
            .lower_expr(val_expr)?
            .ok_or_else(|| CodegenError::Internal("map_singleton: no value".to_string()))?;
        let key_int = self.coerce_to_int(key)?;
        let val_ptr = self.value_to_ptr(val)?;
        let rts_fn = self
            .functions
            .get(&VarId::new(1000101))
            .ok_or_else(|| CodegenError::Internal("bhc_map_singleton not declared".to_string()))?;
        let result = self
            .builder()
            .build_call(*rts_fn, &[key_int.into(), val_ptr.into()], "map_singleton")
            .map_err(|e| CodegenError::Internal(format!("map_singleton call failed: {:?}", e)))?
            .try_as_basic_value()
            .basic()
            .ok_or_else(|| CodegenError::Internal("map_singleton: returned void".to_string()))?;
        Ok(Some(result))
    }

    /// Lower `Data.Map.null` - check if map is empty.
    fn lower_builtin_map_null(
        &mut self,
        map_expr: &Expr,
    ) -> CodegenResult<Option<BasicValueEnum<'ctx>>> {
        let map_val = self
            .lower_expr(map_expr)?
            .ok_or_else(|| CodegenError::Internal("map_null: no map".to_string()))?;
        let map_ptr = self.value_to_ptr(map_val)?;
        let rts_fn = self
            .functions
            .get(&VarId::new(1000102))
            .ok_or_else(|| CodegenError::Internal("bhc_map_null not declared".to_string()))?;
        let result = self
            .builder()
            .build_call(*rts_fn, &[map_ptr.into()], "map_null")
            .map_err(|e| CodegenError::Internal(format!("map_null call failed: {:?}", e)))?
            .try_as_basic_value()
            .basic()
            .ok_or_else(|| CodegenError::Internal("map_null: returned void".to_string()))?;
        self.allocate_bool_adt(result.into_int_value(), "null_bool")
    }

    /// Lower `Data.Map.size` - get map size.
    fn lower_builtin_map_size(
        &mut self,
        map_expr: &Expr,
    ) -> CodegenResult<Option<BasicValueEnum<'ctx>>> {
        let map_val = self
            .lower_expr(map_expr)?
            .ok_or_else(|| CodegenError::Internal("map_size: no map".to_string()))?;
        let map_ptr = self.value_to_ptr(map_val)?;
        let rts_fn = self
            .functions
            .get(&VarId::new(1000103))
            .ok_or_else(|| CodegenError::Internal("bhc_map_size not declared".to_string()))?;
        let result = self
            .builder()
            .build_call(*rts_fn, &[map_ptr.into()], "map_size")
            .map_err(|e| CodegenError::Internal(format!("map_size call failed: {:?}", e)))?
            .try_as_basic_value()
            .basic()
            .ok_or_else(|| CodegenError::Internal("map_size: returned void".to_string()))?;
        Ok(Some(self.int_to_ptr(result.into_int_value())?.into()))
    }

    /// Lower `Data.Map.member` - check membership.
    fn lower_builtin_map_member(
        &mut self,
        key_expr: &Expr,
        map_expr: &Expr,
    ) -> CodegenResult<Option<BasicValueEnum<'ctx>>> {
        let key = self
            .lower_expr(key_expr)?
            .ok_or_else(|| CodegenError::Internal("map_member: no key".to_string()))?;
        let map_val = self
            .lower_expr(map_expr)?
            .ok_or_else(|| CodegenError::Internal("map_member: no map".to_string()))?;
        let key_int = self.coerce_to_int(key)?;
        let map_ptr = self.value_to_ptr(map_val)?;
        let rts_fn = self
            .functions
            .get(&VarId::new(1000104))
            .ok_or_else(|| CodegenError::Internal("bhc_map_member not declared".to_string()))?;
        let result = self
            .builder()
            .build_call(*rts_fn, &[key_int.into(), map_ptr.into()], "map_member")
            .map_err(|e| CodegenError::Internal(format!("map_member call failed: {:?}", e)))?
            .try_as_basic_value()
            .basic()
            .ok_or_else(|| CodegenError::Internal("map_member: returned void".to_string()))?;
        self.allocate_bool_adt(result.into_int_value(), "member_bool")
    }

    /// Lower `Data.Map.lookup` - lookup key, returns Maybe.
    fn lower_builtin_map_lookup(
        &mut self,
        key_expr: &Expr,
        map_expr: &Expr,
    ) -> CodegenResult<Option<BasicValueEnum<'ctx>>> {
        let key = self
            .lower_expr(key_expr)?
            .ok_or_else(|| CodegenError::Internal("map_lookup: no key".to_string()))?;
        let map_val = self
            .lower_expr(map_expr)?
            .ok_or_else(|| CodegenError::Internal("map_lookup: no map".to_string()))?;
        let key_int = self.coerce_to_int(key)?;
        let map_ptr = self.value_to_ptr(map_val)?;
        let rts_fn = self
            .functions
            .get(&VarId::new(1000105))
            .ok_or_else(|| CodegenError::Internal("bhc_map_lookup not declared".to_string()))?;
        let result = self
            .builder()
            .build_call(*rts_fn, &[key_int.into(), map_ptr.into()], "map_lookup")
            .map_err(|e| CodegenError::Internal(format!("map_lookup call failed: {:?}", e)))?
            .try_as_basic_value()
            .basic()
            .ok_or_else(|| CodegenError::Internal("map_lookup: returned void".to_string()))?;
        // Result is a pointer: null = Nothing, non-null = the value
        // Wrap in Maybe: check for null, construct Just/Nothing ADT
        let result_ptr = result.into_pointer_value();
        let is_null = self
            .builder()
            .build_is_null(result_ptr, "is_null")
            .map_err(|e| CodegenError::Internal(format!("map_lookup is_null: {:?}", e)))?;
        let nothing = self.alloc_adt(0, 0)?; // Nothing = tag 0, 0 fields
        let just = self.alloc_adt(1, 1)?; // Just = tag 1, 1 field
        self.store_adt_field(just, 1, 0, result_ptr.into())?;
        let maybe = self
            .builder()
            .build_select(is_null, nothing, just, "maybe")
            .map_err(|e| CodegenError::Internal(format!("map_lookup select: {:?}", e)))?;
        Ok(Some(maybe))
    }

    /// Lower `Data.Map.findWithDefault` - lookup with default.
    fn lower_builtin_map_find_with_default(
        &mut self,
        default_expr: &Expr,
        key_expr: &Expr,
        map_expr: &Expr,
    ) -> CodegenResult<Option<BasicValueEnum<'ctx>>> {
        let default = self
            .lower_expr(default_expr)?
            .ok_or_else(|| CodegenError::Internal("map_fwd: no default".to_string()))?;
        let key = self
            .lower_expr(key_expr)?
            .ok_or_else(|| CodegenError::Internal("map_fwd: no key".to_string()))?;
        let map_val = self
            .lower_expr(map_expr)?
            .ok_or_else(|| CodegenError::Internal("map_fwd: no map".to_string()))?;
        let default_ptr = self.value_to_ptr(default)?;
        let key_int = self.coerce_to_int(key)?;
        let map_ptr = self.value_to_ptr(map_val)?;
        let rts_fn = self.functions.get(&VarId::new(1000106)).ok_or_else(|| {
            CodegenError::Internal("bhc_map_find_with_default not declared".to_string())
        })?;
        let result = self
            .builder()
            .build_call(
                *rts_fn,
                &[default_ptr.into(), key_int.into(), map_ptr.into()],
                "map_fwd",
            )
            .map_err(|e| CodegenError::Internal(format!("map_fwd call failed: {:?}", e)))?
            .try_as_basic_value()
            .basic()
            .ok_or_else(|| CodegenError::Internal("map_fwd: returned void".to_string()))?;
        Ok(Some(result))
    }

    /// Lower `Data.Map.insert` - insert key-value pair.
    fn lower_builtin_map_insert(
        &mut self,
        key_expr: &Expr,
        val_expr: &Expr,
        map_expr: &Expr,
    ) -> CodegenResult<Option<BasicValueEnum<'ctx>>> {
        let key = self
            .lower_expr(key_expr)?
            .ok_or_else(|| CodegenError::Internal("map_insert: no key".to_string()))?;
        let val = self
            .lower_expr(val_expr)?
            .ok_or_else(|| CodegenError::Internal("map_insert: no value".to_string()))?;
        let map_val = self
            .lower_expr(map_expr)?
            .ok_or_else(|| CodegenError::Internal("map_insert: no map".to_string()))?;
        let key_int = self.coerce_to_int(key)?;
        let val_ptr = self.value_to_ptr(val)?;
        let map_ptr = self.value_to_ptr(map_val)?;
        let rts_fn = self
            .functions
            .get(&VarId::new(1000107))
            .ok_or_else(|| CodegenError::Internal("bhc_map_insert not declared".to_string()))?;
        let result = self
            .builder()
            .build_call(
                *rts_fn,
                &[key_int.into(), val_ptr.into(), map_ptr.into()],
                "map_insert",
            )
            .map_err(|e| CodegenError::Internal(format!("map_insert call failed: {:?}", e)))?
            .try_as_basic_value()
            .basic()
            .ok_or_else(|| CodegenError::Internal("map_insert: returned void".to_string()))?;
        Ok(Some(result))
    }

    /// Lower `Data.Map.delete` - delete key from map.
    fn lower_builtin_map_delete(
        &mut self,
        key_expr: &Expr,
        map_expr: &Expr,
    ) -> CodegenResult<Option<BasicValueEnum<'ctx>>> {
        let key = self
            .lower_expr(key_expr)?
            .ok_or_else(|| CodegenError::Internal("map_delete: no key".to_string()))?;
        let map_val = self
            .lower_expr(map_expr)?
            .ok_or_else(|| CodegenError::Internal("map_delete: no map".to_string()))?;
        let key_int = self.coerce_to_int(key)?;
        let map_ptr = self.value_to_ptr(map_val)?;
        let rts_fn = self
            .functions
            .get(&VarId::new(1000108))
            .ok_or_else(|| CodegenError::Internal("bhc_map_delete not declared".to_string()))?;
        let result = self
            .builder()
            .build_call(*rts_fn, &[key_int.into(), map_ptr.into()], "map_delete")
            .map_err(|e| CodegenError::Internal(format!("map_delete call failed: {:?}", e)))?
            .try_as_basic_value()
            .basic()
            .ok_or_else(|| CodegenError::Internal("map_delete: returned void".to_string()))?;
        Ok(Some(result))
    }

    /// Lower `Data.Map.union` - union of two maps.
    fn lower_builtin_map_union(
        &mut self,
        map1_expr: &Expr,
        map2_expr: &Expr,
    ) -> CodegenResult<Option<BasicValueEnum<'ctx>>> {
        let m1 = self
            .lower_expr(map1_expr)?
            .ok_or_else(|| CodegenError::Internal("map_union: no map1".to_string()))?;
        let m2 = self
            .lower_expr(map2_expr)?
            .ok_or_else(|| CodegenError::Internal("map_union: no map2".to_string()))?;
        let m1_ptr = self.value_to_ptr(m1)?;
        let m2_ptr = self.value_to_ptr(m2)?;
        let rts_fn = self
            .functions
            .get(&VarId::new(1000109))
            .ok_or_else(|| CodegenError::Internal("bhc_map_union not declared".to_string()))?;
        let result = self
            .builder()
            .build_call(*rts_fn, &[m1_ptr.into(), m2_ptr.into()], "map_union")
            .map_err(|e| CodegenError::Internal(format!("map_union call failed: {:?}", e)))?
            .try_as_basic_value()
            .basic()
            .ok_or_else(|| CodegenError::Internal("map_union: returned void".to_string()))?;
        Ok(Some(result))
    }

    /// Lower `Data.Map.intersection` - intersection of two maps.
    fn lower_builtin_map_intersection(
        &mut self,
        map1_expr: &Expr,
        map2_expr: &Expr,
    ) -> CodegenResult<Option<BasicValueEnum<'ctx>>> {
        let m1 = self
            .lower_expr(map1_expr)?
            .ok_or_else(|| CodegenError::Internal("map_intersection: no map1".to_string()))?;
        let m2 = self
            .lower_expr(map2_expr)?
            .ok_or_else(|| CodegenError::Internal("map_intersection: no map2".to_string()))?;
        let m1_ptr = self.value_to_ptr(m1)?;
        let m2_ptr = self.value_to_ptr(m2)?;
        let rts_fn = self.functions.get(&VarId::new(1000110)).ok_or_else(|| {
            CodegenError::Internal("bhc_map_intersection not declared".to_string())
        })?;
        let result = self
            .builder()
            .build_call(*rts_fn, &[m1_ptr.into(), m2_ptr.into()], "map_intersection")
            .map_err(|e| CodegenError::Internal(format!("map_intersection call failed: {:?}", e)))?
            .try_as_basic_value()
            .basic()
            .ok_or_else(|| CodegenError::Internal("map_intersection: returned void".to_string()))?;
        Ok(Some(result))
    }

    /// Lower `Data.Map.difference` - difference of two maps.
    fn lower_builtin_map_difference(
        &mut self,
        map1_expr: &Expr,
        map2_expr: &Expr,
    ) -> CodegenResult<Option<BasicValueEnum<'ctx>>> {
        let m1 = self
            .lower_expr(map1_expr)?
            .ok_or_else(|| CodegenError::Internal("map_difference: no map1".to_string()))?;
        let m2 = self
            .lower_expr(map2_expr)?
            .ok_or_else(|| CodegenError::Internal("map_difference: no map2".to_string()))?;
        let m1_ptr = self.value_to_ptr(m1)?;
        let m2_ptr = self.value_to_ptr(m2)?;
        let rts_fn = self
            .functions
            .get(&VarId::new(1000111))
            .ok_or_else(|| CodegenError::Internal("bhc_map_difference not declared".to_string()))?;
        let result = self
            .builder()
            .build_call(*rts_fn, &[m1_ptr.into(), m2_ptr.into()], "map_difference")
            .map_err(|e| CodegenError::Internal(format!("map_difference call failed: {:?}", e)))?
            .try_as_basic_value()
            .basic()
            .ok_or_else(|| CodegenError::Internal("map_difference: returned void".to_string()))?;
        Ok(Some(result))
    }

    /// Lower `Data.Map.isSubmapOf` - check submap.
    fn lower_builtin_map_is_submap_of(
        &mut self,
        map1_expr: &Expr,
        map2_expr: &Expr,
    ) -> CodegenResult<Option<BasicValueEnum<'ctx>>> {
        let m1 = self
            .lower_expr(map1_expr)?
            .ok_or_else(|| CodegenError::Internal("map_isSubmapOf: no map1".to_string()))?;
        let m2 = self
            .lower_expr(map2_expr)?
            .ok_or_else(|| CodegenError::Internal("map_isSubmapOf: no map2".to_string()))?;
        let m1_ptr = self.value_to_ptr(m1)?;
        let m2_ptr = self.value_to_ptr(m2)?;
        let rts_fn = self.functions.get(&VarId::new(1000112)).ok_or_else(|| {
            CodegenError::Internal("bhc_map_is_submap_of not declared".to_string())
        })?;
        let result = self
            .builder()
            .build_call(*rts_fn, &[m1_ptr.into(), m2_ptr.into()], "map_is_submap_of")
            .map_err(|e| CodegenError::Internal(format!("map_is_submap_of call failed: {:?}", e)))?
            .try_as_basic_value()
            .basic()
            .ok_or_else(|| CodegenError::Internal("map_isSubmapOf: returned void".to_string()))?;
        self.allocate_bool_adt(result.into_int_value(), "is_submap_bool")
    }

    // ========================================================================
    // Container Higher-Order Operations
    // ========================================================================
    //
    // These iterate over container elements using RTS indexed access,
    // calling closures via indirect LLVM calls.

    /// Lower `Data.Map.map` — applies closure to each value, building new map.
    fn lower_builtin_map_map(
        &mut self,
        fn_expr: &Expr,
        map_expr: &Expr,
    ) -> CodegenResult<Option<BasicValueEnum<'ctx>>> {
        let fn_val = self
            .lower_expr(fn_expr)?
            .ok_or_else(|| CodegenError::Internal("map_map: no function".to_string()))?;
        let fn_ptr = self.value_to_ptr(fn_val)?;
        let map_val = self
            .lower_expr(map_expr)?
            .ok_or_else(|| CodegenError::Internal("map_map: no map".to_string()))?;
        let map_ptr = self.value_to_ptr(map_val)?;

        let tm = self.type_mapper();
        let ptr_type = tm.ptr_type();
        let current_fn = self
            .builder()
            .get_insert_block()
            .and_then(|b| b.get_parent())
            .ok_or_else(|| CodegenError::Internal("no current function".to_string()))?;

        // Get element count
        let count_fn = self
            .functions
            .get(&VarId::new(1000160))
            .ok_or_else(|| CodegenError::Internal("bhc_map_keys_count not declared".to_string()))?;
        let count = self
            .builder()
            .build_call(*count_fn, &[map_ptr.into()], "count")
            .map_err(|e| CodegenError::Internal(format!("call failed: {:?}", e)))?
            .try_as_basic_value()
            .basic()
            .ok_or_else(|| CodegenError::Internal("void".to_string()))?
            .into_int_value();

        let key_at_fn = *self
            .functions
            .get(&VarId::new(1000161))
            .ok_or_else(|| CodegenError::Internal("bhc_map_key_at not declared".to_string()))?;
        let val_at_fn = *self
            .functions
            .get(&VarId::new(1000162))
            .ok_or_else(|| CodegenError::Internal("bhc_map_value_at not declared".to_string()))?;
        let insert_fn = *self
            .functions
            .get(&VarId::new(1000107))
            .ok_or_else(|| CodegenError::Internal("bhc_map_insert not declared".to_string()))?;

        // Start with empty map
        let empty_fn = self
            .functions
            .get(&VarId::new(1000100))
            .ok_or_else(|| CodegenError::Internal("bhc_map_empty not declared".to_string()))?;
        let empty_map = self
            .builder()
            .build_call(*empty_fn, &[], "empty")
            .map_err(|e| CodegenError::Internal(format!("call failed: {:?}", e)))?
            .try_as_basic_value()
            .basic()
            .ok_or_else(|| CodegenError::Internal("void".to_string()))?;

        // Extract function pointer from closure
        let closure_fn_ptr = self.extract_closure_fn_ptr(fn_ptr)?;
        let call_fn_type = ptr_type.fn_type(&[ptr_type.into(), ptr_type.into()], false);

        let loop_header = self
            .llvm_context()
            .append_basic_block(current_fn, "mm_header");
        let loop_body = self
            .llvm_context()
            .append_basic_block(current_fn, "mm_body");
        let loop_exit = self
            .llvm_context()
            .append_basic_block(current_fn, "mm_exit");

        let entry_block = self.builder().get_insert_block().unwrap();
        self.builder()
            .build_unconditional_branch(loop_header)
            .map_err(|e| CodegenError::Internal(format!("branch failed: {:?}", e)))?;

        self.builder().position_at_end(loop_header);
        let acc_phi = self
            .builder()
            .build_phi(ptr_type, "acc")
            .map_err(|e| CodegenError::Internal(format!("phi failed: {:?}", e)))?;
        let idx_phi = self
            .builder()
            .build_phi(tm.i64_type(), "idx")
            .map_err(|e| CodegenError::Internal(format!("phi failed: {:?}", e)))?;

        let idx = idx_phi.as_basic_value().into_int_value();
        let done = self
            .builder()
            .build_int_compare(inkwell::IntPredicate::SGE, idx, count, "done")
            .map_err(|e| CodegenError::Internal(format!("cmp failed: {:?}", e)))?;
        self.builder()
            .build_conditional_branch(done, loop_exit, loop_body)
            .map_err(|e| CodegenError::Internal(format!("branch failed: {:?}", e)))?;

        // Loop body: get key and value, apply fn to value, insert (key, new_val)
        self.builder().position_at_end(loop_body);
        let key_i64 = self
            .builder()
            .build_call(key_at_fn, &[map_ptr.into(), idx.into()], "key")
            .map_err(|e| CodegenError::Internal(format!("call failed: {:?}", e)))?
            .try_as_basic_value()
            .basic()
            .ok_or_else(|| CodegenError::Internal("void".to_string()))?
            .into_int_value();
        let val = self
            .builder()
            .build_call(val_at_fn, &[map_ptr.into(), idx.into()], "val")
            .map_err(|e| CodegenError::Internal(format!("call failed: {:?}", e)))?
            .try_as_basic_value()
            .basic()
            .ok_or_else(|| CodegenError::Internal("void".to_string()))?;

        // Call closure: fn_ptr(closure, val)
        let mapped_val = self
            .builder()
            .build_indirect_call(
                call_fn_type,
                closure_fn_ptr,
                &[fn_ptr.into(), val.into()],
                "mapped",
            )
            .map_err(|e| CodegenError::Internal(format!("indirect call failed: {:?}", e)))?
            .try_as_basic_value()
            .basic()
            .ok_or_else(|| CodegenError::Internal("closure returned void".to_string()))?;

        let mapped_ptr = self.value_to_ptr(mapped_val)?;
        let acc_ptr = acc_phi.as_basic_value().into_pointer_value();
        let new_map = self
            .builder()
            .build_call(
                insert_fn,
                &[key_i64.into(), mapped_ptr.into(), acc_ptr.into()],
                "new_map",
            )
            .map_err(|e| CodegenError::Internal(format!("call failed: {:?}", e)))?
            .try_as_basic_value()
            .basic()
            .ok_or_else(|| CodegenError::Internal("void".to_string()))?;

        let new_idx = self
            .builder()
            .build_int_add(idx, tm.i64_type().const_int(1, false), "new_idx")
            .map_err(|e| CodegenError::Internal(format!("add failed: {:?}", e)))?;
        self.builder()
            .build_unconditional_branch(loop_header)
            .map_err(|e| CodegenError::Internal(format!("branch failed: {:?}", e)))?;

        acc_phi.add_incoming(&[(&empty_map, entry_block), (&new_map, loop_body)]);
        idx_phi.add_incoming(&[
            (&tm.i64_type().const_zero(), entry_block),
            (&new_idx, loop_body),
        ]);

        self.builder().position_at_end(loop_exit);
        Ok(Some(acc_phi.as_basic_value()))
    }

    /// Lower `Data.Map.filter` — applies predicate to each value, keeping matches.
    fn lower_builtin_map_filter(
        &mut self,
        fn_expr: &Expr,
        map_expr: &Expr,
    ) -> CodegenResult<Option<BasicValueEnum<'ctx>>> {
        let fn_val = self
            .lower_expr(fn_expr)?
            .ok_or_else(|| CodegenError::Internal("map_filter: no function".to_string()))?;
        let fn_ptr = self.value_to_ptr(fn_val)?;
        let map_val = self
            .lower_expr(map_expr)?
            .ok_or_else(|| CodegenError::Internal("map_filter: no map".to_string()))?;
        let map_ptr = self.value_to_ptr(map_val)?;

        let tm = self.type_mapper();
        let ptr_type = tm.ptr_type();
        let current_fn = self
            .builder()
            .get_insert_block()
            .and_then(|b| b.get_parent())
            .ok_or_else(|| CodegenError::Internal("no current function".to_string()))?;

        let count_fn = self
            .functions
            .get(&VarId::new(1000160))
            .ok_or_else(|| CodegenError::Internal("bhc_map_keys_count not declared".to_string()))?;
        let count = self
            .builder()
            .build_call(*count_fn, &[map_ptr.into()], "count")
            .map_err(|e| CodegenError::Internal(format!("call failed: {:?}", e)))?
            .try_as_basic_value()
            .basic()
            .ok_or_else(|| CodegenError::Internal("void".to_string()))?
            .into_int_value();

        let key_at_fn = *self
            .functions
            .get(&VarId::new(1000161))
            .ok_or_else(|| CodegenError::Internal("bhc_map_key_at not declared".to_string()))?;
        let val_at_fn = *self
            .functions
            .get(&VarId::new(1000162))
            .ok_or_else(|| CodegenError::Internal("bhc_map_value_at not declared".to_string()))?;
        let insert_fn = *self
            .functions
            .get(&VarId::new(1000107))
            .ok_or_else(|| CodegenError::Internal("bhc_map_insert not declared".to_string()))?;

        let empty_fn = self
            .functions
            .get(&VarId::new(1000100))
            .ok_or_else(|| CodegenError::Internal("bhc_map_empty not declared".to_string()))?;
        let empty_map = self
            .builder()
            .build_call(*empty_fn, &[], "empty")
            .map_err(|e| CodegenError::Internal(format!("call failed: {:?}", e)))?
            .try_as_basic_value()
            .basic()
            .ok_or_else(|| CodegenError::Internal("void".to_string()))?;

        let closure_fn_ptr = self.extract_closure_fn_ptr(fn_ptr)?;
        let call_fn_type = ptr_type.fn_type(&[ptr_type.into(), ptr_type.into()], false);

        let loop_header = self
            .llvm_context()
            .append_basic_block(current_fn, "mf_header");
        let loop_body = self
            .llvm_context()
            .append_basic_block(current_fn, "mf_body");
        let loop_insert = self
            .llvm_context()
            .append_basic_block(current_fn, "mf_insert");
        let loop_skip = self
            .llvm_context()
            .append_basic_block(current_fn, "mf_skip");
        let loop_exit = self
            .llvm_context()
            .append_basic_block(current_fn, "mf_exit");

        let entry_block = self.builder().get_insert_block().unwrap();
        self.builder()
            .build_unconditional_branch(loop_header)
            .map_err(|e| CodegenError::Internal(format!("branch failed: {:?}", e)))?;

        self.builder().position_at_end(loop_header);
        let acc_phi = self
            .builder()
            .build_phi(ptr_type, "acc")
            .map_err(|e| CodegenError::Internal(format!("phi failed: {:?}", e)))?;
        let idx_phi = self
            .builder()
            .build_phi(tm.i64_type(), "idx")
            .map_err(|e| CodegenError::Internal(format!("phi failed: {:?}", e)))?;

        let idx = idx_phi.as_basic_value().into_int_value();
        let done = self
            .builder()
            .build_int_compare(inkwell::IntPredicate::SGE, idx, count, "done")
            .map_err(|e| CodegenError::Internal(format!("cmp failed: {:?}", e)))?;
        self.builder()
            .build_conditional_branch(done, loop_exit, loop_body)
            .map_err(|e| CodegenError::Internal(format!("branch failed: {:?}", e)))?;

        self.builder().position_at_end(loop_body);
        let key_i64 = self
            .builder()
            .build_call(key_at_fn, &[map_ptr.into(), idx.into()], "key")
            .map_err(|e| CodegenError::Internal(format!("call failed: {:?}", e)))?
            .try_as_basic_value()
            .basic()
            .ok_or_else(|| CodegenError::Internal("void".to_string()))?
            .into_int_value();
        let val = self
            .builder()
            .build_call(val_at_fn, &[map_ptr.into(), idx.into()], "val")
            .map_err(|e| CodegenError::Internal(format!("call failed: {:?}", e)))?
            .try_as_basic_value()
            .basic()
            .ok_or_else(|| CodegenError::Internal("void".to_string()))?;

        // Call predicate: fn_ptr(closure, val) -> Bool (tag 0=False, 1=True)
        let pred_result = self
            .builder()
            .build_indirect_call(
                call_fn_type,
                closure_fn_ptr,
                &[fn_ptr.into(), val.into()],
                "pred",
            )
            .map_err(|e| CodegenError::Internal(format!("indirect call failed: {:?}", e)))?
            .try_as_basic_value()
            .basic()
            .ok_or_else(|| CodegenError::Internal("closure returned void".to_string()))?;

        // Check if predicate returned True (non-zero tag)
        let pred_ptr = self.value_to_ptr(pred_result)?;
        let pred_tag = self.extract_adt_tag(pred_ptr)?;
        let is_true = self
            .builder()
            .build_int_compare(
                inkwell::IntPredicate::NE,
                pred_tag,
                tm.i64_type().const_zero(),
                "is_true",
            )
            .map_err(|e| CodegenError::Internal(format!("cmp failed: {:?}", e)))?;
        self.builder()
            .build_conditional_branch(is_true, loop_insert, loop_skip)
            .map_err(|e| CodegenError::Internal(format!("branch failed: {:?}", e)))?;

        // Insert branch: add (key, val) to accumulator map
        self.builder().position_at_end(loop_insert);
        let val_ptr = self.value_to_ptr(val)?;
        let acc_ptr_ins = acc_phi.as_basic_value().into_pointer_value();
        let inserted = self
            .builder()
            .build_call(
                insert_fn,
                &[key_i64.into(), val_ptr.into(), acc_ptr_ins.into()],
                "inserted",
            )
            .map_err(|e| CodegenError::Internal(format!("call failed: {:?}", e)))?
            .try_as_basic_value()
            .basic()
            .ok_or_else(|| CodegenError::Internal("void".to_string()))?;
        self.builder()
            .build_unconditional_branch(loop_skip)
            .map_err(|e| CodegenError::Internal(format!("branch failed: {:?}", e)))?;

        // Skip/merge block
        self.builder().position_at_end(loop_skip);
        let merge_phi = self
            .builder()
            .build_phi(ptr_type, "merge_acc")
            .map_err(|e| CodegenError::Internal(format!("phi failed: {:?}", e)))?;
        merge_phi.add_incoming(&[
            (&inserted, loop_insert),
            (&acc_phi.as_basic_value(), loop_body),
        ]);

        let new_idx = self
            .builder()
            .build_int_add(idx, tm.i64_type().const_int(1, false), "new_idx")
            .map_err(|e| CodegenError::Internal(format!("add failed: {:?}", e)))?;
        self.builder()
            .build_unconditional_branch(loop_header)
            .map_err(|e| CodegenError::Internal(format!("branch failed: {:?}", e)))?;

        acc_phi.add_incoming(&[
            (&empty_map, entry_block),
            (&merge_phi.as_basic_value(), loop_skip),
        ]);
        idx_phi.add_incoming(&[
            (&tm.i64_type().const_zero(), entry_block),
            (&new_idx, loop_skip),
        ]);

        self.builder().position_at_end(loop_exit);
        Ok(Some(acc_phi.as_basic_value()))
    }

    /// Lower `Data.Map.foldr` — right fold over map values.
    fn lower_builtin_map_foldr(
        &mut self,
        fn_expr: &Expr,
        init_expr: &Expr,
        map_expr: &Expr,
    ) -> CodegenResult<Option<BasicValueEnum<'ctx>>> {
        let fn_val = self
            .lower_expr(fn_expr)?
            .ok_or_else(|| CodegenError::Internal("map_foldr: no function".to_string()))?;
        let fn_ptr = self.value_to_ptr(fn_val)?;
        let init_val = self
            .lower_expr(init_expr)?
            .ok_or_else(|| CodegenError::Internal("map_foldr: no init".to_string()))?;
        let map_val = self
            .lower_expr(map_expr)?
            .ok_or_else(|| CodegenError::Internal("map_foldr: no map".to_string()))?;
        let map_ptr = self.value_to_ptr(map_val)?;

        let tm = self.type_mapper();
        let ptr_type = tm.ptr_type();
        let current_fn = self
            .builder()
            .get_insert_block()
            .and_then(|b| b.get_parent())
            .ok_or_else(|| CodegenError::Internal("no current function".to_string()))?;

        let count_fn = self
            .functions
            .get(&VarId::new(1000160))
            .ok_or_else(|| CodegenError::Internal("bhc_map_keys_count not declared".to_string()))?;
        let count = self
            .builder()
            .build_call(*count_fn, &[map_ptr.into()], "count")
            .map_err(|e| CodegenError::Internal(format!("call failed: {:?}", e)))?
            .try_as_basic_value()
            .basic()
            .ok_or_else(|| CodegenError::Internal("void".to_string()))?
            .into_int_value();

        let val_at_fn = *self
            .functions
            .get(&VarId::new(1000162))
            .ok_or_else(|| CodegenError::Internal("bhc_map_value_at not declared".to_string()))?;

        let closure_fn_ptr = self.extract_closure_fn_ptr(fn_ptr)?;
        // foldr fn takes 2 args: value, accumulator
        let call_fn_type =
            ptr_type.fn_type(&[ptr_type.into(), ptr_type.into(), ptr_type.into()], false);

        // Iterate backwards for right fold
        let loop_header = self
            .llvm_context()
            .append_basic_block(current_fn, "mfr_header");
        let loop_body = self
            .llvm_context()
            .append_basic_block(current_fn, "mfr_body");
        let loop_exit = self
            .llvm_context()
            .append_basic_block(current_fn, "mfr_exit");

        let start_idx = self
            .builder()
            .build_int_sub(count, tm.i64_type().const_int(1, false), "start_idx")
            .map_err(|e| CodegenError::Internal(format!("sub failed: {:?}", e)))?;

        let init_ptr = self.value_to_ptr(init_val)?;

        let entry_block = self.builder().get_insert_block().unwrap();
        self.builder()
            .build_unconditional_branch(loop_header)
            .map_err(|e| CodegenError::Internal(format!("branch failed: {:?}", e)))?;

        self.builder().position_at_end(loop_header);
        let acc_phi = self
            .builder()
            .build_phi(ptr_type, "acc")
            .map_err(|e| CodegenError::Internal(format!("phi failed: {:?}", e)))?;
        let idx_phi = self
            .builder()
            .build_phi(tm.i64_type(), "idx")
            .map_err(|e| CodegenError::Internal(format!("phi failed: {:?}", e)))?;

        let idx = idx_phi.as_basic_value().into_int_value();
        let done = self
            .builder()
            .build_int_compare(
                inkwell::IntPredicate::SLT,
                idx,
                tm.i64_type().const_zero(),
                "done",
            )
            .map_err(|e| CodegenError::Internal(format!("cmp failed: {:?}", e)))?;
        self.builder()
            .build_conditional_branch(done, loop_exit, loop_body)
            .map_err(|e| CodegenError::Internal(format!("branch failed: {:?}", e)))?;

        self.builder().position_at_end(loop_body);
        let val = self
            .builder()
            .build_call(val_at_fn, &[map_ptr.into(), idx.into()], "val")
            .map_err(|e| CodegenError::Internal(format!("call failed: {:?}", e)))?
            .try_as_basic_value()
            .basic()
            .ok_or_else(|| CodegenError::Internal("void".to_string()))?;

        // Call closure: fn_ptr(closure, val, acc)
        let acc_val = acc_phi.as_basic_value();
        let new_acc = self
            .builder()
            .build_indirect_call(
                call_fn_type,
                closure_fn_ptr,
                &[fn_ptr.into(), val.into(), acc_val.into()],
                "new_acc",
            )
            .map_err(|e| CodegenError::Internal(format!("indirect call failed: {:?}", e)))?
            .try_as_basic_value()
            .basic()
            .ok_or_else(|| CodegenError::Internal("closure returned void".to_string()))?;

        let new_idx = self
            .builder()
            .build_int_sub(idx, tm.i64_type().const_int(1, false), "new_idx")
            .map_err(|e| CodegenError::Internal(format!("sub failed: {:?}", e)))?;
        self.builder()
            .build_unconditional_branch(loop_header)
            .map_err(|e| CodegenError::Internal(format!("branch failed: {:?}", e)))?;

        acc_phi.add_incoming(&[(&init_ptr, entry_block), (&new_acc, loop_body)]);
        idx_phi.add_incoming(&[(&start_idx, entry_block), (&new_idx, loop_body)]);

        self.builder().position_at_end(loop_exit);
        Ok(Some(acc_phi.as_basic_value()))
    }

    /// Lower `Data.Map.foldl'` — strict left fold over map values.
    fn lower_builtin_map_foldl(
        &mut self,
        fn_expr: &Expr,
        init_expr: &Expr,
        map_expr: &Expr,
    ) -> CodegenResult<Option<BasicValueEnum<'ctx>>> {
        let fn_val = self
            .lower_expr(fn_expr)?
            .ok_or_else(|| CodegenError::Internal("map_foldl: no function".to_string()))?;
        let fn_ptr = self.value_to_ptr(fn_val)?;
        let init_val = self
            .lower_expr(init_expr)?
            .ok_or_else(|| CodegenError::Internal("map_foldl: no init".to_string()))?;
        let map_val = self
            .lower_expr(map_expr)?
            .ok_or_else(|| CodegenError::Internal("map_foldl: no map".to_string()))?;
        let map_ptr = self.value_to_ptr(map_val)?;

        let tm = self.type_mapper();
        let ptr_type = tm.ptr_type();
        let current_fn = self
            .builder()
            .get_insert_block()
            .and_then(|b| b.get_parent())
            .ok_or_else(|| CodegenError::Internal("no current function".to_string()))?;

        let count_fn = self
            .functions
            .get(&VarId::new(1000160))
            .ok_or_else(|| CodegenError::Internal("bhc_map_keys_count not declared".to_string()))?;
        let count = self
            .builder()
            .build_call(*count_fn, &[map_ptr.into()], "count")
            .map_err(|e| CodegenError::Internal(format!("call failed: {:?}", e)))?
            .try_as_basic_value()
            .basic()
            .ok_or_else(|| CodegenError::Internal("void".to_string()))?
            .into_int_value();

        let val_at_fn = *self
            .functions
            .get(&VarId::new(1000162))
            .ok_or_else(|| CodegenError::Internal("bhc_map_value_at not declared".to_string()))?;

        let closure_fn_ptr = self.extract_closure_fn_ptr(fn_ptr)?;
        // foldl fn takes 2 args: accumulator, value
        let call_fn_type =
            ptr_type.fn_type(&[ptr_type.into(), ptr_type.into(), ptr_type.into()], false);

        let loop_header = self
            .llvm_context()
            .append_basic_block(current_fn, "mfl_header");
        let loop_body = self
            .llvm_context()
            .append_basic_block(current_fn, "mfl_body");
        let loop_exit = self
            .llvm_context()
            .append_basic_block(current_fn, "mfl_exit");

        let init_ptr = self.value_to_ptr(init_val)?;

        let entry_block = self.builder().get_insert_block().unwrap();
        self.builder()
            .build_unconditional_branch(loop_header)
            .map_err(|e| CodegenError::Internal(format!("branch failed: {:?}", e)))?;

        self.builder().position_at_end(loop_header);
        let acc_phi = self
            .builder()
            .build_phi(ptr_type, "acc")
            .map_err(|e| CodegenError::Internal(format!("phi failed: {:?}", e)))?;
        let idx_phi = self
            .builder()
            .build_phi(tm.i64_type(), "idx")
            .map_err(|e| CodegenError::Internal(format!("phi failed: {:?}", e)))?;

        let idx = idx_phi.as_basic_value().into_int_value();
        let done = self
            .builder()
            .build_int_compare(inkwell::IntPredicate::SGE, idx, count, "done")
            .map_err(|e| CodegenError::Internal(format!("cmp failed: {:?}", e)))?;
        self.builder()
            .build_conditional_branch(done, loop_exit, loop_body)
            .map_err(|e| CodegenError::Internal(format!("branch failed: {:?}", e)))?;

        self.builder().position_at_end(loop_body);
        let val = self
            .builder()
            .build_call(val_at_fn, &[map_ptr.into(), idx.into()], "val")
            .map_err(|e| CodegenError::Internal(format!("call failed: {:?}", e)))?
            .try_as_basic_value()
            .basic()
            .ok_or_else(|| CodegenError::Internal("void".to_string()))?;

        // Call closure: fn_ptr(closure, acc, val)
        let acc_val = acc_phi.as_basic_value();
        let new_acc = self
            .builder()
            .build_indirect_call(
                call_fn_type,
                closure_fn_ptr,
                &[fn_ptr.into(), acc_val.into(), val.into()],
                "new_acc",
            )
            .map_err(|e| CodegenError::Internal(format!("indirect call failed: {:?}", e)))?
            .try_as_basic_value()
            .basic()
            .ok_or_else(|| CodegenError::Internal("closure returned void".to_string()))?;

        let new_idx = self
            .builder()
            .build_int_add(idx, tm.i64_type().const_int(1, false), "new_idx")
            .map_err(|e| CodegenError::Internal(format!("add failed: {:?}", e)))?;
        self.builder()
            .build_unconditional_branch(loop_header)
            .map_err(|e| CodegenError::Internal(format!("branch failed: {:?}", e)))?;

        acc_phi.add_incoming(&[(&init_ptr, entry_block), (&new_acc, loop_body)]);
        idx_phi.add_incoming(&[
            (&tm.i64_type().const_zero(), entry_block),
            (&new_idx, loop_body),
        ]);

        self.builder().position_at_end(loop_exit);
        Ok(Some(acc_phi.as_basic_value()))
    }

    /// Lower `Data.Set.map` — applies closure to each element, building new set.
    fn lower_builtin_set_map(
        &mut self,
        fn_expr: &Expr,
        set_expr: &Expr,
    ) -> CodegenResult<Option<BasicValueEnum<'ctx>>> {
        let fn_val = self
            .lower_expr(fn_expr)?
            .ok_or_else(|| CodegenError::Internal("set_map: no function".to_string()))?;
        let fn_ptr = self.value_to_ptr(fn_val)?;
        let set_val = self
            .lower_expr(set_expr)?
            .ok_or_else(|| CodegenError::Internal("set_map: no set".to_string()))?;
        let set_ptr = self.value_to_ptr(set_val)?;

        let tm = self.type_mapper();
        let ptr_type = tm.ptr_type();
        let current_fn = self
            .builder()
            .get_insert_block()
            .and_then(|b| b.get_parent())
            .ok_or_else(|| CodegenError::Internal("no current function".to_string()))?;

        let count_fn = self
            .functions
            .get(&VarId::new(1000163))
            .ok_or_else(|| CodegenError::Internal("bhc_set_elem_count not declared".to_string()))?;
        let count = self
            .builder()
            .build_call(*count_fn, &[set_ptr.into()], "count")
            .map_err(|e| CodegenError::Internal(format!("call failed: {:?}", e)))?
            .try_as_basic_value()
            .basic()
            .ok_or_else(|| CodegenError::Internal("void".to_string()))?
            .into_int_value();

        let elem_at_fn = *self
            .functions
            .get(&VarId::new(1000164))
            .ok_or_else(|| CodegenError::Internal("bhc_set_elem_at not declared".to_string()))?;
        let insert_fn = *self
            .functions
            .get(&VarId::new(1000125))
            .ok_or_else(|| CodegenError::Internal("bhc_set_insert not declared".to_string()))?;

        let empty_fn = self
            .functions
            .get(&VarId::new(1000120))
            .ok_or_else(|| CodegenError::Internal("bhc_set_empty not declared".to_string()))?;
        let empty_set = self
            .builder()
            .build_call(*empty_fn, &[], "empty")
            .map_err(|e| CodegenError::Internal(format!("call failed: {:?}", e)))?
            .try_as_basic_value()
            .basic()
            .ok_or_else(|| CodegenError::Internal("void".to_string()))?;

        let closure_fn_ptr = self.extract_closure_fn_ptr(fn_ptr)?;
        let call_fn_type = ptr_type.fn_type(&[ptr_type.into(), ptr_type.into()], false);

        let loop_header = self
            .llvm_context()
            .append_basic_block(current_fn, "sm_header");
        let loop_body = self
            .llvm_context()
            .append_basic_block(current_fn, "sm_body");
        let loop_exit = self
            .llvm_context()
            .append_basic_block(current_fn, "sm_exit");

        let entry_block = self.builder().get_insert_block().unwrap();
        self.builder()
            .build_unconditional_branch(loop_header)
            .map_err(|e| CodegenError::Internal(format!("branch failed: {:?}", e)))?;

        self.builder().position_at_end(loop_header);
        let acc_phi = self
            .builder()
            .build_phi(ptr_type, "acc")
            .map_err(|e| CodegenError::Internal(format!("phi failed: {:?}", e)))?;
        let idx_phi = self
            .builder()
            .build_phi(tm.i64_type(), "idx")
            .map_err(|e| CodegenError::Internal(format!("phi failed: {:?}", e)))?;

        let idx = idx_phi.as_basic_value().into_int_value();
        let done = self
            .builder()
            .build_int_compare(inkwell::IntPredicate::SGE, idx, count, "done")
            .map_err(|e| CodegenError::Internal(format!("cmp failed: {:?}", e)))?;
        self.builder()
            .build_conditional_branch(done, loop_exit, loop_body)
            .map_err(|e| CodegenError::Internal(format!("branch failed: {:?}", e)))?;

        self.builder().position_at_end(loop_body);
        let elem_i64 = self
            .builder()
            .build_call(elem_at_fn, &[set_ptr.into(), idx.into()], "elem")
            .map_err(|e| CodegenError::Internal(format!("call failed: {:?}", e)))?
            .try_as_basic_value()
            .basic()
            .ok_or_else(|| CodegenError::Internal("void".to_string()))?
            .into_int_value();
        let elem_ptr = self.int_to_ptr(elem_i64)?;

        // Call closure: fn_ptr(closure, elem)
        let mapped_val = self
            .builder()
            .build_indirect_call(
                call_fn_type,
                closure_fn_ptr,
                &[fn_ptr.into(), elem_ptr.into()],
                "mapped",
            )
            .map_err(|e| CodegenError::Internal(format!("indirect call failed: {:?}", e)))?
            .try_as_basic_value()
            .basic()
            .ok_or_else(|| CodegenError::Internal("closure returned void".to_string()))?;

        let mapped_int = self.coerce_to_int(mapped_val)?;
        let acc_ptr = acc_phi.as_basic_value().into_pointer_value();
        let new_set = self
            .builder()
            .build_call(insert_fn, &[mapped_int.into(), acc_ptr.into()], "new_set")
            .map_err(|e| CodegenError::Internal(format!("call failed: {:?}", e)))?
            .try_as_basic_value()
            .basic()
            .ok_or_else(|| CodegenError::Internal("void".to_string()))?;

        let new_idx = self
            .builder()
            .build_int_add(idx, tm.i64_type().const_int(1, false), "new_idx")
            .map_err(|e| CodegenError::Internal(format!("add failed: {:?}", e)))?;
        self.builder()
            .build_unconditional_branch(loop_header)
            .map_err(|e| CodegenError::Internal(format!("branch failed: {:?}", e)))?;

        acc_phi.add_incoming(&[(&empty_set, entry_block), (&new_set, loop_body)]);
        idx_phi.add_incoming(&[
            (&tm.i64_type().const_zero(), entry_block),
            (&new_idx, loop_body),
        ]);

        self.builder().position_at_end(loop_exit);
        Ok(Some(acc_phi.as_basic_value()))
    }

    /// Lower `Data.Set.filter` — applies predicate, keeping matching elements.
    fn lower_builtin_set_filter(
        &mut self,
        fn_expr: &Expr,
        set_expr: &Expr,
    ) -> CodegenResult<Option<BasicValueEnum<'ctx>>> {
        let fn_val = self
            .lower_expr(fn_expr)?
            .ok_or_else(|| CodegenError::Internal("set_filter: no function".to_string()))?;
        let fn_ptr = self.value_to_ptr(fn_val)?;
        let set_val = self
            .lower_expr(set_expr)?
            .ok_or_else(|| CodegenError::Internal("set_filter: no set".to_string()))?;
        let set_ptr = self.value_to_ptr(set_val)?;

        let tm = self.type_mapper();
        let ptr_type = tm.ptr_type();
        let current_fn = self
            .builder()
            .get_insert_block()
            .and_then(|b| b.get_parent())
            .ok_or_else(|| CodegenError::Internal("no current function".to_string()))?;

        let count_fn = self
            .functions
            .get(&VarId::new(1000163))
            .ok_or_else(|| CodegenError::Internal("bhc_set_elem_count not declared".to_string()))?;
        let count = self
            .builder()
            .build_call(*count_fn, &[set_ptr.into()], "count")
            .map_err(|e| CodegenError::Internal(format!("call failed: {:?}", e)))?
            .try_as_basic_value()
            .basic()
            .ok_or_else(|| CodegenError::Internal("void".to_string()))?
            .into_int_value();

        let elem_at_fn = *self
            .functions
            .get(&VarId::new(1000164))
            .ok_or_else(|| CodegenError::Internal("bhc_set_elem_at not declared".to_string()))?;
        let insert_fn = *self
            .functions
            .get(&VarId::new(1000125))
            .ok_or_else(|| CodegenError::Internal("bhc_set_insert not declared".to_string()))?;

        let empty_fn = self
            .functions
            .get(&VarId::new(1000120))
            .ok_or_else(|| CodegenError::Internal("bhc_set_empty not declared".to_string()))?;
        let empty_set = self
            .builder()
            .build_call(*empty_fn, &[], "empty")
            .map_err(|e| CodegenError::Internal(format!("call failed: {:?}", e)))?
            .try_as_basic_value()
            .basic()
            .ok_or_else(|| CodegenError::Internal("void".to_string()))?;

        let closure_fn_ptr = self.extract_closure_fn_ptr(fn_ptr)?;
        let call_fn_type = ptr_type.fn_type(&[ptr_type.into(), ptr_type.into()], false);

        let loop_header = self
            .llvm_context()
            .append_basic_block(current_fn, "sf_header");
        let loop_body = self
            .llvm_context()
            .append_basic_block(current_fn, "sf_body");
        let loop_insert = self
            .llvm_context()
            .append_basic_block(current_fn, "sf_insert");
        let loop_skip = self
            .llvm_context()
            .append_basic_block(current_fn, "sf_skip");
        let loop_exit = self
            .llvm_context()
            .append_basic_block(current_fn, "sf_exit");

        let entry_block = self.builder().get_insert_block().unwrap();
        self.builder()
            .build_unconditional_branch(loop_header)
            .map_err(|e| CodegenError::Internal(format!("branch failed: {:?}", e)))?;

        self.builder().position_at_end(loop_header);
        let acc_phi = self
            .builder()
            .build_phi(ptr_type, "acc")
            .map_err(|e| CodegenError::Internal(format!("phi failed: {:?}", e)))?;
        let idx_phi = self
            .builder()
            .build_phi(tm.i64_type(), "idx")
            .map_err(|e| CodegenError::Internal(format!("phi failed: {:?}", e)))?;

        let idx = idx_phi.as_basic_value().into_int_value();
        let done = self
            .builder()
            .build_int_compare(inkwell::IntPredicate::SGE, idx, count, "done")
            .map_err(|e| CodegenError::Internal(format!("cmp failed: {:?}", e)))?;
        self.builder()
            .build_conditional_branch(done, loop_exit, loop_body)
            .map_err(|e| CodegenError::Internal(format!("branch failed: {:?}", e)))?;

        self.builder().position_at_end(loop_body);
        let elem_i64 = self
            .builder()
            .build_call(elem_at_fn, &[set_ptr.into(), idx.into()], "elem")
            .map_err(|e| CodegenError::Internal(format!("call failed: {:?}", e)))?
            .try_as_basic_value()
            .basic()
            .ok_or_else(|| CodegenError::Internal("void".to_string()))?
            .into_int_value();
        let elem_ptr = self.int_to_ptr(elem_i64)?;

        // Call predicate: fn_ptr(closure, elem)
        let pred_result = self
            .builder()
            .build_indirect_call(
                call_fn_type,
                closure_fn_ptr,
                &[fn_ptr.into(), elem_ptr.into()],
                "pred",
            )
            .map_err(|e| CodegenError::Internal(format!("indirect call failed: {:?}", e)))?
            .try_as_basic_value()
            .basic()
            .ok_or_else(|| CodegenError::Internal("closure returned void".to_string()))?;

        let pred_ptr = self.value_to_ptr(pred_result)?;
        let pred_tag = self.extract_adt_tag(pred_ptr)?;
        let is_true = self
            .builder()
            .build_int_compare(
                inkwell::IntPredicate::NE,
                pred_tag,
                tm.i64_type().const_zero(),
                "is_true",
            )
            .map_err(|e| CodegenError::Internal(format!("cmp failed: {:?}", e)))?;
        self.builder()
            .build_conditional_branch(is_true, loop_insert, loop_skip)
            .map_err(|e| CodegenError::Internal(format!("branch failed: {:?}", e)))?;

        self.builder().position_at_end(loop_insert);
        let acc_ptr_ins = acc_phi.as_basic_value().into_pointer_value();
        let inserted = self
            .builder()
            .build_call(
                insert_fn,
                &[elem_i64.into(), acc_ptr_ins.into()],
                "inserted",
            )
            .map_err(|e| CodegenError::Internal(format!("call failed: {:?}", e)))?
            .try_as_basic_value()
            .basic()
            .ok_or_else(|| CodegenError::Internal("void".to_string()))?;
        self.builder()
            .build_unconditional_branch(loop_skip)
            .map_err(|e| CodegenError::Internal(format!("branch failed: {:?}", e)))?;

        self.builder().position_at_end(loop_skip);
        let merge_phi = self
            .builder()
            .build_phi(ptr_type, "merge_acc")
            .map_err(|e| CodegenError::Internal(format!("phi failed: {:?}", e)))?;
        merge_phi.add_incoming(&[
            (&inserted, loop_insert),
            (&acc_phi.as_basic_value(), loop_body),
        ]);

        let new_idx = self
            .builder()
            .build_int_add(idx, tm.i64_type().const_int(1, false), "new_idx")
            .map_err(|e| CodegenError::Internal(format!("add failed: {:?}", e)))?;
        self.builder()
            .build_unconditional_branch(loop_header)
            .map_err(|e| CodegenError::Internal(format!("branch failed: {:?}", e)))?;

        acc_phi.add_incoming(&[
            (&empty_set, entry_block),
            (&merge_phi.as_basic_value(), loop_skip),
        ]);
        idx_phi.add_incoming(&[
            (&tm.i64_type().const_zero(), entry_block),
            (&new_idx, loop_skip),
        ]);

        self.builder().position_at_end(loop_exit);
        Ok(Some(acc_phi.as_basic_value()))
    }

    /// Lower `Data.Set.foldr` — right fold over set elements.
    fn lower_builtin_set_foldr(
        &mut self,
        fn_expr: &Expr,
        init_expr: &Expr,
        set_expr: &Expr,
    ) -> CodegenResult<Option<BasicValueEnum<'ctx>>> {
        let fn_val = self
            .lower_expr(fn_expr)?
            .ok_or_else(|| CodegenError::Internal("set_foldr: no function".to_string()))?;
        let fn_ptr = self.value_to_ptr(fn_val)?;
        let init_val = self
            .lower_expr(init_expr)?
            .ok_or_else(|| CodegenError::Internal("set_foldr: no init".to_string()))?;
        let set_val = self
            .lower_expr(set_expr)?
            .ok_or_else(|| CodegenError::Internal("set_foldr: no set".to_string()))?;
        let set_ptr = self.value_to_ptr(set_val)?;

        let tm = self.type_mapper();
        let ptr_type = tm.ptr_type();
        let current_fn = self
            .builder()
            .get_insert_block()
            .and_then(|b| b.get_parent())
            .ok_or_else(|| CodegenError::Internal("no current function".to_string()))?;

        let count_fn = self
            .functions
            .get(&VarId::new(1000163))
            .ok_or_else(|| CodegenError::Internal("bhc_set_elem_count not declared".to_string()))?;
        let count = self
            .builder()
            .build_call(*count_fn, &[set_ptr.into()], "count")
            .map_err(|e| CodegenError::Internal(format!("call failed: {:?}", e)))?
            .try_as_basic_value()
            .basic()
            .ok_or_else(|| CodegenError::Internal("void".to_string()))?
            .into_int_value();

        let elem_at_fn = *self
            .functions
            .get(&VarId::new(1000164))
            .ok_or_else(|| CodegenError::Internal("bhc_set_elem_at not declared".to_string()))?;

        let closure_fn_ptr = self.extract_closure_fn_ptr(fn_ptr)?;
        let call_fn_type =
            ptr_type.fn_type(&[ptr_type.into(), ptr_type.into(), ptr_type.into()], false);

        let loop_header = self
            .llvm_context()
            .append_basic_block(current_fn, "sfr_header");
        let loop_body = self
            .llvm_context()
            .append_basic_block(current_fn, "sfr_body");
        let loop_exit = self
            .llvm_context()
            .append_basic_block(current_fn, "sfr_exit");

        let start_idx = self
            .builder()
            .build_int_sub(count, tm.i64_type().const_int(1, false), "start_idx")
            .map_err(|e| CodegenError::Internal(format!("sub failed: {:?}", e)))?;

        let init_ptr = self.value_to_ptr(init_val)?;

        let entry_block = self.builder().get_insert_block().unwrap();
        self.builder()
            .build_unconditional_branch(loop_header)
            .map_err(|e| CodegenError::Internal(format!("branch failed: {:?}", e)))?;

        self.builder().position_at_end(loop_header);
        let acc_phi = self
            .builder()
            .build_phi(ptr_type, "acc")
            .map_err(|e| CodegenError::Internal(format!("phi failed: {:?}", e)))?;
        let idx_phi = self
            .builder()
            .build_phi(tm.i64_type(), "idx")
            .map_err(|e| CodegenError::Internal(format!("phi failed: {:?}", e)))?;

        let idx = idx_phi.as_basic_value().into_int_value();
        let done = self
            .builder()
            .build_int_compare(
                inkwell::IntPredicate::SLT,
                idx,
                tm.i64_type().const_zero(),
                "done",
            )
            .map_err(|e| CodegenError::Internal(format!("cmp failed: {:?}", e)))?;
        self.builder()
            .build_conditional_branch(done, loop_exit, loop_body)
            .map_err(|e| CodegenError::Internal(format!("branch failed: {:?}", e)))?;

        self.builder().position_at_end(loop_body);
        let elem_i64 = self
            .builder()
            .build_call(elem_at_fn, &[set_ptr.into(), idx.into()], "elem")
            .map_err(|e| CodegenError::Internal(format!("call failed: {:?}", e)))?
            .try_as_basic_value()
            .basic()
            .ok_or_else(|| CodegenError::Internal("void".to_string()))?
            .into_int_value();
        let elem_ptr = self.int_to_ptr(elem_i64)?;

        let acc_val = acc_phi.as_basic_value();
        let new_acc = self
            .builder()
            .build_indirect_call(
                call_fn_type,
                closure_fn_ptr,
                &[fn_ptr.into(), elem_ptr.into(), acc_val.into()],
                "new_acc",
            )
            .map_err(|e| CodegenError::Internal(format!("indirect call failed: {:?}", e)))?
            .try_as_basic_value()
            .basic()
            .ok_or_else(|| CodegenError::Internal("closure returned void".to_string()))?;

        let new_idx = self
            .builder()
            .build_int_sub(idx, tm.i64_type().const_int(1, false), "new_idx")
            .map_err(|e| CodegenError::Internal(format!("sub failed: {:?}", e)))?;
        self.builder()
            .build_unconditional_branch(loop_header)
            .map_err(|e| CodegenError::Internal(format!("branch failed: {:?}", e)))?;

        acc_phi.add_incoming(&[(&init_ptr, entry_block), (&new_acc, loop_body)]);
        idx_phi.add_incoming(&[(&start_idx, entry_block), (&new_idx, loop_body)]);

        self.builder().position_at_end(loop_exit);
        Ok(Some(acc_phi.as_basic_value()))
    }

    /// Lower `Data.Set.foldl'` — strict left fold over set elements.
    fn lower_builtin_set_foldl(
        &mut self,
        fn_expr: &Expr,
        init_expr: &Expr,
        set_expr: &Expr,
    ) -> CodegenResult<Option<BasicValueEnum<'ctx>>> {
        let fn_val = self
            .lower_expr(fn_expr)?
            .ok_or_else(|| CodegenError::Internal("set_foldl: no function".to_string()))?;
        let fn_ptr = self.value_to_ptr(fn_val)?;
        let init_val = self
            .lower_expr(init_expr)?
            .ok_or_else(|| CodegenError::Internal("set_foldl: no init".to_string()))?;
        let set_val = self
            .lower_expr(set_expr)?
            .ok_or_else(|| CodegenError::Internal("set_foldl: no set".to_string()))?;
        let set_ptr = self.value_to_ptr(set_val)?;

        let tm = self.type_mapper();
        let ptr_type = tm.ptr_type();
        let current_fn = self
            .builder()
            .get_insert_block()
            .and_then(|b| b.get_parent())
            .ok_or_else(|| CodegenError::Internal("no current function".to_string()))?;

        let count_fn = self
            .functions
            .get(&VarId::new(1000163))
            .ok_or_else(|| CodegenError::Internal("bhc_set_elem_count not declared".to_string()))?;
        let count = self
            .builder()
            .build_call(*count_fn, &[set_ptr.into()], "count")
            .map_err(|e| CodegenError::Internal(format!("call failed: {:?}", e)))?
            .try_as_basic_value()
            .basic()
            .ok_or_else(|| CodegenError::Internal("void".to_string()))?
            .into_int_value();

        let elem_at_fn = *self
            .functions
            .get(&VarId::new(1000164))
            .ok_or_else(|| CodegenError::Internal("bhc_set_elem_at not declared".to_string()))?;

        let closure_fn_ptr = self.extract_closure_fn_ptr(fn_ptr)?;
        let call_fn_type =
            ptr_type.fn_type(&[ptr_type.into(), ptr_type.into(), ptr_type.into()], false);

        let loop_header = self
            .llvm_context()
            .append_basic_block(current_fn, "sfl_header");
        let loop_body = self
            .llvm_context()
            .append_basic_block(current_fn, "sfl_body");
        let loop_exit = self
            .llvm_context()
            .append_basic_block(current_fn, "sfl_exit");

        let init_ptr = self.value_to_ptr(init_val)?;

        let entry_block = self.builder().get_insert_block().unwrap();
        self.builder()
            .build_unconditional_branch(loop_header)
            .map_err(|e| CodegenError::Internal(format!("branch failed: {:?}", e)))?;

        self.builder().position_at_end(loop_header);
        let acc_phi = self
            .builder()
            .build_phi(ptr_type, "acc")
            .map_err(|e| CodegenError::Internal(format!("phi failed: {:?}", e)))?;
        let idx_phi = self
            .builder()
            .build_phi(tm.i64_type(), "idx")
            .map_err(|e| CodegenError::Internal(format!("phi failed: {:?}", e)))?;

        let idx = idx_phi.as_basic_value().into_int_value();
        let done = self
            .builder()
            .build_int_compare(inkwell::IntPredicate::SGE, idx, count, "done")
            .map_err(|e| CodegenError::Internal(format!("cmp failed: {:?}", e)))?;
        self.builder()
            .build_conditional_branch(done, loop_exit, loop_body)
            .map_err(|e| CodegenError::Internal(format!("branch failed: {:?}", e)))?;

        self.builder().position_at_end(loop_body);
        let elem_i64 = self
            .builder()
            .build_call(elem_at_fn, &[set_ptr.into(), idx.into()], "elem")
            .map_err(|e| CodegenError::Internal(format!("call failed: {:?}", e)))?
            .try_as_basic_value()
            .basic()
            .ok_or_else(|| CodegenError::Internal("void".to_string()))?
            .into_int_value();
        let elem_ptr = self.int_to_ptr(elem_i64)?;

        let acc_val = acc_phi.as_basic_value();
        let new_acc = self
            .builder()
            .build_indirect_call(
                call_fn_type,
                closure_fn_ptr,
                &[fn_ptr.into(), acc_val.into(), elem_ptr.into()],
                "new_acc",
            )
            .map_err(|e| CodegenError::Internal(format!("indirect call failed: {:?}", e)))?
            .try_as_basic_value()
            .basic()
            .ok_or_else(|| CodegenError::Internal("closure returned void".to_string()))?;

        let new_idx = self
            .builder()
            .build_int_add(idx, tm.i64_type().const_int(1, false), "new_idx")
            .map_err(|e| CodegenError::Internal(format!("add failed: {:?}", e)))?;
        self.builder()
            .build_unconditional_branch(loop_header)
            .map_err(|e| CodegenError::Internal(format!("branch failed: {:?}", e)))?;

        acc_phi.add_incoming(&[(&init_ptr, entry_block), (&new_acc, loop_body)]);
        idx_phi.add_incoming(&[
            (&tm.i64_type().const_zero(), entry_block),
            (&new_idx, loop_body),
        ]);

        self.builder().position_at_end(loop_exit);
        Ok(Some(acc_phi.as_basic_value()))
    }

    /// Lower `Data.Map.mapWithKey` -- applies closure(key, val) to each entry, building new map.
    /// mapWithKey :: (k -> v -> v') -> Map k v -> Map k v'
    fn lower_builtin_map_map_with_key(
        &mut self,
        fn_expr: &Expr,
        map_expr: &Expr,
    ) -> CodegenResult<Option<BasicValueEnum<'ctx>>> {
        let fn_val = self
            .lower_expr(fn_expr)?
            .ok_or_else(|| CodegenError::Internal("map_mapWithKey: no function".to_string()))?;
        let fn_ptr = self.value_to_ptr(fn_val)?;
        let map_val = self
            .lower_expr(map_expr)?
            .ok_or_else(|| CodegenError::Internal("map_mapWithKey: no map".to_string()))?;
        let map_ptr = self.value_to_ptr(map_val)?;

        let tm = self.type_mapper();
        let ptr_type = tm.ptr_type();
        let current_fn = self
            .builder()
            .get_insert_block()
            .and_then(|b| b.get_parent())
            .ok_or_else(|| CodegenError::Internal("no current function".to_string()))?;

        // Get element count
        let count_fn = self
            .functions
            .get(&VarId::new(1000160))
            .ok_or_else(|| CodegenError::Internal("bhc_map_keys_count not declared".to_string()))?;
        let count = self
            .builder()
            .build_call(*count_fn, &[map_ptr.into()], "count")
            .map_err(|e| CodegenError::Internal(format!("call failed: {:?}", e)))?
            .try_as_basic_value()
            .basic()
            .ok_or_else(|| CodegenError::Internal("void".to_string()))?
            .into_int_value();

        let key_at_fn = *self
            .functions
            .get(&VarId::new(1000161))
            .ok_or_else(|| CodegenError::Internal("bhc_map_key_at not declared".to_string()))?;
        let val_at_fn = *self
            .functions
            .get(&VarId::new(1000162))
            .ok_or_else(|| CodegenError::Internal("bhc_map_value_at not declared".to_string()))?;
        let insert_fn = *self
            .functions
            .get(&VarId::new(1000107))
            .ok_or_else(|| CodegenError::Internal("bhc_map_insert not declared".to_string()))?;

        // Start with empty map
        let empty_fn = self
            .functions
            .get(&VarId::new(1000100))
            .ok_or_else(|| CodegenError::Internal("bhc_map_empty not declared".to_string()))?;
        let empty_map = self
            .builder()
            .build_call(*empty_fn, &[], "empty")
            .map_err(|e| CodegenError::Internal(format!("call failed: {:?}", e)))?
            .try_as_basic_value()
            .basic()
            .ok_or_else(|| CodegenError::Internal("void".to_string()))?;

        // Extract function pointer from closure; 2 args: key, val
        let closure_fn_ptr = self.extract_closure_fn_ptr(fn_ptr)?;
        let call_fn_type =
            ptr_type.fn_type(&[ptr_type.into(), ptr_type.into(), ptr_type.into()], false);

        let loop_header = self
            .llvm_context()
            .append_basic_block(current_fn, "mmwk_header");
        let loop_body = self
            .llvm_context()
            .append_basic_block(current_fn, "mmwk_body");
        let loop_exit = self
            .llvm_context()
            .append_basic_block(current_fn, "mmwk_exit");

        let entry_block = self.builder().get_insert_block().unwrap();
        self.builder()
            .build_unconditional_branch(loop_header)
            .map_err(|e| CodegenError::Internal(format!("branch failed: {:?}", e)))?;

        self.builder().position_at_end(loop_header);
        let acc_phi = self
            .builder()
            .build_phi(ptr_type, "acc")
            .map_err(|e| CodegenError::Internal(format!("phi failed: {:?}", e)))?;
        let idx_phi = self
            .builder()
            .build_phi(tm.i64_type(), "idx")
            .map_err(|e| CodegenError::Internal(format!("phi failed: {:?}", e)))?;

        let idx = idx_phi.as_basic_value().into_int_value();
        let done = self
            .builder()
            .build_int_compare(inkwell::IntPredicate::SGE, idx, count, "done")
            .map_err(|e| CodegenError::Internal(format!("cmp failed: {:?}", e)))?;
        self.builder()
            .build_conditional_branch(done, loop_exit, loop_body)
            .map_err(|e| CodegenError::Internal(format!("branch failed: {:?}", e)))?;

        // Loop body: get key and value, apply fn to (key, value), insert (key, new_val)
        self.builder().position_at_end(loop_body);
        let key_i64 = self
            .builder()
            .build_call(key_at_fn, &[map_ptr.into(), idx.into()], "key")
            .map_err(|e| CodegenError::Internal(format!("call failed: {:?}", e)))?
            .try_as_basic_value()
            .basic()
            .ok_or_else(|| CodegenError::Internal("void".to_string()))?
            .into_int_value();
        let val = self
            .builder()
            .build_call(val_at_fn, &[map_ptr.into(), idx.into()], "val")
            .map_err(|e| CodegenError::Internal(format!("call failed: {:?}", e)))?
            .try_as_basic_value()
            .basic()
            .ok_or_else(|| CodegenError::Internal("void".to_string()))?;

        // Convert key i64 to ptr for closure call
        let key_ptr = self.int_to_ptr(key_i64)?;

        // Call closure: fn_ptr(closure, key_ptr, val)
        let mapped_val = self
            .builder()
            .build_indirect_call(
                call_fn_type,
                closure_fn_ptr,
                &[fn_ptr.into(), key_ptr.into(), val.into()],
                "mapped",
            )
            .map_err(|e| CodegenError::Internal(format!("indirect call failed: {:?}", e)))?
            .try_as_basic_value()
            .basic()
            .ok_or_else(|| CodegenError::Internal("closure returned void".to_string()))?;

        let mapped_ptr = self.value_to_ptr(mapped_val)?;
        let acc_ptr = acc_phi.as_basic_value().into_pointer_value();
        let new_map = self
            .builder()
            .build_call(
                insert_fn,
                &[key_i64.into(), mapped_ptr.into(), acc_ptr.into()],
                "new_map",
            )
            .map_err(|e| CodegenError::Internal(format!("call failed: {:?}", e)))?
            .try_as_basic_value()
            .basic()
            .ok_or_else(|| CodegenError::Internal("void".to_string()))?;

        let new_idx = self
            .builder()
            .build_int_add(idx, tm.i64_type().const_int(1, false), "new_idx")
            .map_err(|e| CodegenError::Internal(format!("add failed: {:?}", e)))?;
        self.builder()
            .build_unconditional_branch(loop_header)
            .map_err(|e| CodegenError::Internal(format!("branch failed: {:?}", e)))?;

        acc_phi.add_incoming(&[(&empty_map, entry_block), (&new_map, loop_body)]);
        idx_phi.add_incoming(&[
            (&tm.i64_type().const_zero(), entry_block),
            (&new_idx, loop_body),
        ]);

        self.builder().position_at_end(loop_exit);
        Ok(Some(acc_phi.as_basic_value()))
    }

    /// Lower `Data.Map.filterWithKey` -- applies predicate(key, val), keeping matches.
    /// filterWithKey :: (k -> v -> Bool) -> Map k v -> Map k v
    fn lower_builtin_map_filter_with_key(
        &mut self,
        fn_expr: &Expr,
        map_expr: &Expr,
    ) -> CodegenResult<Option<BasicValueEnum<'ctx>>> {
        let fn_val = self
            .lower_expr(fn_expr)?
            .ok_or_else(|| CodegenError::Internal("map_filterWithKey: no function".to_string()))?;
        let fn_ptr = self.value_to_ptr(fn_val)?;
        let map_val = self
            .lower_expr(map_expr)?
            .ok_or_else(|| CodegenError::Internal("map_filterWithKey: no map".to_string()))?;
        let map_ptr = self.value_to_ptr(map_val)?;

        let tm = self.type_mapper();
        let ptr_type = tm.ptr_type();
        let current_fn = self
            .builder()
            .get_insert_block()
            .and_then(|b| b.get_parent())
            .ok_or_else(|| CodegenError::Internal("no current function".to_string()))?;

        let count_fn = self
            .functions
            .get(&VarId::new(1000160))
            .ok_or_else(|| CodegenError::Internal("bhc_map_keys_count not declared".to_string()))?;
        let count = self
            .builder()
            .build_call(*count_fn, &[map_ptr.into()], "count")
            .map_err(|e| CodegenError::Internal(format!("call failed: {:?}", e)))?
            .try_as_basic_value()
            .basic()
            .ok_or_else(|| CodegenError::Internal("void".to_string()))?
            .into_int_value();

        let key_at_fn = *self
            .functions
            .get(&VarId::new(1000161))
            .ok_or_else(|| CodegenError::Internal("bhc_map_key_at not declared".to_string()))?;
        let val_at_fn = *self
            .functions
            .get(&VarId::new(1000162))
            .ok_or_else(|| CodegenError::Internal("bhc_map_value_at not declared".to_string()))?;
        let insert_fn = *self
            .functions
            .get(&VarId::new(1000107))
            .ok_or_else(|| CodegenError::Internal("bhc_map_insert not declared".to_string()))?;

        let empty_fn = self
            .functions
            .get(&VarId::new(1000100))
            .ok_or_else(|| CodegenError::Internal("bhc_map_empty not declared".to_string()))?;
        let empty_map = self
            .builder()
            .build_call(*empty_fn, &[], "empty")
            .map_err(|e| CodegenError::Internal(format!("call failed: {:?}", e)))?
            .try_as_basic_value()
            .basic()
            .ok_or_else(|| CodegenError::Internal("void".to_string()))?;

        // 2-arg closure: (key, val)
        let closure_fn_ptr = self.extract_closure_fn_ptr(fn_ptr)?;
        let call_fn_type =
            ptr_type.fn_type(&[ptr_type.into(), ptr_type.into(), ptr_type.into()], false);

        let loop_header = self
            .llvm_context()
            .append_basic_block(current_fn, "mfwk_header");
        let loop_body = self
            .llvm_context()
            .append_basic_block(current_fn, "mfwk_body");
        let loop_insert = self
            .llvm_context()
            .append_basic_block(current_fn, "mfwk_insert");
        let loop_skip = self
            .llvm_context()
            .append_basic_block(current_fn, "mfwk_skip");
        let loop_exit = self
            .llvm_context()
            .append_basic_block(current_fn, "mfwk_exit");

        let entry_block = self.builder().get_insert_block().unwrap();
        self.builder()
            .build_unconditional_branch(loop_header)
            .map_err(|e| CodegenError::Internal(format!("branch failed: {:?}", e)))?;

        self.builder().position_at_end(loop_header);
        let acc_phi = self
            .builder()
            .build_phi(ptr_type, "acc")
            .map_err(|e| CodegenError::Internal(format!("phi failed: {:?}", e)))?;
        let idx_phi = self
            .builder()
            .build_phi(tm.i64_type(), "idx")
            .map_err(|e| CodegenError::Internal(format!("phi failed: {:?}", e)))?;

        let idx = idx_phi.as_basic_value().into_int_value();
        let done = self
            .builder()
            .build_int_compare(inkwell::IntPredicate::SGE, idx, count, "done")
            .map_err(|e| CodegenError::Internal(format!("cmp failed: {:?}", e)))?;
        self.builder()
            .build_conditional_branch(done, loop_exit, loop_body)
            .map_err(|e| CodegenError::Internal(format!("branch failed: {:?}", e)))?;

        self.builder().position_at_end(loop_body);
        let key_i64 = self
            .builder()
            .build_call(key_at_fn, &[map_ptr.into(), idx.into()], "key")
            .map_err(|e| CodegenError::Internal(format!("call failed: {:?}", e)))?
            .try_as_basic_value()
            .basic()
            .ok_or_else(|| CodegenError::Internal("void".to_string()))?
            .into_int_value();
        let val = self
            .builder()
            .build_call(val_at_fn, &[map_ptr.into(), idx.into()], "val")
            .map_err(|e| CodegenError::Internal(format!("call failed: {:?}", e)))?
            .try_as_basic_value()
            .basic()
            .ok_or_else(|| CodegenError::Internal("void".to_string()))?;

        let key_ptr = self.int_to_ptr(key_i64)?;

        // Call predicate: fn_ptr(closure, key_ptr, val) -> Bool (tag 0=False, 1=True)
        let pred_result = self
            .builder()
            .build_indirect_call(
                call_fn_type,
                closure_fn_ptr,
                &[fn_ptr.into(), key_ptr.into(), val.into()],
                "pred",
            )
            .map_err(|e| CodegenError::Internal(format!("indirect call failed: {:?}", e)))?
            .try_as_basic_value()
            .basic()
            .ok_or_else(|| CodegenError::Internal("closure returned void".to_string()))?;

        // Check if predicate returned True (non-zero tag)
        let pred_ptr = self.value_to_ptr(pred_result)?;
        let pred_tag = self.extract_adt_tag(pred_ptr)?;
        let is_true = self
            .builder()
            .build_int_compare(
                inkwell::IntPredicate::NE,
                pred_tag,
                tm.i64_type().const_zero(),
                "is_true",
            )
            .map_err(|e| CodegenError::Internal(format!("cmp failed: {:?}", e)))?;
        self.builder()
            .build_conditional_branch(is_true, loop_insert, loop_skip)
            .map_err(|e| CodegenError::Internal(format!("branch failed: {:?}", e)))?;

        // Insert branch: add (key, val) to accumulator map
        self.builder().position_at_end(loop_insert);
        let val_ptr = self.value_to_ptr(val)?;
        let acc_ptr_ins = acc_phi.as_basic_value().into_pointer_value();
        let inserted = self
            .builder()
            .build_call(
                insert_fn,
                &[key_i64.into(), val_ptr.into(), acc_ptr_ins.into()],
                "inserted",
            )
            .map_err(|e| CodegenError::Internal(format!("call failed: {:?}", e)))?
            .try_as_basic_value()
            .basic()
            .ok_or_else(|| CodegenError::Internal("void".to_string()))?;
        self.builder()
            .build_unconditional_branch(loop_skip)
            .map_err(|e| CodegenError::Internal(format!("branch failed: {:?}", e)))?;

        // Skip/merge block
        self.builder().position_at_end(loop_skip);
        let merge_phi = self
            .builder()
            .build_phi(ptr_type, "merge_acc")
            .map_err(|e| CodegenError::Internal(format!("phi failed: {:?}", e)))?;
        merge_phi.add_incoming(&[
            (&inserted, loop_insert),
            (&acc_phi.as_basic_value(), loop_body),
        ]);

        let new_idx = self
            .builder()
            .build_int_add(idx, tm.i64_type().const_int(1, false), "new_idx")
            .map_err(|e| CodegenError::Internal(format!("add failed: {:?}", e)))?;
        self.builder()
            .build_unconditional_branch(loop_header)
            .map_err(|e| CodegenError::Internal(format!("branch failed: {:?}", e)))?;

        acc_phi.add_incoming(&[
            (&empty_map, entry_block),
            (&merge_phi.as_basic_value(), loop_skip),
        ]);
        idx_phi.add_incoming(&[
            (&tm.i64_type().const_zero(), entry_block),
            (&new_idx, loop_skip),
        ]);

        self.builder().position_at_end(loop_exit);
        Ok(Some(acc_phi.as_basic_value()))
    }

    /// Lower `Data.Map.mapMaybe` — apply function returning Maybe, keep Just values.
    /// mapMaybe :: (a -> Maybe b) -> Map k a -> Map k b
    fn lower_builtin_data_map_maybe(
        &mut self,
        fn_expr: &Expr,
        map_expr: &Expr,
    ) -> CodegenResult<Option<BasicValueEnum<'ctx>>> {
        let fn_val = self
            .lower_expr(fn_expr)?
            .ok_or_else(|| CodegenError::Internal("mapMaybe: no function".to_string()))?;
        let fn_ptr = self.value_to_ptr(fn_val)?;
        let map_val = self
            .lower_expr(map_expr)?
            .ok_or_else(|| CodegenError::Internal("mapMaybe: no map".to_string()))?;
        let map_ptr = self.value_to_ptr(map_val)?;

        let tm = self.type_mapper();
        let ptr_type = tm.ptr_type();
        let current_fn = self
            .builder()
            .get_insert_block()
            .and_then(|b| b.get_parent())
            .ok_or_else(|| CodegenError::Internal("no current function".to_string()))?;

        let count_fn = self
            .functions
            .get(&VarId::new(1000160))
            .ok_or_else(|| CodegenError::Internal("bhc_map_keys_count not declared".to_string()))?;
        let count = self
            .builder()
            .build_call(*count_fn, &[map_ptr.into()], "count")
            .map_err(|e| CodegenError::Internal(format!("call failed: {:?}", e)))?
            .try_as_basic_value()
            .basic()
            .ok_or_else(|| CodegenError::Internal("void".to_string()))?
            .into_int_value();

        let key_at_fn = *self
            .functions
            .get(&VarId::new(1000161))
            .ok_or_else(|| CodegenError::Internal("bhc_map_key_at not declared".to_string()))?;
        let val_at_fn = *self
            .functions
            .get(&VarId::new(1000162))
            .ok_or_else(|| CodegenError::Internal("bhc_map_value_at not declared".to_string()))?;
        let insert_fn = *self
            .functions
            .get(&VarId::new(1000107))
            .ok_or_else(|| CodegenError::Internal("bhc_map_insert not declared".to_string()))?;

        let empty_fn = self
            .functions
            .get(&VarId::new(1000100))
            .ok_or_else(|| CodegenError::Internal("bhc_map_empty not declared".to_string()))?;
        let empty_map = self
            .builder()
            .build_call(*empty_fn, &[], "empty")
            .map_err(|e| CodegenError::Internal(format!("call failed: {:?}", e)))?
            .try_as_basic_value()
            .basic()
            .ok_or_else(|| CodegenError::Internal("void".to_string()))?;

        // 1-arg closure: fn(closure_env, val) -> Maybe b
        let closure_fn_ptr = self.extract_closure_fn_ptr(fn_ptr)?;
        let call_fn_type = ptr_type.fn_type(&[ptr_type.into(), ptr_type.into()], false);

        let loop_header = self
            .llvm_context()
            .append_basic_block(current_fn, "mm_header");
        let loop_body = self
            .llvm_context()
            .append_basic_block(current_fn, "mm_body");
        let loop_insert = self
            .llvm_context()
            .append_basic_block(current_fn, "mm_insert");
        let loop_skip = self
            .llvm_context()
            .append_basic_block(current_fn, "mm_skip");
        let loop_exit = self
            .llvm_context()
            .append_basic_block(current_fn, "mm_exit");

        let entry_block = self.builder().get_insert_block().unwrap();
        self.builder()
            .build_unconditional_branch(loop_header)
            .map_err(|e| CodegenError::Internal(format!("branch failed: {:?}", e)))?;

        self.builder().position_at_end(loop_header);
        let acc_phi = self
            .builder()
            .build_phi(ptr_type, "acc")
            .map_err(|e| CodegenError::Internal(format!("phi failed: {:?}", e)))?;
        let idx_phi = self
            .builder()
            .build_phi(tm.i64_type(), "idx")
            .map_err(|e| CodegenError::Internal(format!("phi failed: {:?}", e)))?;

        let idx = idx_phi.as_basic_value().into_int_value();
        let done = self
            .builder()
            .build_int_compare(inkwell::IntPredicate::SGE, idx, count, "done")
            .map_err(|e| CodegenError::Internal(format!("cmp failed: {:?}", e)))?;
        self.builder()
            .build_conditional_branch(done, loop_exit, loop_body)
            .map_err(|e| CodegenError::Internal(format!("branch failed: {:?}", e)))?;

        self.builder().position_at_end(loop_body);
        let key_i64 = self
            .builder()
            .build_call(key_at_fn, &[map_ptr.into(), idx.into()], "key")
            .map_err(|e| CodegenError::Internal(format!("call failed: {:?}", e)))?
            .try_as_basic_value()
            .basic()
            .ok_or_else(|| CodegenError::Internal("void".to_string()))?
            .into_int_value();
        let val = self
            .builder()
            .build_call(val_at_fn, &[map_ptr.into(), idx.into()], "val")
            .map_err(|e| CodegenError::Internal(format!("call failed: {:?}", e)))?
            .try_as_basic_value()
            .basic()
            .ok_or_else(|| CodegenError::Internal("void".to_string()))?;

        // Call function: fn_ptr(closure, val) -> Maybe b
        let maybe_result = self
            .builder()
            .build_indirect_call(
                call_fn_type,
                closure_fn_ptr,
                &[fn_ptr.into(), val.into()],
                "maybe_res",
            )
            .map_err(|e| CodegenError::Internal(format!("indirect call failed: {:?}", e)))?
            .try_as_basic_value()
            .basic()
            .ok_or_else(|| CodegenError::Internal("closure returned void".to_string()))?;

        // Check Maybe tag: 0=Nothing, 1=Just
        let maybe_ptr = self.value_to_ptr(maybe_result)?;
        let maybe_tag = self.extract_adt_tag(maybe_ptr)?;
        let is_just = self
            .builder()
            .build_int_compare(
                inkwell::IntPredicate::NE,
                maybe_tag,
                tm.i64_type().const_zero(),
                "is_just",
            )
            .map_err(|e| CodegenError::Internal(format!("cmp failed: {:?}", e)))?;
        self.builder()
            .build_conditional_branch(is_just, loop_insert, loop_skip)
            .map_err(|e| CodegenError::Internal(format!("branch failed: {:?}", e)))?;

        // Insert branch: extract Just value and add (key, inner_val) to accumulator map
        self.builder().position_at_end(loop_insert);
        let inner_val = self.extract_adt_field(maybe_ptr, 1, 0)?;
        let acc_ptr_ins = acc_phi.as_basic_value().into_pointer_value();
        let inserted = self
            .builder()
            .build_call(
                insert_fn,
                &[key_i64.into(), inner_val.into(), acc_ptr_ins.into()],
                "inserted",
            )
            .map_err(|e| CodegenError::Internal(format!("call failed: {:?}", e)))?
            .try_as_basic_value()
            .basic()
            .ok_or_else(|| CodegenError::Internal("void".to_string()))?;
        self.builder()
            .build_unconditional_branch(loop_skip)
            .map_err(|e| CodegenError::Internal(format!("branch failed: {:?}", e)))?;

        // Skip/merge block
        self.builder().position_at_end(loop_skip);
        let merge_phi = self
            .builder()
            .build_phi(ptr_type, "merge_acc")
            .map_err(|e| CodegenError::Internal(format!("phi failed: {:?}", e)))?;
        merge_phi.add_incoming(&[
            (&inserted, loop_insert),
            (&acc_phi.as_basic_value(), loop_body),
        ]);

        let new_idx = self
            .builder()
            .build_int_add(idx, tm.i64_type().const_int(1, false), "new_idx")
            .map_err(|e| CodegenError::Internal(format!("add failed: {:?}", e)))?;
        self.builder()
            .build_unconditional_branch(loop_header)
            .map_err(|e| CodegenError::Internal(format!("branch failed: {:?}", e)))?;

        acc_phi.add_incoming(&[
            (&empty_map, entry_block),
            (&merge_phi.as_basic_value(), loop_skip),
        ]);
        idx_phi.add_incoming(&[
            (&tm.i64_type().const_zero(), entry_block),
            (&new_idx, loop_skip),
        ]);

        self.builder().position_at_end(loop_exit);
        Ok(Some(acc_phi.as_basic_value()))
    }

    /// Lower `Data.Map.mapMaybeWithKey` — apply function with key returning Maybe, keep Just values.
    /// mapMaybeWithKey :: (k -> a -> Maybe b) -> Map k a -> Map k b
    fn lower_builtin_data_map_maybe_with_key(
        &mut self,
        fn_expr: &Expr,
        map_expr: &Expr,
    ) -> CodegenResult<Option<BasicValueEnum<'ctx>>> {
        let fn_val = self
            .lower_expr(fn_expr)?
            .ok_or_else(|| CodegenError::Internal("mapMaybeWithKey: no function".to_string()))?;
        let fn_ptr = self.value_to_ptr(fn_val)?;
        let map_val = self
            .lower_expr(map_expr)?
            .ok_or_else(|| CodegenError::Internal("mapMaybeWithKey: no map".to_string()))?;
        let map_ptr = self.value_to_ptr(map_val)?;

        let tm = self.type_mapper();
        let ptr_type = tm.ptr_type();
        let current_fn = self
            .builder()
            .get_insert_block()
            .and_then(|b| b.get_parent())
            .ok_or_else(|| CodegenError::Internal("no current function".to_string()))?;

        let count_fn = self
            .functions
            .get(&VarId::new(1000160))
            .ok_or_else(|| CodegenError::Internal("bhc_map_keys_count not declared".to_string()))?;
        let count = self
            .builder()
            .build_call(*count_fn, &[map_ptr.into()], "count")
            .map_err(|e| CodegenError::Internal(format!("call failed: {:?}", e)))?
            .try_as_basic_value()
            .basic()
            .ok_or_else(|| CodegenError::Internal("void".to_string()))?
            .into_int_value();

        let key_at_fn = *self
            .functions
            .get(&VarId::new(1000161))
            .ok_or_else(|| CodegenError::Internal("bhc_map_key_at not declared".to_string()))?;
        let val_at_fn = *self
            .functions
            .get(&VarId::new(1000162))
            .ok_or_else(|| CodegenError::Internal("bhc_map_value_at not declared".to_string()))?;
        let insert_fn = *self
            .functions
            .get(&VarId::new(1000107))
            .ok_or_else(|| CodegenError::Internal("bhc_map_insert not declared".to_string()))?;

        let empty_fn = self
            .functions
            .get(&VarId::new(1000100))
            .ok_or_else(|| CodegenError::Internal("bhc_map_empty not declared".to_string()))?;
        let empty_map = self
            .builder()
            .build_call(*empty_fn, &[], "empty")
            .map_err(|e| CodegenError::Internal(format!("call failed: {:?}", e)))?
            .try_as_basic_value()
            .basic()
            .ok_or_else(|| CodegenError::Internal("void".to_string()))?;

        // 2-arg closure: fn(closure_env, key, val) -> Maybe b
        let closure_fn_ptr = self.extract_closure_fn_ptr(fn_ptr)?;
        let call_fn_type =
            ptr_type.fn_type(&[ptr_type.into(), ptr_type.into(), ptr_type.into()], false);

        let loop_header = self
            .llvm_context()
            .append_basic_block(current_fn, "mmwk_header");
        let loop_body = self
            .llvm_context()
            .append_basic_block(current_fn, "mmwk_body");
        let loop_insert = self
            .llvm_context()
            .append_basic_block(current_fn, "mmwk_insert");
        let loop_skip = self
            .llvm_context()
            .append_basic_block(current_fn, "mmwk_skip");
        let loop_exit = self
            .llvm_context()
            .append_basic_block(current_fn, "mmwk_exit");

        let entry_block = self.builder().get_insert_block().unwrap();
        self.builder()
            .build_unconditional_branch(loop_header)
            .map_err(|e| CodegenError::Internal(format!("branch failed: {:?}", e)))?;

        self.builder().position_at_end(loop_header);
        let acc_phi = self
            .builder()
            .build_phi(ptr_type, "acc")
            .map_err(|e| CodegenError::Internal(format!("phi failed: {:?}", e)))?;
        let idx_phi = self
            .builder()
            .build_phi(tm.i64_type(), "idx")
            .map_err(|e| CodegenError::Internal(format!("phi failed: {:?}", e)))?;

        let idx = idx_phi.as_basic_value().into_int_value();
        let done = self
            .builder()
            .build_int_compare(inkwell::IntPredicate::SGE, idx, count, "done")
            .map_err(|e| CodegenError::Internal(format!("cmp failed: {:?}", e)))?;
        self.builder()
            .build_conditional_branch(done, loop_exit, loop_body)
            .map_err(|e| CodegenError::Internal(format!("branch failed: {:?}", e)))?;

        self.builder().position_at_end(loop_body);
        let key_i64 = self
            .builder()
            .build_call(key_at_fn, &[map_ptr.into(), idx.into()], "key")
            .map_err(|e| CodegenError::Internal(format!("call failed: {:?}", e)))?
            .try_as_basic_value()
            .basic()
            .ok_or_else(|| CodegenError::Internal("void".to_string()))?
            .into_int_value();
        let val = self
            .builder()
            .build_call(val_at_fn, &[map_ptr.into(), idx.into()], "val")
            .map_err(|e| CodegenError::Internal(format!("call failed: {:?}", e)))?
            .try_as_basic_value()
            .basic()
            .ok_or_else(|| CodegenError::Internal("void".to_string()))?;

        let key_ptr = self.int_to_ptr(key_i64)?;

        // Call function: fn_ptr(closure, key_ptr, val) -> Maybe b
        let maybe_result = self
            .builder()
            .build_indirect_call(
                call_fn_type,
                closure_fn_ptr,
                &[fn_ptr.into(), key_ptr.into(), val.into()],
                "maybe_res",
            )
            .map_err(|e| CodegenError::Internal(format!("indirect call failed: {:?}", e)))?
            .try_as_basic_value()
            .basic()
            .ok_or_else(|| CodegenError::Internal("closure returned void".to_string()))?;

        // Check Maybe tag: 0=Nothing, 1=Just
        let maybe_ptr = self.value_to_ptr(maybe_result)?;
        let maybe_tag = self.extract_adt_tag(maybe_ptr)?;
        let is_just = self
            .builder()
            .build_int_compare(
                inkwell::IntPredicate::NE,
                maybe_tag,
                tm.i64_type().const_zero(),
                "is_just",
            )
            .map_err(|e| CodegenError::Internal(format!("cmp failed: {:?}", e)))?;
        self.builder()
            .build_conditional_branch(is_just, loop_insert, loop_skip)
            .map_err(|e| CodegenError::Internal(format!("branch failed: {:?}", e)))?;

        // Insert branch: extract Just value and add (key, inner_val) to accumulator map
        self.builder().position_at_end(loop_insert);
        let inner_val = self.extract_adt_field(maybe_ptr, 1, 0)?;
        let acc_ptr_ins = acc_phi.as_basic_value().into_pointer_value();
        let inserted = self
            .builder()
            .build_call(
                insert_fn,
                &[key_i64.into(), inner_val.into(), acc_ptr_ins.into()],
                "inserted",
            )
            .map_err(|e| CodegenError::Internal(format!("call failed: {:?}", e)))?
            .try_as_basic_value()
            .basic()
            .ok_or_else(|| CodegenError::Internal("void".to_string()))?;
        self.builder()
            .build_unconditional_branch(loop_skip)
            .map_err(|e| CodegenError::Internal(format!("branch failed: {:?}", e)))?;

        // Skip/merge block
        self.builder().position_at_end(loop_skip);
        let merge_phi = self
            .builder()
            .build_phi(ptr_type, "merge_acc")
            .map_err(|e| CodegenError::Internal(format!("phi failed: {:?}", e)))?;
        merge_phi.add_incoming(&[
            (&inserted, loop_insert),
            (&acc_phi.as_basic_value(), loop_body),
        ]);

        let new_idx = self
            .builder()
            .build_int_add(idx, tm.i64_type().const_int(1, false), "new_idx")
            .map_err(|e| CodegenError::Internal(format!("add failed: {:?}", e)))?;
        self.builder()
            .build_unconditional_branch(loop_header)
            .map_err(|e| CodegenError::Internal(format!("branch failed: {:?}", e)))?;

        acc_phi.add_incoming(&[
            (&empty_map, entry_block),
            (&merge_phi.as_basic_value(), loop_skip),
        ]);
        idx_phi.add_incoming(&[
            (&tm.i64_type().const_zero(), entry_block),
            (&new_idx, loop_skip),
        ]);

        self.builder().position_at_end(loop_exit);
        Ok(Some(acc_phi.as_basic_value()))
    }

    /// Lower `Data.Map.foldlWithKey'` -- strict left fold with key.
    /// foldlWithKey' :: (a -> k -> v -> a) -> a -> Map k v -> a
    fn lower_builtin_map_foldl_with_key(
        &mut self,
        fn_expr: &Expr,
        init_expr: &Expr,
        map_expr: &Expr,
    ) -> CodegenResult<Option<BasicValueEnum<'ctx>>> {
        let fn_val = self
            .lower_expr(fn_expr)?
            .ok_or_else(|| CodegenError::Internal("map_foldlWithKey: no function".to_string()))?;
        let fn_ptr = self.value_to_ptr(fn_val)?;
        let init_val = self
            .lower_expr(init_expr)?
            .ok_or_else(|| CodegenError::Internal("map_foldlWithKey: no init".to_string()))?;
        let map_val = self
            .lower_expr(map_expr)?
            .ok_or_else(|| CodegenError::Internal("map_foldlWithKey: no map".to_string()))?;
        let map_ptr = self.value_to_ptr(map_val)?;

        let tm = self.type_mapper();
        let ptr_type = tm.ptr_type();
        let current_fn = self
            .builder()
            .get_insert_block()
            .and_then(|b| b.get_parent())
            .ok_or_else(|| CodegenError::Internal("no current function".to_string()))?;

        let count_fn = self
            .functions
            .get(&VarId::new(1000160))
            .ok_or_else(|| CodegenError::Internal("bhc_map_keys_count not declared".to_string()))?;
        let count = self
            .builder()
            .build_call(*count_fn, &[map_ptr.into()], "count")
            .map_err(|e| CodegenError::Internal(format!("call failed: {:?}", e)))?
            .try_as_basic_value()
            .basic()
            .ok_or_else(|| CodegenError::Internal("void".to_string()))?
            .into_int_value();

        let key_at_fn = *self
            .functions
            .get(&VarId::new(1000161))
            .ok_or_else(|| CodegenError::Internal("bhc_map_key_at not declared".to_string()))?;
        let val_at_fn = *self
            .functions
            .get(&VarId::new(1000162))
            .ok_or_else(|| CodegenError::Internal("bhc_map_value_at not declared".to_string()))?;

        let closure_fn_ptr = self.extract_closure_fn_ptr(fn_ptr)?;
        // foldlWithKey takes 3 args: acc, key, val
        let call_fn_type = ptr_type.fn_type(
            &[
                ptr_type.into(),
                ptr_type.into(),
                ptr_type.into(),
                ptr_type.into(),
            ],
            false,
        );

        let loop_header = self
            .llvm_context()
            .append_basic_block(current_fn, "mflwk_header");
        let loop_body = self
            .llvm_context()
            .append_basic_block(current_fn, "mflwk_body");
        let loop_exit = self
            .llvm_context()
            .append_basic_block(current_fn, "mflwk_exit");

        let init_ptr = self.value_to_ptr(init_val)?;

        let entry_block = self.builder().get_insert_block().unwrap();
        self.builder()
            .build_unconditional_branch(loop_header)
            .map_err(|e| CodegenError::Internal(format!("branch failed: {:?}", e)))?;

        self.builder().position_at_end(loop_header);
        let acc_phi = self
            .builder()
            .build_phi(ptr_type, "acc")
            .map_err(|e| CodegenError::Internal(format!("phi failed: {:?}", e)))?;
        let idx_phi = self
            .builder()
            .build_phi(tm.i64_type(), "idx")
            .map_err(|e| CodegenError::Internal(format!("phi failed: {:?}", e)))?;

        let idx = idx_phi.as_basic_value().into_int_value();
        let done = self
            .builder()
            .build_int_compare(inkwell::IntPredicate::SGE, idx, count, "done")
            .map_err(|e| CodegenError::Internal(format!("cmp failed: {:?}", e)))?;
        self.builder()
            .build_conditional_branch(done, loop_exit, loop_body)
            .map_err(|e| CodegenError::Internal(format!("branch failed: {:?}", e)))?;

        self.builder().position_at_end(loop_body);
        let key_i64 = self
            .builder()
            .build_call(key_at_fn, &[map_ptr.into(), idx.into()], "key")
            .map_err(|e| CodegenError::Internal(format!("call failed: {:?}", e)))?
            .try_as_basic_value()
            .basic()
            .ok_or_else(|| CodegenError::Internal("void".to_string()))?
            .into_int_value();
        let val = self
            .builder()
            .build_call(val_at_fn, &[map_ptr.into(), idx.into()], "val")
            .map_err(|e| CodegenError::Internal(format!("call failed: {:?}", e)))?
            .try_as_basic_value()
            .basic()
            .ok_or_else(|| CodegenError::Internal("void".to_string()))?;

        let key_ptr = self.int_to_ptr(key_i64)?;

        // Call closure: fn_ptr(closure, acc, key_ptr, val)
        let acc_val = acc_phi.as_basic_value();
        let new_acc = self
            .builder()
            .build_indirect_call(
                call_fn_type,
                closure_fn_ptr,
                &[fn_ptr.into(), acc_val.into(), key_ptr.into(), val.into()],
                "new_acc",
            )
            .map_err(|e| CodegenError::Internal(format!("indirect call failed: {:?}", e)))?
            .try_as_basic_value()
            .basic()
            .ok_or_else(|| CodegenError::Internal("closure returned void".to_string()))?;

        let new_idx = self
            .builder()
            .build_int_add(idx, tm.i64_type().const_int(1, false), "new_idx")
            .map_err(|e| CodegenError::Internal(format!("add failed: {:?}", e)))?;
        self.builder()
            .build_unconditional_branch(loop_header)
            .map_err(|e| CodegenError::Internal(format!("branch failed: {:?}", e)))?;

        acc_phi.add_incoming(&[(&init_ptr, entry_block), (&new_acc, loop_body)]);
        idx_phi.add_incoming(&[
            (&tm.i64_type().const_zero(), entry_block),
            (&new_idx, loop_body),
        ]);

        self.builder().position_at_end(loop_exit);
        Ok(Some(acc_phi.as_basic_value()))
    }

    /// Lower `Data.Map.foldrWithKey` -- right fold with key.
    /// foldrWithKey :: (k -> v -> a -> a) -> a -> Map k v -> a
    fn lower_builtin_map_foldr_with_key(
        &mut self,
        fn_expr: &Expr,
        init_expr: &Expr,
        map_expr: &Expr,
    ) -> CodegenResult<Option<BasicValueEnum<'ctx>>> {
        let fn_val = self
            .lower_expr(fn_expr)?
            .ok_or_else(|| CodegenError::Internal("map_foldrWithKey: no function".to_string()))?;
        let fn_ptr = self.value_to_ptr(fn_val)?;
        let init_val = self
            .lower_expr(init_expr)?
            .ok_or_else(|| CodegenError::Internal("map_foldrWithKey: no init".to_string()))?;
        let map_val = self
            .lower_expr(map_expr)?
            .ok_or_else(|| CodegenError::Internal("map_foldrWithKey: no map".to_string()))?;
        let map_ptr = self.value_to_ptr(map_val)?;

        let tm = self.type_mapper();
        let ptr_type = tm.ptr_type();
        let current_fn = self
            .builder()
            .get_insert_block()
            .and_then(|b| b.get_parent())
            .ok_or_else(|| CodegenError::Internal("no current function".to_string()))?;

        let count_fn = self
            .functions
            .get(&VarId::new(1000160))
            .ok_or_else(|| CodegenError::Internal("bhc_map_keys_count not declared".to_string()))?;
        let count = self
            .builder()
            .build_call(*count_fn, &[map_ptr.into()], "count")
            .map_err(|e| CodegenError::Internal(format!("call failed: {:?}", e)))?
            .try_as_basic_value()
            .basic()
            .ok_or_else(|| CodegenError::Internal("void".to_string()))?
            .into_int_value();

        let key_at_fn = *self
            .functions
            .get(&VarId::new(1000161))
            .ok_or_else(|| CodegenError::Internal("bhc_map_key_at not declared".to_string()))?;
        let val_at_fn = *self
            .functions
            .get(&VarId::new(1000162))
            .ok_or_else(|| CodegenError::Internal("bhc_map_value_at not declared".to_string()))?;

        let closure_fn_ptr = self.extract_closure_fn_ptr(fn_ptr)?;
        // foldrWithKey takes 3 args: key, val, acc
        let call_fn_type = ptr_type.fn_type(
            &[
                ptr_type.into(),
                ptr_type.into(),
                ptr_type.into(),
                ptr_type.into(),
            ],
            false,
        );

        // Iterate backwards for right fold
        let loop_header = self
            .llvm_context()
            .append_basic_block(current_fn, "mfrwk_header");
        let loop_body = self
            .llvm_context()
            .append_basic_block(current_fn, "mfrwk_body");
        let loop_exit = self
            .llvm_context()
            .append_basic_block(current_fn, "mfrwk_exit");

        let start_idx = self
            .builder()
            .build_int_sub(count, tm.i64_type().const_int(1, false), "start_idx")
            .map_err(|e| CodegenError::Internal(format!("sub failed: {:?}", e)))?;

        let init_ptr = self.value_to_ptr(init_val)?;

        let entry_block = self.builder().get_insert_block().unwrap();
        self.builder()
            .build_unconditional_branch(loop_header)
            .map_err(|e| CodegenError::Internal(format!("branch failed: {:?}", e)))?;

        self.builder().position_at_end(loop_header);
        let acc_phi = self
            .builder()
            .build_phi(ptr_type, "acc")
            .map_err(|e| CodegenError::Internal(format!("phi failed: {:?}", e)))?;
        let idx_phi = self
            .builder()
            .build_phi(tm.i64_type(), "idx")
            .map_err(|e| CodegenError::Internal(format!("phi failed: {:?}", e)))?;

        let idx = idx_phi.as_basic_value().into_int_value();
        let done = self
            .builder()
            .build_int_compare(
                inkwell::IntPredicate::SLT,
                idx,
                tm.i64_type().const_zero(),
                "done",
            )
            .map_err(|e| CodegenError::Internal(format!("cmp failed: {:?}", e)))?;
        self.builder()
            .build_conditional_branch(done, loop_exit, loop_body)
            .map_err(|e| CodegenError::Internal(format!("branch failed: {:?}", e)))?;

        self.builder().position_at_end(loop_body);
        let key_i64 = self
            .builder()
            .build_call(key_at_fn, &[map_ptr.into(), idx.into()], "key")
            .map_err(|e| CodegenError::Internal(format!("call failed: {:?}", e)))?
            .try_as_basic_value()
            .basic()
            .ok_or_else(|| CodegenError::Internal("void".to_string()))?
            .into_int_value();
        let val = self
            .builder()
            .build_call(val_at_fn, &[map_ptr.into(), idx.into()], "val")
            .map_err(|e| CodegenError::Internal(format!("call failed: {:?}", e)))?
            .try_as_basic_value()
            .basic()
            .ok_or_else(|| CodegenError::Internal("void".to_string()))?;

        let key_ptr = self.int_to_ptr(key_i64)?;

        // Call closure: fn_ptr(closure, key_ptr, val, acc)
        let acc_val = acc_phi.as_basic_value();
        let new_acc = self
            .builder()
            .build_indirect_call(
                call_fn_type,
                closure_fn_ptr,
                &[fn_ptr.into(), key_ptr.into(), val.into(), acc_val.into()],
                "new_acc",
            )
            .map_err(|e| CodegenError::Internal(format!("indirect call failed: {:?}", e)))?
            .try_as_basic_value()
            .basic()
            .ok_or_else(|| CodegenError::Internal("closure returned void".to_string()))?;

        let new_idx = self
            .builder()
            .build_int_sub(idx, tm.i64_type().const_int(1, false), "new_idx")
            .map_err(|e| CodegenError::Internal(format!("sub failed: {:?}", e)))?;
        self.builder()
            .build_unconditional_branch(loop_header)
            .map_err(|e| CodegenError::Internal(format!("branch failed: {:?}", e)))?;

        acc_phi.add_incoming(&[(&init_ptr, entry_block), (&new_acc, loop_body)]);
        idx_phi.add_incoming(&[(&start_idx, entry_block), (&new_idx, loop_body)]);

        self.builder().position_at_end(loop_exit);
        Ok(Some(acc_phi.as_basic_value()))
    }

    /// Lower `Data.Map.unionWith` -- union of two maps with combining function.
    /// unionWith :: (v -> v -> v) -> Map k v -> Map k v -> Map k v
    fn lower_builtin_map_union_with(
        &mut self,
        fn_expr: &Expr,
        map1_expr: &Expr,
        map2_expr: &Expr,
    ) -> CodegenResult<Option<BasicValueEnum<'ctx>>> {
        let fn_val = self
            .lower_expr(fn_expr)?
            .ok_or_else(|| CodegenError::Internal("map_unionWith: no function".to_string()))?;
        let fn_ptr = self.value_to_ptr(fn_val)?;
        let map1_val = self
            .lower_expr(map1_expr)?
            .ok_or_else(|| CodegenError::Internal("map_unionWith: no map1".to_string()))?;
        let map1_ptr = self.value_to_ptr(map1_val)?;
        let map2_val = self
            .lower_expr(map2_expr)?
            .ok_or_else(|| CodegenError::Internal("map_unionWith: no map2".to_string()))?;
        let map2_ptr = self.value_to_ptr(map2_val)?;

        let tm = self.type_mapper();
        let ptr_type = tm.ptr_type();
        let current_fn = self
            .builder()
            .get_insert_block()
            .and_then(|b| b.get_parent())
            .ok_or_else(|| CodegenError::Internal("no current function".to_string()))?;

        let count_fn = *self
            .functions
            .get(&VarId::new(1000160))
            .ok_or_else(|| CodegenError::Internal("bhc_map_keys_count not declared".to_string()))?;
        let key_at_fn = *self
            .functions
            .get(&VarId::new(1000161))
            .ok_or_else(|| CodegenError::Internal("bhc_map_key_at not declared".to_string()))?;
        let val_at_fn = *self
            .functions
            .get(&VarId::new(1000162))
            .ok_or_else(|| CodegenError::Internal("bhc_map_value_at not declared".to_string()))?;
        let insert_fn = *self
            .functions
            .get(&VarId::new(1000107))
            .ok_or_else(|| CodegenError::Internal("bhc_map_insert not declared".to_string()))?;
        let lookup_fn = *self
            .functions
            .get(&VarId::new(1000105))
            .ok_or_else(|| CodegenError::Internal("bhc_map_lookup not declared".to_string()))?;

        // Start with result = map1 (copy all of map1 first by iterating it)
        // Actually, we can start by copying map1 entries into a fresh map,
        // then iterate map2. But simpler: just start with map1 as the accumulator
        // and iterate map2, since insert overwrites.
        // However, unionWith prefers left values, so we need to check.

        // Get count of map2
        let count2 = self
            .builder()
            .build_call(count_fn, &[map2_ptr.into()], "count2")
            .map_err(|e| CodegenError::Internal(format!("call failed: {:?}", e)))?
            .try_as_basic_value()
            .basic()
            .ok_or_else(|| CodegenError::Internal("void".to_string()))?
            .into_int_value();

        let closure_fn_ptr = self.extract_closure_fn_ptr(fn_ptr)?;
        // 2-arg closure: (v1, v2)
        let call_fn_type =
            ptr_type.fn_type(&[ptr_type.into(), ptr_type.into(), ptr_type.into()], false);

        let loop_header = self
            .llvm_context()
            .append_basic_block(current_fn, "muw_header");
        let loop_body = self
            .llvm_context()
            .append_basic_block(current_fn, "muw_body");
        let found_block = self
            .llvm_context()
            .append_basic_block(current_fn, "muw_found");
        let not_found_block = self
            .llvm_context()
            .append_basic_block(current_fn, "muw_notfound");
        let merge_block = self
            .llvm_context()
            .append_basic_block(current_fn, "muw_merge");
        let loop_exit = self
            .llvm_context()
            .append_basic_block(current_fn, "muw_exit");

        let entry_block = self.builder().get_insert_block().unwrap();
        self.builder()
            .build_unconditional_branch(loop_header)
            .map_err(|e| CodegenError::Internal(format!("branch failed: {:?}", e)))?;

        // Loop header: iterate over map2
        self.builder().position_at_end(loop_header);
        let acc_phi = self
            .builder()
            .build_phi(ptr_type, "acc")
            .map_err(|e| CodegenError::Internal(format!("phi failed: {:?}", e)))?;
        let idx_phi = self
            .builder()
            .build_phi(tm.i64_type(), "idx")
            .map_err(|e| CodegenError::Internal(format!("phi failed: {:?}", e)))?;

        let idx = idx_phi.as_basic_value().into_int_value();
        let done = self
            .builder()
            .build_int_compare(inkwell::IntPredicate::SGE, idx, count2, "done")
            .map_err(|e| CodegenError::Internal(format!("cmp failed: {:?}", e)))?;
        self.builder()
            .build_conditional_branch(done, loop_exit, loop_body)
            .map_err(|e| CodegenError::Internal(format!("branch failed: {:?}", e)))?;

        // Loop body: get key/val from map2, lookup in result (which starts as map1)
        self.builder().position_at_end(loop_body);
        let key_i64 = self
            .builder()
            .build_call(key_at_fn, &[map2_ptr.into(), idx.into()], "key2")
            .map_err(|e| CodegenError::Internal(format!("call failed: {:?}", e)))?
            .try_as_basic_value()
            .basic()
            .ok_or_else(|| CodegenError::Internal("void".to_string()))?
            .into_int_value();
        let val2 = self
            .builder()
            .build_call(val_at_fn, &[map2_ptr.into(), idx.into()], "val2")
            .map_err(|e| CodegenError::Internal(format!("call failed: {:?}", e)))?
            .try_as_basic_value()
            .basic()
            .ok_or_else(|| CodegenError::Internal("void".to_string()))?;

        // Lookup key in accumulator (result map, initially map1)
        let acc_ptr = acc_phi.as_basic_value().into_pointer_value();
        let found_val = self
            .builder()
            .build_call(lookup_fn, &[key_i64.into(), acc_ptr.into()], "lookup")
            .map_err(|e| CodegenError::Internal(format!("call failed: {:?}", e)))?
            .try_as_basic_value()
            .basic()
            .ok_or_else(|| CodegenError::Internal("void".to_string()))?
            .into_pointer_value();

        let is_null = self
            .builder()
            .build_is_null(found_val, "is_null")
            .map_err(|e| CodegenError::Internal(format!("is_null failed: {:?}", e)))?;
        self.builder()
            .build_conditional_branch(is_null, not_found_block, found_block)
            .map_err(|e| CodegenError::Internal(format!("branch failed: {:?}", e)))?;

        // Found: call closure(found_val, val2), insert result
        self.builder().position_at_end(found_block);
        let val2_ptr = self.value_to_ptr(val2)?;
        let combined = self
            .builder()
            .build_indirect_call(
                call_fn_type,
                closure_fn_ptr,
                &[fn_ptr.into(), found_val.into(), val2_ptr.into()],
                "combined",
            )
            .map_err(|e| CodegenError::Internal(format!("indirect call failed: {:?}", e)))?
            .try_as_basic_value()
            .basic()
            .ok_or_else(|| CodegenError::Internal("closure returned void".to_string()))?;
        let combined_ptr = self.value_to_ptr(combined)?;
        let new_map_found = self
            .builder()
            .build_call(
                insert_fn,
                &[key_i64.into(), combined_ptr.into(), acc_ptr.into()],
                "ins_found",
            )
            .map_err(|e| CodegenError::Internal(format!("call failed: {:?}", e)))?
            .try_as_basic_value()
            .basic()
            .ok_or_else(|| CodegenError::Internal("void".to_string()))?;
        self.builder()
            .build_unconditional_branch(merge_block)
            .map_err(|e| CodegenError::Internal(format!("branch failed: {:?}", e)))?;

        // Not found: just insert (key, val2)
        self.builder().position_at_end(not_found_block);
        let val2_ptr_nf = self.value_to_ptr(val2)?;
        let new_map_nf = self
            .builder()
            .build_call(
                insert_fn,
                &[key_i64.into(), val2_ptr_nf.into(), acc_ptr.into()],
                "ins_nf",
            )
            .map_err(|e| CodegenError::Internal(format!("call failed: {:?}", e)))?
            .try_as_basic_value()
            .basic()
            .ok_or_else(|| CodegenError::Internal("void".to_string()))?;
        self.builder()
            .build_unconditional_branch(merge_block)
            .map_err(|e| CodegenError::Internal(format!("branch failed: {:?}", e)))?;

        // Merge block
        self.builder().position_at_end(merge_block);
        let merge_phi = self
            .builder()
            .build_phi(ptr_type, "merge_map")
            .map_err(|e| CodegenError::Internal(format!("phi failed: {:?}", e)))?;
        merge_phi.add_incoming(&[
            (&new_map_found, found_block),
            (&new_map_nf, not_found_block),
        ]);

        let new_idx = self
            .builder()
            .build_int_add(idx, tm.i64_type().const_int(1, false), "new_idx")
            .map_err(|e| CodegenError::Internal(format!("add failed: {:?}", e)))?;
        self.builder()
            .build_unconditional_branch(loop_header)
            .map_err(|e| CodegenError::Internal(format!("branch failed: {:?}", e)))?;

        acc_phi.add_incoming(&[
            (&map1_ptr, entry_block),
            (&merge_phi.as_basic_value(), merge_block),
        ]);
        idx_phi.add_incoming(&[
            (&tm.i64_type().const_zero(), entry_block),
            (&new_idx, merge_block),
        ]);

        self.builder().position_at_end(loop_exit);
        Ok(Some(acc_phi.as_basic_value()))
    }

    /// Lower `Data.Map.intersectionWith` -- intersection with combining function.
    /// intersectionWith :: (a -> b -> c) -> Map k a -> Map k b -> Map k c
    fn lower_builtin_map_intersection_with(
        &mut self,
        fn_expr: &Expr,
        map1_expr: &Expr,
        map2_expr: &Expr,
    ) -> CodegenResult<Option<BasicValueEnum<'ctx>>> {
        let fn_val = self.lower_expr(fn_expr)?.ok_or_else(|| {
            CodegenError::Internal("map_intersectionWith: no function".to_string())
        })?;
        let fn_ptr = self.value_to_ptr(fn_val)?;
        let map1_val = self
            .lower_expr(map1_expr)?
            .ok_or_else(|| CodegenError::Internal("map_intersectionWith: no map1".to_string()))?;
        let map1_ptr = self.value_to_ptr(map1_val)?;
        let map2_val = self
            .lower_expr(map2_expr)?
            .ok_or_else(|| CodegenError::Internal("map_intersectionWith: no map2".to_string()))?;
        let map2_ptr = self.value_to_ptr(map2_val)?;

        let tm = self.type_mapper();
        let ptr_type = tm.ptr_type();
        let current_fn = self
            .builder()
            .get_insert_block()
            .and_then(|b| b.get_parent())
            .ok_or_else(|| CodegenError::Internal("no current function".to_string()))?;

        let count_fn = *self
            .functions
            .get(&VarId::new(1000160))
            .ok_or_else(|| CodegenError::Internal("bhc_map_keys_count not declared".to_string()))?;
        let key_at_fn = *self
            .functions
            .get(&VarId::new(1000161))
            .ok_or_else(|| CodegenError::Internal("bhc_map_key_at not declared".to_string()))?;
        let val_at_fn = *self
            .functions
            .get(&VarId::new(1000162))
            .ok_or_else(|| CodegenError::Internal("bhc_map_value_at not declared".to_string()))?;
        let insert_fn = *self
            .functions
            .get(&VarId::new(1000107))
            .ok_or_else(|| CodegenError::Internal("bhc_map_insert not declared".to_string()))?;
        let lookup_fn = *self
            .functions
            .get(&VarId::new(1000105))
            .ok_or_else(|| CodegenError::Internal("bhc_map_lookup not declared".to_string()))?;
        let empty_fn = self
            .functions
            .get(&VarId::new(1000100))
            .ok_or_else(|| CodegenError::Internal("bhc_map_empty not declared".to_string()))?;

        // Start with empty result
        let empty_map = self
            .builder()
            .build_call(*empty_fn, &[], "empty")
            .map_err(|e| CodegenError::Internal(format!("call failed: {:?}", e)))?
            .try_as_basic_value()
            .basic()
            .ok_or_else(|| CodegenError::Internal("void".to_string()))?;

        // Get count of map1
        let count1 = self
            .builder()
            .build_call(count_fn, &[map1_ptr.into()], "count1")
            .map_err(|e| CodegenError::Internal(format!("call failed: {:?}", e)))?
            .try_as_basic_value()
            .basic()
            .ok_or_else(|| CodegenError::Internal("void".to_string()))?
            .into_int_value();

        let closure_fn_ptr = self.extract_closure_fn_ptr(fn_ptr)?;
        let call_fn_type =
            ptr_type.fn_type(&[ptr_type.into(), ptr_type.into(), ptr_type.into()], false);

        let loop_header = self
            .llvm_context()
            .append_basic_block(current_fn, "miw_header");
        let loop_body = self
            .llvm_context()
            .append_basic_block(current_fn, "miw_body");
        let found_block = self
            .llvm_context()
            .append_basic_block(current_fn, "miw_found");
        let skip_block = self
            .llvm_context()
            .append_basic_block(current_fn, "miw_skip");
        let loop_exit = self
            .llvm_context()
            .append_basic_block(current_fn, "miw_exit");

        let entry_block = self.builder().get_insert_block().unwrap();
        self.builder()
            .build_unconditional_branch(loop_header)
            .map_err(|e| CodegenError::Internal(format!("branch failed: {:?}", e)))?;

        // Loop header: iterate over map1
        self.builder().position_at_end(loop_header);
        let acc_phi = self
            .builder()
            .build_phi(ptr_type, "acc")
            .map_err(|e| CodegenError::Internal(format!("phi failed: {:?}", e)))?;
        let idx_phi = self
            .builder()
            .build_phi(tm.i64_type(), "idx")
            .map_err(|e| CodegenError::Internal(format!("phi failed: {:?}", e)))?;

        let idx = idx_phi.as_basic_value().into_int_value();
        let done = self
            .builder()
            .build_int_compare(inkwell::IntPredicate::SGE, idx, count1, "done")
            .map_err(|e| CodegenError::Internal(format!("cmp failed: {:?}", e)))?;
        self.builder()
            .build_conditional_branch(done, loop_exit, loop_body)
            .map_err(|e| CodegenError::Internal(format!("branch failed: {:?}", e)))?;

        // Loop body: get key/val from map1, lookup in map2
        self.builder().position_at_end(loop_body);
        let key_i64 = self
            .builder()
            .build_call(key_at_fn, &[map1_ptr.into(), idx.into()], "key1")
            .map_err(|e| CodegenError::Internal(format!("call failed: {:?}", e)))?
            .try_as_basic_value()
            .basic()
            .ok_or_else(|| CodegenError::Internal("void".to_string()))?
            .into_int_value();
        let val1 = self
            .builder()
            .build_call(val_at_fn, &[map1_ptr.into(), idx.into()], "val1")
            .map_err(|e| CodegenError::Internal(format!("call failed: {:?}", e)))?
            .try_as_basic_value()
            .basic()
            .ok_or_else(|| CodegenError::Internal("void".to_string()))?;

        // Lookup key in map2
        let found_val = self
            .builder()
            .build_call(lookup_fn, &[key_i64.into(), map2_ptr.into()], "lookup2")
            .map_err(|e| CodegenError::Internal(format!("call failed: {:?}", e)))?
            .try_as_basic_value()
            .basic()
            .ok_or_else(|| CodegenError::Internal("void".to_string()))?
            .into_pointer_value();

        let is_null = self
            .builder()
            .build_is_null(found_val, "is_null")
            .map_err(|e| CodegenError::Internal(format!("is_null failed: {:?}", e)))?;
        self.builder()
            .build_conditional_branch(is_null, skip_block, found_block)
            .map_err(|e| CodegenError::Internal(format!("branch failed: {:?}", e)))?;

        // Found: call closure(val1, found_val), insert into result
        self.builder().position_at_end(found_block);
        let val1_ptr = self.value_to_ptr(val1)?;
        let combined = self
            .builder()
            .build_indirect_call(
                call_fn_type,
                closure_fn_ptr,
                &[fn_ptr.into(), val1_ptr.into(), found_val.into()],
                "combined",
            )
            .map_err(|e| CodegenError::Internal(format!("indirect call failed: {:?}", e)))?
            .try_as_basic_value()
            .basic()
            .ok_or_else(|| CodegenError::Internal("closure returned void".to_string()))?;
        let combined_ptr = self.value_to_ptr(combined)?;
        let acc_ptr_found = acc_phi.as_basic_value().into_pointer_value();
        let new_map_found = self
            .builder()
            .build_call(
                insert_fn,
                &[key_i64.into(), combined_ptr.into(), acc_ptr_found.into()],
                "ins_found",
            )
            .map_err(|e| CodegenError::Internal(format!("call failed: {:?}", e)))?
            .try_as_basic_value()
            .basic()
            .ok_or_else(|| CodegenError::Internal("void".to_string()))?;
        self.builder()
            .build_unconditional_branch(skip_block)
            .map_err(|e| CodegenError::Internal(format!("branch failed: {:?}", e)))?;

        // Skip/merge block
        self.builder().position_at_end(skip_block);
        let merge_phi = self
            .builder()
            .build_phi(ptr_type, "merge_acc")
            .map_err(|e| CodegenError::Internal(format!("phi failed: {:?}", e)))?;
        merge_phi.add_incoming(&[
            (&new_map_found, found_block),
            (&acc_phi.as_basic_value(), loop_body),
        ]);

        let new_idx = self
            .builder()
            .build_int_add(idx, tm.i64_type().const_int(1, false), "new_idx")
            .map_err(|e| CodegenError::Internal(format!("add failed: {:?}", e)))?;
        self.builder()
            .build_unconditional_branch(loop_header)
            .map_err(|e| CodegenError::Internal(format!("branch failed: {:?}", e)))?;

        acc_phi.add_incoming(&[
            (&empty_map, entry_block),
            (&merge_phi.as_basic_value(), skip_block),
        ]);
        idx_phi.add_incoming(&[
            (&tm.i64_type().const_zero(), entry_block),
            (&new_idx, skip_block),
        ]);

        self.builder().position_at_end(loop_exit);
        Ok(Some(acc_phi.as_basic_value()))
    }

    /// Lower `Data.Map.differenceWith` -- difference with combining function.
    /// differenceWith :: (a -> b -> Maybe a) -> Map k a -> Map k b -> Map k a
    fn lower_builtin_map_difference_with(
        &mut self,
        fn_expr: &Expr,
        map1_expr: &Expr,
        map2_expr: &Expr,
    ) -> CodegenResult<Option<BasicValueEnum<'ctx>>> {
        let fn_val = self
            .lower_expr(fn_expr)?
            .ok_or_else(|| CodegenError::Internal("map_differenceWith: no function".to_string()))?;
        let fn_ptr = self.value_to_ptr(fn_val)?;
        let map1_val = self
            .lower_expr(map1_expr)?
            .ok_or_else(|| CodegenError::Internal("map_differenceWith: no map1".to_string()))?;
        let map1_ptr = self.value_to_ptr(map1_val)?;
        let map2_val = self
            .lower_expr(map2_expr)?
            .ok_or_else(|| CodegenError::Internal("map_differenceWith: no map2".to_string()))?;
        let map2_ptr = self.value_to_ptr(map2_val)?;

        let tm = self.type_mapper();
        let ptr_type = tm.ptr_type();
        let current_fn = self
            .builder()
            .get_insert_block()
            .and_then(|b| b.get_parent())
            .ok_or_else(|| CodegenError::Internal("no current function".to_string()))?;

        let count_fn = *self
            .functions
            .get(&VarId::new(1000160))
            .ok_or_else(|| CodegenError::Internal("bhc_map_keys_count not declared".to_string()))?;
        let key_at_fn = *self
            .functions
            .get(&VarId::new(1000161))
            .ok_or_else(|| CodegenError::Internal("bhc_map_key_at not declared".to_string()))?;
        let val_at_fn = *self
            .functions
            .get(&VarId::new(1000162))
            .ok_or_else(|| CodegenError::Internal("bhc_map_value_at not declared".to_string()))?;
        let insert_fn = *self
            .functions
            .get(&VarId::new(1000107))
            .ok_or_else(|| CodegenError::Internal("bhc_map_insert not declared".to_string()))?;
        let lookup_fn = *self
            .functions
            .get(&VarId::new(1000105))
            .ok_or_else(|| CodegenError::Internal("bhc_map_lookup not declared".to_string()))?;
        let empty_fn = self
            .functions
            .get(&VarId::new(1000100))
            .ok_or_else(|| CodegenError::Internal("bhc_map_empty not declared".to_string()))?;

        // Start with empty result
        let empty_map = self
            .builder()
            .build_call(*empty_fn, &[], "empty")
            .map_err(|e| CodegenError::Internal(format!("call failed: {:?}", e)))?
            .try_as_basic_value()
            .basic()
            .ok_or_else(|| CodegenError::Internal("void".to_string()))?;

        // Get count of map1
        let count1 = self
            .builder()
            .build_call(count_fn, &[map1_ptr.into()], "count1")
            .map_err(|e| CodegenError::Internal(format!("call failed: {:?}", e)))?
            .try_as_basic_value()
            .basic()
            .ok_or_else(|| CodegenError::Internal("void".to_string()))?
            .into_int_value();

        let closure_fn_ptr = self.extract_closure_fn_ptr(fn_ptr)?;
        let call_fn_type =
            ptr_type.fn_type(&[ptr_type.into(), ptr_type.into(), ptr_type.into()], false);

        let loop_header = self
            .llvm_context()
            .append_basic_block(current_fn, "mdw_header");
        let loop_body = self
            .llvm_context()
            .append_basic_block(current_fn, "mdw_body");
        let found_block = self
            .llvm_context()
            .append_basic_block(current_fn, "mdw_found");
        let just_block = self
            .llvm_context()
            .append_basic_block(current_fn, "mdw_just");
        let not_found_block = self
            .llvm_context()
            .append_basic_block(current_fn, "mdw_notfound");
        let merge_block = self
            .llvm_context()
            .append_basic_block(current_fn, "mdw_merge");
        let loop_exit = self
            .llvm_context()
            .append_basic_block(current_fn, "mdw_exit");

        let entry_block = self.builder().get_insert_block().unwrap();
        self.builder()
            .build_unconditional_branch(loop_header)
            .map_err(|e| CodegenError::Internal(format!("branch failed: {:?}", e)))?;

        // Loop header: iterate over map1
        self.builder().position_at_end(loop_header);
        let acc_phi = self
            .builder()
            .build_phi(ptr_type, "acc")
            .map_err(|e| CodegenError::Internal(format!("phi failed: {:?}", e)))?;
        let idx_phi = self
            .builder()
            .build_phi(tm.i64_type(), "idx")
            .map_err(|e| CodegenError::Internal(format!("phi failed: {:?}", e)))?;

        let idx = idx_phi.as_basic_value().into_int_value();
        let done = self
            .builder()
            .build_int_compare(inkwell::IntPredicate::SGE, idx, count1, "done")
            .map_err(|e| CodegenError::Internal(format!("cmp failed: {:?}", e)))?;
        self.builder()
            .build_conditional_branch(done, loop_exit, loop_body)
            .map_err(|e| CodegenError::Internal(format!("branch failed: {:?}", e)))?;

        // Loop body: get key/val from map1, lookup in map2
        self.builder().position_at_end(loop_body);
        let key_i64 = self
            .builder()
            .build_call(key_at_fn, &[map1_ptr.into(), idx.into()], "key1")
            .map_err(|e| CodegenError::Internal(format!("call failed: {:?}", e)))?
            .try_as_basic_value()
            .basic()
            .ok_or_else(|| CodegenError::Internal("void".to_string()))?
            .into_int_value();
        let val1 = self
            .builder()
            .build_call(val_at_fn, &[map1_ptr.into(), idx.into()], "val1")
            .map_err(|e| CodegenError::Internal(format!("call failed: {:?}", e)))?
            .try_as_basic_value()
            .basic()
            .ok_or_else(|| CodegenError::Internal("void".to_string()))?;

        // Lookup key in map2
        let found_val = self
            .builder()
            .build_call(lookup_fn, &[key_i64.into(), map2_ptr.into()], "lookup2")
            .map_err(|e| CodegenError::Internal(format!("call failed: {:?}", e)))?
            .try_as_basic_value()
            .basic()
            .ok_or_else(|| CodegenError::Internal("void".to_string()))?
            .into_pointer_value();

        let is_null = self
            .builder()
            .build_is_null(found_val, "is_null")
            .map_err(|e| CodegenError::Internal(format!("is_null failed: {:?}", e)))?;
        self.builder()
            .build_conditional_branch(is_null, not_found_block, found_block)
            .map_err(|e| CodegenError::Internal(format!("branch failed: {:?}", e)))?;

        // Found in map2: call closure(val1, val2) -> Maybe a
        self.builder().position_at_end(found_block);
        let val1_ptr = self.value_to_ptr(val1)?;
        let maybe_result = self
            .builder()
            .build_indirect_call(
                call_fn_type,
                closure_fn_ptr,
                &[fn_ptr.into(), val1_ptr.into(), found_val.into()],
                "maybe_result",
            )
            .map_err(|e| CodegenError::Internal(format!("indirect call failed: {:?}", e)))?
            .try_as_basic_value()
            .basic()
            .ok_or_else(|| CodegenError::Internal("closure returned void".to_string()))?;

        // Check Maybe tag: 0 = Nothing, 1 = Just
        let maybe_ptr = self.value_to_ptr(maybe_result)?;
        let maybe_tag = self.extract_adt_tag(maybe_ptr)?;
        let is_just = self
            .builder()
            .build_int_compare(
                inkwell::IntPredicate::NE,
                maybe_tag,
                tm.i64_type().const_zero(),
                "is_just",
            )
            .map_err(|e| CodegenError::Internal(format!("cmp failed: {:?}", e)))?;
        self.builder()
            .build_conditional_branch(is_just, just_block, merge_block)
            .map_err(|e| CodegenError::Internal(format!("branch failed: {:?}", e)))?;

        // Just: extract value from Just, insert into result
        self.builder().position_at_end(just_block);
        let just_val = self.extract_adt_field(maybe_ptr, 1, 0)?;
        let acc_ptr_just = acc_phi.as_basic_value().into_pointer_value();
        let new_map_just = self
            .builder()
            .build_call(
                insert_fn,
                &[key_i64.into(), just_val.into(), acc_ptr_just.into()],
                "ins_just",
            )
            .map_err(|e| CodegenError::Internal(format!("call failed: {:?}", e)))?
            .try_as_basic_value()
            .basic()
            .ok_or_else(|| CodegenError::Internal("void".to_string()))?;
        self.builder()
            .build_unconditional_branch(merge_block)
            .map_err(|e| CodegenError::Internal(format!("branch failed: {:?}", e)))?;

        // Not found in map2: insert (key, val1) directly
        self.builder().position_at_end(not_found_block);
        let val1_ptr_nf = self.value_to_ptr(val1)?;
        let acc_ptr_nf = acc_phi.as_basic_value().into_pointer_value();
        let new_map_nf = self
            .builder()
            .build_call(
                insert_fn,
                &[key_i64.into(), val1_ptr_nf.into(), acc_ptr_nf.into()],
                "ins_nf",
            )
            .map_err(|e| CodegenError::Internal(format!("call failed: {:?}", e)))?
            .try_as_basic_value()
            .basic()
            .ok_or_else(|| CodegenError::Internal("void".to_string()))?;
        self.builder()
            .build_unconditional_branch(merge_block)
            .map_err(|e| CodegenError::Internal(format!("branch failed: {:?}", e)))?;

        // Merge block: phi for the three incoming edges (just, nothing/found, not_found)
        self.builder().position_at_end(merge_block);
        let merge_phi = self
            .builder()
            .build_phi(ptr_type, "merge_acc")
            .map_err(|e| CodegenError::Internal(format!("phi failed: {:?}", e)))?;
        merge_phi.add_incoming(&[
            (&new_map_just, just_block),
            (&acc_phi.as_basic_value(), found_block), // Nothing case: no change
            (&new_map_nf, not_found_block),
        ]);

        let new_idx = self
            .builder()
            .build_int_add(idx, tm.i64_type().const_int(1, false), "new_idx")
            .map_err(|e| CodegenError::Internal(format!("add failed: {:?}", e)))?;
        self.builder()
            .build_unconditional_branch(loop_header)
            .map_err(|e| CodegenError::Internal(format!("branch failed: {:?}", e)))?;

        acc_phi.add_incoming(&[
            (&empty_map, entry_block),
            (&merge_phi.as_basic_value(), merge_block),
        ]);
        idx_phi.add_incoming(&[
            (&tm.i64_type().const_zero(), entry_block),
            (&new_idx, merge_block),
        ]);

        self.builder().position_at_end(loop_exit);
        Ok(Some(acc_phi.as_basic_value()))
    }

    /// Lower `Data.Map.insertWith` -- insert with combining function.
    /// insertWith :: (v -> v -> v) -> k -> v -> Map k v -> Map k v
    fn lower_builtin_map_insert_with(
        &mut self,
        fn_expr: &Expr,
        key_expr: &Expr,
        val_expr: &Expr,
        map_expr: &Expr,
    ) -> CodegenResult<Option<BasicValueEnum<'ctx>>> {
        let fn_val = self
            .lower_expr(fn_expr)?
            .ok_or_else(|| CodegenError::Internal("map_insertWith: no function".to_string()))?;
        let fn_ptr = self.value_to_ptr(fn_val)?;
        let key_val = self
            .lower_expr(key_expr)?
            .ok_or_else(|| CodegenError::Internal("map_insertWith: no key".to_string()))?;
        let new_val = self
            .lower_expr(val_expr)?
            .ok_or_else(|| CodegenError::Internal("map_insertWith: no value".to_string()))?;
        let map_val = self
            .lower_expr(map_expr)?
            .ok_or_else(|| CodegenError::Internal("map_insertWith: no map".to_string()))?;

        let key_int = self.coerce_to_int(key_val)?;
        let new_ptr = self.value_to_ptr(new_val)?;
        let map_ptr = self.value_to_ptr(map_val)?;

        let tm = self.type_mapper();
        let ptr_type = tm.ptr_type();
        let current_fn = self
            .builder()
            .get_insert_block()
            .and_then(|b| b.get_parent())
            .ok_or_else(|| CodegenError::Internal("no current function".to_string()))?;

        let insert_fn = *self
            .functions
            .get(&VarId::new(1000107))
            .ok_or_else(|| CodegenError::Internal("bhc_map_insert not declared".to_string()))?;
        let lookup_fn = *self
            .functions
            .get(&VarId::new(1000105))
            .ok_or_else(|| CodegenError::Internal("bhc_map_lookup not declared".to_string()))?;

        // Lookup key in map
        let found_val = self
            .builder()
            .build_call(lookup_fn, &[key_int.into(), map_ptr.into()], "lookup")
            .map_err(|e| CodegenError::Internal(format!("call failed: {:?}", e)))?
            .try_as_basic_value()
            .basic()
            .ok_or_else(|| CodegenError::Internal("void".to_string()))?
            .into_pointer_value();

        let is_null = self
            .builder()
            .build_is_null(found_val, "is_null")
            .map_err(|e| CodegenError::Internal(format!("is_null failed: {:?}", e)))?;

        let found_block = self
            .llvm_context()
            .append_basic_block(current_fn, "iw_found");
        let not_found_block = self
            .llvm_context()
            .append_basic_block(current_fn, "iw_notfound");
        let merge_block = self
            .llvm_context()
            .append_basic_block(current_fn, "iw_merge");

        self.builder()
            .build_conditional_branch(is_null, not_found_block, found_block)
            .map_err(|e| CodegenError::Internal(format!("branch failed: {:?}", e)))?;

        // Found: call closure(new_val, old_val), insert result
        self.builder().position_at_end(found_block);
        let closure_fn_ptr = self.extract_closure_fn_ptr(fn_ptr)?;
        let call_fn_type =
            ptr_type.fn_type(&[ptr_type.into(), ptr_type.into(), ptr_type.into()], false);
        let combined = self
            .builder()
            .build_indirect_call(
                call_fn_type,
                closure_fn_ptr,
                &[fn_ptr.into(), new_ptr.into(), found_val.into()],
                "combined",
            )
            .map_err(|e| CodegenError::Internal(format!("indirect call failed: {:?}", e)))?
            .try_as_basic_value()
            .basic()
            .ok_or_else(|| CodegenError::Internal("closure returned void".to_string()))?;
        let combined_ptr = self.value_to_ptr(combined)?;
        let new_map_found = self
            .builder()
            .build_call(
                insert_fn,
                &[key_int.into(), combined_ptr.into(), map_ptr.into()],
                "ins_found",
            )
            .map_err(|e| CodegenError::Internal(format!("call failed: {:?}", e)))?
            .try_as_basic_value()
            .basic()
            .ok_or_else(|| CodegenError::Internal("void".to_string()))?;
        self.builder()
            .build_unconditional_branch(merge_block)
            .map_err(|e| CodegenError::Internal(format!("branch failed: {:?}", e)))?;

        // Not found: insert (key, new_val) directly
        self.builder().position_at_end(not_found_block);
        let new_map_nf = self
            .builder()
            .build_call(
                insert_fn,
                &[key_int.into(), new_ptr.into(), map_ptr.into()],
                "ins_nf",
            )
            .map_err(|e| CodegenError::Internal(format!("call failed: {:?}", e)))?
            .try_as_basic_value()
            .basic()
            .ok_or_else(|| CodegenError::Internal("void".to_string()))?;
        self.builder()
            .build_unconditional_branch(merge_block)
            .map_err(|e| CodegenError::Internal(format!("branch failed: {:?}", e)))?;

        // Merge
        self.builder().position_at_end(merge_block);
        let result_phi = self
            .builder()
            .build_phi(ptr_type, "result")
            .map_err(|e| CodegenError::Internal(format!("phi failed: {:?}", e)))?;
        result_phi.add_incoming(&[
            (&new_map_found, found_block),
            (&new_map_nf, not_found_block),
        ]);

        Ok(Some(result_phi.as_basic_value()))
    }

    /// Lower `Data.Map.adjust` -- adjust value at key with function.
    /// adjust :: (v -> v) -> k -> Map k v -> Map k v
    fn lower_builtin_map_adjust(
        &mut self,
        fn_expr: &Expr,
        key_expr: &Expr,
        map_expr: &Expr,
    ) -> CodegenResult<Option<BasicValueEnum<'ctx>>> {
        let fn_val = self
            .lower_expr(fn_expr)?
            .ok_or_else(|| CodegenError::Internal("map_adjust: no function".to_string()))?;
        let fn_ptr = self.value_to_ptr(fn_val)?;
        let key_val = self
            .lower_expr(key_expr)?
            .ok_or_else(|| CodegenError::Internal("map_adjust: no key".to_string()))?;
        let map_val = self
            .lower_expr(map_expr)?
            .ok_or_else(|| CodegenError::Internal("map_adjust: no map".to_string()))?;

        let key_int = self.coerce_to_int(key_val)?;
        let map_ptr = self.value_to_ptr(map_val)?;

        let tm = self.type_mapper();
        let ptr_type = tm.ptr_type();
        let current_fn = self
            .builder()
            .get_insert_block()
            .and_then(|b| b.get_parent())
            .ok_or_else(|| CodegenError::Internal("no current function".to_string()))?;

        let insert_fn = *self
            .functions
            .get(&VarId::new(1000107))
            .ok_or_else(|| CodegenError::Internal("bhc_map_insert not declared".to_string()))?;
        let delete_fn = *self
            .functions
            .get(&VarId::new(1000108))
            .ok_or_else(|| CodegenError::Internal("bhc_map_delete not declared".to_string()))?;
        let lookup_fn = *self
            .functions
            .get(&VarId::new(1000105))
            .ok_or_else(|| CodegenError::Internal("bhc_map_lookup not declared".to_string()))?;

        // Lookup key in map
        let found_val = self
            .builder()
            .build_call(lookup_fn, &[key_int.into(), map_ptr.into()], "lookup")
            .map_err(|e| CodegenError::Internal(format!("call failed: {:?}", e)))?
            .try_as_basic_value()
            .basic()
            .ok_or_else(|| CodegenError::Internal("void".to_string()))?
            .into_pointer_value();

        let is_null = self
            .builder()
            .build_is_null(found_val, "is_null")
            .map_err(|e| CodegenError::Internal(format!("is_null failed: {:?}", e)))?;

        let found_block = self
            .llvm_context()
            .append_basic_block(current_fn, "adj_found");
        let merge_block = self
            .llvm_context()
            .append_basic_block(current_fn, "adj_merge");

        self.builder()
            .build_conditional_branch(is_null, merge_block, found_block)
            .map_err(|e| CodegenError::Internal(format!("branch failed: {:?}", e)))?;

        let entry_block = self.builder().get_insert_block().unwrap();

        // Found: call closure(old_val), delete key, insert (key, result)
        self.builder().position_at_end(found_block);
        let closure_fn_ptr = self.extract_closure_fn_ptr(fn_ptr)?;
        let call_fn_type = ptr_type.fn_type(&[ptr_type.into(), ptr_type.into()], false);
        let adjusted = self
            .builder()
            .build_indirect_call(
                call_fn_type,
                closure_fn_ptr,
                &[fn_ptr.into(), found_val.into()],
                "adjusted",
            )
            .map_err(|e| CodegenError::Internal(format!("indirect call failed: {:?}", e)))?
            .try_as_basic_value()
            .basic()
            .ok_or_else(|| CodegenError::Internal("closure returned void".to_string()))?;
        let adjusted_ptr = self.value_to_ptr(adjusted)?;

        // Delete old key, then insert with new value
        let deleted_map = self
            .builder()
            .build_call(delete_fn, &[key_int.into(), map_ptr.into()], "deleted")
            .map_err(|e| CodegenError::Internal(format!("call failed: {:?}", e)))?
            .try_as_basic_value()
            .basic()
            .ok_or_else(|| CodegenError::Internal("void".to_string()))?;
        let deleted_ptr = self.value_to_ptr(deleted_map)?;
        let new_map = self
            .builder()
            .build_call(
                insert_fn,
                &[key_int.into(), adjusted_ptr.into(), deleted_ptr.into()],
                "ins_adj",
            )
            .map_err(|e| CodegenError::Internal(format!("call failed: {:?}", e)))?
            .try_as_basic_value()
            .basic()
            .ok_or_else(|| CodegenError::Internal("void".to_string()))?;
        self.builder()
            .build_unconditional_branch(merge_block)
            .map_err(|e| CodegenError::Internal(format!("branch failed: {:?}", e)))?;

        // Merge: if not found, return original map; if found, return adjusted map
        self.builder().position_at_end(merge_block);
        let result_phi = self
            .builder()
            .build_phi(ptr_type, "result")
            .map_err(|e| CodegenError::Internal(format!("phi failed: {:?}", e)))?;
        result_phi.add_incoming(&[(&map_ptr, entry_block), (&new_map, found_block)]);

        Ok(Some(result_phi.as_basic_value()))
    }

    /// Lower `Data.Map.mapKeys` -- apply function to all keys, building new map.
    /// mapKeys :: (k -> k') -> Map k v -> Map k' v
    fn lower_builtin_map_map_keys(
        &mut self,
        fn_expr: &Expr,
        map_expr: &Expr,
    ) -> CodegenResult<Option<BasicValueEnum<'ctx>>> {
        let fn_val = self
            .lower_expr(fn_expr)?
            .ok_or_else(|| CodegenError::Internal("map_mapKeys: no function".to_string()))?;
        let fn_ptr = self.value_to_ptr(fn_val)?;
        let map_val = self
            .lower_expr(map_expr)?
            .ok_or_else(|| CodegenError::Internal("map_mapKeys: no map".to_string()))?;
        let map_ptr = self.value_to_ptr(map_val)?;

        let tm = self.type_mapper();
        let ptr_type = tm.ptr_type();
        let current_fn = self
            .builder()
            .get_insert_block()
            .and_then(|b| b.get_parent())
            .ok_or_else(|| CodegenError::Internal("no current function".to_string()))?;

        let count_fn = self
            .functions
            .get(&VarId::new(1000160))
            .ok_or_else(|| CodegenError::Internal("bhc_map_keys_count not declared".to_string()))?;
        let count = self
            .builder()
            .build_call(*count_fn, &[map_ptr.into()], "count")
            .map_err(|e| CodegenError::Internal(format!("call failed: {:?}", e)))?
            .try_as_basic_value()
            .basic()
            .ok_or_else(|| CodegenError::Internal("void".to_string()))?
            .into_int_value();

        let key_at_fn = *self
            .functions
            .get(&VarId::new(1000161))
            .ok_or_else(|| CodegenError::Internal("bhc_map_key_at not declared".to_string()))?;
        let val_at_fn = *self
            .functions
            .get(&VarId::new(1000162))
            .ok_or_else(|| CodegenError::Internal("bhc_map_value_at not declared".to_string()))?;
        let insert_fn = *self
            .functions
            .get(&VarId::new(1000107))
            .ok_or_else(|| CodegenError::Internal("bhc_map_insert not declared".to_string()))?;

        let empty_fn = self
            .functions
            .get(&VarId::new(1000100))
            .ok_or_else(|| CodegenError::Internal("bhc_map_empty not declared".to_string()))?;
        let empty_map = self
            .builder()
            .build_call(*empty_fn, &[], "empty")
            .map_err(|e| CodegenError::Internal(format!("call failed: {:?}", e)))?
            .try_as_basic_value()
            .basic()
            .ok_or_else(|| CodegenError::Internal("void".to_string()))?;

        // 1-arg closure: (key) -> new_key
        let closure_fn_ptr = self.extract_closure_fn_ptr(fn_ptr)?;
        let call_fn_type = ptr_type.fn_type(&[ptr_type.into(), ptr_type.into()], false);

        let loop_header = self
            .llvm_context()
            .append_basic_block(current_fn, "mmk_header");
        let loop_body = self
            .llvm_context()
            .append_basic_block(current_fn, "mmk_body");
        let loop_exit = self
            .llvm_context()
            .append_basic_block(current_fn, "mmk_exit");

        let entry_block = self.builder().get_insert_block().unwrap();
        self.builder()
            .build_unconditional_branch(loop_header)
            .map_err(|e| CodegenError::Internal(format!("branch failed: {:?}", e)))?;

        self.builder().position_at_end(loop_header);
        let acc_phi = self
            .builder()
            .build_phi(ptr_type, "acc")
            .map_err(|e| CodegenError::Internal(format!("phi failed: {:?}", e)))?;
        let idx_phi = self
            .builder()
            .build_phi(tm.i64_type(), "idx")
            .map_err(|e| CodegenError::Internal(format!("phi failed: {:?}", e)))?;

        let idx = idx_phi.as_basic_value().into_int_value();
        let done = self
            .builder()
            .build_int_compare(inkwell::IntPredicate::SGE, idx, count, "done")
            .map_err(|e| CodegenError::Internal(format!("cmp failed: {:?}", e)))?;
        self.builder()
            .build_conditional_branch(done, loop_exit, loop_body)
            .map_err(|e| CodegenError::Internal(format!("branch failed: {:?}", e)))?;

        self.builder().position_at_end(loop_body);
        let key_i64 = self
            .builder()
            .build_call(key_at_fn, &[map_ptr.into(), idx.into()], "key")
            .map_err(|e| CodegenError::Internal(format!("call failed: {:?}", e)))?
            .try_as_basic_value()
            .basic()
            .ok_or_else(|| CodegenError::Internal("void".to_string()))?
            .into_int_value();
        let val = self
            .builder()
            .build_call(val_at_fn, &[map_ptr.into(), idx.into()], "val")
            .map_err(|e| CodegenError::Internal(format!("call failed: {:?}", e)))?
            .try_as_basic_value()
            .basic()
            .ok_or_else(|| CodegenError::Internal("void".to_string()))?;

        // Convert key i64 to ptr, call closure(key_ptr) -> new_key_ptr
        let key_ptr = self.int_to_ptr(key_i64)?;
        let new_key = self
            .builder()
            .build_indirect_call(
                call_fn_type,
                closure_fn_ptr,
                &[fn_ptr.into(), key_ptr.into()],
                "new_key",
            )
            .map_err(|e| CodegenError::Internal(format!("indirect call failed: {:?}", e)))?
            .try_as_basic_value()
            .basic()
            .ok_or_else(|| CodegenError::Internal("closure returned void".to_string()))?;

        // Convert new key back to i64 for map insert
        let new_key_int = self.coerce_to_int(new_key)?;
        let val_ptr = self.value_to_ptr(val)?;
        let acc_ptr = acc_phi.as_basic_value().into_pointer_value();
        let new_map = self
            .builder()
            .build_call(
                insert_fn,
                &[new_key_int.into(), val_ptr.into(), acc_ptr.into()],
                "new_map",
            )
            .map_err(|e| CodegenError::Internal(format!("call failed: {:?}", e)))?
            .try_as_basic_value()
            .basic()
            .ok_or_else(|| CodegenError::Internal("void".to_string()))?;

        let new_idx = self
            .builder()
            .build_int_add(idx, tm.i64_type().const_int(1, false), "new_idx")
            .map_err(|e| CodegenError::Internal(format!("add failed: {:?}", e)))?;
        self.builder()
            .build_unconditional_branch(loop_header)
            .map_err(|e| CodegenError::Internal(format!("branch failed: {:?}", e)))?;

        acc_phi.add_incoming(&[(&empty_map, entry_block), (&new_map, loop_body)]);
        idx_phi.add_incoming(&[
            (&tm.i64_type().const_zero(), entry_block),
            (&new_idx, loop_body),
        ]);

        self.builder().position_at_end(loop_exit);
        Ok(Some(acc_phi.as_basic_value()))
    }

    /// Lower `Data.Map.fromListWith` -- build map from list with combining function.
    /// fromListWith :: (v -> v -> v) -> [(k,v)] -> Map k v
    fn lower_builtin_map_from_list_with(
        &mut self,
        fn_expr: &Expr,
        list_expr: &Expr,
    ) -> CodegenResult<Option<BasicValueEnum<'ctx>>> {
        let fn_val = self
            .lower_expr(fn_expr)?
            .ok_or_else(|| CodegenError::Internal("map_fromListWith: no function".to_string()))?;
        let fn_ptr = self.value_to_ptr(fn_val)?;
        let list_val = self
            .lower_expr(list_expr)?
            .ok_or_else(|| CodegenError::Internal("map_fromListWith: no list".to_string()))?;
        let list_ptr = self.value_to_ptr(list_val)?;

        let tm = self.type_mapper();
        let ptr_type = tm.ptr_type();
        let current_fn = self
            .builder()
            .get_insert_block()
            .and_then(|b| b.get_parent())
            .ok_or_else(|| CodegenError::Internal("no current function".to_string()))?;

        // Start with empty map
        let empty_fn = self
            .functions
            .get(&VarId::new(1000100))
            .ok_or_else(|| CodegenError::Internal("bhc_map_empty not declared".to_string()))?;
        let empty_map = self
            .builder()
            .build_call(*empty_fn, &[], "empty_map")
            .map_err(|e| CodegenError::Internal(format!("call failed: {:?}", e)))?
            .try_as_basic_value()
            .basic()
            .ok_or_else(|| CodegenError::Internal("void".to_string()))?;

        let insert_fn = *self
            .functions
            .get(&VarId::new(1000107))
            .ok_or_else(|| CodegenError::Internal("bhc_map_insert not declared".to_string()))?;
        let lookup_fn = *self
            .functions
            .get(&VarId::new(1000105))
            .ok_or_else(|| CodegenError::Internal("bhc_map_lookup not declared".to_string()))?;

        let closure_fn_ptr = self.extract_closure_fn_ptr(fn_ptr)?;
        let call_fn_type =
            ptr_type.fn_type(&[ptr_type.into(), ptr_type.into(), ptr_type.into()], false);

        let loop_header = self
            .llvm_context()
            .append_basic_block(current_fn, "mflw_header");
        let loop_body = self
            .llvm_context()
            .append_basic_block(current_fn, "mflw_body");
        let found_block = self
            .llvm_context()
            .append_basic_block(current_fn, "mflw_found");
        let not_found_block = self
            .llvm_context()
            .append_basic_block(current_fn, "mflw_notfound");
        let merge_block = self
            .llvm_context()
            .append_basic_block(current_fn, "mflw_merge");
        let loop_exit = self
            .llvm_context()
            .append_basic_block(current_fn, "mflw_exit");

        let entry_block = self.builder().get_insert_block().unwrap();
        self.builder()
            .build_unconditional_branch(loop_header)
            .map_err(|e| CodegenError::Internal(format!("branch failed: {:?}", e)))?;

        // Loop header: traverse the list
        self.builder().position_at_end(loop_header);
        let map_phi = self
            .builder()
            .build_phi(ptr_type, "map_acc")
            .map_err(|e| CodegenError::Internal(format!("phi failed: {:?}", e)))?;
        let cur_phi = self
            .builder()
            .build_phi(ptr_type, "cur_list")
            .map_err(|e| CodegenError::Internal(format!("phi failed: {:?}", e)))?;

        // Check if current list is nil (tag == 0)
        let cur_ptr = cur_phi.as_basic_value().into_pointer_value();
        let tag = self
            .builder()
            .build_load(tm.i64_type(), cur_ptr, "tag")
            .map_err(|e| CodegenError::Internal(format!("load tag failed: {:?}", e)))?
            .into_int_value();
        let is_nil = self
            .builder()
            .build_int_compare(
                inkwell::IntPredicate::EQ,
                tag,
                tm.i64_type().const_zero(),
                "is_nil",
            )
            .map_err(|e| CodegenError::Internal(format!("cmp failed: {:?}", e)))?;
        self.builder()
            .build_conditional_branch(is_nil, loop_exit, loop_body)
            .map_err(|e| CodegenError::Internal(format!("branch failed: {:?}", e)))?;

        // Loop body: extract (key, value) pair from head
        self.builder().position_at_end(loop_body);
        let head_ptr = self.extract_adt_field(cur_ptr, 2, 0)?;
        let tail_ptr = self.extract_adt_field(cur_ptr, 2, 1)?;

        // Pair is an ADT with (key, value)
        let key_ptr = self.extract_adt_field(head_ptr, 2, 0)?;
        let val_ptr = self.extract_adt_field(head_ptr, 2, 1)?;
        let key_int = self.coerce_to_int(key_ptr.into())?;

        // Lookup key in current map
        let map_acc_ptr = map_phi.as_basic_value().into_pointer_value();
        let found_val = self
            .builder()
            .build_call(lookup_fn, &[key_int.into(), map_acc_ptr.into()], "lookup")
            .map_err(|e| CodegenError::Internal(format!("call failed: {:?}", e)))?
            .try_as_basic_value()
            .basic()
            .ok_or_else(|| CodegenError::Internal("void".to_string()))?
            .into_pointer_value();

        let is_null = self
            .builder()
            .build_is_null(found_val, "is_null")
            .map_err(|e| CodegenError::Internal(format!("is_null failed: {:?}", e)))?;
        self.builder()
            .build_conditional_branch(is_null, not_found_block, found_block)
            .map_err(|e| CodegenError::Internal(format!("branch failed: {:?}", e)))?;

        // Found: call closure(new_val, old_val), insert combined
        self.builder().position_at_end(found_block);
        let combined = self
            .builder()
            .build_indirect_call(
                call_fn_type,
                closure_fn_ptr,
                &[fn_ptr.into(), val_ptr.into(), found_val.into()],
                "combined",
            )
            .map_err(|e| CodegenError::Internal(format!("indirect call failed: {:?}", e)))?
            .try_as_basic_value()
            .basic()
            .ok_or_else(|| CodegenError::Internal("closure returned void".to_string()))?;
        let combined_ptr = self.value_to_ptr(combined)?;
        let new_map_found = self
            .builder()
            .build_call(
                insert_fn,
                &[key_int.into(), combined_ptr.into(), map_acc_ptr.into()],
                "ins_found",
            )
            .map_err(|e| CodegenError::Internal(format!("call failed: {:?}", e)))?
            .try_as_basic_value()
            .basic()
            .ok_or_else(|| CodegenError::Internal("void".to_string()))?;
        self.builder()
            .build_unconditional_branch(merge_block)
            .map_err(|e| CodegenError::Internal(format!("branch failed: {:?}", e)))?;

        // Not found: insert (key, val) directly
        self.builder().position_at_end(not_found_block);
        let new_map_nf = self
            .builder()
            .build_call(
                insert_fn,
                &[key_int.into(), val_ptr.into(), map_acc_ptr.into()],
                "ins_nf",
            )
            .map_err(|e| CodegenError::Internal(format!("call failed: {:?}", e)))?
            .try_as_basic_value()
            .basic()
            .ok_or_else(|| CodegenError::Internal("void".to_string()))?;
        self.builder()
            .build_unconditional_branch(merge_block)
            .map_err(|e| CodegenError::Internal(format!("branch failed: {:?}", e)))?;

        // Merge
        self.builder().position_at_end(merge_block);
        let merge_phi = self
            .builder()
            .build_phi(ptr_type, "merge_map")
            .map_err(|e| CodegenError::Internal(format!("phi failed: {:?}", e)))?;
        merge_phi.add_incoming(&[
            (&new_map_found, found_block),
            (&new_map_nf, not_found_block),
        ]);

        self.builder()
            .build_unconditional_branch(loop_header)
            .map_err(|e| CodegenError::Internal(format!("branch failed: {:?}", e)))?;

        map_phi.add_incoming(&[
            (&empty_map, entry_block),
            (&merge_phi.as_basic_value(), merge_block),
        ]);
        cur_phi.add_incoming(&[(&list_ptr, entry_block), (&tail_ptr, merge_block)]);

        self.builder().position_at_end(loop_exit);
        Ok(Some(map_phi.as_basic_value()))
    }

    /// Lower container operations that need higher-order functions (stub).
    /// Covers: update, alter, unions (remaining unimplemented operations).
    // Stub for not-yet-dispatched container operations.
    #[allow(dead_code)]
    fn lower_builtin_container_ho_stub(
        &mut self,
        args: &[&Expr],
        _name: &str,
    ) -> CodegenResult<Option<BasicValueEnum<'ctx>>> {
        // Evaluate all arguments (for side effects / forcing)
        for arg in args {
            let _ = self.lower_expr(arg)?;
        }
        // For operations that return containers, return the last container arg
        // For operations that return values, return null
        if let Some(last) = args.last() {
            self.lower_expr(last)
        } else {
            Ok(Some(self.type_mapper().ptr_type().const_null().into()))
        }
    }

    /// Lower `Data.Map.unions` — fold `union` over a list of maps.
    /// unions :: [Map k a] -> Map k a
    fn lower_builtin_map_unions(
        &mut self,
        list_expr: &Expr,
    ) -> CodegenResult<Option<BasicValueEnum<'ctx>>> {
        let list_val = self
            .lower_expr(list_expr)?
            .ok_or_else(|| CodegenError::Internal("map_unions: no list".to_string()))?;
        let list_ptr = self.value_to_ptr(list_val)?;

        let tm = self.type_mapper();
        let current_fn = self
            .builder()
            .get_insert_block()
            .and_then(|b| b.get_parent())
            .ok_or_else(|| CodegenError::Internal("no current function".to_string()))?;

        // Start with empty map
        let empty_fn = self
            .functions
            .get(&VarId::new(1000100))
            .ok_or_else(|| CodegenError::Internal("bhc_map_empty not declared".to_string()))?;
        let empty_map = self
            .builder()
            .build_call(*empty_fn, &[], "empty_map")
            .map_err(|e| CodegenError::Internal(format!("empty call failed: {:?}", e)))?
            .try_as_basic_value()
            .basic()
            .ok_or_else(|| CodegenError::Internal("empty: void".to_string()))?;

        let union_fn = *self
            .functions
            .get(&VarId::new(1000109))
            .ok_or_else(|| CodegenError::Internal("bhc_map_union not declared".to_string()))?;

        let loop_header = self
            .llvm_context()
            .append_basic_block(current_fn, "mu_header");
        let loop_body = self
            .llvm_context()
            .append_basic_block(current_fn, "mu_body");
        let loop_exit = self
            .llvm_context()
            .append_basic_block(current_fn, "mu_exit");

        let entry_block = self.builder().get_insert_block().unwrap();
        self.builder()
            .build_unconditional_branch(loop_header)
            .map_err(|e| CodegenError::Internal(format!("branch failed: {:?}", e)))?;

        // Loop header
        self.builder().position_at_end(loop_header);
        let map_phi = self
            .builder()
            .build_phi(tm.ptr_type(), "map_acc")
            .map_err(|e| CodegenError::Internal(format!("phi failed: {:?}", e)))?;
        let cur_phi = self
            .builder()
            .build_phi(tm.ptr_type(), "cur_list")
            .map_err(|e| CodegenError::Internal(format!("phi failed: {:?}", e)))?;

        // Check if current list is nil (tag == 0)
        let cur_ptr = cur_phi.as_basic_value().into_pointer_value();
        let tag = self
            .builder()
            .build_load(tm.i64_type(), cur_ptr, "tag")
            .map_err(|e| CodegenError::Internal(format!("load tag failed: {:?}", e)))?
            .into_int_value();
        let is_nil = self
            .builder()
            .build_int_compare(
                inkwell::IntPredicate::EQ,
                tag,
                tm.i64_type().const_zero(),
                "is_nil",
            )
            .map_err(|e| CodegenError::Internal(format!("cmp failed: {:?}", e)))?;
        self.builder()
            .build_conditional_branch(is_nil, loop_exit, loop_body)
            .map_err(|e| CodegenError::Internal(format!("branch failed: {:?}", e)))?;

        // Loop body: extract head (a map) and tail, union with accumulator
        self.builder().position_at_end(loop_body);
        let head_ptr = self.extract_adt_field(cur_ptr, 2, 0)?;
        let tail_ptr = self.extract_adt_field(cur_ptr, 2, 1)?;

        let map_ptr_val = map_phi.as_basic_value().into_pointer_value();
        let new_map = self
            .builder()
            .build_call(
                union_fn,
                &[map_ptr_val.into(), head_ptr.into()],
                "union_map",
            )
            .map_err(|e| CodegenError::Internal(format!("union call failed: {:?}", e)))?
            .try_as_basic_value()
            .basic()
            .ok_or_else(|| CodegenError::Internal("union: void".to_string()))?;
        self.builder()
            .build_unconditional_branch(loop_header)
            .map_err(|e| CodegenError::Internal(format!("branch failed: {:?}", e)))?;

        map_phi.add_incoming(&[(&empty_map, entry_block), (&new_map, loop_body)]);
        cur_phi.add_incoming(&[(&list_ptr, entry_block), (&tail_ptr, loop_body)]);

        self.builder().position_at_end(loop_exit);
        Ok(Some(map_phi.as_basic_value()))
    }

    /// Lower `Data.Map.keysSet` — build a Set from map keys.
    /// keysSet :: Map k a -> Set k
    fn lower_builtin_map_keys_set(
        &mut self,
        map_expr: &Expr,
    ) -> CodegenResult<Option<BasicValueEnum<'ctx>>> {
        let map_val = self
            .lower_expr(map_expr)?
            .ok_or_else(|| CodegenError::Internal("map_keysSet: no map".to_string()))?;
        let map_ptr = self.value_to_ptr(map_val)?;

        let tm = self.type_mapper();
        let current_fn = self
            .builder()
            .get_insert_block()
            .and_then(|b| b.get_parent())
            .ok_or_else(|| CodegenError::Internal("no current function".to_string()))?;

        let count_fn = *self
            .functions
            .get(&VarId::new(1000160))
            .ok_or_else(|| CodegenError::Internal("bhc_map_keys_count not declared".to_string()))?;
        let key_at_fn = *self
            .functions
            .get(&VarId::new(1000161))
            .ok_or_else(|| CodegenError::Internal("bhc_map_key_at not declared".to_string()))?;
        let set_empty_fn = *self
            .functions
            .get(&VarId::new(1000120))
            .ok_or_else(|| CodegenError::Internal("bhc_set_empty not declared".to_string()))?;
        let set_insert_fn = *self
            .functions
            .get(&VarId::new(1000125))
            .ok_or_else(|| CodegenError::Internal("bhc_set_insert not declared".to_string()))?;

        // Get count of map keys
        let count = self
            .builder()
            .build_call(count_fn, &[map_ptr.into()], "map_count")
            .map_err(|e| CodegenError::Internal(format!("call failed: {:?}", e)))?
            .try_as_basic_value()
            .basic()
            .ok_or_else(|| CodegenError::Internal("void".to_string()))?
            .into_int_value();

        // Start with empty set
        let empty_set = self
            .builder()
            .build_call(set_empty_fn, &[], "empty_set")
            .map_err(|e| CodegenError::Internal(format!("call failed: {:?}", e)))?
            .try_as_basic_value()
            .basic()
            .ok_or_else(|| CodegenError::Internal("void".to_string()))?;

        let loop_header = self
            .llvm_context()
            .append_basic_block(current_fn, "mks_header");
        let loop_body = self
            .llvm_context()
            .append_basic_block(current_fn, "mks_body");
        let loop_exit = self
            .llvm_context()
            .append_basic_block(current_fn, "mks_exit");

        let entry_block = self.builder().get_insert_block().unwrap();
        self.builder()
            .build_unconditional_branch(loop_header)
            .map_err(|e| CodegenError::Internal(format!("branch failed: {:?}", e)))?;

        // Loop header: iterate 0..count
        self.builder().position_at_end(loop_header);
        let set_phi = self
            .builder()
            .build_phi(tm.ptr_type(), "set_acc")
            .map_err(|e| CodegenError::Internal(format!("phi failed: {:?}", e)))?;
        let idx_phi = self
            .builder()
            .build_phi(tm.i64_type(), "idx")
            .map_err(|e| CodegenError::Internal(format!("phi failed: {:?}", e)))?;

        let idx = idx_phi.as_basic_value().into_int_value();
        let done = self
            .builder()
            .build_int_compare(inkwell::IntPredicate::SGE, idx, count, "done")
            .map_err(|e| CodegenError::Internal(format!("cmp failed: {:?}", e)))?;
        self.builder()
            .build_conditional_branch(done, loop_exit, loop_body)
            .map_err(|e| CodegenError::Internal(format!("branch failed: {:?}", e)))?;

        // Loop body: get key at index, insert into set
        self.builder().position_at_end(loop_body);
        let key_i64 = self
            .builder()
            .build_call(key_at_fn, &[map_ptr.into(), idx.into()], "key")
            .map_err(|e| CodegenError::Internal(format!("call failed: {:?}", e)))?
            .try_as_basic_value()
            .basic()
            .ok_or_else(|| CodegenError::Internal("void".to_string()))?
            .into_int_value();

        let set_ptr = set_phi.as_basic_value().into_pointer_value();
        let new_set = self
            .builder()
            .build_call(set_insert_fn, &[key_i64.into(), set_ptr.into()], "new_set")
            .map_err(|e| CodegenError::Internal(format!("call failed: {:?}", e)))?
            .try_as_basic_value()
            .basic()
            .ok_or_else(|| CodegenError::Internal("void".to_string()))?;

        let new_idx = self
            .builder()
            .build_int_add(idx, tm.i64_type().const_int(1, false), "new_idx")
            .map_err(|e| CodegenError::Internal(format!("add failed: {:?}", e)))?;
        self.builder()
            .build_unconditional_branch(loop_header)
            .map_err(|e| CodegenError::Internal(format!("branch failed: {:?}", e)))?;

        set_phi.add_incoming(&[(&empty_set, entry_block), (&new_set, loop_body)]);
        idx_phi.add_incoming(&[
            (&tm.i64_type().const_zero(), entry_block),
            (&new_idx, loop_body),
        ]);

        self.builder().position_at_end(loop_exit);
        Ok(Some(set_phi.as_basic_value()))
    }

    /// Lower `Data.Set.unions` — fold `union` over a list of sets.
    /// unions :: [Set a] -> Set a
    fn lower_builtin_set_unions(
        &mut self,
        list_expr: &Expr,
    ) -> CodegenResult<Option<BasicValueEnum<'ctx>>> {
        let list_val = self
            .lower_expr(list_expr)?
            .ok_or_else(|| CodegenError::Internal("set_unions: no list".to_string()))?;
        let list_ptr = self.value_to_ptr(list_val)?;

        let tm = self.type_mapper();
        let current_fn = self
            .builder()
            .get_insert_block()
            .and_then(|b| b.get_parent())
            .ok_or_else(|| CodegenError::Internal("no current function".to_string()))?;

        // Start with empty set
        let empty_fn = self
            .functions
            .get(&VarId::new(1000120))
            .ok_or_else(|| CodegenError::Internal("bhc_set_empty not declared".to_string()))?;
        let empty_set = self
            .builder()
            .build_call(*empty_fn, &[], "empty_set")
            .map_err(|e| CodegenError::Internal(format!("empty call failed: {:?}", e)))?
            .try_as_basic_value()
            .basic()
            .ok_or_else(|| CodegenError::Internal("empty: void".to_string()))?;

        let union_fn = *self
            .functions
            .get(&VarId::new(1000127))
            .ok_or_else(|| CodegenError::Internal("bhc_set_union not declared".to_string()))?;

        let loop_header = self
            .llvm_context()
            .append_basic_block(current_fn, "su_header");
        let loop_body = self
            .llvm_context()
            .append_basic_block(current_fn, "su_body");
        let loop_exit = self
            .llvm_context()
            .append_basic_block(current_fn, "su_exit");

        let entry_block = self.builder().get_insert_block().unwrap();
        self.builder()
            .build_unconditional_branch(loop_header)
            .map_err(|e| CodegenError::Internal(format!("branch failed: {:?}", e)))?;

        // Loop header
        self.builder().position_at_end(loop_header);
        let set_phi = self
            .builder()
            .build_phi(tm.ptr_type(), "set_acc")
            .map_err(|e| CodegenError::Internal(format!("phi failed: {:?}", e)))?;
        let cur_phi = self
            .builder()
            .build_phi(tm.ptr_type(), "cur_list")
            .map_err(|e| CodegenError::Internal(format!("phi failed: {:?}", e)))?;

        // Check if current list is nil (tag == 0)
        let cur_ptr = cur_phi.as_basic_value().into_pointer_value();
        let tag = self
            .builder()
            .build_load(tm.i64_type(), cur_ptr, "tag")
            .map_err(|e| CodegenError::Internal(format!("load tag failed: {:?}", e)))?
            .into_int_value();
        let is_nil = self
            .builder()
            .build_int_compare(
                inkwell::IntPredicate::EQ,
                tag,
                tm.i64_type().const_zero(),
                "is_nil",
            )
            .map_err(|e| CodegenError::Internal(format!("cmp failed: {:?}", e)))?;
        self.builder()
            .build_conditional_branch(is_nil, loop_exit, loop_body)
            .map_err(|e| CodegenError::Internal(format!("branch failed: {:?}", e)))?;

        // Loop body: extract head (a set) and tail, union with accumulator
        self.builder().position_at_end(loop_body);
        let head_ptr = self.extract_adt_field(cur_ptr, 2, 0)?;
        let tail_ptr = self.extract_adt_field(cur_ptr, 2, 1)?;

        let set_ptr_val = set_phi.as_basic_value().into_pointer_value();
        let new_set = self
            .builder()
            .build_call(
                union_fn,
                &[set_ptr_val.into(), head_ptr.into()],
                "union_set",
            )
            .map_err(|e| CodegenError::Internal(format!("union call failed: {:?}", e)))?
            .try_as_basic_value()
            .basic()
            .ok_or_else(|| CodegenError::Internal("union: void".to_string()))?;
        self.builder()
            .build_unconditional_branch(loop_header)
            .map_err(|e| CodegenError::Internal(format!("branch failed: {:?}", e)))?;

        set_phi.add_incoming(&[(&empty_set, entry_block), (&new_set, loop_body)]);
        cur_phi.add_incoming(&[(&list_ptr, entry_block), (&tail_ptr, loop_body)]);

        self.builder().position_at_end(loop_exit);
        Ok(Some(set_phi.as_basic_value()))
    }

    /// Lower `Data.Set.partition` — split set into (matching, non-matching).
    /// partition :: (a -> Bool) -> Set a -> (Set a, Set a)
    fn lower_builtin_set_partition(
        &mut self,
        fn_expr: &Expr,
        set_expr: &Expr,
    ) -> CodegenResult<Option<BasicValueEnum<'ctx>>> {
        let fn_val = self
            .lower_expr(fn_expr)?
            .ok_or_else(|| CodegenError::Internal("set_partition: no function".to_string()))?;
        let fn_ptr = self.value_to_ptr(fn_val)?;
        let set_val = self
            .lower_expr(set_expr)?
            .ok_or_else(|| CodegenError::Internal("set_partition: no set".to_string()))?;
        let set_ptr = self.value_to_ptr(set_val)?;

        let tm = self.type_mapper();
        let ptr_type = tm.ptr_type();
        let current_fn = self
            .builder()
            .get_insert_block()
            .and_then(|b| b.get_parent())
            .ok_or_else(|| CodegenError::Internal("no current function".to_string()))?;

        let count_fn = self
            .functions
            .get(&VarId::new(1000163))
            .ok_or_else(|| CodegenError::Internal("bhc_set_elem_count not declared".to_string()))?;
        let count = self
            .builder()
            .build_call(*count_fn, &[set_ptr.into()], "count")
            .map_err(|e| CodegenError::Internal(format!("call failed: {:?}", e)))?
            .try_as_basic_value()
            .basic()
            .ok_or_else(|| CodegenError::Internal("void".to_string()))?
            .into_int_value();

        let elem_at_fn = *self
            .functions
            .get(&VarId::new(1000164))
            .ok_or_else(|| CodegenError::Internal("bhc_set_elem_at not declared".to_string()))?;
        let insert_fn = *self
            .functions
            .get(&VarId::new(1000125))
            .ok_or_else(|| CodegenError::Internal("bhc_set_insert not declared".to_string()))?;

        let empty_fn = self
            .functions
            .get(&VarId::new(1000120))
            .ok_or_else(|| CodegenError::Internal("bhc_set_empty not declared".to_string()))?;
        let empty_yes = self
            .builder()
            .build_call(*empty_fn, &[], "empty_yes")
            .map_err(|e| CodegenError::Internal(format!("call failed: {:?}", e)))?
            .try_as_basic_value()
            .basic()
            .ok_or_else(|| CodegenError::Internal("void".to_string()))?;
        let empty_no = self
            .builder()
            .build_call(*empty_fn, &[], "empty_no")
            .map_err(|e| CodegenError::Internal(format!("call failed: {:?}", e)))?
            .try_as_basic_value()
            .basic()
            .ok_or_else(|| CodegenError::Internal("void".to_string()))?;

        let closure_fn_ptr = self.extract_closure_fn_ptr(fn_ptr)?;
        let call_fn_type = ptr_type.fn_type(&[ptr_type.into(), ptr_type.into()], false);

        let loop_header = self
            .llvm_context()
            .append_basic_block(current_fn, "sp_header");
        let loop_body = self
            .llvm_context()
            .append_basic_block(current_fn, "sp_body");
        let sp_yes = self.llvm_context().append_basic_block(current_fn, "sp_yes");
        let sp_no = self.llvm_context().append_basic_block(current_fn, "sp_no");
        let sp_merge = self
            .llvm_context()
            .append_basic_block(current_fn, "sp_merge");
        let loop_exit = self
            .llvm_context()
            .append_basic_block(current_fn, "sp_exit");

        let entry_block = self.builder().get_insert_block().unwrap();
        self.builder()
            .build_unconditional_branch(loop_header)
            .map_err(|e| CodegenError::Internal(format!("branch failed: {:?}", e)))?;

        self.builder().position_at_end(loop_header);
        let yes_phi = self
            .builder()
            .build_phi(ptr_type, "yes_acc")
            .map_err(|e| CodegenError::Internal(format!("phi failed: {:?}", e)))?;
        let no_phi = self
            .builder()
            .build_phi(ptr_type, "no_acc")
            .map_err(|e| CodegenError::Internal(format!("phi failed: {:?}", e)))?;
        let idx_phi = self
            .builder()
            .build_phi(tm.i64_type(), "idx")
            .map_err(|e| CodegenError::Internal(format!("phi failed: {:?}", e)))?;

        let idx = idx_phi.as_basic_value().into_int_value();
        let done = self
            .builder()
            .build_int_compare(inkwell::IntPredicate::SGE, idx, count, "done")
            .map_err(|e| CodegenError::Internal(format!("cmp failed: {:?}", e)))?;
        self.builder()
            .build_conditional_branch(done, loop_exit, loop_body)
            .map_err(|e| CodegenError::Internal(format!("branch failed: {:?}", e)))?;

        // Loop body: get element, call predicate
        self.builder().position_at_end(loop_body);
        let elem_i64 = self
            .builder()
            .build_call(elem_at_fn, &[set_ptr.into(), idx.into()], "elem")
            .map_err(|e| CodegenError::Internal(format!("call failed: {:?}", e)))?
            .try_as_basic_value()
            .basic()
            .ok_or_else(|| CodegenError::Internal("void".to_string()))?
            .into_int_value();
        let elem_ptr = self.int_to_ptr(elem_i64)?;

        let pred_result = self
            .builder()
            .build_indirect_call(
                call_fn_type,
                closure_fn_ptr,
                &[fn_ptr.into(), elem_ptr.into()],
                "pred",
            )
            .map_err(|e| CodegenError::Internal(format!("indirect call failed: {:?}", e)))?
            .try_as_basic_value()
            .basic()
            .ok_or_else(|| CodegenError::Internal("closure returned void".to_string()))?;

        let pred_ptr = self.value_to_ptr(pred_result)?;
        let pred_tag = self.extract_adt_tag(pred_ptr)?;
        let is_true = self
            .builder()
            .build_int_compare(
                inkwell::IntPredicate::NE,
                pred_tag,
                tm.i64_type().const_zero(),
                "is_true",
            )
            .map_err(|e| CodegenError::Internal(format!("cmp failed: {:?}", e)))?;
        self.builder()
            .build_conditional_branch(is_true, sp_yes, sp_no)
            .map_err(|e| CodegenError::Internal(format!("branch failed: {:?}", e)))?;

        // Yes branch: insert into yes_acc
        self.builder().position_at_end(sp_yes);
        let yes_ptr = yes_phi.as_basic_value().into_pointer_value();
        let yes_inserted = self
            .builder()
            .build_call(insert_fn, &[elem_i64.into(), yes_ptr.into()], "yes_ins")
            .map_err(|e| CodegenError::Internal(format!("call failed: {:?}", e)))?
            .try_as_basic_value()
            .basic()
            .ok_or_else(|| CodegenError::Internal("void".to_string()))?;
        self.builder()
            .build_unconditional_branch(sp_merge)
            .map_err(|e| CodegenError::Internal(format!("branch failed: {:?}", e)))?;

        // No branch: insert into no_acc
        self.builder().position_at_end(sp_no);
        let no_ptr = no_phi.as_basic_value().into_pointer_value();
        let no_inserted = self
            .builder()
            .build_call(insert_fn, &[elem_i64.into(), no_ptr.into()], "no_ins")
            .map_err(|e| CodegenError::Internal(format!("call failed: {:?}", e)))?
            .try_as_basic_value()
            .basic()
            .ok_or_else(|| CodegenError::Internal("void".to_string()))?;
        self.builder()
            .build_unconditional_branch(sp_merge)
            .map_err(|e| CodegenError::Internal(format!("branch failed: {:?}", e)))?;

        // Merge: phi for yes_acc and no_acc
        self.builder().position_at_end(sp_merge);
        let yes_merge = self
            .builder()
            .build_phi(ptr_type, "yes_merge")
            .map_err(|e| CodegenError::Internal(format!("phi failed: {:?}", e)))?;
        yes_merge.add_incoming(&[(&yes_inserted, sp_yes), (&yes_phi.as_basic_value(), sp_no)]);

        let no_merge = self
            .builder()
            .build_phi(ptr_type, "no_merge")
            .map_err(|e| CodegenError::Internal(format!("phi failed: {:?}", e)))?;
        no_merge.add_incoming(&[(&no_phi.as_basic_value(), sp_yes), (&no_inserted, sp_no)]);

        let new_idx = self
            .builder()
            .build_int_add(idx, tm.i64_type().const_int(1, false), "new_idx")
            .map_err(|e| CodegenError::Internal(format!("add failed: {:?}", e)))?;
        self.builder()
            .build_unconditional_branch(loop_header)
            .map_err(|e| CodegenError::Internal(format!("branch failed: {:?}", e)))?;

        yes_phi.add_incoming(&[
            (&empty_yes, entry_block),
            (&yes_merge.as_basic_value(), sp_merge),
        ]);
        no_phi.add_incoming(&[
            (&empty_no, entry_block),
            (&no_merge.as_basic_value(), sp_merge),
        ]);
        idx_phi.add_incoming(&[
            (&tm.i64_type().const_zero(), entry_block),
            (&new_idx, sp_merge),
        ]);

        // Exit: build tuple (yes_set, no_set)
        self.builder().position_at_end(loop_exit);
        let pair = self.alloc_adt(0, 2)?;
        self.store_adt_field(pair, 2, 0, yes_phi.as_basic_value())?;
        self.store_adt_field(pair, 2, 1, no_phi.as_basic_value())?;

        Ok(Some(pair.into()))
    }

    /// Lower `Data.Map.update` — update a value at a key, possibly deleting it.
    /// update :: (a -> Maybe a) -> k -> Map k a -> Map k a
    fn lower_builtin_map_update(
        &mut self,
        fn_expr: &Expr,
        key_expr: &Expr,
        map_expr: &Expr,
    ) -> CodegenResult<Option<BasicValueEnum<'ctx>>> {
        let fn_val = self
            .lower_expr(fn_expr)?
            .ok_or_else(|| CodegenError::Internal("map_update: no function".to_string()))?;
        let fn_ptr = self.value_to_ptr(fn_val)?;
        let key_val = self
            .lower_expr(key_expr)?
            .ok_or_else(|| CodegenError::Internal("map_update: no key".to_string()))?;
        let map_val = self
            .lower_expr(map_expr)?
            .ok_or_else(|| CodegenError::Internal("map_update: no map".to_string()))?;

        let key_int = self.coerce_to_int(key_val)?;
        let map_ptr = self.value_to_ptr(map_val)?;

        let tm = self.type_mapper();
        let ptr_type = tm.ptr_type();
        let current_fn = self
            .builder()
            .get_insert_block()
            .and_then(|b| b.get_parent())
            .ok_or_else(|| CodegenError::Internal("no current function".to_string()))?;

        let insert_fn = *self
            .functions
            .get(&VarId::new(1000107))
            .ok_or_else(|| CodegenError::Internal("bhc_map_insert not declared".to_string()))?;
        let delete_fn = *self
            .functions
            .get(&VarId::new(1000108))
            .ok_or_else(|| CodegenError::Internal("bhc_map_delete not declared".to_string()))?;
        let lookup_fn = *self
            .functions
            .get(&VarId::new(1000105))
            .ok_or_else(|| CodegenError::Internal("bhc_map_lookup not declared".to_string()))?;

        // Lookup key in map
        let found_val = self
            .builder()
            .build_call(lookup_fn, &[key_int.into(), map_ptr.into()], "lookup")
            .map_err(|e| CodegenError::Internal(format!("call failed: {:?}", e)))?
            .try_as_basic_value()
            .basic()
            .ok_or_else(|| CodegenError::Internal("void".to_string()))?
            .into_pointer_value();

        let is_null = self
            .builder()
            .build_is_null(found_val, "is_null")
            .map_err(|e| CodegenError::Internal(format!("is_null failed: {:?}", e)))?;

        let found_block = self
            .llvm_context()
            .append_basic_block(current_fn, "upd_found");
        let just_block = self
            .llvm_context()
            .append_basic_block(current_fn, "upd_just");
        let nothing_block = self
            .llvm_context()
            .append_basic_block(current_fn, "upd_nothing");
        let merge_block = self
            .llvm_context()
            .append_basic_block(current_fn, "upd_merge");

        let entry_block = self.builder().get_insert_block().unwrap();
        self.builder()
            .build_conditional_branch(is_null, merge_block, found_block)
            .map_err(|e| CodegenError::Internal(format!("branch failed: {:?}", e)))?;

        // Found: call closure(old_val) -> Maybe a
        self.builder().position_at_end(found_block);
        let closure_fn_ptr = self.extract_closure_fn_ptr(fn_ptr)?;
        let call_fn_type = ptr_type.fn_type(&[ptr_type.into(), ptr_type.into()], false);
        let maybe_result = self
            .builder()
            .build_indirect_call(
                call_fn_type,
                closure_fn_ptr,
                &[fn_ptr.into(), found_val.into()],
                "maybe_result",
            )
            .map_err(|e| CodegenError::Internal(format!("indirect call failed: {:?}", e)))?
            .try_as_basic_value()
            .basic()
            .ok_or_else(|| CodegenError::Internal("closure returned void".to_string()))?;

        // Check Maybe tag: 0 = Nothing, 1 = Just
        let maybe_ptr = self.value_to_ptr(maybe_result)?;
        let maybe_tag = self.extract_adt_tag(maybe_ptr)?;
        let is_just = self
            .builder()
            .build_int_compare(
                inkwell::IntPredicate::NE,
                maybe_tag,
                tm.i64_type().const_zero(),
                "is_just",
            )
            .map_err(|e| CodegenError::Internal(format!("cmp failed: {:?}", e)))?;
        self.builder()
            .build_conditional_branch(is_just, just_block, nothing_block)
            .map_err(|e| CodegenError::Internal(format!("branch failed: {:?}", e)))?;

        // Just: extract value, delete+insert
        self.builder().position_at_end(just_block);
        let just_val = self.extract_adt_field(maybe_ptr, 1, 0)?;
        let deleted_map_j = self
            .builder()
            .build_call(delete_fn, &[key_int.into(), map_ptr.into()], "deleted_j")
            .map_err(|e| CodegenError::Internal(format!("call failed: {:?}", e)))?
            .try_as_basic_value()
            .basic()
            .ok_or_else(|| CodegenError::Internal("void".to_string()))?;
        let deleted_ptr_j = self.value_to_ptr(deleted_map_j)?;
        let new_map_j = self
            .builder()
            .build_call(
                insert_fn,
                &[key_int.into(), just_val.into(), deleted_ptr_j.into()],
                "ins_just",
            )
            .map_err(|e| CodegenError::Internal(format!("call failed: {:?}", e)))?
            .try_as_basic_value()
            .basic()
            .ok_or_else(|| CodegenError::Internal("void".to_string()))?;
        self.builder()
            .build_unconditional_branch(merge_block)
            .map_err(|e| CodegenError::Internal(format!("branch failed: {:?}", e)))?;

        // Nothing: delete the key
        self.builder().position_at_end(nothing_block);
        let deleted_map_n = self
            .builder()
            .build_call(delete_fn, &[key_int.into(), map_ptr.into()], "deleted_n")
            .map_err(|e| CodegenError::Internal(format!("call failed: {:?}", e)))?
            .try_as_basic_value()
            .basic()
            .ok_or_else(|| CodegenError::Internal("void".to_string()))?;
        self.builder()
            .build_unconditional_branch(merge_block)
            .map_err(|e| CodegenError::Internal(format!("branch failed: {:?}", e)))?;

        // Merge: not found -> original map, Just -> inserted, Nothing -> deleted
        self.builder().position_at_end(merge_block);
        let result_phi = self
            .builder()
            .build_phi(ptr_type, "result")
            .map_err(|e| CodegenError::Internal(format!("phi failed: {:?}", e)))?;
        result_phi.add_incoming(&[
            (&map_ptr, entry_block),         // key not found: return original
            (&new_map_j, just_block),        // Just: return updated map
            (&deleted_map_n, nothing_block), // Nothing: return map with key deleted
        ]);

        Ok(Some(result_phi.as_basic_value()))
    }

    /// Lower `Data.Map.alter` — alter a value at a key.
    /// alter :: (Maybe a -> Maybe a) -> k -> Map k a -> Map k a
    fn lower_builtin_map_alter(
        &mut self,
        fn_expr: &Expr,
        key_expr: &Expr,
        map_expr: &Expr,
    ) -> CodegenResult<Option<BasicValueEnum<'ctx>>> {
        let fn_val = self
            .lower_expr(fn_expr)?
            .ok_or_else(|| CodegenError::Internal("map_alter: no function".to_string()))?;
        let fn_ptr = self.value_to_ptr(fn_val)?;
        let key_val = self
            .lower_expr(key_expr)?
            .ok_or_else(|| CodegenError::Internal("map_alter: no key".to_string()))?;
        let map_val = self
            .lower_expr(map_expr)?
            .ok_or_else(|| CodegenError::Internal("map_alter: no map".to_string()))?;

        let key_int = self.coerce_to_int(key_val)?;
        let map_ptr = self.value_to_ptr(map_val)?;

        let tm = self.type_mapper();
        let ptr_type = tm.ptr_type();
        let current_fn = self
            .builder()
            .get_insert_block()
            .and_then(|b| b.get_parent())
            .ok_or_else(|| CodegenError::Internal("no current function".to_string()))?;

        let insert_fn = *self
            .functions
            .get(&VarId::new(1000107))
            .ok_or_else(|| CodegenError::Internal("bhc_map_insert not declared".to_string()))?;
        let delete_fn = *self
            .functions
            .get(&VarId::new(1000108))
            .ok_or_else(|| CodegenError::Internal("bhc_map_delete not declared".to_string()))?;
        let lookup_fn = *self
            .functions
            .get(&VarId::new(1000105))
            .ok_or_else(|| CodegenError::Internal("bhc_map_lookup not declared".to_string()))?;

        // Lookup key in map
        let found_val = self
            .builder()
            .build_call(lookup_fn, &[key_int.into(), map_ptr.into()], "lookup")
            .map_err(|e| CodegenError::Internal(format!("call failed: {:?}", e)))?
            .try_as_basic_value()
            .basic()
            .ok_or_else(|| CodegenError::Internal("void".to_string()))?
            .into_pointer_value();

        let is_null = self
            .builder()
            .build_is_null(found_val, "is_null")
            .map_err(|e| CodegenError::Internal(format!("is_null failed: {:?}", e)))?;

        // Build input Maybe: Nothing if not found, Just(val) if found
        let nothing = self.alloc_adt(0, 0)?; // Nothing = tag 0, 0 fields
        let just = self.alloc_adt(1, 1)?; // Just = tag 1, 1 field
        self.store_adt_field(just, 1, 0, found_val.into())?;
        let input_maybe = self
            .builder()
            .build_select(is_null, nothing, just, "input_maybe")
            .map_err(|e| CodegenError::Internal(format!("select failed: {:?}", e)))?;

        // Call closure(input_maybe) -> Maybe a
        let closure_fn_ptr = self.extract_closure_fn_ptr(fn_ptr)?;
        let call_fn_type = ptr_type.fn_type(&[ptr_type.into(), ptr_type.into()], false);
        let input_ptr = self.value_to_ptr(input_maybe)?;
        let maybe_result = self
            .builder()
            .build_indirect_call(
                call_fn_type,
                closure_fn_ptr,
                &[fn_ptr.into(), input_ptr.into()],
                "maybe_result",
            )
            .map_err(|e| CodegenError::Internal(format!("indirect call failed: {:?}", e)))?
            .try_as_basic_value()
            .basic()
            .ok_or_else(|| CodegenError::Internal("closure returned void".to_string()))?;

        // Check result Maybe tag: 0 = Nothing, 1 = Just
        let result_ptr = self.value_to_ptr(maybe_result)?;
        let result_tag = self.extract_adt_tag(result_ptr)?;
        let is_just = self
            .builder()
            .build_int_compare(
                inkwell::IntPredicate::NE,
                result_tag,
                tm.i64_type().const_zero(),
                "is_just",
            )
            .map_err(|e| CodegenError::Internal(format!("cmp failed: {:?}", e)))?;

        let just_block = self
            .llvm_context()
            .append_basic_block(current_fn, "alt_just");
        let nothing_block = self
            .llvm_context()
            .append_basic_block(current_fn, "alt_nothing");
        let merge_block = self
            .llvm_context()
            .append_basic_block(current_fn, "alt_merge");

        self.builder()
            .build_conditional_branch(is_just, just_block, nothing_block)
            .map_err(|e| CodegenError::Internal(format!("branch failed: {:?}", e)))?;

        // Just: extract value, delete old key (if any), insert new value
        self.builder().position_at_end(just_block);
        let just_val = self.extract_adt_field(result_ptr, 1, 0)?;
        let deleted_map_j = self
            .builder()
            .build_call(delete_fn, &[key_int.into(), map_ptr.into()], "deleted_j")
            .map_err(|e| CodegenError::Internal(format!("call failed: {:?}", e)))?
            .try_as_basic_value()
            .basic()
            .ok_or_else(|| CodegenError::Internal("void".to_string()))?;
        let deleted_ptr_j = self.value_to_ptr(deleted_map_j)?;
        let new_map_j = self
            .builder()
            .build_call(
                insert_fn,
                &[key_int.into(), just_val.into(), deleted_ptr_j.into()],
                "ins_just",
            )
            .map_err(|e| CodegenError::Internal(format!("call failed: {:?}", e)))?
            .try_as_basic_value()
            .basic()
            .ok_or_else(|| CodegenError::Internal("void".to_string()))?;
        self.builder()
            .build_unconditional_branch(merge_block)
            .map_err(|e| CodegenError::Internal(format!("branch failed: {:?}", e)))?;

        // Nothing: delete the key (safe even if key doesn't exist)
        self.builder().position_at_end(nothing_block);
        let deleted_map_n = self
            .builder()
            .build_call(delete_fn, &[key_int.into(), map_ptr.into()], "deleted_n")
            .map_err(|e| CodegenError::Internal(format!("call failed: {:?}", e)))?
            .try_as_basic_value()
            .basic()
            .ok_or_else(|| CodegenError::Internal("void".to_string()))?;
        self.builder()
            .build_unconditional_branch(merge_block)
            .map_err(|e| CodegenError::Internal(format!("branch failed: {:?}", e)))?;

        // Merge
        self.builder().position_at_end(merge_block);
        let result_phi = self
            .builder()
            .build_phi(ptr_type, "result")
            .map_err(|e| CodegenError::Internal(format!("phi failed: {:?}", e)))?;
        result_phi.add_incoming(&[(&new_map_j, just_block), (&deleted_map_n, nothing_block)]);

        Ok(Some(result_phi.as_basic_value()))
    }

    /// Lower `Data.Map.toList` / `toAscList` / `toDescList` / `assocs` / `keys` / `elems`.
    /// Builds a list from the container using iteration RTS functions.
    fn lower_builtin_map_to_list(
        &mut self,
        map_expr: &Expr,
    ) -> CodegenResult<Option<BasicValueEnum<'ctx>>> {
        let map_val = self
            .lower_expr(map_expr)?
            .ok_or_else(|| CodegenError::Internal("map_toList: no map".to_string()))?;
        let map_ptr = self.value_to_ptr(map_val)?;

        let tm = self.type_mapper();
        let current_fn = self
            .builder()
            .get_insert_block()
            .and_then(|b| b.get_parent())
            .ok_or_else(|| CodegenError::Internal("no current function".to_string()))?;

        // Get element count
        let count_fn = self
            .functions
            .get(&VarId::new(1000160))
            .ok_or_else(|| CodegenError::Internal("bhc_map_keys_count not declared".to_string()))?;
        let count = self
            .builder()
            .build_call(*count_fn, &[map_ptr.into()], "map_count")
            .map_err(|e| CodegenError::Internal(format!("map_count call failed: {:?}", e)))?
            .try_as_basic_value()
            .basic()
            .ok_or_else(|| CodegenError::Internal("map_count: returned void".to_string()))?
            .into_int_value();

        let key_at_fn = *self
            .functions
            .get(&VarId::new(1000161))
            .ok_or_else(|| CodegenError::Internal("bhc_map_key_at not declared".to_string()))?;
        let val_at_fn = *self
            .functions
            .get(&VarId::new(1000162))
            .ok_or_else(|| CodegenError::Internal("bhc_map_value_at not declared".to_string()))?;

        // Build list backwards: iterate from (count-1) down to 0, consing (key,value) tuples
        let loop_header = self
            .llvm_context()
            .append_basic_block(current_fn, "mtl_header");
        let loop_body = self
            .llvm_context()
            .append_basic_block(current_fn, "mtl_body");
        let loop_exit = self
            .llvm_context()
            .append_basic_block(current_fn, "mtl_exit");

        let nil = self.build_nil()?;
        let start_idx = self
            .builder()
            .build_int_sub(count, tm.i64_type().const_int(1, false), "start_idx")
            .map_err(|e| CodegenError::Internal(format!("sub failed: {:?}", e)))?;

        let entry_block = self.builder().get_insert_block().unwrap();
        self.builder()
            .build_unconditional_branch(loop_header)
            .map_err(|e| CodegenError::Internal(format!("branch failed: {:?}", e)))?;

        // Loop header with PHI nodes
        self.builder().position_at_end(loop_header);
        let acc_phi = self
            .builder()
            .build_phi(tm.ptr_type(), "acc")
            .map_err(|e| CodegenError::Internal(format!("phi failed: {:?}", e)))?;
        let idx_phi = self
            .builder()
            .build_phi(tm.i64_type(), "idx")
            .map_err(|e| CodegenError::Internal(format!("phi failed: {:?}", e)))?;

        let idx = idx_phi.as_basic_value().into_int_value();
        let done = self
            .builder()
            .build_int_compare(
                inkwell::IntPredicate::SLT,
                idx,
                tm.i64_type().const_zero(),
                "done",
            )
            .map_err(|e| CodegenError::Internal(format!("cmp failed: {:?}", e)))?;
        self.builder()
            .build_conditional_branch(done, loop_exit, loop_body)
            .map_err(|e| CodegenError::Internal(format!("branch failed: {:?}", e)))?;

        // Loop body: get key and value at index, build (key, value) tuple, cons onto acc
        self.builder().position_at_end(loop_body);
        let key_i64 = self
            .builder()
            .build_call(key_at_fn, &[map_ptr.into(), idx.into()], "key")
            .map_err(|e| CodegenError::Internal(format!("key_at call failed: {:?}", e)))?
            .try_as_basic_value()
            .basic()
            .ok_or_else(|| CodegenError::Internal("key_at: void".to_string()))?
            .into_int_value();
        let val_ptr = self
            .builder()
            .build_call(val_at_fn, &[map_ptr.into(), idx.into()], "val")
            .map_err(|e| CodegenError::Internal(format!("val_at call failed: {:?}", e)))?
            .try_as_basic_value()
            .basic()
            .ok_or_else(|| CodegenError::Internal("val_at: void".to_string()))?;

        // Build a (key, value) pair as a 2-tuple ADT (tag=0, arity=2)
        let key_ptr = self.int_to_ptr(key_i64)?;
        let pair = self.alloc_adt(0, 2)?;
        self.store_adt_field(pair, 2, 0, key_ptr.into())?;
        self.store_adt_field(pair, 2, 1, val_ptr)?;

        // Cons pair onto accumulator
        let new_acc = self.build_cons(pair.into(), acc_phi.as_basic_value())?;
        let new_idx = self
            .builder()
            .build_int_sub(idx, tm.i64_type().const_int(1, false), "new_idx")
            .map_err(|e| CodegenError::Internal(format!("sub failed: {:?}", e)))?;
        self.builder()
            .build_unconditional_branch(loop_header)
            .map_err(|e| CodegenError::Internal(format!("branch failed: {:?}", e)))?;

        acc_phi.add_incoming(&[(&nil, entry_block), (&new_acc, loop_body)]);
        idx_phi.add_incoming(&[(&start_idx, entry_block), (&new_idx, loop_body)]);

        self.builder().position_at_end(loop_exit);
        Ok(Some(acc_phi.as_basic_value()))
    }

    /// Lower `Data.Map.fromList` — iterates the list, inserting each (k,v) pair.
    fn lower_builtin_map_from_list(
        &mut self,
        list_expr: &Expr,
    ) -> CodegenResult<Option<BasicValueEnum<'ctx>>> {
        let list_val = self
            .lower_expr(list_expr)?
            .ok_or_else(|| CodegenError::Internal("map_fromList: no list".to_string()))?;
        let list_ptr = self.value_to_ptr(list_val)?;

        let tm = self.type_mapper();
        let current_fn = self
            .builder()
            .get_insert_block()
            .and_then(|b| b.get_parent())
            .ok_or_else(|| CodegenError::Internal("no current function".to_string()))?;

        // Start with empty map
        let empty_fn = self
            .functions
            .get(&VarId::new(1000100))
            .ok_or_else(|| CodegenError::Internal("bhc_map_empty not declared".to_string()))?;
        let empty_map = self
            .builder()
            .build_call(*empty_fn, &[], "empty_map")
            .map_err(|e| CodegenError::Internal(format!("empty call failed: {:?}", e)))?
            .try_as_basic_value()
            .basic()
            .ok_or_else(|| CodegenError::Internal("empty: void".to_string()))?;

        let insert_fn = *self
            .functions
            .get(&VarId::new(1000107))
            .ok_or_else(|| CodegenError::Internal("bhc_map_insert not declared".to_string()))?;

        let loop_header = self
            .llvm_context()
            .append_basic_block(current_fn, "mfl_header");
        let loop_body = self
            .llvm_context()
            .append_basic_block(current_fn, "mfl_body");
        let loop_exit = self
            .llvm_context()
            .append_basic_block(current_fn, "mfl_exit");

        let entry_block = self.builder().get_insert_block().unwrap();
        self.builder()
            .build_unconditional_branch(loop_header)
            .map_err(|e| CodegenError::Internal(format!("branch failed: {:?}", e)))?;

        // Loop header
        self.builder().position_at_end(loop_header);
        let map_phi = self
            .builder()
            .build_phi(tm.ptr_type(), "map_acc")
            .map_err(|e| CodegenError::Internal(format!("phi failed: {:?}", e)))?;
        let cur_phi = self
            .builder()
            .build_phi(tm.ptr_type(), "cur_list")
            .map_err(|e| CodegenError::Internal(format!("phi failed: {:?}", e)))?;

        // Check if current list is nil (tag == 0)
        let cur_ptr = cur_phi.as_basic_value().into_pointer_value();
        let tag = self
            .builder()
            .build_load(tm.i64_type(), cur_ptr, "tag")
            .map_err(|e| CodegenError::Internal(format!("load tag failed: {:?}", e)))?
            .into_int_value();
        let is_nil = self
            .builder()
            .build_int_compare(
                inkwell::IntPredicate::EQ,
                tag,
                tm.i64_type().const_zero(),
                "is_nil",
            )
            .map_err(|e| CodegenError::Internal(format!("cmp failed: {:?}", e)))?;
        self.builder()
            .build_conditional_branch(is_nil, loop_exit, loop_body)
            .map_err(|e| CodegenError::Internal(format!("branch failed: {:?}", e)))?;

        // Loop body: extract (key, value) pair from head, insert into map
        self.builder().position_at_end(loop_body);
        // head = field 0 of cons cell (a pair)
        let head_ptr = self.extract_adt_field(cur_ptr, 2, 0)?;
        // tail = field 1 of cons cell
        let tail_ptr = self.extract_adt_field(cur_ptr, 2, 1)?;

        // pair is an ADT with (key, value) — extract key (field 0) and value (field 1)
        let key_ptr = self.extract_adt_field(head_ptr, 2, 0)?;
        let val_ptr = self.extract_adt_field(head_ptr, 2, 1)?;

        let key_int = self.coerce_to_int(key_ptr.into())?;
        let map_ptr = map_phi.as_basic_value().into_pointer_value();

        let new_map = self
            .builder()
            .build_call(
                insert_fn,
                &[key_int.into(), val_ptr.into(), map_ptr.into()],
                "new_map",
            )
            .map_err(|e| CodegenError::Internal(format!("insert call failed: {:?}", e)))?
            .try_as_basic_value()
            .basic()
            .ok_or_else(|| CodegenError::Internal("insert: void".to_string()))?;
        self.builder()
            .build_unconditional_branch(loop_header)
            .map_err(|e| CodegenError::Internal(format!("branch failed: {:?}", e)))?;

        map_phi.add_incoming(&[(&empty_map, entry_block), (&new_map, loop_body)]);
        cur_phi.add_incoming(&[(&list_ptr, entry_block), (&tail_ptr, loop_body)]);

        self.builder().position_at_end(loop_exit);
        Ok(Some(map_phi.as_basic_value()))
    }

    /// Lower `Data.Map.keys` — returns list of keys only.
    fn lower_builtin_map_keys(
        &mut self,
        map_expr: &Expr,
    ) -> CodegenResult<Option<BasicValueEnum<'ctx>>> {
        let map_val = self
            .lower_expr(map_expr)?
            .ok_or_else(|| CodegenError::Internal("map_keys: no map".to_string()))?;
        let map_ptr = self.value_to_ptr(map_val)?;

        let tm = self.type_mapper();
        let current_fn = self
            .builder()
            .get_insert_block()
            .and_then(|b| b.get_parent())
            .ok_or_else(|| CodegenError::Internal("no current function".to_string()))?;

        let count_fn = self
            .functions
            .get(&VarId::new(1000160))
            .ok_or_else(|| CodegenError::Internal("bhc_map_keys_count not declared".to_string()))?;
        let count = self
            .builder()
            .build_call(*count_fn, &[map_ptr.into()], "map_count")
            .map_err(|e| CodegenError::Internal(format!("call failed: {:?}", e)))?
            .try_as_basic_value()
            .basic()
            .ok_or_else(|| CodegenError::Internal("void".to_string()))?
            .into_int_value();

        let key_at_fn = *self
            .functions
            .get(&VarId::new(1000161))
            .ok_or_else(|| CodegenError::Internal("bhc_map_key_at not declared".to_string()))?;

        let loop_header = self
            .llvm_context()
            .append_basic_block(current_fn, "mk_header");
        let loop_body = self
            .llvm_context()
            .append_basic_block(current_fn, "mk_body");
        let loop_exit = self
            .llvm_context()
            .append_basic_block(current_fn, "mk_exit");

        let nil = self.build_nil()?;
        let start_idx = self
            .builder()
            .build_int_sub(count, tm.i64_type().const_int(1, false), "start_idx")
            .map_err(|e| CodegenError::Internal(format!("sub failed: {:?}", e)))?;

        let entry_block = self.builder().get_insert_block().unwrap();
        self.builder()
            .build_unconditional_branch(loop_header)
            .map_err(|e| CodegenError::Internal(format!("branch failed: {:?}", e)))?;

        self.builder().position_at_end(loop_header);
        let acc_phi = self
            .builder()
            .build_phi(tm.ptr_type(), "acc")
            .map_err(|e| CodegenError::Internal(format!("phi failed: {:?}", e)))?;
        let idx_phi = self
            .builder()
            .build_phi(tm.i64_type(), "idx")
            .map_err(|e| CodegenError::Internal(format!("phi failed: {:?}", e)))?;

        let idx = idx_phi.as_basic_value().into_int_value();
        let done = self
            .builder()
            .build_int_compare(
                inkwell::IntPredicate::SLT,
                idx,
                tm.i64_type().const_zero(),
                "done",
            )
            .map_err(|e| CodegenError::Internal(format!("cmp failed: {:?}", e)))?;
        self.builder()
            .build_conditional_branch(done, loop_exit, loop_body)
            .map_err(|e| CodegenError::Internal(format!("branch failed: {:?}", e)))?;

        self.builder().position_at_end(loop_body);
        let key_i64 = self
            .builder()
            .build_call(key_at_fn, &[map_ptr.into(), idx.into()], "key")
            .map_err(|e| CodegenError::Internal(format!("call failed: {:?}", e)))?
            .try_as_basic_value()
            .basic()
            .ok_or_else(|| CodegenError::Internal("void".to_string()))?
            .into_int_value();
        let key_ptr = self.int_to_ptr(key_i64)?;

        let new_acc = self.build_cons(key_ptr.into(), acc_phi.as_basic_value())?;
        let new_idx = self
            .builder()
            .build_int_sub(idx, tm.i64_type().const_int(1, false), "new_idx")
            .map_err(|e| CodegenError::Internal(format!("sub failed: {:?}", e)))?;
        self.builder()
            .build_unconditional_branch(loop_header)
            .map_err(|e| CodegenError::Internal(format!("branch failed: {:?}", e)))?;

        acc_phi.add_incoming(&[(&nil, entry_block), (&new_acc, loop_body)]);
        idx_phi.add_incoming(&[(&start_idx, entry_block), (&new_idx, loop_body)]);

        self.builder().position_at_end(loop_exit);
        Ok(Some(acc_phi.as_basic_value()))
    }

    /// Lower `Data.Map.elems` — returns list of values only.
    fn lower_builtin_map_elems(
        &mut self,
        map_expr: &Expr,
    ) -> CodegenResult<Option<BasicValueEnum<'ctx>>> {
        let map_val = self
            .lower_expr(map_expr)?
            .ok_or_else(|| CodegenError::Internal("map_elems: no map".to_string()))?;
        let map_ptr = self.value_to_ptr(map_val)?;

        let tm = self.type_mapper();
        let current_fn = self
            .builder()
            .get_insert_block()
            .and_then(|b| b.get_parent())
            .ok_or_else(|| CodegenError::Internal("no current function".to_string()))?;

        let count_fn = self
            .functions
            .get(&VarId::new(1000160))
            .ok_or_else(|| CodegenError::Internal("bhc_map_keys_count not declared".to_string()))?;
        let count = self
            .builder()
            .build_call(*count_fn, &[map_ptr.into()], "map_count")
            .map_err(|e| CodegenError::Internal(format!("call failed: {:?}", e)))?
            .try_as_basic_value()
            .basic()
            .ok_or_else(|| CodegenError::Internal("void".to_string()))?
            .into_int_value();

        let val_at_fn = *self
            .functions
            .get(&VarId::new(1000162))
            .ok_or_else(|| CodegenError::Internal("bhc_map_value_at not declared".to_string()))?;

        let loop_header = self
            .llvm_context()
            .append_basic_block(current_fn, "me_header");
        let loop_body = self
            .llvm_context()
            .append_basic_block(current_fn, "me_body");
        let loop_exit = self
            .llvm_context()
            .append_basic_block(current_fn, "me_exit");

        let nil = self.build_nil()?;
        let start_idx = self
            .builder()
            .build_int_sub(count, tm.i64_type().const_int(1, false), "start_idx")
            .map_err(|e| CodegenError::Internal(format!("sub failed: {:?}", e)))?;

        let entry_block = self.builder().get_insert_block().unwrap();
        self.builder()
            .build_unconditional_branch(loop_header)
            .map_err(|e| CodegenError::Internal(format!("branch failed: {:?}", e)))?;

        self.builder().position_at_end(loop_header);
        let acc_phi = self
            .builder()
            .build_phi(tm.ptr_type(), "acc")
            .map_err(|e| CodegenError::Internal(format!("phi failed: {:?}", e)))?;
        let idx_phi = self
            .builder()
            .build_phi(tm.i64_type(), "idx")
            .map_err(|e| CodegenError::Internal(format!("phi failed: {:?}", e)))?;

        let idx = idx_phi.as_basic_value().into_int_value();
        let done = self
            .builder()
            .build_int_compare(
                inkwell::IntPredicate::SLT,
                idx,
                tm.i64_type().const_zero(),
                "done",
            )
            .map_err(|e| CodegenError::Internal(format!("cmp failed: {:?}", e)))?;
        self.builder()
            .build_conditional_branch(done, loop_exit, loop_body)
            .map_err(|e| CodegenError::Internal(format!("branch failed: {:?}", e)))?;

        self.builder().position_at_end(loop_body);
        let val = self
            .builder()
            .build_call(val_at_fn, &[map_ptr.into(), idx.into()], "val")
            .map_err(|e| CodegenError::Internal(format!("call failed: {:?}", e)))?
            .try_as_basic_value()
            .basic()
            .ok_or_else(|| CodegenError::Internal("void".to_string()))?;

        let new_acc = self.build_cons(val, acc_phi.as_basic_value())?;
        let new_idx = self
            .builder()
            .build_int_sub(idx, tm.i64_type().const_int(1, false), "new_idx")
            .map_err(|e| CodegenError::Internal(format!("sub failed: {:?}", e)))?;
        self.builder()
            .build_unconditional_branch(loop_header)
            .map_err(|e| CodegenError::Internal(format!("branch failed: {:?}", e)))?;

        acc_phi.add_incoming(&[(&nil, entry_block), (&new_acc, loop_body)]);
        idx_phi.add_incoming(&[(&start_idx, entry_block), (&new_idx, loop_body)]);

        self.builder().position_at_end(loop_exit);
        Ok(Some(acc_phi.as_basic_value()))
    }

    // --- Data.Set handlers ---

    /// Lower `Data.Set.empty`.
    fn lower_builtin_set_empty(&mut self) -> CodegenResult<Option<BasicValueEnum<'ctx>>> {
        let rts_fn = self
            .functions
            .get(&VarId::new(1000120))
            .ok_or_else(|| CodegenError::Internal("bhc_set_empty not declared".to_string()))?;
        let result = self
            .builder()
            .build_call(*rts_fn, &[], "set_empty")
            .map_err(|e| CodegenError::Internal(format!("set_empty call failed: {:?}", e)))?
            .try_as_basic_value()
            .basic()
            .ok_or_else(|| CodegenError::Internal("set_empty: returned void".to_string()))?;
        Ok(Some(result))
    }

    /// Lower `Data.Set.singleton`.
    fn lower_builtin_set_singleton(
        &mut self,
        val_expr: &Expr,
    ) -> CodegenResult<Option<BasicValueEnum<'ctx>>> {
        let val = self
            .lower_expr(val_expr)?
            .ok_or_else(|| CodegenError::Internal("set_singleton: no value".to_string()))?;
        let val_int = self.coerce_to_int(val)?;
        let rts_fn = self
            .functions
            .get(&VarId::new(1000121))
            .ok_or_else(|| CodegenError::Internal("bhc_set_singleton not declared".to_string()))?;
        let result = self
            .builder()
            .build_call(*rts_fn, &[val_int.into()], "set_singleton")
            .map_err(|e| CodegenError::Internal(format!("set_singleton call failed: {:?}", e)))?
            .try_as_basic_value()
            .basic()
            .ok_or_else(|| CodegenError::Internal("set_singleton: returned void".to_string()))?;
        Ok(Some(result))
    }

    /// Lower `Data.Set.null`.
    fn lower_builtin_set_null(
        &mut self,
        set_expr: &Expr,
    ) -> CodegenResult<Option<BasicValueEnum<'ctx>>> {
        let set_val = self
            .lower_expr(set_expr)?
            .ok_or_else(|| CodegenError::Internal("set_null: no set".to_string()))?;
        let set_ptr = self.value_to_ptr(set_val)?;
        let rts_fn = self
            .functions
            .get(&VarId::new(1000122))
            .ok_or_else(|| CodegenError::Internal("bhc_set_null not declared".to_string()))?;
        let result = self
            .builder()
            .build_call(*rts_fn, &[set_ptr.into()], "set_null")
            .map_err(|e| CodegenError::Internal(format!("set_null call failed: {:?}", e)))?
            .try_as_basic_value()
            .basic()
            .ok_or_else(|| CodegenError::Internal("set_null: returned void".to_string()))?;
        self.allocate_bool_adt(result.into_int_value(), "set_null_bool")
    }

    /// Lower `Data.Set.size`.
    fn lower_builtin_set_size(
        &mut self,
        set_expr: &Expr,
    ) -> CodegenResult<Option<BasicValueEnum<'ctx>>> {
        let set_val = self
            .lower_expr(set_expr)?
            .ok_or_else(|| CodegenError::Internal("set_size: no set".to_string()))?;
        let set_ptr = self.value_to_ptr(set_val)?;
        let rts_fn = self
            .functions
            .get(&VarId::new(1000123))
            .ok_or_else(|| CodegenError::Internal("bhc_set_size not declared".to_string()))?;
        let result = self
            .builder()
            .build_call(*rts_fn, &[set_ptr.into()], "set_size")
            .map_err(|e| CodegenError::Internal(format!("set_size call failed: {:?}", e)))?
            .try_as_basic_value()
            .basic()
            .ok_or_else(|| CodegenError::Internal("set_size: returned void".to_string()))?;
        Ok(Some(self.int_to_ptr(result.into_int_value())?.into()))
    }

    /// Lower `Data.Set.member`.
    fn lower_builtin_set_member(
        &mut self,
        val_expr: &Expr,
        set_expr: &Expr,
    ) -> CodegenResult<Option<BasicValueEnum<'ctx>>> {
        let val = self
            .lower_expr(val_expr)?
            .ok_or_else(|| CodegenError::Internal("set_member: no value".to_string()))?;
        let set_val = self
            .lower_expr(set_expr)?
            .ok_or_else(|| CodegenError::Internal("set_member: no set".to_string()))?;
        let val_int = self.coerce_to_int(val)?;
        let set_ptr = self.value_to_ptr(set_val)?;
        let rts_fn = self
            .functions
            .get(&VarId::new(1000124))
            .ok_or_else(|| CodegenError::Internal("bhc_set_member not declared".to_string()))?;
        let result = self
            .builder()
            .build_call(*rts_fn, &[val_int.into(), set_ptr.into()], "set_member")
            .map_err(|e| CodegenError::Internal(format!("set_member call failed: {:?}", e)))?
            .try_as_basic_value()
            .basic()
            .ok_or_else(|| CodegenError::Internal("set_member: returned void".to_string()))?;
        self.allocate_bool_adt(result.into_int_value(), "set_member_bool")
    }

    /// Lower `Data.Set.insert`.
    fn lower_builtin_set_insert(
        &mut self,
        val_expr: &Expr,
        set_expr: &Expr,
    ) -> CodegenResult<Option<BasicValueEnum<'ctx>>> {
        let val = self
            .lower_expr(val_expr)?
            .ok_or_else(|| CodegenError::Internal("set_insert: no value".to_string()))?;
        let set_val = self
            .lower_expr(set_expr)?
            .ok_or_else(|| CodegenError::Internal("set_insert: no set".to_string()))?;
        let val_int = self.coerce_to_int(val)?;
        let set_ptr = self.value_to_ptr(set_val)?;
        let rts_fn = self
            .functions
            .get(&VarId::new(1000125))
            .ok_or_else(|| CodegenError::Internal("bhc_set_insert not declared".to_string()))?;
        let result = self
            .builder()
            .build_call(*rts_fn, &[val_int.into(), set_ptr.into()], "set_insert")
            .map_err(|e| CodegenError::Internal(format!("set_insert call failed: {:?}", e)))?
            .try_as_basic_value()
            .basic()
            .ok_or_else(|| CodegenError::Internal("set_insert: returned void".to_string()))?;
        Ok(Some(result))
    }

    /// Lower `Data.Set.delete`.
    fn lower_builtin_set_delete(
        &mut self,
        val_expr: &Expr,
        set_expr: &Expr,
    ) -> CodegenResult<Option<BasicValueEnum<'ctx>>> {
        let val = self
            .lower_expr(val_expr)?
            .ok_or_else(|| CodegenError::Internal("set_delete: no value".to_string()))?;
        let set_val = self
            .lower_expr(set_expr)?
            .ok_or_else(|| CodegenError::Internal("set_delete: no set".to_string()))?;
        let val_int = self.coerce_to_int(val)?;
        let set_ptr = self.value_to_ptr(set_val)?;
        let rts_fn = self
            .functions
            .get(&VarId::new(1000126))
            .ok_or_else(|| CodegenError::Internal("bhc_set_delete not declared".to_string()))?;
        let result = self
            .builder()
            .build_call(*rts_fn, &[val_int.into(), set_ptr.into()], "set_delete")
            .map_err(|e| CodegenError::Internal(format!("set_delete call failed: {:?}", e)))?
            .try_as_basic_value()
            .basic()
            .ok_or_else(|| CodegenError::Internal("set_delete: returned void".to_string()))?;
        Ok(Some(result))
    }

    /// Lower Data.Set binary operations (union, intersection, difference).
    fn lower_builtin_set_binary(
        &mut self,
        set1_expr: &Expr,
        set2_expr: &Expr,
        rts_id: usize,
        name: &str,
    ) -> CodegenResult<Option<BasicValueEnum<'ctx>>> {
        let s1 = self
            .lower_expr(set1_expr)?
            .ok_or_else(|| CodegenError::Internal(format!("{}: no set1", name)))?;
        let s2 = self
            .lower_expr(set2_expr)?
            .ok_or_else(|| CodegenError::Internal(format!("{}: no set2", name)))?;
        let s1_ptr = self.value_to_ptr(s1)?;
        let s2_ptr = self.value_to_ptr(s2)?;
        let rts_fn = self
            .functions
            .get(&VarId::new(rts_id))
            .ok_or_else(|| CodegenError::Internal(format!("{} RTS function not declared", name)))?;
        let result = self
            .builder()
            .build_call(*rts_fn, &[s1_ptr.into(), s2_ptr.into()], name)
            .map_err(|e| CodegenError::Internal(format!("{} call failed: {:?}", name, e)))?
            .try_as_basic_value()
            .basic()
            .ok_or_else(|| CodegenError::Internal(format!("{}: returned void", name)))?;
        Ok(Some(result))
    }

    /// Lower Data.Set predicate operations (isSubsetOf, isProperSubsetOf).
    fn lower_builtin_set_predicate(
        &mut self,
        set1_expr: &Expr,
        set2_expr: &Expr,
        rts_id: usize,
        name: &str,
    ) -> CodegenResult<Option<BasicValueEnum<'ctx>>> {
        let s1 = self
            .lower_expr(set1_expr)?
            .ok_or_else(|| CodegenError::Internal(format!("{}: no set1", name)))?;
        let s2 = self
            .lower_expr(set2_expr)?
            .ok_or_else(|| CodegenError::Internal(format!("{}: no set2", name)))?;
        let s1_ptr = self.value_to_ptr(s1)?;
        let s2_ptr = self.value_to_ptr(s2)?;
        let rts_fn = self
            .functions
            .get(&VarId::new(rts_id))
            .ok_or_else(|| CodegenError::Internal(format!("{} RTS function not declared", name)))?;
        let result = self
            .builder()
            .build_call(*rts_fn, &[s1_ptr.into(), s2_ptr.into()], name)
            .map_err(|e| CodegenError::Internal(format!("{} call failed: {:?}", name, e)))?
            .try_as_basic_value()
            .basic()
            .ok_or_else(|| CodegenError::Internal(format!("{}: returned void", name)))?;
        Ok(Some(self.int_to_ptr(result.into_int_value())?.into()))
    }

    /// Lower `Data.Set.findMin` / `findMax`.
    fn lower_builtin_set_find_extremum(
        &mut self,
        set_expr: &Expr,
        rts_id: usize,
        name: &str,
    ) -> CodegenResult<Option<BasicValueEnum<'ctx>>> {
        let set_val = self
            .lower_expr(set_expr)?
            .ok_or_else(|| CodegenError::Internal(format!("{}: no set", name)))?;
        let set_ptr = self.value_to_ptr(set_val)?;
        let rts_fn = self
            .functions
            .get(&VarId::new(rts_id))
            .ok_or_else(|| CodegenError::Internal(format!("{} RTS function not declared", name)))?;
        let result = self
            .builder()
            .build_call(*rts_fn, &[set_ptr.into()], name)
            .map_err(|e| CodegenError::Internal(format!("{} call failed: {:?}", name, e)))?
            .try_as_basic_value()
            .basic()
            .ok_or_else(|| CodegenError::Internal(format!("{}: returned void", name)))?;
        Ok(Some(self.int_to_ptr(result.into_int_value())?.into()))
    }

    /// Lower `Data.Set.deleteMin` / `deleteMax`.
    fn lower_builtin_set_delete_extremum(
        &mut self,
        set_expr: &Expr,
        rts_id: usize,
        name: &str,
    ) -> CodegenResult<Option<BasicValueEnum<'ctx>>> {
        let set_val = self
            .lower_expr(set_expr)?
            .ok_or_else(|| CodegenError::Internal(format!("{}: no set", name)))?;
        let set_ptr = self.value_to_ptr(set_val)?;
        let rts_fn = self
            .functions
            .get(&VarId::new(rts_id))
            .ok_or_else(|| CodegenError::Internal(format!("{} RTS function not declared", name)))?;
        let result = self
            .builder()
            .build_call(*rts_fn, &[set_ptr.into()], name)
            .map_err(|e| CodegenError::Internal(format!("{} call failed: {:?}", name, e)))?
            .try_as_basic_value()
            .basic()
            .ok_or_else(|| CodegenError::Internal(format!("{}: returned void", name)))?;
        Ok(Some(result))
    }

    /// Lower `Data.Set.toList` / `toAscList` / `toDescList` / `elems` (stub).
    fn lower_builtin_set_to_list(
        &mut self,
        set_expr: &Expr,
    ) -> CodegenResult<Option<BasicValueEnum<'ctx>>> {
        let set_val = self
            .lower_expr(set_expr)?
            .ok_or_else(|| CodegenError::Internal("set_toList: no set".to_string()))?;
        let set_ptr = self.value_to_ptr(set_val)?;

        let tm = self.type_mapper();
        let current_fn = self
            .builder()
            .get_insert_block()
            .and_then(|b| b.get_parent())
            .ok_or_else(|| CodegenError::Internal("no current function".to_string()))?;

        // Get element count
        let count_fn = self
            .functions
            .get(&VarId::new(1000163))
            .ok_or_else(|| CodegenError::Internal("bhc_set_elem_count not declared".to_string()))?;
        let count = self
            .builder()
            .build_call(*count_fn, &[set_ptr.into()], "set_count")
            .map_err(|e| CodegenError::Internal(format!("set_count call failed: {:?}", e)))?
            .try_as_basic_value()
            .basic()
            .ok_or_else(|| CodegenError::Internal("set_count: void".to_string()))?
            .into_int_value();

        let elem_at_fn = *self
            .functions
            .get(&VarId::new(1000164))
            .ok_or_else(|| CodegenError::Internal("bhc_set_elem_at not declared".to_string()))?;

        // Build list backwards: iterate from (count-1) down to 0
        let loop_header = self
            .llvm_context()
            .append_basic_block(current_fn, "stl_header");
        let loop_body = self
            .llvm_context()
            .append_basic_block(current_fn, "stl_body");
        let loop_exit = self
            .llvm_context()
            .append_basic_block(current_fn, "stl_exit");

        let nil = self.build_nil()?;
        let start_idx = self
            .builder()
            .build_int_sub(count, tm.i64_type().const_int(1, false), "start_idx")
            .map_err(|e| CodegenError::Internal(format!("sub failed: {:?}", e)))?;

        let entry_block = self.builder().get_insert_block().unwrap();
        self.builder()
            .build_unconditional_branch(loop_header)
            .map_err(|e| CodegenError::Internal(format!("branch failed: {:?}", e)))?;

        self.builder().position_at_end(loop_header);
        let acc_phi = self
            .builder()
            .build_phi(tm.ptr_type(), "acc")
            .map_err(|e| CodegenError::Internal(format!("phi failed: {:?}", e)))?;
        let idx_phi = self
            .builder()
            .build_phi(tm.i64_type(), "idx")
            .map_err(|e| CodegenError::Internal(format!("phi failed: {:?}", e)))?;

        let idx = idx_phi.as_basic_value().into_int_value();
        let done = self
            .builder()
            .build_int_compare(
                inkwell::IntPredicate::SLT,
                idx,
                tm.i64_type().const_zero(),
                "done",
            )
            .map_err(|e| CodegenError::Internal(format!("cmp failed: {:?}", e)))?;
        self.builder()
            .build_conditional_branch(done, loop_exit, loop_body)
            .map_err(|e| CodegenError::Internal(format!("branch failed: {:?}", e)))?;

        // Loop body: get element at index, cons onto accumulator
        self.builder().position_at_end(loop_body);
        let elem_i64 = self
            .builder()
            .build_call(elem_at_fn, &[set_ptr.into(), idx.into()], "elem")
            .map_err(|e| CodegenError::Internal(format!("elem_at call failed: {:?}", e)))?
            .try_as_basic_value()
            .basic()
            .ok_or_else(|| CodegenError::Internal("elem_at: void".to_string()))?
            .into_int_value();
        let elem_ptr = self.int_to_ptr(elem_i64)?;

        let new_acc = self.build_cons(elem_ptr.into(), acc_phi.as_basic_value())?;
        let new_idx = self
            .builder()
            .build_int_sub(idx, tm.i64_type().const_int(1, false), "new_idx")
            .map_err(|e| CodegenError::Internal(format!("sub failed: {:?}", e)))?;
        self.builder()
            .build_unconditional_branch(loop_header)
            .map_err(|e| CodegenError::Internal(format!("branch failed: {:?}", e)))?;

        acc_phi.add_incoming(&[(&nil, entry_block), (&new_acc, loop_body)]);
        idx_phi.add_incoming(&[(&start_idx, entry_block), (&new_idx, loop_body)]);

        self.builder().position_at_end(loop_exit);
        Ok(Some(acc_phi.as_basic_value()))
    }

    /// Lower `Data.Set.fromList` — iterates list, inserting each element.
    fn lower_builtin_set_from_list(
        &mut self,
        list_expr: &Expr,
    ) -> CodegenResult<Option<BasicValueEnum<'ctx>>> {
        let list_val = self
            .lower_expr(list_expr)?
            .ok_or_else(|| CodegenError::Internal("set_fromList: no list".to_string()))?;
        let list_ptr = self.value_to_ptr(list_val)?;

        let tm = self.type_mapper();
        let current_fn = self
            .builder()
            .get_insert_block()
            .and_then(|b| b.get_parent())
            .ok_or_else(|| CodegenError::Internal("no current function".to_string()))?;

        // Start with empty set
        let empty_fn = self
            .functions
            .get(&VarId::new(1000120))
            .ok_or_else(|| CodegenError::Internal("bhc_set_empty not declared".to_string()))?;
        let empty_set = self
            .builder()
            .build_call(*empty_fn, &[], "empty_set")
            .map_err(|e| CodegenError::Internal(format!("empty call failed: {:?}", e)))?
            .try_as_basic_value()
            .basic()
            .ok_or_else(|| CodegenError::Internal("empty: void".to_string()))?;

        let insert_fn = *self
            .functions
            .get(&VarId::new(1000125))
            .ok_or_else(|| CodegenError::Internal("bhc_set_insert not declared".to_string()))?;

        let loop_header = self
            .llvm_context()
            .append_basic_block(current_fn, "sfl_header");
        let loop_body = self
            .llvm_context()
            .append_basic_block(current_fn, "sfl_body");
        let loop_exit = self
            .llvm_context()
            .append_basic_block(current_fn, "sfl_exit");

        let entry_block = self.builder().get_insert_block().unwrap();
        self.builder()
            .build_unconditional_branch(loop_header)
            .map_err(|e| CodegenError::Internal(format!("branch failed: {:?}", e)))?;

        self.builder().position_at_end(loop_header);
        let set_phi = self
            .builder()
            .build_phi(tm.ptr_type(), "set_acc")
            .map_err(|e| CodegenError::Internal(format!("phi failed: {:?}", e)))?;
        let cur_phi = self
            .builder()
            .build_phi(tm.ptr_type(), "cur_list")
            .map_err(|e| CodegenError::Internal(format!("phi failed: {:?}", e)))?;

        // Check if current list is nil (tag == 0)
        let cur_ptr = cur_phi.as_basic_value().into_pointer_value();
        let tag = self
            .builder()
            .build_load(tm.i64_type(), cur_ptr, "tag")
            .map_err(|e| CodegenError::Internal(format!("load tag failed: {:?}", e)))?
            .into_int_value();
        let is_nil = self
            .builder()
            .build_int_compare(
                inkwell::IntPredicate::EQ,
                tag,
                tm.i64_type().const_zero(),
                "is_nil",
            )
            .map_err(|e| CodegenError::Internal(format!("cmp failed: {:?}", e)))?;
        self.builder()
            .build_conditional_branch(is_nil, loop_exit, loop_body)
            .map_err(|e| CodegenError::Internal(format!("branch failed: {:?}", e)))?;

        // Loop body: extract head element, insert into set
        self.builder().position_at_end(loop_body);
        let head_ptr = self.extract_adt_field(cur_ptr, 2, 0)?;
        let tail_ptr = self.extract_adt_field(cur_ptr, 2, 1)?;

        let elem_int = self.coerce_to_int(head_ptr.into())?;
        let set_ptr = set_phi.as_basic_value().into_pointer_value();

        let new_set = self
            .builder()
            .build_call(insert_fn, &[elem_int.into(), set_ptr.into()], "new_set")
            .map_err(|e| CodegenError::Internal(format!("insert call failed: {:?}", e)))?
            .try_as_basic_value()
            .basic()
            .ok_or_else(|| CodegenError::Internal("insert: void".to_string()))?;
        self.builder()
            .build_unconditional_branch(loop_header)
            .map_err(|e| CodegenError::Internal(format!("branch failed: {:?}", e)))?;

        set_phi.add_incoming(&[(&empty_set, entry_block), (&new_set, loop_body)]);
        cur_phi.add_incoming(&[(&list_ptr, entry_block), (&tail_ptr, loop_body)]);

        self.builder().position_at_end(loop_exit);
        Ok(Some(set_phi.as_basic_value()))
    }

    /// Lower `Data.Set.lookupMin` / `lookupMax` - returns Maybe.
    fn lower_builtin_set_lookup_extremum(
        &mut self,
        set_expr: &Expr,
        rts_id: usize,
        name: &str,
    ) -> CodegenResult<Option<BasicValueEnum<'ctx>>> {
        let set_val = self
            .lower_expr(set_expr)?
            .ok_or_else(|| CodegenError::Internal(format!("{}: no set", name)))?;
        let set_ptr = self.value_to_ptr(set_val)?;
        // Check if set is empty
        let null_fn = self
            .functions
            .get(&VarId::new(1000122))
            .ok_or_else(|| CodegenError::Internal("bhc_set_null not declared".to_string()))?;
        let is_empty = self
            .builder()
            .build_call(*null_fn, &[set_ptr.into()], "set_is_empty")
            .map_err(|e| CodegenError::Internal(format!("{}: null check failed: {:?}", name, e)))?
            .try_as_basic_value()
            .basic()
            .ok_or_else(|| CodegenError::Internal(format!("{}: null returned void", name)))?;
        let is_empty_bool = self
            .builder()
            .build_int_compare(
                inkwell::IntPredicate::NE,
                is_empty.into_int_value(),
                self.type_mapper().i64_type().const_zero(),
                "is_empty_bool",
            )
            .map_err(|e| CodegenError::Internal(format!("{}: compare failed: {:?}", name, e)))?;
        let nothing = self.alloc_adt(0, 0)?;
        let rts_fn = self
            .functions
            .get(&VarId::new(rts_id))
            .ok_or_else(|| CodegenError::Internal(format!("{} RTS function not declared", name)))?;
        let val = self
            .builder()
            .build_call(*rts_fn, &[set_ptr.into()], name)
            .map_err(|e| CodegenError::Internal(format!("{} call failed: {:?}", name, e)))?
            .try_as_basic_value()
            .basic()
            .ok_or_else(|| CodegenError::Internal(format!("{}: returned void", name)))?;
        let val_ptr = self.int_to_ptr(val.into_int_value())?;
        let just = self.alloc_adt(1, 1)?;
        self.store_adt_field(just, 1, 0, val_ptr.into())?;
        let result = self
            .builder()
            .build_select(is_empty_bool, nothing, just, "maybe_extremum")
            .map_err(|e| CodegenError::Internal(format!("{}: select failed: {:?}", name, e)))?;
        Ok(Some(result))
    }

    // ========================================================================
    // Text / ByteArray handler methods
    // ========================================================================

    /// Text nullary: () -> ptr (e.g. text_empty)
    fn lower_builtin_text_nullary(
        &mut self,
        rts_id: usize,
        label: &str,
    ) -> CodegenResult<Option<BasicValueEnum<'ctx>>> {
        let rts_fn = self.functions.get(&VarId::new(rts_id)).ok_or_else(|| {
            CodegenError::Internal(format!("{}: RTS function {} not declared", label, rts_id))
        })?;
        let result = self
            .builder()
            .build_call(*rts_fn, &[], label)
            .map_err(|e| CodegenError::Internal(format!("{} call failed: {:?}", label, e)))?
            .try_as_basic_value()
            .basic()
            .ok_or_else(|| CodegenError::Internal(format!("{}: returned void", label)))?;
        Ok(Some(result))
    }

    /// Text unary: (ptr) -> ptr (e.g. text_tail, text_reverse)
    fn lower_builtin_text_unary_ptr_to_ptr(
        &mut self,
        expr: &Expr,
        rts_id: usize,
        label: &str,
    ) -> CodegenResult<Option<BasicValueEnum<'ctx>>> {
        let val = self
            .lower_expr(expr)?
            .ok_or_else(|| CodegenError::Internal(format!("{}: no value", label)))?;
        let ptr = self.value_to_ptr(val)?;
        let rts_fn = self.functions.get(&VarId::new(rts_id)).ok_or_else(|| {
            CodegenError::Internal(format!("{}: RTS function {} not declared", label, rts_id))
        })?;
        let result = self
            .builder()
            .build_call(*rts_fn, &[ptr.into()], label)
            .map_err(|e| CodegenError::Internal(format!("{} call failed: {:?}", label, e)))?
            .try_as_basic_value()
            .basic()
            .ok_or_else(|| CodegenError::Internal(format!("{}: returned void", label)))?;
        Ok(Some(result))
    }

    /// Text unary: (ptr) -> i64 (e.g. text_null, text_length, text_head)
    fn lower_builtin_text_unary_ptr_to_int(
        &mut self,
        expr: &Expr,
        rts_id: usize,
        label: &str,
    ) -> CodegenResult<Option<BasicValueEnum<'ctx>>> {
        let val = self
            .lower_expr(expr)?
            .ok_or_else(|| CodegenError::Internal(format!("{}: no value", label)))?;
        let ptr = self.value_to_ptr(val)?;
        let rts_fn = self.functions.get(&VarId::new(rts_id)).ok_or_else(|| {
            CodegenError::Internal(format!("{}: RTS function {} not declared", label, rts_id))
        })?;
        let result = self
            .builder()
            .build_call(*rts_fn, &[ptr.into()], label)
            .map_err(|e| CodegenError::Internal(format!("{} call failed: {:?}", label, e)))?
            .try_as_basic_value()
            .basic()
            .ok_or_else(|| CodegenError::Internal(format!("{}: returned void", label)))?;
        Ok(Some(self.int_to_ptr(result.into_int_value())?.into()))
    }

    /// Text unary: (i64) -> ptr (e.g. text_singleton)
    fn lower_builtin_text_unary_int_to_ptr(
        &mut self,
        expr: &Expr,
        rts_id: usize,
        label: &str,
    ) -> CodegenResult<Option<BasicValueEnum<'ctx>>> {
        let val = self
            .lower_expr(expr)?
            .ok_or_else(|| CodegenError::Internal(format!("{}: no value", label)))?;
        let int_val = self.coerce_to_int(val)?;
        let rts_fn = self.functions.get(&VarId::new(rts_id)).ok_or_else(|| {
            CodegenError::Internal(format!("{}: RTS function {} not declared", label, rts_id))
        })?;
        let result = self
            .builder()
            .build_call(*rts_fn, &[int_val.into()], label)
            .map_err(|e| CodegenError::Internal(format!("{} call failed: {:?}", label, e)))?
            .try_as_basic_value()
            .basic()
            .ok_or_else(|| CodegenError::Internal(format!("{}: returned void", label)))?;
        Ok(Some(result))
    }

    /// Text binary: (ptr, ptr) -> ptr (e.g. text_append)
    fn lower_builtin_text_binary_ptr_to_ptr(
        &mut self,
        expr_a: &Expr,
        expr_b: &Expr,
        rts_id: usize,
        label: &str,
    ) -> CodegenResult<Option<BasicValueEnum<'ctx>>> {
        let a = self
            .lower_expr(expr_a)?
            .ok_or_else(|| CodegenError::Internal(format!("{}: no a", label)))?;
        let b = self
            .lower_expr(expr_b)?
            .ok_or_else(|| CodegenError::Internal(format!("{}: no b", label)))?;
        let a_ptr = self.value_to_ptr(a)?;
        let b_ptr = self.value_to_ptr(b)?;
        let rts_fn = self.functions.get(&VarId::new(rts_id)).ok_or_else(|| {
            CodegenError::Internal(format!("{}: RTS function {} not declared", label, rts_id))
        })?;
        let result = self
            .builder()
            .build_call(*rts_fn, &[a_ptr.into(), b_ptr.into()], label)
            .map_err(|e| CodegenError::Internal(format!("{} call failed: {:?}", label, e)))?
            .try_as_basic_value()
            .basic()
            .ok_or_else(|| CodegenError::Internal(format!("{}: returned void", label)))?;
        Ok(Some(result))
    }

    /// Text binary: (ptr, ptr) -> i64 (e.g. text_eq, text_compare)
    fn lower_builtin_text_binary_ptr_to_int(
        &mut self,
        expr_a: &Expr,
        expr_b: &Expr,
        rts_id: usize,
        label: &str,
    ) -> CodegenResult<Option<BasicValueEnum<'ctx>>> {
        let a = self
            .lower_expr(expr_a)?
            .ok_or_else(|| CodegenError::Internal(format!("{}: no a", label)))?;
        let b = self
            .lower_expr(expr_b)?
            .ok_or_else(|| CodegenError::Internal(format!("{}: no b", label)))?;
        let a_ptr = self.value_to_ptr(a)?;
        let b_ptr = self.value_to_ptr(b)?;
        let rts_fn = self.functions.get(&VarId::new(rts_id)).ok_or_else(|| {
            CodegenError::Internal(format!("{}: RTS function {} not declared", label, rts_id))
        })?;
        let result = self
            .builder()
            .build_call(*rts_fn, &[a_ptr.into(), b_ptr.into()], label)
            .map_err(|e| CodegenError::Internal(format!("{} call failed: {:?}", label, e)))?
            .try_as_basic_value()
            .basic()
            .ok_or_else(|| CodegenError::Internal(format!("{}: returned void", label)))?;
        Ok(Some(self.int_to_ptr(result.into_int_value())?.into()))
    }

    /// Text: (i64, ptr) -> ptr (e.g. text_take, text_drop)
    fn lower_builtin_text_int_ptr_to_ptr(
        &mut self,
        int_expr: &Expr,
        ptr_expr: &Expr,
        rts_id: usize,
        label: &str,
    ) -> CodegenResult<Option<BasicValueEnum<'ctx>>> {
        let n = self
            .lower_expr(int_expr)?
            .ok_or_else(|| CodegenError::Internal(format!("{}: no int", label)))?;
        let t = self
            .lower_expr(ptr_expr)?
            .ok_or_else(|| CodegenError::Internal(format!("{}: no ptr", label)))?;
        let n_int = self.coerce_to_int(n)?;
        let t_ptr = self.value_to_ptr(t)?;
        let rts_fn = self.functions.get(&VarId::new(rts_id)).ok_or_else(|| {
            CodegenError::Internal(format!("{}: RTS function {} not declared", label, rts_id))
        })?;
        let result = self
            .builder()
            .build_call(*rts_fn, &[n_int.into(), t_ptr.into()], label)
            .map_err(|e| CodegenError::Internal(format!("{} call failed: {:?}", label, e)))?
            .try_as_basic_value()
            .basic()
            .ok_or_else(|| CodegenError::Internal(format!("{}: returned void", label)))?;
        Ok(Some(result))
    }

    /// ByteArray: (ptr, i64) -> i64 (e.g. ba_index)
    fn lower_builtin_text_ptr_int_to_int(
        &mut self,
        ptr_expr: &Expr,
        int_expr: &Expr,
        rts_id: usize,
        label: &str,
    ) -> CodegenResult<Option<BasicValueEnum<'ctx>>> {
        let p = self
            .lower_expr(ptr_expr)?
            .ok_or_else(|| CodegenError::Internal(format!("{}: no ptr", label)))?;
        let n = self
            .lower_expr(int_expr)?
            .ok_or_else(|| CodegenError::Internal(format!("{}: no int", label)))?;
        let p_ptr = self.value_to_ptr(p)?;
        let n_int = self.coerce_to_int(n)?;
        let rts_fn = self.functions.get(&VarId::new(rts_id)).ok_or_else(|| {
            CodegenError::Internal(format!("{}: RTS function {} not declared", label, rts_id))
        })?;
        let result = self
            .builder()
            .build_call(*rts_fn, &[p_ptr.into(), n_int.into()], label)
            .map_err(|e| CodegenError::Internal(format!("{} call failed: {:?}", label, e)))?
            .try_as_basic_value()
            .basic()
            .ok_or_else(|| CodegenError::Internal(format!("{}: returned void", label)))?;
        Ok(Some(self.int_to_ptr(result.into_int_value())?.into()))
    }

    /// ByteArray: (ptr, i64) -> ptr (e.g. ba_ptr_plus)
    fn lower_builtin_text_ptr_int_to_ptr(
        &mut self,
        ptr_expr: &Expr,
        int_expr: &Expr,
        rts_id: usize,
        label: &str,
    ) -> CodegenResult<Option<BasicValueEnum<'ctx>>> {
        let p = self
            .lower_expr(ptr_expr)?
            .ok_or_else(|| CodegenError::Internal(format!("{}: no ptr", label)))?;
        let n = self
            .lower_expr(int_expr)?
            .ok_or_else(|| CodegenError::Internal(format!("{}: no int", label)))?;
        let p_ptr = self.value_to_ptr(p)?;
        let n_int = self.coerce_to_int(n)?;
        let rts_fn = self.functions.get(&VarId::new(rts_id)).ok_or_else(|| {
            CodegenError::Internal(format!("{}: RTS function {} not declared", label, rts_id))
        })?;
        let result = self
            .builder()
            .build_call(*rts_fn, &[p_ptr.into(), n_int.into()], label)
            .map_err(|e| CodegenError::Internal(format!("{} call failed: {:?}", label, e)))?
            .try_as_basic_value()
            .basic()
            .ok_or_else(|| CodegenError::Internal(format!("{}: returned void", label)))?;
        Ok(Some(result))
    }

    /// ByteArray copy: (ptr, ptr, i64, i64) -> void
    fn lower_builtin_ba_copy(
        &mut self,
        dst_expr: &Expr,
        src_expr: &Expr,
        offset_expr: &Expr,
        len_expr: &Expr,
    ) -> CodegenResult<Option<BasicValueEnum<'ctx>>> {
        let dst = self
            .lower_expr(dst_expr)?
            .ok_or_else(|| CodegenError::Internal("ba_copy: no dst".to_string()))?;
        let src = self
            .lower_expr(src_expr)?
            .ok_or_else(|| CodegenError::Internal("ba_copy: no src".to_string()))?;
        let off = self
            .lower_expr(offset_expr)?
            .ok_or_else(|| CodegenError::Internal("ba_copy: no offset".to_string()))?;
        let len = self
            .lower_expr(len_expr)?
            .ok_or_else(|| CodegenError::Internal("ba_copy: no len".to_string()))?;
        let dst_ptr = self.value_to_ptr(dst)?;
        let src_ptr = self.value_to_ptr(src)?;
        let off_int = self.coerce_to_int(off)?;
        let len_int = self.coerce_to_int(len)?;
        let rts_fn = self
            .functions
            .get(&VarId::new(1000253))
            .ok_or_else(|| CodegenError::Internal("bhc_bytearray_copy not declared".to_string()))?;
        self.builder()
            .build_call(
                *rts_fn,
                &[
                    dst_ptr.into(),
                    src_ptr.into(),
                    off_int.into(),
                    len_int.into(),
                ],
                "ba_copy",
            )
            .map_err(|e| CodegenError::Internal(format!("ba_copy call failed: {:?}", e)))?;
        // void return — return unit (null ptr)
        let unit = self.type_mapper().ptr_type().const_null();
        Ok(Some(unit.into()))
    }

    /// ByteArray poke: (ptr, i64, i64) -> void
    fn lower_builtin_ba_poke(
        &mut self,
        ptr_expr: &Expr,
        offset_expr: &Expr,
        value_expr: &Expr,
    ) -> CodegenResult<Option<BasicValueEnum<'ctx>>> {
        let p = self
            .lower_expr(ptr_expr)?
            .ok_or_else(|| CodegenError::Internal("ba_poke: no ptr".to_string()))?;
        let off = self
            .lower_expr(offset_expr)?
            .ok_or_else(|| CodegenError::Internal("ba_poke: no offset".to_string()))?;
        let val = self
            .lower_expr(value_expr)?
            .ok_or_else(|| CodegenError::Internal("ba_poke: no value".to_string()))?;
        let p_ptr = self.value_to_ptr(p)?;
        let off_int = self.coerce_to_int(off)?;
        let val_int = self.coerce_to_int(val)?;
        let rts_fn = self
            .functions
            .get(&VarId::new(1000255))
            .ok_or_else(|| CodegenError::Internal("bhc_poke_byte not declared".to_string()))?;
        self.builder()
            .build_call(
                *rts_fn,
                &[p_ptr.into(), off_int.into(), val_int.into()],
                "ba_poke",
            )
            .map_err(|e| CodegenError::Internal(format!("ba_poke call failed: {:?}", e)))?;
        let unit = self.type_mapper().ptr_type().const_null();
        Ok(Some(unit.into()))
    }

    /// Text unpack: builds a BHC cons-list from a Text using char_count + char_at iteration.
    ///
    /// This uses the same pattern as `map_keys` codegen:
    /// 1. Call bhc_text_char_count to get N
    /// 2. Loop from N-1 down to 0, calling bhc_text_char_at(text, i)
    /// 3. Build cons-list in reverse (so we end up with correct order)
    fn lower_builtin_text_unpack(
        &mut self,
        text_expr: &Expr,
    ) -> CodegenResult<Option<BasicValueEnum<'ctx>>> {
        let text_val = self
            .lower_expr(text_expr)?
            .ok_or_else(|| CodegenError::Internal("text_unpack: no text".to_string()))?;
        let text_ptr = self.value_to_ptr(text_val)?;

        // Get char count
        let count_fn = self.functions.get(&VarId::new(1000224)).ok_or_else(|| {
            CodegenError::Internal("bhc_text_char_count not declared".to_string())
        })?;
        let count_val = self
            .builder()
            .build_call(*count_fn, &[text_ptr.into()], "char_count")
            .map_err(|e| CodegenError::Internal(format!("char_count call failed: {:?}", e)))?
            .try_as_basic_value()
            .basic()
            .ok_or_else(|| CodegenError::Internal("char_count: returned void".to_string()))?
            .into_int_value();

        let char_at_fn = self
            .functions
            .get(&VarId::new(1000225))
            .ok_or_else(|| CodegenError::Internal("bhc_text_char_at not declared".to_string()))?;

        let tm = self.type_mapper();
        let i64_type = tm.i64_type();
        let ptr_type = tm.ptr_type();

        // Build Nil (empty list) as starting accumulator
        let nil = self.alloc_adt(0, 0)?;

        // Create loop blocks
        let current_fn = self
            .builder()
            .get_insert_block()
            .and_then(|b| b.get_parent())
            .ok_or_else(|| {
                CodegenError::Internal("text_unpack: no current function".to_string())
            })?;
        let loop_header = self
            .llvm_context()
            .append_basic_block(current_fn, "unpack_header");
        let loop_body = self
            .llvm_context()
            .append_basic_block(current_fn, "unpack_body");
        let loop_exit = self
            .llvm_context()
            .append_basic_block(current_fn, "unpack_exit");

        // Initial: i = count - 1, acc = nil
        let one = i64_type.const_int(1, false);
        let init_i = self
            .builder()
            .build_int_sub(count_val, one, "init_i")
            .map_err(|e| CodegenError::Internal(format!("text_unpack sub: {:?}", e)))?;
        self.builder()
            .build_unconditional_branch(loop_header)
            .map_err(|e| CodegenError::Internal(format!("text_unpack br: {:?}", e)))?;

        // Loop header: phi for i and acc
        self.builder().position_at_end(loop_header);
        let phi_i = self
            .builder()
            .build_phi(i64_type, "i")
            .map_err(|e| CodegenError::Internal(format!("text_unpack phi_i: {:?}", e)))?;
        let phi_acc = self
            .builder()
            .build_phi(ptr_type, "acc")
            .map_err(|e| CodegenError::Internal(format!("text_unpack phi_acc: {:?}", e)))?;

        let zero = i64_type.const_int(0, false);
        let cond = self
            .builder()
            .build_int_compare(
                inkwell::IntPredicate::SGE,
                phi_i.as_basic_value().into_int_value(),
                zero,
                "cond",
            )
            .map_err(|e| CodegenError::Internal(format!("text_unpack cmp: {:?}", e)))?;
        self.builder()
            .build_conditional_branch(cond, loop_body, loop_exit)
            .map_err(|e| CodegenError::Internal(format!("text_unpack cbr: {:?}", e)))?;

        // Loop body: char = char_at(text, i); cons = Cons(char, acc); i = i - 1
        self.builder().position_at_end(loop_body);
        let i_val = phi_i.as_basic_value().into_int_value();
        let char_val = self
            .builder()
            .build_call(*char_at_fn, &[text_ptr.into(), i_val.into()], "char_at")
            .map_err(|e| CodegenError::Internal(format!("char_at call failed: {:?}", e)))?
            .try_as_basic_value()
            .basic()
            .ok_or_else(|| CodegenError::Internal("char_at: returned void".to_string()))?;

        // Alloc Cons node: tag=1, arity=2
        let cons = self.alloc_adt(1, 2)?;
        let char_ptr = self.int_to_ptr(char_val.into_int_value())?;
        self.store_adt_field(cons, 2, 0, char_ptr.into())?;
        let acc_val = phi_acc.as_basic_value().into_pointer_value();
        self.store_adt_field(cons, 2, 1, acc_val.into())?;

        let next_i = self
            .builder()
            .build_int_sub(i_val, one, "next_i")
            .map_err(|e| CodegenError::Internal(format!("text_unpack dec: {:?}", e)))?;
        self.builder()
            .build_unconditional_branch(loop_header)
            .map_err(|e| CodegenError::Internal(format!("text_unpack loop_br: {:?}", e)))?;

        // Wire phi nodes
        let pre_header = loop_header.get_previous_basic_block().ok_or_else(|| {
            CodegenError::Internal("text_unpack: no pre-header block".to_string())
        })?;
        phi_i.add_incoming(&[(&init_i, pre_header), (&next_i, loop_body)]);
        phi_acc.add_incoming(&[(&nil, pre_header), (&cons, loop_body)]);

        // Exit: return accumulated list
        self.builder().position_at_end(loop_exit);
        Ok(Some(phi_acc.as_basic_value()))
    }

    /// Text map: (Char -> Char) -> Text -> Text
    ///
    /// The closure is dispatched via the RTS `bhc_text_map(fn_ptr, env_ptr, text_ptr)`.
    /// The Haskell closure is represented as an ADT with fn_ptr at field 0 and env at field 1.
    fn lower_builtin_text_map(
        &mut self,
        func_expr: &Expr,
        text_expr: &Expr,
    ) -> CodegenResult<Option<BasicValueEnum<'ctx>>> {
        let func = self
            .lower_expr(func_expr)?
            .ok_or_else(|| CodegenError::Internal("text_map: no func".to_string()))?;
        let text = self
            .lower_expr(text_expr)?
            .ok_or_else(|| CodegenError::Internal("text_map: no text".to_string()))?;
        let func_ptr = self.value_to_ptr(func)?;
        let text_ptr = self.value_to_ptr(text)?;

        // For closure dispatch, pass the closure pointer as both fn_ptr and env_ptr.
        // The RTS function will interpret the closure accordingly.
        let rts_fn = self
            .functions
            .get(&VarId::new(1000226))
            .ok_or_else(|| CodegenError::Internal("bhc_text_map not declared".to_string()))?;
        let result = self
            .builder()
            .build_call(
                *rts_fn,
                &[func_ptr.into(), func_ptr.into(), text_ptr.into()],
                "text_map",
            )
            .map_err(|e| CodegenError::Internal(format!("text_map call failed: {:?}", e)))?
            .try_as_basic_value()
            .basic()
            .ok_or_else(|| CodegenError::Internal("text_map: returned void".to_string()))?;
        Ok(Some(result))
    }

    /// Text filter: (Char -> Bool) -> Text -> Text
    ///
    /// Dispatches via `bhc_text_filter(fn_ptr, env_ptr, text_ptr)`.
    fn lower_builtin_text_filter(
        &mut self,
        func_expr: &Expr,
        text_expr: &Expr,
    ) -> CodegenResult<Option<BasicValueEnum<'ctx>>> {
        let func = self
            .lower_expr(func_expr)?
            .ok_or_else(|| CodegenError::Internal("text_filter: no func".to_string()))?;
        let text = self
            .lower_expr(text_expr)?
            .ok_or_else(|| CodegenError::Internal("text_filter: no text".to_string()))?;
        let func_ptr = self.value_to_ptr(func)?;
        let text_ptr = self.value_to_ptr(text)?;
        let rts_fn = self
            .functions
            .get(&VarId::new(1000227))
            .ok_or_else(|| CodegenError::Internal("bhc_text_filter not declared".to_string()))?;
        let result = self
            .builder()
            .build_call(
                *rts_fn,
                &[func_ptr.into(), func_ptr.into(), text_ptr.into()],
                "text_filter",
            )
            .map_err(|e| CodegenError::Internal(format!("text_filter call failed: {:?}", e)))?
            .try_as_basic_value()
            .basic()
            .ok_or_else(|| CodegenError::Internal("text_filter: returned void".to_string()))?;
        Ok(Some(result))
    }

    /// Text foldl': (a -> Char -> a) -> a -> Text -> a
    ///
    /// Dispatches via `bhc_text_foldl(fn_ptr, env_ptr, init_val, text_ptr)`.
    fn lower_builtin_text_foldl(
        &mut self,
        func_expr: &Expr,
        init_expr: &Expr,
        text_expr: &Expr,
    ) -> CodegenResult<Option<BasicValueEnum<'ctx>>> {
        let func = self
            .lower_expr(func_expr)?
            .ok_or_else(|| CodegenError::Internal("text_foldl: no func".to_string()))?;
        let init = self
            .lower_expr(init_expr)?
            .ok_or_else(|| CodegenError::Internal("text_foldl: no init".to_string()))?;
        let text = self
            .lower_expr(text_expr)?
            .ok_or_else(|| CodegenError::Internal("text_foldl: no text".to_string()))?;
        let func_ptr = self.value_to_ptr(func)?;
        let init_int = self.coerce_to_int(init)?;
        let text_ptr = self.value_to_ptr(text)?;
        let rts_fn = self
            .functions
            .get(&VarId::new(1000228))
            .ok_or_else(|| CodegenError::Internal("bhc_text_foldl not declared".to_string()))?;
        let result = self
            .builder()
            .build_call(
                *rts_fn,
                &[
                    func_ptr.into(),
                    func_ptr.into(),
                    init_int.into(),
                    text_ptr.into(),
                ],
                "text_foldl",
            )
            .map_err(|e| CodegenError::Internal(format!("text_foldl call failed: {:?}", e)))?
            .try_as_basic_value()
            .basic()
            .ok_or_else(|| CodegenError::Internal("text_foldl: returned void".to_string()))?;
        Ok(Some(self.int_to_ptr(result.into_int_value())?.into()))
    }

    /// Text ternary: (ptr, ptr, ptr) -> ptr (e.g. text_replace)
    fn lower_builtin_text_ternary_ptr(
        &mut self,
        expr_a: &Expr,
        expr_b: &Expr,
        expr_c: &Expr,
        rts_id: usize,
        label: &str,
    ) -> CodegenResult<Option<BasicValueEnum<'ctx>>> {
        let a = self
            .lower_expr(expr_a)?
            .ok_or_else(|| CodegenError::Internal(format!("{}: no a", label)))?;
        let b = self
            .lower_expr(expr_b)?
            .ok_or_else(|| CodegenError::Internal(format!("{}: no b", label)))?;
        let c = self
            .lower_expr(expr_c)?
            .ok_or_else(|| CodegenError::Internal(format!("{}: no c", label)))?;
        let a_ptr = self.value_to_ptr(a)?;
        let b_ptr = self.value_to_ptr(b)?;
        let c_ptr = self.value_to_ptr(c)?;
        let rts_fn = self.functions.get(&VarId::new(rts_id)).ok_or_else(|| {
            CodegenError::Internal(format!("{}: RTS function {} not declared", label, rts_id))
        })?;
        let result = self
            .builder()
            .build_call(*rts_fn, &[a_ptr.into(), b_ptr.into(), c_ptr.into()], label)
            .map_err(|e| CodegenError::Internal(format!("{} call failed: {:?}", label, e)))?
            .try_as_basic_value()
            .basic()
            .ok_or_else(|| CodegenError::Internal(format!("{}: returned void", label)))?;
        Ok(Some(result))
    }

    // ================================================================
    // Data.Text.IO codegen helpers
    // ================================================================

    /// Data.Text.IO.readFile: convert [Char] path to C-string, call RTS, return Text ptr.
    fn lower_builtin_text_io_read_file(
        &mut self,
        path_expr: &Expr,
    ) -> CodegenResult<Option<BasicValueEnum<'ctx>>> {
        let path_val = self
            .lower_expr(path_expr)?
            .ok_or_else(|| CodegenError::Internal("text_io_read_file: no path".to_string()))?;
        let path_ptr = self.value_to_ptr(path_val)?;
        let path_cstr = self.char_list_to_cstring(path_ptr)?;
        let rts_fn = self
            .functions
            .get(&VarId::new(1000240))
            .ok_or_else(|| CodegenError::Internal("bhc_text_read_file not declared".to_string()))?;
        let result = self
            .builder()
            .build_call(*rts_fn, &[path_cstr.into()], "text_read_file")
            .map_err(|e| CodegenError::Internal(format!("text_read_file call failed: {:?}", e)))?
            .try_as_basic_value()
            .basic()
            .ok_or_else(|| CodegenError::Internal("text_read_file: returned void".to_string()))?;
        Ok(Some(result))
    }

    /// Data.Text.IO.writeFile: convert [Char] path to C-string, lower Text arg, call RTS void.
    fn lower_builtin_text_io_write_file(
        &mut self,
        path_expr: &Expr,
        text_expr: &Expr,
    ) -> CodegenResult<Option<BasicValueEnum<'ctx>>> {
        let path_val = self
            .lower_expr(path_expr)?
            .ok_or_else(|| CodegenError::Internal("text_io_write_file: no path".to_string()))?;
        let text_val = self
            .lower_expr(text_expr)?
            .ok_or_else(|| CodegenError::Internal("text_io_write_file: no text".to_string()))?;
        let path_ptr = self.value_to_ptr(path_val)?;
        let path_cstr = self.char_list_to_cstring(path_ptr)?;
        let text_ptr = self.value_to_ptr(text_val)?;
        let rts_fn = self.functions.get(&VarId::new(1000241)).ok_or_else(|| {
            CodegenError::Internal("bhc_text_write_file not declared".to_string())
        })?;
        self.builder()
            .build_call(*rts_fn, &[path_cstr.into(), text_ptr.into()], "")
            .map_err(|e| CodegenError::Internal(format!("text_write_file call failed: {:?}", e)))?;
        Ok(Some(self.type_mapper().ptr_type().const_null().into()))
    }

    /// Data.Text.IO.appendFile: convert [Char] path to C-string, lower Text arg, call RTS void.
    fn lower_builtin_text_io_append_file(
        &mut self,
        path_expr: &Expr,
        text_expr: &Expr,
    ) -> CodegenResult<Option<BasicValueEnum<'ctx>>> {
        let path_val = self
            .lower_expr(path_expr)?
            .ok_or_else(|| CodegenError::Internal("text_io_append_file: no path".to_string()))?;
        let text_val = self
            .lower_expr(text_expr)?
            .ok_or_else(|| CodegenError::Internal("text_io_append_file: no text".to_string()))?;
        let path_ptr = self.value_to_ptr(path_val)?;
        let path_cstr = self.char_list_to_cstring(path_ptr)?;
        let text_ptr = self.value_to_ptr(text_val)?;
        let rts_fn = self.functions.get(&VarId::new(1000242)).ok_or_else(|| {
            CodegenError::Internal("bhc_text_append_file not declared".to_string())
        })?;
        self.builder()
            .build_call(*rts_fn, &[path_cstr.into(), text_ptr.into()], "")
            .map_err(|e| {
                CodegenError::Internal(format!("text_append_file call failed: {:?}", e))
            })?;
        Ok(Some(self.type_mapper().ptr_type().const_null().into()))
    }

    /// Data.Text.IO handle -> Text: hGetContents, hGetLine.
    fn lower_builtin_text_io_handle_to_text(
        &mut self,
        handle_expr: &Expr,
        rts_id: usize,
        label: &str,
    ) -> CodegenResult<Option<BasicValueEnum<'ctx>>> {
        let handle_val = self
            .lower_expr(handle_expr)?
            .ok_or_else(|| CodegenError::Internal(format!("{}: no handle", label)))?;
        let handle_ptr = match handle_val {
            BasicValueEnum::PointerValue(p) => p,
            _ => return Err(CodegenError::TypeError(format!("{} expects handle", label))),
        };
        let rts_fn = self.functions.get(&VarId::new(rts_id)).ok_or_else(|| {
            CodegenError::Internal(format!("{}: RTS function {} not declared", label, rts_id))
        })?;
        let result = self
            .builder()
            .build_call(*rts_fn, &[handle_ptr.into()], label)
            .map_err(|e| CodegenError::Internal(format!("{} call failed: {:?}", label, e)))?
            .try_as_basic_value()
            .basic()
            .ok_or_else(|| CodegenError::Internal(format!("{}: returned void", label)))?;
        Ok(Some(result))
    }

    /// Data.Text.IO (handle, Text) -> void: hPutStr, hPutStrLn.
    fn lower_builtin_text_io_handle_text_void(
        &mut self,
        handle_expr: &Expr,
        text_expr: &Expr,
        rts_id: usize,
        label: &str,
    ) -> CodegenResult<Option<BasicValueEnum<'ctx>>> {
        let handle_val = self
            .lower_expr(handle_expr)?
            .ok_or_else(|| CodegenError::Internal(format!("{}: no handle", label)))?;
        let text_val = self
            .lower_expr(text_expr)?
            .ok_or_else(|| CodegenError::Internal(format!("{}: no text", label)))?;
        let handle_ptr = match handle_val {
            BasicValueEnum::PointerValue(p) => p,
            _ => return Err(CodegenError::TypeError(format!("{} expects handle", label))),
        };
        let text_ptr = self.value_to_ptr(text_val)?;
        let rts_fn = self.functions.get(&VarId::new(rts_id)).ok_or_else(|| {
            CodegenError::Internal(format!("{}: RTS function {} not declared", label, rts_id))
        })?;
        self.builder()
            .build_call(*rts_fn, &[handle_ptr.into(), text_ptr.into()], "")
            .map_err(|e| CodegenError::Internal(format!("{} call failed: {:?}", label, e)))?;
        Ok(Some(self.type_mapper().ptr_type().const_null().into()))
    }

    /// Data.Text.IO putStr/putStrLn: get stdout, call hPutStr/hPutStrLn.
    fn lower_builtin_text_io_stdout_text(
        &mut self,
        text_expr: &Expr,
        rts_id: usize,
        label: &str,
    ) -> CodegenResult<Option<BasicValueEnum<'ctx>>> {
        // Get stdout handle
        let stdout_fn = self
            .functions
            .get(&VarId::new(1000061))
            .ok_or_else(|| CodegenError::Internal("bhc_stdout not declared".to_string()))?;
        let stdout = self
            .builder()
            .build_call(*stdout_fn, &[], "stdout")
            .map_err(|e| CodegenError::Internal(format!("{}: stdout call failed: {:?}", label, e)))?
            .try_as_basic_value()
            .basic()
            .ok_or_else(|| CodegenError::Internal(format!("{}: stdout returned void", label)))?;
        let stdout_ptr = stdout.into_pointer_value();
        // Lower text argument
        let text_val = self
            .lower_expr(text_expr)?
            .ok_or_else(|| CodegenError::Internal(format!("{}: no text", label)))?;
        let text_ptr = self.value_to_ptr(text_val)?;
        // Call RTS function
        let rts_fn = self.functions.get(&VarId::new(rts_id)).ok_or_else(|| {
            CodegenError::Internal(format!("{}: RTS function {} not declared", label, rts_id))
        })?;
        self.builder()
            .build_call(*rts_fn, &[stdout_ptr.into(), text_ptr.into()], "")
            .map_err(|e| CodegenError::Internal(format!("{} call failed: {:?}", label, e)))?;
        Ok(Some(self.type_mapper().ptr_type().const_null().into()))
    }

    /// Data.Text.IO getLine/getContents: get stdin, call hGetLine/hGetContents.
    fn lower_builtin_text_io_stdin_to_text(
        &mut self,
        rts_id: usize,
        label: &str,
    ) -> CodegenResult<Option<BasicValueEnum<'ctx>>> {
        // Get stdin handle
        let stdin_fn = self
            .functions
            .get(&VarId::new(1000060))
            .ok_or_else(|| CodegenError::Internal("bhc_stdin not declared".to_string()))?;
        let stdin = self
            .builder()
            .build_call(*stdin_fn, &[], "stdin")
            .map_err(|e| CodegenError::Internal(format!("{}: stdin call failed: {:?}", label, e)))?
            .try_as_basic_value()
            .basic()
            .ok_or_else(|| CodegenError::Internal(format!("{}: stdin returned void", label)))?;
        let stdin_ptr = stdin.into_pointer_value();
        // Call RTS function
        let rts_fn = self.functions.get(&VarId::new(rts_id)).ok_or_else(|| {
            CodegenError::Internal(format!("{}: RTS function {} not declared", label, rts_id))
        })?;
        let result = self
            .builder()
            .build_call(*rts_fn, &[stdin_ptr.into()], label)
            .map_err(|e| CodegenError::Internal(format!("{} call failed: {:?}", label, e)))?
            .try_as_basic_value()
            .basic()
            .ok_or_else(|| CodegenError::Internal(format!("{}: returned void", label)))?;
        Ok(Some(result))
    }

    /// ByteString snoc: (ptr, i64) -> ptr (e.g. bs_snoc)
    fn lower_builtin_text_snoc(
        &mut self,
        ptr_expr: &Expr,
        int_expr: &Expr,
        rts_id: usize,
        label: &str,
    ) -> CodegenResult<Option<BasicValueEnum<'ctx>>> {
        let p = self
            .lower_expr(ptr_expr)?
            .ok_or_else(|| CodegenError::Internal(format!("{}: no ptr", label)))?;
        let n = self
            .lower_expr(int_expr)?
            .ok_or_else(|| CodegenError::Internal(format!("{}: no int", label)))?;
        let p_ptr = self.value_to_ptr(p)?;
        let n_int = self.coerce_to_int(n)?;
        let rts_fn = self.functions.get(&VarId::new(rts_id)).ok_or_else(|| {
            CodegenError::Internal(format!("{}: RTS function {} not declared", label, rts_id))
        })?;
        let result = self
            .builder()
            .build_call(*rts_fn, &[p_ptr.into(), n_int.into()], label)
            .map_err(|e| CodegenError::Internal(format!("{} call failed: {:?}", label, e)))?
            .try_as_basic_value()
            .basic()
            .ok_or_else(|| CodegenError::Internal(format!("{}: returned void", label)))?;
        Ok(Some(result))
    }

    /// ByteString: (i64, ptr) -> i64 (e.g. bs_elem)
    fn lower_builtin_text_int_ptr_to_int(
        &mut self,
        int_expr: &Expr,
        ptr_expr: &Expr,
        rts_id: usize,
        label: &str,
    ) -> CodegenResult<Option<BasicValueEnum<'ctx>>> {
        let n = self
            .lower_expr(int_expr)?
            .ok_or_else(|| CodegenError::Internal(format!("{}: no int", label)))?;
        let p = self
            .lower_expr(ptr_expr)?
            .ok_or_else(|| CodegenError::Internal(format!("{}: no ptr", label)))?;
        let n_int = self.coerce_to_int(n)?;
        let p_ptr = self.value_to_ptr(p)?;
        let rts_fn = self.functions.get(&VarId::new(rts_id)).ok_or_else(|| {
            CodegenError::Internal(format!("{}: RTS function {} not declared", label, rts_id))
        })?;
        let result = self
            .builder()
            .build_call(*rts_fn, &[n_int.into(), p_ptr.into()], label)
            .map_err(|e| CodegenError::Internal(format!("{} call failed: {:?}", label, e)))?
            .try_as_basic_value()
            .basic()
            .ok_or_else(|| CodegenError::Internal(format!("{}: returned void", label)))?;
        Ok(Some(self.int_to_ptr(result.into_int_value())?.into()))
    }

    /// ByteString unpack: ByteString -> [Int]
    ///
    /// Similar to text_unpack but returns list of Word8 (as Int).
    fn lower_builtin_bs_unpack(
        &mut self,
        bs_expr: &Expr,
    ) -> CodegenResult<Option<BasicValueEnum<'ctx>>> {
        let bs_val = self
            .lower_expr(bs_expr)?
            .ok_or_else(|| CodegenError::Internal("bs_unpack: no bs".to_string()))?;
        let bs_ptr = self.value_to_ptr(bs_val)?;

        // Get byte count
        let length_fn = self
            .functions
            .get(&VarId::new(1000403))
            .ok_or_else(|| CodegenError::Internal("bhc_bs_length not declared".to_string()))?;
        let count_val = self
            .builder()
            .build_call(*length_fn, &[bs_ptr.into()], "bs_len")
            .map_err(|e| CodegenError::Internal(format!("bs_length call failed: {:?}", e)))?
            .try_as_basic_value()
            .basic()
            .ok_or_else(|| CodegenError::Internal("bs_length: returned void".to_string()))?
            .into_int_value();

        let unpack_fn = self
            .functions
            .get(&VarId::new(1000421))
            .ok_or_else(|| CodegenError::Internal("bhc_bs_unpack not declared".to_string()))?;

        let tm = self.type_mapper();
        let i64_type = tm.i64_type();
        let ptr_type = tm.ptr_type();

        // Build Nil as starting accumulator
        let nil = self.alloc_adt(0, 0)?;

        // Create loop blocks
        let current_fn = self
            .builder()
            .get_insert_block()
            .and_then(|b| b.get_parent())
            .ok_or_else(|| CodegenError::Internal("bs_unpack: no current function".to_string()))?;
        let loop_header = self
            .llvm_context()
            .append_basic_block(current_fn, "bs_unpack_header");
        let loop_body = self
            .llvm_context()
            .append_basic_block(current_fn, "bs_unpack_body");
        let loop_exit = self
            .llvm_context()
            .append_basic_block(current_fn, "bs_unpack_exit");

        // Initial: i = count - 1, acc = nil
        let one = i64_type.const_int(1, false);
        let init_i = self
            .builder()
            .build_int_sub(count_val, one, "init_i")
            .map_err(|e| CodegenError::Internal(format!("bs_unpack sub: {:?}", e)))?;
        self.builder()
            .build_unconditional_branch(loop_header)
            .map_err(|e| CodegenError::Internal(format!("bs_unpack br: {:?}", e)))?;

        // Loop header
        self.builder().position_at_end(loop_header);
        let phi_i = self
            .builder()
            .build_phi(i64_type, "i")
            .map_err(|e| CodegenError::Internal(format!("bs_unpack phi_i: {:?}", e)))?;
        let phi_acc = self
            .builder()
            .build_phi(ptr_type, "acc")
            .map_err(|e| CodegenError::Internal(format!("bs_unpack phi_acc: {:?}", e)))?;

        let zero = i64_type.const_int(0, false);
        let cond = self
            .builder()
            .build_int_compare(
                inkwell::IntPredicate::SGE,
                phi_i.as_basic_value().into_int_value(),
                zero,
                "cond",
            )
            .map_err(|e| CodegenError::Internal(format!("bs_unpack cmp: {:?}", e)))?;
        self.builder()
            .build_conditional_branch(cond, loop_body, loop_exit)
            .map_err(|e| CodegenError::Internal(format!("bs_unpack cbr: {:?}", e)))?;

        // Loop body: byte = unpack(bs, i); cons = Cons(byte, acc); i = i - 1
        self.builder().position_at_end(loop_body);
        let i_val = phi_i.as_basic_value().into_int_value();
        let byte_val = self
            .builder()
            .build_call(*unpack_fn, &[bs_ptr.into(), i_val.into()], "bs_byte_at")
            .map_err(|e| CodegenError::Internal(format!("bs_unpack call failed: {:?}", e)))?
            .try_as_basic_value()
            .basic()
            .ok_or_else(|| CodegenError::Internal("bs_unpack: returned void".to_string()))?;

        // Alloc Cons node: tag=1, arity=2
        let cons = self.alloc_adt(1, 2)?;
        let byte_ptr = self.int_to_ptr(byte_val.into_int_value())?;
        self.store_adt_field(cons, 2, 0, byte_ptr.into())?;
        let acc_val = phi_acc.as_basic_value().into_pointer_value();
        self.store_adt_field(cons, 2, 1, acc_val.into())?;

        let next_i = self
            .builder()
            .build_int_sub(i_val, one, "next_i")
            .map_err(|e| CodegenError::Internal(format!("bs_unpack dec: {:?}", e)))?;
        self.builder()
            .build_unconditional_branch(loop_header)
            .map_err(|e| CodegenError::Internal(format!("bs_unpack loop_br: {:?}", e)))?;

        // Wire phi nodes
        let pre_header = loop_header
            .get_previous_basic_block()
            .ok_or_else(|| CodegenError::Internal("bs_unpack: no pre-header block".to_string()))?;
        phi_i.add_incoming(&[(&init_i, pre_header), (&next_i, loop_body)]);
        phi_acc.add_incoming(&[(&nil, pre_header), (&cons, loop_body)]);

        // Exit
        self.builder().position_at_end(loop_exit);
        Ok(Some(phi_acc.as_basic_value()))
    }

    /// ByteString writeFile: String -> ByteString -> IO ()
    fn lower_builtin_bs_write_file(
        &mut self,
        path_expr: &Expr,
        bs_expr: &Expr,
    ) -> CodegenResult<Option<BasicValueEnum<'ctx>>> {
        let path = self
            .lower_expr(path_expr)?
            .ok_or_else(|| CodegenError::Internal("bs_write_file: no path".to_string()))?;
        let bs = self
            .lower_expr(bs_expr)?
            .ok_or_else(|| CodegenError::Internal("bs_write_file: no bs".to_string()))?;
        let path_ptr = self.value_to_ptr(path)?;
        let bs_ptr = self.value_to_ptr(bs)?;
        let rts_fn = self
            .functions
            .get(&VarId::new(1000423))
            .ok_or_else(|| CodegenError::Internal("bhc_bs_write_file not declared".to_string()))?;
        self.builder()
            .build_call(*rts_fn, &[path_ptr.into(), bs_ptr.into()], "bs_write_file")
            .map_err(|e| CodegenError::Internal(format!("bs_write_file call failed: {:?}", e)))?;
        // void return — return unit (null ptr)
        let unit = self.type_mapper().ptr_type().const_null();
        Ok(Some(unit.into()))
    }

    /// Lazy ByteString filter: (Word8 -> Bool) -> LazyByteString -> LazyByteString
    fn lower_builtin_lazy_bs_filter(
        &mut self,
        func_expr: &Expr,
        bs_expr: &Expr,
    ) -> CodegenResult<Option<BasicValueEnum<'ctx>>> {
        let func = self
            .lower_expr(func_expr)?
            .ok_or_else(|| CodegenError::Internal("lazy_bs_filter: no func".to_string()))?;
        let bs = self
            .lower_expr(bs_expr)?
            .ok_or_else(|| CodegenError::Internal("lazy_bs_filter: no bs".to_string()))?;
        let func_ptr = self.value_to_ptr(func)?;
        let bs_ptr = self.value_to_ptr(bs)?;
        let rts_fn = self
            .functions
            .get(&VarId::new(1000453))
            .ok_or_else(|| CodegenError::Internal("bhc_lazy_bs_filter not declared".to_string()))?;
        let result = self
            .builder()
            .build_call(
                *rts_fn,
                &[func_ptr.into(), func_ptr.into(), bs_ptr.into()],
                "lazy_bs_filter",
            )
            .map_err(|e| CodegenError::Internal(format!("lazy_bs_filter call failed: {:?}", e)))?
            .try_as_basic_value()
            .basic()
            .ok_or_else(|| CodegenError::Internal("lazy_bs_filter: returned void".to_string()))?;
        Ok(Some(result))
    }

    /// Lazy ByteString Char8 dropWhile: (Char -> Bool) -> LazyByteString -> LazyByteString
    fn lower_builtin_lazy_bs_drop_while(
        &mut self,
        func_expr: &Expr,
        bs_expr: &Expr,
    ) -> CodegenResult<Option<BasicValueEnum<'ctx>>> {
        let func = self
            .lower_expr(func_expr)?
            .ok_or_else(|| CodegenError::Internal("lazy_bs_drop_while: no func".to_string()))?;
        let bs = self
            .lower_expr(bs_expr)?
            .ok_or_else(|| CodegenError::Internal("lazy_bs_drop_while: no bs".to_string()))?;
        let func_ptr = self.value_to_ptr(func)?;
        let bs_ptr = self.value_to_ptr(bs)?;
        let rts_fn = self.functions.get(&VarId::new(1000474)).ok_or_else(|| {
            CodegenError::Internal("bhc_lazy_bs_char8_drop_while not declared".to_string())
        })?;
        let result = self
            .builder()
            .build_call(
                *rts_fn,
                &[func_ptr.into(), func_ptr.into(), bs_ptr.into()],
                "lazy_bs_drop_while",
            )
            .map_err(|e| {
                CodegenError::Internal(format!("lazy_bs_drop_while call failed: {:?}", e))
            })?
            .try_as_basic_value()
            .basic()
            .ok_or_else(|| {
                CodegenError::Internal("lazy_bs_drop_while: returned void".to_string())
            })?;
        Ok(Some(result))
    }

    /// Lazy ByteString writeFile: FilePath -> LazyByteString -> IO ()
    fn lower_builtin_lazy_bs_write_file(
        &mut self,
        path_expr: &Expr,
        bs_expr: &Expr,
    ) -> CodegenResult<Option<BasicValueEnum<'ctx>>> {
        let path = self
            .lower_expr(path_expr)?
            .ok_or_else(|| CodegenError::Internal("lazy_bs_write_file: no path".to_string()))?;
        let bs = self
            .lower_expr(bs_expr)?
            .ok_or_else(|| CodegenError::Internal("lazy_bs_write_file: no bs".to_string()))?;
        let path_ptr = self.value_to_ptr(path)?;
        let bs_ptr = self.value_to_ptr(bs)?;
        let rts_fn = self.functions.get(&VarId::new(1000456)).ok_or_else(|| {
            CodegenError::Internal("bhc_lazy_bs_write_file not declared".to_string())
        })?;
        self.builder()
            .build_call(
                *rts_fn,
                &[path_ptr.into(), bs_ptr.into()],
                "lazy_bs_write_file",
            )
            .map_err(|e| {
                CodegenError::Internal(format!("lazy_bs_write_file call failed: {:?}", e))
            })?;
        let unit = self.type_mapper().ptr_type().const_null();
        Ok(Some(unit.into()))
    }

    // ============================================================
    // ByteString.Builder composed helpers
    // ============================================================

    /// floatBE: fptrunc f64->f32, bitcast f32->i32, zext i32->i64, word32BE
    fn lower_builtin_bsb_float_be(
        &mut self,
        arg_expr: &Expr,
    ) -> CodegenResult<Option<BasicValueEnum<'ctx>>> {
        let word = self.lower_bsb_float_to_word(arg_expr)?;
        let enc_fn = self
            .functions
            .get(&VarId::new(1000487))
            .ok_or_else(|| CodegenError::Internal("bhc_bsb_word32_be not declared".to_string()))?;
        let result = self
            .builder()
            .build_call(*enc_fn, &[word.into()], "bsb_float_be")
            .map_err(|e| CodegenError::Internal(format!("bsb_float_be failed: {:?}", e)))?
            .try_as_basic_value()
            .basic()
            .ok_or_else(|| CodegenError::Internal("bsb_float_be: no return".to_string()))?;
        Ok(Some(result))
    }

    /// doubleBE: bitcast f64->i64, word64BE
    fn lower_builtin_bsb_double_be(
        &mut self,
        arg_expr: &Expr,
    ) -> CodegenResult<Option<BasicValueEnum<'ctx>>> {
        let word = self.lower_bsb_double_to_word(arg_expr)?;
        let enc_fn = self
            .functions
            .get(&VarId::new(1000488))
            .ok_or_else(|| CodegenError::Internal("bhc_bsb_word64_be not declared".to_string()))?;
        let result = self
            .builder()
            .build_call(*enc_fn, &[word.into()], "bsb_double_be")
            .map_err(|e| CodegenError::Internal(format!("bsb_double_be failed: {:?}", e)))?
            .try_as_basic_value()
            .basic()
            .ok_or_else(|| CodegenError::Internal("bsb_double_be: no return".to_string()))?;
        Ok(Some(result))
    }

    /// floatLE / floatHost: fptrunc f64->f32, bitcast f32->i32, zext i32->i64, word32LE
    fn lower_builtin_bsb_float_le(
        &mut self,
        arg_expr: &Expr,
    ) -> CodegenResult<Option<BasicValueEnum<'ctx>>> {
        let word = self.lower_bsb_float_to_word(arg_expr)?;
        let enc_fn = self
            .functions
            .get(&VarId::new(1000490))
            .ok_or_else(|| CodegenError::Internal("bhc_bsb_word32_le not declared".to_string()))?;
        let result = self
            .builder()
            .build_call(*enc_fn, &[word.into()], "bsb_float_le")
            .map_err(|e| CodegenError::Internal(format!("bsb_float_le failed: {:?}", e)))?
            .try_as_basic_value()
            .basic()
            .ok_or_else(|| CodegenError::Internal("bsb_float_le: no return".to_string()))?;
        Ok(Some(result))
    }

    /// doubleLE / doubleHost: bitcast f64->i64, word64LE
    fn lower_builtin_bsb_double_le(
        &mut self,
        arg_expr: &Expr,
    ) -> CodegenResult<Option<BasicValueEnum<'ctx>>> {
        let word = self.lower_bsb_double_to_word(arg_expr)?;
        let enc_fn = self
            .functions
            .get(&VarId::new(1000491))
            .ok_or_else(|| CodegenError::Internal("bhc_bsb_word64_le not declared".to_string()))?;
        let result = self
            .builder()
            .build_call(*enc_fn, &[word.into()], "bsb_double_le")
            .map_err(|e| CodegenError::Internal(format!("bsb_double_le failed: {:?}", e)))?
            .try_as_basic_value()
            .basic()
            .ok_or_else(|| CodegenError::Internal("bsb_double_le: no return".to_string()))?;
        Ok(Some(result))
    }

    /// Helper: convert float expr to i64 word representation (fptrunc->bitcast->zext)
    fn lower_bsb_float_to_word(
        &mut self,
        arg_expr: &Expr,
    ) -> CodegenResult<inkwell::values::IntValue<'ctx>> {
        let arg = self
            .lower_expr(arg_expr)?
            .ok_or_else(|| CodegenError::Internal("bsb_float: no arg".to_string()))?;
        let f64_val = self.coerce_to_f64(arg)?;
        let f32_type = self.llvm_ctx.f32_type();
        let i32_type = self.llvm_ctx.i32_type();
        let i64_type = self.llvm_ctx.i64_type();
        let f32_val = self
            .builder()
            .build_float_trunc(f64_val, f32_type, "ftrunc")
            .map_err(|e| CodegenError::Internal(format!("ftrunc: {:?}", e)))?;
        let i32_val = self
            .builder()
            .build_bit_cast(f32_val, i32_type, "fbitcast")
            .map_err(|e| CodegenError::Internal(format!("fbitcast: {:?}", e)))?
            .into_int_value();
        let word = self
            .builder()
            .build_int_z_extend(i32_val, i64_type, "fzext")
            .map_err(|e| CodegenError::Internal(format!("fzext: {:?}", e)))?;
        Ok(word)
    }

    /// Helper: convert double expr to i64 word representation (bitcast)
    fn lower_bsb_double_to_word(
        &mut self,
        arg_expr: &Expr,
    ) -> CodegenResult<inkwell::values::IntValue<'ctx>> {
        let arg = self
            .lower_expr(arg_expr)?
            .ok_or_else(|| CodegenError::Internal("bsb_double: no arg".to_string()))?;
        let f64_val = self.coerce_to_f64(arg)?;
        let i64_type = self.llvm_ctx.i64_type();
        let word = self
            .builder()
            .build_bit_cast(f64_val, i64_type, "dbitcast")
            .map_err(|e| CodegenError::Internal(format!("dbitcast: {:?}", e)))?
            .into_int_value();
        Ok(word)
    }

    /// word16HexFixed: append(word8HexFixed(hi), word8HexFixed(lo))
    fn lower_builtin_bsb_word16_hex_fixed(
        &mut self,
        arg_expr: &Expr,
    ) -> CodegenResult<Option<BasicValueEnum<'ctx>>> {
        let arg = self
            .lower_expr(arg_expr)?
            .ok_or_else(|| CodegenError::Internal("bsb_word16_hex_fixed: no arg".to_string()))?;
        let i64_val = self.coerce_to_int(arg)?;
        let i64_type = self.llvm_ctx.i64_type();
        // hi = (value >> 8) & 0xff
        let hi = self
            .builder()
            .build_right_shift(i64_val, i64_type.const_int(8, false), false, "hi")
            .map_err(|e| CodegenError::Internal(format!("hi shift: {:?}", e)))?;
        let hi_masked = self
            .builder()
            .build_and(hi, i64_type.const_int(0xff, false), "hi_masked")
            .map_err(|e| CodegenError::Internal(format!("hi mask: {:?}", e)))?;
        // lo = value & 0xff
        let lo = self
            .builder()
            .build_and(i64_val, i64_type.const_int(0xff, false), "lo")
            .map_err(|e| CodegenError::Internal(format!("lo mask: {:?}", e)))?;
        let hex_fn = self.functions.get(&VarId::new(1000493)).ok_or_else(|| {
            CodegenError::Internal("bhc_bsb_word8_hex_fixed not declared".to_string())
        })?;
        let hi_bld = self
            .builder()
            .build_call(*hex_fn, &[hi_masked.into()], "hi_hex")
            .map_err(|e| CodegenError::Internal(format!("hi_hex failed: {:?}", e)))?
            .try_as_basic_value()
            .basic()
            .ok_or_else(|| CodegenError::Internal("hi_hex: no return".to_string()))?;
        let lo_bld = self
            .builder()
            .build_call(*hex_fn, &[lo.into()], "lo_hex")
            .map_err(|e| CodegenError::Internal(format!("lo_hex failed: {:?}", e)))?
            .try_as_basic_value()
            .basic()
            .ok_or_else(|| CodegenError::Internal("lo_hex: no return".to_string()))?;
        let append_fn = self
            .functions
            .get(&VarId::new(1000448))
            .ok_or_else(|| CodegenError::Internal("bhc_lazy_bs_append not declared".to_string()))?;
        let hi_ptr = self.value_to_ptr(hi_bld)?;
        let lo_ptr = self.value_to_ptr(lo_bld)?;
        let result = self
            .builder()
            .build_call(*append_fn, &[hi_ptr.into(), lo_ptr.into()], "hex16_fixed")
            .map_err(|e| CodegenError::Internal(format!("hex16_fixed failed: {:?}", e)))?
            .try_as_basic_value()
            .basic()
            .ok_or_else(|| CodegenError::Internal("hex16_fixed: no return".to_string()))?;
        Ok(Some(result))
    }

    /// word32HexFixed: append 4 word8HexFixed calls
    fn lower_builtin_bsb_word32_hex_fixed(
        &mut self,
        arg_expr: &Expr,
    ) -> CodegenResult<Option<BasicValueEnum<'ctx>>> {
        let arg = self
            .lower_expr(arg_expr)?
            .ok_or_else(|| CodegenError::Internal("bsb_word32_hex_fixed: no arg".to_string()))?;
        let i64_val = self.coerce_to_int(arg)?;
        let i64_type = self.llvm_ctx.i64_type();
        let hex_fn = *self.functions.get(&VarId::new(1000493)).ok_or_else(|| {
            CodegenError::Internal("bhc_bsb_word8_hex_fixed not declared".to_string())
        })?;
        let append_fn = *self
            .functions
            .get(&VarId::new(1000448))
            .ok_or_else(|| CodegenError::Internal("bhc_lazy_bs_append not declared".to_string()))?;
        // Extract 4 bytes: b3 b2 b1 b0
        let mut byte_builders = Vec::new();
        for shift in [24u64, 16, 8, 0] {
            let shifted = self
                .builder()
                .build_right_shift(
                    i64_val,
                    i64_type.const_int(shift, false),
                    false,
                    "byte_shift",
                )
                .map_err(|e| CodegenError::Internal(format!("shift: {:?}", e)))?;
            let masked = self
                .builder()
                .build_and(shifted, i64_type.const_int(0xff, false), "byte_mask")
                .map_err(|e| CodegenError::Internal(format!("mask: {:?}", e)))?;
            let bld = self
                .builder()
                .build_call(hex_fn, &[masked.into()], "byte_hex")
                .map_err(|e| CodegenError::Internal(format!("byte_hex failed: {:?}", e)))?
                .try_as_basic_value()
                .basic()
                .ok_or_else(|| CodegenError::Internal("byte_hex: no return".to_string()))?;
            byte_builders.push(bld);
        }
        // Chain appends: append(append(append(b3, b2), b1), b0)
        let mut result = byte_builders[0];
        for bld in &byte_builders[1..] {
            let r_ptr = self.value_to_ptr(result)?;
            let b_ptr = self.value_to_ptr(*bld)?;
            result = self
                .builder()
                .build_call(append_fn, &[r_ptr.into(), b_ptr.into()], "hex32_append")
                .map_err(|e| CodegenError::Internal(format!("hex32 append: {:?}", e)))?
                .try_as_basic_value()
                .basic()
                .ok_or_else(|| CodegenError::Internal("hex32 append: no return".to_string()))?;
        }
        Ok(Some(result))
    }

    /// word64HexFixed: append 8 word8HexFixed calls
    fn lower_builtin_bsb_word64_hex_fixed(
        &mut self,
        arg_expr: &Expr,
    ) -> CodegenResult<Option<BasicValueEnum<'ctx>>> {
        let arg = self
            .lower_expr(arg_expr)?
            .ok_or_else(|| CodegenError::Internal("bsb_word64_hex_fixed: no arg".to_string()))?;
        let i64_val = self.coerce_to_int(arg)?;
        let i64_type = self.llvm_ctx.i64_type();
        let hex_fn = *self.functions.get(&VarId::new(1000493)).ok_or_else(|| {
            CodegenError::Internal("bhc_bsb_word8_hex_fixed not declared".to_string())
        })?;
        let append_fn = *self
            .functions
            .get(&VarId::new(1000448))
            .ok_or_else(|| CodegenError::Internal("bhc_lazy_bs_append not declared".to_string()))?;
        // Extract 8 bytes: b7 b6 b5 b4 b3 b2 b1 b0
        let mut byte_builders = Vec::new();
        for shift in [56u64, 48, 40, 32, 24, 16, 8, 0] {
            let shifted = self
                .builder()
                .build_right_shift(
                    i64_val,
                    i64_type.const_int(shift, false),
                    false,
                    "byte_shift",
                )
                .map_err(|e| CodegenError::Internal(format!("shift: {:?}", e)))?;
            let masked = self
                .builder()
                .build_and(shifted, i64_type.const_int(0xff, false), "byte_mask")
                .map_err(|e| CodegenError::Internal(format!("mask: {:?}", e)))?;
            let bld = self
                .builder()
                .build_call(hex_fn, &[masked.into()], "byte_hex")
                .map_err(|e| CodegenError::Internal(format!("byte_hex failed: {:?}", e)))?
                .try_as_basic_value()
                .basic()
                .ok_or_else(|| CodegenError::Internal("byte_hex: no return".to_string()))?;
            byte_builders.push(bld);
        }
        // Chain appends
        let mut result = byte_builders[0];
        for bld in &byte_builders[1..] {
            let r_ptr = self.value_to_ptr(result)?;
            let b_ptr = self.value_to_ptr(*bld)?;
            result = self
                .builder()
                .build_call(append_fn, &[r_ptr.into(), b_ptr.into()], "hex64_append")
                .map_err(|e| CodegenError::Internal(format!("hex64 append: {:?}", e)))?
                .try_as_basic_value()
                .basic()
                .ok_or_else(|| CodegenError::Internal("hex64 append: no return".to_string()))?;
        }
        Ok(Some(result))
    }

    /// Check if an expression is structurally a list (Cons applications or Nil).
    /// This is used when type information isn't available.
    fn is_list_expr(expr: &Expr) -> bool {
        match expr {
            // Empty list []
            Expr::Var(v, _) if v.name.as_str() == "[]" => true,
            // Cons application: (:) head tail
            Expr::App(func, _, _) => {
                // Check if this is a (:) application
                let mut current = func.as_ref();
                // Unwrap one more App for curried (:) x y
                if let Expr::App(inner_func, _, _) = current {
                    current = inner_func.as_ref();
                }
                // Check if the head is the (:) constructor
                if let Expr::Var(v, _) = current {
                    v.name.as_str() == ":"
                } else {
                    false
                }
            }
            _ => false,
        }
    }

    /// E.45: Check if an expression is an Integer (arbitrary precision) value.
    fn is_integer_expr(&self, expr: &Expr) -> bool {
        match expr {
            Expr::Lit(Literal::Integer(_), _, _) => true,
            Expr::Lit(_, ty, _) => matches!(ty, Ty::Con(tc) if tc.name.as_str() == "Integer"),
            Expr::Var(var, _) => {
                self.integer_vars.contains(&var.id) || self.is_integer_type(&var.ty)
            }
            // Type application wrapping
            Expr::TyApp(inner, _, _) => self.is_integer_expr(inner),
            // Application of known Integer RHS functions
            Expr::App(f, arg, _) => {
                if let Expr::Var(var, _) = f.as_ref() {
                    // negate on Integer
                    if var.name.as_str() == "negate" && self.is_integer_expr(arg) {
                        return true;
                    }
                    // Check function's return type from its type signature
                    if let Ty::Fun(_, result) = &var.ty {
                        if self.is_integer_type(result) {
                            return true;
                        }
                    }
                    // E.45: Known function with Integer parameter → result is Integer
                    // e.g., factorial :: Integer -> Integer, when applied to an arg
                    if self.integer_vars.contains(&var.id) {
                        // If the function itself is tracked as returning Integer
                        return true;
                    }
                }
                // Also check for curried apps: f x y where f :: a -> b -> Integer
                if let Expr::App(f2, inner_arg, _) = f.as_ref() {
                    if let Expr::Var(var, _) = f2.as_ref() {
                        // Check if the return type is concretely Integer
                        if let Ty::Fun(_, inner) = &var.ty {
                            if let Ty::Fun(_, result) = inner.as_ref() {
                                if self.is_integer_type(result) {
                                    return true;
                                }
                            }
                        }
                        // E.45: For polymorphic arithmetic operators (+ - * div mod etc.),
                        // if either operand is Integer, the result is Integer too.
                        let name = var.name.as_str();
                        if matches!(
                            name,
                            "+" | "-"
                                | "*"
                                | "div"
                                | "mod"
                                | "rem"
                                | "quot"
                                | "abs"
                                | "signum"
                                | "gcd"
                                | "lcm"
                        ) && (self.is_integer_expr(inner_arg) || self.is_integer_expr(arg))
                        {
                            return true;
                        }
                    }
                }
                // Check type annotation
                let ty = expr.ty();
                matches!(ty, Ty::Con(tc) if tc.name.as_str() == "Integer")
            }
            _ => {
                // Check type annotation
                let ty = expr.ty();
                matches!(ty, Ty::Con(tc) if tc.name.as_str() == "Integer")
            }
        }
    }

    /// E.45: Lower a PrimOp on Integer (arbitrary precision) operands.
    /// Dispatches to RTS functions (bhc_integer_add, bhc_integer_sub, etc.)
    fn lower_integer_primop(
        &mut self,
        op: PrimOp,
        args: &[&Expr],
    ) -> CodegenResult<Option<BasicValueEnum<'ctx>>> {
        // Map PrimOp to RTS VarId
        let (rts_var_id, rts_name): (usize, &str) = match op {
            PrimOp::Add => (1000603, "bhc_integer_add"),
            PrimOp::Sub => (1000604, "bhc_integer_sub"),
            PrimOp::Mul => (1000605, "bhc_integer_mul"),
            PrimOp::Div => (1000606, "bhc_integer_div"),
            PrimOp::Mod => (1000607, "bhc_integer_mod"),
            PrimOp::Rem => (1000607, "bhc_integer_mod"), // rem == mod for Integer
            PrimOp::Quot => (1000606, "bhc_integer_div"), // quot == div for Integer
            PrimOp::Negate => (1000608, "bhc_integer_negate"),
            PrimOp::Eq => (1000612, "bhc_integer_eq"),
            PrimOp::Ne => (1000612, "bhc_integer_eq"), // negate result
            PrimOp::Lt => (1000613, "bhc_integer_lt"),
            PrimOp::Le => (1000614, "bhc_integer_le"),
            PrimOp::Gt => (1000615, "bhc_integer_gt"),
            PrimOp::Ge => (1000616, "bhc_integer_ge"),
            _ => {
                return Err(CodegenError::Internal(format!(
                    "unsupported Integer PrimOp: {:?}",
                    op
                )))
            }
        };

        if matches!(op, PrimOp::Negate) {
            // Unary: negate(x)
            let arg = self
                .lower_expr(args[0])?
                .ok_or_else(|| CodegenError::Internal("integer negate: no arg".to_string()))?;
            let rts_fn = *self
                .functions
                .get(&VarId::new(rts_var_id))
                .ok_or_else(|| CodegenError::Internal(format!("{} not declared", rts_name)))?;
            let result = self
                .builder()
                .build_call(rts_fn, &[arg.into()], "integer_negate")
                .map_err(|e| CodegenError::Internal(format!("integer negate call: {:?}", e)))?;
            let val = result.try_as_basic_value().basic().ok_or_else(|| {
                CodegenError::Internal("integer negate returned void".to_string())
            })?;
            return Ok(Some(val));
        }

        // Binary operations
        let lhs_is_integer = self.is_integer_expr(args[0]);
        let rhs_is_integer = self.is_integer_expr(args[1]);
        let lhs = self
            .lower_expr(args[0])?
            .ok_or_else(|| CodegenError::Internal("integer primop: no lhs".to_string()))?;
        let rhs = self
            .lower_expr(args[1])?
            .ok_or_else(|| CodegenError::Internal("integer primop: no rhs".to_string()))?;

        // Convert non-Integer operands to Integer via bhc_integer_from_i64
        let lhs_ptr = if lhs_is_integer {
            self.value_to_ptr(lhs)?
        } else {
            // Int value — convert to Integer
            let from_i64_fn = *self.functions.get(&VarId::new(1000600)).ok_or_else(|| {
                CodegenError::Internal("bhc_integer_from_i64 not declared".to_string())
            })?;
            let int_val = if lhs.is_int_value() {
                lhs.into_int_value()
            } else {
                self.ptr_to_int(lhs.into_pointer_value())?
            };
            let result = self
                .builder()
                .build_call(from_i64_fn, &[int_val.into()], "int_to_integer_lhs")
                .map_err(|e| CodegenError::Internal(format!("int_to_integer: {:?}", e)))?;
            let ptr = result.try_as_basic_value().basic().ok_or_else(|| {
                CodegenError::Internal("int_to_integer returned void".to_string())
            })?;
            ptr.into_pointer_value()
        };
        let rhs_ptr = if rhs_is_integer {
            self.value_to_ptr(rhs)?
        } else {
            let from_i64_fn = *self.functions.get(&VarId::new(1000600)).ok_or_else(|| {
                CodegenError::Internal("bhc_integer_from_i64 not declared".to_string())
            })?;
            let int_val = if rhs.is_int_value() {
                rhs.into_int_value()
            } else {
                self.ptr_to_int(rhs.into_pointer_value())?
            };
            let result = self
                .builder()
                .build_call(from_i64_fn, &[int_val.into()], "int_to_integer_rhs")
                .map_err(|e| CodegenError::Internal(format!("int_to_integer: {:?}", e)))?;
            let ptr = result.try_as_basic_value().basic().ok_or_else(|| {
                CodegenError::Internal("int_to_integer returned void".to_string())
            })?;
            ptr.into_pointer_value()
        };

        let rts_fn = *self
            .functions
            .get(&VarId::new(rts_var_id))
            .ok_or_else(|| CodegenError::Internal(format!("{} not declared", rts_name)))?;

        let result = self
            .builder()
            .build_call(
                rts_fn,
                &[lhs_ptr.into(), rhs_ptr.into()],
                &format!("integer_{:?}", op),
            )
            .map_err(|e| CodegenError::Internal(format!("integer {:?} call: {:?}", op, e)))?;
        let val = result
            .try_as_basic_value()
            .basic()
            .ok_or_else(|| CodegenError::Internal(format!("integer {:?} returned void", op)))?;

        // For comparison operations, the RTS returns i64 (0 or 1)
        // We need to handle Ne specially (negate eq result)
        if matches!(
            op,
            PrimOp::Eq | PrimOp::Ne | PrimOp::Lt | PrimOp::Le | PrimOp::Gt | PrimOp::Ge
        ) {
            // RTS returns i64 (0 or 1) — extract the int value
            let int_result = if val.is_int_value() {
                val.into_int_value()
            } else {
                // If declared as returning ptr, convert ptr→int
                self.ptr_to_int(val.into_pointer_value())?
            };
            if matches!(op, PrimOp::Ne) {
                // Negate: 0→1, 1→0
                let one = self.type_mapper().i64_type().const_int(1, false);
                let inverted = self
                    .builder()
                    .build_int_sub(one, int_result, "integer_ne")
                    .map_err(|e| CodegenError::Internal(format!("integer ne: {:?}", e)))?;
                return Ok(Some(inverted.into()));
            }
            return Ok(Some(int_result.into()));
        }

        // For arithmetic operations, result is an Integer pointer — track it
        Ok(Some(val))
    }

    /// E.45: Promote an Int value to Integer via bhc_integer_from_i64.
    /// If the value is already a pointer (e.g., already Integer), returns it unchanged.
    fn promote_to_integer(
        &mut self,
        value: BasicValueEnum<'ctx>,
    ) -> CodegenResult<BasicValueEnum<'ctx>> {
        if value.is_pointer_value() {
            // Already a pointer — assume it's an Integer value
            return Ok(value);
        }
        // Int value → convert to Integer via bhc_integer_from_i64
        let int_val = if value.is_int_value() {
            value.into_int_value()
        } else {
            return Ok(value); // Unexpected type, don't convert
        };
        let from_i64_fn = *self.functions.get(&VarId::new(1000600)).ok_or_else(|| {
            CodegenError::Internal("bhc_integer_from_i64 not declared".to_string())
        })?;
        let result = self
            .builder()
            .build_call(from_i64_fn, &[int_val.into()], "promote_to_integer")
            .map_err(|e| CodegenError::Internal(format!("promote_to_integer: {:?}", e)))?;
        let ptr = result.try_as_basic_value().basic().ok_or_else(|| {
            CodegenError::Internal("promote_to_integer returned void".to_string())
        })?;
        Ok(ptr)
    }

    /// Check if an expression is of Rational type.
    fn is_rational_expr(&self, expr: &Expr) -> bool {
        match expr {
            Expr::Var(var, _) => self.is_rational_type(&var.ty),
            Expr::TyApp(inner, _, _) => self.is_rational_expr(inner),
            Expr::App(f, arg, _) => {
                // Check if function returns Rational
                match f.as_ref() {
                    Expr::Var(var, _) => {
                        let name = var.name.as_str();
                        // % always returns Rational
                        if name == "%" {
                            return true;
                        }
                        // fromInteger on Rational returns Rational
                        if name == "fromInteger" && self.is_rational_type(&var.ty) {
                            return true;
                        }
                        // Unary ops that preserve Rational type
                        if matches!(name, "negate" | "abs" | "signum" | "recip")
                            && self.is_rational_expr(arg)
                        {
                            return true;
                        }
                        // Check return type
                        if let Ty::Fun(_, result) = &var.ty {
                            if self.is_rational_type(result) {
                                return true;
                            }
                        }
                        false
                    }
                    // Binary ops: App(App(op, x), y) — f is App(op, x)
                    Expr::App(ff, inner_arg, _) => {
                        if let Expr::Var(var, _) = ff.as_ref() {
                            let name = var.name.as_str();
                            if name == "%" {
                                return true;
                            }
                            if matches!(name, "+" | "-" | "*" | "/" | "negate" | "abs" | "signum")
                                && (self.is_rational_expr(inner_arg) || self.is_rational_expr(arg))
                            {
                                return true;
                            }
                        }
                        false
                    }
                    _ => false,
                }
            }
            _ => {
                let ty = expr.ty();
                self.is_rational_type(&ty)
            }
        }
    }

    /// Lower a PrimOp on Rational operands.
    /// Dispatches to RTS functions (bhc_rational_add, bhc_rational_sub, etc.)
    fn lower_rational_primop(
        &mut self,
        op: PrimOp,
        args: &[&Expr],
    ) -> CodegenResult<Option<BasicValueEnum<'ctx>>> {
        // Map PrimOp to RTS VarId
        let (rts_var_id, rts_name): (usize, &str) = match op {
            PrimOp::Add => (1000903, "bhc_rational_add"),
            PrimOp::Sub => (1000904, "bhc_rational_sub"),
            PrimOp::Mul => (1000905, "bhc_rational_mul"),
            PrimOp::Div => (1000906, "bhc_rational_div"),
            PrimOp::Negate => (1000907, "bhc_rational_negate"),
            PrimOp::Abs => (1000908, "bhc_rational_abs"),
            PrimOp::Signum => (1000909, "bhc_rational_signum"),
            PrimOp::Eq => (1000910, "bhc_rational_eq"),
            PrimOp::Ne => (1000910, "bhc_rational_eq"), // negate result
            PrimOp::Lt | PrimOp::Le | PrimOp::Gt | PrimOp::Ge => (1000911, "bhc_rational_compare"),
            _ => {
                return Err(CodegenError::Internal(format!(
                    "unsupported Rational PrimOp: {:?}",
                    op
                )))
            }
        };

        if matches!(op, PrimOp::Negate | PrimOp::Abs | PrimOp::Signum) {
            // Unary operations
            let arg = self
                .lower_expr(args[0])?
                .ok_or_else(|| CodegenError::Internal("rational unary: no arg".to_string()))?;
            let arg_ptr = self.value_to_ptr(arg)?;
            let rts_fn = *self
                .functions
                .get(&VarId::new(rts_var_id))
                .ok_or_else(|| CodegenError::Internal(format!("{} not declared", rts_name)))?;
            let result = self
                .builder()
                .build_call(rts_fn, &[arg_ptr.into()], &format!("rational_{:?}", op))
                .map_err(|e| CodegenError::Internal(format!("rational {:?} call: {:?}", op, e)))?;
            let val = result.try_as_basic_value().basic().ok_or_else(|| {
                CodegenError::Internal(format!("rational {:?} returned void", op))
            })?;
            return Ok(Some(val));
        }

        // Binary operations
        let lhs = self
            .lower_expr(args[0])?
            .ok_or_else(|| CodegenError::Internal("rational primop: no lhs".to_string()))?;
        let rhs = self
            .lower_expr(args[1])?
            .ok_or_else(|| CodegenError::Internal("rational primop: no rhs".to_string()))?;

        // Convert non-Rational operands (Int) to Rational via bhc_rational_from_int
        let lhs_ptr = if self.is_rational_expr(args[0]) {
            self.value_to_ptr(lhs)?
        } else {
            // Int value -> convert to Rational
            let from_int_fn = *self.functions.get(&VarId::new(1000912)).ok_or_else(|| {
                CodegenError::Internal("bhc_rational_from_int not declared".to_string())
            })?;
            let int_val = self.coerce_to_int(lhs)?;
            let result = self
                .builder()
                .build_call(from_int_fn, &[int_val.into()], "int_to_rational_lhs")
                .map_err(|e| CodegenError::Internal(format!("int_to_rational: {:?}", e)))?;
            result
                .try_as_basic_value()
                .basic()
                .ok_or_else(|| CodegenError::Internal("int_to_rational returned void".to_string()))?
                .into_pointer_value()
        };
        let rhs_ptr = if self.is_rational_expr(args[1]) {
            self.value_to_ptr(rhs)?
        } else {
            let from_int_fn = *self.functions.get(&VarId::new(1000912)).ok_or_else(|| {
                CodegenError::Internal("bhc_rational_from_int not declared".to_string())
            })?;
            let int_val = self.coerce_to_int(rhs)?;
            let result = self
                .builder()
                .build_call(from_int_fn, &[int_val.into()], "int_to_rational_rhs")
                .map_err(|e| CodegenError::Internal(format!("int_to_rational: {:?}", e)))?;
            result
                .try_as_basic_value()
                .basic()
                .ok_or_else(|| CodegenError::Internal("int_to_rational returned void".to_string()))?
                .into_pointer_value()
        };

        let rts_fn = *self
            .functions
            .get(&VarId::new(rts_var_id))
            .ok_or_else(|| CodegenError::Internal(format!("{} not declared", rts_name)))?;

        let result = self
            .builder()
            .build_call(
                rts_fn,
                &[lhs_ptr.into(), rhs_ptr.into()],
                &format!("rational_{:?}", op),
            )
            .map_err(|e| CodegenError::Internal(format!("rational {:?} call: {:?}", op, e)))?;
        let val = result
            .try_as_basic_value()
            .basic()
            .ok_or_else(|| CodegenError::Internal(format!("rational {:?} returned void", op)))?;

        // For comparison operations
        if matches!(
            op,
            PrimOp::Eq | PrimOp::Ne | PrimOp::Lt | PrimOp::Le | PrimOp::Gt | PrimOp::Ge
        ) {
            if matches!(op, PrimOp::Eq | PrimOp::Ne) {
                // bhc_rational_eq returns i64 (0 or 1)
                let int_result = if val.is_int_value() {
                    val.into_int_value()
                } else {
                    self.ptr_to_int(val.into_pointer_value())?
                };
                if matches!(op, PrimOp::Ne) {
                    let one = self.type_mapper().i64_type().const_int(1, false);
                    let inverted = self
                        .builder()
                        .build_int_sub(one, int_result, "rational_ne")
                        .map_err(|e| CodegenError::Internal(format!("rational ne: {:?}", e)))?;
                    return Ok(Some(inverted.into()));
                }
                return Ok(Some(int_result.into()));
            }
            // bhc_rational_compare returns i32 (-1, 0, 1)
            let cmp_result = if val.is_int_value() {
                val.into_int_value()
            } else {
                self.ptr_to_int(val.into_pointer_value())?
            };
            let i64_ty = self.type_mapper().i64_type();
            let zero = i64_ty.const_zero();
            let bool_result = match op {
                PrimOp::Lt => self
                    .builder()
                    .build_int_compare(inkwell::IntPredicate::SLT, cmp_result, zero, "rat_lt")
                    .map_err(|e| CodegenError::Internal(format!("rat lt: {:?}", e)))?,
                PrimOp::Le => self
                    .builder()
                    .build_int_compare(inkwell::IntPredicate::SLE, cmp_result, zero, "rat_le")
                    .map_err(|e| CodegenError::Internal(format!("rat le: {:?}", e)))?,
                PrimOp::Gt => self
                    .builder()
                    .build_int_compare(inkwell::IntPredicate::SGT, cmp_result, zero, "rat_gt")
                    .map_err(|e| CodegenError::Internal(format!("rat gt: {:?}", e)))?,
                PrimOp::Ge => self
                    .builder()
                    .build_int_compare(inkwell::IntPredicate::SGE, cmp_result, zero, "rat_ge")
                    .map_err(|e| CodegenError::Internal(format!("rat ge: {:?}", e)))?,
                _ => unreachable!(),
            };
            let result_i64 = self
                .builder()
                .build_int_z_extend(bool_result, i64_ty, "rat_cmp_ext")
                .map_err(|e| CodegenError::Internal(format!("rat cmp ext: {:?}", e)))?;
            return Ok(Some(result_i64.into()));
        }

        Ok(Some(val))
    }

    /// Check if a name is a data constructor and return (tag, arity).
    ///
    /// This function checks:
    /// 1. Builtin types (Bool, Maybe, Either, List, Tuple, Ordering)
    /// 2. User-defined types registered via `register_constructor`
    fn constructor_info(&self, name: &str) -> Option<(u32, u32)> {
        // First check builtin constructors
        match name {
            // Bool constructors
            "False" => return Some((0, 0)), // tag=0, arity=0
            "True" => return Some((1, 0)),  // tag=1, arity=0

            // Maybe constructors
            "Nothing" => return Some((0, 0)), // tag=0, arity=0
            "Just" => return Some((1, 1)),    // tag=1, arity=1

            // Either constructors
            "Left" => return Some((0, 1)),  // tag=0, arity=1
            "Right" => return Some((1, 1)), // tag=1, arity=1

            // List constructors
            "[]" => return Some((0, 0)), // tag=0, arity=0 (Nil)
            ":" => return Some((1, 2)),  // tag=1, arity=2 (Cons head tail)

            // Unit constructor
            "()" => return Some((0, 0)), // tag=0, arity=0

            // Ordering constructors
            "LT" => return Some((0, 0)), // tag=0, arity=0
            "EQ" => return Some((1, 0)), // tag=1, arity=0
            "GT" => return Some((2, 0)), // tag=2, arity=0

            // GHC.Generics representation constructors
            "U1" => return Some((0, 0)),  // tag=0, arity=0 (unit)
            "K1" => return Some((0, 1)),  // tag=0, arity=1 (field wrapper)
            "M1" => return Some((0, 1)),  // tag=0, arity=1 (metadata wrapper)
            "L1" => return Some((0, 1)),  // tag=0, arity=1 (left sum)
            "R1" => return Some((1, 1)),  // tag=1, arity=1 (right sum)
            ":*:" => return Some((0, 2)), // tag=0, arity=2 (product)

            // IOMode constructors
            "ReadMode" => return Some((0, 0)),   // tag=0, arity=0
            "WriteMode" => return Some((1, 0)),  // tag=1, arity=0
            "AppendMode" => return Some((2, 0)), // tag=2, arity=0
            "ReadWriteMode" => return Some((3, 0)), // tag=3, arity=0

            // Tuple constructors - used for type class dictionaries
            "(,)" => return Some((0, 2)),       // 2-tuple
            "(,,)" => return Some((0, 3)),      // 3-tuple
            "(,,,)" => return Some((0, 4)),     // 4-tuple
            "(,,,,)" => return Some((0, 5)),    // 5-tuple
            "(,,,,,)" => return Some((0, 6)),   // 6-tuple
            "(,,,,,,)" => return Some((0, 7)),  // 7-tuple
            "(,,,,,,,)" => return Some((0, 8)), // 8-tuple

            _ => {}
        }

        // Check user-defined constructors registered from case alternatives
        if let Some(meta) = self.constructor_metadata.get(name) {
            return Some((meta.tag, meta.arity));
        }

        // If it looks like a constructor (starts with uppercase), return None
        // to indicate we don't have metadata yet (might be registered later).
        // Non-constructor names also have no metadata, so return None either way.
        None
    }

    // ========================================================================
    // Closure Support
    // ========================================================================
    //
    // Closures are represented as heap-allocated structs:
    //
    //   struct Closure {
    //       ptr fn_ptr;        // Pointer to the lifted function
    //       i64 env_size;      // Number of captured variables
    //       ptr env[];         // Array of captured variable pointers
    //   }
    //
    // When a lambda captures free variables, we:
    // 1. Compute the free variables
    // 2. Create a lifted function that takes (env_ptr, params...) as arguments
    // 3. Allocate a closure struct with the function pointer and captured values
    //
    // When calling a closure:
    // 1. Load the function pointer from the closure
    // 2. Call with the closure (as env) and the arguments
    // ========================================================================

    /// Compute the free variables of an expression.
    ///
    /// Returns the set of variable IDs that are referenced but not bound
    /// within the expression.
    fn free_vars(&self, expr: &Expr) -> FxHashSet<VarId> {
        let mut free = FxHashSet::default();
        let mut bound = FxHashSet::default();
        self.collect_free_vars(expr, &mut free, &mut bound);
        free
    }

    /// Helper to collect free variables, tracking bound variables.
    fn collect_free_vars(
        &self,
        expr: &Expr,
        free: &mut FxHashSet<VarId>,
        bound: &mut FxHashSet<VarId>,
    ) {
        match expr {
            Expr::Lit(_, _, _) => {}

            Expr::Var(var, _) => {
                // A variable is free if it's not bound and not a top-level function/constructor
                if !bound.contains(&var.id)
                    && !self.functions.contains_key(&var.id)
                    && self.constructor_info(var.name.as_str()).is_none()
                    && self.primitive_op_info(var.name.as_str()).is_none()
                    && self.rts_function_id(var.name.as_str()).is_none()
                {
                    free.insert(var.id);
                }
            }

            Expr::App(func, arg, _) => {
                self.collect_free_vars(func, free, bound);
                self.collect_free_vars(arg, free, bound);
            }

            Expr::Lam(param, body, _) => {
                bound.insert(param.id);
                self.collect_free_vars(body, free, bound);
                bound.remove(&param.id);
            }

            Expr::Let(bind, body, _) => {
                match &**bind {
                    Bind::NonRec(var, rhs) => {
                        self.collect_free_vars(rhs, free, bound);
                        bound.insert(var.id);
                        self.collect_free_vars(body, free, bound);
                        bound.remove(&var.id);
                    }
                    Bind::Rec(bindings) => {
                        // For recursive bindings, all vars are bound in both RHS and body
                        for (var, _) in bindings {
                            bound.insert(var.id);
                        }
                        for (_, rhs) in bindings {
                            self.collect_free_vars(rhs, free, bound);
                        }
                        self.collect_free_vars(body, free, bound);
                        for (var, _) in bindings {
                            bound.remove(&var.id);
                        }
                    }
                }
            }

            Expr::Case(scrut, alts, _, _) => {
                self.collect_free_vars(scrut, free, bound);
                for alt in alts {
                    for binder in &alt.binders {
                        bound.insert(binder.id);
                    }
                    self.collect_free_vars(&alt.rhs, free, bound);
                    for binder in &alt.binders {
                        bound.remove(&binder.id);
                    }
                }
            }

            Expr::TyApp(inner, _, _) => {
                self.collect_free_vars(inner, free, bound);
            }

            Expr::TyLam(_, body, _) => {
                self.collect_free_vars(body, free, bound);
            }

            Expr::Lazy(inner, _) => {
                self.collect_free_vars(inner, free, bound);
            }

            Expr::Cast(inner, _, _) => {
                self.collect_free_vars(inner, free, bound);
            }

            Expr::Tick(_, inner, _) => {
                self.collect_free_vars(inner, free, bound);
            }

            Expr::Type(_, _) | Expr::Coercion(_, _) => {}
        }
    }

    /// Get the LLVM struct type for a closure with the given environment size.
    ///
    /// Closure layout: { ptr fn_ptr, i64 env_size, [env_size x ptr] env }
    fn closure_type(&self, env_size: u32) -> inkwell::types::StructType<'ctx> {
        let tm = self.type_mapper();
        let ptr_type = tm.ptr_type();
        let i64_type = tm.i64_type();

        // Create array type for environment
        let env_type = ptr_type.array_type(env_size);

        // Struct: { ptr fn_ptr, i64 env_size, [env_size x ptr] env }
        self.llvm_ctx
            .struct_type(&[ptr_type.into(), i64_type.into(), env_type.into()], false)
    }

    /// Allocate a closure with the given function pointer and captured variables.
    fn alloc_closure(
        &self,
        fn_ptr: PointerValue<'ctx>,
        captured_vars: &[(VarId, BasicValueEnum<'ctx>)],
    ) -> CodegenResult<PointerValue<'ctx>> {
        let tm = self.type_mapper();
        let env_size = captured_vars.len() as u32;
        let closure_ty = self.closure_type(env_size);

        // Calculate size: sizeof(ptr) + sizeof(i64) + env_size * sizeof(ptr)
        let size = 8 + 8 + (env_size as u64) * 8;

        // Call bhc_alloc
        let alloc_fn = self
            .functions
            .get(&VarId::new(1000005))
            .ok_or_else(|| CodegenError::Internal("bhc_alloc not declared".to_string()))?;

        let size_val = tm.i64_type().const_int(size, false);
        let raw_ptr = self
            .builder()
            .build_call(*alloc_fn, &[size_val.into()], "closure_alloc")
            .map_err(|e| CodegenError::Internal(format!("failed to call bhc_alloc: {:?}", e)))?
            .try_as_basic_value()
            .basic()
            .ok_or_else(|| CodegenError::Internal("bhc_alloc returned void".to_string()))?;

        let closure_ptr = raw_ptr.into_pointer_value();

        // Store function pointer at offset 0
        let fn_ptr_slot = self
            .builder()
            .build_struct_gep(closure_ty, closure_ptr, 0, "fn_ptr_slot")
            .map_err(|e| CodegenError::Internal(format!("failed to get fn_ptr slot: {:?}", e)))?;

        self.builder()
            .build_store(fn_ptr_slot, fn_ptr)
            .map_err(|e| CodegenError::Internal(format!("failed to store fn_ptr: {:?}", e)))?;

        // Store env_size at offset 1
        let env_size_slot = self
            .builder()
            .build_struct_gep(closure_ty, closure_ptr, 1, "env_size_slot")
            .map_err(|e| CodegenError::Internal(format!("failed to get env_size slot: {:?}", e)))?;

        let env_size_val = tm.i64_type().const_int(env_size as u64, false);
        self.builder()
            .build_store(env_size_slot, env_size_val)
            .map_err(|e| CodegenError::Internal(format!("failed to store env_size: {:?}", e)))?;

        // Store captured variables in environment array
        if env_size > 0 {
            let env_slot = self
                .builder()
                .build_struct_gep(closure_ty, closure_ptr, 2, "env_slot")
                .map_err(|e| CodegenError::Internal(format!("failed to get env slot: {:?}", e)))?;

            for (i, (_var_id, val)) in captured_vars.iter().enumerate() {
                let elem_ptr = unsafe {
                    self.builder()
                        .build_in_bounds_gep(
                            tm.ptr_type().array_type(env_size),
                            env_slot,
                            &[
                                tm.i64_type().const_zero(),
                                tm.i64_type().const_int(i as u64, false),
                            ],
                            &format!("env_{}", i),
                        )
                        .map_err(|e| {
                            CodegenError::Internal(format!("failed to get env elem: {:?}", e))
                        })?
                };

                // Convert value to pointer if needed
                let ptr_val = self.value_to_ptr(*val)?;
                self.builder().build_store(elem_ptr, ptr_val).map_err(|e| {
                    CodegenError::Internal(format!("failed to store env elem: {:?}", e))
                })?;
            }
        }

        Ok(closure_ptr)
    }

    /// Extract the function pointer from a closure.
    fn extract_closure_fn_ptr(
        &self,
        closure_ptr: PointerValue<'ctx>,
    ) -> CodegenResult<PointerValue<'ctx>> {
        let closure_ty = self.closure_type(0); // Use 0-size for reading fn_ptr (same offset)

        let fn_ptr_slot = self
            .builder()
            .build_struct_gep(closure_ty, closure_ptr, 0, "fn_ptr_slot")
            .map_err(|e| CodegenError::Internal(format!("failed to get fn_ptr slot: {:?}", e)))?;

        let fn_ptr = self
            .builder()
            .build_load(self.type_mapper().ptr_type(), fn_ptr_slot, "fn_ptr")
            .map_err(|e| CodegenError::Internal(format!("failed to load fn_ptr: {:?}", e)))?;

        Ok(fn_ptr.into_pointer_value())
    }

    /// Extract an element from a closure's environment.
    fn extract_closure_env_elem(
        &self,
        closure_ptr: PointerValue<'ctx>,
        env_size: u32,
        index: u32,
    ) -> CodegenResult<PointerValue<'ctx>> {
        let tm = self.type_mapper();
        let closure_ty = self.closure_type(env_size);

        let env_slot = self
            .builder()
            .build_struct_gep(closure_ty, closure_ptr, 2, "env_slot")
            .map_err(|e| CodegenError::Internal(format!("failed to get env slot: {:?}", e)))?;

        let elem_ptr = unsafe {
            self.builder()
                .build_in_bounds_gep(
                    tm.ptr_type().array_type(env_size),
                    env_slot,
                    &[
                        tm.i64_type().const_zero(),
                        tm.i64_type().const_int(index as u64, false),
                    ],
                    &format!("env_elem_ptr_{}", index),
                )
                .map_err(|e| {
                    CodegenError::Internal(format!("failed to get env elem ptr: {:?}", e))
                })?
        };

        let elem_val = self
            .builder()
            .build_load(tm.ptr_type(), elem_ptr, &format!("env_elem_{}", index))
            .map_err(|e| CodegenError::Internal(format!("failed to load env elem: {:?}", e)))?;

        Ok(elem_val.into_pointer_value())
    }

    // ========================================================================
    // Thunk Support
    // ========================================================================
    //
    // Thunks are suspended computations for lazy evaluation.
    //
    // Memory Layout:
    //   struct Thunk {
    //       i64   tag;       // -1 = unevaluated, -2 = blackhole, >= 0 = evaluated
    //       ptr   eval_fn;   // Function to evaluate (when unevaluated)
    //       i64   env_size;  // Number of captured variables
    //       ptr[] env;       // Captured environment
    //   }
    //
    // The eval_fn has signature: extern "C" fn(env: *mut u8) -> *mut u8
    // It takes the environment pointer and returns the evaluated value.
    // ========================================================================

    /// Tag constant for unevaluated thunks.
    const THUNK_TAG: i64 = -1;

    /// Tag constant for indirections (evaluated thunks with cached result).
    #[allow(dead_code)]
    const INDIRECTION_TAG: i64 = -3;

    /// Check if an expression should be thunked in a let-binding.
    ///
    /// WHNF (Weak Head Normal Form) expressions don't need thunking:
    /// - Literals: already values
    /// - Variables: already evaluated or thunked themselves
    /// - Lambdas: already values (closures)
    /// - Constructors: already values
    ///
    /// Non-WHNF expressions need thunking to achieve lazy evaluation:
    /// - Applications: may trigger computation
    /// - Case expressions: pattern matching
    /// - Let expressions: nested bindings
    /// - PrimOp: primitive operations
    ///
    /// Check if an expression should be thunked in a let-binding.
    ///
    /// WHNF (Weak Head Normal Form) expressions don't need thunking:
    /// E.58: Check if an expression is trivial (doesn't need thunking).
    /// Trivial expressions are cheap to evaluate and have no side effects,
    /// so they can be evaluated eagerly without waste.
    /// Everything else should be thunked for lazy evaluation.
    fn is_trivial_expr(expr: &Expr) -> bool {
        match expr {
            // Literals are trivial
            Expr::Lit(_, _, _) => true,
            // Variable references are trivial (just a lookup)
            Expr::Var(_, _) => true,
            // Lambdas are trivial (just a closure allocation, no computation)
            Expr::Lam(_, _, _) => true,
            // Type-level wrappers are trivial if their inner expression is
            Expr::TyLam(_, inner, _) => Self::is_trivial_expr(inner),
            Expr::TyApp(inner, _, _) => Self::is_trivial_expr(inner),
            Expr::Cast(inner, _, _) => Self::is_trivial_expr(inner),
            Expr::Tick(_, inner, _) => Self::is_trivial_expr(inner),
            Expr::Type(_, _) => true,
            Expr::Coercion(_, _) => true,
            // Everything else is non-trivial (App, Case, Let, Lazy)
            _ => false,
        }
    }

    /// Get the LLVM struct type for a thunk with the given environment size.
    ///
    /// Thunk layout: { i64 tag, ptr eval_fn, i64 env_size, [env_size x ptr] env }
    fn thunk_type(&self, env_size: u32) -> inkwell::types::StructType<'ctx> {
        let tm = self.type_mapper();
        let ptr_type = tm.ptr_type();
        let i64_type = tm.i64_type();

        // Create array type for environment
        let env_type = ptr_type.array_type(env_size);

        // Struct: { i64 tag, ptr eval_fn, i64 env_size, [env_size x ptr] env }
        self.llvm_ctx.struct_type(
            &[
                i64_type.into(),
                ptr_type.into(),
                i64_type.into(),
                env_type.into(),
            ],
            false,
        )
    }

    /// Allocate a thunk with the given evaluation function and captured variables.
    fn alloc_thunk(
        &self,
        eval_fn: PointerValue<'ctx>,
        captured_vars: &[(VarId, BasicValueEnum<'ctx>)],
    ) -> CodegenResult<PointerValue<'ctx>> {
        let tm = self.type_mapper();
        let env_size = captured_vars.len() as u32;
        let thunk_ty = self.thunk_type(env_size);

        // Calculate size: sizeof(i64) + sizeof(ptr) + sizeof(i64) + env_size * sizeof(ptr)
        let size = 8 + 8 + 8 + (env_size as u64) * 8;

        // Call bhc_alloc
        let alloc_fn = self
            .functions
            .get(&VarId::new(1000005))
            .ok_or_else(|| CodegenError::Internal("bhc_alloc not declared".to_string()))?;

        let size_val = tm.i64_type().const_int(size, false);
        let raw_ptr = self
            .builder()
            .build_call(*alloc_fn, &[size_val.into()], "thunk_alloc")
            .map_err(|e| CodegenError::Internal(format!("failed to call bhc_alloc: {:?}", e)))?
            .try_as_basic_value()
            .basic()
            .ok_or_else(|| CodegenError::Internal("bhc_alloc returned void".to_string()))?;

        let thunk_ptr = raw_ptr.into_pointer_value();

        // Store tag = -1 (THUNK_TAG) at offset 0
        let tag_slot = self
            .builder()
            .build_struct_gep(thunk_ty, thunk_ptr, 0, "thunk_tag_slot")
            .map_err(|e| CodegenError::Internal(format!("failed to get tag slot: {:?}", e)))?;

        let tag_val = tm.i64_type().const_int(Self::THUNK_TAG as u64, true);
        self.builder()
            .build_store(tag_slot, tag_val)
            .map_err(|e| CodegenError::Internal(format!("failed to store thunk tag: {:?}", e)))?;

        // Store eval function pointer at offset 1
        let eval_fn_slot = self
            .builder()
            .build_struct_gep(thunk_ty, thunk_ptr, 1, "thunk_eval_fn_slot")
            .map_err(|e| CodegenError::Internal(format!("failed to get eval_fn slot: {:?}", e)))?;

        self.builder()
            .build_store(eval_fn_slot, eval_fn)
            .map_err(|e| CodegenError::Internal(format!("failed to store eval_fn: {:?}", e)))?;

        // Store env_size at offset 2
        let env_size_slot = self
            .builder()
            .build_struct_gep(thunk_ty, thunk_ptr, 2, "thunk_env_size_slot")
            .map_err(|e| CodegenError::Internal(format!("failed to get env_size slot: {:?}", e)))?;

        let env_size_val = tm.i64_type().const_int(env_size as u64, false);
        self.builder()
            .build_store(env_size_slot, env_size_val)
            .map_err(|e| CodegenError::Internal(format!("failed to store env_size: {:?}", e)))?;

        // Store captured variables in environment array
        if env_size > 0 {
            let env_slot = self
                .builder()
                .build_struct_gep(thunk_ty, thunk_ptr, 3, "thunk_env_slot")
                .map_err(|e| CodegenError::Internal(format!("failed to get env slot: {:?}", e)))?;

            for (i, (_var_id, val)) in captured_vars.iter().enumerate() {
                let elem_ptr = unsafe {
                    self.builder()
                        .build_in_bounds_gep(
                            tm.ptr_type().array_type(env_size),
                            env_slot,
                            &[
                                tm.i64_type().const_zero(),
                                tm.i64_type().const_int(i as u64, false),
                            ],
                            &format!("thunk_env_{}", i),
                        )
                        .map_err(|e| {
                            CodegenError::Internal(format!("failed to get env elem: {:?}", e))
                        })?
                };

                // Convert value to pointer if needed
                let ptr_val = self.value_to_ptr(*val)?;
                self.builder().build_store(elem_ptr, ptr_val).map_err(|e| {
                    CodegenError::Internal(format!("failed to store thunk env elem: {:?}", e))
                })?;
            }
        }

        Ok(thunk_ptr)
    }

    /// Generate a call to bhc_force to evaluate a thunk to WHNF.
    fn build_force(&self, val: BasicValueEnum<'ctx>) -> CodegenResult<BasicValueEnum<'ctx>> {
        let ptr = match val {
            BasicValueEnum::PointerValue(p) => p,
            // Non-pointers don't need forcing (primitives)
            _ => return Ok(val),
        };

        let force_fn = self
            .functions
            .get(&VarId::new(1000011))
            .ok_or_else(|| CodegenError::Internal("bhc_force not declared".to_string()))?;

        let result = self
            .builder()
            .build_call(*force_fn, &[ptr.into()], "forced")
            .map_err(|e| CodegenError::Internal(format!("failed to call bhc_force: {:?}", e)))?
            .try_as_basic_value()
            .basic()
            .ok_or_else(|| CodegenError::Internal("bhc_force returned void".to_string()))?;

        Ok(result)
    }

    /// Generate a unique name for a thunk evaluation function.
    /// Includes module name to avoid cross-module duplicate symbols.
    fn next_thunk_name(&mut self) -> String {
        let counter = self.closure_counter;
        self.closure_counter += 1;
        if let Some(ref mod_name) = self.module_name {
            format!("__thunk_eval_{}.{}", mod_name, counter)
        } else {
            format!("__thunk_eval_{}", counter)
        }
    }

    /// Generate a unique name for a closure function.
    /// Includes module name to avoid cross-module duplicate symbols.
    fn next_closure_name(&mut self) -> String {
        let counter = self.closure_counter;
        self.closure_counter += 1;
        if let Some(ref mod_name) = self.module_name {
            format!("__closure_{}.{}", mod_name, counter)
        } else {
            format!("__closure_{}", counter)
        }
    }

    /// Check if an expression is a saturated constructor application.
    ///
    /// Returns Some((tag, collected_args)) if the expression is a fully-applied constructor.
    fn is_saturated_constructor<'a>(
        &self,
        expr: &'a Expr,
    ) -> Option<(u32, u32, Vec<&'a Expr>, String)> {
        // Collect arguments while unwrapping applications
        let mut args = Vec::new();
        let mut current = expr;

        while let Expr::App(func, arg, _) = current {
            args.push(arg.as_ref());
            current = func.as_ref();
        }

        // Skip through type applications (erased at runtime)
        while let Expr::TyApp(inner, _, _) = current {
            current = inner.as_ref();
        }

        // Check if the head is a constructor
        if let Expr::Var(var, _) = current {
            if let Some((tag, arity)) = self.constructor_info(var.name.as_str()) {
                args.reverse();
                if args.len() == arity as usize {
                    return Some((tag, arity, args, var.name.as_str().to_string()));
                }
            }
        }

        None
    }

    /// Lower a Core module to LLVM IR.
    pub fn lower_module(&mut self, core_module: &CoreModule) -> CodegenResult<()> {
        // Propagate language extensions from the Core module
        self.overloaded_strings = core_module.overloaded_strings;

        // Register constructors from CoreModule metadata.
        // This ensures all constructors defined in the module are known to codegen,
        // even if they are only used as values (not in case alternatives).
        for con in &core_module.constructors {
            self.register_constructor(&con.name, con.tag, con.arity);
            if let Some(meta) = self.constructor_metadata.get_mut(&con.name) {
                if let Some(ref type_name) = con.type_name {
                    if meta.type_name.is_none() {
                        meta.type_name = Some(type_name.clone());
                    }
                }
                if con.is_newtype {
                    meta.is_newtype = true;
                }
            }
        }

        // Pre-pass: collect all constructor metadata from case alternatives
        // This ensures constructors are known before we try to lower applications
        for bind in &core_module.bindings {
            self.collect_constructors_from_binding(bind);
        }

        // Pre-pass: detect which functions use StateT/ReaderT operations.
        // This must happen before lowering so that callers of these functions
        // can detect the correct transformer layer.
        self.detect_transformer_functions(&core_module.bindings);

        // Pre-pass: detect derived instance methods (e.g., $derived_show_Color)
        // and populate dispatch tables + constructor-to-type mappings.
        self.detect_derived_instance_methods(&core_module.bindings);

        // Declare foreign imports (creates C declarations and BHC wrappers)
        self.declare_foreign_imports(&core_module.foreign_imports)?;

        // First pass: declare all top-level functions
        for bind in &core_module.bindings {
            self.declare_binding(bind)?;
        }

        // Second pass: define all functions
        for bind in &core_module.bindings {
            self.lower_binding(bind)?;
        }

        Ok(())
    }

    /// Pre-pass: scan all bindings to detect which functions use StateT/ReaderT/ExceptT/WriterT.
    /// This populates the function sets so that `detect_operation_layer` can recognize
    /// calls to user-defined monadic functions.
    fn detect_transformer_functions(&mut self, bindings: &[Bind]) {
        // First pass: detect direct users of get/put/ask/throwE/tell/etc.
        for bind in bindings {
            match bind {
                Bind::NonRec(var, expr) => {
                    if let Some(layer) = self.detect_transformer_for_body(expr) {
                        let name = var.name.as_str().to_string();
                        match layer {
                            TransformerLayer::StateT => {
                                self.state_t_functions.insert(name);
                            }
                            TransformerLayer::ReaderT => {
                                self.reader_t_functions.insert(name);
                            }
                            TransformerLayer::ExceptT => {
                                self.except_t_functions.insert(name);
                            }
                            TransformerLayer::WriterT => {
                                self.writer_t_functions.insert(name);
                            }
                            _ => {}
                        }
                    }
                }
                Bind::Rec(pairs) => {
                    for (var, expr) in pairs {
                        if let Some(layer) = self.detect_transformer_for_body(expr) {
                            let name = var.name.as_str().to_string();
                            match layer {
                                TransformerLayer::StateT => {
                                    self.state_t_functions.insert(name);
                                }
                                TransformerLayer::ReaderT => {
                                    self.reader_t_functions.insert(name);
                                }
                                TransformerLayer::ExceptT => {
                                    self.except_t_functions.insert(name);
                                }
                                TransformerLayer::WriterT => {
                                    self.writer_t_functions.insert(name);
                                }
                                _ => {}
                            }
                        }
                    }
                }
            }
        }
        // Second pass: propagate — functions that call transformer functions
        // are themselves transformer functions.
        let mut changed = true;
        while changed {
            changed = false;
            for bind in bindings {
                match bind {
                    Bind::NonRec(var, expr) => {
                        let name = var.name.as_str().to_string();
                        if !self.state_t_functions.contains(&name)
                            && !self.reader_t_functions.contains(&name)
                            && !self.except_t_functions.contains(&name)
                            && !self.writer_t_functions.contains(&name)
                        {
                            if let Some(layer) = self.detect_transformer_for_calls(expr) {
                                match layer {
                                    TransformerLayer::StateT => {
                                        self.state_t_functions.insert(name);
                                        changed = true;
                                    }
                                    TransformerLayer::ReaderT => {
                                        self.reader_t_functions.insert(name);
                                        changed = true;
                                    }
                                    TransformerLayer::ExceptT => {
                                        self.except_t_functions.insert(name);
                                        changed = true;
                                    }
                                    TransformerLayer::WriterT => {
                                        self.writer_t_functions.insert(name);
                                        changed = true;
                                    }
                                    _ => {}
                                }
                            }
                        }
                    }
                    Bind::Rec(pairs) => {
                        for (var, expr) in pairs {
                            let name = var.name.as_str().to_string();
                            if !self.state_t_functions.contains(&name)
                                && !self.reader_t_functions.contains(&name)
                                && !self.except_t_functions.contains(&name)
                                && !self.writer_t_functions.contains(&name)
                            {
                                if let Some(layer) = self.detect_transformer_for_calls(expr) {
                                    match layer {
                                        TransformerLayer::StateT => {
                                            self.state_t_functions.insert(name);
                                            changed = true;
                                        }
                                        TransformerLayer::ReaderT => {
                                            self.reader_t_functions.insert(name);
                                            changed = true;
                                        }
                                        TransformerLayer::ExceptT => {
                                            self.except_t_functions.insert(name);
                                            changed = true;
                                        }
                                        TransformerLayer::WriterT => {
                                            self.writer_t_functions.insert(name);
                                            changed = true;
                                        }
                                        _ => {}
                                    }
                                }
                            }
                        }
                    }
                }
            }
        }
    }

    /// Detect if an expression calls any known transformer function.
    fn detect_transformer_for_calls(&self, expr: &Expr) -> Option<TransformerLayer> {
        match expr {
            Expr::Var(v, _) => {
                let name = v.name.as_str();
                if self.state_t_functions.contains(name) {
                    Some(TransformerLayer::StateT)
                } else if self.reader_t_functions.contains(name) {
                    Some(TransformerLayer::ReaderT)
                } else if self.except_t_functions.contains(name) {
                    Some(TransformerLayer::ExceptT)
                } else if self.writer_t_functions.contains(name) {
                    Some(TransformerLayer::WriterT)
                } else {
                    None
                }
            }
            Expr::App(func, arg, _) => self
                .detect_transformer_for_calls(func)
                .or_else(|| self.detect_transformer_for_calls(arg)),
            Expr::Lam(_, body, _) => self.detect_transformer_for_calls(body),
            Expr::Let(bind, body, _) => {
                let in_bind = match bind.as_ref() {
                    Bind::NonRec(_, e) => self.detect_transformer_for_calls(e),
                    Bind::Rec(bindings) => bindings
                        .iter()
                        .find_map(|(_, e)| self.detect_transformer_for_calls(e)),
                };
                in_bind.or_else(|| self.detect_transformer_for_calls(body))
            }
            Expr::Case(scrut, alts, _, _) => {
                self.detect_transformer_for_calls(scrut).or_else(|| {
                    alts.iter()
                        .find_map(|alt| self.detect_transformer_for_calls(&alt.rhs))
                })
            }
            Expr::TyApp(e, _, _) => self.detect_transformer_for_calls(e),
            _ => None,
        }
    }

    /// Detect derived instance methods by naming convention ($derived_show_*, $derived_eq_*, $derived_compare_*).
    /// Populates the derived_show_fns/derived_eq_fns/derived_compare_fns dispatch tables and
    /// constructor_metadata.type_name from case alternatives in derived bindings.
    fn detect_derived_instance_methods(&mut self, bindings: &[Bind]) {
        for bind in bindings {
            if let Bind::NonRec(var, expr) = bind {
                let name = var.name.as_str();
                // Detect auto-derived instance methods ($derived_show_TypeName_COUNTER)
                if let Some(remainder) = name.strip_prefix("$derived_show_") {
                    let type_name = Self::strip_deriving_counter_suffix(remainder);
                    self.derived_show_fns.insert(type_name.clone(), var.id);
                    // Tag constructors found in this binding with their type_name
                    self.tag_constructors_with_type(expr, &type_name);
                } else if let Some(remainder) = name.strip_prefix("$derived_read_") {
                    let type_name = Self::strip_deriving_counter_suffix(remainder);
                    self.derived_read_fns.insert(type_name.clone(), var.id);
                    self.tag_constructors_with_type(expr, &type_name);
                } else if let Some(remainder) = name.strip_prefix("$derived_eq_") {
                    let type_name = Self::strip_deriving_counter_suffix(remainder);
                    self.derived_eq_fns.insert(type_name.clone(), var.id);
                    self.tag_constructors_with_type(expr, &type_name);
                } else if let Some(remainder) = name.strip_prefix("$derived_compare_") {
                    let type_name = Self::strip_deriving_counter_suffix(remainder);
                    self.derived_compare_fns.insert(type_name.clone(), var.id);
                    self.tag_constructors_with_type(expr, &type_name);
                }
                // Detect auto-derived Functor instance methods ($derived_fmap_TypeName_COUNTER)
                else if let Some(remainder) = name.strip_prefix("$derived_fmap_") {
                    let type_name = Self::strip_deriving_counter_suffix(remainder);
                    self.derived_functor_fns.insert(type_name.clone(), var.id);
                    self.tag_constructors_with_type(expr, &type_name);
                }
                // Detect auto-derived Foldable instance methods ($derived_foldr_TypeName_COUNTER)
                else if let Some(remainder) = name.strip_prefix("$derived_foldr_") {
                    let type_name = Self::strip_deriving_counter_suffix(remainder);
                    self.derived_foldable_fns.insert(type_name.clone(), var.id);
                    self.tag_constructors_with_type(expr, &type_name);
                }
                // Detect auto-derived Traversable instance methods ($derived_traverse_TypeName_COUNTER)
                else if let Some(remainder) = name.strip_prefix("$derived_traverse_") {
                    let type_name = Self::strip_deriving_counter_suffix(remainder);
                    self.derived_traversable_fns
                        .insert(type_name.clone(), var.id);
                    self.tag_constructors_with_type(expr, &type_name);
                }
                // Detect auto-derived Enum instance methods ($derived_fromEnum_TypeName_COUNTER, $derived_toEnum_TypeName_COUNTER)
                else if let Some(remainder) = name.strip_prefix("$derived_fromEnum_") {
                    let type_name = Self::strip_deriving_counter_suffix(remainder);
                    self.derived_from_enum_fns.insert(type_name.clone(), var.id);
                    self.tag_constructors_with_type(expr, &type_name);
                } else if let Some(remainder) = name.strip_prefix("$derived_toEnum_") {
                    let type_name = Self::strip_deriving_counter_suffix(remainder);
                    self.derived_to_enum_fns.insert(type_name.clone(), var.id);
                    self.tag_constructors_with_type(expr, &type_name);
                }
                // Detect auto-derived Bounded instance methods ($derived_minBound_TypeName_COUNTER, $derived_maxBound_TypeName_COUNTER)
                else if let Some(remainder) = name.strip_prefix("$derived_minBound_") {
                    let type_name = Self::strip_deriving_counter_suffix(remainder);
                    self.derived_min_bound_fns.insert(type_name.clone(), var.id);
                    self.tag_constructors_with_type(expr, &type_name);
                } else if let Some(remainder) = name.strip_prefix("$derived_maxBound_") {
                    let type_name = Self::strip_deriving_counter_suffix(remainder);
                    self.derived_max_bound_fns.insert(type_name.clone(), var.id);
                    self.tag_constructors_with_type(expr, &type_name);
                }
                // Detect auto-derived Generic instance methods ($derived_from_TypeName_COUNTER, $derived_to_TypeName_COUNTER)
                else if let Some(remainder) = name.strip_prefix("$derived_from_") {
                    let type_name = Self::strip_deriving_counter_suffix(remainder);
                    self.derived_from_fns.insert(type_name.clone(), var.id);
                    self.tag_constructors_with_type(expr, &type_name);
                } else if let Some(remainder) = name.strip_prefix("$derived_to_") {
                    let type_name = Self::strip_deriving_counter_suffix(remainder);
                    self.derived_to_fns.insert(type_name.clone(), var.id);
                    self.tag_constructors_with_type(expr, &type_name);
                }
                // Detect manual instance methods ($instance_show_TypeName)
                else if let Some(type_name) = name.strip_prefix("$instance_show_") {
                    self.derived_show_fns.insert(type_name.to_string(), var.id);
                    self.tag_constructors_with_type(expr, type_name);
                } else if let Some(type_name) = name.strip_prefix("$instance_==_") {
                    self.derived_eq_fns.insert(type_name.to_string(), var.id);
                    self.tag_constructors_with_type(expr, type_name);
                } else if let Some(type_name) = name.strip_prefix("$instance_compare_") {
                    self.derived_compare_fns
                        .insert(type_name.to_string(), var.id);
                    self.tag_constructors_with_type(expr, type_name);
                } else if let Some(type_name) = name.strip_prefix("$instance_fmap_") {
                    self.derived_functor_fns
                        .insert(type_name.to_string(), var.id);
                    self.tag_constructors_with_type(expr, type_name);
                } else if let Some(type_name) = name.strip_prefix("$instance_foldr_") {
                    self.derived_foldable_fns
                        .insert(type_name.to_string(), var.id);
                    self.tag_constructors_with_type(expr, type_name);
                } else if let Some(type_name) = name.strip_prefix("$instance_traverse_") {
                    self.derived_traversable_fns
                        .insert(type_name.to_string(), var.id);
                    self.tag_constructors_with_type(expr, type_name);
                } else if let Some(type_name) = name.strip_prefix("$instance_fromEnum_") {
                    self.derived_from_enum_fns
                        .insert(type_name.to_string(), var.id);
                    self.tag_constructors_with_type(expr, type_name);
                } else if let Some(type_name) = name.strip_prefix("$instance_toEnum_") {
                    self.derived_to_enum_fns
                        .insert(type_name.to_string(), var.id);
                    self.tag_constructors_with_type(expr, type_name);
                } else if let Some(type_name) = name.strip_prefix("$instance_minBound_") {
                    self.derived_min_bound_fns
                        .insert(type_name.to_string(), var.id);
                    self.tag_constructors_with_type(expr, type_name);
                } else if let Some(type_name) = name.strip_prefix("$instance_maxBound_") {
                    self.derived_max_bound_fns
                        .insert(type_name.to_string(), var.id);
                    self.tag_constructors_with_type(expr, type_name);
                }
            }
        }
    }

    /// Strip the counter suffix from a derived binding name.
    /// e.g., "Color_50000" → "Color", "My_Type_50002" → "My_Type"
    fn strip_deriving_counter_suffix(name: &str) -> String {
        if let Some((prefix, suffix)) = name.rsplit_once('_') {
            if suffix.chars().all(|c| c.is_ascii_digit()) {
                return prefix.to_string();
            }
        }
        name.to_string()
    }

    /// Walk an expression and tag any constructors in case alternatives with the given type name.
    fn tag_constructors_with_type(&mut self, expr: &Expr, type_name: &str) {
        match expr {
            Expr::Lam(_, body, _) => self.tag_constructors_with_type(body, type_name),
            Expr::Case(_, alts, _, _) => {
                for alt in alts {
                    if let AltCon::DataCon(con) = &alt.con {
                        let con_name = con.name.as_str().to_string();
                        if let Some(meta) = self.constructor_metadata.get_mut(&con_name) {
                            meta.type_name = Some(type_name.to_string());
                        }
                    }
                    self.tag_constructors_with_type(&alt.rhs, type_name);
                }
            }
            Expr::Let(bind, body, _) => {
                if let Bind::NonRec(_, e) = bind.as_ref() {
                    self.tag_constructors_with_type(e, type_name);
                }
                self.tag_constructors_with_type(body, type_name);
            }
            Expr::App(f, arg, _) => {
                self.tag_constructors_with_type(f, type_name);
                self.tag_constructors_with_type(arg, type_name);
            }
            _ => {}
        }
    }

    /// Infer the user ADT type name from an expression, for derived method dispatch.
    fn infer_adt_type_from_expr(&self, expr: &Expr) -> Option<String> {
        match expr {
            // A bare constructor: e.g., `Red`
            Expr::Var(var, _) => {
                let name = var.name.as_str();
                if let Some(meta) = self.constructor_metadata.get(name) {
                    return meta.type_name.clone();
                }
                // E.54: minBound/maxBound — use single-enum heuristic
                if (name == "minBound" || name == "maxBound")
                    && self.derived_min_bound_fns.len() == 1
                {
                    return Some(self.derived_min_bound_fns.keys().next().unwrap().clone());
                }
                None
            }
            // A constructor application: e.g., `Circle 5`
            Expr::App(f, arg, _) => {
                // E.54: succ/pred preserve ADT type from their argument
                // E.63: force/id are identity — preserve ADT type from argument
                if let Expr::Var(fv, _) = f.as_ref() {
                    let fname = fv.name.as_str();
                    if fname == "succ"
                        || fname == "pred"
                        || fname == "force"
                        || fname == "id"
                        || fname == "to"
                        || fname == "from"
                    {
                        return self.infer_adt_type_from_expr(arg);
                    }
                    // E.54: toEnum — use single-enum heuristic
                    if fname == "toEnum" && self.derived_to_enum_fns.len() == 1 {
                        return Some(self.derived_to_enum_fns.keys().next().unwrap().clone());
                    }
                    // read — use single-derived-read heuristic
                    if fname == "read" && self.derived_read_fns.len() == 1 {
                        return Some(self.derived_read_fns.keys().next().unwrap().clone());
                    }
                }
                self.infer_adt_type_from_expr(f)
            }
            // Let binding: check body
            Expr::Let(_, body, _) => self.infer_adt_type_from_expr(body),
            // Type application: check inner
            Expr::TyApp(inner, _, _) => self.infer_adt_type_from_expr(inner),
            // Case expression: check any alternative's DataCon for type info
            Expr::Case(_, alts, _, _) => {
                for alt in alts {
                    if let AltCon::DataCon(con) = &alt.con {
                        let con_name = con.name.as_str();
                        if let Some(meta) = self.constructor_metadata.get(con_name) {
                            if let Some(ref tn) = meta.type_name {
                                return Some(tn.clone());
                            }
                        }
                    }
                }
                None
            }
            _ => None,
        }
    }

    /// Infer the target ADT type for a `read` call.
    ///
    /// When `read` is called on a string expression, we need to determine which
    /// derived Read instance to use. We use a heuristic: if there is exactly one
    /// derived Read instance, use it. This covers the common case where a module
    /// has a single ADT with `deriving Read`.
    fn infer_read_target_type(&self, _str_expr: &Expr) -> Option<String> {
        if self.derived_read_fns.len() == 1 {
            return Some(self.derived_read_fns.keys().next().unwrap().clone());
        }
        None
    }

    /// Check if an expression is a user ADT value (for Eq dispatch).
    fn is_user_adt_expr(
        expr: &Expr,
        constructor_metadata: &FxHashMap<String, ConstructorMeta>,
    ) -> Option<String> {
        match expr {
            Expr::Var(var, _) => {
                let name = var.name.as_str();
                if let Some(meta) = constructor_metadata.get(name) {
                    return meta.type_name.clone();
                }
                None
            }
            Expr::App(f, _, _) => Self::is_user_adt_expr(f, constructor_metadata),
            Expr::Let(_, body, _) => Self::is_user_adt_expr(body, constructor_metadata),
            _ => None,
        }
    }

    /// Collect constructor metadata from a binding's expression.
    fn collect_constructors_from_binding(&mut self, bind: &Bind) {
        match bind {
            Bind::NonRec(_, expr) => {
                self.collect_constructors_from_expr(expr);
            }
            Bind::Rec(bindings) => {
                for (_, expr) in bindings {
                    self.collect_constructors_from_expr(expr);
                }
            }
        }
    }

    /// Recursively collect constructor metadata from an expression.
    fn collect_constructors_from_expr(&mut self, expr: &Expr) {
        match expr {
            Expr::Case(scrut, alts, _, _) => {
                self.collect_constructors_from_expr(scrut);
                for alt in alts {
                    if let AltCon::DataCon(con) = &alt.con {
                        self.register_constructor(con.name.as_str(), con.tag, con.arity);
                    }
                    self.collect_constructors_from_expr(&alt.rhs);
                }
            }
            Expr::App(func, arg, _) => {
                self.collect_constructors_from_expr(func);
                self.collect_constructors_from_expr(arg);
            }
            Expr::TyApp(func, _, _) => {
                self.collect_constructors_from_expr(func);
            }
            Expr::Lam(_, body, _) => {
                self.collect_constructors_from_expr(body);
            }
            Expr::TyLam(_, body, _) => {
                self.collect_constructors_from_expr(body);
            }
            Expr::Let(bind, body, _) => {
                self.collect_constructors_from_binding(bind);
                self.collect_constructors_from_expr(body);
            }
            Expr::Lazy(inner, _) => {
                self.collect_constructors_from_expr(inner);
            }
            Expr::Cast(inner, _, _) => {
                self.collect_constructors_from_expr(inner);
            }
            Expr::Tick(_, inner, _) => {
                self.collect_constructors_from_expr(inner);
            }
            // Leaf nodes that don't contain constructors
            Expr::Var(_, _) | Expr::Lit(_, _, _) | Expr::Type(_, _) | Expr::Coercion(_, _) => {}
        }
    }

    /// Declare a binding (creates function signature without body).
    fn declare_binding(&mut self, bind: &Bind) -> CodegenResult<()> {
        match bind {
            Bind::NonRec(var, expr) => {
                let fn_val = self.declare_function_from_expr(var, expr)?;
                self.functions.insert(var.id, fn_val);
            }
            Bind::Rec(bindings) => {
                for (var, expr) in bindings {
                    let fn_val = self.declare_function_from_expr(var, expr)?;
                    self.functions.insert(var.id, fn_val);
                }
            }
        }
        Ok(())
    }

    /// Count the number of lambda parameters in an expression.
    fn count_lambda_params(&self, expr: &Expr) -> usize {
        let mut count = 0;
        let mut current = expr;
        while let Expr::Lam(_, body, _) = current {
            count += 1;
            current = body.as_ref();
        }
        count
    }

    /// Check if a type contains unresolved type variables or errors.
    // Type-inference predicate retained for monomorphization paths.
    #[allow(dead_code)]
    fn type_needs_inference(&self, ty: &Ty) -> bool {
        match ty {
            Ty::Error => true,
            Ty::Var(_) => true,
            Ty::Fun(arg, ret) => self.type_needs_inference(arg) || self.type_needs_inference(ret),
            Ty::Forall(_, body) => self.type_needs_inference(body),
            Ty::App(f, arg) => self.type_needs_inference(f) || self.type_needs_inference(arg),
            _ => false,
        }
    }

    /// Declare a function from a Core variable and expression.
    /// Uses the expression to infer the function type when var.ty contains unresolved types.
    fn declare_function_from_expr(
        &self,
        var: &Var,
        expr: &Expr,
    ) -> CodegenResult<FunctionValue<'ctx>> {
        let param_count = self.count_lambda_params(expr);

        // Count type-level parameters for comparison
        let type_param_count = {
            let mut count = 0;
            let mut current = &var.ty;
            while let Ty::Fun(_, ret) = current {
                count += 1;
                current = ret;
            }
            count
        };

        // Use expression-based (pointer) typing when:
        // 1. Error types (derived/generated bindings)
        // 2. Expression has more lambdas than the type suggests (dictionary params
        //    from typeclass constraints are not reflected in the Haskell type)
        let fn_type = if param_count > 0
            && (matches!(&var.ty, Ty::Error) || param_count > type_param_count)
        {
            let tm = self.type_mapper();

            // All functions take (env_ptr, args...) for uniform closure calling convention
            let mut param_types: Vec<inkwell::types::BasicMetadataTypeEnum<'ctx>> = Vec::new();
            param_types.push(tm.ptr_type().into()); // env/closure pointer
            for _ in 0..param_count {
                param_types.push(tm.ptr_type().into());
            }

            // Default return type is pointer
            tm.ptr_type().fn_type(&param_types, false)
        } else {
            self.lower_function_type(&var.ty)?
        };
        let name = self.mangle_name(var.name.as_str());
        Ok(self.module.add_function(&name, fn_type))
    }

    /// Declare a function from a Core variable.
    // Alternate function-declaration path retained for incremental lowering.
    #[allow(dead_code)]
    fn declare_function(&self, var: &Var) -> CodegenResult<FunctionValue<'ctx>> {
        let fn_type = self.lower_function_type(&var.ty)?;
        let name = self.mangle_name(var.name.as_str());
        Ok(self.module.add_function(&name, fn_type))
    }

    /// Lower a binding to LLVM IR.
    fn lower_binding(&mut self, bind: &Bind) -> CodegenResult<()> {
        match bind {
            Bind::NonRec(var, expr) => {
                self.lower_function_def(var, expr)?;
            }
            Bind::Rec(bindings) => {
                for (var, expr) in bindings {
                    self.lower_function_def(var, expr)?;
                }
            }
        }
        Ok(())
    }

    /// Lower a function definition.
    fn lower_function_def(&mut self, var: &Var, expr: &Expr) -> CodegenResult<()> {
        let fn_val = self.functions.get(&var.id).copied().ok_or_else(|| {
            CodegenError::Internal(format!("function not declared: {}", var.name.as_str()))
        })?;

        // Create entry block
        let entry = self.llvm_context().append_basic_block(fn_val, "entry");
        self.builder().position_at_end(entry);

        // Detect if this function uses StateT/ReaderT context.
        // We check TWO sources:
        // 1. The function's type signature (for functions like `comp = return 42` where the
        //    body doesn't have transformer-specific operations but the type is a transformer)
        // 2. The function body (for functions that use get/put/ask/etc.)
        //
        // For nested transformers like StateT s (ReaderT r IO), we need to push the FULL
        // transformer stack so that operations like `ask` can detect they're in a nested context.
        let return_type = self.get_return_type_from_function_type(&var.ty);
        let mut transformer_stack_from_type = self.extract_transformer_stack_from_type(return_type);

        // Remove IO from the end since it's already the base of our stack
        // The stack is [StateT, ReaderT, IO], we want to push [StateT, ReaderT]
        if transformer_stack_from_type.last() == Some(&TransformerLayer::IO) {
            transformer_stack_from_type.pop();
        }

        // We need to push in REVERSE order since push() inserts at position 0
        // For [StateT, ReaderT], we push ReaderT first, then StateT, resulting in [StateT, ReaderT, IO]
        let layers_to_push: Vec<_> = transformer_stack_from_type.iter().rev().cloned().collect();
        let pushed_count = layers_to_push.len();
        // Push the full transformer stack (innermost first since push inserts at position 0)
        for layer in &layers_to_push {
            let fn_name = var.name.as_str().to_string();
            match layer {
                TransformerLayer::StateT => {
                    self.state_t_functions.insert(fn_name.clone());
                }
                TransformerLayer::ReaderT => {
                    self.reader_t_functions.insert(fn_name.clone());
                }
                _ => {}
            }
            self.push_transformer_layer(*layer);
        }

        // If type didn't indicate transformers, fall back to body detection
        let extra_layer = if pushed_count == 0 {
            self.detect_transformer_for_body(expr)
        } else {
            None
        };
        if let Some(layer) = extra_layer {
            let fn_name = var.name.as_str().to_string();
            match layer {
                TransformerLayer::StateT => {
                    self.state_t_functions.insert(fn_name);
                }
                TransformerLayer::ReaderT => {
                    self.reader_t_functions.insert(fn_name);
                }
                _ => {}
            }
            self.push_transformer_layer(layer);
        }

        // E.45: Extract Integer parameter positions from the function's type signature.
        // This is needed because Core IR lambda params have Ty::Error (type info lost).
        let integer_param_positions = self.extract_integer_param_positions(&var.ty);

        // Lower the function body, handling lambda parameters
        let result = self.lower_function_body(fn_val, expr, &integer_param_positions)?;

        // Pop all the pushed layers
        if extra_layer.is_some() {
            self.pop_transformer_layer();
        }
        for _ in 0..pushed_count {
            self.pop_transformer_layer();
        }

        // Check if the current block already has a terminator (e.g., from `error` or `unreachable`)
        // If so, don't add another terminator
        let current_block = self.builder().get_insert_block();
        let has_terminator = current_block
            .map(|bb| bb.get_terminator().is_some())
            .unwrap_or(false);

        if !has_terminator {
            // Build return based on function's declared return type, not the computed result
            // This handles cases like IO () which produces a value but should return void
            let ret_type = fn_val.get_type().get_return_type();
            if ret_type.is_none() {
                // Void return type - don't return a value
                self.builder().build_return(None).map_err(|e| {
                    CodegenError::Internal(format!("failed to build return: {:?}", e))
                })?;
            } else if let Some(val) = result {
                // Convert to pointer if return type is pointer (uniform calling convention)
                let ret_val: BasicValueEnum<'ctx> =
                    if ret_type == Some(self.type_mapper().ptr_type().into()) {
                        self.value_to_ptr(val)?.into()
                    } else {
                        val
                    };
                self.builder().build_return(Some(&ret_val)).map_err(|e| {
                    CodegenError::Internal(format!("failed to build return: {:?}", e))
                })?;
            } else {
                // Function expects a return value but body produced none
                // This shouldn't happen with correct type checking
                self.builder().build_return(None).map_err(|e| {
                    CodegenError::Internal(format!("failed to build return: {:?}", e))
                })?;
            }
        }

        Ok(())
    }

    /// Lower a Core expression to LLVM IR.
    fn lower_expr(&mut self, expr: &Expr) -> CodegenResult<Option<BasicValueEnum<'ctx>>> {
        match expr {
            Expr::Lit(lit, ty, _span) => self.lower_literal(lit, ty).map(Some),

            Expr::Var(var, _span) => {
                let name = var.name.as_str();

                // First, check if this is a nullary constructor (like [], True, False, Nothing)
                if let Some((tag, arity)) = self.constructor_info(name) {
                    if arity == 0 {
                        // Nullary constructor - allocate ADT value with just a tag
                        return self.lower_constructor_application(tag, 0, &[]);
                    }
                    // Non-nullary constructor referenced without args - return as closure/function
                    // This case is handled below when the constructor is used in an application
                }

                // Look up the variable in the environment
                if let Some(val) = self.env.get(&var.id) {
                    // E.44: Force thunks on variable lookup.
                    // Only force if this variable was thunked (tracked in thunked_vars set).
                    if self.thunked_vars.contains(&var.id) {
                        let forced = self.build_force(*val)?;
                        Ok(Some(forced))
                    } else {
                        Ok(Some(*val))
                    }
                } else if let Some(fn_val) = self.functions.get(&var.id) {
                    // It's a function reference
                    // Check if it's a CAF (only env pointer, no real arguments)
                    // All functions have at least 1 param (env pointer) due to uniform calling convention
                    let fn_type = fn_val.get_type();
                    if fn_type.count_param_types() <= 1 {
                        // CAF - call the function with null env to get its value
                        let null_env = self.type_mapper().ptr_type().const_null();
                        let call_result = self
                            .builder()
                            .build_call(*fn_val, &[null_env.into()], "caf_result")
                            .map_err(|e| {
                                CodegenError::Internal(format!("failed to call CAF: {:?}", e))
                            })?;
                        // Get the return value
                        if let Some(ret_val) = call_result.try_as_basic_value().basic() {
                            Ok(Some(ret_val))
                        } else {
                            // Void function - shouldn't happen for CAFs
                            Ok(None)
                        }
                    } else {
                        // Function with parameters - wrap in closure for uniform calling convention
                        let fn_ptr = fn_val.as_global_value().as_pointer_value();
                        let closure_ptr = self.alloc_closure(fn_ptr, &[])?;
                        Ok(Some(closure_ptr.into()))
                    }
                } else if let Some((primop, arity)) = self.primitive_op_info(name) {
                    // Primop used as a value - create a wrapper closure
                    self.create_primop_closure(primop, arity, name)
                } else if let Some(arity) = self.builtin_info(name) {
                    // Builtin used as a value - create a wrapper closure
                    self.create_builtin_closure(name, arity)
                } else if let Some(&fn_val) = self.external_functions.get(&var.name) {
                    // Cross-module imported function
                    // Check if it's a CAF (only env pointer, no real arguments)
                    let fn_type = fn_val.get_type();
                    if fn_type.count_param_types() <= 1 {
                        // CAF - call the function with null env to get its value
                        let null_env = self.type_mapper().ptr_type().const_null();
                        let call_result = self
                            .builder()
                            .build_call(fn_val, &[null_env.into()], "ext_caf_result")
                            .map_err(|e| {
                                CodegenError::Internal(format!(
                                    "failed to call external CAF: {:?}",
                                    e
                                ))
                            })?;
                        if let Some(ret_val) = call_result.try_as_basic_value().basic() {
                            Ok(Some(ret_val))
                        } else {
                            Ok(None)
                        }
                    } else {
                        // Function with parameters - wrap in closure
                        let fn_ptr = fn_val.as_global_value().as_pointer_value();
                        let closure_ptr = self.alloc_closure(fn_ptr, &[])?;
                        Ok(Some(closure_ptr.into()))
                    }
                } else if let Some((_tag, arity)) = self.constructor_info(name) {
                    // Constructor used as a first-class value — create a closure wrapper
                    self.create_constructor_closure(name, arity)
                } else {
                    // Unknown variable — generate a runtime stub.
                    // This handles StubValue defs from external packages that passed
                    // type checking but have no implementation.
                    let stub_fn = self.get_or_create_stub_function(name)?;
                    let fn_ptr = stub_fn.as_global_value().as_pointer_value();
                    let closure_ptr = self.alloc_closure(fn_ptr, &[])?;
                    Ok(Some(closure_ptr.into()))
                }
            }

            Expr::App(func, arg, _span) => self.lower_application(func, arg),

            Expr::Lam(param, body, _span) => {
                // Create a closure for this lambda
                self.lower_lambda(param, body)
            }

            Expr::Let(bind, body, _span) => self.lower_let(bind, body),

            Expr::Case(scrut, alts, ty, _span) => self.lower_case(scrut, alts, ty),

            Expr::TyApp(expr, _ty, _span) => {
                // Type applications are erased at runtime
                self.lower_expr(expr)
            }

            Expr::TyLam(_tyvar, body, _span) => {
                // Type lambdas are erased at runtime
                self.lower_expr(body)
            }

            Expr::Lazy(inner, _span) => {
                // Create a thunk for lazy evaluation
                self.lower_lazy(inner)
            }

            Expr::Cast(inner, _coercion, _span) => {
                // Coercions are erased at runtime
                self.lower_expr(inner)
            }

            Expr::Tick(_tick, inner, _span) => {
                // Ticks are for profiling, skip for now
                self.lower_expr(inner)
            }

            Expr::Type(_ty, _span) => {
                // Type expressions have no runtime value
                Ok(None)
            }

            Expr::Coercion(_coercion, _span) => {
                // Coercion values have no runtime representation
                Ok(None)
            }
        }
    }

    /// Lower a literal to LLVM IR.
    fn lower_literal(&self, lit: &Literal, ty: &Ty) -> CodegenResult<BasicValueEnum<'ctx>> {
        let tm = self.type_mapper();
        match lit {
            Literal::Int(n) => Ok(tm.i64_type().const_int(*n as u64, true).into()),

            Literal::Integer(n) => {
                // Arbitrary-precision Integer: call bhc_integer_from_i64 for values that fit in i64,
                // or bhc_integer_from_str for larger values
                if *n >= i64::MIN as i128 && *n <= i64::MAX as i128 {
                    let from_i64 = self.functions.get(&VarId::new(1000600)).ok_or_else(|| {
                        CodegenError::Internal("bhc_integer_from_i64 not declared".to_string())
                    })?;
                    let i64_val = tm.i64_type().const_int(*n as u64, true);
                    let result = self
                        .builder()
                        .build_call(*from_i64, &[i64_val.into()], "integer_from_i64")
                        .map_err(|e| {
                            CodegenError::Internal(format!("integer_from_i64 call failed: {:?}", e))
                        })?
                        .try_as_basic_value()
                        .basic()
                        .ok_or_else(|| {
                            CodegenError::Internal("integer_from_i64 returned void".to_string())
                        })?;
                    Ok(result)
                } else {
                    // Large integer: pass as string
                    let s = n.to_string();
                    let global = self
                        .builder()
                        .build_global_string_ptr(&s, "integer_str")
                        .map_err(|e| {
                            CodegenError::Internal(format!(
                                "failed to create integer string: {:?}",
                                e
                            ))
                        })?;
                    let from_str = self.functions.get(&VarId::new(1000601)).ok_or_else(|| {
                        CodegenError::Internal("bhc_integer_from_str not declared".to_string())
                    })?;
                    let result = self
                        .builder()
                        .build_call(
                            *from_str,
                            &[global.as_pointer_value().into()],
                            "integer_from_str",
                        )
                        .map_err(|e| {
                            CodegenError::Internal(format!("integer_from_str call failed: {:?}", e))
                        })?
                        .try_as_basic_value()
                        .basic()
                        .ok_or_else(|| {
                            CodegenError::Internal("integer_from_str returned void".to_string())
                        })?;
                    Ok(result)
                }
            }

            Literal::Float(f) => {
                // A fractional literal is 32-bit only when its type is
                // explicitly `Float`; otherwise (a `Double` annotation — the
                // Haskell default for fractional literals — or an erased type)
                // it is f64. Emitting f32 for a Double literal corrupts every
                // downstream f64 path (math RTS calls, boxing).
                let is_float32 = matches!(ty, Ty::Con(c) if c.name.as_str() == "Float");
                if is_float32 {
                    Ok(tm.f32_type().const_float(*f as f64).into())
                } else {
                    Ok(tm.f64_type().const_float(*f as f64).into())
                }
            }

            Literal::Double(d) => Ok(tm.f64_type().const_float(*d).into()),

            Literal::Char(c) => Ok(tm.i64_type().const_int(*c as u64, false).into()),

            Literal::String(sym) => {
                // Build a linked list of characters (proper Haskell [Char] representation).
                // This allows pattern matching on strings as character lists.
                let s = sym.as_str();
                let chars: Vec<char> = s.chars().collect();
                let mut current = self.build_nil()?;
                // Build list in reverse so that head of list = first char
                for &ch in chars.iter().rev() {
                    let char_val = tm.i64_type().const_int(ch as u64, false);
                    current = self.build_cons(char_val.into(), current.into())?;
                }
                Ok(current.into())
            }
        }
    }

    /// Lower a lambda expression to a closure.
    ///
    /// Creates a lifted function and allocates a closure struct containing
    /// the function pointer and captured variables.
    fn lower_lambda(
        &mut self,
        param: &Var,
        body: &Expr,
    ) -> CodegenResult<Option<BasicValueEnum<'ctx>>> {
        // Collect all parameters (for multi-argument lambdas)
        let mut params = vec![param.clone()];
        let mut current_body = body;

        while let Expr::Lam(next_param, next_body, _) = current_body {
            params.push(next_param.clone());
            current_body = next_body;
        }

        // Compute free variables in the lambda body
        let full_lambda = Expr::Lam(param.clone(), Box::new(body.clone()), body.span());
        let free = self.free_vars(&full_lambda);

        // Collect the values of free variables from current environment
        let mut captured: Vec<(VarId, BasicValueEnum<'ctx>)> = Vec::new();
        for var_id in &free {
            if let Some(val) = self.env.get(var_id) {
                captured.push((*var_id, *val));
            }
        }

        // Save current insertion point
        let current_block = self.builder().get_insert_block();

        // Generate unique name for the lifted function
        let fn_name = self.next_closure_name();

        // Build the function type:
        // - First param is the closure/env pointer
        // - Remaining params are the lambda parameters
        let tm = self.type_mapper();
        let ptr_type = tm.ptr_type();

        let mut param_types: Vec<inkwell::types::BasicMetadataTypeEnum<'ctx>> = Vec::new();
        param_types.push(ptr_type.into()); // env/closure pointer

        for _ in &params {
            // For now, use ptr for all params (polymorphic)
            param_types.push(ptr_type.into());
        }

        // Return type - use ptr for now (polymorphic)
        let fn_type = ptr_type.fn_type(&param_types, false);

        // Create the lifted function
        let lifted_fn = self.module.add_function(&fn_name, fn_type);

        // Create entry block for the lifted function
        let entry = self.llvm_context().append_basic_block(lifted_fn, "entry");
        self.builder().position_at_end(entry);

        // Save old environment and create new scope
        let old_env = std::mem::take(&mut self.env);

        // Bind captured variables from environment
        // The closure pointer is the first argument
        if !captured.is_empty() {
            let closure_ptr = lifted_fn
                .get_first_param()
                .ok_or_else(|| CodegenError::Internal("missing closure param".to_string()))?
                .into_pointer_value();

            for (i, (var_id, _)) in captured.iter().enumerate() {
                let elem_ptr =
                    self.extract_closure_env_elem(closure_ptr, captured.len() as u32, i as u32)?;
                // Store as pointer - will be unboxed when used
                self.env.insert(*var_id, elem_ptr.into());
            }
        }

        // Bind lambda parameters to function arguments
        // Skip first arg (closure pointer)
        for (i, lam_param) in params.iter().enumerate() {
            if let Some(arg) = lifted_fn.get_nth_param((i + 1) as u32) {
                self.env.insert(lam_param.id, arg);
            }
        }

        // Lower the body in tail position (enables tail call optimization)
        let was_tail = self.in_tail_position;
        self.in_tail_position = true;
        let result = self.lower_expr(current_body)?;
        self.in_tail_position = was_tail;

        // Build return
        if let Some(val) = result {
            // Convert to pointer for return
            let ret_ptr = self.value_to_ptr(val)?;
            self.builder()
                .build_return(Some(&ret_ptr))
                .map_err(|e| CodegenError::Internal(format!("failed to build return: {:?}", e)))?;
        } else {
            // Return null pointer for unit/void
            let null = ptr_type.const_null();
            self.builder()
                .build_return(Some(&null))
                .map_err(|e| CodegenError::Internal(format!("failed to build return: {:?}", e)))?;
        }

        // Restore old environment
        self.env = old_env;

        // Restore insertion point
        if let Some(block) = current_block {
            self.builder().position_at_end(block);
        }

        // Allocate closure with function pointer and captured values
        let fn_ptr = lifted_fn.as_global_value().as_pointer_value();
        let closure_ptr = self.alloc_closure(fn_ptr, &captured)?;

        Ok(Some(closure_ptr.into()))
    }

    /// Lower a lazy expression to a thunk.
    ///
    /// Creates a thunk that suspends the evaluation of the inner expression.
    /// The thunk is evaluated when forced (via bhc_force).
    fn lower_lazy(&mut self, inner: &Expr) -> CodegenResult<Option<BasicValueEnum<'ctx>>> {
        // Compute free variables in the lazy expression
        let free = self.free_vars(inner);

        // Collect the values of free variables from current environment
        let mut captured: Vec<(VarId, BasicValueEnum<'ctx>)> = Vec::new();
        for var_id in &free {
            if let Some(val) = self.env.get(var_id) {
                captured.push((*var_id, *val));
            }
        }

        // Save current insertion point
        let current_block = self.builder().get_insert_block();

        // Generate unique name for the thunk evaluation function
        let fn_name = self.next_thunk_name();

        // Build the function type for the eval function:
        // fn(env: *mut u8) -> *mut u8
        // The env pointer points to the captured variables array
        // Extract types early to avoid borrow conflicts with self.env
        let ptr_type = self.type_mapper().ptr_type();
        let i64_type = self.type_mapper().i64_type();
        let env_array_type = ptr_type.array_type(captured.len() as u32);

        let fn_type = ptr_type.fn_type(&[ptr_type.into()], false);

        // Create the thunk evaluation function
        let eval_fn = self.module.add_function(&fn_name, fn_type);

        // Create entry block for the eval function
        let entry = self.llvm_context().append_basic_block(eval_fn, "entry");
        self.builder().position_at_end(entry);

        // Save old environment and create new scope
        let old_env = std::mem::take(&mut self.env);

        // Bind captured variables from environment parameter
        if !captured.is_empty() {
            let env_ptr = eval_fn
                .get_first_param()
                .ok_or_else(|| CodegenError::Internal("missing env param".to_string()))?
                .into_pointer_value();

            for (i, (var_id, _)) in captured.iter().enumerate() {
                // Load variable from env array
                let elem_ptr = unsafe {
                    self.builder()
                        .build_in_bounds_gep(
                            env_array_type,
                            env_ptr,
                            &[i64_type.const_zero(), i64_type.const_int(i as u64, false)],
                            &format!("thunk_env_load_{}", i),
                        )
                        .map_err(|e| {
                            CodegenError::Internal(format!("failed to get env elem ptr: {:?}", e))
                        })?
                };

                let elem_val = self
                    .builder()
                    .build_load(ptr_type, elem_ptr, &format!("thunk_env_val_{}", i))
                    .map_err(|e| {
                        CodegenError::Internal(format!("failed to load env elem: {:?}", e))
                    })?;

                self.env.insert(*var_id, elem_val);
            }
        }

        // Lower the inner expression (this produces the actual computation)
        let result = self.lower_expr(inner)?;

        // Build return — but only if the current block doesn't already have a terminator.
        // Expressions like `error "..."` generate an `unreachable` terminator.
        let current_bb = self.builder().get_insert_block();
        let has_terminator = current_bb
            .map(|bb| bb.get_terminator().is_some())
            .unwrap_or(false);

        if !has_terminator {
            if let Some(val) = result {
                let ret_ptr = self.value_to_ptr(val)?;
                self.builder().build_return(Some(&ret_ptr)).map_err(|e| {
                    CodegenError::Internal(format!("failed to build return: {:?}", e))
                })?;
            } else {
                let null = ptr_type.const_null();
                self.builder().build_return(Some(&null)).map_err(|e| {
                    CodegenError::Internal(format!("failed to build return: {:?}", e))
                })?;
            }
        }

        // Restore old environment
        self.env = old_env;

        // Restore insertion point
        if let Some(block) = current_block {
            self.builder().position_at_end(block);
        }

        // Allocate thunk with eval function pointer and captured values
        let fn_ptr = eval_fn.as_global_value().as_pointer_value();
        let thunk_ptr = self.alloc_thunk(fn_ptr, &captured)?;

        Ok(Some(thunk_ptr.into()))
    }

    // ========================================================================
    // Primitive Operations
    // ========================================================================

    /// Check if a name is a primitive operation and return its arity.
    fn primitive_op_info(&self, name: &str) -> Option<(PrimOp, u32)> {
        match name {
            // Arithmetic (binary)
            "+" | "GHC.Num.+" => Some((PrimOp::Add, 2)),
            "-" | "GHC.Num.-" => Some((PrimOp::Sub, 2)),
            "*" | "GHC.Num.*" => Some((PrimOp::Mul, 2)),
            "/" | "GHC.Real./" => Some((PrimOp::Div, 2)),
            "div" | "GHC.Real.div" => Some((PrimOp::Div, 2)),
            "mod" | "GHC.Real.mod" => Some((PrimOp::Mod, 2)),
            "rem" | "GHC.Real.rem" => Some((PrimOp::Rem, 2)),
            "quot" | "GHC.Real.quot" => Some((PrimOp::Quot, 2)),

            // Comparison (binary)
            "==" | "GHC.Classes.==" => Some((PrimOp::Eq, 2)),
            "/=" | "GHC.Classes./=" => Some((PrimOp::Ne, 2)),
            "<" | "GHC.Classes.<" => Some((PrimOp::Lt, 2)),
            "<=" | "GHC.Classes.<=" => Some((PrimOp::Le, 2)),
            ">" | "GHC.Classes.>" => Some((PrimOp::Gt, 2)),
            ">=" | "GHC.Classes.>=" => Some((PrimOp::Ge, 2)),

            // Boolean (binary)
            "&&" | "GHC.Classes.&&" => Some((PrimOp::And, 2)),
            "||" | "GHC.Classes.||" => Some((PrimOp::Or, 2)),

            // Unary
            "negate" | "GHC.Num.negate" => Some((PrimOp::Negate, 1)),
            "abs" | "GHC.Num.abs" => Some((PrimOp::Abs, 1)),
            "signum" | "GHC.Num.signum" => Some((PrimOp::Signum, 1)),
            "not" | "GHC.Classes.not" => Some((PrimOp::Not, 1)),

            // Bitwise (binary)
            ".&." => Some((PrimOp::BitAnd, 2)),
            ".|." => Some((PrimOp::BitOr, 2)),
            "xor" => Some((PrimOp::BitXor, 2)),
            "shiftL" => Some((PrimOp::ShiftL, 2)),
            "shiftR" => Some((PrimOp::ShiftR, 2)),

            // Bitwise (unary)
            "complement" => Some((PrimOp::Complement, 1)),

            _ => None,
        }
    }

    /// Create a closure wrapping a primitive operation.
    /// This is used when a primop like (+) is used as a first-class value.
    fn create_primop_closure(
        &mut self,
        primop: PrimOp,
        arity: u32,
        name: &str,
    ) -> CodegenResult<Option<BasicValueEnum<'ctx>>> {
        let tm = self.type_mapper();
        let ptr_type = tm.ptr_type();
        let _i64_type = tm.i64_type();

        // Create a unique name for the wrapper function
        let wrapper_name = format!(
            "primop_wrapper_{}",
            name.replace(|c: char| !c.is_alphanumeric(), "_")
        );

        // Check if wrapper already exists
        let wrapper_fn = if let Some(existing) =
            self.module.llvm_module().get_function(&wrapper_name)
        {
            existing
        } else {
            // Create wrapper function: (ptr env, ptr arg1, [ptr arg2]) -> ptr
            let mut param_types: Vec<inkwell::types::BasicMetadataTypeEnum<'ctx>> = Vec::new();
            param_types.push(ptr_type.into()); // env/closure pointer
            for _ in 0..arity {
                param_types.push(ptr_type.into());
            }

            let wrapper_fn_type = ptr_type.fn_type(&param_types, false);
            let wrapper_fn = self.module.llvm_module().add_function(
                &wrapper_name,
                wrapper_fn_type,
                Some(inkwell::module::Linkage::Internal),
            );

            // Build the wrapper function body
            let entry_bb = self.llvm_ctx.append_basic_block(wrapper_fn, "entry");
            let current_bb = self.builder().get_insert_block();

            self.builder().position_at_end(entry_bb);

            // Load arguments (skip env at index 0)
            let args: Vec<BasicValueEnum<'ctx>> = (1..=arity)
                .map(|i| wrapper_fn.get_nth_param(i).unwrap())
                .collect();

            // Perform the primop
            let result = self.lower_primop_direct(primop, &args)?;

            // Box the result and return
            let result_ptr = self.value_to_ptr(result)?;
            self.builder()
                .build_return(Some(&result_ptr))
                .map_err(|e| CodegenError::Internal(format!("failed to build return: {:?}", e)))?;

            // Restore insertion point
            if let Some(bb) = current_bb {
                self.builder().position_at_end(bb);
            }

            wrapper_fn
        };

        // Create a closure wrapping the primop function
        let fn_ptr = wrapper_fn.as_global_value().as_pointer_value();
        let closure_ptr = self.alloc_closure(fn_ptr, &[])?;
        Ok(Some(closure_ptr.into()))
    }

    /// Create a closure wrapping a builtin operation.
    /// This is used when a builtin like (>>) is used as a first-class value.
    /// Generate (or return cached) a stub function for an unimplemented external function.
    ///
    /// The stub has signature `(ptr) -> ptr` and calls `bhc_error` with a diagnostic
    /// message, then hits unreachable. This allows compilation to succeed for code
    /// paths that reference stub functions, deferring the error to runtime.
    fn get_or_create_stub_function(&mut self, name: &str) -> CodegenResult<FunctionValue<'ctx>> {
        if let Some(fn_val) = self.stub_functions.get(name) {
            return Ok(*fn_val);
        }

        let ptr_type = self.type_mapper().ptr_type();

        // Sanitize name for LLVM symbol
        let stub_fn_name = format!(
            "bhc_stub_{}",
            name.replace(|c: char| !c.is_alphanumeric() && c != '_', "_")
        );

        let fn_type = ptr_type.fn_type(&[ptr_type.into()], false);
        let stub_fn = self.module.llvm_module().add_function(
            &stub_fn_name,
            fn_type,
            Some(inkwell::module::Linkage::Internal),
        );

        // Build the function body: call bhc_error then unreachable
        let entry_bb = self.llvm_ctx.append_basic_block(stub_fn, "entry");
        let saved_bb = self.builder().get_insert_block();

        self.builder().position_at_end(entry_bb);

        let error_msg = format!("stub: {} not implemented", name);
        let msg_global = self.module.add_global_string(&stub_fn_name, &error_msg);

        let error_fn = self
            .functions
            .get(&VarId::new(1000006))
            .copied()
            .ok_or_else(|| CodegenError::Internal("bhc_error not declared".to_string()))?;

        self.builder()
            .build_call(error_fn, &[msg_global.into()], "")
            .map_err(|e| CodegenError::Internal(format!("stub call error: {:?}", e)))?;
        self.builder()
            .build_unreachable()
            .map_err(|e| CodegenError::Internal(format!("stub unreachable: {:?}", e)))?;

        // Restore insertion point
        if let Some(bb) = saved_bb {
            self.builder().position_at_end(bb);
        }

        self.stub_functions.insert(name.to_string(), stub_fn);
        Ok(stub_fn)
    }

    fn create_builtin_closure(
        &mut self,
        name: &str,
        arity: u32,
    ) -> CodegenResult<Option<BasicValueEnum<'ctx>>> {
        // For arity-0 builtins, directly evaluate without wrapping in another closure.
        // This is important for builtins like `ask` and `get` that return closures:
        // wrapping them would create a double-closure that breaks runReaderT/runStateT.
        if arity == 0 {
            return self.lower_builtin_direct(name, &[]);
        }

        let tm = self.type_mapper();
        let ptr_type = tm.ptr_type();

        // Create a unique name for the wrapper function
        let wrapper_name = format!(
            "builtin_wrapper_{}",
            name.replace(|c: char| !c.is_alphanumeric(), "_")
        );

        // Check if wrapper already exists
        let wrapper_fn = if let Some(existing) =
            self.module.llvm_module().get_function(&wrapper_name)
        {
            existing
        } else {
            // Create wrapper function: (ptr env, ptr arg1, ptr arg2, ...) -> ptr
            let mut param_types: Vec<inkwell::types::BasicMetadataTypeEnum<'ctx>> = Vec::new();
            param_types.push(ptr_type.into()); // env/closure pointer
            for _ in 0..arity {
                param_types.push(ptr_type.into());
            }

            let wrapper_fn_type = ptr_type.fn_type(&param_types, false);
            let wrapper_fn = self.module.llvm_module().add_function(
                &wrapper_name,
                wrapper_fn_type,
                Some(inkwell::module::Linkage::Internal),
            );

            // Build the wrapper function body
            let entry_bb = self.llvm_ctx.append_basic_block(wrapper_fn, "entry");
            let current_bb = self.builder().get_insert_block();

            self.builder().position_at_end(entry_bb);

            // Load arguments (skip env at index 0)
            let args: Vec<BasicValueEnum<'ctx>> = (1..=arity)
                .map(|i| wrapper_fn.get_nth_param(i).unwrap())
                .collect();

            // Perform the builtin operation
            let result = self.lower_builtin_direct(name, &args)?;

            // Return the result — convert non-pointer values if needed
            let result_ptr = match result {
                Some(v) => {
                    if v.is_pointer_value() {
                        v.into_pointer_value()
                    } else if v.is_int_value() {
                        self.int_to_ptr(v.into_int_value())?
                    } else {
                        // Float or other — box it
                        let ptr_val = self.value_to_ptr(v)?;
                        ptr_val
                    }
                }
                None => ptr_type.const_null(), // Unit/void result
            };
            self.builder()
                .build_return(Some(&result_ptr))
                .map_err(|e| CodegenError::Internal(format!("failed to build return: {:?}", e)))?;

            // Restore insertion point
            if let Some(bb) = current_bb {
                self.builder().position_at_end(bb);
            }

            wrapper_fn
        };

        // Create a closure wrapping the builtin function
        let fn_ptr = wrapper_fn.as_global_value().as_pointer_value();
        let closure_ptr = self.alloc_closure(fn_ptr, &[])?;
        Ok(Some(closure_ptr.into()))
    }

    /// Create a closure wrapping a data constructor used as a first-class value.
    /// E.g., `(:)` passed to `foldr` becomes a closure that allocates a cons cell.
    fn create_constructor_closure(
        &mut self,
        name: &str,
        arity: u32,
    ) -> CodegenResult<Option<BasicValueEnum<'ctx>>> {
        if arity == 0 {
            // Zero-arity constructor: just allocate the ADT struct directly
            let (tag, _) = self
                .constructor_info(name)
                .ok_or_else(|| CodegenError::Internal(format!("unknown constructor: {}", name)))?;
            let adt_ptr = self.alloc_adt(tag, 0)?;
            return Ok(Some(adt_ptr.into()));
        }

        let tm = self.type_mapper();
        let ptr_type = tm.ptr_type();

        let wrapper_name = format!(
            "constructor_wrapper_{}",
            name.replace(|c: char| !c.is_alphanumeric(), "_")
        );

        let wrapper_fn =
            if let Some(existing) = self.module.llvm_module().get_function(&wrapper_name) {
                existing
            } else {
                let (tag, _) = self.constructor_info(name).ok_or_else(|| {
                    CodegenError::Internal(format!("unknown constructor: {}", name))
                })?;

                // Create wrapper function: (ptr env, ptr arg1, ..., ptr argN) -> ptr
                let mut param_types: Vec<inkwell::types::BasicMetadataTypeEnum<'ctx>> = Vec::new();
                param_types.push(ptr_type.into()); // env/closure pointer
                for _ in 0..arity {
                    param_types.push(ptr_type.into());
                }

                let wrapper_fn_type = ptr_type.fn_type(&param_types, false);
                let wrapper_fn = self.module.llvm_module().add_function(
                    &wrapper_name,
                    wrapper_fn_type,
                    Some(inkwell::module::Linkage::Internal),
                );

                let entry_bb = self.llvm_ctx.append_basic_block(wrapper_fn, "entry");
                let current_bb = self.builder().get_insert_block();

                self.builder().position_at_end(entry_bb);

                // For cons (:), use build_cons for correct list layout
                let result_ptr = if name == ":" && arity == 2 {
                    let head = wrapper_fn.get_nth_param(1).unwrap();
                    let tail = wrapper_fn.get_nth_param(2).unwrap();
                    self.build_cons(head, tail)?
                } else {
                    // General constructor: allocate ADT with tag and fields
                    let adt_ptr = self.alloc_adt(tag, arity)?;
                    for i in 0..arity {
                        let arg = wrapper_fn.get_nth_param(i + 1).unwrap();
                        self.store_adt_field(adt_ptr, arity, i, arg)?;
                    }
                    adt_ptr
                };

                self.builder()
                    .build_return(Some(&result_ptr))
                    .map_err(|e| {
                        CodegenError::Internal(format!("constructor wrapper return: {:?}", e))
                    })?;

                if let Some(bb) = current_bb {
                    self.builder().position_at_end(bb);
                }

                wrapper_fn
            };

        let fn_ptr = wrapper_fn.as_global_value().as_pointer_value();
        let closure_ptr = self.alloc_closure(fn_ptr, &[])?;
        Ok(Some(closure_ptr.into()))
    }

    /// Create a partially-applied builtin closure.
    /// Given a builtin with arity N and M provided args (M < N), creates a
    /// closure that captures the M args and accepts the remaining (N-M) args.
    fn create_partial_builtin_closure(
        &mut self,
        name: &str,
        arity: u32,
        provided_args: &[&Expr],
    ) -> CodegenResult<Option<BasicValueEnum<'ctx>>> {
        let tm = self.type_mapper();
        let ptr_type = tm.ptr_type();
        let n_provided = provided_args.len() as u32;
        let n_remaining = arity - n_provided;

        // Create a unique name for the partial wrapper
        let wrapper_name = format!(
            "builtin_partial_{}_{}of{}",
            name.replace(|c: char| !c.is_alphanumeric(), "_"),
            n_provided,
            arity
        );

        // Check if wrapper already exists
        let wrapper_fn = if let Some(existing) =
            self.module.llvm_module().get_function(&wrapper_name)
        {
            existing
        } else {
            // Create wrapper: (ptr env, ptr remaining_arg1, ...) -> ptr
            // The env closure has the captured args stored in its environment
            let mut param_types: Vec<inkwell::types::BasicMetadataTypeEnum<'ctx>> = Vec::new();
            param_types.push(ptr_type.into()); // env/closure pointer
            for _ in 0..n_remaining {
                param_types.push(ptr_type.into());
            }

            let wrapper_fn_type = ptr_type.fn_type(&param_types, false);
            let wrapper_fn = self.module.llvm_module().add_function(
                &wrapper_name,
                wrapper_fn_type,
                Some(inkwell::module::Linkage::Internal),
            );

            // Build the wrapper function body
            let entry_bb = self.llvm_ctx.append_basic_block(wrapper_fn, "entry");
            let current_bb = self.builder().get_insert_block();

            self.builder().position_at_end(entry_bb);

            // Extract captured args from closure environment
            let closure_param = wrapper_fn.get_nth_param(0).unwrap().into_pointer_value();
            let closure_ty = self.closure_type(n_provided);
            let env_slot = self
                .builder()
                .build_struct_gep(closure_ty, closure_param, 2, "env_slot")
                .map_err(|e| CodegenError::Internal(format!("partial env slot: {:?}", e)))?;

            let mut all_args: Vec<BasicValueEnum<'ctx>> = Vec::new();

            // Load captured args from environment
            for i in 0..n_provided {
                let elem_ptr = unsafe {
                    self.builder()
                        .build_in_bounds_gep(
                            ptr_type.array_type(n_provided),
                            env_slot,
                            &[
                                tm.i64_type().const_zero(),
                                tm.i64_type().const_int(i as u64, false),
                            ],
                            &format!("cap_{}", i),
                        )
                        .map_err(|e| CodegenError::Internal(format!("partial cap gep: {:?}", e)))?
                };
                let captured_val = self
                    .builder()
                    .build_load(ptr_type, elem_ptr, &format!("captured_{}", i))
                    .map_err(|e| CodegenError::Internal(format!("partial cap load: {:?}", e)))?;
                all_args.push(captured_val);
            }

            // Add remaining args from function parameters
            for i in 0..n_remaining {
                all_args.push(wrapper_fn.get_nth_param(1 + i).unwrap());
            }

            // Call lower_builtin_direct with all args
            let result = self.lower_builtin_direct(name, &all_args)?;

            let result_ptr = match result {
                Some(v) => v.into_pointer_value(),
                None => ptr_type.const_null(),
            };
            self.builder()
                .build_return(Some(&result_ptr))
                .map_err(|e| CodegenError::Internal(format!("partial return: {:?}", e)))?;

            // Restore insertion point
            if let Some(bb) = current_bb {
                self.builder().position_at_end(bb);
            }

            wrapper_fn
        };

        // Lower the provided args
        let was_tail = self.in_tail_position;
        self.in_tail_position = false;

        let mut captured_vals: Vec<(VarId, BasicValueEnum<'ctx>)> = Vec::new();
        for (i, arg_expr) in provided_args.iter().enumerate() {
            let val = self
                .lower_expr(arg_expr)?
                .ok_or_else(|| CodegenError::Internal("partial arg has no value".to_string()))?;
            let ptr_val = self.value_to_ptr(val)?;
            captured_vals.push((VarId::new(900000 + i), ptr_val.into()));
        }

        self.in_tail_position = was_tail;

        // Create closure with captured args
        let fn_ptr = wrapper_fn.as_global_value().as_pointer_value();
        let closure_ptr = self.alloc_closure(fn_ptr, &captured_vals)?;
        Ok(Some(closure_ptr.into()))
    }

    /// Execute a builtin operation directly on LLVM values (already lowered).
    /// This is for when builtins are used as values and receive runtime arguments.
    fn lower_builtin_direct(
        &mut self,
        name: &str,
        args: &[BasicValueEnum<'ctx>],
    ) -> CodegenResult<Option<BasicValueEnum<'ctx>>> {
        let ptr_type = self.type_mapper().ptr_type();

        match name {
            ">>" => {
                // (>>) :: m a -> m b -> m b
                // Execute first action (arg1), ignore result, return second (arg2)
                // For our simple model, arg1 and arg2 are thunks/values
                // We just evaluate both and return the second
                // Since args are already evaluated values, we just return arg2
                // Note: If these were actual IO thunks, we'd need to force them
                Ok(Some(args[1]))
            }
            ">>=" => {
                // (>>=) :: m a -> (a -> m b) -> m b
                // Execute first action, pass result to function, return result of function
                let action_result = args[0]; // Result of first action
                let func = args[1].into_pointer_value(); // Function closure

                // Call the function with the action result
                let fn_ptr = self.extract_closure_fn_ptr(func)?;
                let fn_type = ptr_type.fn_type(&[ptr_type.into(), ptr_type.into()], false);
                let result = self
                    .builder()
                    .build_indirect_call(
                        fn_type,
                        fn_ptr,
                        &[func.into(), action_result.into()],
                        "bind_result",
                    )
                    .map_err(|e| {
                        CodegenError::Internal(format!("failed to call bind function: {:?}", e))
                    })?
                    .try_as_basic_value()
                    .basic()
                    .ok_or_else(|| {
                        CodegenError::Internal(">>=: function returned void".to_string())
                    })?;
                Ok(Some(result))
            }
            "=<<" => {
                // (=<<) :: (a -> m b) -> m a -> m b (flipped >>=)
                let func = args[0].into_pointer_value();
                let action_result = args[1];

                let fn_ptr = self.extract_closure_fn_ptr(func)?;
                let fn_type = ptr_type.fn_type(&[ptr_type.into(), ptr_type.into()], false);
                let result = self
                    .builder()
                    .build_indirect_call(
                        fn_type,
                        fn_ptr,
                        &[func.into(), action_result.into()],
                        "bind_result",
                    )
                    .map_err(|e| {
                        CodegenError::Internal(format!("failed to call bind function: {:?}", e))
                    })?
                    .try_as_basic_value()
                    .basic()
                    .ok_or_else(|| {
                        CodegenError::Internal("=<<: function returned void".to_string())
                    })?;
                Ok(Some(result))
            }
            "return" | "pure" => {
                // return :: a -> m a
                // For our simple IO model, just return the value
                Ok(Some(args[0]))
            }
            "id" => {
                // id :: a -> a
                Ok(Some(args[0]))
            }
            "const" if args.len() >= 2 => {
                // const :: a -> b -> a
                Ok(Some(args[0]))
            }
            "flip" if args.len() >= 3 => {
                // flip :: (a -> b -> c) -> b -> a -> c
                let func = args[0].into_pointer_value();
                let fn_ptr = self.extract_closure_fn_ptr(func)?;
                let fn_type =
                    ptr_type.fn_type(&[ptr_type.into(), ptr_type.into(), ptr_type.into()], false);
                let result = self
                    .builder()
                    .build_indirect_call(
                        fn_type,
                        fn_ptr,
                        &[func.into(), args[2].into(), args[1].into()],
                        "flip_result",
                    )
                    .map_err(|e| CodegenError::Internal(format!("flip call failed: {:?}", e)))?
                    .try_as_basic_value()
                    .basic()
                    .ok_or_else(|| CodegenError::Internal("flip: returned void".to_string()))?;
                Ok(Some(result))
            }
            "seq" if args.len() >= 2 => {
                // seq :: a -> b -> b (evaluate first, return second)
                Ok(Some(args[1]))
            }
            "$" if args.len() >= 2 => {
                // ($) :: (a -> b) -> a -> b
                let func = args[0].into_pointer_value();
                let fn_ptr = self.extract_closure_fn_ptr(func)?;
                let fn_type = ptr_type.fn_type(&[ptr_type.into(), ptr_type.into()], false);
                let result = self
                    .builder()
                    .build_indirect_call(
                        fn_type,
                        fn_ptr,
                        &[func.into(), args[1].into()],
                        "dollar_result",
                    )
                    .map_err(|e| CodegenError::Internal(format!("$ call failed: {:?}", e)))?
                    .try_as_basic_value()
                    .basic()
                    .ok_or_else(|| CodegenError::Internal("$: returned void".to_string()))?;
                Ok(Some(result))
            }
            "." if args.len() >= 3 => {
                // (.) :: (b -> c) -> (a -> b) -> a -> c
                let f = args[0].into_pointer_value();
                let g = args[1].into_pointer_value();
                let x = args[2];

                // Apply g to x first
                let g_fn_ptr = self.extract_closure_fn_ptr(g)?;
                let fn_type = ptr_type.fn_type(&[ptr_type.into(), ptr_type.into()], false);
                let g_result = self
                    .builder()
                    .build_indirect_call(fn_type, g_fn_ptr, &[g.into(), x.into()], "compose_g")
                    .map_err(|e| CodegenError::Internal(format!("compose g call failed: {:?}", e)))?
                    .try_as_basic_value()
                    .basic()
                    .ok_or_else(|| {
                        CodegenError::Internal("compose g: returned void".to_string())
                    })?;

                // Apply f to (g x)
                let f_fn_ptr = self.extract_closure_fn_ptr(f)?;
                let f_result = self
                    .builder()
                    .build_indirect_call(
                        fn_type,
                        f_fn_ptr,
                        &[f.into(), g_result.into()],
                        "compose_f",
                    )
                    .map_err(|e| CodegenError::Internal(format!("compose f call failed: {:?}", e)))?
                    .try_as_basic_value()
                    .basic()
                    .ok_or_else(|| {
                        CodegenError::Internal("compose f: returned void".to_string())
                    })?;
                Ok(Some(f_result))
            }
            "fmap" | "<$>" => {
                // fmap :: (a -> b) -> f a -> f b
                // For IO: apply function to action result
                let func = args[0].into_pointer_value();
                let action_result = args[1];

                let fn_ptr = self.extract_closure_fn_ptr(func)?;
                let action_ptr = self.value_to_ptr(action_result)?;
                let fn_type = ptr_type.fn_type(&[ptr_type.into(), ptr_type.into()], false);
                let result = self
                    .builder()
                    .build_indirect_call(
                        fn_type,
                        fn_ptr,
                        &[func.into(), action_ptr.into()],
                        "fmap_result",
                    )
                    .map_err(|e| CodegenError::Internal(format!("fmap call failed: {:?}", e)))?
                    .try_as_basic_value()
                    .basic()
                    .ok_or_else(|| CodegenError::Internal("fmap: returned void".to_string()))?;
                Ok(Some(result))
            }
            "<*>" => {
                // (<*>) :: f (a -> b) -> f a -> f b
                // For IO: extract function from first action, apply to second
                let func_closure = args[0].into_pointer_value();
                let val = args[1];

                let fn_ptr = self.extract_closure_fn_ptr(func_closure)?;
                let val_ptr = self.value_to_ptr(val)?;
                let fn_type = ptr_type.fn_type(&[ptr_type.into(), ptr_type.into()], false);
                let result = self
                    .builder()
                    .build_indirect_call(
                        fn_type,
                        fn_ptr,
                        &[func_closure.into(), val_ptr.into()],
                        "ap_result",
                    )
                    .map_err(|e| CodegenError::Internal(format!("<*> call failed: {:?}", e)))?
                    .try_as_basic_value()
                    .basic()
                    .ok_or_else(|| CodegenError::Internal("<*>: returned void".to_string()))?;
                Ok(Some(result))
            }
            "putStrLn" => {
                // putStrLn :: String -> IO ()
                let str_ptr = args[0].into_pointer_value();
                let rts_fn = self.functions.get(&VarId::new(1000002)).ok_or_else(|| {
                    CodegenError::Internal("bhc_print_string_ln not declared".to_string())
                })?;
                self.builder()
                    .build_call(*rts_fn, &[str_ptr.into()], "")
                    .map_err(|e| {
                        CodegenError::Internal(format!("putStrLn call failed: {:?}", e))
                    })?;
                Ok(Some(ptr_type.const_null().into()))
            }
            "putStr" => {
                let str_ptr = args[0].into_pointer_value();
                let rts_fn = self.functions.get(&VarId::new(1000004)).ok_or_else(|| {
                    CodegenError::Internal("bhc_print_string not declared".to_string())
                })?;
                self.builder()
                    .build_call(*rts_fn, &[str_ptr.into()], "")
                    .map_err(|e| CodegenError::Internal(format!("putStr call failed: {:?}", e)))?;
                Ok(Some(ptr_type.const_null().into()))
            }
            // Identity operations (newtype = pass through)
            "Identity" | "runIdentity" | "Identity.pure" => Ok(Some(args[0])),
            "Identity.fmap" | "Identity.>>=" | "Identity.<*>" => {
                // Apply function to value
                let func = args[0].into_pointer_value();
                let val = args[1];
                let fn_ptr = self.extract_closure_fn_ptr(func)?;
                let val_ptr = self.value_to_ptr(val)?;
                let fn_type = ptr_type.fn_type(&[ptr_type.into(), ptr_type.into()], false);
                let result = self
                    .builder()
                    .build_indirect_call(
                        fn_type,
                        fn_ptr,
                        &[func.into(), val_ptr.into()],
                        "identity_call",
                    )
                    .map_err(|e| CodegenError::Internal(format!("Identity call failed: {:?}", e)))?
                    .try_as_basic_value()
                    .basic()
                    .ok_or_else(|| CodegenError::Internal("Identity: returned void".to_string()))?;
                Ok(Some(result))
            }
            "Identity.>>" => Ok(Some(args[1])),

            // ReaderT/StateT constructors (newtype = pass through)
            "ReaderT" | "StateT" => Ok(Some(args[0])),
            "runReaderT" | "runStateT" => {
                // Call the closure (arg0) with the environment/state (arg1)
                let closure = args[0].into_pointer_value();
                let env_val = args[1];
                let fn_ptr = self.extract_closure_fn_ptr(closure)?;
                let env_ptr = self.value_to_ptr(env_val)?;
                let fn_type = ptr_type.fn_type(&[ptr_type.into(), ptr_type.into()], false);
                let result = self
                    .builder()
                    .build_indirect_call(
                        fn_type,
                        fn_ptr,
                        &[closure.into(), env_ptr.into()],
                        "run_t_result",
                    )
                    .map_err(|e| CodegenError::Internal(format!("runT call failed: {:?}", e)))?
                    .try_as_basic_value()
                    .basic()
                    .ok_or_else(|| CodegenError::Internal("runT: returned void".to_string()))?;
                Ok(Some(result))
            }

            // ReaderT operations (zero-arity: ask returns closure \r -> r)
            "ask" => self.lower_builtin_ask(),

            // ReaderT operations with pre-lowered args
            "asks" => {
                // asks f = closure \r -> f(r)
                let f_val = args[0];
                let fn_name = "bhc_reader_t_asks";
                let func = self.get_or_create_transformer_fn(fn_name);
                if func.count_basic_blocks() == 0 {
                    let entry = self.llvm_ctx.append_basic_block(func, "entry");
                    let saved_bb = self.builder().get_insert_block();
                    self.builder().position_at_end(entry);
                    let env = func.get_nth_param(0).unwrap().into_pointer_value();
                    let r = func.get_nth_param(1).unwrap();
                    let f = self.extract_closure_env_elem(env, 1, 0)?;
                    let f_fn = self.extract_closure_fn_ptr(f)?;
                    let r_ptr = self.value_to_ptr(r)?;
                    let fn_type = ptr_type.fn_type(&[ptr_type.into(), ptr_type.into()], false);
                    let result = self
                        .builder()
                        .build_indirect_call(
                            fn_type,
                            f_fn,
                            &[f.into(), r_ptr.into()],
                            "asks_result",
                        )
                        .map_err(|e| CodegenError::Internal(format!("asks call: {:?}", e)))?
                        .try_as_basic_value()
                        .basic()
                        .ok_or_else(|| CodegenError::Internal("asks: void".to_string()))?;
                    self.builder()
                        .build_return(Some(&result))
                        .map_err(|e| CodegenError::Internal(format!("asks return: {:?}", e)))?;
                    if let Some(bb) = saved_bb {
                        self.builder().position_at_end(bb);
                    }
                }
                let fn_ptr = func.as_global_value().as_pointer_value();
                let f_ptr = self.value_to_ptr(f_val)?;
                let closure_ptr =
                    self.alloc_closure(fn_ptr, &[(VarId::new(900000), f_ptr.into())])?;
                Ok(Some(closure_ptr.into()))
            }
            "local" => {
                // local f m = closure \r -> m(f(r))
                let f_val = args[0];
                let m_val = args[1];
                let fn_name = "bhc_reader_t_local";
                let func = self.get_or_create_transformer_fn(fn_name);
                if func.count_basic_blocks() == 0 {
                    let entry = self.llvm_ctx.append_basic_block(func, "entry");
                    let saved_bb = self.builder().get_insert_block();
                    self.builder().position_at_end(entry);
                    let env = func.get_nth_param(0).unwrap().into_pointer_value();
                    let r = func.get_nth_param(1).unwrap();
                    let f = self.extract_closure_env_elem(env, 2, 0)?;
                    let m = self.extract_closure_env_elem(env, 2, 1)?;
                    let fn_type = ptr_type.fn_type(&[ptr_type.into(), ptr_type.into()], false);
                    let f_fn = self.extract_closure_fn_ptr(f)?;
                    let r_ptr = self.value_to_ptr(r)?;
                    let r_prime = self
                        .builder()
                        .build_indirect_call(fn_type, f_fn, &[f.into(), r_ptr.into()], "local_r")
                        .map_err(|e| CodegenError::Internal(format!("local f call: {:?}", e)))?
                        .try_as_basic_value()
                        .basic()
                        .ok_or_else(|| CodegenError::Internal("local f: void".to_string()))?;
                    let m_fn = self.extract_closure_fn_ptr(m)?;
                    let result = self
                        .builder()
                        .build_indirect_call(
                            fn_type,
                            m_fn,
                            &[m.into(), r_prime.into()],
                            "local_result",
                        )
                        .map_err(|e| CodegenError::Internal(format!("local m call: {:?}", e)))?
                        .try_as_basic_value()
                        .basic()
                        .ok_or_else(|| CodegenError::Internal("local m: void".to_string()))?;
                    self.builder()
                        .build_return(Some(&result))
                        .map_err(|e| CodegenError::Internal(format!("local return: {:?}", e)))?;
                    if let Some(bb) = saved_bb {
                        self.builder().position_at_end(bb);
                    }
                }
                let fn_ptr = func.as_global_value().as_pointer_value();
                let f_ptr = self.value_to_ptr(f_val)?;
                let m_ptr = self.value_to_ptr(m_val)?;
                let closure_ptr = self.alloc_closure(
                    fn_ptr,
                    &[
                        (VarId::new(900000), f_ptr.into()),
                        (VarId::new(900001), m_ptr.into()),
                    ],
                )?;
                Ok(Some(closure_ptr.into()))
            }

            // ReaderT instance methods with pre-lowered args
            "ReaderT.pure" => {
                // \r -> x where x = args[0]
                let x_val = args[0];
                let fn_name = "bhc_reader_t_pure";
                let func = self.get_or_create_transformer_fn(fn_name);
                if func.count_basic_blocks() == 0 {
                    let entry = self.llvm_ctx.append_basic_block(func, "entry");
                    let saved_bb = self.builder().get_insert_block();
                    self.builder().position_at_end(entry);
                    let env = func.get_nth_param(0).unwrap().into_pointer_value();
                    let x = self.extract_closure_env_elem(env, 1, 0)?;
                    self.builder().build_return(Some(&x)).map_err(|e| {
                        CodegenError::Internal(format!("reader_t_pure return: {:?}", e))
                    })?;
                    if let Some(bb) = saved_bb {
                        self.builder().position_at_end(bb);
                    }
                }
                let fn_ptr = func.as_global_value().as_pointer_value();
                let x_ptr = self.value_to_ptr(x_val)?;
                let closure_ptr =
                    self.alloc_closure(fn_ptr, &[(VarId::new(900000), x_ptr.into())])?;
                Ok(Some(closure_ptr.into()))
            }
            "ReaderT.>>=" => {
                // \r -> let a = m(r) in k(a)(r) where m = args[0], k = args[1]
                let m_val = args[0];
                let k_val = args[1];
                let fn_name = "bhc_reader_t_bind";
                let func = self.get_or_create_transformer_fn(fn_name);
                if func.count_basic_blocks() == 0 {
                    let entry = self.llvm_ctx.append_basic_block(func, "entry");
                    let saved_bb = self.builder().get_insert_block();
                    self.builder().position_at_end(entry);
                    let env = func.get_nth_param(0).unwrap().into_pointer_value();
                    let r = func.get_nth_param(1).unwrap();
                    let m = self.extract_closure_env_elem(env, 2, 0)?;
                    let k = self.extract_closure_env_elem(env, 2, 1)?;
                    let fn_type = ptr_type.fn_type(&[ptr_type.into(), ptr_type.into()], false);
                    let m_fn = self.extract_closure_fn_ptr(m)?;
                    let r_ptr = self.value_to_ptr(r)?;
                    let a = self
                        .builder()
                        .build_indirect_call(fn_type, m_fn, &[m.into(), r_ptr.into()], "bind_a")
                        .map_err(|e| CodegenError::Internal(format!("reader bind m: {:?}", e)))?
                        .try_as_basic_value()
                        .basic()
                        .ok_or_else(|| CodegenError::Internal("reader bind m: void".to_string()))?;
                    let k_fn = self.extract_closure_fn_ptr(k)?;
                    let kr = self
                        .builder()
                        .build_indirect_call(fn_type, k_fn, &[k.into(), a.into()], "bind_kr")
                        .map_err(|e| CodegenError::Internal(format!("reader bind k: {:?}", e)))?
                        .try_as_basic_value()
                        .basic()
                        .ok_or_else(|| CodegenError::Internal("reader bind k: void".to_string()))?;
                    let kr_ptr = kr.into_pointer_value();
                    let kr_fn = self.extract_closure_fn_ptr(kr_ptr)?;
                    let result = self
                        .builder()
                        .build_indirect_call(
                            fn_type,
                            kr_fn,
                            &[kr_ptr.into(), r_ptr.into()],
                            "bind_result",
                        )
                        .map_err(|e| CodegenError::Internal(format!("reader bind kr: {:?}", e)))?
                        .try_as_basic_value()
                        .basic()
                        .ok_or_else(|| {
                            CodegenError::Internal("reader bind kr: void".to_string())
                        })?;
                    self.builder().build_return(Some(&result)).map_err(|e| {
                        CodegenError::Internal(format!("reader bind return: {:?}", e))
                    })?;
                    if let Some(bb) = saved_bb {
                        self.builder().position_at_end(bb);
                    }
                }
                let fn_ptr = func.as_global_value().as_pointer_value();
                let m_ptr = self.value_to_ptr(m_val)?;
                let k_ptr = self.value_to_ptr(k_val)?;
                let closure_ptr = self.alloc_closure(
                    fn_ptr,
                    &[
                        (VarId::new(900000), m_ptr.into()),
                        (VarId::new(900001), k_ptr.into()),
                    ],
                )?;
                Ok(Some(closure_ptr.into()))
            }
            "ReaderT.>>" => {
                // \r -> m1(r); m2(r) where m1 = args[0], m2 = args[1]
                let m1_val = args[0];
                let m2_val = args[1];
                let fn_name = "bhc_reader_t_then";
                let func = self.get_or_create_transformer_fn(fn_name);
                if func.count_basic_blocks() == 0 {
                    let entry = self.llvm_ctx.append_basic_block(func, "entry");
                    let saved_bb = self.builder().get_insert_block();
                    self.builder().position_at_end(entry);
                    let env = func.get_nth_param(0).unwrap().into_pointer_value();
                    let r = func.get_nth_param(1).unwrap();
                    let m1 = self.extract_closure_env_elem(env, 2, 0)?;
                    let m2 = self.extract_closure_env_elem(env, 2, 1)?;
                    let fn_type = ptr_type.fn_type(&[ptr_type.into(), ptr_type.into()], false);
                    let m1_fn = self.extract_closure_fn_ptr(m1)?;
                    let r_ptr = self.value_to_ptr(r)?;
                    self.builder()
                        .build_indirect_call(
                            fn_type,
                            m1_fn,
                            &[m1.into(), r_ptr.into()],
                            "then_discard",
                        )
                        .map_err(|e| CodegenError::Internal(format!("reader then m1: {:?}", e)))?;
                    let m2_fn = self.extract_closure_fn_ptr(m2)?;
                    let result = self
                        .builder()
                        .build_indirect_call(
                            fn_type,
                            m2_fn,
                            &[m2.into(), r_ptr.into()],
                            "then_result",
                        )
                        .map_err(|e| CodegenError::Internal(format!("reader then m2: {:?}", e)))?
                        .try_as_basic_value()
                        .basic()
                        .ok_or_else(|| {
                            CodegenError::Internal("reader then m2: void".to_string())
                        })?;
                    self.builder().build_return(Some(&result)).map_err(|e| {
                        CodegenError::Internal(format!("reader then return: {:?}", e))
                    })?;
                    if let Some(bb) = saved_bb {
                        self.builder().position_at_end(bb);
                    }
                }
                let fn_ptr = func.as_global_value().as_pointer_value();
                let m1_ptr = self.value_to_ptr(m1_val)?;
                let m2_ptr = self.value_to_ptr(m2_val)?;
                let closure_ptr = self.alloc_closure(
                    fn_ptr,
                    &[
                        (VarId::new(900000), m1_ptr.into()),
                        (VarId::new(900001), m2_ptr.into()),
                    ],
                )?;
                Ok(Some(closure_ptr.into()))
            }
            "ReaderT.fmap" => {
                // \r -> f(m(r)) where f = args[0], m = args[1]
                let f_val = args[0];
                let m_val = args[1];
                let fn_name = "bhc_reader_t_fmap";
                let func = self.get_or_create_transformer_fn(fn_name);
                if func.count_basic_blocks() == 0 {
                    let entry = self.llvm_ctx.append_basic_block(func, "entry");
                    let saved_bb = self.builder().get_insert_block();
                    self.builder().position_at_end(entry);
                    let env = func.get_nth_param(0).unwrap().into_pointer_value();
                    let r = func.get_nth_param(1).unwrap();
                    let f = self.extract_closure_env_elem(env, 2, 0)?;
                    let m = self.extract_closure_env_elem(env, 2, 1)?;
                    let fn_type = ptr_type.fn_type(&[ptr_type.into(), ptr_type.into()], false);
                    let m_fn = self.extract_closure_fn_ptr(m)?;
                    let r_ptr = self.value_to_ptr(r)?;
                    let a = self
                        .builder()
                        .build_indirect_call(fn_type, m_fn, &[m.into(), r_ptr.into()], "fmap_a")
                        .map_err(|e| CodegenError::Internal(format!("reader fmap m: {:?}", e)))?
                        .try_as_basic_value()
                        .basic()
                        .ok_or_else(|| CodegenError::Internal("reader fmap m: void".to_string()))?;
                    let f_fn = self.extract_closure_fn_ptr(f)?;
                    let result = self
                        .builder()
                        .build_indirect_call(fn_type, f_fn, &[f.into(), a.into()], "fmap_result")
                        .map_err(|e| CodegenError::Internal(format!("reader fmap f: {:?}", e)))?
                        .try_as_basic_value()
                        .basic()
                        .ok_or_else(|| CodegenError::Internal("reader fmap f: void".to_string()))?;
                    self.builder().build_return(Some(&result)).map_err(|e| {
                        CodegenError::Internal(format!("reader fmap return: {:?}", e))
                    })?;
                    if let Some(bb) = saved_bb {
                        self.builder().position_at_end(bb);
                    }
                }
                let fn_ptr = func.as_global_value().as_pointer_value();
                let f_ptr = self.value_to_ptr(f_val)?;
                let m_ptr = self.value_to_ptr(m_val)?;
                let closure_ptr = self.alloc_closure(
                    fn_ptr,
                    &[
                        (VarId::new(900000), f_ptr.into()),
                        (VarId::new(900001), m_ptr.into()),
                    ],
                )?;
                Ok(Some(closure_ptr.into()))
            }
            "ReaderT.<*>" => {
                // \r -> (mf(r))(mx(r)) where mf = args[0], mx = args[1]
                let mf_val = args[0];
                let mx_val = args[1];
                let fn_name = "bhc_reader_t_ap";
                let func = self.get_or_create_transformer_fn(fn_name);
                if func.count_basic_blocks() == 0 {
                    let entry = self.llvm_ctx.append_basic_block(func, "entry");
                    let saved_bb = self.builder().get_insert_block();
                    self.builder().position_at_end(entry);
                    let env = func.get_nth_param(0).unwrap().into_pointer_value();
                    let r = func.get_nth_param(1).unwrap();
                    let mf = self.extract_closure_env_elem(env, 2, 0)?;
                    let mx = self.extract_closure_env_elem(env, 2, 1)?;
                    let fn_type = ptr_type.fn_type(&[ptr_type.into(), ptr_type.into()], false);
                    let mf_fn = self.extract_closure_fn_ptr(mf)?;
                    let r_ptr = self.value_to_ptr(r)?;
                    let f = self
                        .builder()
                        .build_indirect_call(fn_type, mf_fn, &[mf.into(), r_ptr.into()], "ap_f")
                        .map_err(|e| CodegenError::Internal(format!("reader ap mf: {:?}", e)))?
                        .try_as_basic_value()
                        .basic()
                        .ok_or_else(|| CodegenError::Internal("reader ap mf: void".to_string()))?;
                    let mx_fn = self.extract_closure_fn_ptr(mx)?;
                    let x = self
                        .builder()
                        .build_indirect_call(fn_type, mx_fn, &[mx.into(), r_ptr.into()], "ap_x")
                        .map_err(|e| CodegenError::Internal(format!("reader ap mx: {:?}", e)))?
                        .try_as_basic_value()
                        .basic()
                        .ok_or_else(|| CodegenError::Internal("reader ap mx: void".to_string()))?;
                    let f_ptr_val = f.into_pointer_value();
                    let f_fn = self.extract_closure_fn_ptr(f_ptr_val)?;
                    let result = self
                        .builder()
                        .build_indirect_call(
                            fn_type,
                            f_fn,
                            &[f_ptr_val.into(), x.into()],
                            "ap_result",
                        )
                        .map_err(|e| CodegenError::Internal(format!("reader ap f(x): {:?}", e)))?
                        .try_as_basic_value()
                        .basic()
                        .ok_or_else(|| {
                            CodegenError::Internal("reader ap f(x): void".to_string())
                        })?;
                    self.builder().build_return(Some(&result)).map_err(|e| {
                        CodegenError::Internal(format!("reader ap return: {:?}", e))
                    })?;
                    if let Some(bb) = saved_bb {
                        self.builder().position_at_end(bb);
                    }
                }
                let fn_ptr = func.as_global_value().as_pointer_value();
                let mf_ptr = self.value_to_ptr(mf_val)?;
                let mx_ptr = self.value_to_ptr(mx_val)?;
                let closure_ptr = self.alloc_closure(
                    fn_ptr,
                    &[
                        (VarId::new(900000), mf_ptr.into()),
                        (VarId::new(900001), mx_ptr.into()),
                    ],
                )?;
                Ok(Some(closure_ptr.into()))
            }
            "ReaderT.lift" | "ReaderT.liftIO" => {
                // \r -> action (const function) where action = args[0]
                let action_val = args[0];
                let fn_name = "bhc_reader_t_lift";
                let func = self.get_or_create_transformer_fn(fn_name);
                if func.count_basic_blocks() == 0 {
                    let entry = self.llvm_ctx.append_basic_block(func, "entry");
                    let saved_bb = self.builder().get_insert_block();
                    self.builder().position_at_end(entry);
                    let env = func.get_nth_param(0).unwrap().into_pointer_value();
                    let _r = func.get_nth_param(1).unwrap();
                    let action = self.extract_closure_env_elem(env, 1, 0)?;
                    self.builder().build_return(Some(&action)).map_err(|e| {
                        CodegenError::Internal(format!("reader lift return: {:?}", e))
                    })?;
                    if let Some(bb) = saved_bb {
                        self.builder().position_at_end(bb);
                    }
                }
                let fn_ptr = func.as_global_value().as_pointer_value();
                let a_ptr = self.value_to_ptr(action_val)?;
                let closure_ptr =
                    self.alloc_closure(fn_ptr, &[(VarId::new(900000), a_ptr.into())])?;
                Ok(Some(closure_ptr.into()))
            }

            // StateT operations (zero-arity: get returns closure \s -> (s, s))
            "get" => self.lower_builtin_get(),

            // StateT operations with pre-lowered args
            "put" => {
                // put s' = closure \_ -> ((), s')
                let s_val = args[0];
                let fn_name = "bhc_state_t_put";
                let func = self.get_or_create_transformer_fn(fn_name);
                if func.count_basic_blocks() == 0 {
                    let entry = self.llvm_ctx.append_basic_block(func, "entry");
                    let saved_bb = self.builder().get_insert_block();
                    self.builder().position_at_end(entry);
                    let env = func.get_nth_param(0).unwrap().into_pointer_value();
                    let _s = func.get_nth_param(1).unwrap();
                    let new_s = self.extract_closure_env_elem(env, 1, 0)?;
                    let unit_val = ptr_type.const_null();
                    let pair = self.alloc_pair(unit_val.into(), new_s.into())?;
                    self.builder()
                        .build_return(Some(&pair))
                        .map_err(|e| CodegenError::Internal(format!("put return: {:?}", e)))?;
                    if let Some(bb) = saved_bb {
                        self.builder().position_at_end(bb);
                    }
                }
                let fn_ptr = func.as_global_value().as_pointer_value();
                let s_ptr = self.value_to_ptr(s_val)?;
                let closure_ptr =
                    self.alloc_closure(fn_ptr, &[(VarId::new(900000), s_ptr.into())])?;
                Ok(Some(closure_ptr.into()))
            }
            "modify" => {
                // modify f = closure \s -> ((), f(s))
                let f_val = args[0];
                let fn_name = "bhc_state_t_modify";
                let func = self.get_or_create_transformer_fn(fn_name);
                if func.count_basic_blocks() == 0 {
                    let entry = self.llvm_ctx.append_basic_block(func, "entry");
                    let saved_bb = self.builder().get_insert_block();
                    self.builder().position_at_end(entry);
                    let env = func.get_nth_param(0).unwrap().into_pointer_value();
                    let s = func.get_nth_param(1).unwrap();
                    let f = self.extract_closure_env_elem(env, 1, 0)?;
                    let f_fn = self.extract_closure_fn_ptr(f)?;
                    let s_ptr_val = self.value_to_ptr(s)?;
                    let fn_type = ptr_type.fn_type(&[ptr_type.into(), ptr_type.into()], false);
                    let new_s = self
                        .builder()
                        .build_indirect_call(
                            fn_type,
                            f_fn,
                            &[f.into(), s_ptr_val.into()],
                            "modify_s",
                        )
                        .map_err(|e| CodegenError::Internal(format!("modify f call: {:?}", e)))?
                        .try_as_basic_value()
                        .basic()
                        .ok_or_else(|| CodegenError::Internal("modify f: void".to_string()))?;
                    let unit_val = ptr_type.const_null();
                    let pair = self.alloc_pair(unit_val.into(), new_s)?;
                    self.builder()
                        .build_return(Some(&pair))
                        .map_err(|e| CodegenError::Internal(format!("modify return: {:?}", e)))?;
                    if let Some(bb) = saved_bb {
                        self.builder().position_at_end(bb);
                    }
                }
                let fn_ptr = func.as_global_value().as_pointer_value();
                let f_ptr = self.value_to_ptr(f_val)?;
                let closure_ptr =
                    self.alloc_closure(fn_ptr, &[(VarId::new(900000), f_ptr.into())])?;
                Ok(Some(closure_ptr.into()))
            }
            "gets" => {
                // gets f = closure \s -> (f(s), s)
                let f_val = args[0];
                let fn_name = "bhc_state_t_gets";
                let func = self.get_or_create_transformer_fn(fn_name);
                if func.count_basic_blocks() == 0 {
                    let entry = self.llvm_ctx.append_basic_block(func, "entry");
                    let saved_bb = self.builder().get_insert_block();
                    self.builder().position_at_end(entry);
                    let env = func.get_nth_param(0).unwrap().into_pointer_value();
                    let s = func.get_nth_param(1).unwrap();
                    let f = self.extract_closure_env_elem(env, 1, 0)?;
                    let f_fn = self.extract_closure_fn_ptr(f)?;
                    let s_ptr_val = self.value_to_ptr(s)?;
                    let fn_type = ptr_type.fn_type(&[ptr_type.into(), ptr_type.into()], false);
                    let result = self
                        .builder()
                        .build_indirect_call(
                            fn_type,
                            f_fn,
                            &[f.into(), s_ptr_val.into()],
                            "gets_result",
                        )
                        .map_err(|e| CodegenError::Internal(format!("gets f call: {:?}", e)))?
                        .try_as_basic_value()
                        .basic()
                        .ok_or_else(|| CodegenError::Internal("gets f: void".to_string()))?;
                    let pair = self.alloc_pair(result, s)?;
                    self.builder()
                        .build_return(Some(&pair))
                        .map_err(|e| CodegenError::Internal(format!("gets return: {:?}", e)))?;
                    if let Some(bb) = saved_bb {
                        self.builder().position_at_end(bb);
                    }
                }
                let fn_ptr = func.as_global_value().as_pointer_value();
                let f_ptr = self.value_to_ptr(f_val)?;
                let closure_ptr =
                    self.alloc_closure(fn_ptr, &[(VarId::new(900000), f_ptr.into())])?;
                Ok(Some(closure_ptr.into()))
            }
            "evalStateT" => {
                // evalStateT m s = fst (runStateT m s)
                let m_val = args[0];
                let s_val = args[1];
                let closure = m_val.into_pointer_value();
                let fn_ptr_val = self.extract_closure_fn_ptr(closure)?;
                let s_ptr = self.value_to_ptr(s_val)?;
                let fn_type = ptr_type.fn_type(&[ptr_type.into(), ptr_type.into()], false);
                let pair_result = self
                    .builder()
                    .build_indirect_call(
                        fn_type,
                        fn_ptr_val,
                        &[closure.into(), s_ptr.into()],
                        "eval_state",
                    )
                    .map_err(|e| CodegenError::Internal(format!("evalStateT call: {:?}", e)))?
                    .try_as_basic_value()
                    .basic()
                    .ok_or_else(|| CodegenError::Internal("evalStateT: void".to_string()))?;
                let result = self.extract_pair_fst(pair_result.into_pointer_value())?;
                Ok(Some(result.into()))
            }
            "execStateT" => {
                // execStateT m s = snd (runStateT m s)
                let m_val = args[0];
                let s_val = args[1];
                let closure = m_val.into_pointer_value();
                let fn_ptr_val = self.extract_closure_fn_ptr(closure)?;
                let s_ptr = self.value_to_ptr(s_val)?;
                let fn_type = ptr_type.fn_type(&[ptr_type.into(), ptr_type.into()], false);
                let pair_result = self
                    .builder()
                    .build_indirect_call(
                        fn_type,
                        fn_ptr_val,
                        &[closure.into(), s_ptr.into()],
                        "exec_state",
                    )
                    .map_err(|e| CodegenError::Internal(format!("execStateT call: {:?}", e)))?
                    .try_as_basic_value()
                    .basic()
                    .ok_or_else(|| CodegenError::Internal("execStateT: void".to_string()))?;
                let result = self.extract_pair_snd(pair_result.into_pointer_value())?;
                Ok(Some(result.into()))
            }

            // StateT instance methods with pre-lowered args
            "StateT.pure" => {
                // \s -> (x, s) where x = args[0]
                let x_val = args[0];
                let fn_name = "bhc_state_t_pure";
                let func = self.get_or_create_transformer_fn(fn_name);
                if func.count_basic_blocks() == 0 {
                    let entry = self.llvm_ctx.append_basic_block(func, "entry");
                    let saved_bb = self.builder().get_insert_block();
                    self.builder().position_at_end(entry);
                    let env = func.get_nth_param(0).unwrap().into_pointer_value();
                    let s = func.get_nth_param(1).unwrap();
                    let x = self.extract_closure_env_elem(env, 1, 0)?;
                    let pair = self.alloc_pair(x.into(), s)?;
                    self.builder().build_return(Some(&pair)).map_err(|e| {
                        CodegenError::Internal(format!("state_t_pure return: {:?}", e))
                    })?;
                    if let Some(bb) = saved_bb {
                        self.builder().position_at_end(bb);
                    }
                }
                let fn_ptr = func.as_global_value().as_pointer_value();
                let x_ptr = self.value_to_ptr(x_val)?;
                let closure_ptr =
                    self.alloc_closure(fn_ptr, &[(VarId::new(900000), x_ptr.into())])?;
                Ok(Some(closure_ptr.into()))
            }
            "StateT.>>=" => {
                // \s -> let (a, s') = m(s); k(a)(s') where m = args[0], k = args[1]
                let m_val = args[0];
                let k_val = args[1];
                let fn_name = "bhc_state_t_bind";
                let func = self.get_or_create_transformer_fn(fn_name);
                if func.count_basic_blocks() == 0 {
                    let entry = self.llvm_ctx.append_basic_block(func, "entry");
                    let saved_bb = self.builder().get_insert_block();
                    self.builder().position_at_end(entry);
                    let env = func.get_nth_param(0).unwrap().into_pointer_value();
                    let s = func.get_nth_param(1).unwrap();
                    let m = self.extract_closure_env_elem(env, 2, 0)?;
                    let k = self.extract_closure_env_elem(env, 2, 1)?;
                    let fn_type = ptr_type.fn_type(&[ptr_type.into(), ptr_type.into()], false);
                    let m_fn = self.extract_closure_fn_ptr(m)?;
                    let s_ptr_val = self.value_to_ptr(s)?;
                    let pair = self
                        .builder()
                        .build_indirect_call(
                            fn_type,
                            m_fn,
                            &[m.into(), s_ptr_val.into()],
                            "sbind_pair",
                        )
                        .map_err(|e| CodegenError::Internal(format!("state bind m: {:?}", e)))?
                        .try_as_basic_value()
                        .basic()
                        .ok_or_else(|| CodegenError::Internal("state bind m: void".to_string()))?;
                    let pair_ptr = pair.into_pointer_value();
                    let a = self.extract_pair_fst(pair_ptr)?;
                    let s_prime = self.extract_pair_snd(pair_ptr)?;
                    let k_fn = self.extract_closure_fn_ptr(k)?;
                    let kr = self
                        .builder()
                        .build_indirect_call(fn_type, k_fn, &[k.into(), a.into()], "sbind_kr")
                        .map_err(|e| CodegenError::Internal(format!("state bind k: {:?}", e)))?
                        .try_as_basic_value()
                        .basic()
                        .ok_or_else(|| CodegenError::Internal("state bind k: void".to_string()))?;
                    let kr_ptr = kr.into_pointer_value();
                    let kr_fn = self.extract_closure_fn_ptr(kr_ptr)?;
                    let result = self
                        .builder()
                        .build_indirect_call(
                            fn_type,
                            kr_fn,
                            &[kr_ptr.into(), s_prime.into()],
                            "sbind_result",
                        )
                        .map_err(|e| CodegenError::Internal(format!("state bind kr: {:?}", e)))?
                        .try_as_basic_value()
                        .basic()
                        .ok_or_else(|| CodegenError::Internal("state bind kr: void".to_string()))?;
                    self.builder().build_return(Some(&result)).map_err(|e| {
                        CodegenError::Internal(format!("state bind return: {:?}", e))
                    })?;
                    if let Some(bb) = saved_bb {
                        self.builder().position_at_end(bb);
                    }
                }
                let fn_ptr = func.as_global_value().as_pointer_value();
                let m_ptr = self.value_to_ptr(m_val)?;
                let k_ptr = self.value_to_ptr(k_val)?;
                let closure_ptr = self.alloc_closure(
                    fn_ptr,
                    &[
                        (VarId::new(900000), m_ptr.into()),
                        (VarId::new(900001), k_ptr.into()),
                    ],
                )?;
                Ok(Some(closure_ptr.into()))
            }
            "StateT.>>" => {
                // \s -> let (_, s') = m1(s); m2(s') where m1 = args[0], m2 = args[1]
                let m1_val = args[0];
                let m2_val = args[1];
                let fn_name = "bhc_state_t_then";
                let func = self.get_or_create_transformer_fn(fn_name);
                if func.count_basic_blocks() == 0 {
                    let entry = self.llvm_ctx.append_basic_block(func, "entry");
                    let saved_bb = self.builder().get_insert_block();
                    self.builder().position_at_end(entry);
                    let env = func.get_nth_param(0).unwrap().into_pointer_value();
                    let s = func.get_nth_param(1).unwrap();
                    let m1 = self.extract_closure_env_elem(env, 2, 0)?;
                    let m2 = self.extract_closure_env_elem(env, 2, 1)?;
                    let fn_type = ptr_type.fn_type(&[ptr_type.into(), ptr_type.into()], false);
                    let m1_fn = self.extract_closure_fn_ptr(m1)?;
                    let s_ptr_val = self.value_to_ptr(s)?;
                    let pair = self
                        .builder()
                        .build_indirect_call(
                            fn_type,
                            m1_fn,
                            &[m1.into(), s_ptr_val.into()],
                            "sthen_pair",
                        )
                        .map_err(|e| CodegenError::Internal(format!("state then m1: {:?}", e)))?
                        .try_as_basic_value()
                        .basic()
                        .ok_or_else(|| CodegenError::Internal("state then m1: void".to_string()))?;
                    let s_prime = self.extract_pair_snd(pair.into_pointer_value())?;
                    let m2_fn = self.extract_closure_fn_ptr(m2)?;
                    let result = self
                        .builder()
                        .build_indirect_call(
                            fn_type,
                            m2_fn,
                            &[m2.into(), s_prime.into()],
                            "sthen_result",
                        )
                        .map_err(|e| CodegenError::Internal(format!("state then m2: {:?}", e)))?
                        .try_as_basic_value()
                        .basic()
                        .ok_or_else(|| CodegenError::Internal("state then m2: void".to_string()))?;
                    self.builder().build_return(Some(&result)).map_err(|e| {
                        CodegenError::Internal(format!("state then return: {:?}", e))
                    })?;
                    if let Some(bb) = saved_bb {
                        self.builder().position_at_end(bb);
                    }
                }
                let fn_ptr = func.as_global_value().as_pointer_value();
                let m1_ptr = self.value_to_ptr(m1_val)?;
                let m2_ptr = self.value_to_ptr(m2_val)?;
                let closure_ptr = self.alloc_closure(
                    fn_ptr,
                    &[
                        (VarId::new(900000), m1_ptr.into()),
                        (VarId::new(900001), m2_ptr.into()),
                    ],
                )?;
                Ok(Some(closure_ptr.into()))
            }
            "StateT.fmap" => {
                // \s -> let (a, s') = m(s) in (f(a), s') where f = args[0], m = args[1]
                let f_val = args[0];
                let m_val = args[1];
                let fn_name = "bhc_state_t_fmap";
                let func = self.get_or_create_transformer_fn(fn_name);
                if func.count_basic_blocks() == 0 {
                    let entry = self.llvm_ctx.append_basic_block(func, "entry");
                    let saved_bb = self.builder().get_insert_block();
                    self.builder().position_at_end(entry);
                    let env = func.get_nth_param(0).unwrap().into_pointer_value();
                    let s = func.get_nth_param(1).unwrap();
                    let f = self.extract_closure_env_elem(env, 2, 0)?;
                    let m = self.extract_closure_env_elem(env, 2, 1)?;
                    let fn_type = ptr_type.fn_type(&[ptr_type.into(), ptr_type.into()], false);
                    let m_fn = self.extract_closure_fn_ptr(m)?;
                    let s_ptr_val = self.value_to_ptr(s)?;
                    let pair = self
                        .builder()
                        .build_indirect_call(
                            fn_type,
                            m_fn,
                            &[m.into(), s_ptr_val.into()],
                            "sfmap_pair",
                        )
                        .map_err(|e| CodegenError::Internal(format!("state fmap m: {:?}", e)))?
                        .try_as_basic_value()
                        .basic()
                        .ok_or_else(|| CodegenError::Internal("state fmap m: void".to_string()))?;
                    let pair_ptr = pair.into_pointer_value();
                    let a = self.extract_pair_fst(pair_ptr)?;
                    let s_prime = self.extract_pair_snd(pair_ptr)?;
                    let f_fn = self.extract_closure_fn_ptr(f)?;
                    let fa = self
                        .builder()
                        .build_indirect_call(fn_type, f_fn, &[f.into(), a.into()], "sfmap_fa")
                        .map_err(|e| CodegenError::Internal(format!("state fmap f: {:?}", e)))?
                        .try_as_basic_value()
                        .basic()
                        .ok_or_else(|| CodegenError::Internal("state fmap f: void".to_string()))?;
                    let result_pair = self.alloc_pair(fa, s_prime.into())?;
                    self.builder()
                        .build_return(Some(&result_pair))
                        .map_err(|e| {
                            CodegenError::Internal(format!("state fmap return: {:?}", e))
                        })?;
                    if let Some(bb) = saved_bb {
                        self.builder().position_at_end(bb);
                    }
                }
                let fn_ptr = func.as_global_value().as_pointer_value();
                let f_ptr = self.value_to_ptr(f_val)?;
                let m_ptr = self.value_to_ptr(m_val)?;
                let closure_ptr = self.alloc_closure(
                    fn_ptr,
                    &[
                        (VarId::new(900000), f_ptr.into()),
                        (VarId::new(900001), m_ptr.into()),
                    ],
                )?;
                Ok(Some(closure_ptr.into()))
            }
            "StateT.<*>" => {
                // \s -> let (f, s') = mf(s); (x, s'') = mx(s') in (f(x), s'')
                let mf_val = args[0];
                let mx_val = args[1];
                let fn_name = "bhc_state_t_ap";
                let func = self.get_or_create_transformer_fn(fn_name);
                if func.count_basic_blocks() == 0 {
                    let entry = self.llvm_ctx.append_basic_block(func, "entry");
                    let saved_bb = self.builder().get_insert_block();
                    self.builder().position_at_end(entry);
                    let env = func.get_nth_param(0).unwrap().into_pointer_value();
                    let s = func.get_nth_param(1).unwrap();
                    let mf = self.extract_closure_env_elem(env, 2, 0)?;
                    let mx = self.extract_closure_env_elem(env, 2, 1)?;
                    let fn_type = ptr_type.fn_type(&[ptr_type.into(), ptr_type.into()], false);
                    let mf_fn = self.extract_closure_fn_ptr(mf)?;
                    let s_ptr_val = self.value_to_ptr(s)?;
                    let pair1 = self
                        .builder()
                        .build_indirect_call(
                            fn_type,
                            mf_fn,
                            &[mf.into(), s_ptr_val.into()],
                            "sap_p1",
                        )
                        .map_err(|e| CodegenError::Internal(format!("state ap mf: {:?}", e)))?
                        .try_as_basic_value()
                        .basic()
                        .ok_or_else(|| CodegenError::Internal("state ap mf: void".to_string()))?;
                    let pair1_ptr = pair1.into_pointer_value();
                    let f = self.extract_pair_fst(pair1_ptr)?;
                    let s_prime = self.extract_pair_snd(pair1_ptr)?;
                    let mx_fn = self.extract_closure_fn_ptr(mx)?;
                    let pair2 = self
                        .builder()
                        .build_indirect_call(fn_type, mx_fn, &[mx.into(), s_prime.into()], "sap_p2")
                        .map_err(|e| CodegenError::Internal(format!("state ap mx: {:?}", e)))?
                        .try_as_basic_value()
                        .basic()
                        .ok_or_else(|| CodegenError::Internal("state ap mx: void".to_string()))?;
                    let pair2_ptr = pair2.into_pointer_value();
                    let x = self.extract_pair_fst(pair2_ptr)?;
                    let s_double_prime = self.extract_pair_snd(pair2_ptr)?;
                    let f_fn_ptr = self.extract_closure_fn_ptr(f)?;
                    let fx = self
                        .builder()
                        .build_indirect_call(fn_type, f_fn_ptr, &[f.into(), x.into()], "sap_fx")
                        .map_err(|e| CodegenError::Internal(format!("state ap f(x): {:?}", e)))?
                        .try_as_basic_value()
                        .basic()
                        .ok_or_else(|| CodegenError::Internal("state ap f(x): void".to_string()))?;
                    let result_pair = self.alloc_pair(fx, s_double_prime.into())?;
                    self.builder()
                        .build_return(Some(&result_pair))
                        .map_err(|e| CodegenError::Internal(format!("state ap return: {:?}", e)))?;
                    if let Some(bb) = saved_bb {
                        self.builder().position_at_end(bb);
                    }
                }
                let fn_ptr = func.as_global_value().as_pointer_value();
                let mf_ptr = self.value_to_ptr(mf_val)?;
                let mx_ptr = self.value_to_ptr(mx_val)?;
                let closure_ptr = self.alloc_closure(
                    fn_ptr,
                    &[
                        (VarId::new(900000), mf_ptr.into()),
                        (VarId::new(900001), mx_ptr.into()),
                    ],
                )?;
                Ok(Some(closure_ptr.into()))
            }
            "StateT.lift" | "StateT.liftIO" => {
                // \s -> (action, s) where action = args[0]
                let action_val = args[0];
                let fn_name = "bhc_state_t_lift";
                let func = self.get_or_create_transformer_fn(fn_name);
                if func.count_basic_blocks() == 0 {
                    let entry = self.llvm_ctx.append_basic_block(func, "entry");
                    let saved_bb = self.builder().get_insert_block();
                    self.builder().position_at_end(entry);
                    let env = func.get_nth_param(0).unwrap().into_pointer_value();
                    let s = func.get_nth_param(1).unwrap();
                    let action = self.extract_closure_env_elem(env, 1, 0)?;
                    let pair = self.alloc_pair(action.into(), s)?;
                    self.builder().build_return(Some(&pair)).map_err(|e| {
                        CodegenError::Internal(format!("state lift return: {:?}", e))
                    })?;
                    if let Some(bb) = saved_bb {
                        self.builder().position_at_end(bb);
                    }
                }
                let fn_ptr = func.as_global_value().as_pointer_value();
                let a_ptr = self.value_to_ptr(action_val)?;
                let closure_ptr =
                    self.alloc_closure(fn_ptr, &[(VarId::new(900000), a_ptr.into())])?;
                Ok(Some(closure_ptr.into()))
            }

            // Generic lift/liftIO (delegate to ReaderT versions as default)
            "lift" => {
                let action_val = args[0];
                let fn_name = "bhc_reader_t_lift";
                let func = self.get_or_create_transformer_fn(fn_name);
                if func.count_basic_blocks() == 0 {
                    let entry = self.llvm_ctx.append_basic_block(func, "entry");
                    let saved_bb = self.builder().get_insert_block();
                    self.builder().position_at_end(entry);
                    let env = func.get_nth_param(0).unwrap().into_pointer_value();
                    let _r = func.get_nth_param(1).unwrap();
                    let action = self.extract_closure_env_elem(env, 1, 0)?;
                    self.builder()
                        .build_return(Some(&action))
                        .map_err(|e| CodegenError::Internal(format!("lift return: {:?}", e)))?;
                    if let Some(bb) = saved_bb {
                        self.builder().position_at_end(bb);
                    }
                }
                let fn_ptr = func.as_global_value().as_pointer_value();
                let a_ptr = self.value_to_ptr(action_val)?;
                let closure_ptr =
                    self.alloc_closure(fn_ptr, &[(VarId::new(900000), a_ptr.into())])?;
                Ok(Some(closure_ptr.into()))
            }
            "liftIO" => {
                // For IO, liftIO = id
                Ok(Some(args[0]))
            }

            // Data.Text operations - call RTS functions with already-lowered values
            "Data.Text.empty" => {
                let rts_fn = self.functions.get(&VarId::new(1000200)).ok_or_else(|| {
                    CodegenError::Internal("bhc_text_empty not declared".to_string())
                })?;
                let result = self
                    .builder()
                    .build_call(*rts_fn, &[], "text_empty")
                    .map_err(|e| CodegenError::Internal(format!("text_empty: {:?}", e)))?
                    .try_as_basic_value()
                    .basic()
                    .ok_or_else(|| CodegenError::Internal("text_empty: void".to_string()))?;
                Ok(Some(result))
            }
            "Data.Text.singleton" => {
                let rts_fn = self.functions.get(&VarId::new(1000201)).ok_or_else(|| {
                    CodegenError::Internal("bhc_text_singleton not declared".to_string())
                })?;
                let result = self
                    .builder()
                    .build_call(*rts_fn, &[args[0].into()], "text_singleton")
                    .map_err(|e| CodegenError::Internal(format!("text_singleton: {:?}", e)))?
                    .try_as_basic_value()
                    .basic()
                    .ok_or_else(|| CodegenError::Internal("text_singleton: void".to_string()))?;
                Ok(Some(result))
            }
            "Data.Text.pack" => {
                let rts_fn = self.functions.get(&VarId::new(1000223)).ok_or_else(|| {
                    CodegenError::Internal("bhc_text_pack not declared".to_string())
                })?;
                let result = self
                    .builder()
                    .build_call(*rts_fn, &[args[0].into()], "text_pack")
                    .map_err(|e| CodegenError::Internal(format!("text_pack: {:?}", e)))?
                    .try_as_basic_value()
                    .basic()
                    .ok_or_else(|| CodegenError::Internal("text_pack: void".to_string()))?;
                Ok(Some(result))
            }
            "Data.Text.unpack" => {
                let rts_fn = self.functions.get(&VarId::new(1000224)).ok_or_else(|| {
                    CodegenError::Internal("bhc_text_unpack not declared".to_string())
                })?;
                let result = self
                    .builder()
                    .build_call(*rts_fn, &[args[0].into()], "text_unpack")
                    .map_err(|e| CodegenError::Internal(format!("text_unpack: {:?}", e)))?
                    .try_as_basic_value()
                    .basic()
                    .ok_or_else(|| CodegenError::Internal("text_unpack: void".to_string()))?;
                Ok(Some(result))
            }
            "Data.Text.null" => {
                let rts_fn = self.functions.get(&VarId::new(1000202)).ok_or_else(|| {
                    CodegenError::Internal("bhc_text_null not declared".to_string())
                })?;
                let result = self
                    .builder()
                    .build_call(*rts_fn, &[args[0].into()], "text_null")
                    .map_err(|e| CodegenError::Internal(format!("text_null: {:?}", e)))?
                    .try_as_basic_value()
                    .basic()
                    .ok_or_else(|| CodegenError::Internal("text_null: void".to_string()))?;
                Ok(Some(result))
            }
            "Data.Text.length" => {
                let rts_fn = self.functions.get(&VarId::new(1000203)).ok_or_else(|| {
                    CodegenError::Internal("bhc_text_length not declared".to_string())
                })?;
                let result = self
                    .builder()
                    .build_call(*rts_fn, &[args[0].into()], "text_length")
                    .map_err(|e| CodegenError::Internal(format!("text_length: {:?}", e)))?
                    .try_as_basic_value()
                    .basic()
                    .ok_or_else(|| CodegenError::Internal("text_length: void".to_string()))?;
                Ok(Some(result))
            }
            "Data.Text.append" | "Data.Text.<>" => {
                let rts_fn = self.functions.get(&VarId::new(1000210)).ok_or_else(|| {
                    CodegenError::Internal("bhc_text_append not declared".to_string())
                })?;
                let result = self
                    .builder()
                    .build_call(*rts_fn, &[args[0].into(), args[1].into()], "text_append")
                    .map_err(|e| CodegenError::Internal(format!("text_append: {:?}", e)))?
                    .try_as_basic_value()
                    .basic()
                    .ok_or_else(|| CodegenError::Internal("text_append: void".to_string()))?;
                Ok(Some(result))
            }
            "Data.Text.toUpper" => {
                let rts_fn = self.functions.get(&VarId::new(1000220)).ok_or_else(|| {
                    CodegenError::Internal("bhc_text_to_upper not declared".to_string())
                })?;
                let result = self
                    .builder()
                    .build_call(*rts_fn, &[args[0].into()], "text_to_upper")
                    .map_err(|e| CodegenError::Internal(format!("text_to_upper: {:?}", e)))?
                    .try_as_basic_value()
                    .basic()
                    .ok_or_else(|| CodegenError::Internal("text_to_upper: void".to_string()))?;
                Ok(Some(result))
            }
            "Data.Text.toLower" => {
                let rts_fn = self.functions.get(&VarId::new(1000219)).ok_or_else(|| {
                    CodegenError::Internal("bhc_text_to_lower not declared".to_string())
                })?;
                let result = self
                    .builder()
                    .build_call(*rts_fn, &[args[0].into()], "text_to_lower")
                    .map_err(|e| CodegenError::Internal(format!("text_to_lower: {:?}", e)))?
                    .try_as_basic_value()
                    .basic()
                    .ok_or_else(|| CodegenError::Internal("text_to_lower: void".to_string()))?;
                Ok(Some(result))
            }
            "Data.Text.take" => {
                let rts_fn = self.functions.get(&VarId::new(1000212)).ok_or_else(|| {
                    CodegenError::Internal("bhc_text_take not declared".to_string())
                })?;
                let result = self
                    .builder()
                    .build_call(*rts_fn, &[args[0].into(), args[1].into()], "text_take")
                    .map_err(|e| CodegenError::Internal(format!("text_take: {:?}", e)))?
                    .try_as_basic_value()
                    .basic()
                    .ok_or_else(|| CodegenError::Internal("text_take: void".to_string()))?;
                Ok(Some(result))
            }
            "Data.Text.drop" => {
                let rts_fn = self.functions.get(&VarId::new(1000214)).ok_or_else(|| {
                    CodegenError::Internal("bhc_text_drop not declared".to_string())
                })?;
                let result = self
                    .builder()
                    .build_call(*rts_fn, &[args[0].into(), args[1].into()], "text_drop")
                    .map_err(|e| CodegenError::Internal(format!("text_drop: {:?}", e)))?
                    .try_as_basic_value()
                    .basic()
                    .ok_or_else(|| CodegenError::Internal("text_drop: void".to_string()))?;
                Ok(Some(result))
            }

            // Data.Text.Lazy — direct RTS dispatch
            "Data.Text.Lazy.empty" => {
                let rts_fn = self.functions.get(&VarId::new(1000270)).ok_or_else(|| {
                    CodegenError::Internal("bhc_lazy_text_empty not declared".to_string())
                })?;
                let result = self
                    .builder()
                    .build_call(*rts_fn, &[], "lt_empty")
                    .map_err(|e| CodegenError::Internal(format!("lt_empty: {:?}", e)))?
                    .try_as_basic_value()
                    .basic()
                    .ok_or_else(|| CodegenError::Internal("lt_empty: void".to_string()))?;
                Ok(Some(result))
            }
            "Data.Text.Lazy.fromStrict"
            | "Data.Text.Lazy.toStrict"
            | "Data.Text.Lazy.pack"
            | "Data.Text.Lazy.unpack"
            | "Data.Text.Lazy.fromChunks"
            | "Data.Text.Lazy.toChunks"
            | "Data.Text.Lazy.tail" => {
                let var_id = match name {
                    "Data.Text.Lazy.fromStrict" => 1000271,
                    "Data.Text.Lazy.toStrict" => 1000272,
                    "Data.Text.Lazy.pack" => 1000273,
                    "Data.Text.Lazy.unpack" => 1000274,
                    "Data.Text.Lazy.fromChunks" => 1000278,
                    "Data.Text.Lazy.toChunks" => 1000279,
                    "Data.Text.Lazy.tail" => 1000281,
                    _ => unreachable!(),
                };
                let rts_fn = self
                    .functions
                    .get(&VarId::new(var_id))
                    .ok_or_else(|| CodegenError::Internal(format!("{} not declared", name)))?;
                let result = self
                    .builder()
                    .build_call(*rts_fn, &[args[0].into()], "lt_unary")
                    .map_err(|e| CodegenError::Internal(format!("{}: {:?}", name, e)))?
                    .try_as_basic_value()
                    .basic()
                    .ok_or_else(|| CodegenError::Internal(format!("{}: void", name)))?;
                Ok(Some(result))
            }
            "Data.Text.Lazy.null" | "Data.Text.Lazy.length" | "Data.Text.Lazy.head" => {
                let var_id = match name {
                    "Data.Text.Lazy.null" => 1000275,
                    "Data.Text.Lazy.length" => 1000276,
                    "Data.Text.Lazy.head" => 1000280,
                    _ => unreachable!(),
                };
                let rts_fn = self
                    .functions
                    .get(&VarId::new(var_id))
                    .ok_or_else(|| CodegenError::Internal(format!("{} not declared", name)))?;
                let result = self
                    .builder()
                    .build_call(*rts_fn, &[args[0].into()], "lt_to_int")
                    .map_err(|e| CodegenError::Internal(format!("{}: {:?}", name, e)))?
                    .try_as_basic_value()
                    .basic()
                    .ok_or_else(|| CodegenError::Internal(format!("{}: void", name)))?;
                Ok(Some(self.int_to_ptr(result.into_int_value())?.into()))
            }
            "Data.Text.Lazy.append" | "Data.Text.Lazy.<>" => {
                let rts_fn = self.functions.get(&VarId::new(1000277)).ok_or_else(|| {
                    CodegenError::Internal("bhc_lazy_text_append not declared".to_string())
                })?;
                let result = self
                    .builder()
                    .build_call(*rts_fn, &[args[0].into(), args[1].into()], "lt_append")
                    .map_err(|e| CodegenError::Internal(format!("lt_append: {:?}", e)))?
                    .try_as_basic_value()
                    .basic()
                    .ok_or_else(|| CodegenError::Internal("lt_append: void".to_string()))?;
                Ok(Some(result))
            }
            "Data.Text.Lazy.take" | "Data.Text.Lazy.drop" => {
                let var_id = if name == "Data.Text.Lazy.take" {
                    1000282
                } else {
                    1000283
                };
                let rts_fn = self
                    .functions
                    .get(&VarId::new(var_id))
                    .ok_or_else(|| CodegenError::Internal(format!("{} not declared", name)))?;
                let result = self
                    .builder()
                    .build_call(*rts_fn, &[args[0].into(), args[1].into()], "lt_int_ptr")
                    .map_err(|e| CodegenError::Internal(format!("{}: {:?}", name, e)))?
                    .try_as_basic_value()
                    .basic()
                    .ok_or_else(|| CodegenError::Internal(format!("{}: void", name)))?;
                Ok(Some(result))
            }

            // Data.ByteString.Lazy — direct RTS dispatch
            "Data.ByteString.Lazy.empty" => {
                let rts_fn = self.functions.get(&VarId::new(1000440)).ok_or_else(|| {
                    CodegenError::Internal("bhc_lazy_bs_empty not declared".to_string())
                })?;
                let result = self
                    .builder()
                    .build_call(*rts_fn, &[], "lbs_empty")
                    .map_err(|e| CodegenError::Internal(format!("lbs_empty: {:?}", e)))?
                    .try_as_basic_value()
                    .basic()
                    .ok_or_else(|| CodegenError::Internal("lbs_empty: void".to_string()))?;
                Ok(Some(result))
            }
            "Data.ByteString.Lazy.fromStrict"
            | "Data.ByteString.Lazy.toStrict"
            | "Data.ByteString.Lazy.fromChunks"
            | "Data.ByteString.Lazy.toChunks"
            | "Data.ByteString.Lazy.pack"
            | "Data.ByteString.Lazy.tail"
            | "Data.ByteString.Lazy.putStr" => {
                let var_id = match name {
                    "Data.ByteString.Lazy.fromStrict" => 1000441,
                    "Data.ByteString.Lazy.toStrict" => 1000442,
                    "Data.ByteString.Lazy.fromChunks" => 1000443,
                    "Data.ByteString.Lazy.toChunks" => 1000444,
                    "Data.ByteString.Lazy.pack" => 1000447,
                    "Data.ByteString.Lazy.tail" => 1000450,
                    "Data.ByteString.Lazy.putStr" => 1000457,
                    _ => unreachable!(),
                };
                let rts_fn = self
                    .functions
                    .get(&VarId::new(var_id))
                    .ok_or_else(|| CodegenError::Internal(format!("{} not declared", name)))?;
                let result = self
                    .builder()
                    .build_call(*rts_fn, &[args[0].into()], "lbs_unary")
                    .map_err(|e| CodegenError::Internal(format!("{}: {:?}", name, e)))?
                    .try_as_basic_value()
                    .basic()
                    .ok_or_else(|| CodegenError::Internal(format!("{}: void", name)))?;
                Ok(Some(result))
            }
            "Data.ByteString.Lazy.null"
            | "Data.ByteString.Lazy.length"
            | "Data.ByteString.Lazy.head" => {
                let var_id = match name {
                    "Data.ByteString.Lazy.null" => 1000445,
                    "Data.ByteString.Lazy.length" => 1000446,
                    "Data.ByteString.Lazy.head" => 1000449,
                    _ => unreachable!(),
                };
                let rts_fn = self
                    .functions
                    .get(&VarId::new(var_id))
                    .ok_or_else(|| CodegenError::Internal(format!("{} not declared", name)))?;
                let result = self
                    .builder()
                    .build_call(*rts_fn, &[args[0].into()], "lbs_to_int")
                    .map_err(|e| CodegenError::Internal(format!("{}: {:?}", name, e)))?
                    .try_as_basic_value()
                    .basic()
                    .ok_or_else(|| CodegenError::Internal(format!("{}: void", name)))?;
                Ok(Some(self.int_to_ptr(result.into_int_value())?.into()))
            }
            "Data.ByteString.Lazy.append"
            | "Data.ByteString.Lazy.<>"
            | "Data.ByteString.Lazy.isPrefixOf"
            | "Data.ByteString.Lazy.hPutStr" => {
                let var_id = match name {
                    "Data.ByteString.Lazy.append" | "Data.ByteString.Lazy.<>" => 1000448,
                    "Data.ByteString.Lazy.isPrefixOf" => 1000454,
                    "Data.ByteString.Lazy.hPutStr" => 1000458,
                    _ => unreachable!(),
                };
                let rts_fn = self
                    .functions
                    .get(&VarId::new(var_id))
                    .ok_or_else(|| CodegenError::Internal(format!("{} not declared", name)))?;
                let result = self
                    .builder()
                    .build_call(*rts_fn, &[args[0].into(), args[1].into()], "lbs_binary")
                    .map_err(|e| CodegenError::Internal(format!("{}: {:?}", name, e)))?
                    .try_as_basic_value()
                    .basic()
                    .ok_or_else(|| CodegenError::Internal(format!("{}: void", name)))?;
                Ok(Some(result))
            }
            "Data.ByteString.Lazy.take" | "Data.ByteString.Lazy.drop" => {
                let var_id = if name == "Data.ByteString.Lazy.take" {
                    1000451
                } else {
                    1000452
                };
                let rts_fn = self
                    .functions
                    .get(&VarId::new(var_id))
                    .ok_or_else(|| CodegenError::Internal(format!("{} not declared", name)))?;
                let result = self
                    .builder()
                    .build_call(*rts_fn, &[args[0].into(), args[1].into()], "lbs_int_ptr")
                    .map_err(|e| CodegenError::Internal(format!("{}: {:?}", name, e)))?
                    .try_as_basic_value()
                    .basic()
                    .ok_or_else(|| CodegenError::Internal(format!("{}: void", name)))?;
                Ok(Some(result))
            }
            "Data.ByteString.Lazy.readFile" | "Data.ByteString.Lazy.hGetContents" => {
                let var_id = if name == "Data.ByteString.Lazy.readFile" {
                    1000455
                } else {
                    1000459
                };
                let rts_fn = self
                    .functions
                    .get(&VarId::new(var_id))
                    .ok_or_else(|| CodegenError::Internal(format!("{} not declared", name)))?;
                let result = self
                    .builder()
                    .build_call(*rts_fn, &[args[0].into()], "lbs_io_unary")
                    .map_err(|e| CodegenError::Internal(format!("{}: {:?}", name, e)))?
                    .try_as_basic_value()
                    .basic()
                    .ok_or_else(|| CodegenError::Internal(format!("{}: void", name)))?;
                Ok(Some(result))
            }
            "Data.ByteString.Lazy.writeFile" => {
                let rts_fn = self.functions.get(&VarId::new(1000456)).ok_or_else(|| {
                    CodegenError::Internal("bhc_lazy_bs_write_file not declared".to_string())
                })?;
                let result = self
                    .builder()
                    .build_call(*rts_fn, &[args[0].into(), args[1].into()], "lbs_write_file")
                    .map_err(|e| CodegenError::Internal(format!("lbs_write_file: {:?}", e)))?
                    .try_as_basic_value()
                    .basic()
                    .ok_or_else(|| CodegenError::Internal("lbs_write_file: void".to_string()))?;
                Ok(Some(result))
            }

            // Data.ByteString.Lazy.Char8 — direct RTS dispatch
            "Data.ByteString.Lazy.Char8.unpack"
            | "Data.ByteString.Lazy.Char8.lines"
            | "Data.ByteString.Lazy.Char8.unlines" => {
                let var_id = match name {
                    "Data.ByteString.Lazy.Char8.unpack" => 1000470,
                    "Data.ByteString.Lazy.Char8.lines" => 1000471,
                    "Data.ByteString.Lazy.Char8.unlines" => 1000472,
                    _ => unreachable!(),
                };
                let rts_fn = self
                    .functions
                    .get(&VarId::new(var_id))
                    .ok_or_else(|| CodegenError::Internal(format!("{} not declared", name)))?;
                let result = self
                    .builder()
                    .build_call(*rts_fn, &[args[0].into()], "lbs_c8_unary")
                    .map_err(|e| CodegenError::Internal(format!("{}: {:?}", name, e)))?
                    .try_as_basic_value()
                    .basic()
                    .ok_or_else(|| CodegenError::Internal(format!("{}: void", name)))?;
                Ok(Some(result))
            }
            "Data.ByteString.Lazy.Char8.take" => {
                let rts_fn = self.functions.get(&VarId::new(1000473)).ok_or_else(|| {
                    CodegenError::Internal("bhc_lazy_bs_char8_take not declared".to_string())
                })?;
                let result = self
                    .builder()
                    .build_call(*rts_fn, &[args[0].into(), args[1].into()], "lbs_c8_take")
                    .map_err(|e| CodegenError::Internal(format!("lbs_c8_take: {:?}", e)))?
                    .try_as_basic_value()
                    .basic()
                    .ok_or_else(|| CodegenError::Internal("lbs_c8_take: void".to_string()))?;
                Ok(Some(result))
            }
            "Data.ByteString.Lazy.Char8.cons" => {
                let rts_fn = self.functions.get(&VarId::new(1000475)).ok_or_else(|| {
                    CodegenError::Internal("bhc_lazy_bs_char8_cons not declared".to_string())
                })?;
                let result = self
                    .builder()
                    .build_call(*rts_fn, &[args[0].into(), args[1].into()], "lbs_c8_cons")
                    .map_err(|e| CodegenError::Internal(format!("lbs_c8_cons: {:?}", e)))?
                    .try_as_basic_value()
                    .basic()
                    .ok_or_else(|| CodegenError::Internal("lbs_c8_cons: void".to_string()))?;
                Ok(Some(result))
            }

            // Data.Text.Lazy.Encoding — direct RTS dispatch
            "Data.Text.Lazy.Encoding.encodeUtf8" => {
                let rts_fn = self.functions.get(&VarId::new(1000476)).ok_or_else(|| {
                    CodegenError::Internal("bhc_lazy_text_encode_utf8 not declared".to_string())
                })?;
                let result = self
                    .builder()
                    .build_call(*rts_fn, &[args[0].into()], "lt_encode_utf8")
                    .map_err(|e| CodegenError::Internal(format!("lt_encode_utf8: {:?}", e)))?
                    .try_as_basic_value()
                    .basic()
                    .ok_or_else(|| CodegenError::Internal("lt_encode_utf8: void".to_string()))?;
                Ok(Some(result))
            }
            "Data.Text.Lazy.Encoding.decodeUtf8" => {
                let rts_fn = self.functions.get(&VarId::new(1000477)).ok_or_else(|| {
                    CodegenError::Internal("bhc_lazy_text_decode_utf8 not declared".to_string())
                })?;
                let result = self
                    .builder()
                    .build_call(*rts_fn, &[args[0].into()], "lt_decode_utf8")
                    .map_err(|e| CodegenError::Internal(format!("lt_decode_utf8: {:?}", e)))?
                    .try_as_basic_value()
                    .basic()
                    .ok_or_else(|| CodegenError::Internal("lt_decode_utf8: void".to_string()))?;
                Ok(Some(result))
            }

            // Data.ByteString.Builder — direct RTS dispatch
            "Data.ByteString.Builder.empty" => {
                let rts_fn = self.functions.get(&VarId::new(1000440)).ok_or_else(|| {
                    CodegenError::Internal("bhc_lazy_bs_empty not declared".to_string())
                })?;
                let result = self
                    .builder()
                    .build_call(*rts_fn, &[], "bsb_empty")
                    .map_err(|e| CodegenError::Internal(format!("bsb_empty: {:?}", e)))?
                    .try_as_basic_value()
                    .basic()
                    .ok_or_else(|| CodegenError::Internal("bsb_empty: void".to_string()))?;
                Ok(Some(result))
            }
            "Data.ByteString.Builder.singleton"
            | "Data.ByteString.Builder.word8"
            | "Data.ByteString.Builder.charUtf8"
            | "Data.ByteString.Builder.char7"
            | "Data.ByteString.Builder.char8"
            | "Data.ByteString.Builder.intDec"
            | "Data.ByteString.Builder.int8Dec"
            | "Data.ByteString.Builder.int16Dec"
            | "Data.ByteString.Builder.int32Dec"
            | "Data.ByteString.Builder.int64Dec"
            | "Data.ByteString.Builder.integerDec"
            | "Data.ByteString.Builder.wordDec"
            | "Data.ByteString.Builder.word8Dec"
            | "Data.ByteString.Builder.word16Dec"
            | "Data.ByteString.Builder.word32Dec"
            | "Data.ByteString.Builder.word64Dec"
            | "Data.ByteString.Builder.word16BE"
            | "Data.ByteString.Builder.int16BE"
            | "Data.ByteString.Builder.word32BE"
            | "Data.ByteString.Builder.int32BE"
            | "Data.ByteString.Builder.word64BE"
            | "Data.ByteString.Builder.int64BE"
            | "Data.ByteString.Builder.word16LE"
            | "Data.ByteString.Builder.int16LE"
            | "Data.ByteString.Builder.word32LE"
            | "Data.ByteString.Builder.int32LE"
            | "Data.ByteString.Builder.word64LE"
            | "Data.ByteString.Builder.int64LE"
            | "Data.ByteString.Builder.word16Host"
            | "Data.ByteString.Builder.int16Host"
            | "Data.ByteString.Builder.word32Host"
            | "Data.ByteString.Builder.int32Host"
            | "Data.ByteString.Builder.word64Host"
            | "Data.ByteString.Builder.int64Host"
            | "Data.ByteString.Builder.wordHex"
            | "Data.ByteString.Builder.word8Hex"
            | "Data.ByteString.Builder.word16Hex"
            | "Data.ByteString.Builder.word32Hex"
            | "Data.ByteString.Builder.word64Hex"
            | "Data.ByteString.Builder.word8HexFixed" => {
                let var_id = match name {
                    "Data.ByteString.Builder.singleton" | "Data.ByteString.Builder.word8" => {
                        1000478
                    }
                    "Data.ByteString.Builder.charUtf8" => 1000479,
                    "Data.ByteString.Builder.char7" => 1000482,
                    "Data.ByteString.Builder.char8" => 1000483,
                    "Data.ByteString.Builder.intDec"
                    | "Data.ByteString.Builder.int8Dec"
                    | "Data.ByteString.Builder.int16Dec"
                    | "Data.ByteString.Builder.int32Dec"
                    | "Data.ByteString.Builder.int64Dec"
                    | "Data.ByteString.Builder.integerDec"
                    | "Data.ByteString.Builder.wordDec"
                    | "Data.ByteString.Builder.word8Dec"
                    | "Data.ByteString.Builder.word16Dec"
                    | "Data.ByteString.Builder.word32Dec"
                    | "Data.ByteString.Builder.word64Dec" => 1000481,
                    "Data.ByteString.Builder.word16BE" | "Data.ByteString.Builder.int16BE" => {
                        1000486
                    }
                    "Data.ByteString.Builder.word32BE" | "Data.ByteString.Builder.int32BE" => {
                        1000487
                    }
                    "Data.ByteString.Builder.word64BE" | "Data.ByteString.Builder.int64BE" => {
                        1000488
                    }
                    "Data.ByteString.Builder.word16LE"
                    | "Data.ByteString.Builder.int16LE"
                    | "Data.ByteString.Builder.word16Host"
                    | "Data.ByteString.Builder.int16Host" => 1000489,
                    "Data.ByteString.Builder.word32LE"
                    | "Data.ByteString.Builder.int32LE"
                    | "Data.ByteString.Builder.word32Host"
                    | "Data.ByteString.Builder.int32Host" => 1000490,
                    "Data.ByteString.Builder.word64LE"
                    | "Data.ByteString.Builder.int64LE"
                    | "Data.ByteString.Builder.word64Host"
                    | "Data.ByteString.Builder.int64Host" => 1000491,
                    "Data.ByteString.Builder.wordHex"
                    | "Data.ByteString.Builder.word8Hex"
                    | "Data.ByteString.Builder.word16Hex"
                    | "Data.ByteString.Builder.word32Hex"
                    | "Data.ByteString.Builder.word64Hex" => 1000492,
                    "Data.ByteString.Builder.word8HexFixed" => 1000493,
                    _ => unreachable!(),
                };
                let rts_fn = self
                    .functions
                    .get(&VarId::new(var_id))
                    .ok_or_else(|| CodegenError::Internal(format!("{} not declared", name)))?;
                let result = self
                    .builder()
                    .build_call(*rts_fn, &[args[0].into()], "bsb_direct")
                    .map_err(|e| CodegenError::Internal(format!("{}: {:?}", name, e)))?
                    .try_as_basic_value()
                    .basic()
                    .ok_or_else(|| CodegenError::Internal(format!("{}: void", name)))?;
                Ok(Some(result))
            }
            "Data.ByteString.Builder.stringUtf8"
            | "Data.ByteString.Builder.string7"
            | "Data.ByteString.Builder.string8" => {
                let var_id = match name {
                    "Data.ByteString.Builder.stringUtf8" => 1000480,
                    "Data.ByteString.Builder.string7" => 1000484,
                    "Data.ByteString.Builder.string8" => 1000485,
                    _ => unreachable!(),
                };
                let rts_fn = self
                    .functions
                    .get(&VarId::new(var_id))
                    .ok_or_else(|| CodegenError::Internal(format!("{} not declared", name)))?;
                let result = self
                    .builder()
                    .build_call(*rts_fn, &[args[0].into()], "bsb_string_direct")
                    .map_err(|e| CodegenError::Internal(format!("{}: {:?}", name, e)))?
                    .try_as_basic_value()
                    .basic()
                    .ok_or_else(|| CodegenError::Internal(format!("{}: void", name)))?;
                Ok(Some(result))
            }
            "Data.ByteString.Builder.byteString" | "Data.ByteString.Builder.shortByteString" => {
                let rts_fn = self.functions.get(&VarId::new(1000441)).ok_or_else(|| {
                    CodegenError::Internal("bhc_lazy_bs_from_strict not declared".to_string())
                })?;
                let result = self
                    .builder()
                    .build_call(*rts_fn, &[args[0].into()], "bsb_from_strict")
                    .map_err(|e| CodegenError::Internal(format!("bsb_from_strict: {:?}", e)))?
                    .try_as_basic_value()
                    .basic()
                    .ok_or_else(|| CodegenError::Internal("bsb_from_strict: void".to_string()))?;
                Ok(Some(result))
            }
            "Data.ByteString.Builder.lazyByteString"
            | "Data.ByteString.Builder.toLazyByteString" => Ok(Some(args[0])),
            "Data.ByteString.Builder.toStrictByteString" => {
                let rts_fn = self.functions.get(&VarId::new(1000442)).ok_or_else(|| {
                    CodegenError::Internal("bhc_lazy_bs_to_strict not declared".to_string())
                })?;
                let result = self
                    .builder()
                    .build_call(*rts_fn, &[args[0].into()], "bsb_to_strict")
                    .map_err(|e| CodegenError::Internal(format!("bsb_to_strict: {:?}", e)))?
                    .try_as_basic_value()
                    .basic()
                    .ok_or_else(|| CodegenError::Internal("bsb_to_strict: void".to_string()))?;
                Ok(Some(result))
            }
            "Data.ByteString.Builder.append" | "Data.ByteString.Builder.<>" => {
                let rts_fn = self.functions.get(&VarId::new(1000448)).ok_or_else(|| {
                    CodegenError::Internal("bhc_lazy_bs_append not declared".to_string())
                })?;
                let result = self
                    .builder()
                    .build_call(*rts_fn, &[args[0].into(), args[1].into()], "bsb_append")
                    .map_err(|e| CodegenError::Internal(format!("bsb_append: {:?}", e)))?
                    .try_as_basic_value()
                    .basic()
                    .ok_or_else(|| CodegenError::Internal("bsb_append: void".to_string()))?;
                Ok(Some(result))
            }
            "Data.ByteString.Builder.hPutBuilder" => {
                let rts_fn = self.functions.get(&VarId::new(1000458)).ok_or_else(|| {
                    CodegenError::Internal("bhc_lazy_bs_h_put_str not declared".to_string())
                })?;
                self.builder()
                    .build_call(*rts_fn, &[args[0].into(), args[1].into()], "bsb_h_put")
                    .map_err(|e| CodegenError::Internal(format!("bsb_h_put: {:?}", e)))?;
                Ok(Some(self.type_mapper().ptr_type().const_null().into()))
            }

            "even" => {
                let int_val = self.coerce_to_int(args[0])?;
                let i64_type = self.type_mapper().i64_type();
                let two = i64_type.const_int(2, false);
                let rem = self
                    .builder()
                    .build_int_signed_rem(int_val, two, "even_rem")
                    .map_err(|e| CodegenError::Internal(format!("even: srem: {:?}", e)))?;
                let is_even = self
                    .builder()
                    .build_int_compare(
                        inkwell::IntPredicate::EQ,
                        rem,
                        i64_type.const_zero(),
                        "is_even",
                    )
                    .map_err(|e| CodegenError::Internal(format!("even: cmp: {:?}", e)))?;
                let tag = self
                    .builder()
                    .build_int_z_extend(is_even, i64_type, "even_tag")
                    .map_err(|e| CodegenError::Internal(format!("even: ext: {:?}", e)))?;
                self.allocate_bool_adt(tag, "even")
            }
            "odd" => {
                let int_val = self.coerce_to_int(args[0])?;
                let i64_type = self.type_mapper().i64_type();
                let two = i64_type.const_int(2, false);
                let rem = self
                    .builder()
                    .build_int_signed_rem(int_val, two, "odd_rem")
                    .map_err(|e| CodegenError::Internal(format!("odd: srem: {:?}", e)))?;
                let is_odd = self
                    .builder()
                    .build_int_compare(
                        inkwell::IntPredicate::NE,
                        rem,
                        i64_type.const_zero(),
                        "is_odd",
                    )
                    .map_err(|e| CodegenError::Internal(format!("odd: cmp: {:?}", e)))?;
                let tag = self
                    .builder()
                    .build_int_z_extend(is_odd, i64_type, "odd_tag")
                    .map_err(|e| CodegenError::Internal(format!("odd: ext: {:?}", e)))?;
                self.allocate_bool_adt(tag, "odd")
            }
            "compare" => {
                let a_int = self.coerce_to_int(args[0])?;
                let b_int = self.coerce_to_int(args[1])?;
                let i64_type = self.type_mapper().i64_type();
                let is_lt = self
                    .builder()
                    .build_int_compare(inkwell::IntPredicate::SLT, a_int, b_int, "cmp_lt")
                    .map_err(|e| CodegenError::Internal(format!("compare: slt: {:?}", e)))?;
                let is_gt = self
                    .builder()
                    .build_int_compare(inkwell::IntPredicate::SGT, a_int, b_int, "cmp_gt")
                    .map_err(|e| CodegenError::Internal(format!("compare: sgt: {:?}", e)))?;
                let tag_gt_or_eq = self
                    .builder()
                    .build_select(
                        is_gt,
                        i64_type.const_int(2, false),
                        i64_type.const_int(1, false),
                        "gt_or_eq",
                    )
                    .map_err(|e| CodegenError::Internal(format!("compare: select1: {:?}", e)))?
                    .into_int_value();
                let tag = self
                    .builder()
                    .build_select(is_lt, i64_type.const_int(0, false), tag_gt_or_eq, "cmp_tag")
                    .map_err(|e| CodegenError::Internal(format!("compare: select2: {:?}", e)))?;
                self.allocate_ordering_adt(tag.into_int_value(), "compare")
            }

            "fst" => {
                let pair_ptr = args[0].into_pointer_value();
                let fst = self.extract_adt_field(pair_ptr, 2, 0)?;
                Ok(Some(fst.into()))
            }
            "snd" => {
                let pair_ptr = args[0].into_pointer_value();
                let snd = self.extract_adt_field(pair_ptr, 2, 1)?;
                Ok(Some(snd.into()))
            }
            "succ" => {
                let int_val = self.coerce_to_int(args[0])?;
                let one = self.type_mapper().i64_type().const_int(1, false);
                let result = self
                    .builder()
                    .build_int_add(int_val, one, "succ")
                    .map_err(|e| CodegenError::Internal(format!("succ failed: {:?}", e)))?;
                Ok(Some(self.int_to_ptr(result)?.into()))
            }
            "pred" => {
                let int_val = self.coerce_to_int(args[0])?;
                let one = self.type_mapper().i64_type().const_int(1, false);
                let result = self
                    .builder()
                    .build_int_sub(int_val, one, "pred")
                    .map_err(|e| CodegenError::Internal(format!("pred failed: {:?}", e)))?;
                Ok(Some(self.int_to_ptr(result)?.into()))
            }
            "swap" => {
                let pair_ptr = args[0].into_pointer_value();
                let fst_val = self.extract_adt_field(pair_ptr, 2, 0)?;
                let snd_val = self.extract_adt_field(pair_ptr, 2, 1)?;
                self.allocate_ptr_pair_tuple(snd_val, fst_val, "swap")
            }
            "min" => {
                let a_int = self.coerce_to_int(args[0])?;
                let b_int = self.coerce_to_int(args[1])?;
                let cmp = self
                    .builder()
                    .build_int_compare(inkwell::IntPredicate::SLT, a_int, b_int, "min_cmp")
                    .map_err(|e| CodegenError::Internal(format!("min cmp: {:?}", e)))?;
                let result = self
                    .builder()
                    .build_select(cmp, a_int, b_int, "min_sel")
                    .map_err(|e| CodegenError::Internal(format!("min sel: {:?}", e)))?;
                Ok(Some(self.int_to_ptr(result.into_int_value())?.into()))
            }
            "max" => {
                let a_int = self.coerce_to_int(args[0])?;
                let b_int = self.coerce_to_int(args[1])?;
                let cmp = self
                    .builder()
                    .build_int_compare(inkwell::IntPredicate::SGT, a_int, b_int, "max_cmp")
                    .map_err(|e| CodegenError::Internal(format!("max cmp: {:?}", e)))?;
                let result = self
                    .builder()
                    .build_select(cmp, a_int, b_int, "max_sel")
                    .map_err(|e| CodegenError::Internal(format!("max sel: {:?}", e)))?;
                Ok(Some(self.int_to_ptr(result.into_int_value())?.into()))
            }
            "subtract" => {
                // subtract x y = y - x (args flipped)
                let x_int = self.coerce_to_int(args[0])?;
                let y_int = self.coerce_to_int(args[1])?;
                let result = self
                    .builder()
                    .build_int_sub(y_int, x_int, "subtract")
                    .map_err(|e| CodegenError::Internal(format!("subtract: {:?}", e)))?;
                Ok(Some(self.int_to_ptr(result)?.into()))
            }
            "const" => {
                // const x y = x — return first argument
                Ok(Some(args[0]))
            }
            "flip" => {
                // flip f x y = f y x
                // args[0] = f (closure), args[1] = x, args[2] = y
                let f_ptr = args[0].into_pointer_value();
                let fn_ptr = self.extract_closure_fn_ptr(f_ptr)?;
                // Flat 3-arg call: fn(closure_env, y, x)
                let fn_type =
                    ptr_type.fn_type(&[ptr_type.into(), ptr_type.into(), ptr_type.into()], false);
                let result = self
                    .builder()
                    .build_indirect_call(
                        fn_type,
                        fn_ptr,
                        &[f_ptr.into(), args[2].into(), args[1].into()],
                        "flip_direct",
                    )
                    .map_err(|e| CodegenError::Internal(format!("flip_direct: {:?}", e)))?
                    .try_as_basic_value()
                    .basic()
                    .ok_or_else(|| CodegenError::Internal("flip_direct: void".to_string()))?;
                Ok(Some(result))
            }

            "otherwise" => {
                // otherwise = True (tagged-int-as-pointer, 1)
                Ok(Some(
                    self.type_mapper().i64_type().const_int(1, false).into(),
                ))
            }
            // E.63: DeepSeq stubs (no-ops in strict runtime)
            "rnf" => {
                // rnf: evaluate arg (already done), return unit
                Ok(Some(ptr_type.const_null().into()))
            }
            "deepseq" => {
                // deepseq: evaluate first arg (already done), return second
                Ok(Some(args[1]))
            }
            "force" => {
                // force: identity — value already in normal form
                Ok(Some(args[0]))
            }
            // GHC.Generics from/to — identity passthrough in direct mode
            "from" | "to" => Ok(Some(args[0])),

            "toUpper" | "toLower" => {
                let rts_id = if name == "toUpper" { 1000036 } else { 1000037 };
                let int_val = self.coerce_to_int(args[0])?;
                let i32_type = self.type_mapper().i32_type();
                let char_val = self
                    .builder()
                    .build_int_truncate(int_val, i32_type, "char_val")
                    .map_err(|e| {
                        CodegenError::Internal(format!("{}: truncate failed: {:?}", name, e))
                    })?;
                let rts_fn = self.functions.get(&VarId::new(rts_id)).ok_or_else(|| {
                    CodegenError::Internal(format!("char conv {} not declared", name))
                })?;
                let result = self
                    .builder()
                    .build_call(*rts_fn, &[char_val.into()], name)
                    .map_err(|e| CodegenError::Internal(format!("{} call failed: {:?}", name, e)))?
                    .try_as_basic_value()
                    .basic()
                    .ok_or_else(|| CodegenError::Internal(format!("{}: returned void", name)))?;
                let u32_val = result.into_int_value();
                let extended = self
                    .builder()
                    .build_int_z_extend(u32_val, self.type_mapper().i64_type(), "char_ext")
                    .map_err(|e| {
                        CodegenError::Internal(format!("{}: extend failed: {:?}", name, e))
                    })?;
                Ok(Some(self.int_to_ptr(extended)?.into()))
            }

            // E.37: Data.Char predicates as first-class functions
            "isAlpha" | "isDigit" | "isAlphaNum" | "isSpace" | "isUpper" | "isLower"
            | "isPrint" | "isAscii" | "isControl" | "isHexDigit" | "isLetter" | "isNumber"
            | "isPunctuation" | "isSymbol" => {
                let rts_id = match name {
                    "isAlpha" => 1000030,
                    "isDigit" => 1000031,
                    "isAlphaNum" => 1000032,
                    "isSpace" => 1000033,
                    "isUpper" => 1000034,
                    "isLower" => 1000035,
                    "isPrint" => 1000038,
                    "isAscii" => 1000039,
                    "isControl" => 1000040,
                    "isHexDigit" => 1000041,
                    "isLetter" => 1000042,
                    "isNumber" => 1000043,
                    "isPunctuation" => 1000044,
                    "isSymbol" => 1000045,
                    _ => unreachable!(),
                };
                let int_val = self.coerce_to_int(args[0])?;
                let i32_type = self.type_mapper().i32_type();
                let char_val = self
                    .builder()
                    .build_int_truncate(int_val, i32_type, "char_val")
                    .map_err(|e| {
                        CodegenError::Internal(format!("{}: truncate failed: {:?}", name, e))
                    })?;
                let rts_fn = self.functions.get(&VarId::new(rts_id)).ok_or_else(|| {
                    CodegenError::Internal(format!("char pred {} not declared", name))
                })?;
                let result = self
                    .builder()
                    .build_call(*rts_fn, &[char_val.into()], name)
                    .map_err(|e| CodegenError::Internal(format!("{} call failed: {:?}", name, e)))?
                    .try_as_basic_value()
                    .basic()
                    .ok_or_else(|| CodegenError::Internal(format!("{}: returned void", name)))?;
                let bool_val = result.into_int_value();
                let tag = self
                    .builder()
                    .build_int_z_extend(bool_val, self.type_mapper().i64_type(), "bool_tag")
                    .map_err(|e| {
                        CodegenError::Internal(format!("{}: extend failed: {:?}", name, e))
                    })?;
                self.allocate_bool_adt(tag, name)
            }

            // ord/chr are identity (both Int and Char are i64)
            "ord" | "chr" => Ok(Some(args[0])),

            "digitToInt" => {
                let int_val = self.coerce_to_int(args[0])?;
                let i32_type = self.type_mapper().i32_type();
                let char_val = self
                    .builder()
                    .build_int_truncate(int_val, i32_type, "char_val")
                    .map_err(|e| {
                        CodegenError::Internal(format!("digitToInt: truncate failed: {:?}", e))
                    })?;
                let rts_fn = self.functions.get(&VarId::new(1000046)).ok_or_else(|| {
                    CodegenError::Internal("bhc_char_digit_to_int not declared".to_string())
                })?;
                let result = self
                    .builder()
                    .build_call(*rts_fn, &[char_val.into()], "digit_to_int")
                    .map_err(|e| {
                        CodegenError::Internal(format!("digitToInt call failed: {:?}", e))
                    })?
                    .try_as_basic_value()
                    .basic()
                    .ok_or_else(|| {
                        CodegenError::Internal("digitToInt: returned void".to_string())
                    })?;
                let i32_val = result.into_int_value();
                let extended = self
                    .builder()
                    .build_int_s_extend(i32_val, self.type_mapper().i64_type(), "digit_ext")
                    .map_err(|e| {
                        CodegenError::Internal(format!("digitToInt: extend failed: {:?}", e))
                    })?;
                Ok(Some(self.int_to_ptr(extended)?.into()))
            }

            // E.54: Enum/Bounded as first-class values
            "minBound" => self.lower_builtin_min_bound(),
            "maxBound" => self.lower_builtin_max_bound(),

            "intToDigit" => {
                let int_val = self.coerce_to_int(args[0])?;
                let i32_type = self.type_mapper().i32_type();
                let truncated = self
                    .builder()
                    .build_int_truncate(int_val, i32_type, "int_val")
                    .map_err(|e| {
                        CodegenError::Internal(format!("intToDigit: truncate failed: {:?}", e))
                    })?;
                let rts_fn = self.functions.get(&VarId::new(1000047)).ok_or_else(|| {
                    CodegenError::Internal("bhc_char_int_to_digit not declared".to_string())
                })?;
                let result = self
                    .builder()
                    .build_call(*rts_fn, &[truncated.into()], "int_to_digit")
                    .map_err(|e| {
                        CodegenError::Internal(format!("intToDigit call failed: {:?}", e))
                    })?
                    .try_as_basic_value()
                    .basic()
                    .ok_or_else(|| {
                        CodegenError::Internal("intToDigit: returned void".to_string())
                    })?;
                let u32_val = result.into_int_value();
                let extended = self
                    .builder()
                    .build_int_z_extend(u32_val, self.type_mapper().i64_type(), "digit_char_ext")
                    .map_err(|e| {
                        CodegenError::Internal(format!("intToDigit: extend failed: {:?}", e))
                    })?;
                Ok(Some(self.int_to_ptr(extended)?.into()))
            }

            // Async exception masking
            "getMaskingState" => self.lower_builtin_get_masking_state(),
            "mask_" => {
                // mask_ with pre-lowered action arg
                let action_closure = args[0].into_pointer_value();
                let action_fn = self.extract_closure_fn_ptr(action_closure)?;
                let rts_fn = self
                    .functions
                    .get(&VarId::new(1000083))
                    .ok_or_else(|| CodegenError::Internal("bhc_mask not declared".to_string()))?;
                let result = self
                    .builder()
                    .build_call(
                        *rts_fn,
                        &[action_fn.into(), action_closure.into()],
                        "mask_result",
                    )
                    .map_err(|e| CodegenError::Internal(format!("mask_ call: {:?}", e)))?
                    .try_as_basic_value()
                    .basic()
                    .ok_or_else(|| CodegenError::Internal("mask_: void".to_string()))?;
                Ok(Some(result))
            }
            "uninterruptibleMask_" => {
                let action_closure = args[0].into_pointer_value();
                let action_fn = self.extract_closure_fn_ptr(action_closure)?;
                let rts_fn = self.functions.get(&VarId::new(1000086)).ok_or_else(|| {
                    CodegenError::Internal("bhc_uninterruptible_mask not declared".to_string())
                })?;
                let result = self
                    .builder()
                    .build_call(
                        *rts_fn,
                        &[action_fn.into(), action_closure.into()],
                        "uninterruptible_mask_result",
                    )
                    .map_err(|e| {
                        CodegenError::Internal(format!("uninterruptibleMask_ call: {:?}", e))
                    })?
                    .try_as_basic_value()
                    .basic()
                    .ok_or_else(|| {
                        CodegenError::Internal("uninterruptibleMask_: void".to_string())
                    })?;
                Ok(Some(result))
            }
            "mask" => {
                let action_closure = args[0].into_pointer_value();
                let action_fn = self.extract_closure_fn_ptr(action_closure)?;
                let rts_fn = self.functions.get(&VarId::new(1000085)).ok_or_else(|| {
                    CodegenError::Internal("bhc_mask_with_restore not declared".to_string())
                })?;
                let result = self
                    .builder()
                    .build_call(
                        *rts_fn,
                        &[action_fn.into(), action_closure.into()],
                        "mask_restore_result",
                    )
                    .map_err(|e| CodegenError::Internal(format!("mask call: {:?}", e)))?
                    .try_as_basic_value()
                    .basic()
                    .ok_or_else(|| CodegenError::Internal("mask: void".to_string()))?;
                Ok(Some(result))
            }
            "uninterruptibleMask" => {
                let action_closure = args[0].into_pointer_value();
                let action_fn = self.extract_closure_fn_ptr(action_closure)?;
                let rts_fn = self.functions.get(&VarId::new(1000087)).ok_or_else(|| {
                    CodegenError::Internal(
                        "bhc_uninterruptible_mask_with_restore not declared".to_string(),
                    )
                })?;
                let result = self
                    .builder()
                    .build_call(
                        *rts_fn,
                        &[action_fn.into(), action_closure.into()],
                        "uninterruptible_mask_result",
                    )
                    .map_err(|e| {
                        CodegenError::Internal(format!("uninterruptibleMask call: {:?}", e))
                    })?
                    .try_as_basic_value()
                    .basic()
                    .ok_or_else(|| {
                        CodegenError::Internal("uninterruptibleMask: void".to_string())
                    })?;
                Ok(Some(result))
            }

            _ => {
                // Check for field selector pattern: $sel_N
                if let Some(index_str) = name.strip_prefix("$sel_") {
                    if let Ok(field_index) = index_str.parse::<u32>() {
                        let tuple_ptr = args[0].into_pointer_value();
                        let tm = self.type_mapper();
                        let field_offset = 1 + field_index;
                        let field_ptr = unsafe {
                            self.builder()
                                .build_gep(
                                    tm.i64_type(),
                                    tuple_ptr,
                                    &[tm.i64_type().const_int(field_offset as u64, false)],
                                    &format!("sel_field_ptr_{}", field_index),
                                )
                                .map_err(|e| {
                                    CodegenError::Internal(format!(
                                        "$sel_{} gep failed: {:?}",
                                        field_index, e
                                    ))
                                })?
                        };
                        let field_val = self
                            .builder()
                            .build_load(
                                tm.ptr_type(),
                                field_ptr,
                                &format!("sel_field_{}", field_index),
                            )
                            .map_err(|e| {
                                CodegenError::Internal(format!(
                                    "$sel_{} load failed: {:?}",
                                    field_index, e
                                ))
                            })?;
                        return Ok(Some(field_val));
                    }
                }
                // Fallthrough: try container operations that are used as
                // first-class values or with arity 0
                if name.starts_with("Data.Sequence.")
                    || name.starts_with("Data.Map.")
                    || name.starts_with("Data.Set.")
                    || name.starts_with("Data.IntMap.")
                    || name.starts_with("Data.IntSet.")
                {
                    self.lower_builtin_container_direct(name, args)
                } else {
                    // For builtins that are handled in lower_builtin but not
                    // lower_builtin_direct, generate a stub that calls bhc_error
                    // at runtime rather than failing compilation.
                    let stub_fn = self.get_or_create_stub_function(name)?;
                    let null_env = self.type_mapper().ptr_type().const_null();
                    let call_result = self
                        .builder()
                        .build_call(stub_fn, &[null_env.into()], "stub_call")
                        .map_err(|e| CodegenError::Internal(format!("stub call: {:?}", e)))?;
                    if let Some(ret_val) = call_result.try_as_basic_value().basic() {
                        Ok(Some(ret_val))
                    } else {
                        Ok(Some(self.type_mapper().ptr_type().const_null().into()))
                    }
                }
            }
        }
    }

    /// Handle container builtins used as first-class values in lower_builtin_direct.
    /// These have pre-evaluated BasicValueEnum args rather than &Expr args.
    fn lower_builtin_container_direct(
        &mut self,
        name: &str,
        args: &[BasicValueEnum<'ctx>],
    ) -> CodegenResult<Option<BasicValueEnum<'ctx>>> {
        let tm = self.type_mapper();
        let _ptr_type = tm.ptr_type();

        match name {
            // Data.Sequence
            "Data.Sequence.empty" => self.lower_builtin_seq_empty(),
            "Data.Sequence.singleton" => {
                let rts_fn = self.functions.get(&VarId::new(1000801)).ok_or_else(|| {
                    CodegenError::Internal("bhc_seq_singleton not declared".to_string())
                })?;
                let result = self
                    .builder()
                    .build_call(*rts_fn, &[args[0].into()], "seq_sing")
                    .map_err(|e| CodegenError::Internal(format!("seq_sing: {:?}", e)))?
                    .try_as_basic_value()
                    .basic()
                    .ok_or_else(|| CodegenError::Internal("seq_sing: void".to_string()))?;
                Ok(Some(result))
            }
            "Data.Sequence.null" => {
                let rts_fn = self.functions.get(&VarId::new(1000802)).ok_or_else(|| {
                    CodegenError::Internal("bhc_seq_null not declared".to_string())
                })?;
                let result = self
                    .builder()
                    .build_call(*rts_fn, &[args[0].into()], "seq_null")
                    .map_err(|e| CodegenError::Internal(format!("seq_null: {:?}", e)))?
                    .try_as_basic_value()
                    .basic()
                    .ok_or_else(|| CodegenError::Internal("seq_null: void".to_string()))?;
                let int_val = result.into_int_value();
                self.allocate_bool_adt(int_val, "seq_null")
            }
            "Data.Sequence.length" => {
                let rts_fn = self.functions.get(&VarId::new(1000803)).ok_or_else(|| {
                    CodegenError::Internal("bhc_seq_length not declared".to_string())
                })?;
                let result = self
                    .builder()
                    .build_call(*rts_fn, &[args[0].into()], "seq_len")
                    .map_err(|e| CodegenError::Internal(format!("seq_len: {:?}", e)))?
                    .try_as_basic_value()
                    .basic()
                    .ok_or_else(|| CodegenError::Internal("seq_len: void".to_string()))?;
                let int_val = result.into_int_value();
                Ok(Some(self.int_to_ptr(int_val)?.into()))
            }
            "Data.Sequence.index" => {
                let seq_ptr = args[0];
                let idx = self.coerce_to_int(args[1])?;
                let rts_fn = self.functions.get(&VarId::new(1000804)).ok_or_else(|| {
                    CodegenError::Internal("bhc_seq_index not declared".to_string())
                })?;
                let result = self
                    .builder()
                    .build_call(*rts_fn, &[seq_ptr.into(), idx.into()], "seq_idx")
                    .map_err(|e| CodegenError::Internal(format!("seq_idx: {:?}", e)))?
                    .try_as_basic_value()
                    .basic()
                    .ok_or_else(|| CodegenError::Internal("seq_idx: void".to_string()))?;
                Ok(Some(result))
            }
            "Data.Sequence.reverse" => {
                let rts_fn = self.functions.get(&VarId::new(1000811)).ok_or_else(|| {
                    CodegenError::Internal("bhc_seq_reverse not declared".to_string())
                })?;
                let result = self
                    .builder()
                    .build_call(*rts_fn, &[args[0].into()], "seq_rev")
                    .map_err(|e| CodegenError::Internal(format!("seq_rev: {:?}", e)))?
                    .try_as_basic_value()
                    .basic()
                    .ok_or_else(|| CodegenError::Internal("seq_rev: void".to_string()))?;
                Ok(Some(result))
            }
            "Data.Sequence.<|" => {
                let rts_fn = self.functions.get(&VarId::new(1000806)).ok_or_else(|| {
                    CodegenError::Internal("bhc_seq_cons not declared".to_string())
                })?;
                let result = self
                    .builder()
                    .build_call(*rts_fn, &[args[0].into(), args[1].into()], "seq_cons")
                    .map_err(|e| CodegenError::Internal(format!("seq_cons: {:?}", e)))?
                    .try_as_basic_value()
                    .basic()
                    .ok_or_else(|| CodegenError::Internal("seq_cons: void".to_string()))?;
                Ok(Some(result))
            }
            "Data.Sequence.|>" => {
                let rts_fn = self.functions.get(&VarId::new(1000807)).ok_or_else(|| {
                    CodegenError::Internal("bhc_seq_snoc not declared".to_string())
                })?;
                let result = self
                    .builder()
                    .build_call(*rts_fn, &[args[0].into(), args[1].into()], "seq_snoc")
                    .map_err(|e| CodegenError::Internal(format!("seq_snoc: {:?}", e)))?
                    .try_as_basic_value()
                    .basic()
                    .ok_or_else(|| CodegenError::Internal("seq_snoc: void".to_string()))?;
                Ok(Some(result))
            }
            "Data.Sequence.><" => {
                let rts_fn = self.functions.get(&VarId::new(1000808)).ok_or_else(|| {
                    CodegenError::Internal("bhc_seq_append not declared".to_string())
                })?;
                let result = self
                    .builder()
                    .build_call(*rts_fn, &[args[0].into(), args[1].into()], "seq_app")
                    .map_err(|e| CodegenError::Internal(format!("seq_app: {:?}", e)))?
                    .try_as_basic_value()
                    .basic()
                    .ok_or_else(|| CodegenError::Internal("seq_app: void".to_string()))?;
                Ok(Some(result))
            }
            "Data.Sequence.take" => {
                let n = self.coerce_to_int(args[0])?;
                let rts_fn = self.functions.get(&VarId::new(1000809)).ok_or_else(|| {
                    CodegenError::Internal("bhc_seq_take not declared".to_string())
                })?;
                let result = self
                    .builder()
                    .build_call(*rts_fn, &[n.into(), args[1].into()], "seq_take")
                    .map_err(|e| CodegenError::Internal(format!("seq_take: {:?}", e)))?
                    .try_as_basic_value()
                    .basic()
                    .ok_or_else(|| CodegenError::Internal("seq_take: void".to_string()))?;
                Ok(Some(result))
            }
            "Data.Sequence.drop" => {
                let n = self.coerce_to_int(args[0])?;
                let rts_fn = self.functions.get(&VarId::new(1000810)).ok_or_else(|| {
                    CodegenError::Internal("bhc_seq_drop not declared".to_string())
                })?;
                let result = self
                    .builder()
                    .build_call(*rts_fn, &[n.into(), args[1].into()], "seq_drop")
                    .map_err(|e| CodegenError::Internal(format!("seq_drop: {:?}", e)))?
                    .try_as_basic_value()
                    .basic()
                    .ok_or_else(|| CodegenError::Internal("seq_drop: void".to_string()))?;
                Ok(Some(result))
            }
            "Data.Sequence.update" => {
                let idx = self.coerce_to_int(args[0])?;
                let rts_fn = self.functions.get(&VarId::new(1000812)).ok_or_else(|| {
                    CodegenError::Internal("bhc_seq_update not declared".to_string())
                })?;
                let result = self
                    .builder()
                    .build_call(
                        *rts_fn,
                        &[idx.into(), args[1].into(), args[2].into()],
                        "seq_upd",
                    )
                    .map_err(|e| CodegenError::Internal(format!("seq_upd: {:?}", e)))?
                    .try_as_basic_value()
                    .basic()
                    .ok_or_else(|| CodegenError::Internal("seq_upd: void".to_string()))?;
                Ok(Some(result))
            }
            "Data.Sequence.insertAt" => {
                let idx = self.coerce_to_int(args[0])?;
                let rts_fn = self.functions.get(&VarId::new(1000813)).ok_or_else(|| {
                    CodegenError::Internal("bhc_seq_insert_at not declared".to_string())
                })?;
                let result = self
                    .builder()
                    .build_call(
                        *rts_fn,
                        &[idx.into(), args[1].into(), args[2].into()],
                        "seq_ins",
                    )
                    .map_err(|e| CodegenError::Internal(format!("seq_ins: {:?}", e)))?
                    .try_as_basic_value()
                    .basic()
                    .ok_or_else(|| CodegenError::Internal("seq_ins: void".to_string()))?;
                Ok(Some(result))
            }
            "Data.Sequence.deleteAt" => {
                let idx = self.coerce_to_int(args[0])?;
                let rts_fn = self.functions.get(&VarId::new(1000814)).ok_or_else(|| {
                    CodegenError::Internal("bhc_seq_delete_at not declared".to_string())
                })?;
                let result = self
                    .builder()
                    .build_call(*rts_fn, &[idx.into(), args[1].into()], "seq_del")
                    .map_err(|e| CodegenError::Internal(format!("seq_del: {:?}", e)))?
                    .try_as_basic_value()
                    .basic()
                    .ok_or_else(|| CodegenError::Internal("seq_del: void".to_string()))?;
                Ok(Some(result))
            }
            "Data.Sequence.replicate" => {
                let n = self.coerce_to_int(args[0])?;
                let rts_fn = self.functions.get(&VarId::new(1000817)).ok_or_else(|| {
                    CodegenError::Internal("bhc_seq_replicate not declared".to_string())
                })?;
                let result = self
                    .builder()
                    .build_call(*rts_fn, &[n.into(), args[1].into()], "seq_rep")
                    .map_err(|e| CodegenError::Internal(format!("seq_rep: {:?}", e)))?
                    .try_as_basic_value()
                    .basic()
                    .ok_or_else(|| CodegenError::Internal("seq_rep: void".to_string()))?;
                Ok(Some(result))
            }
            // Data.Map, Data.Set, etc. — delegate to existing impls
            "Data.Map.empty" => self.lower_builtin_map_empty(),
            "Data.Set.empty" => self.lower_builtin_set_empty(),
            "Data.IntMap.empty" | "Data.IntSet.empty" => {
                let rts_fn = self
                    .functions
                    .get(&VarId::new(if name == "Data.IntMap.empty" {
                        1000140
                    } else {
                        1000145
                    }))
                    .ok_or_else(|| CodegenError::Internal(format!("{} not declared", name)))?;
                let result = self
                    .builder()
                    .build_call(*rts_fn, &[], &name.replace('.', "_"))
                    .map_err(|e| CodegenError::Internal(format!("{}: {:?}", name, e)))?
                    .try_as_basic_value()
                    .basic()
                    .ok_or_else(|| CodegenError::Internal(format!("{}: void", name)))?;
                Ok(Some(result))
            }
            _ => Err(CodegenError::Internal(format!(
                "lower_builtin_direct: unhandled container builtin '{}'",
                name
            ))),
        }
    }

    /// Execute a primitive operation directly on LLVM values.
    fn lower_primop_direct(
        &mut self,
        op: PrimOp,
        args: &[BasicValueEnum<'ctx>],
    ) -> CodegenResult<BasicValueEnum<'ctx>> {
        let tm = self.type_mapper();

        match op {
            // Binary arithmetic operations
            PrimOp::Add
            | PrimOp::Sub
            | PrimOp::Mul
            | PrimOp::Div
            | PrimOp::Mod
            | PrimOp::Rem
            | PrimOp::Quot => {
                // Unbox arguments
                let lhs = self.ptr_to_int(args[0].into_pointer_value())?;
                let rhs = self.ptr_to_int(args[1].into_pointer_value())?;

                let result = match op {
                    PrimOp::Add => self.builder().build_int_add(lhs, rhs, "add"),
                    PrimOp::Sub => self.builder().build_int_sub(lhs, rhs, "sub"),
                    PrimOp::Mul => self.builder().build_int_mul(lhs, rhs, "mul"),
                    PrimOp::Div | PrimOp::Quot => {
                        self.builder().build_int_signed_div(lhs, rhs, "div")
                    }
                    PrimOp::Mod | PrimOp::Rem => {
                        self.builder().build_int_signed_rem(lhs, rhs, "rem")
                    }
                    _ => unreachable!(),
                }
                .map_err(|e| {
                    CodegenError::Internal(format!("failed to build arithmetic: {:?}", e))
                })?;

                Ok(result.into())
            }

            // Comparison operations
            PrimOp::Eq | PrimOp::Ne | PrimOp::Lt | PrimOp::Le | PrimOp::Gt | PrimOp::Ge => {
                let lhs = self.ptr_to_int(args[0].into_pointer_value())?;
                let rhs = self.ptr_to_int(args[1].into_pointer_value())?;

                let predicate = match op {
                    PrimOp::Eq => inkwell::IntPredicate::EQ,
                    PrimOp::Ne => inkwell::IntPredicate::NE,
                    PrimOp::Lt => inkwell::IntPredicate::SLT,
                    PrimOp::Le => inkwell::IntPredicate::SLE,
                    PrimOp::Gt => inkwell::IntPredicate::SGT,
                    PrimOp::Ge => inkwell::IntPredicate::SGE,
                    _ => unreachable!(),
                };

                let cmp = self
                    .builder()
                    .build_int_compare(predicate, lhs, rhs, "cmp")
                    .map_err(|e| {
                        CodegenError::Internal(format!("failed to build compare: {:?}", e))
                    })?;

                let result = self
                    .builder()
                    .build_int_z_extend(cmp, tm.i64_type(), "cmp_ext")
                    .map_err(|e| CodegenError::Internal(format!("failed to extend: {:?}", e)))?;

                Ok(result.into())
            }

            // Boolean operations - Bool is an ADT where False=tag 0, True=tag 1
            PrimOp::And => {
                // Extract tags from Bool ADT pointers
                let lhs_tag = self.extract_adt_tag(args[0].into_pointer_value())?;
                let rhs_tag = self.extract_adt_tag(args[1].into_pointer_value())?;
                // AND the tags (0 & anything = 0, 1 & 1 = 1)
                let result = self
                    .builder()
                    .build_and(lhs_tag, rhs_tag, "and")
                    .map_err(|e| CodegenError::Internal(format!("failed to build and: {:?}", e)))?;
                Ok(result.into())
            }

            PrimOp::Or => {
                // Extract tags from Bool ADT pointers
                let lhs_tag = self.extract_adt_tag(args[0].into_pointer_value())?;
                let rhs_tag = self.extract_adt_tag(args[1].into_pointer_value())?;
                // OR the tags (0 | 0 = 0, anything | 1 = 1)
                let result = self
                    .builder()
                    .build_or(lhs_tag, rhs_tag, "or")
                    .map_err(|e| CodegenError::Internal(format!("failed to build or: {:?}", e)))?;
                Ok(result.into())
            }

            // Unary operations
            PrimOp::Negate => {
                let val = self.ptr_to_int(args[0].into_pointer_value())?;
                let result = self
                    .builder()
                    .build_int_neg(val, "neg")
                    .map_err(|e| CodegenError::Internal(format!("failed to build neg: {:?}", e)))?;
                Ok(result.into())
            }

            PrimOp::Abs => {
                let val = self.ptr_to_int(args[0].into_pointer_value())?;
                let neg = self
                    .builder()
                    .build_int_neg(val, "neg")
                    .map_err(|e| CodegenError::Internal(format!("failed to build neg: {:?}", e)))?;
                let is_neg = self
                    .builder()
                    .build_int_compare(
                        inkwell::IntPredicate::SLT,
                        val,
                        tm.i64_type().const_zero(),
                        "is_neg",
                    )
                    .map_err(|e| {
                        CodegenError::Internal(format!("failed to build compare: {:?}", e))
                    })?;
                let result = self
                    .builder()
                    .build_select(is_neg, neg, val, "abs")
                    .map_err(|e| {
                        CodegenError::Internal(format!("failed to build select: {:?}", e))
                    })?;
                Ok(result)
            }

            PrimOp::Signum => {
                let val = self.ptr_to_int(args[0].into_pointer_value())?;
                let zero = tm.i64_type().const_zero();
                let one = tm.i64_type().const_int(1, false);
                let neg_one = tm.i64_type().const_int(-1i64 as u64, true);

                let is_neg = self
                    .builder()
                    .build_int_compare(inkwell::IntPredicate::SLT, val, zero, "is_neg")
                    .map_err(|e| CodegenError::Internal(format!("failed: {:?}", e)))?;
                let is_pos = self
                    .builder()
                    .build_int_compare(inkwell::IntPredicate::SGT, val, zero, "is_pos")
                    .map_err(|e| CodegenError::Internal(format!("failed: {:?}", e)))?;

                let tmp = self
                    .builder()
                    .build_select(is_neg, neg_one, zero, "tmp")
                    .map_err(|e| CodegenError::Internal(format!("failed: {:?}", e)))?;
                let result = self
                    .builder()
                    .build_select(is_pos, one, tmp.into_int_value(), "signum")
                    .map_err(|e| CodegenError::Internal(format!("failed: {:?}", e)))?;
                Ok(result)
            }

            PrimOp::Not => {
                let val = self.ptr_to_int(args[0].into_pointer_value())?;
                let is_zero = self
                    .builder()
                    .build_int_compare(
                        inkwell::IntPredicate::EQ,
                        val,
                        tm.i64_type().const_zero(),
                        "is_zero",
                    )
                    .map_err(|e| CodegenError::Internal(format!("failed: {:?}", e)))?;
                let result = self
                    .builder()
                    .build_int_z_extend(is_zero, tm.i64_type(), "not")
                    .map_err(|e| CodegenError::Internal(format!("failed: {:?}", e)))?;
                Ok(result.into())
            }

            // Bitwise operations
            PrimOp::BitAnd => {
                let lhs = self.ptr_to_int(args[0].into_pointer_value())?;
                let rhs = self.ptr_to_int(args[1].into_pointer_value())?;
                let result = self
                    .builder()
                    .build_and(lhs, rhs, "bitand")
                    .map_err(|e| CodegenError::Internal(format!("failed: {:?}", e)))?;
                Ok(result.into())
            }

            PrimOp::BitOr => {
                let lhs = self.ptr_to_int(args[0].into_pointer_value())?;
                let rhs = self.ptr_to_int(args[1].into_pointer_value())?;
                let result = self
                    .builder()
                    .build_or(lhs, rhs, "bitor")
                    .map_err(|e| CodegenError::Internal(format!("failed: {:?}", e)))?;
                Ok(result.into())
            }

            PrimOp::BitXor => {
                let lhs = self.ptr_to_int(args[0].into_pointer_value())?;
                let rhs = self.ptr_to_int(args[1].into_pointer_value())?;
                let result = self
                    .builder()
                    .build_xor(lhs, rhs, "bitxor")
                    .map_err(|e| CodegenError::Internal(format!("failed: {:?}", e)))?;
                Ok(result.into())
            }

            PrimOp::ShiftL => {
                let val = self.ptr_to_int(args[0].into_pointer_value())?;
                let amt = self.ptr_to_int(args[1].into_pointer_value())?;
                let result = self
                    .builder()
                    .build_left_shift(val, amt, "shl")
                    .map_err(|e| CodegenError::Internal(format!("failed: {:?}", e)))?;
                Ok(result.into())
            }

            PrimOp::ShiftR => {
                let val = self.ptr_to_int(args[0].into_pointer_value())?;
                let amt = self.ptr_to_int(args[1].into_pointer_value())?;
                let result = self
                    .builder()
                    .build_right_shift(val, amt, true, "shr")
                    .map_err(|e| CodegenError::Internal(format!("failed: {:?}", e)))?;
                Ok(result.into())
            }

            PrimOp::Complement => {
                let val = self.ptr_to_int(args[0].into_pointer_value())?;
                let result = self
                    .builder()
                    .build_not(val, "complement")
                    .map_err(|e| CodegenError::Internal(format!("failed: {:?}", e)))?;
                Ok(result.into())
            }
        }
    }

    /// Coerce a value to an integer, unboxing if needed.
    fn coerce_to_int(&self, value: BasicValueEnum<'ctx>) -> CodegenResult<IntValue<'ctx>> {
        match value {
            BasicValueEnum::IntValue(i) => Ok(i),
            BasicValueEnum::PointerValue(p) => self.ptr_to_int(p),
            BasicValueEnum::FloatValue(f) => {
                // Cast float bits to int
                let bits = self
                    .builder()
                    .build_bit_cast(f, self.type_mapper().i64_type(), "float_to_int")
                    .map_err(|e| CodegenError::Internal(format!("float to int failed: {:?}", e)))?;
                Ok(bits.into_int_value())
            }
            _ => Err(CodegenError::TypeError("cannot coerce to int".to_string())),
        }
    }

    /// Coerce a value to f64, unboxing if needed.
    fn coerce_to_f64(&self, value: BasicValueEnum<'ctx>) -> CodegenResult<FloatValue<'ctx>> {
        match value {
            BasicValueEnum::FloatValue(f) => {
                // A 32-bit float (e.g. a `Float`-typed literal flowing into a
                // Double context) must be widened — the RTS math functions all
                // take f64.
                let f64_ty = self.type_mapper().f64_type();
                if f.get_type() == f64_ty {
                    Ok(f)
                } else {
                    self.builder()
                        .build_float_ext(f, f64_ty, "f32_to_f64")
                        .map_err(|e| CodegenError::Internal(format!("float widen failed: {:?}", e)))
                }
            }
            BasicValueEnum::IntValue(i) => {
                // A raw integer reaching a Double context is a genuine integer
                // (e.g. a `fromIntegral` result — f64 values are `FloatValue` or
                // boxed pointers), so convert it with `sitofp` rather than
                // reinterpreting its bits.
                let f = self
                    .builder()
                    .build_signed_int_to_float(i, self.type_mapper().f64_type(), "sitofp")
                    .map_err(|e| CodegenError::Internal(format!("int to float failed: {:?}", e)))?;
                Ok(f)
            }
            BasicValueEnum::PointerValue(p) => {
                // Unbox: ptr -> i64 -> f64 bits
                let int_val = self.ptr_to_int(p)?;
                let f = self
                    .builder()
                    .build_bit_cast(int_val, self.type_mapper().f64_type(), "ptr_to_float")
                    .map_err(|e| CodegenError::Internal(format!("ptr to float failed: {:?}", e)))?;
                Ok(f.into_float_value())
            }
            _ => Err(CodegenError::TypeError("cannot coerce to f64".to_string())),
        }
    }

    /// Convert an integer to a pointer (boxing).
    fn int_to_ptr(&self, int_val: IntValue<'ctx>) -> CodegenResult<PointerValue<'ctx>> {
        self.builder()
            .build_int_to_ptr(int_val, self.type_mapper().ptr_type(), "box_int")
            .map_err(|e| CodegenError::Internal(format!("failed to box int: {:?}", e)))
    }

    /// Convert a float to a pointer (boxing via bit cast).
    fn f64_to_ptr(&self, float_val: FloatValue<'ctx>) -> CodegenResult<PointerValue<'ctx>> {
        let bits = self
            .builder()
            .build_bit_cast(float_val, self.type_mapper().i64_type(), "float_bits")
            .map_err(|e| CodegenError::Internal(format!("failed to cast float: {:?}", e)))?
            .into_int_value();
        self.builder()
            .build_int_to_ptr(bits, self.type_mapper().ptr_type(), "box_float")
            .map_err(|e| CodegenError::Internal(format!("failed to box float: {:?}", e)))
    }

    /// Convert a pointer to an integer (unbox).
    fn ptr_to_int(&self, ptr: PointerValue<'ctx>) -> CodegenResult<IntValue<'ctx>> {
        self.builder()
            .build_ptr_to_int(ptr, self.type_mapper().i64_type(), "unbox")
            .map_err(|e| CodegenError::Internal(format!("failed to unbox: {:?}", e)))
    }

    /// Check if an expression is a saturated primitive operation.
    fn is_saturated_primop<'a>(&self, expr: &'a Expr) -> Option<(PrimOp, Vec<&'a Expr>)> {
        // Collect arguments while unwrapping applications
        let mut args = Vec::new();
        let mut current = expr;

        while let Expr::App(func, arg, _) = current {
            args.push(arg.as_ref());
            current = func.as_ref();
        }

        // Skip through type applications (erased at runtime)
        while let Expr::TyApp(inner, _, _) = current {
            current = inner.as_ref();
        }

        // Check if the head is a primitive operation
        if let Expr::Var(var, _) = current {
            if let Some((op, arity)) = self.primitive_op_info(var.name.as_str()) {
                args.reverse();
                if args.len() == arity as usize {
                    return Some((op, args));
                }
            }
        }

        None
    }

    /// Lower a primitive operation to LLVM instructions.
    fn lower_primop(
        &mut self,
        op: PrimOp,
        args: &[&Expr],
    ) -> CodegenResult<Option<BasicValueEnum<'ctx>>> {
        // Check if operands are Rational — dispatch to RTS
        if !args.is_empty() && self.is_rational_expr(args[0]) {
            match op {
                PrimOp::Add
                | PrimOp::Sub
                | PrimOp::Mul
                | PrimOp::Div
                | PrimOp::Negate
                | PrimOp::Abs
                | PrimOp::Signum
                | PrimOp::Eq
                | PrimOp::Ne
                | PrimOp::Lt
                | PrimOp::Le
                | PrimOp::Gt
                | PrimOp::Ge => {
                    return self.lower_rational_primop(op, args);
                }
                _ => {}
            }
        }
        if args.len() >= 2 && self.is_rational_expr(args[1]) {
            match op {
                PrimOp::Add
                | PrimOp::Sub
                | PrimOp::Mul
                | PrimOp::Div
                | PrimOp::Eq
                | PrimOp::Ne
                | PrimOp::Lt
                | PrimOp::Le
                | PrimOp::Gt
                | PrimOp::Ge => {
                    return self.lower_rational_primop(op, args);
                }
                _ => {}
            }
        }

        // E.45: Check if operands are Integer (arbitrary precision) — dispatch to RTS
        if !args.is_empty() && self.is_integer_expr(args[0]) {
            match op {
                PrimOp::Add
                | PrimOp::Sub
                | PrimOp::Mul
                | PrimOp::Div
                | PrimOp::Mod
                | PrimOp::Rem
                | PrimOp::Quot
                | PrimOp::Negate
                | PrimOp::Eq
                | PrimOp::Ne
                | PrimOp::Lt
                | PrimOp::Le
                | PrimOp::Gt
                | PrimOp::Ge => {
                    return self.lower_integer_primop(op, args);
                }
                _ => {}
            }
        }
        // Also check second arg for Integer (e.g., `1 + x` where x is Integer)
        if args.len() >= 2 && self.is_integer_expr(args[1]) {
            match op {
                PrimOp::Add
                | PrimOp::Sub
                | PrimOp::Mul
                | PrimOp::Div
                | PrimOp::Mod
                | PrimOp::Rem
                | PrimOp::Quot
                | PrimOp::Eq
                | PrimOp::Ne
                | PrimOp::Lt
                | PrimOp::Le
                | PrimOp::Gt
                | PrimOp::Ge => {
                    return self.lower_integer_primop(op, args);
                }
                _ => {}
            }
        }

        match op {
            // Binary arithmetic operations
            PrimOp::Add
            | PrimOp::Sub
            | PrimOp::Mul
            | PrimOp::Div
            | PrimOp::Mod
            | PrimOp::Rem
            | PrimOp::Quot => {
                let lhs = self
                    .lower_expr(args[0])?
                    .ok_or_else(|| CodegenError::Internal("primop arg has no value".to_string()))?;
                let rhs = self
                    .lower_expr(args[1])?
                    .ok_or_else(|| CodegenError::Internal("primop arg has no value".to_string()))?;
                // Double/Float arithmetic must operate on f64. A boxed Double
                // operand (e.g. a function parameter) is a PointerValue, which
                // `lower_binary_arith` would int-add on its raw bits — so detect
                // the floating type and route through the unboxing float path.
                let t0 = args[0].ty();
                let t1 = args[1].ty();
                if self.is_double_type(&t0)
                    || self.is_float_type(&t0)
                    || self.is_double_type(&t1)
                    || self.is_float_type(&t1)
                    || self.expr_returns_double(args[0])
                    || self.expr_returns_double(args[1])
                {
                    return self.lower_float_arith(op, args[0], lhs, args[1], rhs);
                }
                self.lower_binary_arith(op, lhs, rhs)
            }

            // Binary comparison operations
            PrimOp::Eq | PrimOp::Ne | PrimOp::Lt | PrimOp::Le | PrimOp::Gt | PrimOp::Ge => {
                // Check if we're comparing lists - need special handling
                // Since type info may be Error, check structurally if expr is a list
                let is_list = Self::is_list_expr(args[0]);
                if is_list {
                    // List comparison - dispatch to builtin list comparison
                    let lhs = self.lower_expr(args[0])?.ok_or_else(|| {
                        CodegenError::Internal("primop arg has no value".to_string())
                    })?;
                    let rhs = self.lower_expr(args[1])?.ok_or_else(|| {
                        CodegenError::Internal("primop arg has no value".to_string())
                    })?;
                    return self.lower_list_comparison(op, lhs, rhs);
                }

                // Check if we're comparing user ADTs with derived Eq
                if matches!(op, PrimOp::Eq | PrimOp::Ne) {
                    let adt_type = Self::is_user_adt_expr(args[0], &self.constructor_metadata)
                        .or_else(|| Self::is_user_adt_expr(args[1], &self.constructor_metadata));
                    if let Some(type_name) = adt_type {
                        if let Some(eq_var_id) = self.derived_eq_fns.get(&type_name).copied() {
                            if let Some(eq_fn) = self.functions.get(&eq_var_id).copied() {
                                let lhs = self.lower_expr(args[0])?.ok_or_else(|| {
                                    CodegenError::Internal("eq: no lhs value".to_string())
                                })?;
                                let rhs = self.lower_expr(args[1])?.ok_or_else(|| {
                                    CodegenError::Internal("eq: no rhs value".to_string())
                                })?;
                                // Call $derived_eq_TypeName(env, lhs, rhs) → Bool ADT pointer
                                // All BHC functions use (env, args...) calling convention
                                let null_env = self.type_mapper().ptr_type().const_null();
                                let fn_ptr = eq_fn.as_global_value().as_pointer_value();
                                let fn_type = self.type_mapper().ptr_type().fn_type(
                                    &[
                                        self.type_mapper().ptr_type().into(),
                                        self.type_mapper().ptr_type().into(),
                                        self.type_mapper().ptr_type().into(),
                                    ],
                                    false,
                                );
                                let result = self
                                    .builder()
                                    .build_indirect_call(
                                        fn_type,
                                        fn_ptr,
                                        &[null_env.into(), lhs.into(), rhs.into()],
                                        "derived_eq",
                                    )
                                    .map_err(|e| {
                                        CodegenError::Internal(format!("derived eq call: {:?}", e))
                                    })?;
                                let bool_ptr =
                                    result.try_as_basic_value().basic().ok_or_else(|| {
                                        CodegenError::Internal(
                                            "derived eq returned void".to_string(),
                                        )
                                    })?;
                                // Derived eq returns Bool ADT (tag 0=False, 1=True).
                                // Extract tag to get i64 result expected by comparison operators.
                                let tag = self.extract_adt_tag(bool_ptr.into_pointer_value())?;
                                if matches!(op, PrimOp::Ne) {
                                    // Invert: 0→1, 1→0
                                    let one = self.type_mapper().i64_type().const_int(1, false);
                                    let inverted = self
                                        .builder()
                                        .build_int_sub(one, tag, "ne_invert")
                                        .map_err(|e| {
                                            CodegenError::Internal(format!("ne invert: {:?}", e))
                                        })?;
                                    return Ok(Some(inverted.into()));
                                }
                                return Ok(Some(tag.into()));
                            }
                        }
                    }
                }

                // Check if we're comparing user ADTs with derived Ord (for <, <=, >, >=)
                if matches!(op, PrimOp::Lt | PrimOp::Le | PrimOp::Gt | PrimOp::Ge) {
                    let adt_type = Self::is_user_adt_expr(args[0], &self.constructor_metadata)
                        .or_else(|| Self::is_user_adt_expr(args[1], &self.constructor_metadata));
                    if let Some(type_name) = adt_type {
                        if let Some(cmp_var_id) = self.derived_compare_fns.get(&type_name).copied()
                        {
                            if let Some(cmp_fn) = self.functions.get(&cmp_var_id).copied() {
                                let lhs = self.lower_expr(args[0])?.ok_or_else(|| {
                                    CodegenError::Internal("ord: no lhs value".to_string())
                                })?;
                                let rhs = self.lower_expr(args[1])?.ok_or_else(|| {
                                    CodegenError::Internal("ord: no rhs value".to_string())
                                })?;
                                // Call $derived_compare_TypeName(env, lhs, rhs) → Ordering ADT pointer
                                let null_env = self.type_mapper().ptr_type().const_null();
                                let fn_ptr = cmp_fn.as_global_value().as_pointer_value();
                                let fn_type = self.type_mapper().ptr_type().fn_type(
                                    &[
                                        self.type_mapper().ptr_type().into(),
                                        self.type_mapper().ptr_type().into(),
                                        self.type_mapper().ptr_type().into(),
                                    ],
                                    false,
                                );
                                let result = self
                                    .builder()
                                    .build_indirect_call(
                                        fn_type,
                                        fn_ptr,
                                        &[null_env.into(), lhs.into(), rhs.into()],
                                        "derived_compare",
                                    )
                                    .map_err(|e| {
                                        CodegenError::Internal(format!(
                                            "derived compare call: {:?}",
                                            e
                                        ))
                                    })?;
                                let ordering_ptr =
                                    result.try_as_basic_value().basic().ok_or_else(|| {
                                        CodegenError::Internal(
                                            "derived compare returned void".to_string(),
                                        )
                                    })?;
                                // Ordering tag: 0=LT, 1=EQ, 2=GT
                                let tag =
                                    self.extract_adt_tag(ordering_ptr.into_pointer_value())?;
                                let i64_ty = self.type_mapper().i64_type();
                                let bool_result = match op {
                                    PrimOp::Lt => self
                                        .builder()
                                        .build_int_compare(
                                            inkwell::IntPredicate::EQ,
                                            tag,
                                            i64_ty.const_int(0, false),
                                            "lt",
                                        )
                                        .map_err(|e| {
                                            CodegenError::Internal(format!("lt cmp: {:?}", e))
                                        })?,
                                    PrimOp::Le => self
                                        .builder()
                                        .build_int_compare(
                                            inkwell::IntPredicate::NE,
                                            tag,
                                            i64_ty.const_int(2, false),
                                            "le",
                                        )
                                        .map_err(|e| {
                                            CodegenError::Internal(format!("le cmp: {:?}", e))
                                        })?,
                                    PrimOp::Gt => self
                                        .builder()
                                        .build_int_compare(
                                            inkwell::IntPredicate::EQ,
                                            tag,
                                            i64_ty.const_int(2, false),
                                            "gt",
                                        )
                                        .map_err(|e| {
                                            CodegenError::Internal(format!("gt cmp: {:?}", e))
                                        })?,
                                    PrimOp::Ge => self
                                        .builder()
                                        .build_int_compare(
                                            inkwell::IntPredicate::NE,
                                            tag,
                                            i64_ty.const_int(0, false),
                                            "ge",
                                        )
                                        .map_err(|e| {
                                            CodegenError::Internal(format!("ge cmp: {:?}", e))
                                        })?,
                                    _ => unreachable!(),
                                };
                                // Convert i1 to i64 (0 or 1)
                                let result_i64 = self
                                    .builder()
                                    .build_int_z_extend(bool_result, i64_ty, "cmp_ext")
                                    .map_err(|e| {
                                        CodegenError::Internal(format!("cmp ext: {:?}", e))
                                    })?;
                                return Ok(Some(result_i64.into()));
                            }
                        }
                    }
                }

                let lhs = self
                    .lower_expr(args[0])?
                    .ok_or_else(|| CodegenError::Internal("primop arg has no value".to_string()))?;
                let rhs = self
                    .lower_expr(args[1])?
                    .ok_or_else(|| CodegenError::Internal("primop arg has no value".to_string()))?;
                self.lower_comparison(op, lhs, rhs)
            }

            // Binary boolean operations
            PrimOp::And | PrimOp::Or => {
                let lhs = self
                    .lower_expr(args[0])?
                    .ok_or_else(|| CodegenError::Internal("primop arg has no value".to_string()))?;
                let rhs = self
                    .lower_expr(args[1])?
                    .ok_or_else(|| CodegenError::Internal("primop arg has no value".to_string()))?;
                self.lower_binary_bool(op, lhs, rhs)
            }

            // Binary bitwise operations
            PrimOp::BitAnd | PrimOp::BitOr | PrimOp::BitXor | PrimOp::ShiftL | PrimOp::ShiftR => {
                let lhs = self
                    .lower_expr(args[0])?
                    .ok_or_else(|| CodegenError::Internal("primop arg has no value".to_string()))?;
                let rhs = self
                    .lower_expr(args[1])?
                    .ok_or_else(|| CodegenError::Internal("primop arg has no value".to_string()))?;
                self.lower_binary_bitwise(op, lhs, rhs)
            }

            // Unary operations
            PrimOp::Negate | PrimOp::Abs | PrimOp::Signum | PrimOp::Not | PrimOp::Complement => {
                let arg = self
                    .lower_expr(args[0])?
                    .ok_or_else(|| CodegenError::Internal("primop arg has no value".to_string()))?;
                self.lower_unary(op, arg)
            }
        }
    }

    /// Unbox a value to an integer if it's a pointer (boxed int).
    fn unbox_to_int(
        &self,
        val: BasicValueEnum<'ctx>,
    ) -> CodegenResult<inkwell::values::IntValue<'ctx>> {
        match val {
            BasicValueEnum::IntValue(i) => Ok(i),
            BasicValueEnum::PointerValue(p) => {
                // Unbox: pointer stores int value directly using ptr_to_int
                self.builder()
                    .build_ptr_to_int(p, self.type_mapper().i64_type(), "unbox_int")
                    .map_err(|e| CodegenError::Internal(format!("failed to unbox int: {:?}", e)))
            }
            _ => Err(CodegenError::TypeError(
                "expected int or boxed int".to_string(),
            )),
        }
    }

    /// Box an integer to a pointer.
    fn box_int(
        &self,
        val: inkwell::values::IntValue<'ctx>,
    ) -> CodegenResult<inkwell::values::PointerValue<'ctx>> {
        self.builder()
            .build_int_to_ptr(val, self.type_mapper().ptr_type(), "box_int")
            .map_err(|e| CodegenError::Internal(format!("failed to box int: {:?}", e)))
    }

    /// The head variable name of an application spine, peeling type applications
    /// and transparent wrappers (`f @t x dict` -> `f`).
    fn app_head_var_name<'a>(&self, e: &'a Expr) -> Option<&'a str> {
        match e {
            Expr::Var(v, _) => Some(v.name.as_str()),
            Expr::App(f, _, _) | Expr::TyApp(f, _, _) => self.app_head_var_name(f),
            Expr::Cast(inner, _, _) | Expr::Tick(_, inner, _) => self.app_head_var_name(inner),
            _ => None,
        }
    }

    /// Coerce one operand of a floating-point operation to `f64`. A
    /// `fromIntegral` operand is a genuine integer (lowered as the identity), so
    /// convert it with `sitofp`; any other operand is a real `Double` value to be
    /// unboxed.
    fn coerce_float_operand(
        &self,
        expr: &Expr,
        val: BasicValueEnum<'ctx>,
    ) -> CodegenResult<FloatValue<'ctx>> {
        if matches!(self.app_head_var_name(expr), Some("fromIntegral")) {
            let i = self.coerce_to_int(val)?;
            self.builder()
                .build_signed_int_to_float(i, self.type_mapper().f64_type(), "sitofp")
                .map_err(|e| CodegenError::Internal(format!("fromIntegral sitofp failed: {:?}", e)))
        } else {
            self.coerce_to_f64(val)
        }
    }

    /// Lower a binary arithmetic operation on `Double`/`Float`. Both operands
    /// are coerced to `f64` first — crucially unboxing a boxed `Double` (a
    /// `PointerValue`, e.g. a function parameter), which `lower_binary_arith`
    /// would otherwise int-add on its raw bits. Returns an unboxed `FloatValue`.
    fn lower_float_arith(
        &self,
        op: PrimOp,
        lhs_expr: &Expr,
        lhs: BasicValueEnum<'ctx>,
        rhs_expr: &Expr,
        rhs: BasicValueEnum<'ctx>,
    ) -> CodegenResult<Option<BasicValueEnum<'ctx>>> {
        let l = self.coerce_float_operand(lhs_expr, lhs)?;
        let r = self.coerce_float_operand(rhs_expr, rhs)?;
        let result = match op {
            PrimOp::Add => self.builder().build_float_add(l, r, "fadd"),
            PrimOp::Sub => self.builder().build_float_sub(l, r, "fsub"),
            PrimOp::Mul => self.builder().build_float_mul(l, r, "fmul"),
            PrimOp::Div | PrimOp::Quot => self.builder().build_float_div(l, r, "fdiv"),
            PrimOp::Mod | PrimOp::Rem => self.builder().build_float_rem(l, r, "frem"),
            _ => return Err(CodegenError::Internal("invalid float arith op".to_string())),
        };
        result
            .map(|v| Some(v.into()))
            .map_err(|e| CodegenError::Internal(format!("failed to build float op: {:?}", e)))
    }

    /// Lower a binary arithmetic operation.
    fn lower_binary_arith(
        &self,
        op: PrimOp,
        lhs: BasicValueEnum<'ctx>,
        rhs: BasicValueEnum<'ctx>,
    ) -> CodegenResult<Option<BasicValueEnum<'ctx>>> {
        // Check if we're dealing with integers or floats
        // Handle boxed values (pointers) by unboxing first
        match (lhs, rhs) {
            (BasicValueEnum::IntValue(l), BasicValueEnum::IntValue(r)) => {
                let result = match op {
                    PrimOp::Add => self.builder().build_int_add(l, r, "add"),
                    PrimOp::Sub => self.builder().build_int_sub(l, r, "sub"),
                    PrimOp::Mul => self.builder().build_int_mul(l, r, "mul"),
                    PrimOp::Div | PrimOp::Quot => self.builder().build_int_signed_div(l, r, "div"),
                    PrimOp::Mod | PrimOp::Rem => self.builder().build_int_signed_rem(l, r, "rem"),
                    _ => return Err(CodegenError::Internal("invalid arith op".to_string())),
                };
                result
                    .map(|v| Some(v.into()))
                    .map_err(|e| CodegenError::Internal(format!("failed to build int op: {:?}", e)))
            }
            (BasicValueEnum::FloatValue(l), BasicValueEnum::FloatValue(r)) => {
                let result = match op {
                    PrimOp::Add => self.builder().build_float_add(l, r, "fadd"),
                    PrimOp::Sub => self.builder().build_float_sub(l, r, "fsub"),
                    PrimOp::Mul => self.builder().build_float_mul(l, r, "fmul"),
                    PrimOp::Div | PrimOp::Quot => self.builder().build_float_div(l, r, "fdiv"),
                    PrimOp::Mod | PrimOp::Rem => self.builder().build_float_rem(l, r, "frem"),
                    _ => return Err(CodegenError::Internal("invalid arith op".to_string())),
                };
                result.map(|v| Some(v.into())).map_err(|e| {
                    CodegenError::Internal(format!("failed to build float op: {:?}", e))
                })
            }
            // Handle boxed integers (pointers) - unbox, compute, rebox
            (BasicValueEnum::PointerValue(_), _) | (_, BasicValueEnum::PointerValue(_)) => {
                let l = self.unbox_to_int(lhs)?;
                let r = self.unbox_to_int(rhs)?;
                let result = match op {
                    PrimOp::Add => self.builder().build_int_add(l, r, "add"),
                    PrimOp::Sub => self.builder().build_int_sub(l, r, "sub"),
                    PrimOp::Mul => self.builder().build_int_mul(l, r, "mul"),
                    PrimOp::Div | PrimOp::Quot => self.builder().build_int_signed_div(l, r, "div"),
                    PrimOp::Mod | PrimOp::Rem => self.builder().build_int_signed_rem(l, r, "rem"),
                    _ => return Err(CodegenError::Internal("invalid arith op".to_string())),
                };
                let int_result = result.map_err(|e| {
                    CodegenError::Internal(format!("failed to build int op: {:?}", e))
                })?;
                // Box the result back to pointer for uniform calling convention
                let boxed = self.box_int(int_result)?;
                Ok(Some(boxed.into()))
            }
            _ => Err(CodegenError::TypeError(
                "arithmetic operations require matching numeric types".to_string(),
            )),
        }
    }

    /// Lower a list comparison operation (== or /= for lists).
    /// This generates a loop that compares elements one by one.
    fn lower_list_comparison(
        &mut self,
        op: PrimOp,
        lhs: BasicValueEnum<'ctx>,
        rhs: BasicValueEnum<'ctx>,
    ) -> CodegenResult<Option<BasicValueEnum<'ctx>>> {
        let tm = self.type_mapper();
        let ptr_type = tm.ptr_type();
        let i64_type = tm.i64_type();
        let current_fn = self
            .builder()
            .get_insert_block()
            .and_then(|b| b.get_parent())
            .ok_or_else(|| CodegenError::Internal("no current function".to_string()))?;

        // Create basic blocks
        let entry_block = self
            .builder()
            .get_insert_block()
            .ok_or_else(|| CodegenError::Internal("no current block".to_string()))?;
        let loop_block = self
            .llvm_context()
            .append_basic_block(current_fn, "list_cmp_loop");
        let both_nil_block = self
            .llvm_context()
            .append_basic_block(current_fn, "list_cmp_both_nil");
        let one_nil_block = self
            .llvm_context()
            .append_basic_block(current_fn, "list_cmp_one_nil");
        let compare_heads_block = self
            .llvm_context()
            .append_basic_block(current_fn, "list_cmp_heads");
        let heads_equal_block = self
            .llvm_context()
            .append_basic_block(current_fn, "list_cmp_heads_eq");
        let done_block = self
            .llvm_context()
            .append_basic_block(current_fn, "list_cmp_done");

        let lhs_ptr = lhs.into_pointer_value();
        let rhs_ptr = rhs.into_pointer_value();

        // Jump to loop
        self.builder()
            .build_unconditional_branch(loop_block)
            .map_err(|e| CodegenError::Internal(format!("failed to branch: {:?}", e)))?;

        // Loop block: check tags
        self.builder().position_at_end(loop_block);
        let list1_phi = self
            .builder()
            .build_phi(ptr_type, "list1")
            .map_err(|e| CodegenError::Internal(format!("failed to build phi: {:?}", e)))?;
        let list2_phi = self
            .builder()
            .build_phi(ptr_type, "list2")
            .map_err(|e| CodegenError::Internal(format!("failed to build phi: {:?}", e)))?;
        list1_phi.add_incoming(&[(&lhs_ptr, entry_block)]);
        list2_phi.add_incoming(&[(&rhs_ptr, entry_block)]);

        let list1_ptr = list1_phi.as_basic_value().into_pointer_value();
        let list2_ptr = list2_phi.as_basic_value().into_pointer_value();

        // Get tags
        let tag1 = self
            .builder()
            .build_load(i64_type, list1_ptr, "tag1")
            .map_err(|e| CodegenError::Internal(format!("failed to load tag1: {:?}", e)))?
            .into_int_value();
        let tag2 = self
            .builder()
            .build_load(i64_type, list2_ptr, "tag2")
            .map_err(|e| CodegenError::Internal(format!("failed to load tag2: {:?}", e)))?
            .into_int_value();

        let zero = i64_type.const_int(0, false);
        let is_nil1 = self
            .builder()
            .build_int_compare(inkwell::IntPredicate::EQ, tag1, zero, "is_nil1")
            .map_err(|e| CodegenError::Internal(format!("failed to build cmp: {:?}", e)))?;
        let is_nil2 = self
            .builder()
            .build_int_compare(inkwell::IntPredicate::EQ, tag2, zero, "is_nil2")
            .map_err(|e| CodegenError::Internal(format!("failed to build cmp: {:?}", e)))?;

        // Check if both are nil
        let both_nil = self
            .builder()
            .build_and(is_nil1, is_nil2, "both_nil")
            .map_err(|e| CodegenError::Internal(format!("failed to build and: {:?}", e)))?;
        self.builder()
            .build_conditional_branch(both_nil, both_nil_block, one_nil_block)
            .map_err(|e| CodegenError::Internal(format!("failed to branch: {:?}", e)))?;

        // Both nil: lists are equal
        self.builder().position_at_end(both_nil_block);
        let true_val = i64_type.const_int(1, false);
        self.builder()
            .build_unconditional_branch(done_block)
            .map_err(|e| CodegenError::Internal(format!("failed to branch: {:?}", e)))?;

        // One nil (but not both): check if either is nil
        self.builder().position_at_end(one_nil_block);
        let either_nil = self
            .builder()
            .build_or(is_nil1, is_nil2, "either_nil")
            .map_err(|e| CodegenError::Internal(format!("failed to build or: {:?}", e)))?;
        let false_val = i64_type.const_int(0, false);
        // If either is nil (but not both), lists are not equal
        self.builder()
            .build_conditional_branch(either_nil, done_block, compare_heads_block)
            .map_err(|e| CodegenError::Internal(format!("failed to branch: {:?}", e)))?;

        // Compare heads - use extract_adt_field which handles the ADT layout
        self.builder().position_at_end(compare_heads_block);
        let head1 = self.extract_adt_field(list1_ptr, 2, 0)?; // arity=2 (Cons), field=0 (head)
        let head2 = self.extract_adt_field(list2_ptr, 2, 0)?;

        // Compare heads as integers (works for primitive types like Int)
        let head1_int = self
            .builder()
            .build_ptr_to_int(head1, i64_type, "head1_int")
            .map_err(|e| CodegenError::Internal(format!("failed to ptr_to_int: {:?}", e)))?;
        let head2_int = self
            .builder()
            .build_ptr_to_int(head2, i64_type, "head2_int")
            .map_err(|e| CodegenError::Internal(format!("failed to ptr_to_int: {:?}", e)))?;
        let heads_equal = self
            .builder()
            .build_int_compare(inkwell::IntPredicate::EQ, head1_int, head2_int, "heads_eq")
            .map_err(|e| CodegenError::Internal(format!("failed to cmp: {:?}", e)))?;

        // If heads not equal, lists not equal
        self.builder()
            .build_conditional_branch(heads_equal, heads_equal_block, done_block)
            .map_err(|e| CodegenError::Internal(format!("failed to branch: {:?}", e)))?;

        // Heads equal - continue with tails
        self.builder().position_at_end(heads_equal_block);
        let tail1 = self.extract_adt_field(list1_ptr, 2, 1)?; // arity=2 (Cons), field=1 (tail)
        let tail2 = self.extract_adt_field(list2_ptr, 2, 1)?;

        // Update phis and loop back
        list1_phi.add_incoming(&[(&tail1, heads_equal_block)]);
        list2_phi.add_incoming(&[(&tail2, heads_equal_block)]);
        self.builder()
            .build_unconditional_branch(loop_block)
            .map_err(|e| CodegenError::Internal(format!("failed to branch: {:?}", e)))?;

        // Done block: collect result
        self.builder().position_at_end(done_block);
        let result_phi = self
            .builder()
            .build_phi(i64_type, "list_cmp_result")
            .map_err(|e| CodegenError::Internal(format!("failed to build phi: {:?}", e)))?;
        result_phi.add_incoming(&[(&true_val, both_nil_block)]); // Both nil = equal
        result_phi.add_incoming(&[(&false_val, one_nil_block)]); // One nil = not equal
        result_phi.add_incoming(&[(&false_val, compare_heads_block)]); // Heads not equal

        let result = result_phi.as_basic_value().into_int_value();

        // For /= we need to invert the result
        let final_result = if matches!(op, PrimOp::Ne) {
            let one = i64_type.const_int(1, false);
            self.builder()
                .build_xor(result, one, "invert")
                .map_err(|e| CodegenError::Internal(format!("failed to xor: {:?}", e)))?
        } else {
            result
        };

        Ok(Some(final_result.into()))
    }

    /// Lower a comparison operation.
    fn lower_comparison(
        &self,
        op: PrimOp,
        lhs: BasicValueEnum<'ctx>,
        rhs: BasicValueEnum<'ctx>,
    ) -> CodegenResult<Option<BasicValueEnum<'ctx>>> {
        // Helper to do int comparison and return boxed result
        let do_int_cmp = |this: &Self,
                          l: inkwell::values::IntValue<'ctx>,
                          r: inkwell::values::IntValue<'ctx>|
         -> CodegenResult<Option<BasicValueEnum<'ctx>>> {
            use inkwell::IntPredicate;
            let pred = match op {
                PrimOp::Eq => IntPredicate::EQ,
                PrimOp::Ne => IntPredicate::NE,
                PrimOp::Lt => IntPredicate::SLT,
                PrimOp::Le => IntPredicate::SLE,
                PrimOp::Gt => IntPredicate::SGT,
                PrimOp::Ge => IntPredicate::SGE,
                _ => return Err(CodegenError::Internal("invalid comparison op".to_string())),
            };
            let cmp = this
                .builder()
                .build_int_compare(pred, l, r, "cmp")
                .map_err(|e| CodegenError::Internal(format!("failed to build int cmp: {:?}", e)))?;

            // Convert i1 to i64 (0 or 1) for consistency with our Bool representation
            let result = this
                .builder()
                .build_int_z_extend(cmp, this.type_mapper().i64_type(), "cmp_ext")
                .map_err(|e| CodegenError::Internal(format!("failed to extend cmp: {:?}", e)))?;

            Ok(Some(result.into()))
        };

        match (lhs, rhs) {
            (BasicValueEnum::IntValue(l), BasicValueEnum::IntValue(r)) => do_int_cmp(self, l, r),
            (BasicValueEnum::FloatValue(l), BasicValueEnum::FloatValue(r)) => {
                use inkwell::FloatPredicate;
                let pred = match op {
                    PrimOp::Eq => FloatPredicate::OEQ,
                    PrimOp::Ne => FloatPredicate::ONE,
                    PrimOp::Lt => FloatPredicate::OLT,
                    PrimOp::Le => FloatPredicate::OLE,
                    PrimOp::Gt => FloatPredicate::OGT,
                    PrimOp::Ge => FloatPredicate::OGE,
                    _ => return Err(CodegenError::Internal("invalid comparison op".to_string())),
                };
                let cmp = self
                    .builder()
                    .build_float_compare(pred, l, r, "fcmp")
                    .map_err(|e| {
                        CodegenError::Internal(format!("failed to build float cmp: {:?}", e))
                    })?;

                // Convert i1 to i64
                let result = self
                    .builder()
                    .build_int_z_extend(cmp, self.type_mapper().i64_type(), "fcmp_ext")
                    .map_err(|e| {
                        CodegenError::Internal(format!("failed to extend fcmp: {:?}", e))
                    })?;

                Ok(Some(result.into()))
            }
            // Handle boxed integers (pointers)
            (BasicValueEnum::PointerValue(_), _) | (_, BasicValueEnum::PointerValue(_)) => {
                let l = self.unbox_to_int(lhs)?;
                let r = self.unbox_to_int(rhs)?;
                do_int_cmp(self, l, r)
            }
            _ => Err(CodegenError::TypeError(
                "comparison operations require matching types".to_string(),
            )),
        }
    }

    /// Lower a binary boolean operation.
    /// Returns a proper Bool ADT pointer (not just an integer tag).
    fn lower_binary_bool(
        &self,
        op: PrimOp,
        lhs: BasicValueEnum<'ctx>,
        rhs: BasicValueEnum<'ctx>,
    ) -> CodegenResult<Option<BasicValueEnum<'ctx>>> {
        // Extract the boolean value (tag) from each operand
        // Bool is an ADT with False=tag 0, True=tag 1
        let l = self.extract_bool_value(lhs)?;
        let r = self.extract_bool_value(rhs)?;

        // Convert to i1 for boolean operations (compare to zero)
        let zero = self.type_mapper().i64_type().const_zero();
        let l_bool = self
            .builder()
            .build_int_compare(inkwell::IntPredicate::NE, l, zero, "l_bool")
            .map_err(|e| CodegenError::Internal(format!("failed to convert to bool: {:?}", e)))?;
        let r_bool = self
            .builder()
            .build_int_compare(inkwell::IntPredicate::NE, r, zero, "r_bool")
            .map_err(|e| CodegenError::Internal(format!("failed to convert to bool: {:?}", e)))?;

        let result = match op {
            PrimOp::And => self.builder().build_and(l_bool, r_bool, "and"),
            PrimOp::Or => self.builder().build_or(l_bool, r_bool, "or"),
            _ => return Err(CodegenError::Internal("invalid bool op".to_string())),
        };

        let bool_result = result
            .map_err(|e| CodegenError::Internal(format!("failed to build bool op: {:?}", e)))?;

        // Extend to i64 (0 or 1)
        let _tag = self
            .builder()
            .build_int_z_extend(bool_result, self.type_mapper().i64_type(), "bool_ext")
            .map_err(|e| CodegenError::Internal(format!("failed to extend bool: {:?}", e)))?;

        // Allocate and return a proper Bool ADT
        // We need to select between True (tag 1) and False (tag 0) ADTs
        let current_fn = self
            .builder()
            .get_insert_block()
            .ok_or_else(|| CodegenError::Internal("no insert block".to_string()))?
            .get_parent()
            .ok_or_else(|| CodegenError::Internal("no parent function".to_string()))?;

        let true_block = self
            .llvm_ctx
            .append_basic_block(current_fn, "bool_true_alloc");
        let false_block = self
            .llvm_ctx
            .append_basic_block(current_fn, "bool_false_alloc");
        let merge_block = self
            .llvm_ctx
            .append_basic_block(current_fn, "bool_alloc_merge");

        self.builder()
            .build_conditional_branch(bool_result, true_block, false_block)
            .map_err(|e| CodegenError::Internal(format!("failed to build branch: {:?}", e)))?;

        // True case: allocate True ADT (tag 1, arity 0)
        self.builder().position_at_end(true_block);
        let true_adt = self.alloc_adt(1, 0)?;
        self.builder()
            .build_unconditional_branch(merge_block)
            .map_err(|e| CodegenError::Internal(format!("failed to build branch: {:?}", e)))?;
        let true_end = self.builder().get_insert_block().unwrap();

        // False case: allocate False ADT (tag 0, arity 0)
        self.builder().position_at_end(false_block);
        let false_adt = self.alloc_adt(0, 0)?;
        self.builder()
            .build_unconditional_branch(merge_block)
            .map_err(|e| CodegenError::Internal(format!("failed to build branch: {:?}", e)))?;
        let false_end = self.builder().get_insert_block().unwrap();

        // Merge and return the ADT pointer
        self.builder().position_at_end(merge_block);
        let phi = self
            .builder()
            .build_phi(self.type_mapper().ptr_type(), "bool_adt")
            .map_err(|e| CodegenError::Internal(format!("failed to build phi: {:?}", e)))?;
        phi.add_incoming(&[(&true_adt, true_end), (&false_adt, false_end)]);

        Ok(Some(phi.as_basic_value()))
    }

    /// Extract the boolean value (as i64 tag: 0 for False, 1 for True) from a value.
    /// Handles both raw integer booleans and Bool ADT pointers.
    fn extract_bool_value(&self, val: BasicValueEnum<'ctx>) -> CodegenResult<IntValue<'ctx>> {
        match val {
            BasicValueEnum::IntValue(i) => Ok(i),
            BasicValueEnum::PointerValue(p) => {
                // The pointer might be:
                // 1. A valid Bool ADT pointer (from constructor allocation)
                // 2. A small integer (0 or 1) cast to pointer (from primop result)
                let ptr_as_int = self
                    .builder()
                    .build_ptr_to_int(p, self.type_mapper().i64_type(), "ptr_as_int")
                    .map_err(|e| {
                        CodegenError::Internal(format!("failed to convert ptr to int: {:?}", e))
                    })?;

                // Check if it's a small integer (0 or 1)
                let is_raw_bool = self
                    .builder()
                    .build_int_compare(
                        inkwell::IntPredicate::ULE,
                        ptr_as_int,
                        self.type_mapper().i64_type().const_int(1, false),
                        "is_raw_bool",
                    )
                    .map_err(|e| {
                        CodegenError::Internal(format!("failed to compare ptr: {:?}", e))
                    })?;

                // Create blocks for the two cases
                let current_fn = self
                    .builder()
                    .get_insert_block()
                    .ok_or_else(|| CodegenError::Internal("no insert block".to_string()))?
                    .get_parent()
                    .ok_or_else(|| CodegenError::Internal("no parent function".to_string()))?;
                let raw_bool_block = self.llvm_ctx.append_basic_block(current_fn, "raw_bool_ext");
                let adt_bool_block = self.llvm_ctx.append_basic_block(current_fn, "adt_bool_ext");
                let merge_block = self
                    .llvm_ctx
                    .append_basic_block(current_fn, "bool_ext_merge");

                self.builder()
                    .build_conditional_branch(is_raw_bool, raw_bool_block, adt_bool_block)
                    .map_err(|e| {
                        CodegenError::Internal(format!("failed to build cond branch: {:?}", e))
                    })?;

                // Raw bool case: pointer value IS the boolean (0 or 1)
                self.builder().position_at_end(raw_bool_block);
                let raw_val = ptr_as_int;
                self.builder()
                    .build_unconditional_branch(merge_block)
                    .map_err(|e| {
                        CodegenError::Internal(format!("failed to build branch: {:?}", e))
                    })?;
                let raw_bool_end = self.builder().get_insert_block().unwrap();

                // ADT bool case: load the tag from the ADT structure
                self.builder().position_at_end(adt_bool_block);
                let adt_val = self.extract_adt_tag(p)?;
                self.builder()
                    .build_unconditional_branch(merge_block)
                    .map_err(|e| {
                        CodegenError::Internal(format!("failed to build branch: {:?}", e))
                    })?;
                let adt_bool_end = self.builder().get_insert_block().unwrap();

                // Merge
                self.builder().position_at_end(merge_block);
                let phi = self
                    .builder()
                    .build_phi(self.type_mapper().i64_type(), "bool_val_ext")
                    .map_err(|e| CodegenError::Internal(format!("failed to build phi: {:?}", e)))?;
                phi.add_incoming(&[(&raw_val, raw_bool_end), (&adt_val, adt_bool_end)]);
                Ok(phi.as_basic_value().into_int_value())
            }
            _ => Err(CodegenError::TypeError(
                "expected Bool value in boolean operation".to_string(),
            )),
        }
    }

    /// Lower a binary bitwise operation.
    fn lower_binary_bitwise(
        &self,
        op: PrimOp,
        lhs: BasicValueEnum<'ctx>,
        rhs: BasicValueEnum<'ctx>,
    ) -> CodegenResult<Option<BasicValueEnum<'ctx>>> {
        let l = self.to_int_value(lhs)?;
        let r = self.to_int_value(rhs)?;

        let result = match op {
            PrimOp::BitAnd => self.builder().build_and(l, r, "band"),
            PrimOp::BitOr => self.builder().build_or(l, r, "bor"),
            PrimOp::BitXor => self.builder().build_xor(l, r, "bxor"),
            PrimOp::ShiftL => self.builder().build_left_shift(l, r, "shl"),
            PrimOp::ShiftR => self.builder().build_right_shift(l, r, true, "shr"),
            _ => return Err(CodegenError::Internal("invalid bitwise op".to_string())),
        };

        result
            .map(|v| Some(v.into()))
            .map_err(|e| CodegenError::Internal(format!("failed to build bitwise op: {:?}", e)))
    }

    /// Lower a unary operation.
    fn lower_unary(
        &self,
        op: PrimOp,
        arg: BasicValueEnum<'ctx>,
    ) -> CodegenResult<Option<BasicValueEnum<'ctx>>> {
        match op {
            PrimOp::Negate => match arg {
                BasicValueEnum::IntValue(i) => {
                    let result = self.builder().build_int_neg(i, "neg").map_err(|e| {
                        CodegenError::Internal(format!("failed to negate: {:?}", e))
                    })?;
                    Ok(Some(result.into()))
                }
                BasicValueEnum::FloatValue(f) => {
                    let result = self.builder().build_float_neg(f, "fneg").map_err(|e| {
                        CodegenError::Internal(format!("failed to fnegate: {:?}", e))
                    })?;
                    Ok(Some(result.into()))
                }
                _ => Err(CodegenError::TypeError(
                    "negate requires numeric type".to_string(),
                )),
            },
            PrimOp::Abs => {
                match arg {
                    BasicValueEnum::IntValue(i) => {
                        // abs(x) = x < 0 ? -x : x
                        let zero = self.type_mapper().i64_type().const_zero();
                        let is_neg = self
                            .builder()
                            .build_int_compare(inkwell::IntPredicate::SLT, i, zero, "is_neg")
                            .map_err(|e| {
                                CodegenError::Internal(format!("failed to compare: {:?}", e))
                            })?;
                        let neg = self.builder().build_int_neg(i, "neg").map_err(|e| {
                            CodegenError::Internal(format!("failed to negate: {:?}", e))
                        })?;
                        let result =
                            self.builder()
                                .build_select(is_neg, neg, i, "abs")
                                .map_err(|e| {
                                    CodegenError::Internal(format!("failed to select: {:?}", e))
                                })?;
                        Ok(Some(result))
                    }
                    BasicValueEnum::FloatValue(f) => {
                        // For floats, use llvm.fabs intrinsic or manual comparison
                        let zero = self.type_mapper().f64_type().const_zero();
                        let is_neg = self
                            .builder()
                            .build_float_compare(inkwell::FloatPredicate::OLT, f, zero, "is_neg")
                            .map_err(|e| {
                                CodegenError::Internal(format!("failed to compare: {:?}", e))
                            })?;
                        let neg = self.builder().build_float_neg(f, "fneg").map_err(|e| {
                            CodegenError::Internal(format!("failed to fnegate: {:?}", e))
                        })?;
                        let result = self
                            .builder()
                            .build_select(is_neg, neg, f, "fabs")
                            .map_err(|e| {
                                CodegenError::Internal(format!("failed to select: {:?}", e))
                            })?;
                        Ok(Some(result))
                    }
                    _ => Err(CodegenError::TypeError(
                        "abs requires numeric type".to_string(),
                    )),
                }
            }
            PrimOp::Signum => {
                match arg {
                    BasicValueEnum::IntValue(i) => {
                        // signum(x) = x < 0 ? -1 : (x > 0 ? 1 : 0)
                        let zero = self.type_mapper().i64_type().const_zero();
                        let one = self.type_mapper().i64_type().const_int(1, false);
                        let neg_one = self.type_mapper().i64_type().const_int(-1i64 as u64, true);

                        let is_neg = self
                            .builder()
                            .build_int_compare(inkwell::IntPredicate::SLT, i, zero, "is_neg")
                            .map_err(|e| {
                                CodegenError::Internal(format!("failed to compare: {:?}", e))
                            })?;
                        let is_pos = self
                            .builder()
                            .build_int_compare(inkwell::IntPredicate::SGT, i, zero, "is_pos")
                            .map_err(|e| {
                                CodegenError::Internal(format!("failed to compare: {:?}", e))
                            })?;

                        let pos_or_zero = self
                            .builder()
                            .build_select(is_pos, one, zero, "pos_or_zero")
                            .map_err(|e| {
                                CodegenError::Internal(format!("failed to select: {:?}", e))
                            })?;
                        let result = self
                            .builder()
                            .build_select(is_neg, neg_one, pos_or_zero.into_int_value(), "signum")
                            .map_err(|e| {
                                CodegenError::Internal(format!("failed to select: {:?}", e))
                            })?;

                        Ok(Some(result))
                    }
                    BasicValueEnum::FloatValue(f) => {
                        let zero = f.get_type().const_zero();
                        let one = f.get_type().const_float(1.0);
                        let neg_one = f.get_type().const_float(-1.0);

                        let is_neg = self
                            .builder()
                            .build_float_compare(inkwell::FloatPredicate::OLT, f, zero, "is_neg")
                            .map_err(|e| {
                                CodegenError::Internal(format!("failed to compare: {:?}", e))
                            })?;
                        let is_pos = self
                            .builder()
                            .build_float_compare(inkwell::FloatPredicate::OGT, f, zero, "is_pos")
                            .map_err(|e| {
                                CodegenError::Internal(format!("failed to compare: {:?}", e))
                            })?;

                        let pos_or_zero = self
                            .builder()
                            .build_select(is_pos, one, zero, "pos_or_zero")
                            .map_err(|e| {
                                CodegenError::Internal(format!("failed to select: {:?}", e))
                            })?;
                        let result = self
                            .builder()
                            .build_select(
                                is_neg,
                                neg_one,
                                pos_or_zero.into_float_value(),
                                "fsignum",
                            )
                            .map_err(|e| {
                                CodegenError::Internal(format!("failed to select: {:?}", e))
                            })?;

                        Ok(Some(result))
                    }
                    _ => Err(CodegenError::TypeError(
                        "signum requires numeric type".to_string(),
                    )),
                }
            }
            PrimOp::Not => {
                let i = self.to_int_value(arg)?;
                let zero = self.type_mapper().i64_type().const_zero();
                let one = self.type_mapper().i64_type().const_int(1, false);

                // not x = x == 0 ? 1 : 0
                let is_zero = self
                    .builder()
                    .build_int_compare(inkwell::IntPredicate::EQ, i, zero, "is_zero")
                    .map_err(|e| CodegenError::Internal(format!("failed to compare: {:?}", e)))?;
                let result = self
                    .builder()
                    .build_select(is_zero, one, zero, "not")
                    .map_err(|e| CodegenError::Internal(format!("failed to select: {:?}", e)))?;

                Ok(Some(result))
            }
            PrimOp::Complement => {
                let i = self.to_int_value(arg)?;
                let result = self.builder().build_not(i, "complement").map_err(|e| {
                    CodegenError::Internal(format!("failed to complement: {:?}", e))
                })?;
                Ok(Some(result.into()))
            }
            _ => Err(CodegenError::Internal("invalid unary op".to_string())),
        }
    }

    /// Convert a basic value to an int value.
    fn to_int_value(&self, val: BasicValueEnum<'ctx>) -> CodegenResult<IntValue<'ctx>> {
        match val {
            BasicValueEnum::IntValue(i) => Ok(i),
            BasicValueEnum::PointerValue(p) => {
                // Pointer might be a boxed int - convert
                self.builder()
                    .build_ptr_to_int(p, self.type_mapper().i64_type(), "ptr_to_int")
                    .map_err(|e| {
                        CodegenError::Internal(format!("failed to convert ptr to int: {:?}", e))
                    })
            }
            _ => Err(CodegenError::TypeError(
                "expected integer value".to_string(),
            )),
        }
    }

    /// Lower a function application.
    ///
    /// Handles four cases:
    /// 1. Builtin function (e.g., `head xs`) - generate specialized code
    /// 2. Primitive operation (e.g., `1 + 2`) - generate LLVM instruction
    /// 3. Constructor application (e.g., `Just 42`) - allocate ADT value
    /// 4. Function call - generate call instruction
    fn lower_application(
        &mut self,
        func: &Expr,
        arg: &Expr,
    ) -> CodegenResult<Option<BasicValueEnum<'ctx>>> {
        // First, reconstruct the full application to check for special cases
        let full_app = Expr::App(Box::new(func.clone()), Box::new(arg.clone()), func.span());

        // Check if this is a saturated builtin function
        if let Some((name, builtin_args)) = self.is_saturated_builtin(&full_app) {
            return self.lower_builtin(name, &builtin_args);
        }

        // Check if this is a saturated primitive operation
        if let Some((op, prim_args)) = self.is_saturated_primop(&full_app) {
            return self.lower_primop(op, &prim_args);
        }

        // Check if this is a saturated constructor application
        if let Some((tag, arity, con_args, con_name)) = self.is_saturated_constructor(&full_app) {
            // Newtype erasure: newtype constructor is identity at runtime
            if self.is_newtype_constructor(&con_name) && arity == 1 {
                return self.lower_expr(con_args[0]);
            }
            return self.lower_constructor_application(tag, arity, &con_args);
        }

        // Not a primop or constructor - proceed with function call
        // Collect all arguments (for curried applications)
        let mut args = vec![arg];
        let mut current = func;

        loop {
            match current {
                Expr::App(inner_func, inner_arg, _) => {
                    args.push(inner_arg);
                    current = inner_func;
                }
                // Skip through type applications (erased at runtime)
                Expr::TyApp(inner, _, _) => {
                    current = inner;
                }
                _ => break,
            }
        }

        args.reverse();

        // Get the function being called
        match current {
            Expr::Var(var, _) => {
                let name = var.name.as_str();

                // Check if this is a nullary constructor (no args, just a value)
                if let Some((tag, arity)) = self.constructor_info(name) {
                    if arity == 0 {
                        // Nullary constructor like True, False, Nothing, ()
                        return self.lower_constructor_application(tag, 0, &[]);
                    }
                }

                // Check if this is an RTS builtin
                if let Some(rts_id) = self.rts_function_id(name) {
                    let fn_val = self.functions.get(&rts_id).copied().ok_or_else(|| {
                        CodegenError::Internal(format!("RTS function not declared: {}", name))
                    })?;
                    return self.lower_direct_call(fn_val, &args, None);
                }

                // Check if this is a known top-level function
                if let Some(fn_val) = self.functions.get(&var.id).copied() {
                    // E.45: Pass callee type for Integer argument promotion
                    return self.lower_direct_call(fn_val, &args, Some(&var.ty));
                }

                // Check if this is a closure in the environment
                if let Some(closure_val) = self.env.get(&var.id).copied() {
                    return self.lower_closure_call(closure_val, &args);
                }

                // Check if this is an imported (external) function from another module
                if let Some(&fn_val) = self.external_functions.get(&var.name) {
                    return self.lower_direct_call(fn_val, &args, Some(&var.ty));
                }

                // Check if this is an unsaturated builtin (partial application)
                // e.g. (min 5) applies 1 arg to a 2-arg builtin
                if let Some(arity) = self.builtin_info(name) {
                    let n_provided = args.len() as u32;
                    if n_provided < arity {
                        // Partial application: create a closure that captures the provided args
                        // and accepts the remaining args
                        return self.create_partial_builtin_closure(name, arity, &args);
                    } else if n_provided == arity {
                        // Exact application through builtin dispatch
                        return self.lower_builtin(name, &args);
                    } else {
                        // Over-application: call the builtin with its expected args,
                        // then call the result (a closure/function) with remaining args.
                        // This happens for e.g. App(App($sel_0, dict), x) where
                        // $sel_0 has arity 1 but we have 2 args.
                        let (builtin_args, remaining_args) = args.split_at(arity as usize);
                        let result = self.lower_builtin(name, builtin_args)?;
                        if let Some(result_val) = result {
                            return self.lower_closure_call(result_val, remaining_args);
                        } else {
                            return Err(CodegenError::Internal(format!(
                                "builtin {} returned no value for over-application",
                                name
                            )));
                        }
                    }
                }

                {
                    // Unknown function in application — generate a runtime stub.
                    let stub_fn = self.get_or_create_stub_function(name)?;
                    let null_env = self.type_mapper().ptr_type().const_null();
                    let call_result = self
                        .builder()
                        .build_call(stub_fn, &[null_env.into()], "stub_call")
                        .map_err(|e| CodegenError::Internal(format!("stub call: {:?}", e)))?;
                    if let Some(ret_val) = call_result.try_as_basic_value().basic() {
                        Ok(Some(ret_val))
                    } else {
                        Ok(Some(self.type_mapper().ptr_type().const_null().into()))
                    }
                }
            }
            _ => {
                // Indirect call - evaluate the function expression
                let func_val = self.lower_expr(current)?.ok_or_else(|| {
                    CodegenError::Internal("function expression has no value".to_string())
                })?;

                // Treat as closure call
                self.lower_closure_call(func_val, &args)
            }
        }
    }

    /// Lower a direct function call (to a known function).
    /// `callee_ty` is the Haskell-level type of the callee, used for E.45 Integer promotion.
    fn lower_direct_call(
        &mut self,
        fn_val: FunctionValue<'ctx>,
        args: &[&Expr],
        callee_ty: Option<&Ty>,
    ) -> CodegenResult<Option<BasicValueEnum<'ctx>>> {
        // Get the function's expected parameter count (excluding env pointer)
        let fn_type = fn_val.get_type();
        let expected_params = fn_type.count_param_types() as usize;
        let expected_args = if expected_params > 0 {
            expected_params - 1
        } else {
            0
        }; // Subtract env pointer

        // Check for over-application: more args than the function expects
        if args.len() > expected_args {
            // Split args: first part goes to this function, rest goes to the returned closure
            let (fn_args, remaining_args) = args.split_at(expected_args);

            // Call the function with its expected args
            let closure_result = self.lower_direct_call_inner(fn_val, fn_args, callee_ty)?;

            // The result should be a closure - call it with remaining args
            if let Some(closure_val) = closure_result {
                return self.lower_closure_call(closure_val, remaining_args);
            } else {
                return Err(CodegenError::Internal(
                    "function returned no value for over-application".to_string(),
                ));
            }
        }

        // Check for under-application: fewer args than the function expects
        if args.len() < expected_args {
            return self.lower_partial_application(fn_val, args, expected_args);
        }

        // Exact application
        self.lower_direct_call_inner(fn_val, args, callee_ty)
    }

    /// Lower a partial application (under-application) to a PAP closure.
    ///
    /// Creates a PAP wrapper function that takes the remaining args and calls
    /// the original function with all args combined.
    fn lower_partial_application(
        &mut self,
        fn_val: FunctionValue<'ctx>,
        applied_args: &[&Expr],
        expected_args: usize,
    ) -> CodegenResult<Option<BasicValueEnum<'ctx>>> {
        let tm = self.type_mapper();
        let ptr_type = tm.ptr_type();
        let i64_type = tm.i64_type();

        let num_applied = applied_args.len();
        let num_remaining = expected_args - num_applied;

        // Lower the applied arguments
        let was_tail = self.in_tail_position;
        self.in_tail_position = false;

        let mut applied_vals: Vec<(VarId, BasicValueEnum<'ctx>)> = Vec::new();
        for (i, arg_expr) in applied_args.iter().enumerate() {
            if let Some(val) = self.lower_expr(arg_expr)? {
                // Use a dummy VarId for PAP captured args
                applied_vals.push((VarId::new(10000 + i), val));
            }
        }

        self.in_tail_position = was_tail;

        // Create the PAP wrapper function
        // Signature: (ptr env, ptr arg1, ptr arg2, ...) -> ptr
        let fn_name = fn_val.get_name().to_str().unwrap_or("fn");
        let wrapper_name = format!("pap_{}_{}", fn_name, num_applied);

        // Check if wrapper already exists
        let wrapper_fn = if let Some(existing) =
            self.module.llvm_module().get_function(&wrapper_name)
        {
            existing
        } else {
            // Create param types: env + remaining args
            let mut wrapper_param_types: Vec<inkwell::types::BasicMetadataTypeEnum<'ctx>> =
                Vec::new();
            wrapper_param_types.push(ptr_type.into()); // env/closure pointer
            for _ in 0..num_remaining {
                wrapper_param_types.push(ptr_type.into());
            }

            let wrapper_fn_type = ptr_type.fn_type(&wrapper_param_types, false);
            let wrapper_fn =
                self.module
                    .llvm_module()
                    .add_function(&wrapper_name, wrapper_fn_type, None);

            // Build the wrapper function body
            let entry_bb = self.llvm_ctx.append_basic_block(wrapper_fn, "entry");
            let current_bb = self.builder().get_insert_block();

            self.builder().position_at_end(entry_bb);

            // Extract applied args from closure environment
            let closure_ptr = wrapper_fn.get_first_param().unwrap().into_pointer_value();
            let closure_ty = self.closure_type(num_applied as u32);

            let mut call_args: Vec<inkwell::values::BasicMetadataValueEnum<'ctx>> = Vec::new();
            // Original function expects null env for direct calls
            call_args.push(ptr_type.const_null().into());

            // Load applied args from env
            for i in 0..num_applied {
                let env_slot = self
                    .builder()
                    .build_struct_gep(closure_ty, closure_ptr, 2, "env_slot")
                    .map_err(|e| CodegenError::Internal(format!("PAP gep failed: {:?}", e)))?;

                let elem_ptr = unsafe {
                    self.builder()
                        .build_in_bounds_gep(
                            ptr_type.array_type(num_applied as u32),
                            env_slot,
                            &[i64_type.const_zero(), i64_type.const_int(i as u64, false)],
                            &format!("pap_arg_{}", i),
                        )
                        .map_err(|e| {
                            CodegenError::Internal(format!("PAP elem gep failed: {:?}", e))
                        })?
                };

                let arg_val = self
                    .builder()
                    .build_load(ptr_type, elem_ptr, &format!("pap_load_{}", i))
                    .map_err(|e| CodegenError::Internal(format!("PAP load failed: {:?}", e)))?;

                call_args.push(arg_val.into());
            }

            // Add remaining args from wrapper params
            for i in 0..num_remaining {
                let param = wrapper_fn.get_nth_param((i + 1) as u32).unwrap(); // +1 to skip env
                call_args.push(param.into());
            }

            // Call the original function
            let result = self
                .builder()
                .build_call(fn_val, &call_args, "pap_call")
                .map_err(|e| CodegenError::Internal(format!("PAP call failed: {:?}", e)))?
                .try_as_basic_value()
                .basic();

            // Return the result
            if let Some(ret_val) = result {
                self.builder()
                    .build_return(Some(&ret_val))
                    .map_err(|e| CodegenError::Internal(format!("PAP return failed: {:?}", e)))?;
            } else {
                self.builder()
                    .build_return(Some(&ptr_type.const_null()))
                    .map_err(|e| CodegenError::Internal(format!("PAP return failed: {:?}", e)))?;
            }

            // Restore insertion point
            if let Some(bb) = current_bb {
                self.builder().position_at_end(bb);
            }

            wrapper_fn
        };

        // Create closure pointing to the wrapper with applied args as env
        let wrapper_ptr = wrapper_fn.as_global_value().as_pointer_value();
        let closure_ptr = self.alloc_closure(wrapper_ptr, &applied_vals)?;

        Ok(Some(closure_ptr.into()))
    }

    /// Inner implementation of direct call (doesn't handle over-application).
    /// `callee_ty` is the Haskell-level type of the callee, used for E.45 Integer promotion.
    fn lower_direct_call_inner(
        &mut self,
        fn_val: FunctionValue<'ctx>,
        args: &[&Expr],
        callee_ty: Option<&Ty>,
    ) -> CodegenResult<Option<BasicValueEnum<'ctx>>> {
        // Arguments are not in tail position
        let was_tail = self.in_tail_position;
        self.in_tail_position = false;

        // All functions take (env_ptr, args...) for uniform calling convention
        let mut llvm_args = Vec::new();
        // First arg is env/closure pointer (null for direct calls)
        let null_env = self.type_mapper().ptr_type().const_null();
        llvm_args.push(null_env.into());

        // Lower remaining arguments and convert to pointers
        for (i, arg_expr) in args.iter().enumerate() {
            if let Some(val) = self.lower_expr(arg_expr)? {
                // E.45: If callee expects Integer at this position and we have an Int value,
                // promote to Integer via bhc_integer_from_i64 instead of int_to_ptr
                let ptr_val = if val.is_int_value()
                    && callee_ty.is_some_and(|ty| self.is_integer_param_at(ty, i))
                {
                    self.promote_to_integer(val)?.into_pointer_value()
                } else {
                    self.value_to_ptr(val)?
                };
                llvm_args.push(ptr_val.into());
            }
        }

        // Restore tail position flag
        self.in_tail_position = was_tail;

        // Build the call
        let call = self
            .builder()
            .build_call(fn_val, &llvm_args, "call")
            .map_err(|e| CodegenError::Internal(format!("failed to build call: {:?}", e)))?;

        // Mark as tail call if in tail position
        if self.in_tail_position {
            call.set_tail_call(true);
        }

        Ok(call.try_as_basic_value().basic())
    }

    /// Lower a closure call (indirect call through closure struct).
    fn lower_closure_call(
        &mut self,
        closure_val: BasicValueEnum<'ctx>,
        args: &[&Expr],
    ) -> CodegenResult<Option<BasicValueEnum<'ctx>>> {
        let tm = self.type_mapper();
        let ptr_type = tm.ptr_type();

        // Get closure pointer
        let closure_ptr = match closure_val {
            BasicValueEnum::PointerValue(p) => p,
            _ => {
                return Err(CodegenError::Internal(
                    "closure value is not a pointer".to_string(),
                ))
            }
        };

        // Extract function pointer from closure
        let fn_ptr = self.extract_closure_fn_ptr(closure_ptr)?;

        // Build function type for the call:
        // - First param is the closure pointer (environment)
        // - Remaining params are the arguments
        let mut param_types: Vec<inkwell::types::BasicMetadataTypeEnum<'ctx>> = Vec::new();
        param_types.push(ptr_type.into()); // closure/env pointer

        for _ in args {
            param_types.push(ptr_type.into());
        }

        let fn_type = ptr_type.fn_type(&param_types, false);

        // Arguments are not in tail position
        let was_tail = self.in_tail_position;
        self.in_tail_position = false;

        // Lower arguments
        let mut llvm_args: Vec<inkwell::values::BasicMetadataValueEnum<'ctx>> = Vec::new();
        llvm_args.push(closure_ptr.into()); // Pass closure as first argument (environment)

        for arg_expr in args {
            if let Some(val) = self.lower_expr(arg_expr)? {
                // Convert to pointer for uniform calling convention
                let ptr_val = self.value_to_ptr(val)?;
                llvm_args.push(ptr_val.into());
            }
        }

        // Restore tail position flag
        self.in_tail_position = was_tail;

        // Build indirect call through function pointer
        let call = self
            .builder()
            .build_indirect_call(fn_type, fn_ptr, &llvm_args, "closure_call")
            .map_err(|e| {
                CodegenError::Internal(format!("failed to build closure call: {:?}", e))
            })?;

        // Mark as tail call if in tail position
        if self.in_tail_position {
            call.set_tail_call(true);
        }

        Ok(call.try_as_basic_value().basic())
    }

    /// Lower a constructor application to an ADT value.
    ///
    /// Allocates an ADT value with the given tag and stores the arguments as fields.
    fn lower_constructor_application(
        &mut self,
        tag: u32,
        arity: u32,
        args: &[&Expr],
    ) -> CodegenResult<Option<BasicValueEnum<'ctx>>> {
        // Allocate the ADT value
        let adt_ptr = self.alloc_adt(tag, arity)?;

        // Store each argument as a field
        for (i, arg_expr) in args.iter().enumerate() {
            if let Some(arg_val) = self.lower_expr(arg_expr)? {
                self.store_adt_field(adt_ptr, arity, i as u32, arg_val)?;
            }
        }

        // Return the pointer to the ADT value
        Ok(Some(adt_ptr.into()))
    }

    /// Lower a let binding.
    fn lower_let(
        &mut self,
        bind: &Bind,
        body: &Expr,
    ) -> CodegenResult<Option<BasicValueEnum<'ctx>>> {
        match bind {
            Bind::NonRec(var, rhs) => {
                // E.58: Lazy let-bindings.
                // Thunk non-trivial expressions for lazy evaluation.
                // Trivial expressions (literals, vars, lambdas) are evaluated eagerly.
                let should_thunk = !Self::is_trivial_expr(rhs.as_ref());

                if should_thunk {
                    // E.45: Track Integer variables BEFORE thunking
                    // (is_integer_expr inspects expression structure, not thunk wrapper)
                    if self.is_integer_expr(rhs.as_ref()) {
                        self.integer_vars.insert(var.id);
                    }

                    // Create a thunk for lazy evaluation
                    let thunk_result = self.lower_lazy(rhs.as_ref())?;
                    if let Some(val) = thunk_result {
                        self.env.insert(var.id, val);
                        self.thunked_vars.insert(var.id);
                    }
                } else {
                    // Eager evaluation for trivial expressions
                    let was_tail = self.in_tail_position;
                    self.in_tail_position = false;
                    let rhs_result = self.lower_expr(rhs.as_ref())?;
                    self.in_tail_position = was_tail;

                    if let Some(val) = rhs_result {
                        self.env.insert(var.id, val);
                    }

                    // E.45: Track Integer variables for show dispatch
                    if self.is_integer_expr(rhs.as_ref()) {
                        self.integer_vars.insert(var.id);
                    }
                }

                // Lower the body (preserves tail position from parent)
                let result = self.lower_expr(body)?;

                // Remove bindings for proper scoping
                self.env.remove(&var.id);
                self.thunked_vars.remove(&var.id);

                Ok(result)
            }

            Bind::Rec(bindings) => {
                // For recursive let bindings, we lift them to top-level functions.
                // Free variables from the enclosing scope are captured via the
                // closure environment (param 0).

                // Save the current insertion point
                let current_block = self.builder().get_insert_block();

                // Compute free variables for the entire rec group
                let rec_var_ids: FxHashSet<VarId> = bindings.iter().map(|(v, _)| v.id).collect();
                let mut all_free = FxHashSet::default();
                for (_var, expr) in bindings {
                    let fv = self.free_vars(expr);
                    for v in fv {
                        if !rec_var_ids.contains(&v) {
                            all_free.insert(v);
                        }
                    }
                }

                // Collect captured values (free vars that exist in current env)
                let captured: Vec<(VarId, BasicValueEnum<'ctx>)> = all_free
                    .iter()
                    .filter_map(|vid| self.env.get(vid).map(|val| (*vid, *val)))
                    .collect();

                // First pass: declare all recursive functions
                for (var, _expr) in bindings {
                    let lifted_name = format!("{}${}", var.name.as_str(), var.id.index());
                    let fn_type = self.lower_function_type(&var.ty)?;
                    let fn_val = self.module.add_function(&lifted_name, fn_type);
                    self.functions.insert(var.id, fn_val);
                }

                // Second pass: define all recursive functions, injecting captured
                // free variables from the closure env into self.env
                for (var, expr) in bindings {
                    self.lower_recursive_function_with_captures(var, expr, &captured)?;
                }

                // Restore insertion point
                if let Some(block) = current_block {
                    self.builder().position_at_end(block);
                }

                // Restore captured variable values in env (they were overwritten
                // during recursive function body lowering with values from that
                // function's closure env which are invalid in this scope).
                for (vid, val) in &captured {
                    self.env.insert(*vid, *val);
                }

                // Create closures for the lifted functions so they can be
                // called (the closure env carries the captured free variables).
                for (var, _expr) in bindings {
                    let fn_val = self.functions[&var.id];
                    let fn_ptr = fn_val.as_global_value().as_pointer_value();
                    let closure_ptr = self.alloc_closure(fn_ptr, &captured)?;
                    self.env.insert(var.id, closure_ptr.into());
                }

                // Lower the body (recursive functions are now available)
                let result = self.lower_expr(body)?;

                Ok(result)
            }
        }
    }

    /// Lower a recursive function that was lifted from a let binding.
    // Retained for let-lifted recursive function lowering.
    #[allow(dead_code)]
    fn lower_recursive_function(&mut self, var: &Var, expr: &Expr) -> CodegenResult<()> {
        let fn_val = self.functions.get(&var.id).copied().ok_or_else(|| {
            CodegenError::Internal(format!(
                "recursive function not declared: {}",
                var.name.as_str()
            ))
        })?;

        // Create entry block
        let entry = self.llvm_context().append_basic_block(fn_val, "entry");
        self.builder().position_at_end(entry);

        // E.45: Extract Integer parameter positions from the function's type signature
        let integer_param_positions = self.extract_integer_param_positions(&var.ty);

        // Handle lambda parameters
        let result = self.lower_function_body(fn_val, expr, &integer_param_positions)?;

        // Check if the current block already has a terminator (e.g., from `error` or `unreachable`)
        // If so, don't add another terminator
        let current_block = self.builder().get_insert_block();
        let has_terminator = current_block
            .map(|bb| bb.get_terminator().is_some())
            .unwrap_or(false);

        if !has_terminator {
            // Build return - convert to pointer if return type is pointer (uniform calling convention)
            let ret_type = fn_val.get_type().get_return_type();
            if let Some(val) = result {
                let ret_val: BasicValueEnum<'ctx> =
                    if ret_type == Some(self.type_mapper().ptr_type().into()) {
                        self.value_to_ptr(val)?.into()
                    } else {
                        val
                    };
                self.builder().build_return(Some(&ret_val)).map_err(|e| {
                    CodegenError::Internal(format!("failed to build return: {:?}", e))
                })?;
            } else {
                self.builder().build_return(None).map_err(|e| {
                    CodegenError::Internal(format!("failed to build return: {:?}", e))
                })?;
            }
        }

        Ok(())
    }

    /// Lower a recursive function with captured free variables from enclosing scope.
    /// The captured variables are extracted from the closure environment (param 0).
    fn lower_recursive_function_with_captures(
        &mut self,
        var: &Var,
        expr: &Expr,
        captures: &[(VarId, BasicValueEnum<'ctx>)],
    ) -> CodegenResult<()> {
        let fn_val = self.functions.get(&var.id).copied().ok_or_else(|| {
            CodegenError::Internal(format!(
                "recursive function not declared: {}",
                var.name.as_str()
            ))
        })?;

        // Create entry block
        let entry = self.llvm_context().append_basic_block(fn_val, "entry");
        self.builder().position_at_end(entry);

        // Extract captured variables from closure env (param 0)
        if !captures.is_empty() {
            if let Some(env_ptr) = fn_val.get_nth_param(0) {
                let env_ptr = env_ptr.into_pointer_value();
                let env_size = captures.len() as u32;
                for (i, (vid, _)) in captures.iter().enumerate() {
                    let val = self.extract_closure_env_elem(env_ptr, env_size, i as u32)?;
                    self.env.insert(*vid, val.into());
                }
            }
        }

        // E.45: Extract Integer parameter positions from the function's type signature
        let integer_param_positions = self.extract_integer_param_positions(&var.ty);

        // Handle lambda parameters
        let result = self.lower_function_body(fn_val, expr, &integer_param_positions)?;

        // Check if the current block already has a terminator
        let current_block = self.builder().get_insert_block();
        let has_terminator = current_block
            .map(|bb| bb.get_terminator().is_some())
            .unwrap_or(false);

        if !has_terminator {
            let ret_type = fn_val.get_type().get_return_type();
            if let Some(val) = result {
                let ret_val: BasicValueEnum<'ctx> =
                    if ret_type == Some(self.type_mapper().ptr_type().into()) {
                        self.value_to_ptr(val)?.into()
                    } else {
                        val
                    };
                self.builder().build_return(Some(&ret_val)).map_err(|e| {
                    CodegenError::Internal(format!("failed to build return: {:?}", e))
                })?;
            } else {
                self.builder().build_return(None).map_err(|e| {
                    CodegenError::Internal(format!("failed to build return: {:?}", e))
                })?;
            }
        }

        Ok(())
    }

    /// Lower a function body, handling lambda parameters.
    fn lower_function_body(
        &mut self,
        fn_val: FunctionValue<'ctx>,
        expr: &Expr,
        integer_param_positions: &[bool],
    ) -> CodegenResult<Option<BasicValueEnum<'ctx>>> {
        // If the expression is a lambda, bind parameters to function arguments
        // Note: param_idx starts at 1 because index 0 is the env/closure pointer
        let mut current = expr;
        let mut param_idx = 1;
        let mut lambda_idx = 0usize; // Index into integer_param_positions

        // Unwrap lambdas, type lambdas, and trivial case expressions.
        // - Lambdas (Lam): bind parameter to function argument
        // - Type lambdas (TyLam): erased at runtime, skip
        // - Trivial Case: pattern matching on a variable we just bound, rebind and continue
        loop {
            match current {
                Expr::Lam(param, body, _span) => {
                    // Term lambda - bind parameter to function argument
                    if let Some(arg) = fn_val.get_nth_param(param_idx) {
                        self.env.insert(param.id, arg);
                    }
                    // E.45: Track Integer parameters using function type signature
                    // (param.ty is Ty::Error in Core IR, so use declared type instead)
                    if self.is_integer_type(&param.ty)
                        || integer_param_positions
                            .get(lambda_idx)
                            .copied()
                            .unwrap_or(false)
                    {
                        self.integer_vars.insert(param.id);
                    }
                    param_idx += 1;
                    lambda_idx += 1;
                    current = body.as_ref();
                }
                Expr::TyLam(_tyvar, body, _span) => {
                    // Type lambda - skip (erased at runtime)
                    current = body.as_ref();
                }
                Expr::Case(scrut, alts, _ty, _span) => {
                    // Check if this is a "trivial" case that just rebinds a variable.
                    // This pattern comes from HIR->Core lowering for pattern matching
                    // on function parameters.
                    //
                    // Pattern: case x of { _ -> body } or case x of { y -> body }
                    // where x is a variable we already have bound AND all alternatives are Default.
                    //
                    // IMPORTANT: Only optimize when ALL alternatives are Default.
                    // If there are any literal or constructor patterns, we must do real pattern matching.
                    let all_default = alts.iter().all(|a| matches!(a.con, AltCon::Default));
                    if all_default {
                        if let Expr::Var(scrut_var, _) = scrut.as_ref() {
                            // Use the first Default alternative
                            if let Some(alt) = alts.first() {
                                // If the alternative has a binder, bind it to the same value as scrut
                                for binder in &alt.binders {
                                    if let Some(val) = self.env.get(&scrut_var.id) {
                                        self.env.insert(binder.id, *val);
                                    }
                                }
                                current = &alt.rhs;
                                continue;
                            }
                        }
                    }
                    // Not a trivial case, stop unwrapping
                    break;
                }
                _ => break,
            }
        }

        // Check if there are remaining LLVM parameters that weren't bound to lambdas.
        // This happens for definitions like `add5 = add 5` where the body is not a lambda
        // but the type implies it takes arguments. We need to eta-expand at codegen.
        let total_params = fn_val.count_params();
        let remaining_params: Vec<_> = (param_idx..total_params)
            .filter_map(|i| fn_val.get_nth_param(i))
            .collect();

        // Lower the body
        let was_tail = self.in_tail_position;
        self.in_tail_position = remaining_params.is_empty(); // Only tail if no eta-expansion needed
        let result = self.lower_expr(current)?;
        self.in_tail_position = was_tail;

        // If there are remaining parameters, the body should evaluate to a closure.
        // Apply the remaining parameters to it (eta-expansion).
        if !remaining_params.is_empty() {
            if let Some(closure_val) = result {
                // The result should be a closure - call it with remaining params
                let tm = self.type_mapper();
                let ptr_type = tm.ptr_type();

                let closure_ptr = match closure_val {
                    BasicValueEnum::PointerValue(p) => p,
                    _ => {
                        return Err(CodegenError::Internal(
                            "eta-expansion: expected closure but got non-pointer".to_string(),
                        ))
                    }
                };

                // Extract function pointer from closure
                let fn_ptr = self.extract_closure_fn_ptr(closure_ptr)?;

                // Build function type for the call
                let mut param_types: Vec<inkwell::types::BasicMetadataTypeEnum<'ctx>> = Vec::new();
                param_types.push(ptr_type.into()); // closure/env pointer
                for _ in &remaining_params {
                    param_types.push(ptr_type.into());
                }
                let fn_type = ptr_type.fn_type(&param_types, false);

                // Build args: closure ptr + remaining params
                let mut llvm_args: Vec<inkwell::values::BasicMetadataValueEnum<'ctx>> = Vec::new();
                llvm_args.push(closure_ptr.into());
                for param in &remaining_params {
                    // Convert to pointer if needed
                    let ptr_val = self.value_to_ptr(*param)?;
                    llvm_args.push(ptr_val.into());
                }

                // Build indirect call
                let call = self
                    .builder()
                    .build_indirect_call(fn_type, fn_ptr, &llvm_args, "eta_call")
                    .map_err(|e| {
                        CodegenError::Internal(format!(
                            "failed to build eta-expansion call: {:?}",
                            e
                        ))
                    })?;

                return Ok(call.try_as_basic_value().basic());
            } else {
                return Err(CodegenError::Internal(
                    "eta-expansion: body has no value".to_string(),
                ));
            }
        }

        Ok(result)
    }

    /// Lower a case expression.
    ///
    /// Handles three cases:
    /// 1. Literal patterns (Int, Char) - switch on the primitive value
    /// 2. Constructor patterns (DataCon) - switch on the tag, extract fields
    /// 3. Default pattern - catch-all fallback
    fn lower_case(
        &mut self,
        scrut: &Expr,
        alts: &[Alt],
        case_ty: &Ty,
    ) -> CodegenResult<Option<BasicValueEnum<'ctx>>> {
        // EmptyCase: `case x of {}` — zero alternatives means bottom/unreachable
        if alts.is_empty() {
            // Still evaluate the scrutinee for side effects
            let was_tail = self.in_tail_position;
            self.in_tail_position = false;
            let _scrut_val = self.lower_expr(scrut)?;
            self.in_tail_position = was_tail;
            self.builder().build_unreachable().map_err(|e| {
                CodegenError::Internal(format!(
                    "failed to build unreachable for empty case: {:?}",
                    e
                ))
            })?;
            return Ok(None);
        }

        // Scrutinee is NOT in tail position
        let was_tail = self.in_tail_position;
        self.in_tail_position = false;
        let scrut_val = self
            .lower_expr(scrut)?
            .ok_or_else(|| CodegenError::Internal("scrutinee has no value".to_string()))?;
        self.in_tail_position = was_tail;

        // Check if all alternatives are Default (no actual pattern matching needed)
        // This happens for simple variable patterns like `f n = 42`
        let all_default = alts.iter().all(|alt| matches!(&alt.con, AltCon::Default));
        if all_default {
            // Just use the first (and likely only) default alternative
            let alt = &alts[0];

            // Bind any pattern variables to the scrutinee value
            for binder in &alt.binders {
                self.env.insert(binder.id, scrut_val);
            }

            // Lower the RHS (inherits tail position from parent case)
            let result = self.lower_expr(&alt.rhs)?;

            // Clean up bindings
            for binder in &alt.binders {
                self.env.remove(&binder.id);
            }

            return Ok(result);
        }

        // Determine if this is a constructor case or a literal case
        let has_datacon = alts
            .iter()
            .any(|alt| matches!(&alt.con, AltCon::DataCon(_)));

        // Check if this is a Bool case (True/False patterns) with an integer scrutinee
        // This happens when the condition is a comparison result
        let is_bool_case = alts.iter().any(|alt| {
            if let AltCon::DataCon(con) = &alt.con {
                let name = con.name.as_str();
                name == "True"
                    || name == "False"
                    || name == "GHC.Types.True"
                    || name == "GHC.Types.False"
            } else {
                false
            }
        });

        if is_bool_case {
            // Bool case - convert scrutinee to integer if needed
            let int_scrut = match scrut_val {
                BasicValueEnum::IntValue(_) => scrut_val,
                BasicValueEnum::PointerValue(p) => {
                    // The pointer might be:
                    // 1. A valid Bool ADT pointer (from constructor allocation)
                    // 2. A small integer (0 or 1) cast to pointer (from primop result)
                    //
                    // We need to distinguish these cases. Check if pointer value <= 1,
                    // in which case it's a raw boolean, otherwise load from the ADT.
                    let ptr_as_int = self
                        .builder()
                        .build_ptr_to_int(p, self.type_mapper().i64_type(), "ptr_as_int")
                        .map_err(|e| {
                            CodegenError::Internal(format!("failed to convert ptr to int: {:?}", e))
                        })?;

                    // Check if it's a small integer (0 or 1)
                    let is_raw_bool = self
                        .builder()
                        .build_int_compare(
                            inkwell::IntPredicate::ULE,
                            ptr_as_int,
                            self.type_mapper().i64_type().const_int(1, false),
                            "is_raw_bool",
                        )
                        .map_err(|e| {
                            CodegenError::Internal(format!("failed to compare ptr: {:?}", e))
                        })?;

                    // Create blocks for the two cases
                    let current_fn = self
                        .builder()
                        .get_insert_block()
                        .ok_or_else(|| CodegenError::Internal("no insert block".to_string()))?
                        .get_parent()
                        .ok_or_else(|| CodegenError::Internal("no parent function".to_string()))?;
                    let raw_bool_block = self.llvm_ctx.append_basic_block(current_fn, "raw_bool");
                    let adt_bool_block = self.llvm_ctx.append_basic_block(current_fn, "adt_bool");
                    let merge_block = self.llvm_ctx.append_basic_block(current_fn, "bool_merge");

                    self.builder()
                        .build_conditional_branch(is_raw_bool, raw_bool_block, adt_bool_block)
                        .map_err(|e| {
                            CodegenError::Internal(format!("failed to build cond branch: {:?}", e))
                        })?;

                    // Raw bool case: pointer value IS the boolean (0 or 1)
                    self.builder().position_at_end(raw_bool_block);
                    let raw_val = ptr_as_int;
                    self.builder()
                        .build_unconditional_branch(merge_block)
                        .map_err(|e| {
                            CodegenError::Internal(format!("failed to build branch: {:?}", e))
                        })?;
                    let raw_bool_end = self.builder().get_insert_block().unwrap();

                    // ADT bool case: load the tag from the ADT structure
                    self.builder().position_at_end(adt_bool_block);
                    let adt_ty = self.adt_type(0);
                    let tag_ptr = self
                        .builder()
                        .build_struct_gep(adt_ty, p, 0, "bool_tag_ptr")
                        .map_err(|e| {
                            CodegenError::Internal(format!("failed to get bool tag ptr: {:?}", e))
                        })?;
                    let adt_val = self
                        .builder()
                        .build_load(self.type_mapper().i64_type(), tag_ptr, "bool_tag")
                        .map_err(|e| {
                            CodegenError::Internal(format!("failed to load bool tag: {:?}", e))
                        })?
                        .into_int_value();
                    self.builder()
                        .build_unconditional_branch(merge_block)
                        .map_err(|e| {
                            CodegenError::Internal(format!("failed to build branch: {:?}", e))
                        })?;
                    let adt_bool_end = self.builder().get_insert_block().unwrap();

                    // Merge
                    self.builder().position_at_end(merge_block);
                    let phi = self
                        .builder()
                        .build_phi(self.type_mapper().i64_type(), "bool_val")
                        .map_err(|e| {
                            CodegenError::Internal(format!("failed to build phi: {:?}", e))
                        })?;
                    phi.add_incoming(&[(&raw_val, raw_bool_end), (&adt_val, adt_bool_end)]);
                    phi.as_basic_value()
                }
                _ => scrut_val, // Fall through to datacon handling
            };
            if matches!(int_scrut, BasicValueEnum::IntValue(_)) {
                return self.lower_case_bool_as_int(int_scrut, alts, case_ty);
            }
        }

        // Get the scrutinee's type for determining binder types
        let scrut_ty = scrut.ty();

        if has_datacon {
            self.lower_case_datacon(scrut_val, alts, &scrut_ty)
        } else {
            self.lower_case_literal(scrut_val, alts)
        }
    }

    /// Lower a case expression with literal patterns.
    fn lower_case_literal(
        &mut self,
        scrut_val: BasicValueEnum<'ctx>,
        alts: &[Alt],
    ) -> CodegenResult<Option<BasicValueEnum<'ctx>>> {
        // Determine the type of literals in the alternatives
        let has_int_lit = alts
            .iter()
            .any(|alt| matches!(&alt.con, AltCon::Lit(Literal::Int(_))));
        let has_char_lit = alts
            .iter()
            .any(|alt| matches!(&alt.con, AltCon::Lit(Literal::Char(_))));
        let has_float_lit = alts
            .iter()
            .any(|alt| matches!(&alt.con, AltCon::Lit(Literal::Double(_))));
        let has_string_lit = alts
            .iter()
            .any(|alt| matches!(&alt.con, AltCon::Lit(Literal::String(_))));

        // Dispatch based on scrutinee type
        match scrut_val {
            BasicValueEnum::IntValue(i) => self.lower_case_literal_int(i, alts),
            BasicValueEnum::FloatValue(f) => self.lower_case_literal_float(f, alts),
            BasicValueEnum::PointerValue(p) => {
                // Check if the alternatives have integer/char literals - if so, unbox the pointer
                if has_int_lit || has_char_lit {
                    // Pointer contains a boxed integer - unbox it
                    let int_val = self
                        .builder()
                        .build_ptr_to_int(p, self.type_mapper().i64_type(), "unbox_for_case")
                        .map_err(|e| {
                            CodegenError::Internal(format!("failed to unbox for case: {:?}", e))
                        })?;
                    self.lower_case_literal_int(int_val, alts)
                } else if has_float_lit {
                    // Pointer contains a boxed float - unbox it
                    let bits = self
                        .builder()
                        .build_ptr_to_int(p, self.type_mapper().i64_type(), "unbox_float_bits")
                        .map_err(|e| {
                            CodegenError::Internal(format!("failed to unbox float: {:?}", e))
                        })?;
                    let float_val = self
                        .builder()
                        .build_bit_cast(bits, self.type_mapper().f64_type(), "to_double")
                        .map_err(|e| {
                            CodegenError::Internal(format!("failed to cast to double: {:?}", e))
                        })?;
                    if let BasicValueEnum::FloatValue(f) = float_val {
                        self.lower_case_literal_float(f, alts)
                    } else {
                        Err(CodegenError::Internal(
                            "expected float value after unboxing".to_string(),
                        ))
                    }
                } else if has_string_lit {
                    // String pattern matching
                    self.lower_case_literal_string(p, alts)
                } else {
                    // Default case or only Default alternatives - assume integer
                    let int_val = self
                        .builder()
                        .build_ptr_to_int(p, self.type_mapper().i64_type(), "unbox_for_case")
                        .map_err(|e| {
                            CodegenError::Internal(format!("failed to unbox for case: {:?}", e))
                        })?;
                    self.lower_case_literal_int(int_val, alts)
                }
            }
            _ => Err(CodegenError::Unsupported(format!(
                "unsupported scrutinee type for literal case: {:?}",
                scrut_val.get_type()
            ))),
        }
    }

    /// Lower a case expression with integer literal patterns.
    fn lower_case_literal_int(
        &mut self,
        scrut_int: IntValue<'ctx>,
        alts: &[Alt],
    ) -> CodegenResult<Option<BasicValueEnum<'ctx>>> {
        let current_fn = self
            .builder()
            .get_insert_block()
            .ok_or_else(|| CodegenError::Internal("no insert block".to_string()))?
            .get_parent()
            .ok_or_else(|| CodegenError::Internal("no parent function".to_string()))?;

        // Create blocks for each alternative
        let mut blocks = Vec::new();
        let merge_block = self
            .llvm_context()
            .append_basic_block(current_fn, "case_merge");

        let mut default_block = None;
        let mut cases = Vec::new();

        for alt in alts {
            let block = self
                .llvm_context()
                .append_basic_block(current_fn, "case_alt");
            blocks.push(block);

            match &alt.con {
                AltCon::Lit(Literal::Int(n)) => {
                    cases.push((
                        self.type_mapper().i64_type().const_int(*n as u64, true),
                        block,
                    ));
                }
                AltCon::Lit(Literal::Char(c)) => {
                    // Use the same integer type as the scrutinee to avoid type mismatches
                    cases.push((scrut_int.get_type().const_int(*c as u64, false), block));
                }
                AltCon::Default => {
                    // Use the FIRST default block as the switch default.
                    // The pattern compiler adds error fallbacks as additional Defaults,
                    // but we want non-matching cases to go to the first (user-defined) default.
                    if default_block.is_none() {
                        default_block = Some(block);
                    }
                }
                _ => {
                    return Err(CodegenError::Unsupported(format!(
                        "unsupported pattern in literal case: {:?}",
                        alt.con
                    )))
                }
            }
        }

        // Build switch
        // If there's no explicit default, create an unreachable block
        let default = if let Some(db) = default_block {
            db
        } else {
            let unreachable_block = self
                .llvm_context()
                .append_basic_block(current_fn, "case_unreachable");
            unreachable_block
        };

        let _switch = self
            .builder()
            .build_switch(scrut_int, default, &cases)
            .map_err(|e| CodegenError::Internal(format!("failed to build switch: {:?}", e)))?;

        // If we created an unreachable block, fill it in
        if default_block.is_none() {
            self.builder().position_at_end(default);
            self.builder().build_unreachable().map_err(|e| {
                CodegenError::Internal(format!("failed to build unreachable: {:?}", e))
            })?;
        }

        // Generate code for each alternative
        self.lower_case_alternatives(alts, &blocks, merge_block)
    }

    /// Lower a case expression with float/double literal patterns.
    /// Uses chained comparisons since LLVM switch doesn't support floats.
    fn lower_case_literal_float(
        &mut self,
        scrut_float: FloatValue<'ctx>,
        alts: &[Alt],
    ) -> CodegenResult<Option<BasicValueEnum<'ctx>>> {
        let current_fn = self
            .builder()
            .get_insert_block()
            .ok_or_else(|| CodegenError::Internal("no insert block".to_string()))?
            .get_parent()
            .ok_or_else(|| CodegenError::Internal("no parent function".to_string()))?;

        let merge_block = self
            .llvm_context()
            .append_basic_block(current_fn, "float_case_merge");

        // Separate literal alts from default
        let mut literal_alts: Vec<(f64, &Alt)> = Vec::new();
        let mut default_alt: Option<&Alt> = None;

        for alt in alts {
            match &alt.con {
                AltCon::Lit(Literal::Float(f)) => literal_alts.push((*f as f64, alt)),
                AltCon::Lit(Literal::Double(d)) => literal_alts.push((*d, alt)),
                AltCon::Default => default_alt = Some(alt),
                _ => {
                    return Err(CodegenError::Unsupported(format!(
                        "unsupported pattern in float case: {:?}",
                        alt.con
                    )))
                }
            }
        }

        // Create all blocks upfront to avoid creating duplicate blocks
        let mut phi_values: Vec<(BasicValueEnum<'ctx>, inkwell::basic_block::BasicBlock<'ctx>)> =
            Vec::new();

        // Create the default/else block
        let default_block = self
            .llvm_context()
            .append_basic_block(current_fn, "float_default");

        // Create comparison blocks (one per literal except the first which uses current block)
        let mut cmp_blocks: Vec<inkwell::basic_block::BasicBlock<'ctx>> = Vec::new();
        for i in 1..literal_alts.len() {
            cmp_blocks.push(
                self.llvm_context()
                    .append_basic_block(current_fn, &format!("float_cmp_{}", i)),
            );
        }

        // Create match blocks (one per literal)
        let mut match_blocks: Vec<inkwell::basic_block::BasicBlock<'ctx>> = Vec::new();
        for i in 0..literal_alts.len() {
            match_blocks.push(
                self.llvm_context()
                    .append_basic_block(current_fn, &format!("float_match_{}", i)),
            );
        }

        // Generate chain of comparisons
        for (i, (val, alt)) in literal_alts.iter().enumerate() {
            // Position at the comparison block
            if i > 0 {
                self.builder().position_at_end(cmp_blocks[i - 1]);
            }
            // i == 0 uses the current block (already positioned there)

            let match_block = match_blocks[i];

            // Determine next block (next comparison or default)
            let next_block = if i + 1 < literal_alts.len() {
                cmp_blocks[i] // cmp_blocks[0] corresponds to float_cmp_1
            } else {
                default_block
            };

            // Build float comparison (ordered equal)
            // Check if it's f32 or f64 by comparing types
            let is_f32 = scrut_float.get_type() == self.type_mapper().f32_type();
            let const_val = if is_f32 {
                self.type_mapper().f32_type().const_float(*val)
            } else {
                self.type_mapper().f64_type().const_float(*val)
            };
            let cmp = self
                .builder()
                .build_float_compare(
                    inkwell::FloatPredicate::OEQ,
                    scrut_float,
                    const_val,
                    "float_eq",
                )
                .map_err(|e| {
                    CodegenError::Internal(format!("failed to build float cmp: {:?}", e))
                })?;

            self.builder()
                .build_conditional_branch(cmp, match_block, next_block)
                .map_err(|e| {
                    CodegenError::Internal(format!("failed to build cond branch: {:?}", e))
                })?;

            // Generate code for match block
            self.builder().position_at_end(match_block);
            if let Some(result) = self.lower_expr(&alt.rhs)? {
                phi_values.push((result, self.builder().get_insert_block().unwrap()));
            }
            self.builder()
                .build_unconditional_branch(merge_block)
                .map_err(|e| CodegenError::Internal(format!("failed to build branch: {:?}", e)))?;
        }

        // Generate default block
        self.builder().position_at_end(default_block);
        if let Some(alt) = default_alt {
            // Bind scrutinee to any pattern variables
            for binder in &alt.binders {
                self.env.insert(binder.id, scrut_float.into());
            }
            if let Some(result) = self.lower_expr(&alt.rhs)? {
                phi_values.push((result, self.builder().get_insert_block().unwrap()));
            }
            for binder in &alt.binders {
                self.env.remove(&binder.id);
            }
            self.builder()
                .build_unconditional_branch(merge_block)
                .map_err(|e| CodegenError::Internal(format!("failed to build branch: {:?}", e)))?;
        } else {
            // No default - generate unreachable
            self.builder().build_unreachable().map_err(|e| {
                CodegenError::Internal(format!("failed to build unreachable: {:?}", e))
            })?;
        }

        // Build phi in merge block
        self.builder().position_at_end(merge_block);
        if phi_values.is_empty() {
            Ok(None)
        } else {
            let target_type = phi_values[0].0.get_type();

            // Coerce all values to the target type
            let mut coerced_values: Vec<(
                BasicValueEnum<'ctx>,
                inkwell::basic_block::BasicBlock<'ctx>,
            )> = Vec::new();

            for (val, block) in &phi_values {
                if val.get_type() == target_type {
                    coerced_values.push((*val, *block));
                } else {
                    let terminator = block.get_terminator();
                    if let Some(term) = terminator {
                        self.builder().position_before(&term);
                        let coerced = self.coerce_to_type(*val, target_type)?;
                        coerced_values.push((coerced, *block));
                    } else {
                        coerced_values.push((*val, *block));
                    }
                }
            }

            self.builder().position_at_end(merge_block);

            let phi = self
                .builder()
                .build_phi(target_type, "float_case_result")
                .map_err(|e| CodegenError::Internal(format!("failed to build phi: {:?}", e)))?;

            for (val, block) in &coerced_values {
                phi.add_incoming(&[(val, *block)]);
            }

            Ok(Some(phi.as_basic_value()))
        }
    }

    /// Lower a case expression with string literal patterns.
    /// Uses strcmp to compare strings.
    fn lower_case_literal_string(
        &mut self,
        scrut_ptr: PointerValue<'ctx>,
        alts: &[Alt],
    ) -> CodegenResult<Option<BasicValueEnum<'ctx>>> {
        let current_fn = self
            .builder()
            .get_insert_block()
            .ok_or_else(|| CodegenError::Internal("no insert block".to_string()))?
            .get_parent()
            .ok_or_else(|| CodegenError::Internal("no parent function".to_string()))?;

        let merge_block = self
            .llvm_context()
            .append_basic_block(current_fn, "str_case_merge");

        // Get or declare strcmp
        let strcmp_fn = self.get_or_declare_strcmp()?;

        // Separate literal alts from default
        let mut literal_alts: Vec<(&Symbol, &Alt)> = Vec::new();
        let mut default_alt: Option<&Alt> = None;

        for alt in alts {
            match &alt.con {
                AltCon::Lit(Literal::String(s)) => literal_alts.push((s, alt)),
                AltCon::Default => default_alt = Some(alt),
                _ => {
                    return Err(CodegenError::Unsupported(format!(
                        "unsupported pattern in string case: {:?}",
                        alt.con
                    )))
                }
            }
        }

        // Create all blocks upfront to avoid creating duplicate blocks
        let mut phi_values: Vec<(BasicValueEnum<'ctx>, inkwell::basic_block::BasicBlock<'ctx>)> =
            Vec::new();
        let default_block = self
            .llvm_context()
            .append_basic_block(current_fn, "str_default");

        // Create comparison blocks (one per literal except the first which uses current block)
        let mut cmp_blocks: Vec<inkwell::basic_block::BasicBlock<'ctx>> = Vec::new();
        for i in 1..literal_alts.len() {
            cmp_blocks.push(
                self.llvm_context()
                    .append_basic_block(current_fn, &format!("str_cmp_{}", i)),
            );
        }

        // Create match blocks (one per literal)
        let mut match_blocks: Vec<inkwell::basic_block::BasicBlock<'ctx>> = Vec::new();
        for i in 0..literal_alts.len() {
            match_blocks.push(
                self.llvm_context()
                    .append_basic_block(current_fn, &format!("str_match_{}", i)),
            );
        }

        // Generate chain of strcmp comparisons
        for (i, (sym, alt)) in literal_alts.iter().enumerate() {
            // Position at the comparison block
            if i > 0 {
                self.builder().position_at_end(cmp_blocks[i - 1]);
            }
            // i == 0 uses the current block (already positioned there)

            let match_block = match_blocks[i];

            // Determine next block (next comparison or default)
            let next_block = if i + 1 < literal_alts.len() {
                cmp_blocks[i] // cmp_blocks[0] corresponds to str_cmp_1
            } else {
                default_block
            };

            // Create global string constant for the pattern
            let str_const = self
                .module
                .add_global_string(&format!("str_pat_{}", i), sym.as_str());

            // Call strcmp(scrut, pattern)
            let cmp_result = self
                .builder()
                .build_call(
                    strcmp_fn,
                    &[scrut_ptr.into(), str_const.into()],
                    "strcmp_result",
                )
                .map_err(|e| CodegenError::Internal(format!("failed to call strcmp: {:?}", e)))?
                .try_as_basic_value()
                .basic()
                .ok_or_else(|| CodegenError::Internal("strcmp returned void".to_string()))?;

            // strcmp returns 0 for equal strings
            let zero = self.type_mapper().i32_type().const_zero();
            let is_equal = self
                .builder()
                .build_int_compare(
                    inkwell::IntPredicate::EQ,
                    cmp_result.into_int_value(),
                    zero,
                    "str_eq",
                )
                .map_err(|e| CodegenError::Internal(format!("failed to build int cmp: {:?}", e)))?;

            self.builder()
                .build_conditional_branch(is_equal, match_block, next_block)
                .map_err(|e| {
                    CodegenError::Internal(format!("failed to build cond branch: {:?}", e))
                })?;

            // Generate match block
            self.builder().position_at_end(match_block);
            if let Some(result) = self.lower_expr(&alt.rhs)? {
                phi_values.push((result, self.builder().get_insert_block().unwrap()));
            }
            self.builder()
                .build_unconditional_branch(merge_block)
                .map_err(|e| CodegenError::Internal(format!("failed to build branch: {:?}", e)))?;
        }

        // Generate default block
        self.builder().position_at_end(default_block);
        if let Some(alt) = default_alt {
            for binder in &alt.binders {
                self.env.insert(binder.id, scrut_ptr.into());
            }
            if let Some(result) = self.lower_expr(&alt.rhs)? {
                phi_values.push((result, self.builder().get_insert_block().unwrap()));
            }
            for binder in &alt.binders {
                self.env.remove(&binder.id);
            }
            self.builder()
                .build_unconditional_branch(merge_block)
                .map_err(|e| CodegenError::Internal(format!("failed to build branch: {:?}", e)))?;
        } else {
            self.builder().build_unreachable().map_err(|e| {
                CodegenError::Internal(format!("failed to build unreachable: {:?}", e))
            })?;
        }

        // Build phi in merge block
        self.builder().position_at_end(merge_block);
        if phi_values.is_empty() {
            Ok(None)
        } else {
            let target_type = phi_values[0].0.get_type();

            // Coerce all values to the target type
            let mut coerced_values: Vec<(
                BasicValueEnum<'ctx>,
                inkwell::basic_block::BasicBlock<'ctx>,
            )> = Vec::new();

            for (val, block) in &phi_values {
                if val.get_type() == target_type {
                    coerced_values.push((*val, *block));
                } else {
                    let terminator = block.get_terminator();
                    if let Some(term) = terminator {
                        self.builder().position_before(&term);
                        let coerced = self.coerce_to_type(*val, target_type)?;
                        coerced_values.push((coerced, *block));
                    } else {
                        coerced_values.push((*val, *block));
                    }
                }
            }

            self.builder().position_at_end(merge_block);

            let phi = self
                .builder()
                .build_phi(target_type, "str_case_result")
                .map_err(|e| CodegenError::Internal(format!("failed to build phi: {:?}", e)))?;

            for (val, block) in &coerced_values {
                phi.add_incoming(&[(val, *block)]);
            }

            Ok(Some(phi.as_basic_value()))
        }
    }

    /// Get or declare the strcmp function.
    fn get_or_declare_strcmp(&self) -> CodegenResult<FunctionValue<'ctx>> {
        let name = "strcmp";
        if let Some(fn_val) = self.module.get_function(name) {
            return Ok(fn_val);
        }

        // int strcmp(const char*, const char*)
        let tm = self.type_mapper();
        let fn_type = tm
            .i32_type()
            .fn_type(&[tm.ptr_type().into(), tm.ptr_type().into()], false);
        Ok(self.module.add_function(name, fn_type))
    }

    /// Lower a case expression with constructor patterns.
    fn lower_case_datacon(
        &mut self,
        scrut_val: BasicValueEnum<'ctx>,
        alts: &[Alt],
        scrut_ty: &Ty,
    ) -> CodegenResult<Option<BasicValueEnum<'ctx>>> {
        // Newtype erasure: if the case matches a single newtype constructor,
        // the scrutinee IS the inner value (no ADT struct to destructure).
        // Just bind the scrutinee to the binder and evaluate the RHS.
        if let Some(alt) = alts.first() {
            if let AltCon::DataCon(con) = &alt.con {
                if self.is_newtype_constructor(con.name.as_str()) && con.arity == 1 {
                    // Bind the single field to the scrutinee value directly
                    if let Some(binder) = alt.binders.first() {
                        self.env.insert(binder.id, scrut_val);
                    }
                    let result = self.lower_expr(&alt.rhs)?;
                    // Clean up bindings
                    for binder in &alt.binders {
                        self.env.remove(&binder.id);
                    }
                    return Ok(result);
                }
            }
            // Also handle Default alt that binds the scrutinee for newtype cases
            if let AltCon::Default = &alt.con {
                if alts.len() == 1 {
                    // Single default alt — bind scrutinee if there's a binder
                    if let Some(binder) = alt.binders.first() {
                        self.env.insert(binder.id, scrut_val);
                    }
                    let result = self.lower_expr(&alt.rhs)?;
                    for binder in &alt.binders {
                        self.env.remove(&binder.id);
                    }
                    return Ok(result);
                }
            }
        }

        // Count existing case_alt blocks before we add more
        let current_fn = self
            .builder()
            .get_insert_block()
            .ok_or_else(|| CodegenError::Internal("no insert block".to_string()))?
            .get_parent()
            .ok_or_else(|| CodegenError::Internal("no parent function".to_string()))?;
        let _existing_blocks: Vec<_> = current_fn.get_basic_block_iter().collect();

        // For constructor patterns, scrutinee must be a pointer (ADT value)
        let scrut_ptr = match scrut_val {
            BasicValueEnum::PointerValue(p) => p,
            BasicValueEnum::IntValue(i) => {
                // If it's an int, it might be a boxed value - try to interpret as ptr
                self.builder()
                    .build_int_to_ptr(i, self.type_mapper().ptr_type(), "scrut_ptr")
                    .map_err(|e| {
                        CodegenError::Internal(format!("failed to cast scrutinee: {:?}", e))
                    })?
            }
            _ => {
                return Err(CodegenError::Unsupported(
                    "case on non-pointer value with constructor patterns".to_string(),
                ))
            }
        };

        // Extract the tag from the ADT value
        let tag = self.extract_adt_tag(scrut_ptr)?;

        let current_fn = self
            .builder()
            .get_insert_block()
            .ok_or_else(|| CodegenError::Internal("no insert block".to_string()))?
            .get_parent()
            .ok_or_else(|| CodegenError::Internal("no parent function".to_string()))?;

        // Create blocks for each alternative
        let mut blocks = Vec::new();
        let merge_block = self
            .llvm_context()
            .append_basic_block(current_fn, "case_merge");

        let mut default_block = None;
        let mut cases = Vec::new();

        // Collect DataCon info for field extraction later
        let mut datacon_info: Vec<Option<&DataCon>> = Vec::new();

        for alt in alts {
            let block = self
                .llvm_context()
                .append_basic_block(current_fn, "case_alt");
            blocks.push(block);

            match &alt.con {
                AltCon::DataCon(con) => {
                    let tag_val = self
                        .type_mapper()
                        .i64_type()
                        .const_int(con.tag as u64, false);
                    cases.push((tag_val, block));
                    datacon_info.push(Some(con));
                    // Register this constructor for later use in constructor applications
                    self.register_constructor(con.name.as_str(), con.tag, con.arity);
                }
                AltCon::Default => {
                    default_block = Some(block);
                    datacon_info.push(None);
                }
                AltCon::Lit(_) => {
                    return Err(CodegenError::Unsupported(
                        "mixed literal and constructor patterns".to_string(),
                    ))
                }
            }
        }

        // Deduplicate switch cases — if nested patterns cause the same tag to
        // appear multiple times, keep only the first (the later alternatives are
        // dead code or handled by nested pattern matching inside the first alt).
        {
            let mut seen_tags = FxHashSet::default();
            cases.retain(|(tag_val, _block)| {
                let tag_u64 = tag_val.get_zero_extended_constant().unwrap_or(u64::MAX);
                seen_tags.insert(tag_u64)
            });
        }

        // Build switch on tag
        // If there's no explicit default, create an unreachable block to indicate exhaustive matching
        let default = if let Some(db) = default_block {
            db
        } else {
            // Create an unreachable block for the default case
            // This avoids having merge_block as a direct successor of the switch
            let unreachable_block = self
                .llvm_context()
                .append_basic_block(current_fn, "case_unreachable");
            unreachable_block
        };

        let _switch = self
            .builder()
            .build_switch(tag, default, &cases)
            .map_err(|e| CodegenError::Internal(format!("failed to build switch: {:?}", e)))?;

        // If we created an unreachable block, fill it in
        if default_block.is_none() {
            self.builder().position_at_end(default);
            self.builder().build_unreachable().map_err(|e| {
                CodegenError::Internal(format!("failed to build unreachable: {:?}", e))
            })?;
        }

        // Generate code for each alternative with field extraction
        let result = self.lower_case_datacon_alternatives(
            alts,
            &blocks,
            merge_block,
            scrut_ptr,
            &datacon_info,
            scrut_ty,
        )?;

        Ok(result)
    }

    /// Lower case alternatives (shared logic for RHS generation).
    ///
    /// Uses a two-pass approach to avoid inserting instructions after terminators:
    /// 1. First pass: lower all RHS expressions and collect (value, block) pairs
    /// 2. Second pass: coerce values if needed, then build branches
    ///
    /// Each alternative's RHS is in tail position if the case expression itself is.
    fn lower_case_alternatives(
        &mut self,
        alts: &[Alt],
        blocks: &[inkwell::basic_block::BasicBlock<'ctx>],
        merge_block: inkwell::basic_block::BasicBlock<'ctx>,
    ) -> CodegenResult<Option<BasicValueEnum<'ctx>>> {
        // Pass 1: Lower all expressions and collect values WITHOUT building branches
        // Each alternative's RHS is in tail position if the case expression is
        let mut collected: Vec<(
            Option<BasicValueEnum<'ctx>>,
            inkwell::basic_block::BasicBlock<'ctx>,
        )> = Vec::new();

        for (i, alt) in alts.iter().enumerate() {
            self.builder().position_at_end(blocks[i]);

            // RHS is in tail position (inherits from parent case expression)
            let result = self.lower_expr(&alt.rhs)?;

            // Get the ACTUAL current block (lower_expr may have created nested blocks)
            let current_block = self.builder().get_insert_block().ok_or_else(|| {
                CodegenError::Internal("no current block after lower_expr".to_string())
            })?;

            collected.push((result, current_block));
        }

        // Determine target type from first non-None value
        let target_type = collected
            .iter()
            .find_map(|(val, _)| val.map(|v| v.get_type()));

        // Pass 2: Coerce values and build branches
        let mut phi_values: Vec<(BasicValueEnum<'ctx>, inkwell::basic_block::BasicBlock<'ctx>)> =
            Vec::new();

        for (result, block) in collected {
            // Position at the end of the block where the value was produced
            self.builder().position_at_end(block);

            // Only add to phi_values if we will branch to merge_block
            // If the block already has a terminator (e.g., error call with unreachable),
            // it doesn't reach merge_block and shouldn't contribute to the PHI
            if block.get_terminator().is_none() {
                if let Some(val) = result {
                    // Coerce if needed (BEFORE building the branch)
                    let final_val = if let Some(target) = target_type {
                        if val.get_type() != target {
                            self.coerce_to_type(val, target)?
                        } else {
                            val
                        }
                    } else {
                        val
                    };

                    // Get the current block
                    let final_block = self.builder().get_insert_block().ok_or_else(|| {
                        CodegenError::Internal("no current block after coercion".to_string())
                    })?;
                    phi_values.push((final_val, final_block));
                }

                // Build the branch to merge_block
                self.builder()
                    .build_unconditional_branch(merge_block)
                    .map_err(|e| {
                        CodegenError::Internal(format!("failed to build branch: {:?}", e))
                    })?;
            }
            // If block already has a terminator, skip it entirely for PHI
        }

        // Build phi node in merge block
        self.builder().position_at_end(merge_block);

        if phi_values.is_empty() {
            Ok(None)
        } else {
            let phi_type = phi_values[0].0.get_type();
            let phi = self
                .builder()
                .build_phi(phi_type, "case_result")
                .map_err(|e| CodegenError::Internal(format!("failed to build phi: {:?}", e)))?;

            for (val, block) in &phi_values {
                phi.add_incoming(&[(val, *block)]);
            }

            Ok(Some(phi.as_basic_value()))
        }
    }

    /// Lower a Bool case expression when the scrutinee is an integer (from comparison).
    /// True is represented as non-zero (typically 1), False as 0.
    ///
    /// Uses a two-pass approach to avoid inserting instructions after terminators.
    fn lower_case_bool_as_int(
        &mut self,
        scrut_val: BasicValueEnum<'ctx>,
        alts: &[Alt],
        case_ty: &Ty,
    ) -> CodegenResult<Option<BasicValueEnum<'ctx>>> {
        let scrut_int = match scrut_val {
            BasicValueEnum::IntValue(i) => i,
            _ => {
                return Err(CodegenError::Internal(
                    "lower_case_bool_as_int called with non-integer scrutinee".to_string(),
                ))
            }
        };

        let current_fn = self
            .builder()
            .get_insert_block()
            .ok_or_else(|| CodegenError::Internal("no insert block".to_string()))?
            .get_parent()
            .ok_or_else(|| CodegenError::Internal("no parent function".to_string()))?;

        // Find the True and False alternatives
        let mut true_alt = None;
        let mut false_alt = None;
        let mut default_alt = None;

        for alt in alts {
            match &alt.con {
                AltCon::DataCon(con) => {
                    let name = con.name.as_str();
                    if name == "True" || name == "GHC.Types.True" {
                        true_alt = Some(alt);
                    } else if name == "False" || name == "GHC.Types.False" {
                        false_alt = Some(alt);
                    }
                }
                AltCon::Default => {
                    default_alt = Some(alt);
                }
                _ => {}
            }
        }

        // Create blocks
        let true_block = self
            .llvm_context()
            .append_basic_block(current_fn, "bool_true");
        let false_block = self
            .llvm_context()
            .append_basic_block(current_fn, "bool_false");
        let merge_block = self
            .llvm_context()
            .append_basic_block(current_fn, "bool_merge");

        // Compare scrutinee to zero (False = 0, True = non-zero)
        let zero = self.type_mapper().i64_type().const_zero();
        let is_true = self
            .builder()
            .build_int_compare(inkwell::IntPredicate::NE, scrut_int, zero, "is_true")
            .map_err(|e| CodegenError::Internal(format!("failed to build bool cmp: {:?}", e)))?;

        // Branch based on the comparison
        self.builder()
            .build_conditional_branch(is_true, true_block, false_block)
            .map_err(|e| CodegenError::Internal(format!("failed to build cond branch: {:?}", e)))?;

        // Pass 1: Lower both branches and collect values WITHOUT building terminators
        // Each branch RHS inherits tail position from parent case expression

        // True branch
        self.builder().position_at_end(true_block);
        let true_rhs = if let Some(alt) = true_alt {
            self.lower_expr(&alt.rhs)?
        } else if let Some(alt) = default_alt {
            self.lower_expr(&alt.rhs)?
        } else {
            None
        };
        let true_end_block = self.builder().get_insert_block().ok_or_else(|| {
            CodegenError::Internal("no current block after lower_expr".to_string())
        })?;

        // False branch
        self.builder().position_at_end(false_block);
        let false_rhs = if let Some(alt) = false_alt {
            self.lower_expr(&alt.rhs)?
        } else if let Some(alt) = default_alt {
            self.lower_expr(&alt.rhs)?
        } else {
            None
        };
        let false_end_block = self.builder().get_insert_block().ok_or_else(|| {
            CodegenError::Internal("no current block after lower_expr".to_string())
        })?;

        // E.45: Check if the case result type is Integer — we need special promotion
        // case_ty is often Ty::Error in Core IR, so also check if any branch RHS is Integer
        let is_integer_case =
            self.is_integer_type(case_ty) || alts.iter().any(|alt| self.is_integer_expr(&alt.rhs));

        // Determine target type: prefer pointer if one branch is pointer (for Integer cases)
        let target_type = if is_integer_case {
            // For Integer cases, target is pointer
            Some(self.type_mapper().ptr_type().into())
        } else {
            true_rhs
                .map(|v| v.get_type())
                .or_else(|| false_rhs.map(|v| v.get_type()))
        };

        // Pass 2: Coerce values and build branches
        let mut phi_values: Vec<(BasicValueEnum<'ctx>, inkwell::basic_block::BasicBlock<'ctx>)> =
            Vec::new();

        // True branch: coerce and terminate (only if block reaches merge)
        self.builder().position_at_end(true_end_block);
        if true_end_block.get_terminator().is_none() {
            if let Some(val) = true_rhs {
                let final_val = if is_integer_case && val.is_int_value() {
                    // Promote Int to Integer via bhc_integer_from_i64
                    self.promote_to_integer(val)?
                } else if let Some(target) = target_type {
                    if val.get_type() != target {
                        self.coerce_to_type(val, target)?
                    } else {
                        val
                    }
                } else {
                    val
                };
                let final_block = self.builder().get_insert_block().ok_or_else(|| {
                    CodegenError::Internal("no current block after coercion".to_string())
                })?;
                phi_values.push((final_val, final_block));
            }
            self.builder()
                .build_unconditional_branch(merge_block)
                .map_err(|e| CodegenError::Internal(format!("failed to build branch: {:?}", e)))?;
        }

        // False branch: coerce and terminate (only if block reaches merge)
        self.builder().position_at_end(false_end_block);
        if false_end_block.get_terminator().is_none() {
            if let Some(val) = false_rhs {
                let final_val = if is_integer_case && val.is_int_value() {
                    // Promote Int to Integer via bhc_integer_from_i64
                    self.promote_to_integer(val)?
                } else if let Some(target) = target_type {
                    if val.get_type() != target {
                        self.coerce_to_type(val, target)?
                    } else {
                        val
                    }
                } else {
                    val
                };
                let final_block = self.builder().get_insert_block().ok_or_else(|| {
                    CodegenError::Internal("no current block after coercion".to_string())
                })?;
                phi_values.push((final_val, final_block));
            }
            self.builder()
                .build_unconditional_branch(merge_block)
                .map_err(|e| CodegenError::Internal(format!("failed to build branch: {:?}", e)))?;
        }

        // Build phi node in merge block
        self.builder().position_at_end(merge_block);

        if phi_values.is_empty() {
            Ok(None)
        } else {
            let phi_type = phi_values[0].0.get_type();

            let phi = self
                .builder()
                .build_phi(phi_type, "bool_result")
                .map_err(|e| CodegenError::Internal(format!("failed to build phi: {:?}", e)))?;

            for (val, block) in &phi_values {
                phi.add_incoming(&[(val, *block)]);
            }

            Ok(Some(phi.as_basic_value()))
        }
    }

    /// Lower case alternatives with DataCon patterns (extracts fields and binds variables).
    ///
    /// Uses a two-pass approach:
    /// 1. First pass: lower all RHS expressions and collect (value, block) pairs WITHOUT building branches
    /// 2. Second pass: determine target type, coerce values if needed, then build branches
    fn lower_case_datacon_alternatives(
        &mut self,
        alts: &[Alt],
        blocks: &[inkwell::basic_block::BasicBlock<'ctx>],
        merge_block: inkwell::basic_block::BasicBlock<'ctx>,
        scrut_ptr: PointerValue<'ctx>,
        datacon_info: &[Option<&DataCon>],
        scrut_ty: &Ty,
    ) -> CodegenResult<Option<BasicValueEnum<'ctx>>> {
        // Pass 1: Lower all expressions and collect values WITHOUT building branches
        // We collect (Option<value>, block) so we know which block each value came from
        let mut collected: Vec<(
            Option<BasicValueEnum<'ctx>>,
            inkwell::basic_block::BasicBlock<'ctx>,
        )> = Vec::new();

        for (i, alt) in alts.iter().enumerate() {
            self.builder().position_at_end(blocks[i]);

            // Extract fields and bind to pattern variables
            if let Some(con) = datacon_info[i] {
                let arity = con.arity;

                // Bind each field to its corresponding pattern variable
                for (field_idx, binder) in alt.binders.iter().enumerate() {
                    if field_idx < arity as usize {
                        let field_ptr =
                            self.extract_adt_field(scrut_ptr, arity, field_idx as u32)?;

                        // Determine the field type:
                        // - Use binder.ty if it's not Error
                        // - Otherwise, infer from scrutinee type (e.g., for list elements)
                        let field_ty = if matches!(&binder.ty, Ty::Error) {
                            self.infer_field_type(scrut_ty, con, field_idx)
                        } else {
                            binder.ty.clone()
                        };

                        // Determine if we need to unbox the field
                        let field_val = self.ptr_to_value(field_ptr, &field_ty)?;
                        self.env.insert(binder.id, field_val);
                    }
                }
            }

            // Lower the RHS with bound variables (inherits tail position from parent case)
            let result = self.lower_expr(&alt.rhs)?;

            // Remove bindings (for proper scoping)
            for binder in &alt.binders {
                self.env.remove(&binder.id);
            }

            // Get the ACTUAL current block (lower_expr may have created nested blocks)
            let current_block = self.builder().get_insert_block().ok_or_else(|| {
                CodegenError::Internal("no current block after lower_expr".to_string())
            })?;

            collected.push((result, current_block));
        }

        // Determine target type from first non-None value
        let target_type = collected
            .iter()
            .find_map(|(val, _)| val.map(|v| v.get_type()));

        // Pass 2: Coerce values and build branches
        let mut phi_values: Vec<(BasicValueEnum<'ctx>, inkwell::basic_block::BasicBlock<'ctx>)> =
            Vec::new();

        for (result, block) in collected {
            // Position at the end of the block where the value was produced
            self.builder().position_at_end(block);

            // Only add to phi_values if we will branch to merge_block
            // If the block already has a terminator (e.g., error call with unreachable),
            // it doesn't reach merge_block and shouldn't contribute to the PHI
            if block.get_terminator().is_none() {
                if let Some(val) = result {
                    // Coerce if needed (BEFORE building the branch)
                    let final_val = if let Some(target) = target_type {
                        if val.get_type() != target {
                            self.coerce_to_type(val, target)?
                        } else {
                            val
                        }
                    } else {
                        val
                    };

                    // Get the current block (coercion doesn't change it)
                    let final_block = self.builder().get_insert_block().ok_or_else(|| {
                        CodegenError::Internal("no current block after coercion".to_string())
                    })?;
                    phi_values.push((final_val, final_block));
                }

                // Build the branch to merge_block
                self.builder()
                    .build_unconditional_branch(merge_block)
                    .map_err(|e| {
                        CodegenError::Internal(format!("failed to build branch: {:?}", e))
                    })?;
            }
            // If block already has a terminator, skip it entirely for PHI
        }

        // Build phi node in merge block
        self.builder().position_at_end(merge_block);

        if phi_values.is_empty() {
            Ok(None)
        } else {
            let phi_type = phi_values[0].0.get_type();
            let phi = self
                .builder()
                .build_phi(phi_type, "case_result")
                .map_err(|e| CodegenError::Internal(format!("failed to build phi: {:?}", e)))?;

            for (val, block) in &phi_values {
                phi.add_incoming(&[(val, *block)]);
            }

            Ok(Some(phi.as_basic_value()))
        }
    }

    /// Infer the type of a field from a data constructor based on the scrutinee type.
    ///
    /// This is used when pattern binders have `Ty::Error` type (which happens when
    /// HIR-to-Core lowering doesn't preserve types). We can infer the field types
    /// from the scrutinee type.
    ///
    /// For lists:
    /// - Cons `:` (tag 1, arity 2): field 0 is element type, field 1 is list type
    /// - Nil `[]` (tag 0, arity 0): no fields
    fn infer_field_type(&self, scrut_ty: &Ty, con: &DataCon, field_idx: usize) -> Ty {
        match scrut_ty {
            Ty::List(elem_ty) => {
                // List cons `:` has tag 1, arity 2
                // field 0: head element (elem_ty)
                // field 1: tail (list type)
                if con.tag == 1 && con.arity == 2 {
                    match field_idx {
                        0 => (**elem_ty).clone(), // head: element type
                        1 => scrut_ty.clone(),    // tail: list type
                        _ => Ty::Error,
                    }
                } else {
                    // Nil has no fields
                    Ty::Error
                }
            }
            Ty::Tuple(elem_tys) => {
                // Tuple fields correspond directly to element types
                if field_idx < elem_tys.len() {
                    elem_tys[field_idx].clone()
                } else {
                    Ty::Error
                }
            }
            Ty::App(_con_ty, arg_ty) => {
                // For type applications like `Maybe Int`, we need to look up
                // the constructor's field types and substitute type arguments.
                // For now, just propagate the argument type for single-arg constructors.
                if con.arity == 1 && field_idx == 0 {
                    (**arg_ty).clone()
                } else {
                    Ty::Error
                }
            }
            _ => {
                // For other types, we can't infer - return Error and let codegen handle it
                Ty::Error
            }
        }
    }

    /// Convert a pointer to a basic value, unboxing if necessary based on type.
    fn ptr_to_value(
        &self,
        ptr: PointerValue<'ctx>,
        ty: &Ty,
    ) -> CodegenResult<BasicValueEnum<'ctx>> {
        let tm = self.type_mapper();

        match ty {
            Ty::Con(con) => {
                let name = con.name.as_str();
                match name {
                    "Int" | "Int#" | "Int64" => {
                        // Unbox: ptr -> int
                        let int_val = self
                            .builder()
                            .build_ptr_to_int(ptr, tm.i64_type(), "unbox_int")
                            .map_err(|e| {
                                CodegenError::Internal(format!("failed to unbox int: {:?}", e))
                            })?;
                        Ok(int_val.into())
                    }
                    "Int32" => {
                        let int_val = self
                            .builder()
                            .build_ptr_to_int(ptr, tm.i64_type(), "unbox_int")
                            .map_err(|e| {
                                CodegenError::Internal(format!("failed to unbox int: {:?}", e))
                            })?;
                        let truncated = self
                            .builder()
                            .build_int_truncate(int_val, tm.i32_type(), "trunc_i32")
                            .map_err(|e| {
                                CodegenError::Internal(format!("failed to truncate: {:?}", e))
                            })?;
                        Ok(truncated.into())
                    }
                    "Bool" => {
                        // Bool values are always pointers (either tagged-int-as-pointer
                        // 0/1 or heap-allocated Bool ADT). Keep as pointer — the case
                        // expression handler uses extract_bool_tag() to handle both
                        // representations correctly.
                        Ok(ptr.into())
                    }
                    "Char" | "Char#" => {
                        let int_val = self
                            .builder()
                            .build_ptr_to_int(ptr, tm.i64_type(), "unbox_char")
                            .map_err(|e| {
                                CodegenError::Internal(format!("failed to unbox char: {:?}", e))
                            })?;
                        let char_val = self
                            .builder()
                            .build_int_truncate(int_val, tm.i32_type(), "to_char")
                            .map_err(|e| {
                                CodegenError::Internal(format!("failed to truncate char: {:?}", e))
                            })?;
                        Ok(char_val.into())
                    }
                    "Float" | "Float#" => {
                        // Unbox: ptr -> bits -> float
                        let bits = self
                            .builder()
                            .build_ptr_to_int(ptr, tm.i64_type(), "unbox_float_bits")
                            .map_err(|e| {
                                CodegenError::Internal(format!("failed to unbox float: {:?}", e))
                            })?;
                        let truncated = self
                            .builder()
                            .build_int_truncate(bits, tm.i32_type(), "float_bits_32")
                            .map_err(|e| {
                                CodegenError::Internal(format!(
                                    "failed to truncate float bits: {:?}",
                                    e
                                ))
                            })?;
                        let float_val = self
                            .builder()
                            .build_bit_cast(truncated, tm.f32_type(), "to_float")
                            .map_err(|e| {
                                CodegenError::Internal(format!("failed to cast to float: {:?}", e))
                            })?;
                        Ok(float_val)
                    }
                    "Double" | "Double#" => {
                        let bits = self
                            .builder()
                            .build_ptr_to_int(ptr, tm.i64_type(), "unbox_double_bits")
                            .map_err(|e| {
                                CodegenError::Internal(format!("failed to unbox double: {:?}", e))
                            })?;
                        let double_val = self
                            .builder()
                            .build_bit_cast(bits, tm.f64_type(), "to_double")
                            .map_err(|e| {
                                CodegenError::Internal(format!("failed to cast to double: {:?}", e))
                            })?;
                        Ok(double_val)
                    }
                    _ => {
                        // For other types (ADTs), keep as pointer
                        Ok(ptr.into())
                    }
                }
            }
            Ty::Prim(prim) => {
                use bhc_types::PrimTy;
                match prim {
                    PrimTy::I64 | PrimTy::U64 => {
                        let int_val = self
                            .builder()
                            .build_ptr_to_int(ptr, tm.i64_type(), "unbox_i64")
                            .map_err(|e| {
                                CodegenError::Internal(format!("failed to unbox i64: {:?}", e))
                            })?;
                        Ok(int_val.into())
                    }
                    PrimTy::I32 | PrimTy::U32 => {
                        let int_val = self
                            .builder()
                            .build_ptr_to_int(ptr, tm.i64_type(), "unbox_int")
                            .map_err(|e| {
                                CodegenError::Internal(format!("failed to unbox int: {:?}", e))
                            })?;
                        let truncated = self
                            .builder()
                            .build_int_truncate(int_val, tm.i32_type(), "trunc_i32")
                            .map_err(|e| {
                                CodegenError::Internal(format!("failed to truncate: {:?}", e))
                            })?;
                        Ok(truncated.into())
                    }
                    PrimTy::F32 => {
                        let bits = self
                            .builder()
                            .build_ptr_to_int(ptr, tm.i64_type(), "unbox_float_bits")
                            .map_err(|e| {
                                CodegenError::Internal(format!("failed to unbox float: {:?}", e))
                            })?;
                        let truncated = self
                            .builder()
                            .build_int_truncate(bits, tm.i32_type(), "float_bits_32")
                            .map_err(|e| {
                                CodegenError::Internal(format!(
                                    "failed to truncate float bits: {:?}",
                                    e
                                ))
                            })?;
                        let float_val = self
                            .builder()
                            .build_bit_cast(truncated, tm.f32_type(), "to_float")
                            .map_err(|e| {
                                CodegenError::Internal(format!("failed to cast to float: {:?}", e))
                            })?;
                        Ok(float_val)
                    }
                    PrimTy::F64 => {
                        let bits = self
                            .builder()
                            .build_ptr_to_int(ptr, tm.i64_type(), "unbox_double_bits")
                            .map_err(|e| {
                                CodegenError::Internal(format!("failed to unbox double: {:?}", e))
                            })?;
                        let double_val = self
                            .builder()
                            .build_bit_cast(bits, tm.f64_type(), "to_double")
                            .map_err(|e| {
                                CodegenError::Internal(format!("failed to cast to double: {:?}", e))
                            })?;
                        Ok(double_val)
                    }
                    PrimTy::Char => {
                        let int_val = self
                            .builder()
                            .build_ptr_to_int(ptr, tm.i64_type(), "unbox_char")
                            .map_err(|e| {
                                CodegenError::Internal(format!("failed to unbox char: {:?}", e))
                            })?;
                        let char_val = self
                            .builder()
                            .build_int_truncate(int_val, tm.i32_type(), "to_char")
                            .map_err(|e| {
                                CodegenError::Internal(format!("failed to truncate char: {:?}", e))
                            })?;
                        Ok(char_val.into())
                    }
                    PrimTy::Addr => Ok(ptr.into()),
                }
            }
            _ => {
                // For function types, type variables, etc., keep as pointer
                Ok(ptr.into())
            }
        }
    }

    /// Convert a Core type to an LLVM function type.
    ///
    /// Uses pointer types for all parameters to enable uniform calling convention
    /// for higher-order functions and closures.
    /// All functions take an env_ptr as the first parameter (even if unused)
    /// to enable uniform closure calling convention.
    fn lower_function_type(&self, ty: &Ty) -> CodegenResult<inkwell::types::FunctionType<'ctx>> {
        let tm = self.type_mapper();
        let ptr_type = tm.ptr_type();

        // Count the number of parameters
        let mut param_count = 0;
        let mut current = ty;

        while let Ty::Fun(_, ret) = current {
            param_count += 1;
            current = ret;
        }

        // All functions take (env_ptr, args...) for uniform closure calling convention
        let mut arg_types: Vec<inkwell::types::BasicMetadataTypeEnum<'ctx>> = Vec::new();
        arg_types.push(ptr_type.into()); // env/closure pointer (may be unused)
        for _ in 0..param_count {
            arg_types.push(ptr_type.into());
        }

        // Return type is also pointer for uniformity
        Ok(ptr_type.fn_type(&arg_types, false))
    }

    /// Convert a Core type to an LLVM basic type (for function return).
    // Type-lowering helper retained for return-type handling paths.
    #[allow(dead_code)]
    fn lower_basic_type(&self, ty: &Ty) -> CodegenResult<Option<BasicTypeEnum<'ctx>>> {
        let tm = self.type_mapper();
        match ty {
            Ty::Con(con) => {
                let name = con.name.as_str();
                match name {
                    "Int" | "Int#" | "Int64" => Ok(Some(tm.i64_type().into())),
                    "Int32" => Ok(Some(tm.i32_type().into())),
                    "Float" | "Float#" => Ok(Some(tm.f32_type().into())),
                    "Double" | "Double#" => Ok(Some(tm.f64_type().into())),
                    "Char" | "Char#" => Ok(Some(tm.i32_type().into())),
                    // Bool uses i64 for consistency with how comparisons return values
                    "Bool" => Ok(Some(tm.i64_type().into())),
                    "()" | "Unit" => Ok(None), // Unit type has no value
                    _ => {
                        // Unknown type - use a pointer for now
                        Ok(Some(tm.ptr_type().into()))
                    }
                }
            }
            Ty::Prim(prim) => {
                use bhc_types::PrimTy;
                match prim {
                    PrimTy::I32 => Ok(Some(tm.i32_type().into())),
                    PrimTy::I64 => Ok(Some(tm.i64_type().into())),
                    PrimTy::U32 => Ok(Some(tm.i32_type().into())),
                    PrimTy::U64 => Ok(Some(tm.i64_type().into())),
                    PrimTy::F32 => Ok(Some(tm.f32_type().into())),
                    PrimTy::F64 => Ok(Some(tm.f64_type().into())),
                    PrimTy::Char => Ok(Some(tm.i32_type().into())),
                    PrimTy::Addr => Ok(Some(tm.ptr_type().into())),
                }
            }
            Ty::App(f, arg) => {
                // Check for IO () which should map to void
                if let Ty::Con(con) = f.as_ref() {
                    if con.name.as_str() == "IO" {
                        // IO a - check if a is ()
                        if let Ty::Tuple(elems) = arg.as_ref() {
                            if elems.is_empty() {
                                // IO () -> void
                                return Ok(None);
                            }
                        }
                        if let Ty::Con(inner_con) = arg.as_ref() {
                            if inner_con.name.as_str() == "()" {
                                // IO () -> void
                                return Ok(None);
                            }
                        }
                    }
                }
                self.lower_basic_type(f)
            }
            Ty::Fun(_, _) => {
                // Function types are pointers
                Ok(Some(tm.ptr_type().into()))
            }
            Ty::Forall(_, body) => self.lower_basic_type(body),
            Ty::Var(_) => {
                // Type variables are erased - use pointer
                Ok(Some(tm.ptr_type().into()))
            }
            Ty::Tuple(elems) => {
                if elems.is_empty() {
                    // Unit type ()
                    Ok(None)
                } else {
                    // Tuples become pointers for now
                    Ok(Some(tm.ptr_type().into()))
                }
            }
            Ty::List(_) => {
                // Lists become pointers
                Ok(Some(tm.ptr_type().into()))
            }
            Ty::Nat(_) => {
                // Type-level naturals are erased at runtime
                Ok(None)
            }
            Ty::TyList(_) => {
                // Type-level lists are erased at runtime
                Ok(None)
            }
            Ty::Error => Ok(None),
        }
    }

    /// Convert a Core type to an LLVM type (for arguments).
    // Type-lowering helper retained for argument-type handling paths.
    #[allow(dead_code)]
    fn lower_type(&self, ty: &Ty) -> CodegenResult<BasicTypeEnum<'ctx>> {
        self.lower_basic_type(ty)?.ok_or_else(|| {
            CodegenError::TypeError("cannot lower void type to basic type".to_string())
        })
    }

    // ========================================================================
    // Data.Sequence lowering helpers
    // ========================================================================

    fn lower_builtin_seq_empty(&mut self) -> CodegenResult<Option<BasicValueEnum<'ctx>>> {
        let rts_fn = self
            .functions
            .get(&VarId::new(1000800))
            .ok_or_else(|| CodegenError::Internal("bhc_seq_empty not declared".to_string()))?;
        let result = self
            .builder()
            .build_call(*rts_fn, &[], "seq_empty")
            .map_err(|e| CodegenError::Internal(format!("seq_empty: {:?}", e)))?
            .try_as_basic_value()
            .basic()
            .ok_or_else(|| CodegenError::Internal("seq_empty: void".to_string()))?;
        Ok(Some(result))
    }

    fn lower_builtin_seq_singleton(
        &mut self,
        elem_expr: &Expr,
    ) -> CodegenResult<Option<BasicValueEnum<'ctx>>> {
        let elem = self
            .lower_expr(elem_expr)?
            .ok_or_else(|| CodegenError::Internal("seq_singleton: no elem".to_string()))?;
        let elem_ptr = self.value_to_ptr(elem)?;
        let rts_fn = self
            .functions
            .get(&VarId::new(1000801))
            .ok_or_else(|| CodegenError::Internal("bhc_seq_singleton not declared".to_string()))?;
        let result = self
            .builder()
            .build_call(*rts_fn, &[elem_ptr.into()], "seq_singleton")
            .map_err(|e| CodegenError::Internal(format!("seq_singleton: {:?}", e)))?
            .try_as_basic_value()
            .basic()
            .ok_or_else(|| CodegenError::Internal("seq_singleton: void".to_string()))?;
        Ok(Some(result))
    }

    fn lower_builtin_seq_null(
        &mut self,
        seq_expr: &Expr,
    ) -> CodegenResult<Option<BasicValueEnum<'ctx>>> {
        let seq = self
            .lower_expr(seq_expr)?
            .ok_or_else(|| CodegenError::Internal("seq_null: no seq".to_string()))?;
        let seq_ptr = self.value_to_ptr(seq)?;
        let rts_fn = self
            .functions
            .get(&VarId::new(1000802))
            .ok_or_else(|| CodegenError::Internal("bhc_seq_null not declared".to_string()))?;
        let result = self
            .builder()
            .build_call(*rts_fn, &[seq_ptr.into()], "seq_null")
            .map_err(|e| CodegenError::Internal(format!("seq_null: {:?}", e)))?
            .try_as_basic_value()
            .basic()
            .ok_or_else(|| CodegenError::Internal("seq_null: void".to_string()))?;
        self.allocate_bool_adt(result.into_int_value(), "seq_null_bool")
    }

    fn lower_builtin_seq_length(
        &mut self,
        seq_expr: &Expr,
    ) -> CodegenResult<Option<BasicValueEnum<'ctx>>> {
        let seq = self
            .lower_expr(seq_expr)?
            .ok_or_else(|| CodegenError::Internal("seq_length: no seq".to_string()))?;
        let seq_ptr = self.value_to_ptr(seq)?;
        let rts_fn = self
            .functions
            .get(&VarId::new(1000803))
            .ok_or_else(|| CodegenError::Internal("bhc_seq_length not declared".to_string()))?;
        let result = self
            .builder()
            .build_call(*rts_fn, &[seq_ptr.into()], "seq_length")
            .map_err(|e| CodegenError::Internal(format!("seq_length: {:?}", e)))?
            .try_as_basic_value()
            .basic()
            .ok_or_else(|| CodegenError::Internal("seq_length: void".to_string()))?;
        // Return as int (will be converted to ptr by caller if needed)
        let int_val = result.into_int_value();
        Ok(Some(self.int_to_ptr(int_val)?.into()))
    }

    fn lower_builtin_seq_index(
        &mut self,
        seq_expr: &Expr,
        idx_expr: &Expr,
    ) -> CodegenResult<Option<BasicValueEnum<'ctx>>> {
        let seq = self
            .lower_expr(seq_expr)?
            .ok_or_else(|| CodegenError::Internal("seq_index: no seq".to_string()))?;
        let idx = self
            .lower_expr(idx_expr)?
            .ok_or_else(|| CodegenError::Internal("seq_index: no idx".to_string()))?;
        let seq_ptr = self.value_to_ptr(seq)?;
        let idx_int = self.coerce_to_int(idx)?;
        let rts_fn = self
            .functions
            .get(&VarId::new(1000804))
            .ok_or_else(|| CodegenError::Internal("bhc_seq_index not declared".to_string()))?;
        let result = self
            .builder()
            .build_call(*rts_fn, &[seq_ptr.into(), idx_int.into()], "seq_index")
            .map_err(|e| CodegenError::Internal(format!("seq_index: {:?}", e)))?
            .try_as_basic_value()
            .basic()
            .ok_or_else(|| CodegenError::Internal("seq_index: void".to_string()))?;
        Ok(Some(result))
    }

    fn lower_builtin_seq_lookup(
        &mut self,
        idx_expr: &Expr,
        seq_expr: &Expr,
    ) -> CodegenResult<Option<BasicValueEnum<'ctx>>> {
        let idx = self
            .lower_expr(idx_expr)?
            .ok_or_else(|| CodegenError::Internal("seq_lookup: no idx".to_string()))?;
        let seq = self
            .lower_expr(seq_expr)?
            .ok_or_else(|| CodegenError::Internal("seq_lookup: no seq".to_string()))?;
        let idx_int = self.coerce_to_int(idx)?;
        let seq_ptr = self.value_to_ptr(seq)?;
        let rts_fn = self
            .functions
            .get(&VarId::new(1000805))
            .ok_or_else(|| CodegenError::Internal("bhc_seq_lookup not declared".to_string()))?;
        let result = self
            .builder()
            .build_call(*rts_fn, &[idx_int.into(), seq_ptr.into()], "seq_lookup")
            .map_err(|e| CodegenError::Internal(format!("seq_lookup: {:?}", e)))?
            .try_as_basic_value()
            .basic()
            .ok_or_else(|| CodegenError::Internal("seq_lookup: void".to_string()))?;
        // Wrap in Maybe: null → Nothing, non-null → Just
        let result_ptr = result.into_pointer_value();
        let is_null = self
            .builder()
            .build_is_null(result_ptr, "seq_lookup_is_null")
            .map_err(|e| CodegenError::Internal(format!("seq_lookup is_null: {:?}", e)))?;
        let nothing = self.alloc_adt(0, 0)?;
        let just = self.alloc_adt(1, 1)?;
        self.store_adt_field(just, 1, 0, result_ptr.into())?;
        let maybe = self
            .builder()
            .build_select(is_null, nothing, just, "seq_lookup_maybe")
            .map_err(|e| CodegenError::Internal(format!("seq_lookup select: {:?}", e)))?;
        Ok(Some(maybe))
    }

    fn lower_builtin_seq_cons(
        &mut self,
        elem_expr: &Expr,
        seq_expr: &Expr,
    ) -> CodegenResult<Option<BasicValueEnum<'ctx>>> {
        let elem = self
            .lower_expr(elem_expr)?
            .ok_or_else(|| CodegenError::Internal("seq_cons: no elem".to_string()))?;
        let seq = self
            .lower_expr(seq_expr)?
            .ok_or_else(|| CodegenError::Internal("seq_cons: no seq".to_string()))?;
        let elem_ptr = self.value_to_ptr(elem)?;
        let seq_ptr = self.value_to_ptr(seq)?;
        let rts_fn = self
            .functions
            .get(&VarId::new(1000806))
            .ok_or_else(|| CodegenError::Internal("bhc_seq_cons not declared".to_string()))?;
        let result = self
            .builder()
            .build_call(*rts_fn, &[elem_ptr.into(), seq_ptr.into()], "seq_cons")
            .map_err(|e| CodegenError::Internal(format!("seq_cons: {:?}", e)))?
            .try_as_basic_value()
            .basic()
            .ok_or_else(|| CodegenError::Internal("seq_cons: void".to_string()))?;
        Ok(Some(result))
    }

    fn lower_builtin_seq_snoc(
        &mut self,
        seq_expr: &Expr,
        elem_expr: &Expr,
    ) -> CodegenResult<Option<BasicValueEnum<'ctx>>> {
        let seq = self
            .lower_expr(seq_expr)?
            .ok_or_else(|| CodegenError::Internal("seq_snoc: no seq".to_string()))?;
        let elem = self
            .lower_expr(elem_expr)?
            .ok_or_else(|| CodegenError::Internal("seq_snoc: no elem".to_string()))?;
        let seq_ptr = self.value_to_ptr(seq)?;
        let elem_ptr = self.value_to_ptr(elem)?;
        let rts_fn = self
            .functions
            .get(&VarId::new(1000807))
            .ok_or_else(|| CodegenError::Internal("bhc_seq_snoc not declared".to_string()))?;
        let result = self
            .builder()
            .build_call(*rts_fn, &[seq_ptr.into(), elem_ptr.into()], "seq_snoc")
            .map_err(|e| CodegenError::Internal(format!("seq_snoc: {:?}", e)))?
            .try_as_basic_value()
            .basic()
            .ok_or_else(|| CodegenError::Internal("seq_snoc: void".to_string()))?;
        Ok(Some(result))
    }

    fn lower_builtin_seq_append(
        &mut self,
        s1_expr: &Expr,
        s2_expr: &Expr,
    ) -> CodegenResult<Option<BasicValueEnum<'ctx>>> {
        let s1 = self
            .lower_expr(s1_expr)?
            .ok_or_else(|| CodegenError::Internal("seq_append: no s1".to_string()))?;
        let s2 = self
            .lower_expr(s2_expr)?
            .ok_or_else(|| CodegenError::Internal("seq_append: no s2".to_string()))?;
        let s1_ptr = self.value_to_ptr(s1)?;
        let s2_ptr = self.value_to_ptr(s2)?;
        let rts_fn = self
            .functions
            .get(&VarId::new(1000808))
            .ok_or_else(|| CodegenError::Internal("bhc_seq_append not declared".to_string()))?;
        let result = self
            .builder()
            .build_call(*rts_fn, &[s1_ptr.into(), s2_ptr.into()], "seq_append")
            .map_err(|e| CodegenError::Internal(format!("seq_append: {:?}", e)))?
            .try_as_basic_value()
            .basic()
            .ok_or_else(|| CodegenError::Internal("seq_append: void".to_string()))?;
        Ok(Some(result))
    }

    fn lower_builtin_seq_take(
        &mut self,
        n_expr: &Expr,
        seq_expr: &Expr,
    ) -> CodegenResult<Option<BasicValueEnum<'ctx>>> {
        let n = self
            .lower_expr(n_expr)?
            .ok_or_else(|| CodegenError::Internal("seq_take: no n".to_string()))?;
        let seq = self
            .lower_expr(seq_expr)?
            .ok_or_else(|| CodegenError::Internal("seq_take: no seq".to_string()))?;
        let n_int = self.coerce_to_int(n)?;
        let seq_ptr = self.value_to_ptr(seq)?;
        let rts_fn = self
            .functions
            .get(&VarId::new(1000809))
            .ok_or_else(|| CodegenError::Internal("bhc_seq_take not declared".to_string()))?;
        let result = self
            .builder()
            .build_call(*rts_fn, &[n_int.into(), seq_ptr.into()], "seq_take")
            .map_err(|e| CodegenError::Internal(format!("seq_take: {:?}", e)))?
            .try_as_basic_value()
            .basic()
            .ok_or_else(|| CodegenError::Internal("seq_take: void".to_string()))?;
        Ok(Some(result))
    }

    fn lower_builtin_seq_drop(
        &mut self,
        n_expr: &Expr,
        seq_expr: &Expr,
    ) -> CodegenResult<Option<BasicValueEnum<'ctx>>> {
        let n = self
            .lower_expr(n_expr)?
            .ok_or_else(|| CodegenError::Internal("seq_drop: no n".to_string()))?;
        let seq = self
            .lower_expr(seq_expr)?
            .ok_or_else(|| CodegenError::Internal("seq_drop: no seq".to_string()))?;
        let n_int = self.coerce_to_int(n)?;
        let seq_ptr = self.value_to_ptr(seq)?;
        let rts_fn = self
            .functions
            .get(&VarId::new(1000810))
            .ok_or_else(|| CodegenError::Internal("bhc_seq_drop not declared".to_string()))?;
        let result = self
            .builder()
            .build_call(*rts_fn, &[n_int.into(), seq_ptr.into()], "seq_drop")
            .map_err(|e| CodegenError::Internal(format!("seq_drop: {:?}", e)))?
            .try_as_basic_value()
            .basic()
            .ok_or_else(|| CodegenError::Internal("seq_drop: void".to_string()))?;
        Ok(Some(result))
    }

    fn lower_builtin_seq_reverse(
        &mut self,
        seq_expr: &Expr,
    ) -> CodegenResult<Option<BasicValueEnum<'ctx>>> {
        let seq = self
            .lower_expr(seq_expr)?
            .ok_or_else(|| CodegenError::Internal("seq_reverse: no seq".to_string()))?;
        let seq_ptr = self.value_to_ptr(seq)?;
        let rts_fn = self
            .functions
            .get(&VarId::new(1000811))
            .ok_or_else(|| CodegenError::Internal("bhc_seq_reverse not declared".to_string()))?;
        let result = self
            .builder()
            .build_call(*rts_fn, &[seq_ptr.into()], "seq_reverse")
            .map_err(|e| CodegenError::Internal(format!("seq_reverse: {:?}", e)))?
            .try_as_basic_value()
            .basic()
            .ok_or_else(|| CodegenError::Internal("seq_reverse: void".to_string()))?;
        Ok(Some(result))
    }

    fn lower_builtin_seq_update(
        &mut self,
        idx_expr: &Expr,
        elem_expr: &Expr,
        seq_expr: &Expr,
    ) -> CodegenResult<Option<BasicValueEnum<'ctx>>> {
        let idx = self
            .lower_expr(idx_expr)?
            .ok_or_else(|| CodegenError::Internal("seq_update: no idx".to_string()))?;
        let elem = self
            .lower_expr(elem_expr)?
            .ok_or_else(|| CodegenError::Internal("seq_update: no elem".to_string()))?;
        let seq = self
            .lower_expr(seq_expr)?
            .ok_or_else(|| CodegenError::Internal("seq_update: no seq".to_string()))?;
        let idx_int = self.coerce_to_int(idx)?;
        let elem_ptr = self.value_to_ptr(elem)?;
        let seq_ptr = self.value_to_ptr(seq)?;
        let rts_fn = self
            .functions
            .get(&VarId::new(1000812))
            .ok_or_else(|| CodegenError::Internal("bhc_seq_update not declared".to_string()))?;
        let result = self
            .builder()
            .build_call(
                *rts_fn,
                &[idx_int.into(), elem_ptr.into(), seq_ptr.into()],
                "seq_update",
            )
            .map_err(|e| CodegenError::Internal(format!("seq_update: {:?}", e)))?
            .try_as_basic_value()
            .basic()
            .ok_or_else(|| CodegenError::Internal("seq_update: void".to_string()))?;
        Ok(Some(result))
    }

    fn lower_builtin_seq_insert_at(
        &mut self,
        idx_expr: &Expr,
        elem_expr: &Expr,
        seq_expr: &Expr,
    ) -> CodegenResult<Option<BasicValueEnum<'ctx>>> {
        let idx = self
            .lower_expr(idx_expr)?
            .ok_or_else(|| CodegenError::Internal("seq_insert_at: no idx".to_string()))?;
        let elem = self
            .lower_expr(elem_expr)?
            .ok_or_else(|| CodegenError::Internal("seq_insert_at: no elem".to_string()))?;
        let seq = self
            .lower_expr(seq_expr)?
            .ok_or_else(|| CodegenError::Internal("seq_insert_at: no seq".to_string()))?;
        let idx_int = self.coerce_to_int(idx)?;
        let elem_ptr = self.value_to_ptr(elem)?;
        let seq_ptr = self.value_to_ptr(seq)?;
        let rts_fn = self
            .functions
            .get(&VarId::new(1000813))
            .ok_or_else(|| CodegenError::Internal("bhc_seq_insert_at not declared".to_string()))?;
        let result = self
            .builder()
            .build_call(
                *rts_fn,
                &[idx_int.into(), elem_ptr.into(), seq_ptr.into()],
                "seq_insert_at",
            )
            .map_err(|e| CodegenError::Internal(format!("seq_insert_at: {:?}", e)))?
            .try_as_basic_value()
            .basic()
            .ok_or_else(|| CodegenError::Internal("seq_insert_at: void".to_string()))?;
        Ok(Some(result))
    }

    fn lower_builtin_seq_delete_at(
        &mut self,
        idx_expr: &Expr,
        seq_expr: &Expr,
    ) -> CodegenResult<Option<BasicValueEnum<'ctx>>> {
        let idx = self
            .lower_expr(idx_expr)?
            .ok_or_else(|| CodegenError::Internal("seq_delete_at: no idx".to_string()))?;
        let seq = self
            .lower_expr(seq_expr)?
            .ok_or_else(|| CodegenError::Internal("seq_delete_at: no seq".to_string()))?;
        let idx_int = self.coerce_to_int(idx)?;
        let seq_ptr = self.value_to_ptr(seq)?;
        let rts_fn = self
            .functions
            .get(&VarId::new(1000814))
            .ok_or_else(|| CodegenError::Internal("bhc_seq_delete_at not declared".to_string()))?;
        let result = self
            .builder()
            .build_call(*rts_fn, &[idx_int.into(), seq_ptr.into()], "seq_delete_at")
            .map_err(|e| CodegenError::Internal(format!("seq_delete_at: {:?}", e)))?
            .try_as_basic_value()
            .basic()
            .ok_or_else(|| CodegenError::Internal("seq_delete_at: void".to_string()))?;
        Ok(Some(result))
    }

    fn lower_builtin_seq_replicate(
        &mut self,
        n_expr: &Expr,
        elem_expr: &Expr,
    ) -> CodegenResult<Option<BasicValueEnum<'ctx>>> {
        let n = self
            .lower_expr(n_expr)?
            .ok_or_else(|| CodegenError::Internal("seq_replicate: no n".to_string()))?;
        let elem = self
            .lower_expr(elem_expr)?
            .ok_or_else(|| CodegenError::Internal("seq_replicate: no elem".to_string()))?;
        let n_int = self.coerce_to_int(n)?;
        let elem_ptr = self.value_to_ptr(elem)?;
        let rts_fn = self
            .functions
            .get(&VarId::new(1000817))
            .ok_or_else(|| CodegenError::Internal("bhc_seq_replicate not declared".to_string()))?;
        let result = self
            .builder()
            .build_call(*rts_fn, &[n_int.into(), elem_ptr.into()], "seq_replicate")
            .map_err(|e| CodegenError::Internal(format!("seq_replicate: {:?}", e)))?
            .try_as_basic_value()
            .basic()
            .ok_or_else(|| CodegenError::Internal("seq_replicate: void".to_string()))?;
        Ok(Some(result))
    }

    /// Seq.toList: iterate backward from count-1 to 0, consing elements
    fn lower_builtin_seq_to_list(
        &mut self,
        seq_expr: &Expr,
    ) -> CodegenResult<Option<BasicValueEnum<'ctx>>> {
        let seq = self
            .lower_expr(seq_expr)?
            .ok_or_else(|| CodegenError::Internal("seq_to_list: no seq".to_string()))?;
        let seq_ptr = self.value_to_ptr(seq)?;
        let tm = self.type_mapper();

        // Get element count
        let count_fn = self
            .functions
            .get(&VarId::new(1000815))
            .ok_or_else(|| CodegenError::Internal("bhc_seq_elem_count not declared".to_string()))?;
        let count = self
            .builder()
            .build_call(*count_fn, &[seq_ptr.into()], "seq_count")
            .map_err(|e| CodegenError::Internal(format!("seq_count: {:?}", e)))?
            .try_as_basic_value()
            .basic()
            .ok_or_else(|| CodegenError::Internal("seq_count: void".to_string()))?
            .into_int_value();

        let current_fn = self
            .builder()
            .get_insert_block()
            .and_then(|b| b.get_parent())
            .ok_or_else(|| {
                CodegenError::Internal("seq_to_list: no current function".to_string())
            })?;

        let loop_header = self
            .llvm_context()
            .append_basic_block(current_fn, "stl_header");
        let loop_body = self
            .llvm_context()
            .append_basic_block(current_fn, "stl_body");
        let loop_exit = self
            .llvm_context()
            .append_basic_block(current_fn, "stl_exit");

        let nil = self.build_nil()?;
        let one = tm.i64_type().const_int(1, false);
        let start_idx = self
            .builder()
            .build_int_sub(count, one, "stl_start_idx")
            .map_err(|e| CodegenError::Internal(format!("stl start_idx: {:?}", e)))?;

        let entry_block = self.builder().get_insert_block().unwrap();
        self.builder()
            .build_unconditional_branch(loop_header)
            .map_err(|e| CodegenError::Internal(format!("stl br: {:?}", e)))?;

        // Loop header
        self.builder().position_at_end(loop_header);
        let acc_phi = self
            .builder()
            .build_phi(tm.ptr_type(), "stl_acc")
            .map_err(|e| CodegenError::Internal(format!("stl acc phi: {:?}", e)))?;
        let idx_phi = self
            .builder()
            .build_phi(tm.i64_type(), "stl_idx")
            .map_err(|e| CodegenError::Internal(format!("stl idx phi: {:?}", e)))?;

        let idx = idx_phi.as_basic_value().into_int_value();
        let done = self
            .builder()
            .build_int_compare(
                inkwell::IntPredicate::SLT,
                idx,
                tm.i64_type().const_zero(),
                "stl_done",
            )
            .map_err(|e| CodegenError::Internal(format!("stl cmp: {:?}", e)))?;
        self.builder()
            .build_conditional_branch(done, loop_exit, loop_body)
            .map_err(|e| CodegenError::Internal(format!("stl cbr: {:?}", e)))?;

        // Loop body
        self.builder().position_at_end(loop_body);
        let elem_fn = self
            .functions
            .get(&VarId::new(1000816))
            .ok_or_else(|| CodegenError::Internal("bhc_seq_elem_at not declared".to_string()))?;
        let elem = self
            .builder()
            .build_call(*elem_fn, &[seq_ptr.into(), idx.into()], "stl_elem")
            .map_err(|e| CodegenError::Internal(format!("stl elem: {:?}", e)))?
            .try_as_basic_value()
            .basic()
            .ok_or_else(|| CodegenError::Internal("stl elem: void".to_string()))?;

        let new_acc =
            self.build_cons(elem.into_pointer_value().into(), acc_phi.as_basic_value())?;
        let new_idx = self
            .builder()
            .build_int_sub(idx, one, "stl_new_idx")
            .map_err(|e| CodegenError::Internal(format!("stl new_idx: {:?}", e)))?;
        self.builder()
            .build_unconditional_branch(loop_header)
            .map_err(|e| CodegenError::Internal(format!("stl loop br: {:?}", e)))?;

        // Phi incoming
        acc_phi.add_incoming(&[(&nil, entry_block), (&new_acc, loop_body)]);
        idx_phi.add_incoming(&[(&start_idx, entry_block), (&new_idx, loop_body)]);

        self.builder().position_at_end(loop_exit);
        Ok(Some(acc_phi.as_basic_value()))
    }

    /// Seq.fromList: walk cons-list, snoc each element
    fn lower_builtin_seq_from_list(
        &mut self,
        list_expr: &Expr,
    ) -> CodegenResult<Option<BasicValueEnum<'ctx>>> {
        let list = self
            .lower_expr(list_expr)?
            .ok_or_else(|| CodegenError::Internal("seq_from_list: no list".to_string()))?;
        let list_ptr = self.value_to_ptr(list)?;
        let tm = self.type_mapper();

        // Start with empty seq
        let empty_fn = self
            .functions
            .get(&VarId::new(1000800))
            .ok_or_else(|| CodegenError::Internal("bhc_seq_empty not declared".to_string()))?;
        let empty_seq = self
            .builder()
            .build_call(*empty_fn, &[], "sfl_empty")
            .map_err(|e| CodegenError::Internal(format!("sfl empty: {:?}", e)))?
            .try_as_basic_value()
            .basic()
            .ok_or_else(|| CodegenError::Internal("sfl empty: void".to_string()))?;

        let snoc_fn = self
            .functions
            .get(&VarId::new(1000807))
            .ok_or_else(|| CodegenError::Internal("bhc_seq_snoc not declared".to_string()))?;

        let current_fn = self
            .builder()
            .get_insert_block()
            .and_then(|b| b.get_parent())
            .ok_or_else(|| {
                CodegenError::Internal("seq_from_list: no current function".to_string())
            })?;

        let loop_header = self
            .llvm_context()
            .append_basic_block(current_fn, "sfl_header");
        let loop_body = self
            .llvm_context()
            .append_basic_block(current_fn, "sfl_body");
        let loop_exit = self
            .llvm_context()
            .append_basic_block(current_fn, "sfl_exit");

        let entry_block = self.builder().get_insert_block().unwrap();
        self.builder()
            .build_unconditional_branch(loop_header)
            .map_err(|e| CodegenError::Internal(format!("sfl br: {:?}", e)))?;

        // Loop header
        self.builder().position_at_end(loop_header);
        let seq_phi = self
            .builder()
            .build_phi(tm.ptr_type(), "sfl_seq")
            .map_err(|e| CodegenError::Internal(format!("sfl seq phi: {:?}", e)))?;
        let cur_phi = self
            .builder()
            .build_phi(tm.ptr_type(), "sfl_cur")
            .map_err(|e| CodegenError::Internal(format!("sfl cur phi: {:?}", e)))?;

        // Check if current list is nil (tag == 0)
        let cur_ptr = cur_phi.as_basic_value().into_pointer_value();
        let tag = self.extract_adt_tag(cur_ptr)?;
        let is_nil = self
            .builder()
            .build_int_compare(
                inkwell::IntPredicate::EQ,
                tag,
                tm.i64_type().const_zero(),
                "sfl_is_nil",
            )
            .map_err(|e| CodegenError::Internal(format!("sfl cmp: {:?}", e)))?;
        self.builder()
            .build_conditional_branch(is_nil, loop_exit, loop_body)
            .map_err(|e| CodegenError::Internal(format!("sfl cbr: {:?}", e)))?;

        // Loop body: extract head, snoc onto seq, advance tail
        self.builder().position_at_end(loop_body);
        let head_ptr = self.extract_adt_field(cur_ptr, 2, 0)?;
        let tail_ptr = self.extract_adt_field(cur_ptr, 2, 1)?;
        let seq_acc = seq_phi.as_basic_value().into_pointer_value();

        let new_seq = self
            .builder()
            .build_call(*snoc_fn, &[seq_acc.into(), head_ptr.into()], "sfl_snoc")
            .map_err(|e| CodegenError::Internal(format!("sfl snoc: {:?}", e)))?
            .try_as_basic_value()
            .basic()
            .ok_or_else(|| CodegenError::Internal("sfl snoc: void".to_string()))?;

        self.builder()
            .build_unconditional_branch(loop_header)
            .map_err(|e| CodegenError::Internal(format!("sfl loop br: {:?}", e)))?;

        // Phi incoming
        seq_phi.add_incoming(&[(&empty_seq, entry_block), (&new_seq, loop_body)]);
        let list_ptr_bv: inkwell::values::BasicValueEnum = list_ptr.into();
        cur_phi.add_incoming(&[(&list_ptr_bv, entry_block), (&tail_ptr, loop_body)]);

        self.builder().position_at_end(loop_exit);
        Ok(Some(seq_phi.as_basic_value()))
    }

    fn lower_builtin_seq_viewl(
        &mut self,
        seq_expr: &Expr,
    ) -> CodegenResult<Option<BasicValueEnum<'ctx>>> {
        let seq = self
            .lower_expr(seq_expr)?
            .ok_or_else(|| CodegenError::Internal("seq_viewl: no seq".to_string()))?;
        let seq_ptr = self.value_to_ptr(seq)?;
        let tm = self.type_mapper();

        // Get tag (0=empty, 1=non-empty)
        let tag_fn = self
            .functions
            .get(&VarId::new(1000818))
            .ok_or_else(|| CodegenError::Internal("bhc_seq_viewl_tag not declared".to_string()))?;
        let tag = self
            .builder()
            .build_call(*tag_fn, &[seq_ptr.into()], "viewl_tag")
            .map_err(|e| CodegenError::Internal(format!("viewl_tag: {:?}", e)))?
            .try_as_basic_value()
            .basic()
            .ok_or_else(|| CodegenError::Internal("viewl_tag: void".to_string()))?
            .into_int_value();

        let is_empty = self
            .builder()
            .build_int_compare(
                inkwell::IntPredicate::EQ,
                tag,
                tm.i64_type().const_zero(),
                "viewl_is_empty",
            )
            .map_err(|e| CodegenError::Internal(format!("viewl cmp: {:?}", e)))?;

        let current_fn = self
            .builder()
            .get_insert_block()
            .and_then(|b| b.get_parent())
            .ok_or_else(|| CodegenError::Internal("seq_viewl: no current function".to_string()))?;

        let empty_bb = self
            .llvm_context()
            .append_basic_block(current_fn, "viewl_empty");
        let nonempty_bb = self
            .llvm_context()
            .append_basic_block(current_fn, "viewl_nonempty");
        let merge_bb = self
            .llvm_context()
            .append_basic_block(current_fn, "viewl_merge");

        self.builder()
            .build_conditional_branch(is_empty, empty_bb, nonempty_bb)
            .map_err(|e| CodegenError::Internal(format!("viewl cbr: {:?}", e)))?;

        // EmptyL: tag 0, 0 fields
        self.builder().position_at_end(empty_bb);
        let empty_l = self.alloc_adt(0, 0)?;
        self.builder()
            .build_unconditional_branch(merge_bb)
            .map_err(|e| CodegenError::Internal(format!("viewl empty br: {:?}", e)))?;

        // (:<): tag 1, 2 fields (head, tail_seq)
        self.builder().position_at_end(nonempty_bb);
        let head_fn = self
            .functions
            .get(&VarId::new(1000819))
            .ok_or_else(|| CodegenError::Internal("bhc_seq_viewl_head not declared".to_string()))?;
        let head = self
            .builder()
            .build_call(*head_fn, &[seq_ptr.into()], "viewl_head")
            .map_err(|e| CodegenError::Internal(format!("viewl_head: {:?}", e)))?
            .try_as_basic_value()
            .basic()
            .ok_or_else(|| CodegenError::Internal("viewl_head: void".to_string()))?;

        let tail_fn = self
            .functions
            .get(&VarId::new(1000820))
            .ok_or_else(|| CodegenError::Internal("bhc_seq_viewl_tail not declared".to_string()))?;
        let tail = self
            .builder()
            .build_call(*tail_fn, &[seq_ptr.into()], "viewl_tail")
            .map_err(|e| CodegenError::Internal(format!("viewl_tail: {:?}", e)))?
            .try_as_basic_value()
            .basic()
            .ok_or_else(|| CodegenError::Internal("viewl_tail: void".to_string()))?;

        let cons_l = self.alloc_adt(1, 2)?;
        self.store_adt_field(cons_l, 2, 0, head)?;
        self.store_adt_field(cons_l, 2, 1, tail)?;
        self.builder()
            .build_unconditional_branch(merge_bb)
            .map_err(|e| CodegenError::Internal(format!("viewl nonempty br: {:?}", e)))?;

        // Merge
        self.builder().position_at_end(merge_bb);
        let phi = self
            .builder()
            .build_phi(tm.ptr_type(), "viewl_result")
            .map_err(|e| CodegenError::Internal(format!("viewl phi: {:?}", e)))?;
        let empty_l_bv: inkwell::values::BasicValueEnum = empty_l.into();
        let cons_l_bv: inkwell::values::BasicValueEnum = cons_l.into();
        phi.add_incoming(&[(&empty_l_bv, empty_bb), (&cons_l_bv, nonempty_bb)]);
        Ok(Some(phi.as_basic_value()))
    }

    fn lower_builtin_seq_viewr(
        &mut self,
        seq_expr: &Expr,
    ) -> CodegenResult<Option<BasicValueEnum<'ctx>>> {
        let seq = self
            .lower_expr(seq_expr)?
            .ok_or_else(|| CodegenError::Internal("seq_viewr: no seq".to_string()))?;
        let seq_ptr = self.value_to_ptr(seq)?;
        let tm = self.type_mapper();

        let tag_fn = self
            .functions
            .get(&VarId::new(1000821))
            .ok_or_else(|| CodegenError::Internal("bhc_seq_viewr_tag not declared".to_string()))?;
        let tag = self
            .builder()
            .build_call(*tag_fn, &[seq_ptr.into()], "viewr_tag")
            .map_err(|e| CodegenError::Internal(format!("viewr_tag: {:?}", e)))?
            .try_as_basic_value()
            .basic()
            .ok_or_else(|| CodegenError::Internal("viewr_tag: void".to_string()))?
            .into_int_value();

        let is_empty = self
            .builder()
            .build_int_compare(
                inkwell::IntPredicate::EQ,
                tag,
                tm.i64_type().const_zero(),
                "viewr_is_empty",
            )
            .map_err(|e| CodegenError::Internal(format!("viewr cmp: {:?}", e)))?;

        let current_fn = self
            .builder()
            .get_insert_block()
            .and_then(|b| b.get_parent())
            .ok_or_else(|| CodegenError::Internal("seq_viewr: no current function".to_string()))?;

        let empty_bb = self
            .llvm_context()
            .append_basic_block(current_fn, "viewr_empty");
        let nonempty_bb = self
            .llvm_context()
            .append_basic_block(current_fn, "viewr_nonempty");
        let merge_bb = self
            .llvm_context()
            .append_basic_block(current_fn, "viewr_merge");

        self.builder()
            .build_conditional_branch(is_empty, empty_bb, nonempty_bb)
            .map_err(|e| CodegenError::Internal(format!("viewr cbr: {:?}", e)))?;

        // EmptyR: tag 0, 0 fields
        self.builder().position_at_end(empty_bb);
        let empty_r = self.alloc_adt(0, 0)?;
        self.builder()
            .build_unconditional_branch(merge_bb)
            .map_err(|e| CodegenError::Internal(format!("viewr empty br: {:?}", e)))?;

        // (:>): tag 1, 2 fields (init_seq, last)
        self.builder().position_at_end(nonempty_bb);
        let init_fn = self
            .functions
            .get(&VarId::new(1000823))
            .ok_or_else(|| CodegenError::Internal("bhc_seq_viewr_init not declared".to_string()))?;
        let init_val = self
            .builder()
            .build_call(*init_fn, &[seq_ptr.into()], "viewr_init")
            .map_err(|e| CodegenError::Internal(format!("viewr_init: {:?}", e)))?
            .try_as_basic_value()
            .basic()
            .ok_or_else(|| CodegenError::Internal("viewr_init: void".to_string()))?;

        let last_fn = self
            .functions
            .get(&VarId::new(1000822))
            .ok_or_else(|| CodegenError::Internal("bhc_seq_viewr_last not declared".to_string()))?;
        let last_val = self
            .builder()
            .build_call(*last_fn, &[seq_ptr.into()], "viewr_last")
            .map_err(|e| CodegenError::Internal(format!("viewr_last: {:?}", e)))?
            .try_as_basic_value()
            .basic()
            .ok_or_else(|| CodegenError::Internal("viewr_last: void".to_string()))?;

        let snoc_r = self.alloc_adt(1, 2)?;
        self.store_adt_field(snoc_r, 2, 0, init_val)?;
        self.store_adt_field(snoc_r, 2, 1, last_val)?;
        self.builder()
            .build_unconditional_branch(merge_bb)
            .map_err(|e| CodegenError::Internal(format!("viewr nonempty br: {:?}", e)))?;

        // Merge
        self.builder().position_at_end(merge_bb);
        let phi = self
            .builder()
            .build_phi(tm.ptr_type(), "viewr_result")
            .map_err(|e| CodegenError::Internal(format!("viewr phi: {:?}", e)))?;
        let empty_r_bv: inkwell::values::BasicValueEnum = empty_r.into();
        let snoc_r_bv: inkwell::values::BasicValueEnum = snoc_r.into();
        phi.add_incoming(&[(&empty_r_bv, empty_bb), (&snoc_r_bv, nonempty_bb)]);
        Ok(Some(phi.as_basic_value()))
    }

    /// Seq.filter: walk seq, apply predicate closure, build new seq
    fn lower_builtin_seq_filter(
        &mut self,
        pred_expr: &Expr,
        seq_expr: &Expr,
    ) -> CodegenResult<Option<BasicValueEnum<'ctx>>> {
        let pred = self
            .lower_expr(pred_expr)?
            .ok_or_else(|| CodegenError::Internal("seq_filter: no pred".to_string()))?;
        let seq = self
            .lower_expr(seq_expr)?
            .ok_or_else(|| CodegenError::Internal("seq_filter: no seq".to_string()))?;
        let pred_ptr = self.value_to_ptr(pred)?;
        let seq_ptr = self.value_to_ptr(seq)?;
        let tm = self.type_mapper();

        // Get count
        let count_fn = self
            .functions
            .get(&VarId::new(1000815))
            .ok_or_else(|| CodegenError::Internal("bhc_seq_elem_count not declared".to_string()))?;
        let count = self
            .builder()
            .build_call(*count_fn, &[seq_ptr.into()], "sf_count")
            .map_err(|e| CodegenError::Internal(format!("sf count: {:?}", e)))?
            .try_as_basic_value()
            .basic()
            .ok_or_else(|| CodegenError::Internal("sf count: void".to_string()))?
            .into_int_value();

        // Start with empty seq
        let empty_fn = self
            .functions
            .get(&VarId::new(1000800))
            .ok_or_else(|| CodegenError::Internal("bhc_seq_empty not declared".to_string()))?;
        let empty_seq = self
            .builder()
            .build_call(*empty_fn, &[], "sf_empty")
            .map_err(|e| CodegenError::Internal(format!("sf empty: {:?}", e)))?
            .try_as_basic_value()
            .basic()
            .ok_or_else(|| CodegenError::Internal("sf empty: void".to_string()))?;

        let snoc_fn = self
            .functions
            .get(&VarId::new(1000807))
            .ok_or_else(|| CodegenError::Internal("bhc_seq_snoc not declared".to_string()))?;
        let elem_fn = self
            .functions
            .get(&VarId::new(1000816))
            .ok_or_else(|| CodegenError::Internal("bhc_seq_elem_at not declared".to_string()))?;

        let current_fn = self
            .builder()
            .get_insert_block()
            .and_then(|b| b.get_parent())
            .ok_or_else(|| CodegenError::Internal("seq_filter: no current function".to_string()))?;

        let loop_header = self
            .llvm_context()
            .append_basic_block(current_fn, "sf_header");
        let loop_body = self
            .llvm_context()
            .append_basic_block(current_fn, "sf_body");
        let loop_keep = self
            .llvm_context()
            .append_basic_block(current_fn, "sf_keep");
        let loop_next = self
            .llvm_context()
            .append_basic_block(current_fn, "sf_next");
        let loop_exit = self
            .llvm_context()
            .append_basic_block(current_fn, "sf_exit");

        let entry_block = self.builder().get_insert_block().unwrap();
        self.builder()
            .build_unconditional_branch(loop_header)
            .map_err(|e| CodegenError::Internal(format!("sf br: {:?}", e)))?;

        // Header
        self.builder().position_at_end(loop_header);
        let acc_phi = self
            .builder()
            .build_phi(tm.ptr_type(), "sf_acc")
            .map_err(|e| CodegenError::Internal(format!("sf acc phi: {:?}", e)))?;
        let idx_phi = self
            .builder()
            .build_phi(tm.i64_type(), "sf_idx")
            .map_err(|e| CodegenError::Internal(format!("sf idx phi: {:?}", e)))?;

        let idx = idx_phi.as_basic_value().into_int_value();
        let done = self
            .builder()
            .build_int_compare(inkwell::IntPredicate::SGE, idx, count, "sf_done")
            .map_err(|e| CodegenError::Internal(format!("sf cmp: {:?}", e)))?;
        self.builder()
            .build_conditional_branch(done, loop_exit, loop_body)
            .map_err(|e| CodegenError::Internal(format!("sf cbr: {:?}", e)))?;

        // Body: get element, call predicate
        self.builder().position_at_end(loop_body);
        let elem = self
            .builder()
            .build_call(*elem_fn, &[seq_ptr.into(), idx.into()], "sf_elem")
            .map_err(|e| CodegenError::Internal(format!("sf elem: {:?}", e)))?
            .try_as_basic_value()
            .basic()
            .ok_or_else(|| CodegenError::Internal("sf elem: void".to_string()))?;

        // Call predicate closure: fn_ptr(closure_ptr, elem) -> Bool ADT
        let fn_ptr_ptr = self.extract_adt_field(pred_ptr, 2, 1)?;
        let fn_ty = tm
            .ptr_type()
            .fn_type(&[tm.ptr_type().into(), tm.ptr_type().into()], false);
        let fn_ptr = fn_ptr_ptr;
        let pred_result = self
            .builder()
            .build_indirect_call(fn_ty, fn_ptr, &[pred_ptr.into(), elem.into()], "sf_pred")
            .map_err(|e| CodegenError::Internal(format!("sf pred call: {:?}", e)))?
            .try_as_basic_value()
            .basic()
            .ok_or_else(|| CodegenError::Internal("sf pred: void".to_string()))?;

        // Check Bool ADT tag
        let bool_tag = self.extract_adt_tag(pred_result.into_pointer_value())?;
        let is_true = self
            .builder()
            .build_int_compare(
                inkwell::IntPredicate::NE,
                bool_tag,
                tm.i64_type().const_zero(),
                "sf_is_true",
            )
            .map_err(|e| CodegenError::Internal(format!("sf bool cmp: {:?}", e)))?;
        self.builder()
            .build_conditional_branch(is_true, loop_keep, loop_next)
            .map_err(|e| CodegenError::Internal(format!("sf pred cbr: {:?}", e)))?;

        // Keep: snoc element
        self.builder().position_at_end(loop_keep);
        let acc_val = acc_phi.as_basic_value().into_pointer_value();
        let new_acc = self
            .builder()
            .build_call(*snoc_fn, &[acc_val.into(), elem.into()], "sf_snoc")
            .map_err(|e| CodegenError::Internal(format!("sf snoc: {:?}", e)))?
            .try_as_basic_value()
            .basic()
            .ok_or_else(|| CodegenError::Internal("sf snoc: void".to_string()))?;
        self.builder()
            .build_unconditional_branch(loop_next)
            .map_err(|e| CodegenError::Internal(format!("sf keep br: {:?}", e)))?;

        // Next: increment index
        self.builder().position_at_end(loop_next);
        let acc_next = self
            .builder()
            .build_phi(tm.ptr_type(), "sf_acc_next")
            .map_err(|e| CodegenError::Internal(format!("sf acc_next phi: {:?}", e)))?;
        acc_next.add_incoming(&[
            (&acc_phi.as_basic_value(), loop_body),
            (&new_acc, loop_keep),
        ]);
        let one = tm.i64_type().const_int(1, false);
        let new_idx = self
            .builder()
            .build_int_add(idx, one, "sf_new_idx")
            .map_err(|e| CodegenError::Internal(format!("sf new_idx: {:?}", e)))?;
        self.builder()
            .build_unconditional_branch(loop_header)
            .map_err(|e| CodegenError::Internal(format!("sf next br: {:?}", e)))?;

        // Phi incoming
        acc_phi.add_incoming(&[
            (&empty_seq, entry_block),
            (&acc_next.as_basic_value(), loop_next),
        ]);
        let zero_bv: inkwell::values::BasicValueEnum = tm.i64_type().const_zero().into();
        let new_idx_bv: inkwell::values::BasicValueEnum = new_idx.into();
        idx_phi.add_incoming(&[(&zero_bv, entry_block), (&new_idx_bv, loop_next)]);

        self.builder().position_at_end(loop_exit);
        Ok(Some(acc_phi.as_basic_value()))
    }

    // Helper accessors
    fn llvm_context(&self) -> &'ctx Context {
        self.llvm_ctx
    }

    fn builder(&self) -> &Builder<'ctx> {
        self.module.builder()
    }

    fn type_mapper(&self) -> &TypeMapper<'ctx> {
        self.module.type_mapper()
    }
}

/// Lower a Core module to an LLVM module.
///
/// The module reference `'m` can be shorter than the context lifetime `'ctx`.
pub fn lower_core_module<'ctx>(
    ctx: &'ctx LlvmContext,
    module: &LlvmModule<'ctx>,
    core_module: &CoreModule,
) -> CodegenResult<()> {
    let mut lowering = Lowering::new(ctx, module);
    lowering.lower_module(core_module)
}

/// Lower a Core module to an LLVM module with multi-module support.
///
/// This variant accepts a module name for symbol mangling and a list of imported
/// symbols from already-compiled modules. Functions are declared with
/// module-qualified names (e.g., `Helper.double`) and extern declarations are
/// added for cross-module references.
pub fn lower_core_module_multimodule<'ctx>(
    ctx: &'ctx LlvmContext,
    module: &LlvmModule<'ctx>,
    core_module: &CoreModule,
    module_name: &str,
    imported_symbols: &[CompiledSymbol],
) -> CodegenResult<()> {
    lower_core_module_multimodule_with_constructors(
        ctx,
        module,
        core_module,
        module_name,
        imported_symbols,
        &[],
    )
}

/// Lower a Core module to LLVM IR in multi-module mode, with imported constructor metadata.
pub fn lower_core_module_multimodule_with_constructors<'ctx>(
    ctx: &'ctx LlvmContext,
    module: &LlvmModule<'ctx>,
    core_module: &CoreModule,
    module_name: &str,
    imported_symbols: &[CompiledSymbol],
    imported_constructors: &[(String, ConstructorMeta)],
) -> CodegenResult<()> {
    let mut lowering = Lowering::new_multimodule(ctx, module, module_name, imported_symbols)?;
    // Register imported constructor metadata so the codegen knows about
    // constructors from other modules (e.g., Types.hs constructors used in Parser.hs).
    for (name, meta) in imported_constructors {
        lowering.register_constructor(name, meta.tag, meta.arity);
        if meta.is_newtype {
            if let Some(m) = lowering.constructor_metadata.get_mut(name.as_str()) {
                m.is_newtype = true;
            }
        }
    }
    lowering.lower_module(core_module)
}

#[cfg(test)]
mod tests {

    // Tests would go here
}
