use super::VM;
use crate::nan_value::NanValue;
use crate::vm::symbol::VmSymbolKind;
use crate::vm::types::VmError;

impl VM {
    pub(super) fn decode_vm_symbol_id(&self, val: NanValue) -> Option<u32> {
        if !val.is_int() {
            return None;
        }
        let symbol_id = val.as_int(&self.arena);
        (symbol_id >= 0).then_some(symbol_id as u32).filter(|&id| {
            self.code
                .symbols
                .get(id)
                .is_some_and(|info| info.kind.is_some())
        })
    }

    pub(super) fn nan_tag(&self, val: NanValue) -> u8 {
        if val.is_float() {
            return 0xFF;
        }
        ((val.bits() >> 46) & 0xF) as u8
    }

    pub(super) fn decode_vm_fn_ref(
        &self,
        val: NanValue,
        caller_fn_id: u32,
        ip: usize,
    ) -> Result<u32, VmError> {
        if let Some(symbol_id) = self.decode_vm_symbol_id(val)
            && let Some(fn_id) = self.code.symbols.resolve_function(symbol_id)
        {
            return Ok(fn_id);
        }
        let caller_name = &self.code.functions[caller_fn_id as usize].name;
        Err(VmError::type_err(format!(
            "cannot call non-function (got {} = {:?}) in {} at ip={}",
            self.value_type_name(val),
            self.value_repr(val),
            caller_name,
            ip
        )))
    }

    pub(super) fn variant_ctor_id_vm(&self, val: NanValue) -> Option<u32> {
        if let Some(symbol_id) = self.decode_vm_symbol_id(val)
            && let Some(ctor) = self.code.symbols.resolve_variant_ctor(symbol_id)
            && ctor.field_count == 0
        {
            return Some(ctor.ctor_id);
        }
        val.variant_ctor_id(&self.arena)
    }

    pub(super) fn value_type_name(&self, val: NanValue) -> String {
        if let Some(symbol_id) = self.decode_vm_symbol_id(val)
            && let Some(info) = self.code.symbols.get(symbol_id)
            && let Some(kind) = info.kind
        {
            return match kind {
                VmSymbolKind::Function(_) => "Fn".to_string(),
                VmSymbolKind::Builtin(_) => "Builtin".to_string(),
                VmSymbolKind::Namespace => "Namespace".to_string(),
                VmSymbolKind::VariantCtor(ctor) => {
                    if ctor.field_count == 0 {
                        "Variant".to_string()
                    } else {
                        "VariantCtor".to_string()
                    }
                }
                VmSymbolKind::Wrapper(kind) => match kind {
                    0 => "Result.Ok".to_string(),
                    1 => "Result.Err".to_string(),
                    2 => "Option.Some".to_string(),
                    _ => "Wrapper".to_string(),
                },
                VmSymbolKind::Constant(value) => value.type_name().to_string(),
            };
        }
        val.type_name().to_string()
    }

    pub(super) fn value_repr(&self, val: NanValue) -> String {
        if let Some(symbol_id) = self.decode_vm_symbol_id(val)
            && let Some(info) = self.code.symbols.get(symbol_id)
            && let Some(kind) = info.kind
        {
            return match kind {
                VmSymbolKind::Function(_) => {
                    format!("<fn {}>", info.name)
                }
                VmSymbolKind::Builtin(_) => {
                    format!("<builtin {}>", info.name)
                }
                VmSymbolKind::Namespace => {
                    format!("<type {}>", info.name)
                }
                VmSymbolKind::VariantCtor(ctor) => {
                    if ctor.field_count == 0 {
                        info.name
                            .rsplit('.')
                            .next()
                            .unwrap_or(info.name.as_str())
                            .to_string()
                    } else {
                        format!("<ctor {}>", info.name)
                    }
                }
                VmSymbolKind::Wrapper(_) => {
                    format!("<ctor {}>", info.name)
                }
                VmSymbolKind::Constant(value) => value.repr(&self.arena),
            };
        }
        val.repr(&self.arena)
    }

    pub(super) fn arith_add(&mut self, a: NanValue, b: NanValue) -> Result<NanValue, VmError> {
        if a.is_int() && b.is_int() {
            Ok(NanValue::new_int(
                a.as_int(&self.arena) + b.as_int(&self.arena),
                &mut self.arena,
            ))
        } else if a.is_float() && b.is_float() {
            Ok(NanValue::new_float(a.as_float() + b.as_float()))
        } else if a.is_int() && b.is_float() {
            Ok(NanValue::new_float(
                a.as_int(&self.arena) as f64 + b.as_float(),
            ))
        } else if a.is_float() && b.is_int() {
            Ok(NanValue::new_float(
                a.as_float() + b.as_int(&self.arena) as f64,
            ))
        } else if a.is_string() && b.is_string() {
            let s = format!(
                "{}{}",
                self.arena.get_string_value(a),
                self.arena.get_string_value(b)
            );
            Ok(NanValue::new_string_value(&s, &mut self.arena))
        } else {
            Err(VmError::type_err(format!(
                "cannot add {} and {}",
                self.value_type_name(a),
                self.value_type_name(b)
            )))
        }
    }

    pub(super) fn arith_sub(&mut self, a: NanValue, b: NanValue) -> Result<NanValue, VmError> {
        if a.is_int() && b.is_int() {
            Ok(NanValue::new_int(
                a.as_int(&self.arena) - b.as_int(&self.arena),
                &mut self.arena,
            ))
        } else if a.is_float() && b.is_float() {
            Ok(NanValue::new_float(a.as_float() - b.as_float()))
        } else if a.is_int() && b.is_float() {
            Ok(NanValue::new_float(
                a.as_int(&self.arena) as f64 - b.as_float(),
            ))
        } else if a.is_float() && b.is_int() {
            Ok(NanValue::new_float(
                a.as_float() - b.as_int(&self.arena) as f64,
            ))
        } else {
            Err(VmError::type_err(format!(
                "cannot subtract {} and {}",
                self.value_type_name(a),
                self.value_type_name(b)
            )))
        }
    }

    pub(super) fn arith_mul(&mut self, a: NanValue, b: NanValue) -> Result<NanValue, VmError> {
        if a.is_int() && b.is_int() {
            Ok(NanValue::new_int(
                a.as_int(&self.arena) * b.as_int(&self.arena),
                &mut self.arena,
            ))
        } else if a.is_float() && b.is_float() {
            Ok(NanValue::new_float(a.as_float() * b.as_float()))
        } else if a.is_int() && b.is_float() {
            Ok(NanValue::new_float(
                a.as_int(&self.arena) as f64 * b.as_float(),
            ))
        } else if a.is_float() && b.is_int() {
            Ok(NanValue::new_float(
                a.as_float() * b.as_int(&self.arena) as f64,
            ))
        } else {
            Err(VmError::type_err(format!(
                "cannot multiply {} and {}",
                self.value_type_name(a),
                self.value_type_name(b)
            )))
        }
    }

    pub(super) fn arith_div(&mut self, a: NanValue, b: NanValue) -> Result<NanValue, VmError> {
        if a.is_int() && b.is_int() {
            let bv = b.as_int(&self.arena);
            if bv == 0 {
                return Err(VmError::runtime("division by zero"));
            }
            Ok(NanValue::new_int(
                a.as_int(&self.arena) / bv,
                &mut self.arena,
            ))
        } else if a.is_float() && b.is_float() {
            Ok(NanValue::new_float(a.as_float() / b.as_float()))
        } else if a.is_int() && b.is_float() {
            Ok(NanValue::new_float(
                a.as_int(&self.arena) as f64 / b.as_float(),
            ))
        } else if a.is_float() && b.is_int() {
            Ok(NanValue::new_float(
                a.as_float() / b.as_int(&self.arena) as f64,
            ))
        } else {
            Err(VmError::type_err(format!(
                "cannot divide {} and {}",
                self.value_type_name(a),
                self.value_type_name(b)
            )))
        }
    }

    pub(super) fn arith_mod(&mut self, a: NanValue, b: NanValue) -> Result<NanValue, VmError> {
        if a.is_int() && b.is_int() {
            let bv = b.as_int(&self.arena);
            if bv == 0 {
                return Err(VmError::runtime("modulo by zero"));
            }
            Ok(NanValue::new_int(
                a.as_int(&self.arena) % bv,
                &mut self.arena,
            ))
        } else {
            Err(VmError::type_err(format!(
                "cannot modulo {} and {}",
                self.value_type_name(a),
                self.value_type_name(b)
            )))
        }
    }

    pub(super) fn compare_lt(&self, a: NanValue, b: NanValue) -> Result<bool, VmError> {
        if a.is_int() && b.is_int() {
            Ok(a.as_int(&self.arena) < b.as_int(&self.arena))
        } else if a.is_float() && b.is_float() {
            Ok(a.as_float() < b.as_float())
        } else if a.is_string() && b.is_string() {
            Ok(self.arena.get_string_value(a) < self.arena.get_string_value(b))
        } else if a.is_int() && b.is_float() {
            Ok((a.as_int(&self.arena) as f64) < b.as_float())
        } else if a.is_float() && b.is_int() {
            Ok(a.as_float() < (b.as_int(&self.arena) as f64))
        } else {
            Err(VmError::type_err(format!(
                "cannot compare {} and {}",
                self.value_type_name(a),
                self.value_type_name(b)
            )))
        }
    }
}
