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

use crate::ast::Span;
use crate::mir::join_ir::{JoinInst, MirLikeInst};
use crate::mir::{BasicBlockId, EffectMask, MirFunction, MirInstruction, ValueId};

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 189: Stable function name → ValueId mapping for tail call detection
    // Ensures the same function name always gets the same ValueId
    func_name_to_value_id: std::collections::HashMap<String, ValueId>,
    next_func_name_value_id: u32,
}

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_to_value_id: std::collections::HashMap::new(),
            next_func_name_value_id: 99990, // Use 99990+ range for function names
        }
    }

    /// 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.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(())
    }

    // === 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;
        let then_block = BasicBlockId(self.next_block_id);
        self.next_block_id += 1;
        let else_block = BasicBlockId(self.next_block_id);
        self.next_block_id += 1;
        let merge_block = BasicBlockId(self.next_block_id);
        self.next_block_id += 1;

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

        // then block: method call
        let mut then_block_obj = crate::mir::BasicBlock::new(then_block);
        then_block_obj.instructions.push(MirInstruction::BoxCall {
            dst: Some(*dst),
            box_val: *receiver,
            method: method.to_string(),
            method_id: None,
            args: args.to_vec(),
            effects: EffectMask::PURE,
        });
        then_block_obj.instruction_spans.push(Span::unknown());
        then_block_obj.terminator = Some(MirInstruction::Jump {
            target: merge_block,
        });
        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: *dst,
            src: *receiver,
        });
        else_block_obj.instruction_spans.push(Span::unknown());
        else_block_obj.terminator = Some(MirInstruction::Jump {
            target: merge_block,
        });
        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_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(),
            ));
        }

        let func_name = join_func_name(*func);

        // Phase 189: Use stable function name → ValueId mapping
        // This ensures the same function name always gets the same ValueId,
        // which is critical for tail call detection in merge_joinir_mir_blocks
        let func_name_id = *self
            .func_name_to_value_id
            .entry(func_name.clone())
            .or_insert_with(|| {
                let id = ValueId(self.next_func_name_value_id);
                self.next_func_name_value_id += 1;
                id
            });

        self.current_instructions.push(MirInstruction::Const {
            dst: func_name_id,
            value: crate::mir::ConstValue::String(func_name),
        });

        match dst {
            Some(result_dst) => {
                // Non-tail call
                self.current_instructions.push(MirInstruction::Call {
                    dst: Some(*result_dst),
                    func: func_name_id,
                    callee: None,
                    args: args.to_vec(),
                    effects: EffectMask::PURE,
                });
            }
            None => {
                // Tail call
                let call_result_id = ValueId(99991);
                self.current_instructions.push(MirInstruction::Call {
                    dst: Some(call_result_id),
                    func: func_name_id,
                    callee: None,
                    args: args.to_vec(),
                    effects: EffectMask::PURE,
                });

                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,
                );
            }
        }
        Ok(())
    }

    fn handle_jump(
        &mut self,
        mir_func: &mut MirFunction,
        _cont: &crate::mir::join_ir::JoinContId,
        args: &[ValueId],
        cond: &Option<ValueId>,
    ) -> Result<(), JoinIrVmBridgeError> {
        // Phase 246-EX: Preserve ALL Jump args as metadata for exit PHI construction
        // Phase 27-shortterm S-4.4-A: Jump → Branch/Return
        debug_log!(
            "[joinir_block] Converting Jump args={:?}, cond={:?}",
            args,
            cond
        );

        match cond {
            Some(cond_var) => {
                // Conditional jump
                let exit_block_id = BasicBlockId(self.next_block_id);
                self.next_block_id += 1;
                let continue_block_id = BasicBlockId(self.next_block_id);
                self.next_block_id += 1;

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

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

                // Phase 246-EX: Store all Jump args in exit block metadata
                // Exit block: Create with all Jump args stored as metadata
                let exit_value = args.first().copied();
                let mut exit_block = crate::mir::BasicBlock::new(exit_block_id);

                // Phase 246-EX: Store Jump args in a new metadata field
                // This preserves carrier values for exit PHI construction
                exit_block.jump_args = Some(args.to_vec());

                exit_block.terminator = Some(MirInstruction::Return { value: exit_value });
                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
                let exit_value = args.first().copied();
                let return_terminator = MirInstruction::Return { value: exit_value };

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

    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 33-2: Select → if/phi
        debug_log!(
            "[joinir_block] Converting Select: dst={:?}, type_hint={:?}",
            dst,
            type_hint
        );

        let cond_block = self.current_block_id;
        let then_block = BasicBlockId(self.next_block_id);
        self.next_block_id += 1;
        let else_block = BasicBlockId(self.next_block_id);
        self.next_block_id += 1;
        let merge_block = BasicBlockId(self.next_block_id);
        self.next_block_id += 1;

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

        // then/else blocks
        let mut then_block_obj = crate::mir::BasicBlock::new(then_block);
        then_block_obj.terminator = Some(MirInstruction::Jump {
            target: merge_block,
        });
        mir_func.blocks.insert(then_block, then_block_obj);

        let mut else_block_obj = crate::mir::BasicBlock::new(else_block);
        else_block_obj.terminator = Some(MirInstruction::Jump {
            target: merge_block,
        });
        mir_func.blocks.insert(else_block, else_block_obj);

        // merge block: PHI
        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_val), (else_block, *else_val)],
            type_hint: type_hint.clone(),
        });
        merge_block_obj.instruction_spans.push(Span::unknown());
        log_dbg(format!(
            "[joinir_block/handle_select] Created merge_block {:?} with {} instructions (first={:?})",
            merge_block,
            merge_block_obj.instructions.len(),
            merge_block_obj.instructions.first()
        ));
        mir_func.blocks.insert(merge_block, merge_block_obj);

        // Verify PHI was inserted
        if let Some(inserted) = mir_func.blocks.get(&merge_block) {
            log_dbg(format!(
                "[joinir_block/handle_select] After insert: merge_block {:?} has {} instructions",
                merge_block,
                inserted.instructions.len()
            ));
        }

        self.current_block_id = merge_block;
        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;
        let then_block = BasicBlockId(self.next_block_id);
        self.next_block_id += 1;
        let else_block = BasicBlockId(self.next_block_id);
        self.next_block_id += 1;
        let merge_block = BasicBlockId(self.next_block_id);
        self.next_block_id += 1;

        // cond block: branch
        let branch_terminator = MirInstruction::Branch {
            condition: *cond,
            then_bb: then_block,
            else_bb: else_block,
        };
        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);
        for merge in merges {
            then_block_obj.instructions.push(MirInstruction::Copy {
                dst: merge.dst,
                src: merge.then_val,
            });
            then_block_obj.instruction_spans.push(Span::unknown());
        }
        then_block_obj.terminator = Some(MirInstruction::Jump {
            target: merge_block,
        });
        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);
        for merge in merges {
            else_block_obj.instructions.push(MirInstruction::Copy {
                dst: merge.dst,
                src: merge.else_val,
            });
            else_block_obj.instruction_spans.push(Span::unknown());
        }
        else_block_obj.terminator = Some(MirInstruction::Jump {
            target: merge_block,
        });
        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();
        let mut level_blocks: Vec<BasicBlockId> = Vec::with_capacity(num_conds);
        level_blocks.push(self.current_block_id);

        for _ in 1..num_conds {
            level_blocks.push(BasicBlockId(self.next_block_id));
            self.next_block_id += 1;
        }

        let then_block = BasicBlockId(self.next_block_id);
        self.next_block_id += 1;
        let final_else_block = BasicBlockId(self.next_block_id);
        self.next_block_id += 1;
        let merge_block = BasicBlockId(self.next_block_id);
        self.next_block_id += 1;

        // 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,
            };

            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);
        for merge in merges {
            then_block_obj.instructions.push(MirInstruction::Copy {
                dst: merge.dst,
                src: merge.then_val,
            });
            then_block_obj.instruction_spans.push(Span::unknown());
        }
        then_block_obj.terminator = Some(MirInstruction::Jump {
            target: merge_block,
        });
        mir_func.blocks.insert(then_block, then_block_obj);

        // else block
        let mut else_block_obj = crate::mir::BasicBlock::new(final_else_block);
        for merge in merges {
            else_block_obj.instructions.push(MirInstruction::Copy {
                dst: merge.dst,
                src: merge.else_val,
            });
            else_block_obj.instruction_spans.push(Span::unknown());
        }
        else_block_obj.terminator = Some(MirInstruction::Jump {
            target: merge_block,
        });
        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.terminator = Some(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);
    }
}
