//! PFCL tree-walking evaluator.
//!
//! Adapted from the platform evaluator. Stripped:
//!   - SynthesisContext / served_catalog dispatch
//!   - DataLayerEngine / data.* dispatch
//!   - AppCompose / AppComposeShared / AppWithConfig intrinsics
//!   - DataValue conversion layer (Value IS DataValue in standalone PFCL)
//!
//! NativeFn: `fn(&[Value]) -> Value` — called directly, no conversion.

use std::sync::Arc;
use std::collections::BTreeMap;
use pfcl_types::{Hash, Value};
use pfcl_catalog::{FunctionCatalog, FunctionKind, Intrinsic, NativeFn};
use crate::closure_node::{ClosureArm, ClosureNode, ClosurePattern};
use crate::evaluator::cache::CompileCache;

// ── Runtime Environment (de Bruijn indexed) ───────────────────────────────────

#[derive(Debug, Clone)]
pub struct RuntimeEnv {
    stack: Vec<EvalResult>,
}

impl RuntimeEnv {
    pub fn new() -> Self { Self { stack: Vec::new() } }

    pub fn get(&self, index: usize) -> Option<&EvalResult> {
        if index < self.stack.len() { Some(&self.stack[self.stack.len() - 1 - index]) }
        else { None }
    }

    pub fn push(&mut self, value: EvalResult) { self.stack.push(value); }

    pub fn push_values(&mut self, values: &[Value]) {
        self.stack.extend(values.iter().map(|v| EvalResult::Val(v.clone())));
    }

    pub fn pop_n(&mut self, n: usize) { self.stack.truncate(self.stack.len().saturating_sub(n)); }
    pub fn depth(&self) -> usize { self.stack.len() }
    pub fn snapshot_values(&self) -> Vec<EvalResult> { self.stack.clone() }
}

impl Default for RuntimeEnv {
    fn default() -> Self { Self::new() }
}

// ── RuntimeClosure ────────────────────────────────────────────────────────────

#[derive(Clone, Debug)]
pub struct RuntimeClosure {
    pub arity: usize,
    pub body: Arc<ClosureNode>,
    pub env_snapshot: Vec<EvalResult>,
    /// fix trampoline depth: None = normal, Some(0) = fresh, Some(N) = running at depth N
    pub fix_trampoline_depth: Option<usize>,
}

// ── EvalResult ────────────────────────────────────────────────────────────────

#[derive(Clone)]
pub enum EvalResult {
    Val(Value),
    Closure(RuntimeClosure),
    NativeFn { func: NativeFn, name: String },
    Intrinsic(Intrinsic),
    FixTailCall { depth: usize, f: Box<EvalResult>, args: Vec<EvalResult> },
    DynPrimitive(std::sync::Arc<dyn pfcl_catalog::PrimitiveProvider>),
}

impl std::fmt::Debug for EvalResult {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            Self::Val(v)                      => write!(f, "Val({:?})", v),
            Self::Closure(..)                 => write!(f, "Closure(..)"),
            Self::NativeFn { name, .. }       => write!(f, "NativeFn({})", name),
            Self::Intrinsic(id)               => write!(f, "Intrinsic({:?})", id),
            Self::FixTailCall { depth, .. }   => write!(f, "FixTailCall({})", depth),
            Self::DynPrimitive(p)             => write!(f, "DynPrimitive({})", p.name()),
        }
    }
}

impl EvalResult {
    pub fn to_value(&self) -> Value {
        match self {
            EvalResult::Val(v) => v.clone(),
            EvalResult::Closure(_) | EvalResult::NativeFn { .. } | EvalResult::Intrinsic(_)
            | EvalResult::DynPrimitive(_) =>
                Value::Unit,
            EvalResult::FixTailCall { depth, .. } =>
                unreachable!("FixTailCall(depth={}) reached to_value()", depth),
        }
    }

    pub fn into_value(self) -> Value {
        match self {
            EvalResult::Val(v) => v,
            other => other.to_value(),
        }
    }

    pub fn expect_value(self, ctx: &str) -> Result<Value, String> {
        match self {
            EvalResult::Val(v)                        => Ok(v),
            EvalResult::Closure(_)                    => Err(format!("{}: expected value, got closure", ctx)),
            EvalResult::NativeFn { name, .. }         => Err(format!("{}: expected value, got fn {}", ctx, name)),
            EvalResult::Intrinsic(id)                 => Err(format!("{}: expected value, got intrinsic {:?}", ctx, id)),
            EvalResult::FixTailCall { depth, .. }     => Err(format!("{}: FixTailCall(depth={}) escaped", ctx, depth)),
            EvalResult::DynPrimitive(p)               => Err(format!("{}: expected value, got dyn primitive {}", ctx, p.name())),
        }
    }
}

// ── Evaluator ─────────────────────────────────────────────────────────────────

pub struct Evaluator<'a> {
    catalog: &'a FunctionCatalog,
    compile_cache: &'a mut CompileCache,
    fix_depth: usize,
    eval_depth: usize,
}

impl<'a> Evaluator<'a> {
    pub fn new(catalog: &'a FunctionCatalog, compile_cache: &'a mut CompileCache) -> Self {
        Self { catalog, compile_cache, fix_depth: 0, eval_depth: 0 }
    }

    pub fn eval_top(&mut self, node: &ClosureNode) -> Result<Value, String> {
        let mut env = RuntimeEnv::new();
        let result = self.eval(node, &mut env)?;
        let result = self.resolve_fix_tail(result, &mut env)?;
        Ok(result.to_value())
    }

    pub fn eval_with_args(&mut self, node: &ClosureNode, args: &[Value]) -> Result<Value, String> {
        let mut env = RuntimeEnv::new();
        match node {
            ClosureNode::Lambda { arity, body } => {
                if args.len() != *arity {
                    return Err(format!("Function expects {} args, got {}", arity, args.len()));
                }
                for v in args.iter().rev() { env.push(EvalResult::Val(v.clone())); }
                let result = self.eval(body, &mut env)?;
                let result = self.resolve_fix_tail(result, &mut env)?;
                env.pop_n(*arity);
                Ok(result.to_value())
            }
            _ => {
                let result = self.eval(node, &mut env)?;
                let eval_args: Vec<EvalResult> = args.iter().map(|v| EvalResult::Val(v.clone())).collect();
                let r = self.apply(result, eval_args, &mut env)?;
                let r = self.resolve_fix_tail(r, &mut env)?;
                Ok(r.to_value())
            }
        }
    }

    fn resolve_fix_tail(&mut self, mut result: EvalResult, env: &mut RuntimeEnv) -> Result<EvalResult, String> {
        loop {
            match result {
                EvalResult::FixTailCall { f, args, .. } => {
                    let f = *f;
                    let f_arity = match &f { EvalResult::Closure(rc) => rc.arity, _ => return Err("resolve_fix_tail: f is not a Closure".into()) };
                    if f_arity == 0 { return Err("resolve_fix_tail: f has arity 0".into()); }
                    result = self.run_fix_loop(f, f_arity, &args, env)?;
                }
                other => return Ok(other),
            }
        }
    }

    const MAX_EVAL_DEPTH: usize = 500;

    fn eval(&mut self, node: &ClosureNode, env: &mut RuntimeEnv) -> Result<EvalResult, String> {
        self.eval_depth += 1;
        if self.eval_depth > Self::MAX_EVAL_DEPTH {
            self.eval_depth -= 1;
            return Err(format!("eval depth exceeded {} (deep call chain)", Self::MAX_EVAL_DEPTH));
        }
        let result = self.eval_core(node, env);
        self.eval_depth -= 1;
        result
    }

    fn eval_core(&mut self, node: &ClosureNode, env: &mut RuntimeEnv) -> Result<EvalResult, String> {
        match node {
            ClosureNode::Literal(v) => Ok(EvalResult::Val(v.clone())),

            ClosureNode::Var(index) => {
                let r = env.get(*index)
                   .ok_or_else(|| format!("Var({}) out of bounds (env depth {})", index, env.depth()))
                   .map(|r| r.clone());
r
            }

            ClosureNode::Primitive { func, name } =>
                Ok(EvalResult::NativeFn { func: *func, name: name.clone() }),

            ClosureNode::Apply { func, args } => {
                let func_result = self.eval(func, env)?;
                // Lazy conditional
                if let EvalResult::NativeFn { ref name, .. } = func_result {
                    if name == "conditional" && args.len() == 3 {
                        let cond = self.eval(&args[0], env)?.to_value();
                        return match cond {
                            Value::Bool(true) => self.eval(&args[1], env),
                            Value::Bool(false) | Value::Nothing => self.eval(&args[2], env),
                            other => Err(format!("conditional: expected Bool, got {:?}", other)),
                        };
                    }
                }
                let mut eval_args = Vec::with_capacity(args.len());
                for arg in args { eval_args.push(self.eval(arg, env)?); }
                self.apply(func_result, eval_args, env)
            }

            ClosureNode::Lambda { arity, body } => {
                let snapshot = env.snapshot_values();
                Ok(EvalResult::Closure(RuntimeClosure {
                    arity: *arity,
                    body: Arc::new((**body).clone()),
                    env_snapshot: snapshot,
                    fix_trampoline_depth: None,
                }))
            }

            ClosureNode::Let { value, body } => {
                let val = self.eval(value, env)?;
                env.push(val);
                let result = self.eval(body, env)?;
                env.pop_n(1);
                Ok(result)
            }

            ClosureNode::Match { scrutinee, arms } => {
                let scrut_val = self.eval(scrutinee, env)?.to_value();
                for arm in arms {
                    let mut bindings = Vec::new();
                    if match_pattern(&arm.pattern, &scrut_val, &mut bindings) {
                        let n = bindings.len();
                        for b in bindings.iter().rev() { env.push(EvalResult::Val(b.clone())); }
                        let result = self.eval(&arm.body, env)?;
                        env.pop_n(n);
                        return Ok(result);
                    }
                }
                Err(format!("Non-exhaustive match: no arm matched {:?}", scrut_val))
            }

            ClosureNode::ListLit(elems) => {
                let mut vals = Vec::with_capacity(elems.len());
                for e in elems { vals.push(self.eval(e, env)?.to_value()); }
                Ok(EvalResult::Val(Value::list(vals)))
            }

            ClosureNode::RecordLit(fields) => {
                let mut map = BTreeMap::new();
                for (key, node) in fields { map.insert(key.clone(), self.eval(node, env)?.to_value()); }
                Ok(EvalResult::Val(Value::Record(Arc::new(map))))
            }

            ClosureNode::TupleLit(elems) => {
                let mut vals = Vec::with_capacity(elems.len());
                for e in elems { vals.push(self.eval(e, env)?.to_value()); }
                Ok(EvalResult::Val(Value::list(vals)))
            }

            ClosureNode::Constructor { name, args } => {
                let mut vals = Vec::with_capacity(args.len());
                for a in args { vals.push(self.eval(a, env)?.to_value()); }
                Ok(EvalResult::Val(construct_value(name, &vals)))
            }

            ClosureNode::FieldAccess { expr, fields } => {
                let mut val = self.eval(expr, env)?.to_value();
                for field in fields {
                    val = val.record_get(field)
                        .cloned()
                        .ok_or_else(|| format!("Field '{}' not found in {:?}", field, val))?;
                }
                Ok(EvalResult::Val(val))
            }

            ClosureNode::ComposedRef { hash, name } => {
                if let Some(cached) = self.compile_cache.get(hash) {
                    closure_from_compiled(cached)
                } else {
                    self.compile_composed(hash, name)
                }
            }

            ClosureNode::Intrinsic { id, .. } => Ok(EvalResult::Intrinsic(*id)),
        }
    }

    fn apply(&mut self, func: EvalResult, args: Vec<EvalResult>, env: &mut RuntimeEnv) -> Result<EvalResult, String> {
        match func {
            EvalResult::NativeFn { func: f, name } => {
                if name == "conditional" {
                    let vals = args_to_values(&args);
                    return self.eval_conditional(&vals);
                }
                let vals = args_to_values(&args);
                let result = f(&vals);
                Ok(EvalResult::Val(result))
            }

            EvalResult::DynPrimitive(provider) => {
                let vals = args_to_values(&args);
                if vals.len() != provider.arity() {
                    return Ok(EvalResult::Val(Value::Nothing));
                }
                Ok(EvalResult::Val(provider.call(&vals)))
            }

            EvalResult::Intrinsic(id) => {
                match id {
                    Intrinsic::ListFoldLeft  => self.eval_fold_left(&args, env),
                    Intrinsic::ListFoldRight => self.eval_fold_right(&args, env),
                    Intrinsic::ListMap       => self.eval_map(&args, env),
                    Intrinsic::ListFilter    => self.eval_filter(&args, env),
                    Intrinsic::ListHead      => self.eval_head(&args),
                    Intrinsic::ListMatch     => self.eval_list_match(&args, env),
                    Intrinsic::Fix           => self.eval_fix(&args, env),
                    Intrinsic::ListSortBy    => self.eval_sort_by(&args, env),
                    Intrinsic::ListTakeWhile => self.eval_take_while(&args, env),
                    Intrinsic::ListDropWhile => self.eval_drop_while(&args, env),
                    Intrinsic::ListUnfold    => self.eval_unfold(&args, env),
                }
            }

            EvalResult::Closure(closure) => {
                if args.len() < closure.arity {
                    return Err(format!("Closure expects {} args, got {}", closure.arity, args.len()));
                }
                let (current_args, remaining_args) = args.split_at(closure.arity);
                if let Some(_trampoline_depth) = closure.fix_trampoline_depth {
                    if let Some(f_ref) = closure.env_snapshot.first() {
                        let f = f_ref.clone();
                        let f_arity = match &f { EvalResult::Closure(rc) => rc.arity, _ => 0 };
                        if f_arity > 0 {
                            let mut all_user_args: Vec<EvalResult> = current_args.to_vec();
                            all_user_args.extend(remaining_args.iter().cloned());
                            let result = self.run_fix_loop(f, f_arity, &all_user_args, env)?;
                            return Ok(result);
                        }
                    }
                }

                let saved = core::mem::take(&mut env.stack);
                env.stack = closure.env_snapshot.clone();
                for arg in current_args.iter().rev() { env.push(arg.clone()); }
                let result = self.eval(&closure.body, env)?;
                env.pop_n(closure.arity);
                env.stack = saved;

                if remaining_args.is_empty() {
                    Ok(result)
                } else {
                    self.apply(result, remaining_args.to_vec(), env)
                }
            }

            EvalResult::Val(other) => {
                Err(format!("Cannot apply non-function: {:?}", other))
            }

            EvalResult::FixTailCall { depth, f, args } =>
                Ok(EvalResult::FixTailCall { depth, f, args }),
        }
    }

    fn compile_composed(&mut self, hash: &Hash, name: &str) -> Result<EvalResult, String> {
        let entry = self.catalog.get_by_name(name)
            .ok_or_else(|| format!("Function '{}' not found in catalog", name))?;
        match &entry.kind {
            FunctionKind::Composed(source) => {
                let compiled = self.compile_source(&entry.hash, &source.clone())?;
                self.compile_cache.insert(*hash, compiled.clone());
                closure_from_compiled(compiled)
            }
            FunctionKind::Primitive(f)                        => Ok(EvalResult::NativeFn { func: *f, name: name.to_string() }),
            FunctionKind::Accelerated { native_fn, .. }       => Ok(EvalResult::NativeFn { func: *native_fn, name: name.to_string() }),
            FunctionKind::Intrinsic(id)                       => Ok(EvalResult::Intrinsic(*id)),
            FunctionKind::DynPrimitive(arc)                   => Ok(EvalResult::DynPrimitive(arc.clone())),
        }
    }

    fn compile_source(&mut self, hash: &Hash, source: &str) -> Result<Arc<ClosureNode>, String> {
        if let Some(cached) = self.compile_cache.get(hash) { return Ok(cached); }
        let node = Arc::new(crate::compiler_bridge::compile_source(self.catalog, source)?);
        self.compile_cache.insert(*hash, node.clone());
        Ok(node)
    }

    fn eval_conditional(&self, args: &[Value]) -> Result<EvalResult, String> {
        match &args[0] {
            Value::Bool(true)  => Ok(EvalResult::Val(args[1].clone())),
            Value::Bool(false) | Value::Nothing => Ok(EvalResult::Val(args[2].clone())),
            _ => Err("conditional: first arg must be Bool".to_string()),
        }
    }

    // ── Intrinsics ────────────────────────────────────────────────────────────

    fn eval_fold_left(&mut self, args: &[EvalResult], env: &mut RuntimeEnv) -> Result<EvalResult, String> {
        let f    = &args[0];
        let init = args[1].to_value();
        let xs   = args[2].to_value();
        let xs = match xs.as_list() { Some(l) => l, None => return Ok(EvalResult::Val(init)) };
        let mut acc = init;
        // Clone the list to avoid borrow issues
        let xs_vec: Vec<Value> = xs.to_vec();
        for x in xs_vec {
            let r = self.apply(f.clone(), vec![EvalResult::Val(acc), EvalResult::Val(x)], env)?;
            acc = r.to_value();
        }
        Ok(EvalResult::Val(acc))
    }

    fn eval_fold_right(&mut self, args: &[EvalResult], env: &mut RuntimeEnv) -> Result<EvalResult, String> {
        let f    = &args[0];
        let init = args[1].to_value();
        let xs   = args[2].to_value();
        let xs = match xs.as_list() { Some(l) => l, None => return Ok(EvalResult::Val(init)) };
        let xs_vec: Vec<Value> = xs.to_vec();
        let mut acc = init;
        for x in xs_vec.into_iter().rev() {
            let r = self.apply(f.clone(), vec![EvalResult::Val(x), EvalResult::Val(acc)], env)?;
            acc = r.to_value();
        }
        Ok(EvalResult::Val(acc))
    }

    fn eval_map(&mut self, args: &[EvalResult], env: &mut RuntimeEnv) -> Result<EvalResult, String> {
        let f  = &args[0];
        let xs = args[1].to_value();
        let xs = match xs.as_list() { Some(l) => l, None => return Ok(EvalResult::Val(Value::list(vec![]))) };
        let xs_vec: Vec<Value> = xs.to_vec();
        let mut result = Vec::with_capacity(xs_vec.len());
        for x in xs_vec {
            let y = self.apply(f.clone(), vec![EvalResult::Val(x)], env)?.to_value();
            result.push(y);
        }
        Ok(EvalResult::Val(Value::list(result)))
    }

    fn eval_filter(&mut self, args: &[EvalResult], env: &mut RuntimeEnv) -> Result<EvalResult, String> {
        let pred = &args[0];
        let xs   = args[1].to_value();
        let xs   = match xs.as_list() { Some(l) => l, None => return Ok(EvalResult::Val(Value::list(vec![]))) };
        let xs_vec: Vec<Value> = xs.to_vec();
        let mut result = Vec::new();
        for x in xs_vec {
            let keep = self.apply(pred.clone(), vec![EvalResult::Val(x.clone())], env)?.to_value();
            if matches!(keep, Value::Bool(true)) { result.push(x); }
        }
        Ok(EvalResult::Val(Value::list(result)))
    }

    fn eval_head(&self, args: &[EvalResult]) -> Result<EvalResult, String> {
        let default = &args[0];
        let xs = args[1].to_value();
        match xs.as_list() {
            Some(list) => Ok(list.first().map(|v| EvalResult::Val(v.clone())).unwrap_or_else(|| default.clone())),
            None => Ok(default.clone()),
        }
    }

    fn eval_list_match(&mut self, args: &[EvalResult], env: &mut RuntimeEnv) -> Result<EvalResult, String> {
        let empty_val = &args[0];
        let cons_fn   = &args[1];
        let xs        = args[2].to_value();
        let xs = match xs.as_list() {
            Some(list) if !list.is_empty() => list,
            _ => return Ok(empty_val.clone()),
        };
        let head = EvalResult::Val(xs[0].clone());
        let tail = EvalResult::Val(Value::list(xs[1..].to_vec()));
        self.apply(cons_fn.clone(), vec![head, tail], env)
    }

    fn eval_fix(&mut self, args: &[EvalResult], env: &mut RuntimeEnv) -> Result<EvalResult, String> {
        let f = args[0].clone();
        let f_arity = match &f { EvalResult::Closure(rc) => rc.arity, _ => 1 };
        if f_arity == 0 { return Err("fix: function must take at least one param (self)".to_string()); }
        if args.len() == 1 { return Ok(Self::make_fixpoint_closure(f, f_arity, 0)); }
        self.run_fix_loop(f, f_arity, &args[1..], env)
    }

    fn run_fix_loop(&mut self, f: EvalResult, f_arity: usize, user_args: &[EvalResult], env: &mut RuntimeEnv) -> Result<EvalResult, String> {
        self.fix_depth += 1;
        let my_depth = self.fix_depth;
        let mut cur_f    = f;
        let mut cur_args: Vec<EvalResult> = user_args.to_vec();
        let mut iterations = 0usize;
        const MAX: usize = 100_000;

        let result = loop {
            iterations += 1;
            if iterations > MAX {
                self.fix_depth -= 1;
                return Err(format!("fix loop exceeded {} iterations (likely infinite recursion)", MAX));
            }
            let self_ref = Self::make_fixpoint_closure(cur_f.clone(), f_arity, my_depth);
            let mut call_args = vec![self_ref];
            call_args.extend(cur_args.drain(..));
            match self.apply(cur_f.clone(), call_args, env)? {
                EvalResult::FixTailCall { depth, f: next_f, args: next_args } if depth == my_depth => {
                    cur_f    = *next_f;
                    cur_args = next_args;
                }
                other => break other,
            }
        };
        self.fix_depth -= 1;
        Ok(result)
    }

    fn make_fixpoint_closure(f: EvalResult, f_arity: usize, depth: usize) -> EvalResult {
        let user_arity = f_arity - 1;
        let mut trampoline_args: Vec<ClosureNode> = vec![ClosureNode::Var(user_arity)];
        for i in 0..user_arity { trampoline_args.push(ClosureNode::Var(i)); }
        let body = ClosureNode::Apply {
            func: Box::new(ClosureNode::Intrinsic { id: Intrinsic::Fix, name: "fix".to_string() }),
            args: trampoline_args,
        };
        EvalResult::Closure(RuntimeClosure {
            arity: user_arity,
            body: Arc::new(body),
            env_snapshot: vec![f],
            fix_trampoline_depth: Some(depth),
        })
    }

    fn eval_sort_by(&mut self, args: &[EvalResult], env: &mut RuntimeEnv) -> Result<EvalResult, String> {
        let key_fn = &args[0];
        let xs = args[1].to_value();
        let xs = match xs.as_list() { Some(l) => l, None => return Ok(EvalResult::Val(Value::list(vec![]))) };
        if xs.is_empty() { return Ok(EvalResult::Val(Value::list(vec![]))); }
        let xs_vec: Vec<Value> = xs.to_vec();
        let mut keyed: Vec<(Value, Value)> = Vec::with_capacity(xs_vec.len());
        for x in xs_vec {
            let key = self.apply(key_fn.clone(), vec![EvalResult::Val(x.clone())], env)?.to_value();
            keyed.push((key, x));
        }
        keyed.sort_by(|(ka, _), (kb, _)| match (ka, kb) {
            (Value::Int(a), Value::Int(b))     => a.cmp(b),
            (Value::Float(a), Value::Float(b)) => a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal),
            (Value::Str(a), Value::Str(b))     => a.cmp(b),
            _ => std::cmp::Ordering::Equal,
        });
        Ok(EvalResult::Val(Value::list(keyed.into_iter().map(|(_, v)| v).collect())))
    }

    fn eval_take_while(&mut self, args: &[EvalResult], env: &mut RuntimeEnv) -> Result<EvalResult, String> {
        let pred = &args[0];
        let xs   = args[1].to_value();
        let xs   = match xs.as_list() { Some(l) => l, None => return Ok(EvalResult::Val(Value::list(vec![]))) };
        let xs_vec: Vec<Value> = xs.to_vec();
        let mut result = Vec::new();
        for x in xs_vec {
            let keep = self.apply(pred.clone(), vec![EvalResult::Val(x.clone())], env)?.to_value();
            match keep { Value::Bool(true) => result.push(x), _ => break }
        }
        Ok(EvalResult::Val(Value::list(result)))
    }

    fn eval_drop_while(&mut self, args: &[EvalResult], env: &mut RuntimeEnv) -> Result<EvalResult, String> {
        let pred = &args[0];
        let xs   = args[1].to_value();
        let xs   = match xs.as_list() { Some(l) => l, None => return Ok(EvalResult::Val(Value::list(vec![]))) };
        let xs_vec: Vec<Value> = xs.to_vec();
        let mut idx = 0;
        for x in xs_vec.iter() {
            let keep = self.apply(pred.clone(), vec![EvalResult::Val(x.clone())], env)?.to_value();
            match keep { Value::Bool(true) => idx += 1, _ => break }
        }
        Ok(EvalResult::Val(Value::list(xs_vec[idx..].to_vec())))
    }

    fn eval_unfold(&mut self, args: &[EvalResult], env: &mut RuntimeEnv) -> Result<EvalResult, String> {
        let step = &args[0];
        let mut state = args[1].to_value();
        let mut result = Vec::new();
        loop {
            let out = self.apply(step.clone(), vec![EvalResult::Val(state)], env)?.to_value();
            match out {
                Value::Nothing => break,
                Value::Just(pair) => {
                    let pair_list = match pair.as_ref() { Value::List(l) => l.clone(), _ => break };
                    if pair_list.len() < 2 { break; }
                    result.push(pair_list[0].clone());
                    state = pair_list[1].clone();
                }
                _ => break,
            }
        }
        Ok(EvalResult::Val(Value::list(result)))
    }
}

// ── Pattern matching ──────────────────────────────────────────────────────────

pub fn match_pattern(pattern: &ClosurePattern, value: &Value, bindings: &mut Vec<Value>) -> bool {
    match (pattern, value) {
        (ClosurePattern::Wildcard, _) => true,
        (ClosurePattern::Var, val)    => { bindings.push(val.clone()); true }
        (ClosurePattern::IntPat(a),    Value::Int(b))     => a == b,
        (ClosurePattern::StringPat(a), Value::Str(b))     => a.as_str() == b.as_ref(),
        (ClosurePattern::BoolPat(a),   Value::Bool(b))    => a == b,
        (ClosurePattern::NothingPat,   Value::Nothing)    => true,
        (ClosurePattern::Constructor { name, args: pats }, val) => {
            match (name.as_str(), val) {
                ("Just", Value::Just(inner)) if pats.len() == 1 => match_pattern(&pats[0], inner, bindings),
                ("Nothing", Value::Nothing) if pats.is_empty()  => true,
                _ => false,
            }
        }
        (ClosurePattern::TuplePat(pats), Value::List(xs)) if pats.len() == xs.len() => {
            for (p, v) in pats.iter().zip(xs.iter()) {
                if !match_pattern(p, v, bindings) { return false; }
            }
            true
        }
        _ => false,
    }
}

fn construct_value(name: &str, args: &[Value]) -> Value {
    match name {
        "Just" if args.len() == 1 => Value::Just(Box::new(args[0].clone())),
        "Nothing" => Value::Nothing,
        _ => {
            let mut fields = BTreeMap::new();
            fields.insert("tag".to_string(), Value::Str(Arc::from(name)));
            for (i, arg) in args.iter().enumerate() { fields.insert(format!("_{}", i), arg.clone()); }
            Value::Record(Arc::new(fields))
        }
    }
}

fn args_to_values(args: &[EvalResult]) -> Vec<Value> {
    args.iter().map(|a| a.to_value()).collect()
}

#[allow(dead_code)]
fn eval_results_to_values(results: &[EvalResult]) -> Vec<Value> {
    results.iter().map(|r| r.to_value()).collect()
}

fn closure_from_compiled(node: Arc<ClosureNode>) -> Result<EvalResult, String> {
    match &*node {
        ClosureNode::Lambda { arity, body } => Ok(EvalResult::Closure(RuntimeClosure {
            arity: *arity,
            body: Arc::new((**body).clone()),
            env_snapshot: Vec::new(),
            fix_trampoline_depth: None,
        })),
        _ => Ok(EvalResult::Closure(RuntimeClosure {
            arity: 0,
            body: node,
            env_snapshot: Vec::new(),
            fix_trampoline_depth: None,
        })),
    }
}
