use crate::ast::*;
use crate::error::CompileError;
use crate::opcodes::OpCode;
use crate::token::Span;
use crate::ui_decl::{UiDeclaration, UiElement};
use crate::validator::VType;
use crate::vm::ScriptVM;

/// Where a named variable lives in the VM
#[derive(Debug, Clone, Copy)]
enum VarLoc {
    Local(u16, VType),
    Param(u16),
    StateScalar(u16, VType),
    InputBuffer(u8),
    OutputBuffer(u8),
    StateArray(u16, VType), // VType is the element type
    SampleSlot(u8),
    BuiltinSampleRate,
    BuiltinBufferSize,
}

struct Compiler {
    code: Vec<u8>,
    constants_f32: Vec<f32>,
    constants_i32: Vec<i32>,
    vars: Vec<(String, VarLoc)>,
    next_local: u16,
    scope_stack: Vec<u16>, // local count at scope entry
    draw_context: bool,     // true when compiling a draw {} block
}

impl Compiler {
    fn new() -> Self {
        Self {
            code: Vec::new(),
            constants_f32: Vec::new(),
            constants_i32: Vec::new(),
            vars: Vec::new(),
            next_local: 0,
            scope_stack: Vec::new(),
            draw_context: false,
        }
    }

    fn emit(&mut self, op: OpCode) {
        self.code.push(op as u8);
    }

    fn emit_u8(&mut self, v: u8) {
        self.code.push(v);
    }

    fn emit_u16(&mut self, v: u16) {
        self.code.extend_from_slice(&v.to_le_bytes());
    }

    fn emit_u32(&mut self, v: u32) {
        self.code.extend_from_slice(&v.to_le_bytes());
    }

    /// Returns index into constants_f32
    fn add_const_f32(&mut self, v: f32) -> u16 {
        // Reuse existing constant if possible
        for (i, &c) in self.constants_f32.iter().enumerate() {
            if c.to_bits() == v.to_bits() {
                return i as u16;
            }
        }
        let idx = self.constants_f32.len() as u16;
        self.constants_f32.push(v);
        idx
    }

    /// Returns index into constants_i32
    fn add_const_i32(&mut self, v: i32) -> u16 {
        for (i, &c) in self.constants_i32.iter().enumerate() {
            if c == v {
                return i as u16;
            }
        }
        let idx = self.constants_i32.len() as u16;
        self.constants_i32.push(v);
        idx
    }

    fn push_scope(&mut self) {
        self.scope_stack.push(self.next_local);
    }

    fn pop_scope(&mut self) {
        let prev = self.scope_stack.pop().unwrap();
        // Remove variables defined in this scope
        self.vars.retain(|(_, loc)| {
            if let VarLoc::Local(idx, _) = loc {
                *idx < prev
            } else {
                true
            }
        });
        self.next_local = prev;
    }

    fn alloc_local(&mut self, name: String, ty: VType) -> u16 {
        let idx = self.next_local;
        self.next_local += 1;
        self.vars.push((name, VarLoc::Local(idx, ty)));
        idx
    }

    fn lookup(&self, name: &str) -> Option<VarLoc> {
        self.vars.iter().rev().find(|(n, _)| n == name).map(|(_, l)| *l)
    }

    /// Emit a placeholder u32 and return the offset where it was written
    fn emit_jump_placeholder(&mut self, op: OpCode) -> usize {
        self.emit(op);
        let pos = self.code.len();
        self.emit_u32(0);
        pos
    }

    /// Patch a previously emitted u32 placeholder
    fn patch_jump(&mut self, placeholder_pos: usize) {
        let target = self.code.len() as u32;
        let bytes = target.to_le_bytes();
        self.code[placeholder_pos] = bytes[0];
        self.code[placeholder_pos + 1] = bytes[1];
        self.code[placeholder_pos + 2] = bytes[2];
        self.code[placeholder_pos + 3] = bytes[3];
    }

    fn compile_script(&mut self, script: &Script) -> Result<(), CompileError> {
        // Register built-in variables
        self.vars.push(("sample_rate".into(), VarLoc::BuiltinSampleRate));
        self.vars.push(("buffer_size".into(), VarLoc::BuiltinBufferSize));

        // Register inputs
        for (i, input) in script.inputs.iter().enumerate() {
            match input.signal {
                SignalKind::Audio | SignalKind::Cv => {
                    self.vars.push((input.name.clone(), VarLoc::InputBuffer(i as u8)));
                }
                SignalKind::Midi => {}
            }
        }

        // Register outputs
        for (i, output) in script.outputs.iter().enumerate() {
            match output.signal {
                SignalKind::Audio | SignalKind::Cv => {
                    self.vars.push((output.name.clone(), VarLoc::OutputBuffer(i as u8)));
                }
                SignalKind::Midi => {}
            }
        }

        self.register_params_and_state(script, true);

        // Compile process block
        for stmt in &script.process {
            self.compile_stmt(stmt)?;
        }

        self.emit(OpCode::Halt);
        Ok(())
    }

    /// Compile the draw block into separate bytecode (for the DrawVM)
    fn compile_draw(&mut self, script: &Script) -> Result<(), CompileError> {
        self.draw_context = true;
        self.register_params_and_state(script, false);

        // Compile draw block
        if let Some(draw) = &script.draw {
            for stmt in draw {
                self.compile_stmt(stmt)?;
            }
        }

        self.emit(OpCode::Halt);
        Ok(())
    }

    /// Register params and state variables. If `include_samples` is true, also registers sample slots.
    fn register_params_and_state(&mut self, script: &Script, include_samples: bool) {
        for (i, param) in script.params.iter().enumerate() {
            self.vars.push((param.name.clone(), VarLoc::Param(i as u16)));
        }

        let mut scalar_idx: u16 = 0;
        let mut array_idx: u16 = 0;
        let mut sample_idx: u8 = 0;
        for state in &script.state {
            match &state.ty {
                StateType::F32 => {
                    self.vars.push((state.name.clone(), VarLoc::StateScalar(scalar_idx, VType::F32)));
                    scalar_idx += 1;
                }
                StateType::Int => {
                    self.vars.push((state.name.clone(), VarLoc::StateScalar(scalar_idx, VType::Int)));
                    scalar_idx += 1;
                }
                StateType::Bool => {
                    self.vars.push((state.name.clone(), VarLoc::StateScalar(scalar_idx, VType::Bool)));
                    scalar_idx += 1;
                }
                StateType::ArrayF32(_) => {
                    self.vars.push((state.name.clone(), VarLoc::StateArray(array_idx, VType::F32)));
                    array_idx += 1;
                }
                StateType::ArrayInt(_) => {
                    self.vars.push((state.name.clone(), VarLoc::StateArray(array_idx, VType::Int)));
                    array_idx += 1;
                }
                StateType::Sample if include_samples => {
                    self.vars.push((state.name.clone(), VarLoc::SampleSlot(sample_idx)));
                    sample_idx += 1;
                }
                StateType::Sample => {}
            }
        }
    }

    fn compile_stmt(&mut self, stmt: &Stmt) -> Result<(), CompileError> {
        match stmt {
            Stmt::Let { name, init, .. } => {
                let ty = self.infer_type(init)?;
                self.compile_expr(init)?;
                let _idx = self.alloc_local(name.clone(), ty);
                self.emit(OpCode::StoreLocal);
                self.emit_u16(_idx);
            }
            Stmt::Assign { target, value, span } => {
                match target {
                    LValue::Ident(name, _) => {
                        let loc = self.lookup(name).ok_or_else(|| {
                            CompileError::new(format!("Undefined variable: {}", name), *span)
                        })?;
                        self.compile_expr(value)?;
                        match loc {
                            VarLoc::Local(idx, _) => {
                                self.emit(OpCode::StoreLocal);
                                self.emit_u16(idx);
                            }
                            VarLoc::StateScalar(idx, _) => {
                                self.emit(OpCode::StoreState);
                                self.emit_u16(idx);
                            }
                            VarLoc::Param(idx) if self.draw_context => {
                                self.emit(OpCode::StoreParam);
                                self.emit_u16(idx);
                            }
                            _ => {
                                return Err(CompileError::new(
                                    format!("Cannot assign to {}", name), *span,
                                ));
                            }
                        }
                    }
                    LValue::Index(name, idx_expr, s) => {
                        let loc = self.lookup(name).ok_or_else(|| {
                            CompileError::new(format!("Undefined variable: {}", name), *s)
                        })?;
                        match loc {
                            VarLoc::OutputBuffer(port) => {
                                // StoreOutput: pops value then index
                                self.compile_expr(idx_expr)?;
                                self.compile_expr(value)?;
                                self.emit(OpCode::StoreOutput);
                                self.emit_u8(port);
                            }
                            VarLoc::StateArray(arr_id, _) => {
                                // StoreStateArray: pops value then index
                                self.compile_expr(idx_expr)?;
                                self.compile_expr(value)?;
                                self.emit(OpCode::StoreStateArray);
                                self.emit_u16(arr_id);
                            }
                            _ => {
                                return Err(CompileError::new(
                                    format!("Cannot index-assign to {}", name), *s,
                                ));
                            }
                        }
                    }
                }
            }
            Stmt::If { cond, then_block, else_block, .. } => {
                self.compile_expr(cond)?;
                if let Some(else_b) = else_block {
                    // JumpIfFalse -> else
                    let else_jump = self.emit_jump_placeholder(OpCode::JumpIfFalse);
                    self.push_scope();
                    self.compile_block(then_block)?;
                    self.pop_scope();
                    // Jump -> end (skip else)
                    let end_jump = self.emit_jump_placeholder(OpCode::Jump);
                    self.patch_jump(else_jump);
                    self.push_scope();
                    self.compile_block(else_b)?;
                    self.pop_scope();
                    self.patch_jump(end_jump);
                } else {
                    let end_jump = self.emit_jump_placeholder(OpCode::JumpIfFalse);
                    self.push_scope();
                    self.compile_block(then_block)?;
                    self.pop_scope();
                    self.patch_jump(end_jump);
                }
            }
            Stmt::For { var, end, body, span: _ } => {
                // Allocate loop variable as local
                self.push_scope();
                let loop_var = self.alloc_local(var.clone(), VType::Int);

                // Initialize loop var to 0
                let zero_idx = self.add_const_i32(0);
                self.emit(OpCode::PushI32);
                self.emit_u16(zero_idx);
                self.emit(OpCode::StoreLocal);
                self.emit_u16(loop_var);

                // Loop start: check condition (i < end)
                let loop_start = self.code.len();
                self.emit(OpCode::LoadLocal);
                self.emit_u16(loop_var);
                self.compile_expr(end)?;
                self.emit(OpCode::LtI);

                let exit_jump = self.emit_jump_placeholder(OpCode::JumpIfFalse);

                // Body
                self.compile_block(body)?;

                // Increment loop var
                self.emit(OpCode::LoadLocal);
                self.emit_u16(loop_var);
                let one_idx = self.add_const_i32(1);
                self.emit(OpCode::PushI32);
                self.emit_u16(one_idx);
                self.emit(OpCode::AddI);
                self.emit(OpCode::StoreLocal);
                self.emit_u16(loop_var);

                // Jump back to loop start
                self.emit(OpCode::Jump);
                self.emit_u32(loop_start as u32);

                // Patch exit
                self.patch_jump(exit_jump);
                self.pop_scope();
            }
            Stmt::ExprStmt(expr) => {
                let is_void = self.is_void_call(expr);
                self.compile_expr(expr)?;
                if !is_void {
                    self.emit(OpCode::Pop);
                }
            }
        }
        Ok(())
    }

    fn compile_block(&mut self, block: &[Stmt]) -> Result<(), CompileError> {
        for stmt in block {
            self.compile_stmt(stmt)?;
        }
        Ok(())
    }

    fn compile_expr(&mut self, expr: &Expr) -> Result<(), CompileError> {
        match expr {
            Expr::FloatLit(v, _) => {
                let idx = self.add_const_f32(*v);
                self.emit(OpCode::PushF32);
                self.emit_u16(idx);
            }
            Expr::IntLit(v, _) => {
                let idx = self.add_const_i32(*v);
                self.emit(OpCode::PushI32);
                self.emit_u16(idx);
            }
            Expr::BoolLit(v, _) => {
                self.emit(OpCode::PushBool);
                self.emit_u8(if *v { 1 } else { 0 });
            }
            Expr::Ident(name, span) => {
                let loc = self.lookup(name).ok_or_else(|| {
                    CompileError::new(format!("Undefined variable: {}", name), *span)
                })?;
                match loc {
                    VarLoc::Local(idx, _) => {
                        self.emit(OpCode::LoadLocal);
                        self.emit_u16(idx);
                    }
                    VarLoc::Param(idx) => {
                        self.emit(OpCode::LoadParam);
                        self.emit_u16(idx);
                    }
                    VarLoc::StateScalar(idx, _) => {
                        self.emit(OpCode::LoadState);
                        self.emit_u16(idx);
                    }
                    VarLoc::BuiltinSampleRate => {
                        self.emit(OpCode::LoadSampleRate);
                    }
                    VarLoc::BuiltinBufferSize => {
                        self.emit(OpCode::LoadBufferSize);
                    }
                    // Arrays/buffers/samples used bare (for len(), etc.) — handled by call codegen
                    _ => {}
                }
            }
            Expr::BinOp(left, op, right, _span) => {
                let lt = self.infer_type(left)?;
                let rt = self.infer_type(right)?;
                self.compile_expr(left)?;
                self.compile_expr(right)?;

                match op {
                    BinOp::Add => {
                        if lt == VType::F32 || rt == VType::F32 {
                            self.emit(OpCode::AddF);
                        } else {
                            self.emit(OpCode::AddI);
                        }
                    }
                    BinOp::Sub => {
                        if lt == VType::F32 || rt == VType::F32 {
                            self.emit(OpCode::SubF);
                        } else {
                            self.emit(OpCode::SubI);
                        }
                    }
                    BinOp::Mul => {
                        if lt == VType::F32 || rt == VType::F32 {
                            self.emit(OpCode::MulF);
                        } else {
                            self.emit(OpCode::MulI);
                        }
                    }
                    BinOp::Div => {
                        if lt == VType::F32 || rt == VType::F32 {
                            self.emit(OpCode::DivF);
                        } else {
                            self.emit(OpCode::DivI);
                        }
                    }
                    BinOp::Mod => {
                        if lt == VType::F32 || rt == VType::F32 {
                            self.emit(OpCode::ModF);
                        } else {
                            self.emit(OpCode::ModI);
                        }
                    }
                    BinOp::Eq => {
                        if lt == VType::F32 || rt == VType::F32 {
                            self.emit(OpCode::EqF);
                        } else if lt == VType::Int || rt == VType::Int {
                            self.emit(OpCode::EqI);
                        } else {
                            // bool comparison: treat as int
                            self.emit(OpCode::EqI);
                        }
                    }
                    BinOp::Ne => {
                        if lt == VType::F32 || rt == VType::F32 {
                            self.emit(OpCode::NeF);
                        } else {
                            self.emit(OpCode::NeI);
                        }
                    }
                    BinOp::Lt => {
                        if lt == VType::F32 || rt == VType::F32 {
                            self.emit(OpCode::LtF);
                        } else {
                            self.emit(OpCode::LtI);
                        }
                    }
                    BinOp::Gt => {
                        if lt == VType::F32 || rt == VType::F32 {
                            self.emit(OpCode::GtF);
                        } else {
                            self.emit(OpCode::GtI);
                        }
                    }
                    BinOp::Le => {
                        if lt == VType::F32 || rt == VType::F32 {
                            self.emit(OpCode::LeF);
                        } else {
                            self.emit(OpCode::LeI);
                        }
                    }
                    BinOp::Ge => {
                        if lt == VType::F32 || rt == VType::F32 {
                            self.emit(OpCode::GeF);
                        } else {
                            self.emit(OpCode::GeI);
                        }
                    }
                    BinOp::And => self.emit(OpCode::And),
                    BinOp::Or => self.emit(OpCode::Or),
                }
            }
            Expr::UnaryOp(op, inner, _) => {
                let ty = self.infer_type(inner)?;
                self.compile_expr(inner)?;
                match op {
                    UnaryOp::Neg => {
                        if ty == VType::F32 {
                            self.emit(OpCode::NegF);
                        } else {
                            self.emit(OpCode::NegI);
                        }
                    }
                    UnaryOp::Not => self.emit(OpCode::Not),
                }
            }
            Expr::Cast(kind, inner, _) => {
                self.compile_expr(inner)?;
                match kind {
                    CastKind::ToInt => self.emit(OpCode::F32ToI32),
                    CastKind::ToFloat => self.emit(OpCode::I32ToF32),
                }
            }
            Expr::Index(base, idx, span) => {
                // base must be an Ident referencing an array/buffer
                if let Expr::Ident(name, _) = base.as_ref() {
                    let loc = self.lookup(name).ok_or_else(|| {
                        CompileError::new(format!("Undefined variable: {}", name), *span)
                    })?;
                    match loc {
                        VarLoc::InputBuffer(port) => {
                            self.compile_expr(idx)?;
                            self.emit(OpCode::LoadInput);
                            self.emit_u8(port);
                        }
                        VarLoc::OutputBuffer(port) => {
                            self.compile_expr(idx)?;
                            self.emit(OpCode::LoadInput);
                            // Reading from output buffer — use same port but from outputs
                            // Actually outputs aren't readable in the VM. This would be
                            // an error in practice, but the validator should catch it.
                            // For now, treat as input read (will read zeros).
                            self.emit_u8(port);
                        }
                        VarLoc::StateArray(arr_id, _) => {
                            self.compile_expr(idx)?;
                            self.emit(OpCode::LoadStateArray);
                            self.emit_u16(arr_id);
                        }
                        _ => {
                            return Err(CompileError::new(
                                format!("Cannot index variable: {}", name), *span,
                            ));
                        }
                    }
                } else {
                    return Err(CompileError::new("Index base must be an identifier", *span));
                }
            }
            Expr::Call(name, args, span) => {
                self.compile_call(name, args, *span)?;
            }
        }
        Ok(())
    }

    /// Returns true if the expression is a call to a void function (no return value).
    fn is_void_call(&self, expr: &Expr) -> bool {
        if let Expr::Call(name, _, _) = expr {
            matches!(name.as_str(),
                "fill_circle" | "stroke_circle" | "stroke_arc" |
                "line" | "fill_rect" | "stroke_rect"
            )
        } else {
            false
        }
    }

    fn compile_call(&mut self, name: &str, args: &[Expr], span: Span) -> Result<(), CompileError> {
        match name {
            // 1-arg math → push arg, emit opcode
            "sin" => { self.compile_expr(&args[0])?; self.emit(OpCode::Sin); }
            "cos" => { self.compile_expr(&args[0])?; self.emit(OpCode::Cos); }
            "tan" => { self.compile_expr(&args[0])?; self.emit(OpCode::Tan); }
            "asin" => { self.compile_expr(&args[0])?; self.emit(OpCode::Asin); }
            "acos" => { self.compile_expr(&args[0])?; self.emit(OpCode::Acos); }
            "atan" => { self.compile_expr(&args[0])?; self.emit(OpCode::Atan); }
            "exp" => { self.compile_expr(&args[0])?; self.emit(OpCode::Exp); }
            "log" => { self.compile_expr(&args[0])?; self.emit(OpCode::Log); }
            "log2" => { self.compile_expr(&args[0])?; self.emit(OpCode::Log2); }
            "sqrt" => { self.compile_expr(&args[0])?; self.emit(OpCode::Sqrt); }
            "floor" => { self.compile_expr(&args[0])?; self.emit(OpCode::Floor); }
            "ceil" => { self.compile_expr(&args[0])?; self.emit(OpCode::Ceil); }
            "round" => { self.compile_expr(&args[0])?; self.emit(OpCode::Round); }
            "trunc" => { self.compile_expr(&args[0])?; self.emit(OpCode::Trunc); }
            "fract" => { self.compile_expr(&args[0])?; self.emit(OpCode::Fract); }
            "abs" => { self.compile_expr(&args[0])?; self.emit(OpCode::Abs); }
            "sign" => { self.compile_expr(&args[0])?; self.emit(OpCode::Sign); }

            // 2-arg math
            "atan2" => {
                self.compile_expr(&args[0])?;
                self.compile_expr(&args[1])?;
                self.emit(OpCode::Atan2);
            }
            "pow" => {
                self.compile_expr(&args[0])?;
                self.compile_expr(&args[1])?;
                self.emit(OpCode::Pow);
            }
            "min" => {
                self.compile_expr(&args[0])?;
                self.compile_expr(&args[1])?;
                self.emit(OpCode::Min);
            }
            "max" => {
                self.compile_expr(&args[0])?;
                self.compile_expr(&args[1])?;
                self.emit(OpCode::Max);
            }

            // 3-arg math
            "clamp" => {
                self.compile_expr(&args[0])?;
                self.compile_expr(&args[1])?;
                self.compile_expr(&args[2])?;
                self.emit(OpCode::Clamp);
            }
            "mix" => {
                self.compile_expr(&args[0])?;
                self.compile_expr(&args[1])?;
                self.compile_expr(&args[2])?;
                self.emit(OpCode::Mix);
            }
            "smoothstep" => {
                self.compile_expr(&args[0])?;
                self.compile_expr(&args[1])?;
                self.compile_expr(&args[2])?;
                self.emit(OpCode::Smoothstep);
            }

            // cv_or(value, default) — if value is NaN, use default
            "cv_or" => {
                // Compile: push value, check IsNan, if true use default else keep value
                // Strategy: push value, dup-like via local, IsNan, branch
                // Simpler: push value, push value again, IsNan, JumpIfFalse skip, Pop, push default, skip:
                // But we don't have Dup. Use a temp local instead.
                let temp = self.next_local;
                self.next_local += 1;
                self.compile_expr(&args[0])?;
                // Store to temp
                self.emit(OpCode::StoreLocal);
                self.emit_u16(temp);
                // Load and check NaN
                self.emit(OpCode::LoadLocal);
                self.emit_u16(temp);
                self.emit(OpCode::IsNan);
                let skip_default = self.emit_jump_placeholder(OpCode::JumpIfFalse);
                // NaN path: use default
                self.compile_expr(&args[1])?;
                let skip_end = self.emit_jump_placeholder(OpCode::Jump);
                // Not NaN path: use original value
                self.patch_jump(skip_default);
                self.emit(OpCode::LoadLocal);
                self.emit_u16(temp);
                self.patch_jump(skip_end);
                self.next_local -= 1; // release temp
            }

            // len(array) -> int
            "len" => {
                // Arg must be an ident referencing a state array or input/output buffer
                if let Expr::Ident(arr_name, s) = &args[0] {
                    let loc = self.lookup(arr_name).ok_or_else(|| {
                        CompileError::new(format!("Undefined variable: {}", arr_name), *s)
                    })?;
                    match loc {
                        VarLoc::StateArray(arr_id, _) => {
                            self.emit(OpCode::ArrayLen);
                            self.emit_u16(arr_id);
                        }
                        VarLoc::InputBuffer(_) | VarLoc::OutputBuffer(_) => {
                            // Buffer length is buffer_size (for CV) or buffer_size*2 (for audio)
                            // We emit LoadBufferSize — scripts use buffer_size for iteration
                            self.emit(OpCode::LoadBufferSize);
                        }
                        _ => {
                            return Err(CompileError::new("len() argument must be an array", span));
                        }
                    }
                } else {
                    return Err(CompileError::new("len() argument must be an identifier", span));
                }
            }

            // sample_len(sample) -> int
            "sample_len" => {
                if let Expr::Ident(sname, s) = &args[0] {
                    let loc = self.lookup(sname).ok_or_else(|| {
                        CompileError::new(format!("Undefined: {}", sname), *s)
                    })?;
                    if let VarLoc::SampleSlot(slot) = loc {
                        self.emit(OpCode::SampleLen);
                        self.emit_u8(slot);
                    } else {
                        return Err(CompileError::new("sample_len() requires a sample", span));
                    }
                } else {
                    return Err(CompileError::new("sample_len() requires an identifier", span));
                }
            }

            // sample_read(sample, index) -> f32
            "sample_read" => {
                if let Expr::Ident(sname, s) = &args[0] {
                    let loc = self.lookup(sname).ok_or_else(|| {
                        CompileError::new(format!("Undefined: {}", sname), *s)
                    })?;
                    if let VarLoc::SampleSlot(slot) = loc {
                        self.compile_expr(&args[1])?;
                        self.emit(OpCode::SampleRead);
                        self.emit_u8(slot);
                    } else {
                        return Err(CompileError::new("sample_read() requires a sample", span));
                    }
                } else {
                    return Err(CompileError::new("sample_read() requires an identifier", span));
                }
            }

            // sample_rate_of(sample) -> int
            "sample_rate_of" => {
                if let Expr::Ident(sname, s) = &args[0] {
                    let loc = self.lookup(sname).ok_or_else(|| {
                        CompileError::new(format!("Undefined: {}", sname), *s)
                    })?;
                    if let VarLoc::SampleSlot(slot) = loc {
                        self.emit(OpCode::SampleRateOf);
                        self.emit_u8(slot);
                    } else {
                        return Err(CompileError::new("sample_rate_of() requires a sample", span));
                    }
                } else {
                    return Err(CompileError::new("sample_rate_of() requires an identifier", span));
                }
            }

            // Draw builtins (only valid in draw context)
            "fill_circle" | "stroke_circle" | "stroke_arc" | "line" |
            "fill_rect" | "stroke_rect" |
            "mouse_x" | "mouse_y" | "mouse_down" if self.draw_context => {
                match name {
                    "fill_circle" => {
                        // fill_circle(cx, cy, r, color)
                        for arg in args { self.compile_expr(arg)?; }
                        self.emit(OpCode::DrawFillCircle);
                    }
                    "stroke_circle" => {
                        // stroke_circle(cx, cy, r, color, width)
                        for arg in args { self.compile_expr(arg)?; }
                        self.emit(OpCode::DrawStrokeCircle);
                    }
                    "stroke_arc" => {
                        // stroke_arc(cx, cy, r, start_deg, end_deg, color, width)
                        for arg in args { self.compile_expr(arg)?; }
                        self.emit(OpCode::DrawStrokeArc);
                    }
                    "line" => {
                        // line(x1, y1, x2, y2, color, width)
                        for arg in args { self.compile_expr(arg)?; }
                        self.emit(OpCode::DrawLine);
                    }
                    "fill_rect" => {
                        // fill_rect(x, y, w, h, color)
                        for arg in args { self.compile_expr(arg)?; }
                        self.emit(OpCode::DrawFillRect);
                    }
                    "stroke_rect" => {
                        // stroke_rect(x, y, w, h, color, width)
                        for arg in args { self.compile_expr(arg)?; }
                        self.emit(OpCode::DrawStrokeRect);
                    }
                    "mouse_x" => { self.emit(OpCode::MouseX); }
                    "mouse_y" => { self.emit(OpCode::MouseY); }
                    "mouse_down" => { self.emit(OpCode::MouseDown); }
                    _ => unreachable!(),
                }
            }

            _ => {
                return Err(CompileError::new(format!("Unknown function: {}", name), span));
            }
        }
        Ok(())
    }

    /// Infer the type of an expression (mirrors validator logic, needed for selecting typed opcodes)
    fn infer_type(&self, expr: &Expr) -> Result<VType, CompileError> {
        match expr {
            Expr::FloatLit(_, _) => Ok(VType::F32),
            Expr::IntLit(_, _) => Ok(VType::Int),
            Expr::BoolLit(_, _) => Ok(VType::Bool),
            Expr::Ident(name, span) => {
                let loc = self.lookup(name).ok_or_else(|| {
                    CompileError::new(format!("Undefined variable: {}", name), *span)
                })?;
                match loc {
                    VarLoc::Local(_, ty) => Ok(ty),
                    VarLoc::Param(_) => Ok(VType::F32),
                    VarLoc::StateScalar(_, ty) => Ok(ty),
                    VarLoc::InputBuffer(_) => Ok(VType::ArrayF32),
                    VarLoc::OutputBuffer(_) => Ok(VType::ArrayF32),
                    VarLoc::StateArray(_, elem_ty) => {
                        if elem_ty == VType::Int { Ok(VType::ArrayInt) } else { Ok(VType::ArrayF32) }
                    }
                    VarLoc::SampleSlot(_) => Ok(VType::Sample),
                    VarLoc::BuiltinSampleRate => Ok(VType::Int),
                    VarLoc::BuiltinBufferSize => Ok(VType::Int),
                }
            }
            Expr::BinOp(left, op, right, _) => {
                let lt = self.infer_type(left)?;
                let rt = self.infer_type(right)?;
                match op {
                    BinOp::And | BinOp::Or | BinOp::Eq | BinOp::Ne |
                    BinOp::Lt | BinOp::Gt | BinOp::Le | BinOp::Ge => Ok(VType::Bool),
                    _ => {
                        if lt == VType::F32 || rt == VType::F32 {
                            Ok(VType::F32)
                        } else {
                            Ok(VType::Int)
                        }
                    }
                }
            }
            Expr::UnaryOp(op, inner, _) => {
                match op {
                    UnaryOp::Neg => self.infer_type(inner),
                    UnaryOp::Not => Ok(VType::Bool),
                }
            }
            Expr::Cast(kind, _, _) => match kind {
                CastKind::ToInt => Ok(VType::Int),
                CastKind::ToFloat => Ok(VType::F32),
            },
            Expr::Index(base, _, _) => {
                let base_ty = self.infer_type(base)?;
                match base_ty {
                    VType::ArrayF32 => Ok(VType::F32),
                    VType::ArrayInt => Ok(VType::Int),
                    _ => Ok(VType::F32), // fallback
                }
            }
            Expr::Call(name, _, _) => {
                match name.as_str() {
                    "len" | "sample_len" | "sample_rate_of" => Ok(VType::Int),
                    "isnan" => Ok(VType::Bool),
                    _ => Ok(VType::F32), // all math functions return f32
                }
            }
        }
    }
}

/// Compile a validated AST into bytecode VM and UI declaration
pub fn compile(script: &Script) -> Result<(ScriptVM, UiDeclaration, Option<crate::vm::DrawVM>), CompileError> {
    let mut compiler = Compiler::new();
    compiler.compile_script(script)?;

    // Collect state layout info
    let mut num_state_scalars = 0usize;
    let mut state_array_sizes = Vec::new();
    let mut num_sample_slots = 0usize;

    for state in &script.state {
        match &state.ty {
            StateType::F32 | StateType::Int | StateType::Bool => {
                num_state_scalars += 1;
            }
            StateType::ArrayF32(sz) => state_array_sizes.push(*sz),
            StateType::ArrayInt(sz) => state_array_sizes.push(*sz),
            StateType::Sample => num_sample_slots += 1,
        }
    }

    let param_defaults: Vec<f32> = script.params.iter().map(|p| p.default).collect();

    let vm = ScriptVM::new(
        compiler.code,
        compiler.constants_f32,
        compiler.constants_i32,
        script.params.len(),
        &param_defaults,
        num_state_scalars,
        &state_array_sizes,
        num_sample_slots,
    );

    // Build UI declaration
    let ui_decl = if let Some(elements) = &script.ui {
        UiDeclaration { elements: elements.clone() }
    } else {
        // Auto-generate: sample pickers first, then all params
        let mut elements = Vec::new();
        for state in &script.state {
            if state.ty == StateType::Sample {
                elements.push(UiElement::Sample(state.name.clone()));
            }
        }
        for param in &script.params {
            elements.push(UiElement::Param(param.name.clone()));
        }
        UiDeclaration { elements }
    };

    // Compile draw block if present
    let draw_vm = if script.draw.is_some() {
        let mut draw_compiler = Compiler::new();
        draw_compiler.compile_draw(script)?;
        Some(crate::vm::DrawVM::new(
            draw_compiler.code,
            draw_compiler.constants_f32,
            draw_compiler.constants_i32,
            script.params.len(),
            &param_defaults,
            num_state_scalars,
            &state_array_sizes,
        ))
    } else {
        None
    };

    Ok((vm, ui_decl, draw_vm))
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::lexer::Lexer;
    use crate::parser::Parser;
    use crate::validator;

    fn compile_source(src: &str) -> Result<(ScriptVM, UiDeclaration), CompileError> {
        let mut lexer = Lexer::new(src);
        let tokens = lexer.tokenize()?;
        let mut parser = Parser::new(&tokens);
        let script = parser.parse()?;
        let validated = validator::validate(&script)?;
        let (vm, ui, _draw_vm) = compile(validated)?;
        Ok((vm, ui))
    }

    #[test]
    fn test_passthrough() {
        let src = r#"
            name "Pass"
            category effect
            inputs { audio_in: audio }
            outputs { audio_out: audio }
            process {
                for i in 0..buffer_size {
                    audio_out[i] = audio_in[i];
                }
            }
        "#;
        let (mut vm, _) = compile_source(src).unwrap();
        let input = vec![1.0f32, 2.0, 3.0, 4.0];
        let mut output = vec![0.0f32; 4];
        let inputs: Vec<&[f32]> = vec![&input];
        let mut out_slice: Vec<&mut [f32]> = vec![&mut output];
        vm.execute(&inputs, &mut out_slice, 44100, 4).unwrap();
        assert_eq!(output, vec![1.0, 2.0, 3.0, 4.0]);
    }

    #[test]
    fn test_gain() {
        let src = r#"
            name "Gain"
            category effect
            inputs { audio_in: audio }
            outputs { audio_out: audio }
            params { gain: 0.5 [0.0, 1.0] "" }
            process {
                for i in 0..buffer_size {
                    audio_out[i] = audio_in[i] * gain;
                }
            }
        "#;
        let (mut vm, _) = compile_source(src).unwrap();
        let input = vec![1.0f32, 2.0, 3.0, 4.0];
        let mut output = vec![0.0f32; 4];
        let inputs: Vec<&[f32]> = vec![&input];
        let mut out_slice: Vec<&mut [f32]> = vec![&mut output];
        vm.execute(&inputs, &mut out_slice, 44100, 4).unwrap();
        assert_eq!(output, vec![0.5, 1.0, 1.5, 2.0]);
    }

    #[test]
    fn test_state_array() {
        let src = r#"
            name "Delay"
            category effect
            inputs { audio_in: audio }
            outputs { audio_out: audio }
            state { buf: [8]f32 }
            process {
                for i in 0..buffer_size {
                    audio_out[i] = buf[i];
                    buf[i] = audio_in[i];
                }
            }
        "#;
        let (mut vm, _) = compile_source(src).unwrap();

        // First call: output should be zeros (state initialized to 0), state gets input
        let input = vec![10.0f32, 20.0, 30.0, 40.0];
        let mut output = vec![0.0f32; 4];
        {
            let inputs: Vec<&[f32]> = vec![&input];
            let mut out_slice: Vec<&mut [f32]> = vec![&mut output];
            vm.execute(&inputs, &mut out_slice, 44100, 4).unwrap();
        }
        assert_eq!(output, vec![0.0, 0.0, 0.0, 0.0]);

        // Second call: output should be previous input
        let input2 = vec![50.0f32, 60.0, 70.0, 80.0];
        let mut output2 = vec![0.0f32; 4];
        {
            let inputs: Vec<&[f32]> = vec![&input2];
            let mut out_slice: Vec<&mut [f32]> = vec![&mut output2];
            vm.execute(&inputs, &mut out_slice, 44100, 4).unwrap();
        }
        assert_eq!(output2, vec![10.0, 20.0, 30.0, 40.0]);
    }

    #[test]
    fn test_if_else() {
        let src = r#"
            name "Gate"
            category effect
            inputs { audio_in: audio }
            outputs { audio_out: audio }
            params { threshold: 0.5 [0.0, 1.0] "" }
            process {
                for i in 0..buffer_size {
                    if audio_in[i] >= threshold {
                        audio_out[i] = audio_in[i];
                    } else {
                        audio_out[i] = 0.0;
                    }
                }
            }
        "#;
        let (mut vm, _) = compile_source(src).unwrap();
        let input = vec![0.2f32, 0.8, 0.1, 0.9];
        let mut output = vec![0.0f32; 4];
        let inputs: Vec<&[f32]> = vec![&input];
        let mut out_slice: Vec<&mut [f32]> = vec![&mut output];
        vm.execute(&inputs, &mut out_slice, 44100, 4).unwrap();
        assert_eq!(output, vec![0.0, 0.8, 0.0, 0.9]);
    }

    #[test]
    fn test_auto_ui() {
        let src = r#"
            name "Test"
            category utility
            params { gain: 1.0 [0.0, 2.0] "dB" }
            state { clip: sample }
            outputs { out: audio }
            process {}
        "#;
        let (_, ui) = compile_source(src).unwrap();
        // Auto-generated: sample first, then params
        assert_eq!(ui.elements.len(), 2);
        assert!(matches!(&ui.elements[0], UiElement::Sample(n) if n == "clip"));
        assert!(matches!(&ui.elements[1], UiElement::Param(n) if n == "gain"));
    }

    #[test]
    fn test_draw_block() {
        let src = r#"
            name "Knob"
            category utility
            params { volume: 0.75 [0.0, 1.0] "" }
            outputs { out: audio }
            ui { canvas [80, 80] }
            draw {
                let cx = 40.0;
                let cy = 40.0;
                fill_circle(cx, cy, 35.0, 0x333333FF);
                let angle = volume * 270.0 - 135.0;
                stroke_arc(cx, cy, 30.0, -135.0, angle, 0x4488FFFF, 3.0);
            }
            process {
                for i in 0..buffer_size {
                    out[i] = 0.0;
                }
            }
        "#;
        let mut lexer = Lexer::new(src);
        let tokens = lexer.tokenize().unwrap();
        let mut parser = Parser::new(&tokens);
        let script = parser.parse().unwrap();
        let validated = validator::validate(&script).unwrap();
        let (_vm, _ui, draw_vm) = compile(validated).unwrap();

        // Draw VM should exist
        let mut dvm = draw_vm.expect("draw_vm should be Some");
        assert!(dvm.has_bytecode());

        // Execute should succeed without stack errors
        dvm.execute().unwrap();

        // Should have produced draw commands
        assert_eq!(dvm.draw_commands.len(), 2); // fill_circle + stroke_arc

    }
}
