use crate::ir::{
    BinaryOp, Builtin, Const, DType, DTypeKind, FloatEncoding, Node, NodeId, TypedDag, UnaryOp,
    promote,
};

use half::{bf16, f16};

#[derive(Debug, Clone, PartialEq)]
pub enum Value {
    Bool(bool),

    /// Unsigned integer with explicit bit-width.
    UInt {
        bits: u16,
        value: u64,
    },

    /// Signed integer with explicit bit-width.
    SInt {
        bits: u16,
        value: i64,
    },

    // Floats
    F16(f16),
    BF16(bf16),
    F32(f32),
    F64(f64),

    // Complex
    C64 {
        re: f32,
        im: f32,
    },
    C128 {
        re: f64,
        im: f64,
    },
}

#[derive(Debug, thiserror::Error)]
pub enum InterpError {
    #[error("invalid input count: expected {expected}, got {got}")]
    InputCount { expected: usize, got: usize },

    #[error("type mismatch at node {node_id}: expected {expected}, got {actual}")]
    TypeMismatch {
        node_id: usize,
        expected: DType,
        actual: DType,
    },

    #[error("invalid node reference {node_id}")]
    InvalidNodeRef { node_id: usize },

    #[error("invalid condition type at node {node_id}")]
    InvalidCondition { node_id: usize },

    #[error("division by zero at node {node_id}")]
    DivisionByZero { node_id: usize },
}

impl Value {
    pub fn dtype(&self) -> DType {
        match self {
            Value::Bool(_) => DType::BOOL,
            Value::UInt { bits, .. } => DType {
                kind: DTypeKind::UInt,
                bits: *bits,
            },
            Value::SInt { bits, .. } => DType {
                kind: DTypeKind::SInt,
                bits: *bits,
            },

            Value::F16(_) => DType::F16,
            Value::BF16(_) => DType::BF16,
            Value::F32(_) => DType::F32,
            Value::F64(_) => DType::F64,

            Value::C64 { .. } => DType::C64,
            Value::C128 { .. } => DType::C128,
        }
    }

    fn as_bool_strict(&self) -> Option<bool> {
        match self {
            Value::Bool(v) => Some(*v),
            _ => None,
        }
    }

    fn as_f64_scalar(&self) -> Option<f64> {
        match self {
            Value::F16(v) => Some(v.to_f64()),
            Value::BF16(v) => Some(v.to_f64()),
            Value::F32(v) => Some(*v as f64),
            Value::F64(v) => Some(*v),
            _ => None,
        }
    }

    fn as_complex_f64(&self) -> Option<(f64, f64)> {
        match self {
            Value::C64 { re, im } => Some((*re as f64, *im as f64)),
            Value::C128 { re, im } => Some((*re, *im)),
            _ => None,
        }
    }

    fn is_zero_imag(&self) -> bool {
        match self {
            Value::C64 { im, .. } => *im == 0.0,
            Value::C128 { im, .. } => *im == 0.0,
            _ => false,
        }
    }

    fn to_bool_lossy(&self) -> Option<bool> {
        match self {
            Value::Bool(v) => Some(*v),
            Value::UInt { value, .. } => Some(*value != 0),
            Value::SInt { value, .. } => Some(*value != 0),
            Value::F16(v) => Some(v.to_f32() != 0.0),
            Value::BF16(v) => Some(v.to_f32() != 0.0),
            Value::F32(v) => Some(*v != 0.0),
            Value::F64(v) => Some(*v != 0.0),
            Value::C64 { re, im } => Some(*re != 0.0 || *im != 0.0),
            Value::C128 { re, im } => Some(*re != 0.0 || *im != 0.0),
        }
    }

    fn to_f64_lossy(&self) -> Option<f64> {
        match self {
            Value::Bool(v) => Some(if *v { 1.0 } else { 0.0 }),
            Value::UInt { value, .. } => Some(*value as f64),
            Value::SInt { value, .. } => Some(*value as f64),
            Value::F16(v) => Some(v.to_f64()),
            Value::BF16(v) => Some(v.to_f64()),
            Value::F32(v) => Some(*v as f64),
            Value::F64(v) => Some(*v),
            Value::C64 { .. } | Value::C128 { .. } => None,
        }
    }

    fn uint_mask(bits: u16) -> u64 {
        if bits >= 64 {
            u64::MAX
        } else {
            (1u64 << bits) - 1
        }
    }

    fn sign_extend(bits: u16, raw: u64) -> i64 {
        if bits >= 64 {
            raw as i64
        } else {
            let mask = Self::uint_mask(bits);
            let raw = raw & mask;
            let sign = 1u64 << (bits - 1);
            if (raw & sign) != 0 {
                (raw | (!mask)) as i64
            } else {
                raw as i64
            }
        }
    }

    fn wrap_uint(bits: u16, v: u64) -> u64 {
        v & Self::uint_mask(bits)
    }

    fn wrap_sint(bits: u16, v: i64) -> i64 {
        let mask = Self::uint_mask(bits);
        let raw = (v as u64) & mask;
        Self::sign_extend(bits, raw)
    }

    fn float_from_f64(dtype: DType, v: f64) -> Option<Value> {
        match (dtype.kind, dtype.bits) {
            (DTypeKind::Float(FloatEncoding::Ieee), 16) => Some(Value::F16(f16::from_f64(v))),
            (DTypeKind::Float(FloatEncoding::Brain), 16) => Some(Value::BF16(bf16::from_f64(v))),
            (DTypeKind::Float(FloatEncoding::Ieee), 32) => Some(Value::F32(v as f32)),
            (DTypeKind::Float(FloatEncoding::Ieee), 64) => Some(Value::F64(v)),
            _ => None,
        }
    }

    fn complex_from_f64(dtype: DType, re: f64, im: f64) -> Option<Value> {
        match (dtype.kind, dtype.bits) {
            (DTypeKind::Complex(_), 64) => Some(Value::C64 {
                re: re as f32,
                im: im as f32,
            }),
            (DTypeKind::Complex(_), 128) => Some(Value::C128 { re, im }),
            _ => None,
        }
    }

    fn cast_to(&self, dtype: DType) -> Option<Value> {
        match dtype.kind {
            DTypeKind::Bool => self.to_bool_lossy().map(Value::Bool),

            DTypeKind::UInt => {
                let bits = dtype.bits;
                let value = match self {
                    Value::UInt { value, .. } => *value,
                    Value::SInt { value, .. } => *value as u64,
                    Value::Bool(v) => {
                        if *v {
                            1
                        } else {
                            0
                        }
                    }
                    Value::F16(v) => v.to_f32() as u64,
                    Value::BF16(v) => v.to_f32() as u64,
                    Value::F32(v) => *v as u64,
                    Value::F64(v) => *v as u64,
                    Value::C64 { re, im } => {
                        if *im != 0.0 {
                            return None;
                        }
                        *re as u64
                    }
                    Value::C128 { re, im } => {
                        if *im != 0.0 {
                            return None;
                        }
                        *re as u64
                    }
                };
                Some(Value::UInt {
                    bits,
                    value: Self::wrap_uint(bits, value),
                })
            }

            DTypeKind::SInt => {
                let bits = dtype.bits;
                let value = match self {
                    Value::SInt { value, .. } => *value,
                    Value::UInt { value, .. } => *value as i64,
                    Value::Bool(v) => {
                        if *v {
                            1
                        } else {
                            0
                        }
                    }
                    Value::F16(v) => v.to_f32() as i64,
                    Value::BF16(v) => v.to_f32() as i64,
                    Value::F32(v) => *v as i64,
                    Value::F64(v) => *v as i64,
                    Value::C64 { re, im } => {
                        if *im != 0.0 {
                            return None;
                        }
                        *re as i64
                    }
                    Value::C128 { re, im } => {
                        if *im != 0.0 {
                            return None;
                        }
                        *re as i64
                    }
                };
                Some(Value::SInt {
                    bits,
                    value: Self::wrap_sint(bits, value),
                })
            }

            DTypeKind::Float(_) => {
                if let Some(v) = self.to_f64_lossy() {
                    return Self::float_from_f64(dtype, v);
                }
                // Allow Complex -> Float only when imag is zero.
                if self.is_zero_imag() {
                    let (re, _) = self.as_complex_f64()?;
                    return Self::float_from_f64(dtype, re);
                }
                None
            }

            DTypeKind::Complex(_) => {
                if let Some((re, im)) = self.as_complex_f64() {
                    return Self::complex_from_f64(dtype, re, im);
                }
                let re = self.to_f64_lossy()?;
                Self::complex_from_f64(dtype, re, 0.0)
            }
        }
    }
}

fn read_value(values: &[Value], node_id: NodeId) -> Result<&Value, InterpError> {
    values
        .get(node_id.0)
        .ok_or(InterpError::InvalidNodeRef { node_id: node_id.0 })
}

fn unary_eval(op: UnaryOp, expected: DType, x: &Value) -> Option<Value> {
    match op {
        UnaryOp::Neg => match expected.kind {
            DTypeKind::UInt => {
                let v = x.cast_to(expected)?;
                let Value::UInt { bits, value } = v else {
                    return None;
                };
                let mask = Value::uint_mask(bits);
                Some(Value::UInt {
                    bits,
                    value: (0u64.wrapping_sub(value)) & mask,
                })
            }
            DTypeKind::SInt => {
                let v = x.cast_to(expected)?;
                let Value::SInt { bits, value } = v else {
                    return None;
                };
                let mask = Value::uint_mask(bits);
                let raw = (value as u64) & mask;
                let neg_raw = (0u64.wrapping_sub(raw)) & mask;
                Some(Value::SInt {
                    bits,
                    value: Value::sign_extend(bits, neg_raw),
                })
            }
            DTypeKind::Float(_) => {
                let v = x.cast_to(expected)?;
                let f = v.as_f64_scalar()?;
                Value::float_from_f64(expected, -f)
            }
            DTypeKind::Complex(_) => {
                let v = x.cast_to(expected)?;
                let (re, im) = v.as_complex_f64()?;
                Value::complex_from_f64(expected, -re, -im)
            }
            DTypeKind::Bool => None,
        },
        UnaryOp::Not => x.as_bool_strict().map(|v| Value::Bool(!v)),
    }
}

fn pow_mod_u64(mut base: u64, mut exp: u32, mask: u64) -> u64 {
    let mut acc: u64 = 1 & mask;
    base &= mask;
    while exp > 0 {
        if (exp & 1) == 1 {
            acc = acc.wrapping_mul(base) & mask;
        }
        exp >>= 1;
        if exp > 0 {
            base = base.wrapping_mul(base) & mask;
        }
    }
    acc & mask
}

fn floor_div_i64(a: i64, b: i64) -> Option<i64> {
    if b == 0 {
        return None;
    }
    // Keep legacy behavior: overflow is an error.
    if a == i64::MIN && b == -1 {
        return None;
    }
    let a = a as i128;
    let b = b as i128;
    let mut q = a / b; // trunc toward zero
    let r = a % b;
    if r != 0 && ((r > 0) != (b > 0)) {
        q -= 1;
    }
    Some(q as i64)
}

fn floor_rem_i64(a: i64, b: i64) -> Option<i64> {
    let q = floor_div_i64(a, b)?;
    Some(a - q * b)
}

fn binary_eval(op: BinaryOp, expected: DType, lhs: &Value, rhs: &Value) -> Option<Value> {
    match op {
        BinaryOp::And => Some(Value::Bool(lhs.as_bool_strict()? && rhs.as_bool_strict()?)),
        BinaryOp::Or => Some(Value::Bool(lhs.as_bool_strict()? || rhs.as_bool_strict()?)),

        BinaryOp::Eq | BinaryOp::Ne => {
            if matches!((lhs, rhs), (Value::Bool(_), Value::Bool(_))) {
                let out = lhs.as_bool_strict()? == rhs.as_bool_strict()?;
                return Some(Value::Bool(if op == BinaryOp::Eq { out } else { !out }));
            }
            let cmp_ty = promote(lhs.dtype(), rhs.dtype()).ok()?;
            let l = lhs.cast_to(cmp_ty)?;
            let r = rhs.cast_to(cmp_ty)?;
            let eq = match cmp_ty.kind {
                DTypeKind::Complex(_) => l.as_complex_f64()? == r.as_complex_f64()?,
                DTypeKind::Float(_) => l.as_f64_scalar()? == r.as_f64_scalar()?,
                DTypeKind::UInt => {
                    let Value::UInt { value: a, .. } = l else {
                        return None;
                    };
                    let Value::UInt { value: b, .. } = r else {
                        return None;
                    };
                    a == b
                }
                DTypeKind::SInt => {
                    let Value::SInt { value: a, .. } = l else {
                        return None;
                    };
                    let Value::SInt { value: b, .. } = r else {
                        return None;
                    };
                    a == b
                }
                DTypeKind::Bool => l.as_bool_strict()? == r.as_bool_strict()?,
            };
            Some(Value::Bool(if op == BinaryOp::Eq { eq } else { !eq }))
        }

        BinaryOp::Lt | BinaryOp::Le | BinaryOp::Gt | BinaryOp::Ge => {
            let cmp_ty = promote(lhs.dtype(), rhs.dtype()).ok()?;
            if matches!(cmp_ty.kind, DTypeKind::Complex(_)) {
                return None;
            }
            let l = lhs.cast_to(cmp_ty)?;
            let r = rhs.cast_to(cmp_ty)?;
            let ord = match cmp_ty.kind {
                DTypeKind::Float(_) => {
                    let a = l.as_f64_scalar()?;
                    let b = r.as_f64_scalar()?;
                    match op {
                        BinaryOp::Lt => a < b,
                        BinaryOp::Le => a <= b,
                        BinaryOp::Gt => a > b,
                        BinaryOp::Ge => a >= b,
                        _ => unreachable!(),
                    }
                }
                DTypeKind::UInt => {
                    let Value::UInt { value: a, .. } = l else {
                        return None;
                    };
                    let Value::UInt { value: b, .. } = r else {
                        return None;
                    };
                    match op {
                        BinaryOp::Lt => a < b,
                        BinaryOp::Le => a <= b,
                        BinaryOp::Gt => a > b,
                        BinaryOp::Ge => a >= b,
                        _ => unreachable!(),
                    }
                }
                DTypeKind::SInt => {
                    let Value::SInt { value: a, .. } = l else {
                        return None;
                    };
                    let Value::SInt { value: b, .. } = r else {
                        return None;
                    };
                    match op {
                        BinaryOp::Lt => a < b,
                        BinaryOp::Le => a <= b,
                        BinaryOp::Gt => a > b,
                        BinaryOp::Ge => a >= b,
                        _ => unreachable!(),
                    }
                }
                DTypeKind::Bool => return None,
                DTypeKind::Complex(_) => unreachable!(),
            };
            Some(Value::Bool(ord))
        }

        BinaryOp::Add | BinaryOp::Sub | BinaryOp::Mul => match expected.kind {
            DTypeKind::UInt => {
                let l = lhs.cast_to(expected)?;
                let r = rhs.cast_to(expected)?;
                let Value::UInt { bits, value: a } = l else {
                    return None;
                };
                let Value::UInt { value: b, .. } = r else {
                    return None;
                };
                let mask = Value::uint_mask(bits);
                let out = match op {
                    BinaryOp::Add => a.wrapping_add(b),
                    BinaryOp::Sub => a.wrapping_sub(b),
                    BinaryOp::Mul => a.wrapping_mul(b),
                    _ => unreachable!(),
                } & mask;
                Some(Value::UInt { bits, value: out })
            }
            DTypeKind::SInt => {
                let l = lhs.cast_to(expected)?;
                let r = rhs.cast_to(expected)?;
                let Value::SInt { bits, value: a } = l else {
                    return None;
                };
                let Value::SInt { value: b, .. } = r else {
                    return None;
                };
                let mask = Value::uint_mask(bits);
                let ar = (a as u64) & mask;
                let br = (b as u64) & mask;
                let raw = match op {
                    BinaryOp::Add => ar.wrapping_add(br),
                    BinaryOp::Sub => ar.wrapping_sub(br),
                    BinaryOp::Mul => ar.wrapping_mul(br),
                    _ => unreachable!(),
                } & mask;
                Some(Value::SInt {
                    bits,
                    value: Value::sign_extend(bits, raw),
                })
            }
            DTypeKind::Float(_) => {
                let l = lhs.cast_to(expected)?;
                let r = rhs.cast_to(expected)?;
                let a = l.as_f64_scalar()?;
                let b = r.as_f64_scalar()?;
                let out = match op {
                    BinaryOp::Add => a + b,
                    BinaryOp::Sub => a - b,
                    BinaryOp::Mul => a * b,
                    _ => unreachable!(),
                };
                Value::float_from_f64(expected, out)
            }
            DTypeKind::Complex(_) => {
                let l = lhs.cast_to(expected)?;
                let r = rhs.cast_to(expected)?;
                let (ar, ai) = l.as_complex_f64()?;
                let (br, bi) = r.as_complex_f64()?;
                let (re, im) = match op {
                    BinaryOp::Add => (ar + br, ai + bi),
                    BinaryOp::Sub => (ar - br, ai - bi),
                    BinaryOp::Mul => (ar * br - ai * bi, ar * bi + ai * br),
                    _ => unreachable!(),
                };
                Value::complex_from_f64(expected, re, im)
            }
            DTypeKind::Bool => None,
        },

        BinaryOp::Div => match expected.kind {
            DTypeKind::Float(_) => {
                let l = lhs.cast_to(expected)?;
                let r = rhs.cast_to(expected)?;
                let a = l.as_f64_scalar()?;
                let b = r.as_f64_scalar()?;
                Value::float_from_f64(expected, a / b)
            }
            DTypeKind::Complex(_) => {
                let l = lhs.cast_to(expected)?;
                let r = rhs.cast_to(expected)?;
                let (ar, ai) = l.as_complex_f64()?;
                let (br, bi) = r.as_complex_f64()?;
                let denom = br * br + bi * bi;
                Value::complex_from_f64(
                    expected,
                    (ar * br + ai * bi) / denom,
                    (ai * br - ar * bi) / denom,
                )
            }
            _ => None,
        },

        BinaryOp::FloorDiv => match expected.kind {
            DTypeKind::UInt => {
                let l = lhs.cast_to(expected)?;
                let r = rhs.cast_to(expected)?;
                let Value::UInt { bits, value: a } = l else {
                    return None;
                };
                let Value::UInt { value: b, .. } = r else {
                    return None;
                };
                if b == 0 {
                    return None;
                }
                Some(Value::UInt {
                    bits,
                    value: Value::wrap_uint(bits, a / b),
                })
            }
            DTypeKind::SInt => {
                let l = lhs.cast_to(expected)?;
                let r = rhs.cast_to(expected)?;
                let Value::SInt { bits, value: a } = l else {
                    return None;
                };
                let Value::SInt { value: b, .. } = r else {
                    return None;
                };
                let q = floor_div_i64(a, b)?;
                Some(Value::SInt {
                    bits,
                    value: Value::wrap_sint(bits, q),
                })
            }
            _ => None,
        },

        BinaryOp::Rem => match expected.kind {
            DTypeKind::UInt => {
                let l = lhs.cast_to(expected)?;
                let r = rhs.cast_to(expected)?;
                let Value::UInt { bits, value: a } = l else {
                    return None;
                };
                let Value::UInt { value: b, .. } = r else {
                    return None;
                };
                if b == 0 {
                    return None;
                }
                Some(Value::UInt {
                    bits,
                    value: Value::wrap_uint(bits, a % b),
                })
            }
            DTypeKind::SInt => {
                let l = lhs.cast_to(expected)?;
                let r = rhs.cast_to(expected)?;
                let Value::SInt { bits, value: a } = l else {
                    return None;
                };
                let Value::SInt { value: b, .. } = r else {
                    return None;
                };
                let rem = floor_rem_i64(a, b)?;
                Some(Value::SInt {
                    bits,
                    value: Value::wrap_sint(bits, rem),
                })
            }
            DTypeKind::Float(_) => {
                let l = lhs.cast_to(expected)?;
                let r = rhs.cast_to(expected)?;
                let a = l.as_f64_scalar()?;
                let b = r.as_f64_scalar()?;
                Value::float_from_f64(expected, a % b)
            }
            DTypeKind::Complex(_) | DTypeKind::Bool => None,
        },

        BinaryOp::Pow => match expected.kind {
            DTypeKind::UInt => {
                let l = lhs.cast_to(expected)?;
                let r = rhs.cast_to(expected)?;
                let Value::UInt { bits, value: a } = l else {
                    return None;
                };
                let Value::UInt { value: b, .. } = r else {
                    return None;
                };
                let e: u32 = b.try_into().ok()?;
                let mask = Value::uint_mask(bits);
                Some(Value::UInt {
                    bits,
                    value: pow_mod_u64(a, e, mask),
                })
            }
            DTypeKind::SInt => {
                let l = lhs.cast_to(expected)?;
                let r = rhs.cast_to(expected)?;
                let Value::SInt { bits, value: a } = l else {
                    return None;
                };
                let Value::SInt { value: b, .. } = r else {
                    return None;
                };
                if b < 0 {
                    return None;
                }
                let e: u32 = (b as u64).try_into().ok()?;
                let mask = Value::uint_mask(bits);
                let base_raw = (a as u64) & mask;
                let raw = pow_mod_u64(base_raw, e, mask);
                Some(Value::SInt {
                    bits,
                    value: Value::sign_extend(bits, raw),
                })
            }
            DTypeKind::Float(_) => {
                let l = lhs.cast_to(expected)?;
                let r = rhs.cast_to(expected)?;
                let a = l.as_f64_scalar()?;
                let b = r.as_f64_scalar()?;
                Value::float_from_f64(expected, a.powf(b))
            }
            DTypeKind::Complex(_) | DTypeKind::Bool => None,
        },
    }
}

fn call_eval(builtin: Builtin, expected: DType, args: &[Value]) -> Option<Value> {
    if args.len() != 1 {
        return None;
    }
    let x = args[0].cast_to(expected)?;
    let x = x.as_f64_scalar()?;
    match builtin {
        Builtin::Exp => Value::float_from_f64(expected, x.exp()),
        Builtin::Log => Value::float_from_f64(expected, x.ln()),
        Builtin::Sqrt => Value::float_from_f64(expected, x.sqrt()),
        Builtin::Abs => Value::float_from_f64(expected, x.abs()),
        Builtin::Sin => Value::float_from_f64(expected, x.sin()),
        Builtin::Cos => Value::float_from_f64(expected, x.cos()),
        Builtin::Tanh => Value::float_from_f64(expected, x.tanh()),
        Builtin::Sigmoid => Value::float_from_f64(expected, 1.0 / (1.0 + (-x).exp())),
        Builtin::Relu => Value::float_from_f64(expected, if x > 0.0 { x } else { 0.0 }),
        Builtin::Softplus => Value::float_from_f64(expected, (1.0 + x.exp()).ln()),
        Builtin::Floor => Value::float_from_f64(expected, x.floor()),
        Builtin::Ceil => Value::float_from_f64(expected, x.ceil()),
        Builtin::ExpSigmoid => {
            let ex = x.exp();
            Value::float_from_f64(expected, 1.0 / (1.0 + (-ex).exp()))
        }
        Builtin::SigmoidLog => {
            let sig = 1.0 / (1.0 + (-x).exp());
            Value::float_from_f64(expected, sig.ln())
        }
    }
}

pub fn eval(dag: &TypedDag, inputs: &[Value]) -> Result<Vec<Value>, InterpError> {
    if inputs.len() != dag.params.len() {
        return Err(InterpError::InputCount {
            expected: dag.params.len(),
            got: inputs.len(),
        });
    }
    if dag.nodes.len() != dag.dtypes.len() {
        return Err(InterpError::InvalidNodeRef {
            node_id: dag.nodes.len(),
        });
    }

    let mut values: Vec<Value> = Vec::with_capacity(dag.nodes.len());

    for (idx, node) in dag.nodes.iter().enumerate() {
        let expected = dag.dtypes[idx];
        let raw = match node {
            Node::Param(param_id) => inputs
                .get(param_id.0)
                .cloned()
                .ok_or(InterpError::InvalidNodeRef { node_id: idx })?,
            Node::Const(Const::Bool(v)) => Value::Bool(*v),
            Node::Const(Const::Int(v)) => Value::SInt {
                bits: 64,
                value: *v,
            },
            Node::Const(Const::Float(v)) => Value::F64(*v),
            Node::Const(Const::Complex(re, im)) => Value::C128 { re: *re, im: *im },
            Node::Unary(op, child_id) => {
                let child = read_value(&values, *child_id)?;
                unary_eval(*op, expected, child).ok_or(InterpError::TypeMismatch {
                    node_id: idx,
                    expected,
                    actual: child.dtype(),
                })?
            }
            Node::Binary(op, lhs_id, rhs_id) => {
                let lhs = read_value(&values, *lhs_id)?;
                let rhs = read_value(&values, *rhs_id)?;
                if matches!(*op, BinaryOp::FloorDiv | BinaryOp::Rem) {
                    let cast_rhs = rhs.cast_to(expected).ok_or(InterpError::TypeMismatch {
                        node_id: idx,
                        expected,
                        actual: rhs.dtype(),
                    })?;
                    if matches!(
                        cast_rhs,
                        Value::UInt { value: 0, .. } | Value::SInt { value: 0, .. }
                    ) {
                        return Err(InterpError::DivisionByZero { node_id: idx });
                    }
                }
                if matches!(*op, BinaryOp::Div) {
                    if matches!(expected.kind, DTypeKind::Float(_))
                        && let Some(b) = rhs.cast_to(expected).and_then(|v| v.as_f64_scalar())
                        && b == 0.0
                    {
                        return Err(InterpError::DivisionByZero { node_id: idx });
                    }
                    if matches!(expected.kind, DTypeKind::Complex(_))
                        && let Some((br, bi)) =
                            rhs.cast_to(expected).and_then(|v| v.as_complex_f64())
                    {
                        let denom = br * br + bi * bi;
                        if denom == 0.0 {
                            return Err(InterpError::DivisionByZero { node_id: idx });
                        }
                    }
                }
                binary_eval(*op, expected, lhs, rhs).ok_or(InterpError::TypeMismatch {
                    node_id: idx,
                    expected,
                    actual: lhs.dtype(),
                })?
            }
            Node::IfElse(cond_id, then_id, else_id) => {
                let cond = read_value(&values, *cond_id)?;
                let cond_bool = cond
                    .as_bool_strict()
                    .ok_or(InterpError::InvalidCondition { node_id: idx })?;
                if cond_bool {
                    read_value(&values, *then_id)?.clone()
                } else {
                    read_value(&values, *else_id)?.clone()
                }
            }
            Node::Call(builtin, arg_ids) => {
                let mut args = Vec::with_capacity(arg_ids.len());
                for arg_id in arg_ids {
                    args.push(read_value(&values, *arg_id)?.clone());
                }
                call_eval(*builtin, expected, &args).ok_or(InterpError::TypeMismatch {
                    node_id: idx,
                    expected,
                    actual: args
                        .first()
                        .map(|v| v.dtype())
                        .unwrap_or_else(|| DType::BOOL),
                })?
            }
            Node::Cast(dtype, child_id, _) => {
                let child = read_value(&values, *child_id)?;
                child.cast_to(*dtype).ok_or(InterpError::TypeMismatch {
                    node_id: idx,
                    expected: *dtype,
                    actual: child.dtype(),
                })?
            }
        };

        let value = raw.cast_to(expected).ok_or(InterpError::TypeMismatch {
            node_id: idx,
            expected,
            actual: raw.dtype(),
        })?;
        values.push(value);
    }

    let mut outputs = Vec::with_capacity(dag.outputs.len());
    for output_id in &dag.outputs {
        outputs.push(read_value(&values, *output_id)?.clone());
    }
    Ok(outputs)
}

#[cfg(test)]
mod tests {
    use super::{Value, eval};
    use crate::lower;
    use crate::parse::parse_dsl;

    #[test]
    fn interp_floor_division_and_remainder_sign() {
        let func = parse_dsl("||\n-7@i32 // 3@i32").expect("parse");
        let dag = lower(&func).expect("lower").dag;
        let out = eval(&dag, &[]).expect("eval");
        assert_eq!(
            out[0],
            Value::SInt {
                bits: 32,
                value: -3
            }
        );

        let func = parse_dsl("||\n-7@i32 % 3@i32").expect("parse");
        let dag = lower(&func).expect("lower").dag;
        let out = eval(&dag, &[]).expect("eval");
        assert_eq!(out[0], Value::SInt { bits: 32, value: 2 });
    }

    #[test]
    fn interp_floor_div_overflow_is_error() {
        let func = parse_dsl("||\n(-9223372036854775807@i64 - 1@i64) // -1@i64").expect("parse");
        let dag = lower(&func).expect("lower").dag;
        assert!(eval(&dag, &[]).is_err());
    }
}
