//! Return Jump Emitter
//!
//! Phase 284 P1: Extracted from loop_with_continue_minimal.rs (lines 430-530)
//!
//! This module provides a reusable helper for emitting conditional Jump to k_return
//! in JoinIR loop patterns (Pattern4/5 and future patterns).
//!
//! ## Design Rationale
//!
//! Pattern4/5 both need to handle early return statements in loop bodies:
//! ```nyash
//! loop(i < 10) {
//!     i = i + 1
//!     if (i == 3) {
//!         return 7  // Early return
//!     }
//!     // ... loop body continues
//! }
//! ```
//!
//! The JoinIR representation emits a conditional Jump to k_return:
//! ```text
//! return_cond = (i_next == 3)
//! Jump(k_return, [], cond=return_cond)
//! ```
//!
//! This helper consolidates the code generation logic into a single SSOT,
//! preventing duplication across multiple pattern lowerers.
//!
//! ## Usage
//!
//! ```rust,ignore
//! use crate::mir::join_ir::lowering::return_jump_emitter::emit_return_conditional_jump;
//!
//! if let Some(ret_info) = return_info {
//!     emit_return_conditional_jump(
//!         &mut loop_step_func,
//!         &Some(ret_info),
//!         k_return_id,
//!         &mut alloc_value,
//!         &debug,
//!     )?;
//! }
//! ```

use crate::ast::ASTNode;
use crate::mir::join_ir::lowering::debug_output_box::DebugOutputBox;
use crate::mir::join_ir::lowering::return_collector::ReturnInfo;
use crate::mir::join_ir::{CompareOp, ConstValue, JoinFuncId, JoinFunction, JoinInst, MirLikeInst};
use crate::mir::ValueId;

/// Emit conditional Jump to k_return
///
/// Generates JoinIR instructions for early return handling in loop bodies.
///
/// # Arguments
///
/// * `loop_step_func` - The loop step function (body) to append instructions to
/// * `return_info` - Return statement metadata (value, condition, in_else flag)
/// * `k_return_id` - The k_return continuation function ID
/// * `loop_var_id` - ValueId of the loop variable to check (typically i_next after increment)
/// * `loop_var_name` - Name of the loop variable (for condition validation)
/// * `alloc_value` - Value ID allocator closure
/// * `debug` - Debug output box for logging
///
/// # Returns
///
/// * `Ok(())` - Instructions emitted successfully
/// * `Err(msg)` - Unsupported return pattern (Fail-Fast)
///
/// # Phase 284 P1 Scope
///
/// - Single return statement only
/// - Return condition: Simple comparison (loop_var == N)
/// - Return value: Integer literal
/// - Supports then/else branches (via `in_else` flag)
///
/// # Implementation Notes
///
/// The generated instructions follow this pattern:
///
/// **Unconditional return:**
/// ```text
/// Jump(k_return, [])
/// ```
///
/// **Conditional return (then branch):**
/// ```text
/// const_cmp = Const(3)
/// return_cond = Compare(Eq, i_next, const_cmp)
/// Jump(k_return, [], cond=return_cond)
/// ```
///
/// **Conditional return (else branch):**
/// ```text
/// const_cmp = Const(3)
/// return_cond = Compare(Ne, i_next, const_cmp)  // Note: Ne (negated)
/// Jump(k_return, [], cond=return_cond)
/// ```
///
/// # Future Extensions (Pattern4/5 reuse)
///
/// This function is designed to be reusable across:
/// - Pattern4 (loop with continue)
/// - Pattern5 (infinite loop with early exit)
/// - Future patterns with early return support
///
/// When extending, consider:
/// - Complex condition support (beyond loop_var == N)
/// - Multiple return statements (Phase 284 P2+)
/// - Return value expressions (beyond integer literals)
pub fn emit_return_conditional_jump(
    loop_step_func: &mut JoinFunction,
    return_info: Option<&ReturnInfo>,
    k_return_id: JoinFuncId,
    loop_var_id: ValueId,
    loop_var_name: &str,
    alloc_value: &mut dyn FnMut() -> ValueId,
    debug: &DebugOutputBox,
) -> Result<(), String> {
    let Some(ret_info) = return_info else {
        // No return statement - nothing to emit
        return Ok(());
    };

    debug.log(
        "phase284",
        &format!(
            "Generating return handling: value={}, has_condition={}",
            ret_info.value,
            ret_info.condition.is_some()
        ),
    );

    if let Some(ref cond_ast) = ret_info.condition {
        // Return is inside an if block - need to evaluate condition
        // Phase 284 P1: Only support simple comparison (loop_var == N)
        // Extract the comparison value from the condition AST
        if let ASTNode::BinaryOp {
            operator: crate::ast::BinaryOperator::Equal,
            left,
            right,
            ..
        } = cond_ast.as_ref()
        {
            // Check if left is variable and right is integer
            let compare_value = match (left.as_ref(), right.as_ref()) {
                (
                    ASTNode::Variable { name, .. },
                    ASTNode::Literal {
                        value: crate::ast::LiteralValue::Integer(n),
                        ..
                    },
                ) if name == loop_var_name => Some(*n),
                _ => None,
            };

            if let Some(cmp_val) = compare_value {
                // Generate: return_cond = (i_next == cmp_val)
                let return_cmp_const = alloc_value();
                let return_cond = alloc_value();

                loop_step_func
                    .body
                    .push(JoinInst::Compute(MirLikeInst::Const {
                        dst: return_cmp_const,
                        value: ConstValue::Integer(cmp_val),
                    }));

                // Phase 284 P1: Use loop_var_id (i_next) because fixture has increment BEFORE the if check
                // Execution: i_next = i_param + 1, then check if i_next == compare_value
                loop_step_func
                    .body
                    .push(JoinInst::Compute(MirLikeInst::Compare {
                        dst: return_cond,
                        op: if ret_info.in_else {
                            CompareOp::Ne
                        } else {
                            CompareOp::Eq
                        },
                        lhs: loop_var_id, // Use loop_var_id (post-increment value)
                        rhs: return_cmp_const,
                    }));

                // Generate: Jump(k_return, [], cond=return_cond)
                loop_step_func.body.push(JoinInst::Jump {
                    cont: k_return_id.as_cont(),
                    args: vec![],
                    cond: Some(return_cond),
                });

                debug.log(
                    "phase284",
                    &format!("Return condition: {} == {} → jump to k_return", loop_var_name, cmp_val),
                );
                Ok(())
            } else {
                debug.log(
                    "phase284",
                    "Return condition not supported (not loop_var == N pattern)",
                );
                Err(format!(
                    "Phase 284 P1 scope: return condition variable must match loop variable '{}' (got: {:?})",
                    loop_var_name, left
                ))
            }
        } else {
            Err(
                "Phase 284 P1 scope: return condition must be Equal comparison".to_string(),
            )
        }
    } else {
        // Unconditional return - always jump to k_return
        loop_step_func.body.push(JoinInst::Jump {
            cont: k_return_id.as_cont(),
            args: vec![],
            cond: None,
        });
        debug.log("phase284", "Unconditional return → jump to k_return");
        Ok(())
    }
}

// ============================================================================
// Unit Tests
// ============================================================================

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

    fn make_debug() -> DebugOutputBox {
        DebugOutputBox::new_dev("test")
    }

    #[test]
    fn test_unconditional_return() {
        let mut func = JoinFunction::new(JoinFuncId::new(0), "test".to_string(), vec![]);
        let info = ReturnInfo {
            value: 42,
            condition: None,
            in_else: false,
        };
        let return_info = Some(&info);
        let k_return_id = JoinFuncId(99);
        let loop_var_id = ValueId(100);
        let mut counter = 0u32;
        let mut alloc = || {
            counter += 1;
            ValueId(1000 + counter)
        };
        let debug = make_debug();

        let result = emit_return_conditional_jump(
            &mut func,
            return_info,
            k_return_id,
            loop_var_id,
            "i",
            &mut alloc,
            &debug,
        );

        assert!(result.is_ok());
        assert_eq!(func.body.len(), 1);
        match &func.body[0] {
            JoinInst::Jump { cont, args, cond } => {
                assert_eq!(*cont, k_return_id.as_cont());
                assert_eq!(args.len(), 0);
                assert!(cond.is_none());
            }
            _ => panic!("Expected Jump instruction"),
        }
    }

    #[test]
    fn test_no_return() {
        let mut func = JoinFunction::new(JoinFuncId::new(0), "test".to_string(), vec![]);
        let return_info: Option<&ReturnInfo> = None;
        let k_return_id = JoinFuncId(99);
        let loop_var_id = ValueId(100);
        let mut counter = 0u32;
        let mut alloc = || {
            counter += 1;
            ValueId(1000 + counter)
        };
        let debug = make_debug();

        let result = emit_return_conditional_jump(
            &mut func,
            return_info,
            k_return_id,
            loop_var_id,
            "i",
            &mut alloc,
            &debug,
        );

        assert!(result.is_ok());
        assert_eq!(func.body.len(), 0); // No instructions emitted
    }

    #[test]
    fn test_conditional_return_success() {
        // Phase 284 P1: Test successful conditional return generation
        let mut func = JoinFunction::new(JoinFuncId::new(0), "test".to_string(), vec![]);
        let condition = Box::new(ASTNode::BinaryOp {
            operator: crate::ast::BinaryOperator::Equal,
            left: Box::new(ASTNode::Variable {
                name: "i".to_string(),
                span: Span::unknown(),
            }),
            right: Box::new(ASTNode::Literal {
                value: crate::ast::LiteralValue::Integer(3),
                span: Span::unknown(),
            }),
            span: Span::unknown(),
        });
        let info = ReturnInfo {
            value: 7,
            condition: Some(condition),
            in_else: false,
        };
        let return_info = Some(&info);
        let k_return_id = JoinFuncId(99);
        let loop_var_id = ValueId(100); // i_next
        let mut counter = 0u32;
        let mut alloc = || {
            counter += 1;
            ValueId(1000 + counter)
        };
        let debug = make_debug();

        let result = emit_return_conditional_jump(
            &mut func,
            return_info,
            k_return_id,
            loop_var_id,
            "i",
            &mut alloc,
            &debug,
        );

        // Should succeed
        assert!(result.is_ok(), "Failed: {:?}", result.err());

        // Check generated instructions
        // 1. Const(3)
        // 2. Compare(Eq, loop_var_id, const)
        // 3. Jump(k_return, [], cond)
        assert_eq!(func.body.len(), 3);
    }
}
