use super::*;
use crate::mir::basic_block::BasicBlock;
use std::fs::OpenOptions;
use std::io::Write;
use std::mem;

impl MirInterpreter {
    fn trace_enabled() -> bool {
        std::env::var("NYASH_VM_TRACE").ok().as_deref() == Some("1")
            || std::env::var("NYASH_VM_TRACE_EXEC").ok().as_deref() == Some("1")
    }

    pub(super) fn exec_function_inner(
        &mut self,
        func: &MirFunction,
        arg_vals: Option<&[VMValue]>,
    ) -> Result<VMValue, VMError> {
        // Safety valve: cap nested exec_function_inner depth to avoid Rust stack overflow
        // on accidental infinite recursion in MIR (e.g., self-recursive call chains).
        const MAX_CALL_DEPTH: usize = 128;
        self.call_depth = self.call_depth.saturating_add(1);
        if self.call_depth > MAX_CALL_DEPTH {
            eprintln!(
                "[vm-call-depth] exceeded {} in fn={} (depth={})",
                MAX_CALL_DEPTH, func.signature.name, self.call_depth
            );
            self.call_depth = self.call_depth.saturating_sub(1);
            return Err(VMError::InvalidInstruction(format!(
                "vm call stack depth exceeded (max_depth={}, fn={})",
                MAX_CALL_DEPTH, func.signature.name
            )));
        }
        // Phase 1: delegate cross-class reroute / narrow fallbacks to method_router
        if let Some(r) = super::method_router::pre_exec_reroute(self, func, arg_vals) {
            return r;
        }
        let saved_regs = mem::take(&mut self.regs);
        let saved_fn = self.cur_fn.clone();
        self.cur_fn = Some(func.signature.name.clone());

        match arg_vals {
            Some(args) => {
                // Regular parameter binding: params and args are 1:1
                for (i, pid) in func.params.iter().enumerate() {
                    let v = args.get(i).cloned().unwrap_or(VMValue::Void);
                    self.regs.insert(*pid, v);
                }
            }
            None => {
                // Seed all parameters with Void so SSA consumers observe defined values.
                for pid in &func.params {
                    self.regs.insert(*pid, VMValue::Void);
                }
            }
        }

        let mut cur = func.entry_block;
        let mut last_pred: Option<BasicBlockId> = None;
        // Dev/runtime safety valve: cap the number of interpreter steps per function
        // to prevent infinite loops from hanging the process.
        let max_steps: u64 = std::env::var("HAKO_VM_MAX_STEPS")
            .ok()
            .and_then(|v| v.parse::<u64>().ok())
            .or_else(|| {
                std::env::var("NYASH_VM_MAX_STEPS")
                    .ok()
                    .and_then(|v| v.parse::<u64>().ok())
            })
            .unwrap_or(1_000_000);
        let mut steps: u64 = 0;
        let trace_log_path: Option<String> = std::env::var("NYASH_VM_TRACE_LOG").ok().map(|v| {
            let trimmed = v.trim();
            if trimmed.is_empty() || trimmed == "1" {
                "__mir__.log".to_string()
            } else {
                v
            }
        });

        loop {
            steps += 1;
            // max_steps == 0 は「上限なし」を意味する（開発/診断専用）。
            if max_steps > 0 && steps > max_steps {
                let last_inst_str = self.last_inst.as_ref().map(|inst| format!("{:?}", inst));
                let target_bb = self.last_block.unwrap_or(cur);
                let span = func
                    .blocks
                    .get(&target_bb)
                    .and_then(|block| self.lookup_span_for_inst(block, self.last_inst_index));
                return Err(VMError::StepBudgetExceeded {
                    max_steps,
                    steps,
                    function: self.cur_fn.clone(),
                    current_block: cur,
                    last_block: self.last_block,
                    last_inst: last_inst_str,
                    last_inst_index: self.last_inst_index,
                    span,
                    source_file: func.metadata.source_file.clone(),
                });
            }
            let block = func
                .blocks
                .get(&cur)
                .ok_or_else(|| VMError::InvalidBasicBlock(format!("bb {:?} not found", cur)))?;

            if let Some(path) = trace_log_path.as_ref() {
                let _ = OpenOptions::new()
                    .create(true)
                    .append(true)
                    .open(path)
                    .and_then(|mut f| {
                        writeln!(
                            f,
                            "[vm-trace-log] fn={} bb={:?} pred={:?} step={}",
                            self.cur_fn.as_deref().unwrap_or(""),
                            cur,
                            last_pred,
                            steps
                        )
                    });
            }

            if Self::trace_enabled() {
                eprintln!(
                    "[vm-trace] enter bb={:?} pred={:?} fn={}",
                    cur,
                    last_pred,
                    self.cur_fn.as_deref().unwrap_or("")
                );
            }

            self.apply_phi_nodes(block, last_pred)?;
            if let Err(e) = self.execute_block_instructions(block) {
                if Self::trace_enabled() {
                    eprintln!(
                        "[vm-trace] error in bb={:?}: {:?}\n  last_inst={:?}",
                        cur, e, self.last_inst
                    );
                }
                // Optional concise error location print (env‑gated)
                if std::env::var("HAKO_VM_ERROR_LOC").ok().as_deref() == Some("1") {
                    eprintln!(
                        "[vm/error/loc] fn={} bb={:?} last_inst={:?}",
                        self.cur_fn.as_deref().unwrap_or("<unknown>"),
                        cur,
                        self.last_inst
                    );
                }
                return Err(e);
            }

            match self.handle_terminator(block)? {
                BlockOutcome::Return(result) => {
                    self.cur_fn = saved_fn;
                    self.regs = saved_regs;
                    self.call_depth = self.call_depth.saturating_sub(1);
                    return Ok(result);
                }
                BlockOutcome::Next {
                    target,
                    predecessor,
                } => {
                    last_pred = Some(predecessor);
                    cur = target;
                }
            }
        }
    }

    fn lookup_span_for_inst(
        &self,
        block: &BasicBlock,
        inst_index: Option<usize>,
    ) -> Option<crate::ast::Span> {
        let idx = inst_index?;
        if idx < block.instructions.len() {
            block.instruction_with_span(idx).map(|sp| sp.span)
        } else if idx == block.instructions.len() {
            block.terminator_spanned().map(|sp| sp.span)
        } else {
            None
        }
    }

    fn apply_phi_nodes(
        &mut self,
        block: &BasicBlock,
        last_pred: Option<BasicBlockId>,
    ) -> Result<(), VMError> {
        let trace_phi = std::env::var("NYASH_VM_TRACE_PHI").ok().as_deref() == Some("1");
        if trace_phi {
            eprintln!(
                "[vm-trace-phi] enter bb={:?} last_pred={:?} preds={:?}",
                block.id, last_pred, block.predecessors
            );
        }
        for (idx, inst) in block.phi_instructions().enumerate() {
            self.last_block = Some(block.id);
            self.last_inst_index = Some(idx);
            self.last_inst = Some(inst.clone());
            if let MirInstruction::Phi { dst, inputs, .. } = inst {
                let dst_id = *dst;
                if trace_phi {
                    let in_preds: Vec<_> = inputs.iter().map(|(bb, _)| *bb).collect();
                    eprintln!(
                        "[vm-trace-phi] phi dst={:?} inputs.pred={:?}",
                        dst_id, in_preds
                    );
                }
                if let Some(pred) = last_pred {
                    if let Some((_, val)) = inputs.iter().find(|(bb, _)| *bb == pred) {
                        let v = match self.reg_load(*val) {
                            Ok(v) => v,
                            Err(e) => {
                                // Dev safety valve: tolerate undefined phi inputs by substituting Void
                                if std::env::var("NYASH_VM_PHI_TOLERATE_UNDEFINED")
                                    .ok()
                                    .as_deref()
                                    == Some("1")
                                {
                                    if Self::trace_enabled() {
                                        eprintln!("[vm-trace] phi tolerate undefined input {:?} -> Void (err={:?})", val, e);
                                    }
                                    VMValue::Void
                                } else {
                                    return Err(e);
                                }
                            }
                        };
                        self.regs.insert(dst_id, v);
                        if Self::trace_enabled() {
                            eprintln!(
                                "[vm-trace] phi dst={:?} take pred={:?} val={:?}",
                                dst_id, pred, val
                            );
                        }
                    } else {
                        // No matching predecessor in PHI inputs. Fallback policy:
                        //  - Prefer first input when available to avoid use-before-def crashes.
                        //  - Emit a trace note when tracing is enabled.
                        // Strict mode: fail-fast when enabled.
                        // Strict PHI (default ON). Can be disabled with HAKO_VM_PHI_STRICT=0 (or NYASH_VM_PHI_STRICT=0).
                        let strict = {
                            let on = |s: &str| {
                                matches!(
                                    s,
                                    "1" | "true" | "on" | "yes" | "y" | "t" | "enabled" | "enable"
                                )
                            };
                            let off = |s: &str| {
                                matches!(
                                    s,
                                    "0" | "false"
                                        | "off"
                                        | "no"
                                        | "n"
                                        | "f"
                                        | "disabled"
                                        | "disable"
                                )
                            };
                            if let Ok(v) = std::env::var("HAKO_VM_PHI_STRICT") {
                                let v = v.to_ascii_lowercase();
                                if off(&v) {
                                    false
                                } else if on(&v) {
                                    true
                                } else {
                                    true
                                }
                            } else if let Ok(v) = std::env::var("NYASH_VM_PHI_STRICT") {
                                let v = v.to_ascii_lowercase();
                                if off(&v) {
                                    false
                                } else if on(&v) {
                                    true
                                } else {
                                    true
                                }
                            } else {
                                true
                            }
                        };
                        if strict {
                            if trace_phi {
                                eprintln!(
                                    "[vm-trace-phi][strict] mismatch dst={:?} last_pred={:?} inputs={:?} preds_of_bb={:?}",
                                    dst_id,
                                    pred,
                                    inputs,
                                    block.predecessors
                                );
                            }
                            return Err(VMError::InvalidInstruction(format!(
                                "phi pred mismatch at {:?}: no input for predecessor {:?}",
                                dst_id, pred
                            )));
                        }
                        if let Some((_, val0)) = inputs.first() {
                            let v = match self.reg_load(*val0) {
                                Ok(v) => v,
                                Err(e) => {
                                    if std::env::var("NYASH_VM_PHI_TOLERATE_UNDEFINED")
                                        .ok()
                                        .as_deref()
                                        == Some("1")
                                    {
                                        if Self::trace_enabled() {
                                            eprintln!("[vm-trace] phi missing pred match {:?}; fallback first input {:?} -> Void (err={:?})", pred, val0, e);
                                        }
                                        VMValue::Void
                                    } else {
                                        return Err(e);
                                    }
                                }
                            };
                            self.regs.insert(dst_id, v);
                            if Self::trace_enabled() {
                                eprintln!(
                                    "[vm-trace] phi dst={:?} pred-miss fallback first val={:?}",
                                    dst_id, val0
                                );
                            }
                        } else {
                            // Empty inputs — assign Void (with optional trace) to avoid undefined dst
                            let v = VMValue::Void;
                            self.regs.insert(dst_id, v);
                            if Self::trace_enabled() {
                                eprintln!("[vm-trace] phi dst={:?} no inputs; assign Void", dst_id);
                            }
                        }
                    }
                } else if let Some((_, val)) = inputs.first() {
                    let v = match self.reg_load(*val) {
                        Ok(v) => v,
                        Err(e) => {
                            if std::env::var("NYASH_VM_PHI_TOLERATE_UNDEFINED")
                                .ok()
                                .as_deref()
                                == Some("1")
                            {
                                if Self::trace_enabled() {
                                    eprintln!("[vm-trace] phi tolerate undefined default input {:?} -> Void (err={:?})", val, e);
                                }
                                VMValue::Void
                            } else {
                                return Err(e);
                            }
                        }
                    };
                    self.regs.insert(dst_id, v);
                    if Self::trace_enabled() {
                        eprintln!("[vm-trace] phi dst={:?} take default val={:?}", dst_id, val);
                    }
                }
            }
        }
        Ok(())
    }

    fn execute_block_instructions(&mut self, block: &BasicBlock) -> Result<(), VMError> {
        let phi_count = block.phi_instructions().count();
        for (idx, sp) in block.iter_spanned_enumerated().skip(phi_count) {
            let inst = sp.inst;
            self.last_block = Some(block.id);
            self.last_inst_index = Some(idx);
            self.last_inst = Some(inst.clone());
            if Self::trace_enabled() {
                eprintln!("[vm-trace] inst bb={:?} {:?}", block.id, inst);
            }
            // Dev counters: count non-phi instructions and compares
            self.inst_count = self.inst_count.saturating_add(1);
            if let MirInstruction::Compare { .. } = inst {
                self.compare_count = self.compare_count.saturating_add(1);
            }
            self.execute_instruction(inst)?;
        }
        Ok(())
    }

    fn handle_terminator(&mut self, block: &BasicBlock) -> Result<BlockOutcome, VMError> {
        if let Some(term) = &block.terminator {
            self.last_block = Some(block.id);
            self.last_inst_index = Some(block.instructions.len());
            self.last_inst = Some(term.clone());
        }
        match &block.terminator {
            Some(MirInstruction::Return { value }) => {
                let result = if let Some(v) = value {
                    self.reg_load(*v)?
                } else {
                    VMValue::Void
                };
                Ok(BlockOutcome::Return(result))
            }
            Some(MirInstruction::Jump { target, .. }) => Ok(BlockOutcome::Next {
                target: *target,
                predecessor: block.id,
            }),
            Some(MirInstruction::Branch {
                condition,
                then_bb,
                else_bb,
                ..
            }) => {
                // Dev counter: count branch terminators actually evaluated
                self.branch_count = self.branch_count.saturating_add(1);
                let cond = self.reg_load(*condition)?;
                let branch = to_bool_vm(&cond).map_err(VMError::TypeError)?;
                let target = if branch { *then_bb } else { *else_bb };
                Ok(BlockOutcome::Next {
                    target,
                    predecessor: block.id,
                })
            }
            None => Err(VMError::InvalidBasicBlock(format!(
                "unterminated block {:?}",
                block.id
            ))),
            Some(other) => Err(VMError::InvalidInstruction(format!(
                "invalid terminator in MIR interp: {:?}",
                other
            ))),
        }
    }
}

enum BlockOutcome {
    Return(VMValue),
    Next {
        target: BasicBlockId,
        predecessor: BasicBlockId,
    },
}
