/*!
 * Minimal MIR Interpreter
 *
 * Executes a subset of MIR instructions for fast iteration without LLVM/JIT.
 * Supported: Const, BinOp(Add/Sub/Mul/Div/Mod), Compare, Load/Store, Branch, Jump, Return,
 * Print/Debug (best-effort), Barrier/Safepoint (no-op).
 */

use std::cell::RefCell;
use std::collections::{BTreeMap, HashMap};
use std::rc::Rc;

use crate::box_trait::NyashBox;

pub(super) use crate::backend::abi_util::{eq_vm, to_bool_vm};
pub(super) use crate::backend::vm::{VMError, VMValue};
pub(super) use crate::mir::{
    BasicBlockId, BinaryOp, Callee, CompareOp, ConstValue, MirFunction, MirInstruction, MirModule,
    MirType, TypeOpKind, ValueId, WeakRefOp,
};

mod exec;
mod handlers;
mod helpers;
mod method_router;
pub mod static_box_registry;
mod utils;

pub use static_box_registry::StaticBoxRegistry;

pub struct MirInterpreter {
    pub(super) regs: HashMap<ValueId, VMValue>,
    pub(super) mem: HashMap<ValueId, VMValue>,
    // Object field storage keyed by stable object identity (Arc ptr addr fallback)
    pub(super) obj_fields: HashMap<u64, HashMap<String, VMValue>>,
    pub(super) functions: BTreeMap<String, MirFunction>,
    pub(super) cur_fn: Option<String>,
    // Trace context (dev-only; enabled with NYASH_VM_TRACE=1)
    pub(super) last_block: Option<BasicBlockId>,
    pub(super) last_inst: Option<MirInstruction>,
    pub(super) last_inst_index: Option<usize>,
    // 🎯 Phase 173-B: Unified static box management via StaticBoxRegistry
    pub(super) static_box_registry: StaticBoxRegistry,
    // Lightweight dev counters (opt-in print via NYASH_VM_STATS=1)
    pub(super) inst_count: u64,
    pub(super) branch_count: u64,
    pub(super) compare_count: u64,
    /// Call stack depth (exec_function_inner nesting). Used as a safety valve
    /// to prevent Rust stack overflow on accidental infinite recursion in MIR.
    pub(super) call_depth: usize,
    /// Phase 288.1: REPL session reference (REPL mode only)
    /// Enables variable persistence across REPL lines via __repl.get/set bridge
    pub(super) repl_session: Option<Rc<RefCell<crate::runner::repl::ReplSessionBox>>>,
}

impl MirInterpreter {
    pub fn new() -> Self {
        Self {
            regs: HashMap::new(),
            mem: HashMap::new(),
            obj_fields: HashMap::new(),
            functions: BTreeMap::new(),
            cur_fn: None,
            last_block: None,
            last_inst: None,
            last_inst_index: None,
            static_box_registry: StaticBoxRegistry::new(),
            inst_count: 0,
            branch_count: 0,
            compare_count: 0,
            call_depth: 0,
            repl_session: None,
        }
    }

    /// Return (inst_count, branch_count, compare_count)
    pub fn stats_counters(&self) -> (u64, u64, u64) {
        (self.inst_count, self.branch_count, self.compare_count)
    }

    /// Register static box declarations (called from vm.rs during setup)
    /// Now delegated to StaticBoxRegistry
    pub fn register_static_box_decl(
        &mut self,
        name: String,
        decl: crate::core::model::BoxDeclaration,
    ) {
        self.static_box_registry.register_declaration(name, decl);
    }

    /// Check if a static box is already registered (Phase 173-B)
    /// Now delegated to StaticBoxRegistry
    pub fn has_static_box_decl(&self, name: &str) -> bool {
        self.static_box_registry.exists(name)
    }

    /// Initialize registry from MIR function names (auto-detect using imports)
    /// Called from vm.rs after module is loaded
    pub fn detect_static_boxes_from_functions(&mut self) {
        self.static_box_registry
            .detect_from_mir_functions(self.functions.keys());
    }

    /// Phase 288.1: Set REPL session for variable persistence
    /// Enables __repl.get/set ExternCall handlers to access session state
    pub fn set_repl_session(&mut self, session: Rc<RefCell<crate::runner::repl::ReplSessionBox>>) {
        self.repl_session = Some(session);
    }

    /// Execute a BoxCall with VM's complete semantics (Phase 27-shortterm S-5.2-improved)
    ///
    /// This wrapper allows external modules (e.g., JoinIR Runner) to invoke BoxCall
    /// with the VM's complete semantics including:
    /// - Void guards (e.g., Void.length() → 0)
    /// - PluginBox support (FileBox, NetBox, etc.)
    /// - InstanceBox policy checks
    /// - object_fields handling
    /// - Method re-routing (toString→str)
    ///
    /// # Implementation Notes
    /// - Uses 1_000_000 register range for scratch registers (避ける ID 衝突)
    /// - Properly cleans up temporary registers after use
    /// - Delegates to `handle_box_call` for complete VM semantics
    ///
    /// # Arguments
    /// - `receiver`: The box instance (VMValue::BoxRef or primitive)
    /// - `method`: Method name to invoke
    /// - `args`: Method arguments as VMValue
    ///
    /// # Returns
    /// Result value as VMValue (may be Void, Int, String, BoxRef, etc.)
    pub fn execute_box_call(
        &mut self,
        receiver: VMValue,
        method: &str,
        args: Vec<VMValue>,
    ) -> Result<VMValue, VMError> {
        // Allocate temporary register IDs in the 1_000_000 range (not 1000!)
        // This avoids conflicts with user code and future extensions
        let base = ValueId(1_000_000);
        let recv_id = base;

        // Place receiver in register
        self.regs.insert(recv_id, receiver);

        // Place arguments in consecutive registers
        let arg_ids: Vec<ValueId> = args
            .into_iter()
            .enumerate()
            .map(|(i, v)| {
                let id = ValueId(base.0 + 1 + i as u32);
                self.regs.insert(id, v);
                id
            })
            .collect();

        // Allocate destination register
        let dst_id = ValueId(base.0 + 1000);

        // Invoke handle_box_call for complete VM semantics
        self.handle_box_call(Some(dst_id), recv_id, method, &arg_ids)?;

        // Read result (may be Void if method returns nothing)
        let result = self.regs.remove(&dst_id).unwrap_or(VMValue::Void);

        // Cleanup temporary registers (important to avoid stale values!)
        self.regs.remove(&recv_id);
        for id in arg_ids {
            self.regs.remove(&id);
        }

        Ok(result)
    }

    /// Ensure static box singleton instance exists, create if not
    /// Returns mutable reference to the singleton instance
    /// Now delegated to StaticBoxRegistry
    fn ensure_static_box_instance(
        &mut self,
        box_name: &str,
    ) -> Result<&mut crate::instance_v2::InstanceBox, VMError> {
        self.static_box_registry
            .get_or_create_instance(box_name)
            .map_err(|e| VMError::InvalidInstruction(e))
    }

    /// Check if a function name represents a static box method
    /// Format: "BoxName.method/Arity"
    /// Now uses StaticBoxRegistry naming utilities
    #[allow(dead_code)]
    fn is_static_box_method(&self, func_name: &str) -> Option<String> {
        static_box_registry::naming::extract_box_name(func_name)
            .filter(|name| self.static_box_registry.exists(name))
    }

    /// Execute module entry (main) and return boxed result
    pub fn execute_module(&mut self, module: &MirModule) -> Result<Box<dyn NyashBox>, VMError> {
        // Snapshot functions for call resolution
        self.functions = module.functions.clone();

        // 🎯 Phase 173-B: Auto-detect static boxes from MIR function names
        // This handles using-imported static boxes that aren't in AST
        self.detect_static_boxes_from_functions();

        // Determine entry function with sensible fallbacks (arity-aware, Strangler-safe).
        //
        // Priority (SSOT-ish for VM backend):
        //   1) NYASH_ENTRY env (exact; if arity-less, try auto-match)
        //   2) Main.main/0
        //   3) Main.main
        //   4) main (top-level; only if NYASH_ENTRY_ALLOW_TOPLEVEL_MAIN=1)
        let mut candidates: Vec<String> = Vec::new();
        if let Ok(e) = std::env::var("NYASH_ENTRY") {
            let entry = e.trim();
            if !entry.is_empty() {
                candidates.push(entry.to_string());
                if !entry.contains('/') {
                    candidates.push(format!("{}/0", entry));
                    candidates.push(format!("{}/1", entry));
                }
            }
        }
        candidates.push(crate::mir::naming::encode_static_method("Main", "main", 0));
        candidates.push("Main.main".to_string());
        if crate::config::env::entry_allow_toplevel_main() {
            candidates.push("main".to_string());
        }

        let mut chosen: Option<&nyash_rust::mir::MirFunction> = None;
        let mut chosen_name: Option<String> = None;
        for c in &candidates {
            if let Some(f) = module.functions.get(c) {
                chosen = Some(f);
                chosen_name = Some(c.clone());
                break;
            }
        }

        let func = match chosen {
            Some(f) => f,
            None => {
                let mut names: Vec<&String> = module.functions.keys().collect();
                names.sort();
                let avail = names
                    .into_iter()
                    .take(12)
                    .cloned()
                    .collect::<Vec<_>>()
                    .join(", ");
                let tried = candidates.join(", ");
                let msg = format!(
                    "entry function not found. searched: [{}]. available: [{}]. hint: define 'static box Main {{ method main(args){{ ... }} }}' or set NYASH_ENTRY=Name",
                    tried, avail
                );
                return Err(VMError::InvalidInstruction(msg));
            }
        };

        if std::env::var("NYASH_EMIT_MIR_TRACE").ok().as_deref() == Some("1") {
            if let Some(name) = chosen_name {
                eprintln!("[vm/entry] main={}", name);
            }
        }

        // Prepare arguments if the entry takes parameters (pass script args as ArrayBox)
        let ret = if func.signature.params.len() == 0 {
            self.execute_function(func)?
        } else {
            // Build argv from (priority) HEX JSON, normal JSON, or NYASH_ARGV
            // 1) NYASH_SCRIPT_ARGS_HEX_JSON: JSON array of hex-encoded UTF-8 strings
            // 2) NYASH_SCRIPT_ARGS_JSON: JSON array of strings
            // 3) NYASH_ARGV: JSON array (legacy)
            let mut argv_list: Vec<String> = Vec::new();
            if let Ok(s) = std::env::var("NYASH_SCRIPT_ARGS_HEX_JSON") {
                if let Ok(v) = serde_json::from_str::<Vec<String>>(&s) {
                    let mut out = Vec::with_capacity(v.len());
                    for hx in v.into_iter() {
                        match hex_decode_to_string(&hx) {
                            Ok(ss) => out.push(ss),
                            Err(_) => out.push(String::new()),
                        }
                    }
                    argv_list = out;
                }
            } else if let Ok(s) = std::env::var("NYASH_SCRIPT_ARGS_JSON") {
                if let Ok(v) = serde_json::from_str::<Vec<String>>(&s) {
                    argv_list = v;
                }
            } else if let Ok(s) = std::env::var("NYASH_ARGV") {
                if let Ok(v) = serde_json::from_str::<Vec<String>>(&s) {
                    argv_list = v;
                }
            }
            // Construct ArrayBox of StringBox
            let array = crate::boxes::array::ArrayBox::new();
            for a in argv_list.iter() {
                let sb = crate::boxes::basic::StringBox::new(a);
                let _ = array.push(Box::new(sb));
            }
            let boxed: Box<dyn crate::box_trait::NyashBox> = Box::new(array);
            let arg0 = super::vm_types::VMValue::from_nyash_box(boxed);
            // Fill remaining params with Void
            let mut vm_args: Vec<super::vm_types::VMValue> = Vec::new();
            vm_args.push(arg0);
            for _ in 1..func.signature.params.len() {
                vm_args.push(super::vm_types::VMValue::Void);
            }
            self.exec_function_inner(func, Some(&vm_args))?
        };
        Ok(ret.to_nyash_box())
    }

    /// Execute a specific function with explicit arguments (bypasses entry discovery).
    pub fn execute_function_with_args(
        &mut self,
        module: &MirModule,
        func_name: &str,
        args: &[VMValue],
    ) -> Result<VMValue, VMError> {
        // Snapshot functions for call resolution
        self.functions = module.functions.clone();

        let func = self
            .functions
            .get(func_name)
            .ok_or_else(|| {
                VMError::InvalidInstruction(format!("function not found: {}", func_name))
            })?
            .clone();

        self.exec_function_inner(&func, Some(args))
    }

    fn execute_function(&mut self, func: &MirFunction) -> Result<VMValue, VMError> {
        self.exec_function_inner(func, None)
    }
}

fn hex_decode_to_string(hex: &str) -> Result<String, ()> {
    let mut bytes: Vec<u8> = Vec::with_capacity(hex.len() / 2);
    let mut it = hex.as_bytes().iter().cloned();
    while let (Some(h), Some(l)) = (it.next(), it.next()) {
        let hi = from_hex(h).ok_or(())?;
        let lo = from_hex(l).ok_or(())?;
        bytes.push((hi << 4) | lo);
    }
    match String::from_utf8(bytes) {
        Ok(s) => Ok(s),
        Err(e) => Ok(String::from_utf8_lossy(e.as_bytes()).into_owned()),
    }
}

fn from_hex(b: u8) -> Option<u8> {
    match b {
        b'0'..=b'9' => Some(b - b'0'),
        b'a'..=b'f' => Some(b - b'a' + 10),
        b'A'..=b'F' => Some(b - b'A' + 10),
        _ => None,
    }
}
