use super::ast::{ExprV0, ProgramV0, StmtV0};
use crate::mir::Callee;
use crate::mir::{
    BasicBlockId, ConstValue, EffectMask, FunctionSignature, MirFunction, MirInstruction,
    MirModule, MirPrinter, MirType, ValueId,
};
use std::cell::RefCell;
// Phase 25.1: BTreeMap → BTreeMap（決定性確保）
use std::collections::BTreeMap;

// Split out merge/new_block helpers for readability (no behavior change)
mod merge;
use merge::{merge_var_maps, new_block};
// Feature splits (gradual extraction)
pub(super) mod expr;
pub(super) mod globals;
pub(super) mod if_else;
pub(super) mod loop_;
pub(super) mod match_expr; // placeholder (not wired)
pub(super) mod ternary; // placeholder (not wired)
pub(super) mod throw_ctx;
pub(super) mod throw_lower;
pub(super) mod try_catch; // thread-local ctx for Result-mode throw routing

#[derive(Clone, Copy)]
pub(super) struct LoopContext {
    /// ループ条件を評価する header ブロック
    pub(super) cond_bb: BasicBlockId,
    /// break がジャンプする exit ブロック
    pub(super) exit_bb: BasicBlockId,
    /// canonical continue merge ブロック（存在する場合）
    /// - Some(continue_merge_bb): continue は一度ここに集約してから header へ戻る
    /// - None: 旧来どおり header へ直接 continue
    pub(super) continue_merge_bb: Option<BasicBlockId>,
}

// Snapshot stacks for loop break/continue (per-nested-loop frame)
thread_local! {
    static EXIT_SNAPSHOT_STACK: RefCell<Vec<Vec<(BasicBlockId, BTreeMap<String, ValueId>)>>> = RefCell::new(Vec::new());
    static CONT_SNAPSHOT_STACK: RefCell<Vec<Vec<(BasicBlockId, BTreeMap<String, ValueId>)>>> = RefCell::new(Vec::new());
    // Optional increment hint for current loop frame: (var_name, step)
    static INCR_HINT_STACK: RefCell<Vec<Option<(String, i64)>>> = RefCell::new(Vec::new());
}

pub(super) fn push_loop_snapshot_frames() {
    EXIT_SNAPSHOT_STACK.with(|s| s.borrow_mut().push(Vec::new()));
    CONT_SNAPSHOT_STACK.with(|s| s.borrow_mut().push(Vec::new()));
}

pub(super) fn pop_exit_snapshots() -> Vec<(BasicBlockId, BTreeMap<String, ValueId>)> {
    EXIT_SNAPSHOT_STACK.with(|s| s.borrow_mut().pop().unwrap_or_default())
}

pub(super) fn pop_continue_snapshots() -> Vec<(BasicBlockId, BTreeMap<String, ValueId>)> {
    CONT_SNAPSHOT_STACK.with(|s| s.borrow_mut().pop().unwrap_or_default())
}

fn record_exit_snapshot(cur_bb: BasicBlockId, vars: &BTreeMap<String, ValueId>) {
    EXIT_SNAPSHOT_STACK.with(|s| {
        if let Some(top) = s.borrow_mut().last_mut() {
            top.push((cur_bb, vars.clone()));
        }
    });
}

fn record_continue_snapshot(cur_bb: BasicBlockId, vars: &BTreeMap<String, ValueId>) {
    CONT_SNAPSHOT_STACK.with(|s| {
        if let Some(top) = s.borrow_mut().last_mut() {
            top.push((cur_bb, vars.clone()));
        }
    });
}

pub(super) fn detect_and_push_increment_hint(body: &[StmtV0]) {
    let mut hint: Option<(String, i64)> = None;
    for stmt in body.iter().rev() {
        if let StmtV0::Local { name, expr } = stmt.clone() {
            if let ExprV0::Binary { op, lhs, rhs } = expr {
                if let ExprV0::Var { name: vname } = *lhs {
                    if vname == name {
                        if let ExprV0::Int { value } = *rhs {
                            if let Some(v) = value.as_i64() {
                                let s = match op.as_str() {
                                    "+" => v,
                                    "-" => -v,
                                    _ => 0,
                                };
                                if s != 0 {
                                    hint = Some((name.clone(), s));
                                    break;
                                }
                            }
                        }
                    }
                }
            }
        }
    }
    INCR_HINT_STACK.with(|s| s.borrow_mut().push(hint));
}

pub(super) fn pop_increment_hint() -> Option<(String, i64)> {
    INCR_HINT_STACK.with(|s| s.borrow_mut().pop().unwrap_or(None))
}

fn peek_increment_hint() -> Option<(String, i64)> {
    INCR_HINT_STACK.with(|s| s.borrow().last().cloned().unwrap_or(None))
}

#[derive(Clone)]
pub(super) struct BridgeEnv {
    pub(super) throw_enabled: bool,
    // フェーズM.2: mir_no_phiフィールド削除（PHI統一で不要）
    pub(super) allow_me_dummy: bool,
    pub(super) me_class: String,
    pub(super) try_result_mode: bool,
    // Phase 21.8: using imports map (alias -> box_type)
    pub(super) imports: BTreeMap<String, String>,
}

impl BridgeEnv {
    #[allow(dead_code)]
    pub(super) fn load() -> Self {
        Self::with_imports(BTreeMap::new())
    }

    pub(super) fn with_imports(imports: BTreeMap<String, String>) -> Self {
        let trm = crate::config::env::try_result_mode();
        // フェーズM.2: no_phi変数削除
        if crate::config::env::cli_verbose() {
            eprintln!("[Bridge] load: try_result_mode={}", trm);
        }
        Self {
            throw_enabled: std::env::var("NYASH_BRIDGE_THROW_ENABLE").ok().as_deref() == Some("1"),
            // フェーズM.2: mir_no_phiフィールド削除
            allow_me_dummy: std::env::var("NYASH_BRIDGE_ME_DUMMY").ok().as_deref() == Some("1"),
            me_class: std::env::var("NYASH_BRIDGE_ME_CLASS").unwrap_or_else(|_| "Main".to_string()),
            try_result_mode: trm,
            imports,
        }
    }
}

/// Phase 25.1p: FunctionDefBuilder — 関数定義から MIR 関数への変換を箱化
/// SSOT for instance method detection and parameter ValueId assignment
struct FunctionDefBuilder {
    def: super::ast::FuncDefV0,
}

impl FunctionDefBuilder {
    fn new(def: super::ast::FuncDefV0) -> Self {
        Self { def }
    }

    /// インスタンスメソッド判定の SSOT
    /// - box_name が空でない（static box 内のメソッド）
    /// - かつ Main 以外、または明示的なインスタンスメソッド命名規則
    fn is_instance_method(&self) -> bool {
        // Phase 25.1m で確立した規則：
        // - static box 内のメソッドで、box_name が Main 以外 → インスタンスメソッド
        // - Main.main は特別扱い（エントリポイント）
        !self.def.box_name.is_empty() && self.def.box_name != "Main"
    }

    /// パラメータ ValueId の生成（インスタンスメソッドなら %0 を me 用に予約）
    fn build_param_ids(&self) -> Vec<ValueId> {
        let offset = if self.is_instance_method() {
            0 // %0 = me, params start from %1
        } else {
            1 // params start from %1 (no implicit receiver)
        };

        (0..self.def.params.len())
            .map(|i| ValueId::new((i + offset) as u32 + 1))
            .collect()
    }

    /// 変数マップの初期化（me を含む）
    fn build_var_map(&self, param_ids: &[ValueId]) -> BTreeMap<String, ValueId> {
        let mut map = BTreeMap::new();

        // インスタンスメソッドなら me を ValueId(0) に予約
        if self.is_instance_method() {
            map.insert("me".to_string(), ValueId::new(0));
        }

        // パラメータをマッピング
        for (i, param_name) in self.def.params.iter().enumerate() {
            map.insert(param_name.clone(), param_ids[i]);
        }

        map
    }

    /// 関数シグネチャの構築
    fn build_signature(&self) -> FunctionSignature {
        let func_name = format!(
            "{}.{}/{}",
            self.def.box_name,
            self.def.name,
            self.def.params.len()
        );

        let param_types: Vec<MirType> = (0..self.def.params.len())
            .map(|_| MirType::Unknown)
            .collect();

        FunctionSignature {
            name: func_name,
            params: param_types,
            return_type: MirType::Integer,
            effects: EffectMask::PURE,
        }
    }

    /// MirFunction への next_value_id 設定
    fn setup_next_value_id(&self, func: &mut MirFunction) {
        let base_id = if self.is_instance_method() {
            // me (%0) + params (%1..%N)
            self.def.params.len() as u32 + 1
        } else {
            // params (%1..%N)
            self.def.params.len() as u32 + 1
        };

        if func.next_value_id < base_id {
            func.next_value_id = base_id;
        }
    }
}

/// Small helper: set Jump terminator and record predecessor on the target.
fn jump_with_pred(f: &mut MirFunction, cur_bb: BasicBlockId, target: BasicBlockId) {
    // Delegate to SSOT CF helper for consistency
    crate::mir::ssot::cf_common::set_jump(f, cur_bb, target);
}

/// Strip Phi instructions by inserting edge copies on each predecessor.
/// This normalizes MIR to PHI-off form for downstream harnesses that synthesize PHIs.
// フェーズM.2: strip_phi_functions()削除 - PHI統一により不要

fn lower_break_stmt(f: &mut MirFunction, cur_bb: BasicBlockId, exit_bb: BasicBlockId) {
    jump_with_pred(f, cur_bb, exit_bb);
    // ARCHIVED: JIT events moved to archive/jit-cranelift/ during Phase 15
    // crate::jit::events::emit_lower(
    //     serde_json::json!({ "id": "loop_break","exit_bb": exit_bb.0,"decision": "lower" }),
    //     "loop",
    //     "<json_v0>",
    // );
}

fn lower_continue_stmt(f: &mut MirFunction, cur_bb: BasicBlockId, target_bb: BasicBlockId) {
    // target_bb は header か canonical continue_merge_bb のいずれか
    jump_with_pred(f, cur_bb, target_bb);
    // ARCHIVED: JIT events moved to archive/jit-cranelift/ during Phase 15
    // crate::jit::events::emit_lower(
    //     serde_json::json!({ "id": "loop_continue","cond_bb": cond_bb.0,"decision": "lower" }),
    //     "loop",
    //     "<json_v0>",
    // );
}

pub(super) fn lower_stmt_with_vars(
    f: &mut MirFunction,
    cur_bb: BasicBlockId,
    s: &StmtV0,
    vars: &mut BTreeMap<String, ValueId>,
    loop_stack: &mut Vec<LoopContext>,
    env: &BridgeEnv,
) -> Result<BasicBlockId, String> {
    match s {
        StmtV0::Return { expr } => {
            let (v, cur) = expr::lower_expr_with_vars(env, f, cur_bb, expr, vars)?;
            if let Some(bb) = f.get_block_mut(cur) {
                bb.set_terminator(MirInstruction::Return { value: Some(v) });
            }
            Ok(cur)
        }
        StmtV0::Extern {
            iface,
            method,
            args,
        } => {
            let (arg_ids, cur) = expr::lower_args_with_vars(env, f, cur_bb, args, vars)?;
            if let Some(bb) = f.get_block_mut(cur) {
                bb.add_instruction(MirInstruction::ExternCall {
                    dst: None,
                    iface_name: iface.clone(),
                    method_name: method.clone(),
                    args: arg_ids,
                    effects: EffectMask::IO,
                });
            }
            Ok(cur)
        }
        StmtV0::Expr { expr } => {
            let (_v, cur) = expr::lower_expr_with_vars(env, f, cur_bb, expr, vars)?;
            Ok(cur)
        }
        StmtV0::Local { name, expr } => {
            let (v, cur) = expr::lower_expr_with_vars(env, f, cur_bb, expr, vars)?;
            vars.insert(name.clone(), v);
            Ok(cur)
        }
        StmtV0::Break => {
            if let Some(ctx) = loop_stack.last().copied() {
                // snapshot variables at break
                record_exit_snapshot(cur_bb, vars);
                lower_break_stmt(f, cur_bb, ctx.exit_bb);
            }
            Ok(cur_bb)
        }
        StmtV0::Continue => {
            if let Some(ctx) = loop_stack.last().copied() {
                // Optional: apply increment hint before continue (so header sees updated var)
                if let Some((ref var_name, step)) = peek_increment_hint() {
                    let _ = crate::mir::ssot::loop_common::apply_increment_before_continue(
                        f, cur_bb, vars, var_name, step,
                    );
                }
                // snapshot variables at continue (after increment)
                record_continue_snapshot(cur_bb, vars);
                let target = ctx.continue_merge_bb.unwrap_or(ctx.cond_bb);
                lower_continue_stmt(f, cur_bb, target);
            }
            Ok(cur_bb)
        }
        StmtV0::Try {
            try_body,
            catches,
            finally,
        } => {
            try_catch::lower_try_stmt(f, cur_bb, try_body, catches, finally, vars, loop_stack, env)
        }
        StmtV0::If { cond, then, r#else } => {
            if_else::lower_if_stmt(f, cur_bb, cond, then, r#else, vars, loop_stack, env)
        }
        StmtV0::Loop { cond, body } => {
            loop_::lower_loop_stmt(f, cur_bb, cond, body, vars, loop_stack, env)
        }
    }
}

pub(super) fn lower_stmt_list_with_vars(
    f: &mut MirFunction,
    start_bb: BasicBlockId,
    stmts: &[StmtV0],
    vars: &mut BTreeMap<String, ValueId>,
    loop_stack: &mut Vec<LoopContext>,
    env: &BridgeEnv,
) -> Result<BasicBlockId, String> {
    let mut cur = start_bb;
    for s in stmts {
        cur = lower_stmt_with_vars(f, cur, s, vars, loop_stack, env)?;
        if let Some(bb) = f.blocks.get(&cur) {
            if bb.is_terminated() {
                break;
            }
        }
    }
    Ok(cur)
}

pub(super) fn lower_program(
    prog: ProgramV0,
    imports: std::collections::BTreeMap<String, String>,
) -> Result<MirModule, String> {
    if prog.body.is_empty() {
        return Err("empty body".into());
    }
    let env = BridgeEnv::with_imports(imports);
    let mut module = MirModule::new("ny_json_v0".into());
    // Treat CLI entry as taking a single parameter `args`.
    let sig = FunctionSignature {
        name: "main".into(),
        params: vec![MirType::Unknown],
        return_type: MirType::Integer,
        effects: EffectMask::PURE,
    };
    let entry = BasicBlockId::new(0);
    let mut f = MirFunction::new(sig, entry);
    let mut var_map: BTreeMap<String, ValueId> = BTreeMap::new();
    // Stage-3 programs (launcher / CLI entry) implicitly reference `args`.
    let args_param = ValueId::new(1);
    f.params = vec![args_param];
    if f.next_value_id < 2 {
        f.next_value_id = 2;
    }
    var_map.insert("args".into(), args_param);
    let mut loop_stack: Vec<LoopContext> = Vec::new();
    let start_bb = f.entry_block;
    let end_bb = lower_stmt_list_with_vars(
        &mut f,
        start_bb,
        &prog.body,
        &mut var_map,
        &mut loop_stack,
        &env,
    )?;
    let need_default_ret = f.blocks.iter().any(|(_k, b)| !b.is_terminated());
    if need_default_ret {
        let target_bb = end_bb;
        let dst_id = f.next_value_id();
        if let Some(bb) = f.get_block_mut(target_bb) {
            if !bb.is_terminated() {
                bb.add_instruction(MirInstruction::Const {
                    dst: dst_id,
                    value: ConstValue::Integer(0),
                });
                bb.set_terminator(MirInstruction::Return {
                    value: Some(dst_id),
                });
            }
        }
    }
    f.signature.return_type = MirType::Unknown;
    // フェーズM.2: PHI後処理削除 - MirBuilder/LoopBuilderでPHI統一済み
    module.add_function(f);

    // Phase 21.6: Process function definitions (defs)
    // Phase 25.1p: FunctionDefBuilder による箱化・SSOT化
    // Toggle: HAKO_STAGEB_FUNC_SCAN=1 + HAKO_MIR_BUILDER_FUNCS=1
    let mut func_map: BTreeMap<String, String> = BTreeMap::new();
    if !prog.defs.is_empty() {
        for func_def in prog.defs {
            // Phase 25.1p: FunctionDefBuilder で SSOT 化
            let builder = FunctionDefBuilder::new(func_def.clone());

            let func_name = format!(
                "{}.{}/{}",
                func_def.box_name,
                func_def.name,
                func_def.params.len()
            );

            if std::env::var("HAKO_MIR_BUILDER_DEBUG").ok().as_deref() == Some("1") {
                eprintln!(
                    "[lowering/defs] define {} (params={}, is_instance={})",
                    func_name,
                    func_def.params.len(),
                    builder.is_instance_method()
                );
            }

            // Register function in map for Call resolution
            func_map.insert(func_def.name.clone(), func_name);

            // Build signature and function
            let sig = builder.build_signature();
            let entry = BasicBlockId::new(0);
            let mut func = MirFunction::new(sig, entry);

            // Build parameter ValueIds and variable map (SSOT)
            let param_ids = builder.build_param_ids();
            func.params = param_ids.clone();
            builder.setup_next_value_id(&mut func);

            let mut func_var_map = builder.build_var_map(&param_ids);

            // Lower function body
            let mut loop_stack: Vec<LoopContext> = Vec::new();
            let start_bb = func.entry_block;
            let _end_bb = lower_stmt_list_with_vars(
                &mut func,
                start_bb,
                &func_def.body.body,
                &mut func_var_map,
                &mut loop_stack,
                &env,
            )?;

            func.signature.return_type = MirType::Unknown;
            module.add_function(func);
        }
    }

    // Phase 21.6: Call resolution post-processing
    // Toggle: HAKO_MIR_BUILDER_CALL_RESOLVE=1
    // Resolve Call instructions to use qualified function names (e.g., "add" -> "Main.add")
    if std::env::var("HAKO_MIR_BUILDER_CALL_RESOLVE")
        .ok()
        .as_deref()
        == Some("1")
    {
        if !func_map.is_empty() {
            for (_func_idx, func) in module.functions.iter_mut() {
                for (_block_id, block) in func.blocks.iter_mut() {
                    let mut const_replacements: Vec<(ValueId, String)> = Vec::new();

                    // Find Call instructions and their associated Const values
                    for inst in &block.instructions {
                        if let MirInstruction::Call {
                            func: func_reg,
                            args,
                            ..
                        } = inst
                        {
                            // Look for the Const instruction that defines func_reg
                            for const_inst in &block.instructions {
                                if let MirInstruction::Const { dst, value } = const_inst {
                                    if dst == func_reg {
                                        if let ConstValue::String(name) = value {
                                            // Try to resolve the name
                                            if let Some(resolved) = func_map.get(name) {
                                                let mut new_name = resolved.clone();
                                                // Avoid double suffix if already contains '/N'
                                                if !resolved
                                                    .rsplit('/')
                                                    .next()
                                                    .unwrap_or("")
                                                    .chars()
                                                    .all(|c| c.is_ascii_digit())
                                                    || !resolved.contains('/')
                                                {
                                                    new_name = format!(
                                                        "{}{}",
                                                        resolved.clone(),
                                                        format!("/{}", args.len())
                                                    );
                                                }
                                                const_replacements.push((*dst, new_name));
                                                if std::env::var("HAKO_MIR_BUILDER_DEBUG")
                                                    .ok()
                                                    .as_deref()
                                                    == Some("1")
                                                {
                                                    eprintln!(
                                                        "[mirbuilder/call:resolve] {} => {}",
                                                        name, resolved
                                                    );
                                                }
                                            }
                                        }
                                    }
                                }
                            }
                        }
                    }

                    // Apply replacements
                    for (dst, new_name) in const_replacements.iter() {
                        for inst in &mut block.instructions {
                            if let MirInstruction::Const { dst: d, value } = inst {
                                if d == dst {
                                    *value = ConstValue::String(new_name.clone());
                                }
                            }
                        }
                    }
                    // Build a map reg -> name after replacements
                    let mut reg_name: std::collections::BTreeMap<ValueId, String> =
                        std::collections::BTreeMap::new();
                    for inst in &block.instructions {
                        if let MirInstruction::Const { dst, value } = inst {
                            if let ConstValue::String(s) = value {
                                reg_name.insert(*dst, s.clone());
                            }
                        }
                    }
                    // Upgrade legacy calls to Global callee when name is known
                    for inst in &mut block.instructions {
                        if let MirInstruction::Call {
                            func: func_reg,
                            callee,
                            ..
                        } = inst
                        {
                            if let Some(name) = reg_name.get(func_reg).cloned() {
                                *callee = Some(Callee::Global(name));
                            }
                        }
                    }
                }
            }
        }
    }

    Ok(module)
}

pub(super) fn maybe_dump_mir(module: &MirModule) {
    // New: file dump path for offline analysis (Stage‑1/Stage‑B selfhost, ParserBox 等)
    // Use env RUST_MIR_DUMP_PATH to write the MIR printer output to a file.
    if let Some(path) = crate::config::env::rust_mir_dump_path() {
        if let Ok(mut f) = std::fs::File::create(&path) {
            let p = MirPrinter::new();
            let _ = std::io::Write::write_all(&mut f, p.print_module(module).as_bytes());
        }
    }
    // Existing: verbose flag dumps to stdout
    if crate::config::env::cli_verbose() {
        let p = MirPrinter::new();
        println!("{}", p.print_module(module));
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use std::fs;
    use std::path::PathBuf;
    use std::sync::{Mutex, OnceLock};

    fn temp_path(name: &str) -> PathBuf {
        let mut p = std::env::temp_dir();
        p.push(format!("{}_{}", name, std::process::id()));
        p
    }

    fn env_guard() -> &'static Mutex<()> {
        static GUARD: OnceLock<Mutex<()>> = OnceLock::new();
        GUARD.get_or_init(|| Mutex::new(()))
    }

    fn dummy_module() -> MirModule {
        MirModule::new("test-module".to_string())
    }

    #[test]
    fn writes_file_when_dump_path_is_set() {
        let _lock = env_guard().lock().unwrap();
        let path = temp_path("mir_dump_path");
        let _ = fs::remove_file(&path);
        std::env::set_var("RUST_MIR_DUMP_PATH", path.to_string_lossy().to_string());

        maybe_dump_mir(&dummy_module());

        assert!(
            path.exists(),
            "maybe_dump_mir should write when RUST_MIR_DUMP_PATH is set"
        );
        let contents = fs::read_to_string(&path).unwrap_or_default();
        assert!(
            contents.contains("test-module"),
            "dump should contain module name"
        );

        let _ = fs::remove_file(&path);
        std::env::remove_var("RUST_MIR_DUMP_PATH");
    }

    #[test]
    fn does_not_write_when_dump_path_is_unset() {
        let _lock = env_guard().lock().unwrap();
        std::env::remove_var("RUST_MIR_DUMP_PATH");
        let path = temp_path("mir_dump_path_unset");
        let _ = fs::remove_file(&path);

        maybe_dump_mir(&dummy_module());

        assert!(
            !path.exists(),
            "maybe_dump_mir should not write when RUST_MIR_DUMP_PATH is unset"
        );
    }
}
