//! Phase 190: JoinIR Block Converter
//!
//! 責務: JoinIR のブロックを MIR ブロックに変換
//! - 命令列変換
//! - ターミネータ（Jump/Branch/Return）の処理
//! - ブロックID マッピング管理

use crate::ast::Span;
use crate::mir::join_ir::{JoinFuncId, JoinInst, MirLikeInst};
use crate::mir::join_ir::lowering::inline_boundary::JumpArgsLayout;
use crate::mir::{BasicBlockId, EffectMask, MirFunction, MirInstruction, MirType, ValueId};
use crate::mir::types::ConstValue;
use std::collections::BTreeMap;

use super::block_allocator::BlockAllocator; // Phase 269 P1.2+: Use BlockAllocator for deduplication
use super::call_generator::{emit_call_pair, emit_call_pair_with_spans}; // Phase 269 P1.2+: Call instruction deduplication
use super::merge_variable_handler::{emit_merge_copies, MergeBranch}; // Phase 269 P1.2+: Merge copy deduplication
use super::{convert_mir_like_inst, join_func_name, JoinIrVmBridgeError};

fn log_dbg(message: impl AsRef<str>) {
    if crate::config::env::joinir_test_debug_enabled() {
        eprintln!("{}", message.as_ref());
    }
}

pub struct JoinIrBlockConverter {
    current_block_id: BasicBlockId,
    current_instructions: Vec<MirInstruction>,
    next_block_id: u32,
    /// Phase 256 P1.8: Map from JoinFuncId to actual function name
    /// When set, handle_call uses this instead of join_func_name()
    func_name_map: Option<BTreeMap<JoinFuncId, String>>,
}

impl JoinIrBlockConverter {
    pub fn new() -> Self {
        Self {
            current_block_id: BasicBlockId(0), // entry block
            current_instructions: Vec::new(),
            next_block_id: 1, // start from 1 (0 is entry)
            func_name_map: None,
        }
    }

    /// Phase 256 P1.8: Create converter with function name map
    /// This ensures Call instructions use actual function names instead of "join_func_N"
    pub fn new_with_func_names(func_name_map: BTreeMap<JoinFuncId, String>) -> Self {
        Self {
            current_block_id: BasicBlockId(0),
            current_instructions: Vec::new(),
            next_block_id: 1,
            func_name_map: Some(func_name_map),
        }
    }

    /// JoinIR 関数本体を MIR ブロック群に変換
    ///
    /// # Phase 27-shortterm S-4.4: Multi-block conversion
    ///
    /// Strategy:
    /// - Accumulate Compute instructions in current block
    /// - On Jump: emit Branch + create exit block with Return
    /// - On Call: emit Call in current block
    pub fn convert_function_body(
        &mut self,
        mir_func: &mut MirFunction,
        join_body: &[JoinInst],
    ) -> Result<(), JoinIrVmBridgeError> {
        for join_inst in join_body {
            match join_inst {
                JoinInst::Compute(mir_like) => {
                    // Phase 189: Special handling for MirLikeInst::Select
                    // Pattern 3 uses JoinInst::Compute(MirLikeInst::Select {...})
                    // but Select needs control flow expansion (Branch + Phi), not single instruction
                    if let MirLikeInst::Select {
                        dst,
                        cond,
                        then_val,
                        else_val,
                    } = mir_like
                    {
                        log_dbg(format!(
                            "[joinir_block] ✅ Found Select! dst={:?}, calling handle_select",
                            dst
                        ));
                        self.handle_select(mir_func, dst, cond, then_val, else_val, &None)?;
                        continue;
                    }
                    // Debug: show what instruction we're processing
                    log_dbg(format!(
                        "[joinir_block] Compute instruction: {:?}",
                        mir_like
                    ));
                    let mir_inst = convert_mir_like_inst(mir_like)?;
                    Self::annotate_value_types_for_inst(mir_func, &mir_inst);
                    self.current_instructions.push(mir_inst);
                }
                JoinInst::MethodCall {
                    dst,
                    receiver,
                    method,
                    args,
                    type_hint,
                } => {
                    self.handle_method_call(dst, receiver, method, args, type_hint)?;
                }
                JoinInst::ConditionalMethodCall {
                    cond,
                    dst,
                    receiver,
                    method,
                    args,
                } => {
                    log_dbg(format!(
                        "[joinir_block] Converting ConditionalMethodCall: dst={:?}, cond={:?}",
                        dst, cond
                    ));
                    self.handle_conditional_method_call(
                        mir_func, cond, dst, receiver, method, args,
                    )?;
                }
                JoinInst::FieldAccess { dst, object, field } => {
                    self.handle_field_access(dst, object, field)?;
                }
                JoinInst::NewBox {
                    dst,
                    box_name,
                    args,
                    type_hint,
                } => {
                    self.handle_new_box(dst, box_name, args, type_hint)?;
                }
                JoinInst::Call {
                    func,
                    args,
                    dst,
                    k_next,
                } => {
                    self.handle_call(mir_func, func, args, dst, k_next)?;
                }
                JoinInst::Jump { cont, args, cond } => {
                    self.handle_jump(mir_func, cont, args, cond)?;
                }
                JoinInst::Select {
                    dst,
                    cond,
                    then_val,
                    else_val,
                    type_hint,
                } => {
                    self.handle_select(mir_func, dst, cond, then_val, else_val, type_hint)?;
                }
                JoinInst::IfMerge {
                    cond,
                    merges,
                    k_next,
                } => {
                    self.handle_if_merge(mir_func, cond, merges, k_next)?;
                }
                JoinInst::Ret { value } => {
                    self.handle_ret(mir_func, value)?;
                }
                JoinInst::NestedIfMerge {
                    conds,
                    merges,
                    k_next,
                } => {
                    self.handle_nested_if_merge(mir_func, conds, merges, k_next)?;
                }
            }
        }

        // Finalize any remaining instructions
        self.finalize_remaining_instructions(mir_func);

        Ok(())
    }

    fn annotate_value_types_for_inst(mir_func: &mut MirFunction, inst: &MirInstruction) {
        match inst {
            MirInstruction::Const { dst, value } => {
                let ty = match value {
                    ConstValue::Integer(_) => Some(MirType::Integer),
                    ConstValue::Float(_) => Some(MirType::Float),
                    ConstValue::Bool(_) => Some(MirType::Bool),
                    ConstValue::String(_) => Some(MirType::String),
                    ConstValue::Void => Some(MirType::Void),
                    _ => None,
                };
                if let Some(ty) = ty {
                    mir_func.metadata.value_types.insert(*dst, ty);
                }
            }
            MirInstruction::BinOp { dst, lhs, rhs, .. } => {
                // Conservative typing for numeric operations.
                //
                // This avoids forcing numeric semantics for `+` in cases where the operands are not
                // known to be numeric (e.g. string concatenation patterns).
                let lhs_ty = mir_func.metadata.value_types.get(lhs);
                let rhs_ty = mir_func.metadata.value_types.get(rhs);

                // Phase 275 P0: Int + Float promotion
                // If either operand is Float, result is Float
                if matches!(lhs_ty, Some(MirType::Float)) || matches!(rhs_ty, Some(MirType::Float)) {
                    mir_func.metadata.value_types.insert(*dst, MirType::Float);
                } else if matches!(lhs_ty, Some(MirType::Integer))
                    && matches!(rhs_ty, Some(MirType::Integer))
                {
                    mir_func.metadata.value_types.insert(*dst, MirType::Integer);
                }
            }
            MirInstruction::Compare { dst, .. } => {
                mir_func.metadata.value_types.insert(*dst, MirType::Bool);
            }
            MirInstruction::TypeCheck { dst, .. } => {
                mir_func.metadata.value_types.insert(*dst, MirType::Bool);
            }
            MirInstruction::Phi {
                dst,
                type_hint,
                inputs,
            } => {
                if let Some(ty) = type_hint.clone() {
                    mir_func.metadata.value_types.insert(*dst, ty);
                } else {
                    // Phase 275 P0: Infer PHI type from incoming values
                    // If all incoming values have the same type, use that type
                    let mut inferred_type: Option<MirType> = None;
                    for (_, incoming_vid) in inputs.iter() {
                        if let Some(incoming_ty) = mir_func.metadata.value_types.get(incoming_vid) {
                            match &inferred_type {
                                None => {
                                    inferred_type = Some(incoming_ty.clone());
                                }
                                Some(existing) => {
                                    // If types don't match, we can't infer
                                    if existing != incoming_ty {
                                        inferred_type = None;
                                        break;
                                    }
                                }
                            }
                        }
                    }
                    if let Some(ty) = inferred_type {
                        mir_func.metadata.value_types.insert(*dst, ty);
                    }
                }
            }
            _ => {}
        }
    }

    // === Instruction Handlers ===

    fn handle_method_call(
        &mut self,
        dst: &ValueId,
        receiver: &ValueId,
        method: &str,
        args: &[ValueId],
        type_hint: &Option<crate::mir::MirType>,
    ) -> Result<(), JoinIrVmBridgeError> {
        let mir_inst = MirInstruction::BoxCall {
            dst: Some(*dst),
            box_val: *receiver,
            method: method.to_string(),
            method_id: None,
            args: args.to_vec(),
            effects: EffectMask::PURE,
        };
        self.current_instructions.push(mir_inst);

        // Phase 65-2-A: TODO: type_hint を value_types に記録
        let _ = type_hint;
        Ok(())
    }

    fn handle_conditional_method_call(
        &mut self,
        mir_func: &mut MirFunction,
        cond: &ValueId,
        dst: &ValueId,
        receiver: &ValueId,
        method: &str,
        args: &[ValueId],
    ) -> Result<(), JoinIrVmBridgeError> {
        // Phase 56: ConditionalMethodCall を if/phi に変換
        debug_log!(
            "[joinir_block] Converting ConditionalMethodCall: dst={:?}, cond={:?}",
            dst,
            cond
        );

        let cond_block = self.current_block_id;
        // Phase 269 P1.2+: Use BlockAllocator to eliminate duplication (Site 1/4)
        let mut allocator = BlockAllocator::new(self.next_block_id);
        let (then_block, else_block, merge_block) = allocator.allocate_three();
        self.next_block_id = allocator.peek_next();

        // cond block: branch
        let branch_terminator = MirInstruction::Branch {
            condition: *cond,
            then_bb: then_block,
            else_bb: else_block,
            then_edge_args: None,
            else_edge_args: None,
        };
        Self::finalize_block(
            mir_func,
            cond_block,
            std::mem::take(&mut self.current_instructions),
            branch_terminator,
        );

        let then_value = mir_func.next_value_id();
        let else_value = mir_func.next_value_id();

        // then block: method call
        let mut then_block_obj = crate::mir::BasicBlock::new(then_block);
        then_block_obj.instructions.push(MirInstruction::BoxCall {
            dst: Some(then_value),
            box_val: *receiver,
            method: method.to_string(),
            method_id: None,
            args: args.to_vec(),
            effects: EffectMask::WRITE,
        });
        then_block_obj.instruction_spans.push(Span::unknown());
        then_block_obj.set_terminator(MirInstruction::Jump {
            target: merge_block,
            edge_args: None,
        });
        mir_func.blocks.insert(then_block, then_block_obj);

        // else block: copy receiver
        let mut else_block_obj = crate::mir::BasicBlock::new(else_block);
        else_block_obj.instructions.push(MirInstruction::Copy {
            dst: else_value,
            src: *receiver,
        });
        else_block_obj.instruction_spans.push(Span::unknown());
        else_block_obj.set_terminator(MirInstruction::Jump {
            target: merge_block,
            edge_args: None,
        });
        mir_func.blocks.insert(else_block, else_block_obj);

        // merge block: phi for dst
        let mut merge_block_obj = crate::mir::BasicBlock::new(merge_block);
        merge_block_obj.instructions.push(MirInstruction::Phi {
            dst: *dst,
            inputs: vec![(then_block, then_value), (else_block, else_value)],
            type_hint: None,
        });
        merge_block_obj.instruction_spans.push(Span::unknown());
        mir_func.blocks.insert(merge_block, merge_block_obj);

        self.current_block_id = merge_block;
        Ok(())
    }

    fn handle_field_access(
        &mut self,
        dst: &ValueId,
        object: &ValueId,
        field: &str,
    ) -> Result<(), JoinIrVmBridgeError> {
        // Phase 51: FieldAccess → BoxCall (getter pattern)
        let mir_inst = MirInstruction::BoxCall {
            dst: Some(*dst),
            box_val: *object,
            method: field.to_string(),
            method_id: None,
            args: vec![],
            effects: EffectMask::PURE,
        };
        self.current_instructions.push(mir_inst);
        Ok(())
    }

    fn handle_new_box(
        &mut self,
        dst: &ValueId,
        box_name: &str,
        args: &[ValueId],
        type_hint: &Option<crate::mir::MirType>,
    ) -> Result<(), JoinIrVmBridgeError> {
        let mir_inst = MirInstruction::NewBox {
            dst: *dst,
            box_type: box_name.to_string(),
            args: args.to_vec(),
        };
        self.current_instructions.push(mir_inst);

        // Phase 65-2-B: TODO: type_hint を value_types に記録
        let _ = type_hint;
        Ok(())
    }

    fn handle_call(
        &mut self,
        mir_func: &mut MirFunction,
        func: &crate::mir::join_ir::JoinFuncId,
        args: &[ValueId],
        dst: &Option<ValueId>,
        k_next: &Option<crate::mir::join_ir::JoinContId>,
    ) -> Result<(), JoinIrVmBridgeError> {
        // Phase 30.x: Call conversion
        if k_next.is_some() {
            return Err(JoinIrVmBridgeError::new(
                "Call with k_next is not yet supported".to_string(),
            ));
        }

        // Phase 256 P1.8: Use actual function name if available
        let func_name = if let Some(ref map) = self.func_name_map {
            map.get(func).cloned().unwrap_or_else(|| join_func_name(*func))
        } else {
            join_func_name(*func)
        };

        // Phase 131 P2: Stable function name ValueId (module-global SSOT)
        //
        // The merge pipeline relies on `Const(String("join_func_N"))` to detect tail calls.
        // The ValueId used for that const MUST be stable across *all* functions in the module.
        //
        // IMPORTANT: avoid collisions with `call_result_id = ValueId(99991)`.
        const FUNC_NAME_ID_BASE: u32 = 90000;
        let func_name_id = ValueId(FUNC_NAME_ID_BASE + func.0);
        if func_name_id == ValueId(99991) {
            return Err(JoinIrVmBridgeError::new(
                "[joinir_block] func_name_id collided with call_result_id (99991)".to_string(),
            ));
        }

        match dst {
            Some(result_dst) => {
                // Non-tail call
                emit_call_pair(
                    &mut self.current_instructions,
                    func_name_id,
                    *result_dst,
                    &func_name,
                    args,
                );
            }
            None => {
                // Tail call
                let call_result_id = ValueId(99991);
                // Phase 188.3 P2: Use emit_call_pair with callee field
                emit_call_pair(
                    &mut self.current_instructions,
                    func_name_id,
                    call_result_id,
                    &func_name,
                    args,
                );

                // Phase 131 P2: Preserve tail-call args as legacy jump-args metadata (for exit wiring)
                //
                // The merge pipeline recovers carrier/env values from the legacy jump-args path
                // (via BasicBlock APIs) when converting Return → Jump to the exit block.
                // Without this, tail-call blocks look like "no args", forcing fallbacks that can
                // produce undefined ValueIds in DirectValue mode.
                let terminator = MirInstruction::Return {
                    value: Some(call_result_id),
                };
                Self::finalize_block(
                    mir_func,
                    self.current_block_id,
                    std::mem::take(&mut self.current_instructions),
                    terminator,
                );
                if let Some(block) = mir_func.blocks.get_mut(&self.current_block_id) {
                    if matches!(block.terminator, Some(MirInstruction::Return { .. })) && block.return_env().is_none() {
                        block.set_return_env(crate::mir::EdgeArgs {
                            layout: JumpArgsLayout::CarriersOnly,
                            values: args.to_vec(),
                        });
                    }
                }
            }
        }
        Ok(())
    }

    fn handle_jump(
        &mut self,
        mir_func: &mut MirFunction,
        cont: &crate::mir::join_ir::JoinContId,
        args: &[ValueId],
        cond: &Option<ValueId>,
    ) -> Result<(), JoinIrVmBridgeError> {
        // Phase 256 P1.9: Jump → tail call to continuation function
        // Previously was just `ret args[0]`, now generates `call cont(args...); ret result`
        debug_log!(
            "[joinir_block] Converting Jump to tail call: cont={:?}, args={:?}, cond={:?}",
            cont,
            args,
            cond
        );

        // Get continuation function name
        let cont_name = self.get_continuation_name(cont);

        // Phase 256 P1.9: Use distinct ValueIds for Jump tail call
        // FUNC_NAME_ID_BASE for call targets, 99992 for Jump result (distinct from 99991 in handle_call)
        const JUMP_FUNC_NAME_ID_BASE: u32 = 91000;  // Different from handle_call's 90000
        let func_name_id = ValueId(JUMP_FUNC_NAME_ID_BASE + cont.0);
        let call_result_id = ValueId(99992);  // Distinct from handle_call's 99991

        match cond {
            Some(cond_var) => {
                // Conditional jump → Branch + tail call to continuation
                // Phase 269 P1.2+: Use BlockAllocator (Site 2/4)
                let mut allocator = BlockAllocator::new(self.next_block_id);
                let (exit_block_id, continue_block_id) = allocator.allocate_two();
                self.next_block_id = allocator.peek_next();

                let branch_terminator = MirInstruction::Branch {
                    condition: *cond_var,
                    then_bb: exit_block_id,
                    else_bb: continue_block_id,
                    then_edge_args: None,
                    else_edge_args: None,
                };

                Self::finalize_block(
                    mir_func,
                    self.current_block_id,
                    std::mem::take(&mut self.current_instructions),
                    branch_terminator,
                );

                // Exit block: tail call to continuation function
                let mut exit_block = crate::mir::BasicBlock::new(exit_block_id);

                // Phase 256 P1.9: Generate tail call to continuation
                emit_call_pair_with_spans(
                    &mut exit_block.instructions,
                    &mut exit_block.instruction_spans,
                    func_name_id,
                    call_result_id,
                    &cont_name,
                    args,
                );
                exit_block.set_terminator(MirInstruction::Return { value: Some(call_result_id) });
                exit_block.set_return_env(crate::mir::EdgeArgs {
                    layout: JumpArgsLayout::CarriersOnly,
                    values: args.to_vec(),
                });
                mir_func.blocks.insert(exit_block_id, exit_block);

                // Continue block
                let continue_block = crate::mir::BasicBlock::new(continue_block_id);
                mir_func.blocks.insert(continue_block_id, continue_block);

                self.current_block_id = continue_block_id;
            }
            None => {
                // Unconditional jump → tail call to continuation
                // Finalize current block with tail call
                emit_call_pair(
                    &mut self.current_instructions,
                    func_name_id,
                    call_result_id,
                    &cont_name,
                    args,
                );

                let return_terminator = MirInstruction::Return { value: Some(call_result_id) };

                Self::finalize_block(
                    mir_func,
                    self.current_block_id,
                    std::mem::take(&mut self.current_instructions),
                    return_terminator,
                );
                if let Some(block) = mir_func.blocks.get_mut(&self.current_block_id) {
                    if matches!(block.terminator, Some(MirInstruction::Return { .. })) && block.return_env().is_none() {
                        block.set_return_env(crate::mir::EdgeArgs {
                            layout: JumpArgsLayout::CarriersOnly,
                            values: args.to_vec(),
                        });
                    }
                }
            }
        }
        Ok(())
    }

    /// Phase 256 P1.9: Get continuation function name from func_name_map
    fn get_continuation_name(&self, cont: &crate::mir::join_ir::JoinContId) -> String {
        // JoinContId.0 == JoinFuncId.0 (same underlying ID via as_cont())
        if let Some(ref map) = self.func_name_map {
            if let Some(name) = map.get(&JoinFuncId(cont.0)) {
                return name.clone();
            }
        }
        // Fallback: use join_func_name()
        join_func_name(JoinFuncId(cont.0))
    }

    fn handle_select(
        &mut self,
        _mir_func: &mut MirFunction,
        dst: &ValueId,
        cond: &ValueId,
        then_val: &ValueId,
        else_val: &ValueId,
        _type_hint: &Option<crate::mir::MirType>,
    ) -> Result<(), JoinIrVmBridgeError> {
        // Phase 256 P1.5: Select → MirInstruction::Select (direct instruction, not control flow expansion)
        debug_log!(
            "[joinir_block] Converting Select: dst={:?}, cond={:?}, then_val={:?}, else_val={:?}",
            dst,
            cond,
            then_val,
            else_val
        );

        // Emit Select instruction directly (no branch/phi expansion)
        let select_inst = MirInstruction::Select {
            dst: *dst,
            cond: *cond,
            then_val: *then_val,
            else_val: *else_val,
        };

        self.current_instructions.push(select_inst);
        Ok(())
    }

    fn handle_if_merge(
        &mut self,
        mir_func: &mut MirFunction,
        cond: &ValueId,
        merges: &[crate::mir::join_ir::MergePair],
        k_next: &Option<crate::mir::join_ir::JoinContId>,
    ) -> Result<(), JoinIrVmBridgeError> {
        // Phase 33-6: IfMerge → if/phi (multiple variables)
        if k_next.is_some() {
            return Err(JoinIrVmBridgeError::new(
                "IfMerge: k_next not yet supported".to_string(),
            ));
        }

        debug_log!(
            "[joinir_block] Converting IfMerge: merges.len()={}",
            merges.len()
        );

        let cond_block = self.current_block_id;
        // Phase 269 P1.2+: Use BlockAllocator (Site 3/4)
        let mut allocator = BlockAllocator::new(self.next_block_id);
        let (then_block, else_block, merge_block) = allocator.allocate_three();
        self.next_block_id = allocator.peek_next();

        // cond block: branch
        let branch_terminator = MirInstruction::Branch {
            condition: *cond,
            then_bb: then_block,
            else_bb: else_block,
            then_edge_args: None,
            else_edge_args: None,
        };
        Self::finalize_block(
            mir_func,
            cond_block,
            std::mem::take(&mut self.current_instructions),
            branch_terminator,
        );

        // then block: copy then_val
        let mut then_block_obj = crate::mir::BasicBlock::new(then_block);
        emit_merge_copies(&mut then_block_obj, merges, MergeBranch::Then);
        then_block_obj.set_terminator(MirInstruction::Jump {
            target: merge_block,
            edge_args: None,
        });
        mir_func.blocks.insert(then_block, then_block_obj);

        // else block: copy else_val
        let mut else_block_obj = crate::mir::BasicBlock::new(else_block);
        emit_merge_copies(&mut else_block_obj, merges, MergeBranch::Else);
        else_block_obj.set_terminator(MirInstruction::Jump {
            target: merge_block,
            edge_args: None,
        });
        mir_func.blocks.insert(else_block, else_block_obj);

        // merge block
        let merge_block_obj = crate::mir::BasicBlock::new(merge_block);
        mir_func.blocks.insert(merge_block, merge_block_obj);

        self.current_block_id = merge_block;
        Ok(())
    }

    fn handle_ret(
        &mut self,
        mir_func: &mut MirFunction,
        value: &Option<ValueId>,
    ) -> Result<(), JoinIrVmBridgeError> {
        let return_terminator = MirInstruction::Return { value: *value };
        Self::finalize_block(
            mir_func,
            self.current_block_id,
            std::mem::take(&mut self.current_instructions),
            return_terminator,
        );
        Ok(())
    }

    fn handle_nested_if_merge(
        &mut self,
        mir_func: &mut MirFunction,
        conds: &[ValueId],
        merges: &[crate::mir::join_ir::MergePair],
        k_next: &Option<crate::mir::join_ir::JoinContId>,
    ) -> Result<(), JoinIrVmBridgeError> {
        // Phase 41-4: NestedIfMerge → multi-level Branch + PHI
        if k_next.is_some() {
            return Err(JoinIrVmBridgeError::new(
                "NestedIfMerge: k_next not yet supported".to_string(),
            ));
        }

        if conds.is_empty() {
            return Err(JoinIrVmBridgeError::new(
                "NestedIfMerge: conds must not be empty".to_string(),
            ));
        }

        debug_log!(
            "[joinir_block] Converting NestedIfMerge: conds.len()={}",
            conds.len()
        );

        let num_conds = conds.len();
        // Phase 269 P1.2+: Use BlockAllocator (Site 4/4)
        let mut allocator = BlockAllocator::new(self.next_block_id);

        let mut level_blocks: Vec<BasicBlockId> = Vec::with_capacity(num_conds);
        level_blocks.push(self.current_block_id);
        // Allocate level 1..num_conds blocks
        level_blocks.extend(allocator.allocate_n(num_conds - 1));

        let (then_block, final_else_block, merge_block) = allocator.allocate_three();
        self.next_block_id = allocator.peek_next();

        // Pre-create level 1+ blocks
        for level in 1..num_conds {
            mir_func.blocks.insert(
                level_blocks[level],
                crate::mir::BasicBlock::new(level_blocks[level]),
            );
        }

        // Create branches
        for (level, cond_var) in conds.iter().enumerate() {
            let this_block = level_blocks[level];
            let next_true_block = if level + 1 < num_conds {
                level_blocks[level + 1]
            } else {
                then_block
            };

            let branch_terminator = MirInstruction::Branch {
                condition: *cond_var,
                then_bb: next_true_block,
                else_bb: final_else_block,
                then_edge_args: None,
                else_edge_args: None,
            };

            if level == 0 {
                Self::finalize_block(
                    mir_func,
                    this_block,
                    std::mem::take(&mut self.current_instructions),
                    branch_terminator,
                );
            } else {
                Self::finalize_block(mir_func, this_block, Vec::new(), branch_terminator);
            }
        }

        // then block
        let mut then_block_obj = crate::mir::BasicBlock::new(then_block);
        emit_merge_copies(&mut then_block_obj, merges, MergeBranch::Then);
        then_block_obj.set_terminator(MirInstruction::Jump {
            target: merge_block,
            edge_args: None,
        });
        mir_func.blocks.insert(then_block, then_block_obj);

        // else block
        let mut else_block_obj = crate::mir::BasicBlock::new(final_else_block);
        emit_merge_copies(&mut else_block_obj, merges, MergeBranch::Else);
        else_block_obj.set_terminator(MirInstruction::Jump {
            target: merge_block,
            edge_args: None,
        });
        mir_func.blocks.insert(final_else_block, else_block_obj);

        // merge block
        let merge_block_obj = crate::mir::BasicBlock::new(merge_block);
        mir_func.blocks.insert(merge_block, merge_block_obj);

        self.current_block_id = merge_block;
        Ok(())
    }

    // === Utilities ===

    fn finalize_block(
        mir_func: &mut MirFunction,
        block_id: BasicBlockId,
        instructions: Vec<MirInstruction>,
        terminator: MirInstruction,
    ) {
        log_dbg(format!(
            "[joinir_block/finalize_block] block_id={:?}, instructions.len()={}",
            block_id,
            instructions.len()
        ));
        if let Some(block) = mir_func.blocks.get_mut(&block_id) {
            // Phase 189 FIX: Preserve existing PHI instructions at block start
            // PHI instructions must remain at the beginning of the block
            let existing_phis: Vec<_> = block
                .instructions
                .iter()
                .filter(|i| matches!(i, MirInstruction::Phi { .. }))
                .cloned()
                .collect();
            let phi_count = existing_phis.len();

            if phi_count > 0 {
                log_dbg(format!(
                    "[joinir_block/finalize_block] Preserving {} PHI instructions in block {:?}",
                    phi_count, block_id
                ));
                // PHI first, then new instructions
                let mut merged = existing_phis;
                merged.extend(instructions);
                let total_count = merged.len();
                block.instructions = merged;
                block.instruction_spans = vec![Span::unknown(); total_count];
            } else {
                let inst_count = instructions.len();
                block.instructions = instructions;
                block.instruction_spans = vec![Span::unknown(); inst_count];
            }
            block.set_terminator(terminator);
        }
    }

    fn finalize_remaining_instructions(&mut self, mir_func: &mut MirFunction) {
        if !self.current_instructions.is_empty() {
            debug_log!(
                "[joinir_block] Final block {:?} has {} remaining instructions",
                self.current_block_id,
                self.current_instructions.len()
            );
            if let Some(block) = mir_func.blocks.get_mut(&self.current_block_id) {
                // Phase 189 FIX: Use extend() instead of assignment to preserve
                // existing instructions (e.g., PHI from handle_select())
                let new_insts = std::mem::take(&mut self.current_instructions);
                let new_count = new_insts.len();
                block.instructions.extend(new_insts);
                block
                    .instruction_spans
                    .extend(vec![Span::unknown(); new_count]);
            }
        }
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_block_converter_exists() {
        let converter = JoinIrBlockConverter::new();
        assert_eq!(converter.current_block_id, BasicBlockId(0));
        assert_eq!(converter.next_block_id, 1);
    }
}
