//! Stack-based Forth interpreter with audio command generation.

use parking_lot::Mutex;
use rand::rngs::StdRng;
use rand::{Rng as RngTrait, SeedableRng};
use std::borrow::Cow;
use std::collections::HashMap;
use std::sync::Arc;

use super::compiler::compile_script;
use super::ops::Op;
use super::types::{
    CmdRegister, Dictionary, ExecutionTrace, ResolvedValue, Rng, SourceSpan, Stack, StepContext,
    Value, Variables, VariablesMap,
};

/// Forth VM instance. Holds the stack, variables, dictionary, and RNG.
pub struct Forth {
    stack: Stack,
    vars: Variables,
    dict: Dictionary,
    rng: Rng,
    global_params: Mutex<Vec<(&'static str, Value)>>,
}

impl Forth {
    pub fn new(vars: Variables, dict: Dictionary, rng: Rng) -> Self {
        Self {
            stack: Mutex::new(Vec::new()),
            vars,
            dict,
            rng,
            global_params: Mutex::new(Vec::new()),
        }
    }

    pub fn stack(&self) -> Vec<Value> {
        self.stack.lock().clone()
    }

    pub fn clear_stack(&self) {
        self.stack.lock().clear();
    }

    pub fn clear_global_params(&self) {
        self.global_params.lock().clear();
    }

    /// Evaluate a Forth script and return audio command strings.
    pub fn evaluate(&self, script: &str, ctx: &StepContext) -> Result<Vec<String>, String> {
        let (outputs, var_writes) = self.evaluate_impl(script, ctx, None)?;
        self.apply_var_writes(var_writes);
        Ok(outputs)
    }

    /// Evaluate and collect an execution trace for UI highlighting.
    pub fn evaluate_with_trace(
        &self,
        script: &str,
        ctx: &StepContext,
        trace: &mut ExecutionTrace,
    ) -> Result<Vec<String>, String> {
        let (outputs, var_writes) = self.evaluate_impl(script, ctx, Some(trace))?;
        self.apply_var_writes(var_writes);
        Ok(outputs)
    }

    /// Evaluate and return both outputs and pending variable writes (without applying them).
    pub fn evaluate_raw(
        &self,
        script: &str,
        ctx: &StepContext,
        trace: &mut ExecutionTrace,
    ) -> Result<(Vec<String>, HashMap<String, Value>), String> {
        self.evaluate_impl(script, ctx, Some(trace))
    }

    fn apply_var_writes(&self, writes: HashMap<String, Value>) {
        if writes.is_empty() {
            return;
        }
        let mut new_vars = (*self.vars.load_full()).clone();
        for (k, v) in writes {
            new_vars.insert(k, v);
        }
        self.vars.store(Arc::new(new_vars));
    }

    fn evaluate_impl(
        &self,
        script: &str,
        ctx: &StepContext,
        trace: Option<&mut ExecutionTrace>,
    ) -> Result<(Vec<String>, HashMap<String, Value>), String> {
        if script.trim().is_empty() {
            return Err("empty script".into());
        }

        let ops = compile_script(script, &self.dict)?;
        self.execute(&ops, ctx, trace)
    }

    fn execute(
        &self,
        ops: &[Op],
        ctx: &StepContext,
        trace: Option<&mut ExecutionTrace>,
    ) -> Result<(Vec<String>, HashMap<String, Value>), String> {
        let mut stack = self.stack.lock();
        let mut outputs: Vec<String> = Vec::with_capacity(8);
        let mut cmd = CmdRegister::new();
        let vars_snapshot = self.vars.load_full();
        let mut var_writes: HashMap<String, Value> = HashMap::new();

        cmd.set_global(self.global_params.lock().clone());

        self.execute_ops(
            ops,
            ctx,
            &mut stack,
            &mut outputs,
            &mut cmd,
            trace,
            &vars_snapshot,
            &mut var_writes,
        )?;

        *self.global_params.lock() = cmd.take_global();

        Ok((outputs, var_writes))
    }

    #[allow(clippy::too_many_arguments)]
    #[allow(clippy::only_used_in_recursion)]
    fn execute_ops(
        &self,
        ops: &[Op],
        ctx: &StepContext,
        stack: &mut Vec<Value>,
        outputs: &mut Vec<String>,
        cmd: &mut CmdRegister,
        trace: Option<&mut ExecutionTrace>,
        vars_snapshot: &VariablesMap,
        var_writes: &mut HashMap<String, Value>,
    ) -> Result<(), String> {
        let mut pc = 0;
        let mut marks: Vec<usize> = Vec::new();
        let trace_cell = std::cell::RefCell::new(trace);
        let var_writes_cell = std::cell::RefCell::new(Some(var_writes));

        let read_key = |vwc: &std::cell::RefCell<Option<&mut HashMap<String, Value>>>,
                        vs: &VariablesMap|
         -> i64 {
            vwc.borrow()
                .as_ref()
                .and_then(|vw| vw.get("__key__"))
                .or_else(|| vs.get("__key__"))
                .and_then(|v| v.as_int().ok())
                .unwrap_or(60)
        };

        let run_quotation = |quot: Value,
                             stack: &mut Vec<Value>,
                             outputs: &mut Vec<String>,
                             cmd: &mut CmdRegister|
         -> Result<(), String> {
            match quot {
                Value::Quotation(quot_ops, body_span) => {
                    if let Some(span) = body_span {
                        if let Some(trace) = trace_cell.borrow_mut().as_mut() {
                            trace.executed_spans.push(span);
                        }
                    }
                    let mut trace_opt = trace_cell.borrow_mut().take();
                    let mut var_writes_guard = var_writes_cell.borrow_mut();
                    let vw = var_writes_guard.as_mut().expect("var_writes taken");
                    self.execute_ops(
                        &quot_ops,
                        ctx,
                        stack,
                        outputs,
                        cmd,
                        trace_opt.as_deref_mut(),
                        vars_snapshot,
                        vw,
                    )?;
                    drop(var_writes_guard);
                    *trace_cell.borrow_mut() = trace_opt;
                    Ok(())
                }
                _ => Err("expected quotation".into()),
            }
        };

        let select_and_run = |selected: Value,
                              stack: &mut Vec<Value>,
                              outputs: &mut Vec<String>,
                              cmd: &mut CmdRegister|
         -> Result<(), String> {
            if let Some(span) = selected.span() {
                if let Some(trace) = trace_cell.borrow_mut().as_mut() {
                    trace.selected_spans.push(span);
                }
            }
            if matches!(selected, Value::Quotation(..)) {
                run_quotation(selected, stack, outputs, cmd)
            } else {
                stack.push(selected);
                Ok(())
            }
        };

        let drain_select_run = |count: usize,
                                idx: usize,
                                stack: &mut Vec<Value>,
                                outputs: &mut Vec<String>,
                                cmd: &mut CmdRegister|
         -> Result<(), String> {
            ensure(stack, count)?;
            let start = stack.len() - count;
            let selected = stack[start + idx].clone();
            stack.truncate(start);
            select_and_run(selected, stack, outputs, cmd)
        };

        let compute_poly_count = |cmd: &CmdRegister| -> usize {
            let sound_len = match cmd.sound() {
                Some(Value::CycleList(items)) => items.len(),
                _ => 1,
            };
            let param_max = cmd
                .global_params()
                .iter()
                .chain(cmd.params().iter())
                .map(|(_, v)| match v {
                    Value::CycleList(items) => items.len(),
                    _ => 1,
                })
                .max()
                .unwrap_or(1);
            sound_len.max(param_max)
        };

        let has_arp_list = |cmd: &CmdRegister| -> bool {
            matches!(cmd.sound(), Some(Value::ArpList(_)))
                || cmd.global_params().iter().chain(cmd.params().iter())
                    .any(|(_, v)| matches!(v, Value::ArpList(_)))
        };

        let compute_arp_count = |cmd: &CmdRegister| -> usize {
            let sound_len = match cmd.sound() {
                Some(Value::ArpList(items)) => items.len(),
                _ => 0,
            };
            let param_max = cmd
                .params()
                .iter()
                .map(|(_, v)| match v {
                    Value::ArpList(items) => items.len(),
                    _ => 0,
                })
                .max()
                .unwrap_or(0);
            sound_len.max(param_max).max(1)
        };

        let emit_with_cycling = |cmd: &CmdRegister,
                                 arp_idx: usize,
                                 poly_idx: usize,
                                 delta_secs: f64,
                                 outputs: &mut Vec<String>|
         -> Result<Option<Value>, String> {
            let has_sound = cmd.sound().is_some();
            let has_params = !cmd.params().is_empty();
            let has_global = !cmd.global_params().is_empty();
            if !has_sound && !has_params && !has_global {
                return Err("nothing to emit".into());
            }
            let resolved_sound_val =
                cmd.sound().map(|sv| resolve_value(sv, arp_idx, poly_idx));
            let sound_str = match &resolved_sound_val {
                Some(v) => Some(v.as_str()?.to_string()),
                None => None,
            };
            let resolved_params: Vec<(&str, String)> = cmd.global_params()
                .iter()
                .chain(cmd.params().iter())
                .map(|(k, v)| {
                    let resolved = resolve_value(v, arp_idx, poly_idx);
                    if let Value::CycleList(_) | Value::ArpList(_) = v {
                        if let Some(span) = resolved.span() {
                            if let Some(trace) = trace_cell.borrow_mut().as_mut() {
                                trace.selected_spans.push(span);
                            }
                        }
                    }
                    (*k, resolved.to_param_string())
                })
                .collect();
            emit_output(
                sound_str.as_deref(),
                &resolved_params,
                ctx.step_duration(),
                delta_secs,
                outputs,
            );
            Ok(resolved_sound_val.map(|v| v.into_owned()))
        };

        while pc < ops.len() {
            match &ops[pc] {
                Op::PushInt(n, span) => stack.push(Value::Int(*n, *span)),
                Op::PushFloat(f, span) => stack.push(Value::Float(*f, *span)),
                Op::PushStr(s, span) => stack.push(Value::Str(s.clone(), *span)),

                Op::Dup => {
                    ensure(stack, 1)?;
                    let v = stack.last().unwrap().clone();
                    stack.push(v);
                }
                Op::Dupn => {
                    let n = pop_int(stack)?;
                    let v = pop(stack)?;
                    for _ in 0..n {
                        stack.push(v.clone());
                    }
                }
                Op::Drop => {
                    pop(stack)?;
                }
                Op::Swap => {
                    ensure(stack, 2)?;
                    let len = stack.len();
                    stack.swap(len - 1, len - 2);
                }
                Op::Over => {
                    ensure(stack, 2)?;
                    let v = stack[stack.len() - 2].clone();
                    stack.push(v);
                }
                Op::Rot => {
                    ensure(stack, 3)?;
                    let v = stack.remove(stack.len() - 3);
                    stack.push(v);
                }
                Op::Nip => {
                    ensure(stack, 2)?;
                    stack.remove(stack.len() - 2);
                }
                Op::Tuck => {
                    ensure(stack, 2)?;
                    let len = stack.len();
                    let v = stack[len - 1].clone();
                    stack.insert(len - 2, v);
                }
                Op::Dup2 => {
                    ensure(stack, 2)?;
                    let len = stack.len();
                    let a = stack[len - 2].clone();
                    let b = stack[len - 1].clone();
                    stack.push(a);
                    stack.push(b);
                }
                Op::Drop2 => {
                    ensure(stack, 2)?;
                    stack.pop();
                    stack.pop();
                }
                Op::Swap2 => {
                    ensure(stack, 4)?;
                    let len = stack.len();
                    stack.swap(len - 4, len - 2);
                    stack.swap(len - 3, len - 1);
                }
                Op::Over2 => {
                    ensure(stack, 4)?;
                    let len = stack.len();
                    let a = stack[len - 4].clone();
                    let b = stack[len - 3].clone();
                    stack.push(a);
                    stack.push(b);
                }
                Op::Rev => {
                    let count = pop_int(stack)? as usize;
                    ensure(stack, count)?;
                    let start = stack.len() - count;
                    stack[start..].reverse();
                }
                Op::Shuffle => {
                    let count = pop_int(stack)? as usize;
                    ensure(stack, count)?;
                    let start = stack.len() - count;
                    let slice = &mut stack[start..];
                    let mut rng = self.rng.lock();
                    for i in (1..slice.len()).rev() {
                        let j = rng.gen_range(0..=i);
                        slice.swap(i, j);
                    }
                }
                Op::Sort => {
                    let count = pop_int(stack)? as usize;
                    ensure(stack, count)?;
                    let start = stack.len() - count;
                    stack[start..].sort_by(|a, b| {
                        a.as_float()
                            .unwrap_or(0.0)
                            .partial_cmp(&b.as_float().unwrap_or(0.0))
                            .unwrap_or(std::cmp::Ordering::Equal)
                    });
                }
                Op::RSort => {
                    let count = pop_int(stack)? as usize;
                    ensure(stack, count)?;
                    let start = stack.len() - count;
                    stack[start..].sort_by(|a, b| {
                        b.as_float()
                            .unwrap_or(0.0)
                            .partial_cmp(&a.as_float().unwrap_or(0.0))
                            .unwrap_or(std::cmp::Ordering::Equal)
                    });
                }
                Op::Sum => {
                    let count = pop_int(stack)? as usize;
                    ensure(stack, count)?;
                    let start = stack.len() - count;
                    let total: f64 = stack
                        .drain(start..)
                        .map(|v| v.as_float().unwrap_or(0.0))
                        .sum();
                    stack.push(float_to_value(total));
                }
                Op::Prod => {
                    let count = pop_int(stack)? as usize;
                    ensure(stack, count)?;
                    let start = stack.len() - count;
                    let product: f64 = stack
                        .drain(start..)
                        .map(|v| v.as_float().unwrap_or(1.0))
                        .product();
                    stack.push(float_to_value(product));
                }

                Op::Add => binary_op(stack, |a, b| a + b)?,
                Op::Sub => binary_op(stack, |a, b| a - b)?,
                Op::Mul => binary_op(stack, |a, b| a * b)?,
                Op::Div => {
                    let b = pop(stack)?;
                    let a = pop(stack)?;
                    if b.as_float().map_or(true, |v| v == 0.0) {
                        return Err("division by zero".into());
                    }
                    stack.push(lift_binary(a, b, |x, y| x / y)?);
                }
                Op::Mod => {
                    let b = pop(stack)?;
                    let a = pop(stack)?;
                    if b.as_float().map_or(true, |v| v == 0.0) {
                        return Err("modulo by zero".into());
                    }
                    let result = lift_binary(a, b, |x, y| (x as i64 % y as i64) as f64)?;
                    stack.push(result);
                }
                Op::Neg => {
                    let v = pop(stack)?;
                    stack.push(lift_unary(v, |x| -x)?);
                }
                Op::Abs => {
                    let v = pop(stack)?;
                    stack.push(lift_unary(v, |x| x.abs())?);
                }
                Op::Floor => {
                    let v = pop(stack)?;
                    stack.push(lift_unary(v, |x| x.floor())?);
                }
                Op::Ceil => {
                    let v = pop(stack)?;
                    stack.push(lift_unary(v, |x| x.ceil())?);
                }
                Op::Round => {
                    let v = pop(stack)?;
                    stack.push(lift_unary(v, |x| x.round())?);
                }
                Op::Min => binary_op(stack, |a, b| a.min(b))?,
                Op::Max => binary_op(stack, |a, b| a.max(b))?,
                Op::Pow => binary_op(stack, |a, b| a.powf(b))?,
                Op::Sqrt => {
                    let v = pop(stack)?;
                    stack.push(lift_unary(v, |x| x.sqrt())?);
                }
                Op::Sin => {
                    let v = pop(stack)?;
                    stack.push(lift_unary(v, |x| x.sin())?);
                }
                Op::Cos => {
                    let v = pop(stack)?;
                    stack.push(lift_unary(v, |x| x.cos())?);
                }
                Op::Log => {
                    let v = pop(stack)?;
                    stack.push(lift_unary(v, |x| x.ln())?);
                }

                Op::Eq => cmp_op(stack, |a, b| (a - b).abs() < f64::EPSILON)?,
                Op::Ne => cmp_op(stack, |a, b| (a - b).abs() >= f64::EPSILON)?,
                Op::Lt => cmp_op(stack, |a, b| a < b)?,
                Op::Gt => cmp_op(stack, |a, b| a > b)?,
                Op::Le => cmp_op(stack, |a, b| a <= b)?,
                Op::Ge => cmp_op(stack, |a, b| a >= b)?,

                Op::And => {
                    let b = pop_bool(stack)?;
                    let a = pop_bool(stack)?;
                    stack.push(Value::Int(if a && b { 1 } else { 0 }, None));
                }
                Op::Or => {
                    let b = pop_bool(stack)?;
                    let a = pop_bool(stack)?;
                    stack.push(Value::Int(if a || b { 1 } else { 0 }, None));
                }
                Op::Not => {
                    let v = pop_bool(stack)?;
                    stack.push(Value::Int(if v { 0 } else { 1 }, None));
                }
                Op::Xor => {
                    let b = pop_bool(stack)?;
                    let a = pop_bool(stack)?;
                    stack.push(Value::Int(if a ^ b { 1 } else { 0 }, None));
                }
                Op::Nand => {
                    let b = pop_bool(stack)?;
                    let a = pop_bool(stack)?;
                    stack.push(Value::Int(if !(a && b) { 1 } else { 0 }, None));
                }
                Op::Nor => {
                    let b = pop_bool(stack)?;
                    let a = pop_bool(stack)?;
                    stack.push(Value::Int(if !(a || b) { 1 } else { 0 }, None));
                }

                Op::BranchIfZero(offset, then_span, else_span) => {
                    let v = pop(stack)?;
                    if !v.is_truthy() {
                        if let Some(span) = else_span {
                            if let Some(trace) = trace_cell.borrow_mut().as_mut() {
                                trace.executed_spans.push(*span);
                            }
                        }
                        pc += offset;
                    } else if let Some(span) = then_span {
                        if let Some(trace) = trace_cell.borrow_mut().as_mut() {
                            trace.executed_spans.push(*span);
                        }
                    }
                }
                Op::Branch(offset) => {
                    pc += offset;
                }

                Op::NewCmd => {
                    ensure(stack, 1)?;
                    let values = std::mem::take(stack);
                    let val = if values.len() == 1 {
                        values.into_iter().next().unwrap()
                    } else {
                        Value::CycleList(Arc::from(values))
                    };
                    cmd.set_sound(val);
                }
                Op::SetParam(param) => {
                    ensure(stack, 1)?;
                    let values = std::mem::take(stack);
                    let val = if values.len() == 1 {
                        values.into_iter().next().unwrap()
                    } else {
                        Value::CycleList(Arc::from(values))
                    };
                    cmd.set_param(param, val);
                }

                Op::Emit => {
                    if has_arp_list(cmd) {
                        let arp_count = compute_arp_count(cmd);
                        let poly_count = compute_poly_count(cmd);
                        let explicit_deltas = !cmd.deltas().is_empty();
                        let delta_list: Vec<Value> = if explicit_deltas {
                            cmd.deltas().to_vec()
                        } else {
                            Vec::new()
                        };
                        let count = if explicit_deltas {
                            arp_count.max(delta_list.len())
                        } else {
                            arp_count
                        };

                        for i in 0..count {
                            let delta_secs = if explicit_deltas {
                                let dv = &delta_list[i % delta_list.len()];
                                let frac = dv.as_float()?;
                                if let Some(span) = dv.span() {
                                    if let Some(trace) = trace_cell.borrow_mut().as_mut() {
                                        trace.selected_spans.push(span);
                                    }
                                }
                                ctx.nudge_secs + frac * ctx.step_duration()
                            } else {
                                ctx.nudge_secs
                                    + (i as f64 / count as f64) * ctx.step_duration()
                            };
                            for poly_i in 0..poly_count {
                                if let Some(sound_val) =
                                    emit_with_cycling(cmd, i, poly_i, delta_secs, outputs)?
                                {
                                    if let Some(span) = sound_val.span() {
                                        if let Some(trace) =
                                            trace_cell.borrow_mut().as_mut()
                                        {
                                            trace.selected_spans.push(span);
                                        }
                                    }
                                }
                            }
                        }
                    } else {
                        let poly_count = compute_poly_count(cmd);
                        let deltas = if cmd.deltas().is_empty() {
                            vec![Value::Float(0.0, None)]
                        } else {
                            cmd.deltas().to_vec()
                        };

                        for poly_idx in 0..poly_count {
                            for delta_val in deltas.iter() {
                                let delta_frac = delta_val.as_float()?;
                                let delta_secs =
                                    ctx.nudge_secs + delta_frac * ctx.step_duration();
                                if let Some(span) = delta_val.span() {
                                    if let Some(trace) = trace_cell.borrow_mut().as_mut() {
                                        trace.selected_spans.push(span);
                                    }
                                }
                                if let Some(sound_val) =
                                    emit_with_cycling(cmd, 0, poly_idx, delta_secs, outputs)?
                                {
                                    if let Some(span) = sound_val.span() {
                                        if let Some(trace) =
                                            trace_cell.borrow_mut().as_mut()
                                        {
                                            trace.selected_spans.push(span);
                                        }
                                    }
                                }
                            }
                        }
                    }
                }

                Op::Get => {
                    let name = pop(stack)?;
                    let name = name.as_str()?;
                    let vw = var_writes_cell.borrow();
                    let vw_ref = vw.as_ref().expect("var_writes taken");
                    let val = vw_ref
                        .get(name)
                        .or_else(|| vars_snapshot.get(name))
                        .cloned()
                        .unwrap_or(Value::Int(0, None));
                    drop(vw);
                    stack.push(val);
                }
                Op::Set => {
                    let name = pop(stack)?;
                    let name = name.as_str()?.to_string();
                    let val = pop(stack)?;
                    var_writes_cell
                        .borrow_mut()
                        .as_mut()
                        .expect("var_writes taken")
                        .insert(name, val);
                }
                Op::SetKeep => {
                    let name = pop(stack)?;
                    let name = name.as_str()?.to_string();
                    let val = stack.last().ok_or("Stack underflow")?.clone();
                    var_writes_cell
                        .borrow_mut()
                        .as_mut()
                        .expect("var_writes taken")
                        .insert(name, val);
                }

                Op::GetContext(name) => {
                    let val = match *name {
                        "step" => Value::Int(ctx.step as i64, None),
                        "beat" => Value::Float(ctx.beat, None),
                        "bank" => Value::Int(ctx.bank as i64, None),
                        "pattern" => Value::Int(ctx.pattern as i64, None),
                        "tempo" => Value::Float(ctx.tempo, None),
                        "phase" => Value::Float(ctx.phase, None),
                        "slot" => Value::Int(ctx.slot as i64, None),
                        "runs" => Value::Int(ctx.runs as i64, None),
                        "iter" => Value::Int(ctx.iter as i64, None),
                        "speed" => Value::Float(ctx.speed, None),
                        "stepdur" => Value::Float(ctx.step_duration(), None),
                        "fill" => Value::Int(if ctx.fill { 1 } else { 0 }, None),
                        "mx" => Value::Float(ctx.mouse_x, None),
                        "my" => Value::Float(ctx.mouse_y, None),
                        "mdown" => Value::Float(ctx.mouse_down, None),
                        _ => Value::Int(0, None),
                    };
                    stack.push(val);
                }

                Op::Rand(word_span) => {
                    let b = pop(stack)?;
                    let a = pop(stack)?;
                    match (&a, &b) {
                        (Value::Int(a_i, _), Value::Int(b_i, _)) => {
                            let (lo, hi) = if a_i <= b_i {
                                (*a_i, *b_i)
                            } else {
                                (*b_i, *a_i)
                            };
                            let val = self.rng.lock().gen_range(lo..=hi);
                            record_resolved(&trace_cell, *word_span, ResolvedValue::Int(val));
                            stack.push(Value::Int(val, None));
                        }
                        _ => {
                            let a_f = a.as_float()?;
                            let b_f = b.as_float()?;
                            let (lo, hi) = if a_f <= b_f { (a_f, b_f) } else { (b_f, a_f) };
                            let val = if (hi - lo).abs() < f64::EPSILON {
                                lo
                            } else {
                                self.rng.lock().gen_range(lo..hi)
                            };
                            record_resolved(&trace_cell, *word_span, ResolvedValue::Float(val));
                            stack.push(Value::Float(val, None));
                        }
                    }
                }
                Op::ExpRand(word_span) => {
                    let hi = pop_float(stack)?;
                    let lo = pop_float(stack)?;
                    if lo <= 0.0 || hi <= 0.0 {
                        return Err("exprand requires positive values".into());
                    }
                    let (lo, hi) = if lo <= hi { (lo, hi) } else { (hi, lo) };
                    let u: f64 = self.rng.lock().gen();
                    let val = lo * (hi / lo).powf(u);
                    record_resolved(&trace_cell, *word_span, ResolvedValue::Float(val));
                    stack.push(Value::Float(val, None));
                }
                Op::LogRand(word_span) => {
                    let hi = pop_float(stack)?;
                    let lo = pop_float(stack)?;
                    if lo <= 0.0 || hi <= 0.0 {
                        return Err("logrand requires positive values".into());
                    }
                    let (lo, hi) = if lo <= hi { (lo, hi) } else { (hi, lo) };
                    let u: f64 = self.rng.lock().gen();
                    let val = hi * (lo / hi).powf(u);
                    record_resolved(&trace_cell, *word_span, ResolvedValue::Float(val));
                    stack.push(Value::Float(val, None));
                }
                Op::Seed => {
                    let s = pop_int(stack)?;
                    *self.rng.lock() = StdRng::seed_from_u64(s as u64);
                }

                Op::Cycle(word_span) | Op::PCycle(word_span) => {
                    let count = pop_int(stack)? as usize;
                    if count == 0 {
                        return Err("cycle count must be > 0".into());
                    }
                    let idx = match &ops[pc] {
                        Op::Cycle(_) => ctx.runs,
                        _ => ctx.iter,
                    } % count;
                    if let Some(span) = word_span {
                        if stack.len() >= count {
                            let start = stack.len() - count;
                            let selected = &stack[start + idx];
                            record_resolved_from_value(&trace_cell, Some(*span), selected);
                        }
                    }
                    drain_select_run(count, idx, stack, outputs, cmd)?;
                }

                Op::Choose(word_span) => {
                    let count = pop_int(stack)? as usize;
                    if count == 0 {
                        return Err("choose count must be > 0".into());
                    }
                    let idx = self.rng.lock().gen_range(0..count);
                    if let Some(span) = word_span {
                        if stack.len() >= count {
                            let start = stack.len() - count;
                            let selected = &stack[start + idx];
                            record_resolved_from_value(&trace_cell, Some(*span), selected);
                        }
                    }
                    drain_select_run(count, idx, stack, outputs, cmd)?;
                }

                Op::Bounce(word_span) | Op::PBounce(word_span) => {
                    let count = pop_int(stack)? as usize;
                    if count == 0 {
                        return Err("bounce count must be > 0".into());
                    }
                    let counter = match &ops[pc] {
                        Op::Bounce(_) => ctx.runs,
                        _ => ctx.iter,
                    };
                    let idx = if count == 1 {
                        0
                    } else {
                        let period = 2 * (count - 1);
                        let raw = counter % period;
                        if raw < count { raw } else { period - raw }
                    };
                    if let Some(span) = word_span {
                        if stack.len() >= count {
                            let start = stack.len() - count;
                            let selected = &stack[start + idx];
                            record_resolved_from_value(&trace_cell, Some(*span), selected);
                        }
                    }
                    drain_select_run(count, idx, stack, outputs, cmd)?;
                }

                Op::WChoose(word_span) => {
                    let count = pop_int(stack)? as usize;
                    if count == 0 {
                        return Err("wchoose count must be > 0".into());
                    }
                    let pairs_needed = count * 2;
                    ensure(stack, pairs_needed)?;
                    let start = stack.len() - pairs_needed;
                    let mut values = Vec::with_capacity(count);
                    let mut weights = Vec::with_capacity(count);
                    for i in 0..count {
                        let val = stack[start + i * 2].clone();
                        let w = stack[start + i * 2 + 1].as_float()?;
                        if w < 0.0 {
                            return Err("wchoose: negative weight".into());
                        }
                        values.push(val);
                        weights.push(w);
                    }
                    stack.truncate(start);
                    let total: f64 = weights.iter().sum();
                    if total <= 0.0 {
                        return Err("wchoose: total weight must be > 0".into());
                    }
                    let threshold: f64 = self.rng.lock().gen::<f64>() * total;
                    let mut cumulative = 0.0;
                    let mut selected_idx = count - 1;
                    for (i, &w) in weights.iter().enumerate() {
                        cumulative += w;
                        if threshold < cumulative {
                            selected_idx = i;
                            break;
                        }
                    }
                    let selected = values.swap_remove(selected_idx);
                    record_resolved_from_value(&trace_cell, *word_span, &selected);
                    select_and_run(selected, stack, outputs, cmd)?;
                }

                Op::ChanceExec(word_span) | Op::ProbExec(word_span) => {
                    let threshold = pop_float(stack)?;
                    let quot = pop(stack)?;
                    let val: f64 = self.rng.lock().gen();
                    let limit = match &ops[pc] {
                        Op::ChanceExec(_) => threshold,
                        _ => threshold / 100.0,
                    };
                    let fired = val < limit;
                    record_resolved(&trace_cell, *word_span, ResolvedValue::Bool(fired));
                    if fired {
                        run_quotation(quot, stack, outputs, cmd)?;
                    }
                }

                Op::Coin(word_span) => {
                    let val: f64 = self.rng.lock().gen();
                    let result = val < 0.5;
                    record_resolved(&trace_cell, *word_span, ResolvedValue::Bool(result));
                    stack.push(Value::Int(if result { 1 } else { 0 }, None));
                }

                Op::Every(word_span) => {
                    let n = pop_int(stack)?;
                    let quot = pop(stack)?;
                    if n <= 0 {
                        return Err("every count must be > 0".into());
                    }
                    let result = ctx.iter as i64 % n == 0;
                    record_resolved(&trace_cell, *word_span, ResolvedValue::Bool(result));
                    if result {
                        run_quotation(quot, stack, outputs, cmd)?;
                    }
                }

                Op::Except(word_span) => {
                    let n = pop_int(stack)?;
                    let quot = pop(stack)?;
                    if n <= 0 {
                        return Err("except count must be > 0".into());
                    }
                    let result = ctx.iter as i64 % n != 0;
                    record_resolved(&trace_cell, *word_span, ResolvedValue::Bool(result));
                    if result {
                        run_quotation(quot, stack, outputs, cmd)?;
                    }
                }

                Op::EveryOffset(word_span) => {
                    let offset = pop_int(stack)?;
                    let n = pop_int(stack)?;
                    let quot = pop(stack)?;
                    if n <= 0 {
                        return Err("every+ count must be > 0".into());
                    }
                    let result = ctx.iter as i64 % n == offset.rem_euclid(n);
                    record_resolved(&trace_cell, *word_span, ResolvedValue::Bool(result));
                    if result {
                        run_quotation(quot, stack, outputs, cmd)?;
                    }
                }

                Op::ExceptOffset(word_span) => {
                    let offset = pop_int(stack)?;
                    let n = pop_int(stack)?;
                    let quot = pop(stack)?;
                    if n <= 0 {
                        return Err("except+ count must be > 0".into());
                    }
                    let result = ctx.iter as i64 % n != offset.rem_euclid(n);
                    record_resolved(&trace_cell, *word_span, ResolvedValue::Bool(result));
                    if result {
                        run_quotation(quot, stack, outputs, cmd)?;
                    }
                }

                Op::Bjork(word_span) | Op::PBjork(word_span) => {
                    let n = pop_int(stack)?;
                    let k = pop_int(stack)?;
                    let quot = pop(stack)?;
                    if n <= 0 || k < 0 {
                        return Err("bjork: n must be > 0, k must be >= 0".into());
                    }
                    let counter = match &ops[pc] {
                        Op::Bjork(_) => ctx.runs,
                        _ => ctx.iter,
                    };
                    let pos = counter % n as usize;
                    let hit = k >= n || euclidean_hit(k as usize, n as usize, pos);
                    record_resolved(&trace_cell, *word_span, ResolvedValue::Bool(hit));
                    if hit {
                        run_quotation(quot, stack, outputs, cmd)?;
                    }
                }

                Op::Quotation(quote_ops, body_span) => {
                    stack.push(Value::Quotation(quote_ops.clone(), *body_span));
                }

                Op::When | Op::Unless => {
                    let cond = pop(stack)?;
                    let quot = pop(stack)?;
                    let should_run = match &ops[pc] {
                        Op::When => cond.is_truthy(),
                        _ => !cond.is_truthy(),
                    };
                    if should_run {
                        run_quotation(quot, stack, outputs, cmd)?;
                    }
                }

                Op::IfElse => {
                    let cond = pop(stack)?;
                    let false_quot = pop(stack)?;
                    let true_quot = pop(stack)?;
                    let quot = if cond.is_truthy() {
                        true_quot
                    } else {
                        false_quot
                    };
                    run_quotation(quot, stack, outputs, cmd)?;
                }

                Op::Pick => {
                    let idx_i = pop_int(stack)?;
                    if idx_i < 0 {
                        return Err(format!("pick index must be >= 0, got {idx_i}"));
                    }
                    let idx = idx_i as usize;
                    let mut quots: Vec<Value> = Vec::new();
                    while let Some(val) = stack.pop() {
                        match &val {
                            Value::Quotation(_, _) => quots.push(val),
                            _ => {
                                stack.push(val);
                                break;
                            }
                        }
                    }
                    quots.reverse();
                    if idx >= quots.len() {
                        return Err(format!(
                            "pick index {} out of range (have {} quotations)",
                            idx,
                            quots.len()
                        ));
                    }
                    run_quotation(quots.swap_remove(idx), stack, outputs, cmd)?;
                }

                Op::Mtof => {
                    let note = pop_float(stack)?;
                    let freq = 440.0 * 2.0_f64.powf((note - 69.0) / 12.0);
                    stack.push(Value::Float(freq, None));
                }

                Op::Ftom => {
                    let freq = pop_float(stack)?;
                    let note = 69.0 + 12.0 * (freq / 440.0).log2();
                    stack.push(Value::Float(note, None));
                }

                Op::Degree(pattern) => {
                    if pattern.is_empty() {
                        return Err("empty scale pattern".into());
                    }
                    ensure(stack, 1)?;
                    let len = pattern.len() as i64;
                    let key = read_key(&var_writes_cell, vars_snapshot);
                    let values = std::mem::take(stack);
                    for val in values {
                        let result = lift_unary_int(val, |degree| {
                            let octave_offset = degree.div_euclid(len);
                            let idx = degree.rem_euclid(len) as usize;
                            key + octave_offset * 12 + pattern[idx]
                        })?;
                        stack.push(result);
                    }
                }

                Op::Chord(intervals) => {
                    let root = pop_int(stack)?;
                    for &interval in *intervals {
                        stack.push(Value::Int(root + interval, None));
                    }
                }

                Op::Transpose => {
                    let n = pop_int(stack)?;
                    for val in stack.iter_mut() {
                        if let Value::Int(v, _) = val {
                            *v += n;
                        }
                    }
                }

                Op::SetKey => {
                    let key = pop_int(stack)?;
                    var_writes_cell
                        .borrow_mut()
                        .as_mut()
                        .expect("var_writes taken")
                        .insert("__key__".to_string(), Value::Int(key, None));
                }

                Op::Invert => {
                    ensure(stack, 2)?;
                    let start = stack.iter().rposition(|v| !matches!(v, Value::Int(..))).map_or(0, |i| i + 1);
                    let bottom = stack[start].as_int()? + 12;
                    stack.remove(start);
                    stack.push(Value::Int(bottom, None));
                }

                Op::DownInvert => {
                    ensure(stack, 2)?;
                    let top = pop_int(stack)? - 12;
                    let start = stack.iter().rposition(|v| !matches!(v, Value::Int(..))).map_or(0, |i| i + 1);
                    stack.insert(start, Value::Int(top, None));
                }

                Op::VoiceDrop2 => {
                    ensure(stack, 3)?;
                    let len = stack.len();
                    let note = stack[len - 2].as_int()? - 12;
                    stack.remove(len - 2);
                    let start = stack.iter().rposition(|v| !matches!(v, Value::Int(..))).map_or(0, |i| i + 1);
                    stack.insert(start, Value::Int(note, None));
                }

                Op::VoiceDrop3 => {
                    ensure(stack, 4)?;
                    let len = stack.len();
                    let note = stack[len - 3].as_int()? - 12;
                    stack.remove(len - 3);
                    let start = stack.iter().rposition(|v| !matches!(v, Value::Int(..))).map_or(0, |i| i + 1);
                    stack.insert(start, Value::Int(note, None));
                }

                Op::DiatonicTriad(pattern) => {
                    if pattern.is_empty() {
                        return Err("empty scale pattern".into());
                    }
                    let degree = pop_int(stack)?;
                    let key = read_key(&var_writes_cell, vars_snapshot);
                    let len = pattern.len() as i64;
                    for offset in [0, 2, 4] {
                        let d = degree + offset;
                        let octave_offset = d.div_euclid(len);
                        let idx = d.rem_euclid(len) as usize;
                        stack.push(Value::Int(key + octave_offset * 12 + pattern[idx], None));
                    }
                }

                Op::DiatonicSeventh(pattern) => {
                    if pattern.is_empty() {
                        return Err("empty scale pattern".into());
                    }
                    let degree = pop_int(stack)?;
                    let key = read_key(&var_writes_cell, vars_snapshot);
                    let len = pattern.len() as i64;
                    for offset in [0, 2, 4, 6] {
                        let d = degree + offset;
                        let octave_offset = d.div_euclid(len);
                        let idx = d.rem_euclid(len) as usize;
                        stack.push(Value::Int(key + octave_offset * 12 + pattern[idx], None));
                    }
                }

                Op::Oct => {
                    let shift = pop(stack)?;
                    let note = pop(stack)?;
                    let result = lift_binary(note, shift, |n, s| n + s * 12.0)?;
                    stack.push(result);
                }

                Op::SetTempo => {
                    let tempo = pop_float(stack)?;
                    let clamped = tempo.clamp(20.0, 300.0);
                    var_writes_cell
                        .borrow_mut()
                        .as_mut()
                        .expect("var_writes taken")
                        .insert("__tempo__".to_string(), Value::Float(clamped, None));
                }

                Op::SetSpeed => {
                    let speed = pop_float(stack)?;
                    let clamped = speed.clamp(0.125, 8.0);
                    var_writes_cell
                        .borrow_mut()
                        .as_mut()
                        .expect("var_writes taken")
                        .insert(ctx.speed_key.to_string(), Value::Float(clamped, None));
                }

                Op::Loop => {
                    let beats = pop_float(stack)?;
                    if ctx.tempo == 0.0 || ctx.speed == 0.0 {
                        return Err("tempo and speed must be non-zero".into());
                    }
                    let dur = beats * 60.0 / ctx.tempo / ctx.speed;
                    cmd.set_param("fit", Value::Float(dur, None));
                    cmd.set_param("dur", Value::Float(dur, None));
                }

                Op::At => {
                    ensure(stack, 1)?;
                    let deltas = std::mem::take(stack);
                    cmd.set_deltas(deltas);
                }

                Op::Arp => {
                    ensure(stack, 1)?;
                    let values = std::mem::take(stack);
                    stack.push(Value::ArpList(Arc::from(values)));
                }

                Op::Adsr => {
                    let r = pop(stack)?;
                    let s = pop(stack)?;
                    let d = pop(stack)?;
                    let a = pop(stack)?;
                    cmd.set_param("attack", a);
                    cmd.set_param("decay", d);
                    cmd.set_param("sustain", s);
                    cmd.set_param("release", r);
                }

                Op::Ad => {
                    let d = pop(stack)?;
                    let a = pop(stack)?;
                    cmd.set_param("attack", a);
                    cmd.set_param("decay", d);
                    cmd.set_param("sustain", Value::Int(0, None));
                }

                Op::Apply => {
                    let quot = pop(stack)?;
                    run_quotation(quot, stack, outputs, cmd)?;
                }

                Op::Ramp => {
                    let curve = pop_float(stack)?;
                    let freq = pop_float(stack)?;
                    let phase = (freq * ctx.beat).fract();
                    let phase = if phase < 0.0 { phase + 1.0 } else { phase };
                    let val = phase.powf(curve);
                    stack.push(Value::Float(val, None));
                }
                Op::Triangle => {
                    let freq = pop_float(stack)?;
                    let phase = (freq * ctx.beat).fract();
                    let phase = if phase < 0.0 { phase + 1.0 } else { phase };
                    let val = 1.0 - (2.0 * phase - 1.0).abs();
                    stack.push(Value::Float(val, None));
                }
                Op::Range => {
                    let max = pop_float(stack)?;
                    let min = pop_float(stack)?;
                    let val = pop_float(stack)?;
                    stack.push(Value::Float(min + val * (max - min), None));
                }
                Op::LinMap => {
                    let out_hi = pop_float(stack)?;
                    let out_lo = pop_float(stack)?;
                    let in_hi = pop_float(stack)?;
                    let in_lo = pop_float(stack)?;
                    let val = pop_float(stack)?;
                    let t = if (in_hi - in_lo).abs() < f64::EPSILON {
                        0.0
                    } else {
                        (val - in_lo) / (in_hi - in_lo)
                    };
                    stack.push(Value::Float(out_lo + t * (out_hi - out_lo), None));
                }
                Op::ExpMap => {
                    let hi = pop_float(stack)?;
                    let lo = pop_float(stack)?;
                    let val = pop_float(stack)?;
                    if lo <= 0.0 || hi <= 0.0 {
                        return Err("expmap requires positive bounds".into());
                    }
                    stack.push(Value::Float(lo * (hi / lo).powf(val), None));
                }
                Op::Perlin => {
                    let freq = pop_float(stack)?;
                    let val = perlin_noise_1d(freq * ctx.beat);
                    stack.push(Value::Float(val, None));
                }

                Op::ClearCmd => {
                    cmd.clear();
                }

                Op::EmitAll => {
                    // Retroactive: patch existing sound outputs with current params
                    if !cmd.params().is_empty() {
                        let step_duration = ctx.step_duration();
                        for output in outputs.iter_mut() {
                            if output.starts_with("/sound/") {
                                use std::fmt::Write;
                                for (k, v) in cmd.params() {
                                    let val_str = v.to_param_string();
                                    if !output.ends_with('/') {
                                        output.push('/');
                                    }
                                    if is_tempo_scaled_param(k) {
                                        if let Ok(val) = val_str.parse::<f64>() {
                                            let _ = write!(output, "{k}/{}", val * step_duration);
                                            continue;
                                        }
                                    }
                                    let _ = write!(output, "{k}/{val_str}");
                                }
                            }
                        }
                    }
                    // Prospective: store for future emits
                    cmd.commit_global();
                }

                Op::ClearGlobal => {
                    cmd.clear_global();
                }

                Op::IntRange => {
                    let end = pop_int(stack)?;
                    let start = pop_int(stack)?;
                    if start <= end {
                        for i in start..=end {
                            stack.push(Value::Int(i, None));
                        }
                    } else {
                        for i in (end..=start).rev() {
                            stack.push(Value::Int(i, None));
                        }
                    }
                }

                Op::StepRange => {
                    let step = pop_float(stack)?;
                    let end = pop_float(stack)?;
                    let start = pop_float(stack)?;
                    if step == 0.0 {
                        return Err("step cannot be zero".into());
                    }
                    let ascending = step > 0.0;
                    let mut val = start;
                    loop {
                        let done = if ascending { val > end } else { val < end };
                        if done {
                            break;
                        }
                        stack.push(float_to_value(val));
                        val += step;
                    }
                }

                Op::Generate => {
                    let count = pop_int(stack)?;
                    let quot = pop(stack)?;
                    if count < 0 {
                        return Err("gen count must be >= 0".into());
                    }
                    let mut results = Vec::with_capacity(count as usize);
                    for _ in 0..count {
                        run_quotation(quot.clone(), stack, outputs, cmd)?;
                        results.push(stack.pop().ok_or("gen: quotation must produce a value")?);
                    }
                    for val in results {
                        stack.push(val);
                    }
                }

                Op::Times => {
                    let quot = pop(stack)?;
                    let count = pop_int(stack)?;
                    if count < 0 {
                        return Err("times count must be >= 0".into());
                    }
                    for i in 0..count {
                        var_writes_cell
                            .borrow_mut()
                            .as_mut()
                            .expect("var_writes taken")
                            .insert("i".to_string(), Value::Int(i, None));
                        run_quotation(quot.clone(), stack, outputs, cmd)?;
                    }
                }

                Op::GeomRange => {
                    let count = pop_int(stack)?;
                    let ratio = pop_float(stack)?;
                    let start = pop_float(stack)?;
                    if count < 0 {
                        return Err("geom.. count must be >= 0".into());
                    }
                    let mut val = start;
                    for _ in 0..count {
                        stack.push(float_to_value(val));
                        val *= ratio;
                    }
                }

                Op::Euclid => {
                    let n = pop_int(stack)?;
                    let k = pop_int(stack)?;
                    if k < 0 || n < 0 {
                        return Err("euclid: k and n must be >= 0".into());
                    }
                    for idx in euclidean_rhythm(k as usize, n as usize, 0) {
                        stack.push(Value::Int(idx, None));
                    }
                }

                Op::EuclidRot => {
                    let r = pop_int(stack)?;
                    let n = pop_int(stack)?;
                    let k = pop_int(stack)?;
                    if k < 0 || n < 0 || r < 0 {
                        return Err("euclidrot: k, n, and r must be >= 0".into());
                    }
                    for idx in euclidean_rhythm(k as usize, n as usize, r as usize) {
                        stack.push(Value::Int(idx, None));
                    }
                }

                Op::ModLfo(shape) => {
                    let period = pop_float(stack)? * ctx.step_duration();
                    let max = pop_float(stack)?;
                    let min = pop_float(stack)?;
                    let suffix = match shape { 1 => "t", 2 => "w", 3 => "q", _ => "" };
                    let s = format!("{min}~{max}:{period}{suffix}");
                    stack.push(Value::Str(s.into(), None));
                }
                Op::ModSlide(curve) => {
                    let dur = pop_float(stack)? * ctx.step_duration();
                    let end = pop_float(stack)?;
                    let start = pop_float(stack)?;
                    let suffix = match curve { 1 => "e", 2 => "s", _ => "" };
                    let s = format!("{start}>{end}:{dur}{suffix}");
                    stack.push(Value::Str(s.into(), None));
                }
                Op::ModRnd(dist) => {
                    let period = pop_float(stack)? * ctx.step_duration();
                    let max = pop_float(stack)?;
                    let min = pop_float(stack)?;
                    let suffix = match dist { 1 => "s", 2 => "d", _ => "" };
                    let s = format!("{min}?{max}:{period}{suffix}");
                    stack.push(Value::Str(s.into(), None));
                }
                Op::ModEnv => {
                    ensure(stack, 1)?;
                    let values = std::mem::take(stack);
                    let mut floats = Vec::with_capacity(values.len());
                    for v in &values {
                        floats.push(v.as_float()?);
                    }
                    if floats.len() < 3 || (floats.len() - 1) % 2 != 0 {
                        return Err("env expects: start target1 dur1 [target2 dur2 ...]".into());
                    }
                    let step_dur = ctx.step_duration();
                    use std::fmt::Write;
                    let mut s = String::new();
                    let _ = write!(&mut s, "{}", floats[0]);
                    for pair in floats[1..].chunks(2) {
                        let _ = write!(&mut s, ">{}:{}", pair[0], pair[1] * step_dur);
                    }
                    stack.push(Value::Str(s.into(), None));
                }

                // MIDI operations
                Op::MidiEmit => {
                    let (_, params) = cmd.snapshot().unwrap_or((None, &[]));

                    // Build schedule: (arp_idx, poly_idx, delta_secs)
                    let schedule: Vec<(usize, usize, f64)> = if has_arp_list(cmd) {
                        let arp_count = compute_arp_count(cmd);
                        let poly_count = compute_poly_count(cmd);
                        let explicit = !cmd.deltas().is_empty();
                        let delta_list = cmd.deltas();
                        let count = if explicit {
                            arp_count.max(delta_list.len())
                        } else {
                            arp_count
                        };
                        let mut sched = Vec::with_capacity(count * poly_count);
                        for i in 0..count {
                            let delta_secs = if explicit {
                                let frac = delta_list[i % delta_list.len()]
                                    .as_float()
                                    .unwrap_or(0.0);
                                ctx.nudge_secs + frac * ctx.step_duration()
                            } else {
                                ctx.nudge_secs
                                    + (i as f64 / count as f64) * ctx.step_duration()
                            };
                            for poly_i in 0..poly_count {
                                sched.push((i, poly_i, delta_secs));
                            }
                        }
                        sched
                    } else {
                        let poly_count = compute_poly_count(cmd);
                        let deltas: Vec<f64> = if cmd.deltas().is_empty() {
                            vec![0.0]
                        } else {
                            cmd.deltas()
                                .iter()
                                .filter_map(|v| v.as_float().ok())
                                .collect()
                        };
                        let mut sched = Vec::with_capacity(poly_count * deltas.len());
                        for poly_idx in 0..poly_count {
                            for &frac in &deltas {
                                sched.push((
                                    0,
                                    poly_idx,
                                    ctx.nudge_secs + frac * ctx.step_duration(),
                                ));
                            }
                        }
                        sched
                    };

                    for (arp_idx, poly_idx, delta_secs) in schedule {
                        let get_int = |name: &str| -> Option<i64> {
                            params
                                .iter()
                                .rev()
                                .find(|(k, _)| *k == name)
                                .and_then(|(_, v)| {
                                    resolve_value(v, arp_idx, poly_idx).as_int().ok()
                                })
                        };
                        let get_float = |name: &str| -> Option<f64> {
                            params
                                .iter()
                                .rev()
                                .find(|(k, _)| *k == name)
                                .and_then(|(_, v)| {
                                    resolve_value(v, arp_idx, poly_idx).as_float().ok()
                                })
                        };
                        let chan = get_int("chan")
                            .map(|c| (c.clamp(1, 16) - 1) as u8)
                            .unwrap_or(0);
                        let dev =
                            get_int("dev").map(|d| d.clamp(0, 3) as u8).unwrap_or(0);
                        let delta_suffix = if delta_secs > 0.0 {
                            format!("/delta/{delta_secs}")
                        } else {
                            String::new()
                        };

                        if let (Some(cc), Some(val)) = (get_int("ccnum"), get_int("ccout")) {
                            let cc = cc.clamp(0, 127) as u8;
                            let val = val.clamp(0, 127) as u8;
                            outputs.push(format!(
                                "/midi/cc/{cc}/{val}/chan/{chan}/dev/{dev}{delta_suffix}"
                            ));
                        } else if let Some(bend) = get_float("bend") {
                            let bend_clamped = bend.clamp(-1.0, 1.0);
                            let bend_14bit = ((bend_clamped + 1.0) * 8191.5) as u16;
                            outputs.push(format!(
                                "/midi/bend/{bend_14bit}/chan/{chan}/dev/{dev}{delta_suffix}"
                            ));
                        } else if let Some(pressure) = get_int("pressure") {
                            let pressure = pressure.clamp(0, 127) as u8;
                            outputs.push(format!(
                                "/midi/pressure/{pressure}/chan/{chan}/dev/{dev}{delta_suffix}"
                            ));
                        } else if let Some(program) = get_int("program") {
                            let program = program.clamp(0, 127) as u8;
                            outputs.push(format!(
                                "/midi/program/{program}/chan/{chan}/dev/{dev}{delta_suffix}"
                            ));
                        } else {
                            let note = get_int("note").unwrap_or(60).clamp(0, 127) as u8;
                            let velocity =
                                get_int("velocity").unwrap_or(100).clamp(0, 127) as u8;
                            let dur = get_float("dur").unwrap_or(1.0);
                            let dur_secs = dur * ctx.step_duration();
                            outputs.push(format!(
                                "/midi/note/{note}/vel/{velocity}/chan/{chan}/dur/{dur_secs}/dev/{dev}{delta_suffix}"
                            ));
                        }
                    }
                }
                Op::MidiClock => {
                    let (_, params) = cmd.snapshot().unwrap_or((None, &[]));
                    let dev = extract_dev_param(params);
                    outputs.push(format!("/midi/clock/dev/{dev}"));
                }
                Op::MidiStart => {
                    let (_, params) = cmd.snapshot().unwrap_or((None, &[]));
                    let dev = extract_dev_param(params);
                    outputs.push(format!("/midi/start/dev/{dev}"));
                }
                Op::MidiStop => {
                    let (_, params) = cmd.snapshot().unwrap_or((None, &[]));
                    let dev = extract_dev_param(params);
                    outputs.push(format!("/midi/stop/dev/{dev}"));
                }
                Op::MidiContinue => {
                    let (_, params) = cmd.snapshot().unwrap_or((None, &[]));
                    let dev = extract_dev_param(params);
                    outputs.push(format!("/midi/continue/dev/{dev}"));
                }
                Op::GetMidiCC => {
                    let chan = pop_int(stack)?;
                    let cc = pop_int(stack)?;
                    let cc_clamped = (cc.clamp(0, 127)) as usize;
                    let chan_clamped = (chan.clamp(1, 16) - 1) as usize;
                    let (_, params) = cmd.snapshot().unwrap_or((None, &[]));
                    let dev = extract_dev_param(params) as usize;
                    let val = ctx
                        .cc_access
                        .as_ref()
                        .map(|cc| cc.get_cc(dev, chan_clamped, cc_clamped))
                        .unwrap_or(0);
                    stack.push(Value::Int(val as i64, None));
                }
                Op::Mark => {
                    marks.push(stack.len());
                }
                Op::Count(span) => {
                    let mark = marks.pop().ok_or("count without mark")?;
                    stack.push(Value::Int((stack.len() - mark) as i64, *span));
                }
                Op::Index(word_span) => {
                    let idx = pop_int(stack)?;
                    let count = pop_int(stack)? as usize;
                    if count == 0 {
                        return Err("index count must be > 0".into());
                    }
                    let resolved_idx = ((idx % count as i64 + count as i64) % count as i64) as usize;
                    if let Some(span) = word_span {
                        if stack.len() >= count {
                            let start = stack.len() - count;
                            let selected = &stack[start + resolved_idx];
                            record_resolved_from_value(&trace_cell, Some(*span), selected);
                        }
                    }
                    drain_select_run(count, resolved_idx, stack, outputs, cmd)?;
                }
                Op::Rec => {
                    let name = pop(stack)?;
                    outputs.push(format!("/doux/rec/sound/{}", name.as_str()?));
                }
                Op::Overdub => {
                    let name = pop(stack)?;
                    outputs.push(format!("/doux/rec/sound/{}/overdub/1", name.as_str()?));
                }
                Op::Orec => {
                    let orbit = pop(stack)?.as_int()?;
                    let name = pop(stack)?;
                    outputs.push(format!("/doux/rec/sound/{}/orbit/{}", name.as_str()?, orbit));
                }
                Op::Odub => {
                    let orbit = pop(stack)?.as_int()?;
                    let name = pop(stack)?;
                    outputs.push(format!("/doux/rec/sound/{}/overdub/1/orbit/{}", name.as_str()?, orbit));
                }
                Op::Forget => {
                    let name = pop(stack)?;
                    self.dict.lock().remove(name.as_str()?);
                }
            }
            pc += 1;
        }

        Ok(())
    }
}

fn record_resolved(
    trace_cell: &std::cell::RefCell<Option<&mut ExecutionTrace>>,
    span: Option<SourceSpan>,
    value: ResolvedValue,
) {
    if let Some(span) = span {
        if let Some(trace) = trace_cell.borrow_mut().as_mut() {
            trace.resolved.push((span, value));
        }
    }
}

fn record_resolved_from_value(
    trace_cell: &std::cell::RefCell<Option<&mut ExecutionTrace>>,
    span: Option<SourceSpan>,
    value: &Value,
) {
    if let Some(span) = span {
        let resolved = match value {
            Value::Int(i, _) => ResolvedValue::Int(*i),
            Value::Float(f, _) => ResolvedValue::Float(*f),
            Value::Str(s, _) => ResolvedValue::Str(s.clone()),
            _ => return,
        };
        if let Some(trace) = trace_cell.borrow_mut().as_mut() {
            trace.resolved.push((span, resolved));
        }
    }
}

fn extract_dev_param(params: &[(&str, Value)]) -> u8 {
    params
        .iter()
        .rev()
        .find(|(k, _)| *k == "dev")
        .and_then(|(_, v)| v.as_int().ok())
        .map(|d| d.clamp(0, 3) as u8)
        .unwrap_or(0)
}

fn is_tempo_scaled_param(name: &str) -> bool {
    matches!(
        name,
        "attack"
            | "decay"
            | "release"
            | "lpa"
            | "lpd"
            | "lpr"
            | "hpa"
            | "hpd"
            | "hpr"
            | "bpa"
            | "bpd"
            | "bpr"
            | "patt"
            | "pdec"
            | "prel"
            | "fma"
            | "fmd"
            | "fmr"
            | "glide"
            | "chorusdelay"
            | "duration"
    )
}

fn emit_output(
    sound: Option<&str>,
    params: &[(&str, String)],
    step_duration: f64,
    nudge_secs: f64,
    outputs: &mut Vec<String>,
) {
    use std::fmt::Write;
    let mut out = String::with_capacity(128);
    out.push('/');

    let has_dur = params.iter().any(|(k, _)| *k == "dur");
    let delaytime_idx = params.iter().position(|(k, _)| *k == "delaytime");

    if let Some(s) = sound {
        let _ = write!(&mut out, "sound/{s}");
    }

    for (i, (k, v)) in params.iter().enumerate() {
        if !out.ends_with('/') {
            out.push('/');
        }
        if is_tempo_scaled_param(k) {
            if let Ok(val) = v.parse::<f64>() {
                let _ = write!(&mut out, "{k}/{}", val * step_duration);
                continue;
            }
        }
        if Some(i) == delaytime_idx && sound.is_some() {
            let ratio: f64 = v.parse().unwrap_or(1.0);
            let _ = write!(&mut out, "{k}/{}", ratio * step_duration);
        } else {
            let _ = write!(&mut out, "{k}/{v}");
        }
    }

    if nudge_secs > 0.0 {
        if !out.ends_with('/') {
            out.push('/');
        }
        let _ = write!(&mut out, "delta/{nudge_secs}");
    }

    if !has_dur {
        if !out.ends_with('/') {
            out.push('/');
        }
        let _ = write!(&mut out, "dur/{}", step_duration * 4.0);
    }

    if sound.is_some() && delaytime_idx.is_none() {
        if !out.ends_with('/') {
            out.push('/');
        }
        let _ = write!(&mut out, "delaytime/{step_duration}");
    }

    outputs.push(out);
}

fn euclidean_hit(k: usize, n: usize, pos: usize) -> bool {
    if k == 0 {
        return false;
    }
    ((pos + 1) * k) / n != (pos * k) / n
}

fn euclidean_rhythm(k: usize, n: usize, rotation: usize) -> Vec<i64> {
    if k == 0 || n == 0 {
        return Vec::new();
    }
    if k >= n {
        return (0..n as i64).collect();
    }

    let mut groups: Vec<Vec<bool>> = (0..k).map(|_| vec![true]).collect();
    groups.extend((0..(n - k)).map(|_| vec![false]));

    while groups.len() > 1 {
        let ones_count = groups.iter().filter(|g| g[0]).count();
        let zeros_count = groups.len() - ones_count;

        if zeros_count == 0 || ones_count == 0 {
            break;
        }

        let min_count = ones_count.min(zeros_count);
        let mut new_groups = Vec::with_capacity(groups.len() - min_count);

        let (mut ones, mut zeros): (Vec<_>, Vec<_>) =
            groups.into_iter().partition(|g| g[0]);

        for _ in 0..min_count {
            let mut one = ones.pop().unwrap();
            one.extend(zeros.pop().unwrap());
            new_groups.push(one);
        }
        new_groups.extend(ones);
        new_groups.extend(zeros);

        groups = new_groups;
    }

    let pattern: Vec<bool> = groups.into_iter().flatten().collect();

    let rotated = if rotation > 0 && !pattern.is_empty() {
        let r = rotation % pattern.len();
        pattern.iter().cycle().skip(r).take(pattern.len()).copied().collect()
    } else {
        pattern
    };

    rotated
        .into_iter()
        .enumerate()
        .filter_map(|(i, hit)| if hit { Some(i as i64) } else { None })
        .collect()
}

fn perlin_grad(hash_input: i64) -> f64 {
    let mut h = (hash_input as u64)
        .wrapping_mul(6364136223846793005)
        .wrapping_add(1442695040888963407);
    h ^= h >> 33;
    h = h.wrapping_mul(0xff51afd7ed558ccd);
    h ^= h >> 33;
    // Convert to float in [-1, 1] range for varied gradients
    (h as i64 as f64) / (i64::MAX as f64)
}

fn perlin_noise_1d(x: f64) -> f64 {
    let x0 = x.floor() as i64;
    let t = x - x0 as f64;
    let s = t * t * (3.0 - 2.0 * t);
    let d0 = perlin_grad(x0) * t;
    let d1 = perlin_grad(x0 + 1) * (t - 1.0);
    (d0 + s * (d1 - d0)) * 0.5 + 0.5
}

fn pop(stack: &mut Vec<Value>) -> Result<Value, String> {
    stack.pop().ok_or_else(|| "stack underflow".to_string())
}

fn pop_int(stack: &mut Vec<Value>) -> Result<i64, String> {
    pop(stack)?.as_int()
}

fn pop_float(stack: &mut Vec<Value>) -> Result<f64, String> {
    pop(stack)?.as_float()
}

fn pop_bool(stack: &mut Vec<Value>) -> Result<bool, String> {
    Ok(pop(stack)?.is_truthy())
}

fn ensure(stack: &[Value], n: usize) -> Result<(), String> {
    if stack.len() < n {
        return Err("stack underflow".into());
    }
    Ok(())
}

fn float_to_value(result: f64) -> Value {
    if result.fract() == 0.0 && result.abs() < i64::MAX as f64 {
        Value::Int(result as i64, None)
    } else {
        Value::Float(result, None)
    }
}

fn lift_unary<F>(val: Value, f: F) -> Result<Value, String>
where
    F: Fn(f64) -> f64 + Copy,
{
    match val {
        Value::ArpList(items) => {
            let mapped: Result<Vec<_>, _> = items.iter().map(|x| lift_unary(x.clone(), f)).collect();
            Ok(Value::ArpList(Arc::from(mapped?)))
        }
        Value::CycleList(items) => {
            let mapped: Result<Vec<_>, _> = items.iter().map(|x| lift_unary(x.clone(), f)).collect();
            Ok(Value::CycleList(Arc::from(mapped?)))
        }
        v => Ok(float_to_value(f(v.as_float()?))),
    }
}

fn lift_unary_int<F>(val: Value, f: F) -> Result<Value, String>
where
    F: Fn(i64) -> i64 + Copy,
{
    match val {
        Value::ArpList(items) => {
            let mapped: Result<Vec<_>, _> =
                items.iter().map(|x| lift_unary_int(x.clone(), f)).collect();
            Ok(Value::ArpList(Arc::from(mapped?)))
        }
        Value::CycleList(items) => {
            let mapped: Result<Vec<_>, _> =
                items.iter().map(|x| lift_unary_int(x.clone(), f)).collect();
            Ok(Value::CycleList(Arc::from(mapped?)))
        }
        v => Ok(Value::Int(f(v.as_int()?), None)),
    }
}

fn lift_binary<F>(a: Value, b: Value, f: F) -> Result<Value, String>
where
    F: Fn(f64, f64) -> f64 + Copy,
{
    match (a, b) {
        (Value::ArpList(items), b) => {
            let mapped: Result<Vec<_>, _> =
                items.iter().map(|x| lift_binary(x.clone(), b.clone(), f)).collect();
            Ok(Value::ArpList(Arc::from(mapped?)))
        }
        (a, Value::ArpList(items)) => {
            let mapped: Result<Vec<_>, _> =
                items.iter().map(|x| lift_binary(a.clone(), x.clone(), f)).collect();
            Ok(Value::ArpList(Arc::from(mapped?)))
        }
        (Value::CycleList(items), b) => {
            let mapped: Result<Vec<_>, _> =
                items.iter().map(|x| lift_binary(x.clone(), b.clone(), f)).collect();
            Ok(Value::CycleList(Arc::from(mapped?)))
        }
        (a, Value::CycleList(items)) => {
            let mapped: Result<Vec<_>, _> =
                items.iter().map(|x| lift_binary(a.clone(), x.clone(), f)).collect();
            Ok(Value::CycleList(Arc::from(mapped?)))
        }
        (a, b) => Ok(float_to_value(f(a.as_float()?, b.as_float()?))),
    }
}

fn binary_op<F>(stack: &mut Vec<Value>, f: F) -> Result<(), String>
where
    F: Fn(f64, f64) -> f64 + Copy,
{
    let b = pop(stack)?;
    let a = pop(stack)?;
    stack.push(lift_binary(a, b, f)?);
    Ok(())
}

fn cmp_op<F>(stack: &mut Vec<Value>, f: F) -> Result<(), String>
where
    F: Fn(f64, f64) -> bool,
{
    let b = pop(stack)?;
    let a = pop(stack)?;
    let result = if f(a.as_float()?, b.as_float()?) {
        1
    } else {
        0
    };
    stack.push(Value::Int(result, None));
    Ok(())
}

fn resolve_value(val: &Value, arp_idx: usize, poly_idx: usize) -> Cow<'_, Value> {
    match val {
        Value::ArpList(items) if !items.is_empty() => {
            Cow::Owned(items[arp_idx % items.len()].clone())
        }
        Value::CycleList(items) if !items.is_empty() => {
            Cow::Owned(items[poly_idx % items.len()].clone())
        }
        other => Cow::Borrowed(other),
    }
}
