//! Pattern 8: Boolean Predicate Scan (is_integer/is_valid form) - LEGACY LINE
//!
//! Phase 259 P0: Dedicated pattern for boolean predicate validation loops
//!
//! ## Legacy Status (Phase 286 P3)
//!
//! **RETAINED FOR STATIC BOX CONTEXTS**
//!
//! - **Plan line** (Phase 286 P2.4): Handles regular loops via extract_pattern8_plan()
//! - **Legacy line** (this file): Handles static box loops ONLY
//!
//! ### Why Legacy is Still Needed:
//!
//! 1. **Static box filtering**: Plan extractors are pure functions without builder access
//!    - Plan extractors cannot check `builder.comp_ctx.current_static_box`
//!    - Legacy `can_lower()` rejects static box contexts (see static box check below)
//! 2. **ReceiverNormalizeBox coordination**: Static box `this.method()` calls
//!    are normalized by ReceiverNormalizeBox, not Pattern8
//! 3. **Fail-Fast**: Plan line succeeds → legacy unreachable. Plan line fails → fallback to legacy
//!
//! **Deletion Condition**: Legacy can be removed when Plan extraction router
//! adds static box filtering OR when ReceiverNormalizeBox handles all static box loops.
//!
//! ## Pattern Structure
//!
//! ```nyash
//! is_integer(s) {
//!     local i = start  // Computed in prelude
//!     loop(i < s.length()) {
//!         if not this.is_digit(s.substring(i, i + 1)) {
//!             return false
//!         }
//!         i = i + 1
//!     }
//!     return true
//! }
//! ```
//!
//! ## Detection Criteria (P0: Fixed Form Only)
//!
//! 1. Loop condition: `i < s.length()`
//! 2. Loop body has if statement with:
//!    - Condition: `not this.method(...)` (UnaryOp::Not + MethodCall)
//!    - Then branch: `return false` (early exit)
//! 3. Loop body has step: `i = i + 1`
//! 4. Post-loop: `return true`
//!
//! ## vs Pattern 6
//!
//! - Pattern 6: Match scan (substring == needle → return i)
//! - Pattern 8: Predicate scan (not is_digit → return false, else true)
//! - Pattern 6: Returns integer (index or -1)
//! - Pattern 8: Returns boolean (true/false)

use super::super::trace;
use crate::ast::{ASTNode, BinaryOperator, LiteralValue, UnaryOperator};
use crate::mir::builder::MirBuilder;
use crate::mir::ValueId;

/// Phase 259 P0: Extracted structure for boolean predicate scan
#[derive(Debug, Clone)]
struct BoolPredicateScanParts {
    /// Loop variable name (e.g., "i")
    loop_var: String,
    /// Haystack variable name (e.g., "s")
    haystack: String,
    /// Predicate method receiver (e.g., "this")
    predicate_receiver: String,
    /// Predicate method name (e.g., "is_digit")
    predicate_method: String,
    /// Step literal (P0: must be 1)
    step_lit: i64,
}

/// Phase 269 P1: Detection for Pattern 8 (BoolPredicateScan)
/// Now uses EdgeCFG Frag lowering via emission entrypoint
/// Phase 269 P1.2: Skip Pattern8 for static box this.method() - let ReceiverNormalizeBox handle
pub(crate) fn can_lower(builder: &MirBuilder, ctx: &super::router::LoopPatternContext) -> bool {
    // Phase 269 P1.2: Skip Pattern8 for static box contexts
    // this.method() in static boxes should be normalized to static calls by ReceiverNormalizeBox
    // Pattern8 requires a receiver object, which doesn't exist for static box methods
    if builder.comp_ctx.current_static_box.is_some() {
        if ctx.debug {
            trace::trace().debug(
                "pattern8/can_lower",
                "reject: static box context (ReceiverNormalizeBox handles this.method())",
            );
        }
        return false;
    }

    match extract_bool_predicate_scan_parts(ctx.condition, ctx.body) {
        Ok(Some(_)) => {
            if ctx.debug {
                trace::trace().debug(
                    "pattern8/can_lower",
                    "accept: boolean predicate scan pattern extractable",
                );
            }
            true
        }
        Ok(None) => {
            if ctx.debug {
                trace::trace().debug(
                    "pattern8/can_lower",
                    "reject: not a boolean predicate scan pattern",
                );
            }
            false
        }
        Err(e) => {
            if ctx.debug {
                trace::trace().debug(
                    "pattern8/can_lower",
                    &format!("reject: extraction error: {}", e),
                );
            }
            false
        }
    }
}

/// Phase 259 P0: Extract boolean predicate scan pattern parts
///
/// # P0 Restrictions (Fail-Fast)
///
/// - Loop condition: `i < s.length()` (forward only)
/// - If condition: `not this.method(s.substring(i, i + 1))` (UnaryOp::Not)
/// - Then branch: `return false` (Literal::Bool(false))
/// - Step: `i = i + 1` (step_lit == 1)
/// - Post-loop: `return true` (enforced by caller)
fn extract_bool_predicate_scan_parts(
    condition: &ASTNode,
    body: &[ASTNode],
) -> Result<Option<BoolPredicateScanParts>, String> {
    // Phase 269 P1: Debug output removed (was breaking quick smoke output)

    // 1. Check loop condition: i < s.length()
    let (loop_var, haystack) = match condition {
        ASTNode::BinaryOp {
            operator: BinaryOperator::Less,
            left,
            right,
            ..
        } => {
            let loop_var = match left.as_ref() {
                ASTNode::Variable { name, .. } => name.clone(),
                _ => return Ok(None),
            };

            let haystack = match right.as_ref() {
                ASTNode::MethodCall {
                    object, method, ..
                } if method == "length" => match object.as_ref() {
                    ASTNode::Variable { name, .. } => name.clone(),
                    _ => return Ok(None),
                },
                _ => return Ok(None),
            };

            (loop_var, haystack)
        }
        _ => return Ok(None),
    };

    // 2. Find if statement with predicate check and return false
    let mut predicate_receiver_opt = None;
    let mut predicate_method_opt = None;

    for stmt in body.iter() {
        if let ASTNode::If {
            condition: if_cond,
            then_body,
            ..
        } = stmt
        {
            // Check if condition is: not this.method(...)
            if let ASTNode::UnaryOp {
                operator: UnaryOperator::Not,
                operand,
                ..
            } = if_cond.as_ref()
            {
                // Operand must be MethodCall
                if let ASTNode::MethodCall {
                    object,
                    method,
                    arguments,
                    ..
                } = operand.as_ref()
                {
                    // Extract receiver (e.g., "me")
                    // Phase 259 P0: Support both Variable and Me node
                    // Phase 269 P1.2: Skip This in static box context (handled by ReceiverNormalizeBox)
                    // IMPORTANT: Me is registered as "me" in variable_map (not "this")
                    let receiver = match object.as_ref() {
                        ASTNode::Variable { name, .. } => name.clone(),
                        ASTNode::Me { .. } => "me".to_string(),  // Me is registered as "me" in MirBuilder
                        ASTNode::This { .. } => {
                            // Phase 269 P1.2: Skip pattern for This in static box context
                            // Let ReceiverNormalizeBox handle static call normalization instead
                            continue;
                        }
                        _ => continue,
                    };

                    // P0: Expect 1 argument: s.substring(i, i + 1)
                    if arguments.len() != 1 {
                        continue;
                    }

                    // Validate argument is substring call
                    if let ASTNode::MethodCall {
                        object: substr_obj,
                        method: substr_method,
                        arguments: substr_args,
                        ..
                    } = &arguments[0]
                    {
                        if substr_method != "substring" {
                            continue;
                        }

                        // Object must be haystack
                        if let ASTNode::Variable { name, .. } = substr_obj.as_ref() {
                            if name != &haystack {
                                continue;
                            }
                        } else {
                            continue;
                        }

                        // Args: (i, i + 1)
                        if substr_args.len() != 2 {
                            continue;
                        }

                        // Arg 0: loop_var
                        match &substr_args[0] {
                            ASTNode::Variable { name, .. } if name == &loop_var => {}
                            _ => continue,
                        }

                        // Arg 1: loop_var + 1
                        match &substr_args[1] {
                            ASTNode::BinaryOp {
                                operator: BinaryOperator::Add,
                                left,
                                right,
                                ..
                            } => {
                                // Left: loop_var
                                match left.as_ref() {
                                    ASTNode::Variable { name, .. } if name == &loop_var => {}
                                    _ => continue,
                                }

                                // Right: Literal(1)
                                match right.as_ref() {
                                    ASTNode::Literal {
                                        value: LiteralValue::Integer(1),
                                        ..
                                    } => {}
                                    _ => continue,
                                }
                            }
                            _ => continue,
                        }
                    } else {
                        continue;
                    }

                    // Check then_body contains: return false
                    if then_body.len() == 1 {
                        if let ASTNode::Return { value, .. } = &then_body[0] {
                            if let Some(ret_val) = value {
                                if let ASTNode::Literal {
                                    value: LiteralValue::Bool(false),
                                    ..
                                } = ret_val.as_ref()
                                {
                                    predicate_receiver_opt = Some(receiver);
                                    predicate_method_opt = Some(method.clone());
                                }
                            }
                        }
                    }
                }
            }
        }
    }

    let predicate_receiver = predicate_receiver_opt.ok_or_else(|| "No predicate pattern found")?;
    let predicate_method = predicate_method_opt.ok_or_else(|| "No predicate method found")?;

    // 3. Check for step: i = i + 1
    let mut step_lit_opt = None;
    for stmt in body {
        if let ASTNode::Assignment { target, value, .. } = stmt {
            if let ASTNode::Variable { name: target_name, .. } = target.as_ref() {
                if target_name == &loop_var {
                    if let ASTNode::BinaryOp {
                        operator: BinaryOperator::Add,
                        left,
                        right,
                        ..
                    } = value.as_ref()
                    {
                        if let ASTNode::Variable { name: left_name, .. } = left.as_ref() {
                            if left_name == &loop_var {
                                if let ASTNode::Literal {
                                    value: LiteralValue::Integer(lit),
                                    ..
                                } = right.as_ref()
                                {
                                    step_lit_opt = Some(*lit);
                                }
                            }
                        }
                    }
                }
            }
        }
    }

    let step_lit = step_lit_opt.ok_or_else(|| "No step pattern found")?;

    // P0: Step must be 1
    if step_lit != 1 {
        return Ok(None);
    }

    Ok(Some(BoolPredicateScanParts {
        loop_var,
        haystack,
        predicate_receiver,
        predicate_method,
        step_lit,
    }))
}

/// Phase 269 P1: Lowering function for Pattern 8 (Frag-based)
/// Now uses EdgeCFG Frag API via emission entrypoint
pub(crate) fn lower(
    builder: &mut MirBuilder,
    ctx: &super::router::LoopPatternContext,
) -> Result<Option<ValueId>, String> {
    use crate::mir::types::{BinaryOp, CompareOp, ConstValue, UnaryOp};
    use crate::mir::{Effect, EffectMask, MirInstruction, MirType};

    let trace = trace::trace();

    // Step 1: Extract pattern parts (SSOT)
    let parts = extract_bool_predicate_scan_parts(ctx.condition, ctx.body)?
        .ok_or_else(|| format!("[pattern8] Not a boolean predicate scan pattern in {}", ctx.func_name))?;

    if ctx.debug {
        trace.debug(
            "pattern8/lower",
            &format!(
                "Pattern8 Frag lowering: loop_var={}, haystack={}, predicate={}.{}",
                parts.loop_var, parts.haystack, parts.predicate_receiver, parts.predicate_method
            ),
        );
    }

    // Step 2: Get me_val using build_me_expression() (SSOT - no receiver name speculation)
    let me_val = builder.build_me_expression()?;

    // Step 3: Get i and s from variable_map
    let i_init_val = builder
        .variable_ctx
        .variable_map
        .get(&parts.loop_var)
        .copied()
        .ok_or_else(|| format!("[pattern8] Variable '{}' not found", parts.loop_var))?;

    let s_val = builder
        .variable_ctx
        .variable_map
        .get(&parts.haystack)
        .copied()
        .ok_or_else(|| format!("[pattern8] Variable '{}' not found", parts.haystack))?;

    // Step 4a: Capture preheader block (entry to loop) for PHI input
    let preheader_bb = builder.current_block
        .ok_or_else(|| "[pattern8] No current block for loop entry".to_string())?;

    // Step 4b: Allocate PHI destination for loop variable BEFORE generating blocks
    let i_current = builder.next_value_id();
    builder.type_ctx.value_types.insert(i_current, MirType::Integer);

    // Step 4c: Allocate BasicBlockIds for 5 blocks
    let header_bb = builder.next_block_id();
    let body_bb = builder.next_block_id();
    let step_bb = builder.next_block_id();
    let after_bb = builder.next_block_id();
    let ret_false_bb = builder.next_block_id();

    // Add Jump from current block to header_bb (to terminate the previous block)
    if let Some(_current) = builder.current_block {
        builder.emit_instruction(MirInstruction::Jump {
            target: header_bb,
            edge_args: None,
        })?;
    }

    // Build header_bb: len = s.length(), cond_loop = (i < len)
    builder.start_new_block(header_bb)?;

    // Note: PHI node for i_current will be inserted AFTER all blocks are generated
    // (see Step 4 below, after step_bb generates i_next_val)

    let len_val = builder.next_value_id();
    builder.emit_instruction(MirInstruction::BoxCall {
        dst: Some(len_val),
        box_val: s_val,
        method: "length".to_string(),
        method_id: None,
        args: vec![],
        effects: EffectMask::PURE.add(Effect::Io),
    })?;
    builder.type_ctx.value_types.insert(len_val, MirType::Integer);

    let cond_loop = builder.next_value_id();
    builder.emit_instruction(MirInstruction::Compare {
        dst: cond_loop,
        lhs: i_current,  // Use PHI result, not initial value
        op: CompareOp::Lt,
        rhs: len_val,
    })?;
    builder.type_ctx.value_types.insert(cond_loop, MirType::Bool);

    // Create ret_false_val in header_bb (dominates both step_bb and ret_false_bb)
    let ret_false_val = builder.next_value_id();
    builder.emit_instruction(MirInstruction::Const {
        dst: ret_false_val,
        value: ConstValue::Bool(false),
    })?;
    builder.type_ctx.value_types.insert(ret_false_val, MirType::Bool);

    // Build body_bb: ch = s.substring(i, i+1), ok = me.<predicate_method>(ch), cond_fail = not ok
    builder.start_new_block(body_bb)?;

    let one = builder.next_value_id();
    builder.emit_instruction(MirInstruction::Const {
        dst: one,
        value: ConstValue::Integer(1),
    })?;
    builder.type_ctx.value_types.insert(one, MirType::Integer);

    let i_plus_one = builder.next_value_id();
    builder.emit_instruction(MirInstruction::BinOp {
        dst: i_plus_one,
        lhs: i_current,  // Use PHI result, not initial value
        op: BinaryOp::Add,
        rhs: one,
    })?;
    builder.type_ctx.value_types.insert(i_plus_one, MirType::Integer);

    let ch_val = builder.next_value_id();
    builder.emit_instruction(MirInstruction::BoxCall {
        dst: Some(ch_val),
        box_val: s_val,
        method: "substring".to_string(),
        method_id: None,
        args: vec![i_current, i_plus_one],  // Use PHI result, not initial value
        effects: EffectMask::PURE.add(Effect::Io),
    })?;
    builder.type_ctx.value_types.insert(ch_val, MirType::String);

    let ok_val = builder.next_value_id();
    builder.emit_instruction(MirInstruction::BoxCall {
        dst: Some(ok_val),
        box_val: me_val,
        method: parts.predicate_method.clone(),
        method_id: None,
        args: vec![ch_val],
        effects: EffectMask::PURE.add(Effect::Io),
    })?;
    builder.type_ctx.value_types.insert(ok_val, MirType::Bool);

    let cond_fail = builder.next_value_id();
    builder.emit_instruction(MirInstruction::UnaryOp {
        dst: cond_fail,
        op: UnaryOp::Not,
        operand: ok_val,
    })?;
    builder.type_ctx.value_types.insert(cond_fail, MirType::Bool);

    // Build step_bb: i = i + 1
    builder.start_new_block(step_bb)?;

    let i_next_val = builder.next_value_id();
    builder.emit_instruction(MirInstruction::BinOp {
        dst: i_next_val,
        lhs: i_current,  // Use PHI result, not initial value
        op: BinaryOp::Add,
        rhs: one,
    })?;
    builder.type_ctx.value_types.insert(i_next_val, MirType::Integer);
    // Note: Do NOT update variable_map here - PHI will handle SSA renaming

    // Ensure ret_false_bb and after_bb exist (they don't have instructions, but must exist for emit_frag)
    builder.ensure_block_exists(ret_false_bb)?;
    builder.ensure_block_exists(after_bb)?;

    // Step 4: Insert PHI at head of header_bb - Phase 272 P0.2 Refactoring: use emission/phi.rs
    use crate::mir::builder::emission::phi::insert_loop_phi;

    insert_loop_phi(
        builder,
        header_bb,
        i_current,
        vec![
            (preheader_bb, i_init_val),  // Entry edge: initial value
            (step_bb, i_next_val),       // Latch edge: updated value
        ],
        "pattern8/header_phi",
    )?;

    // Step 5: Call emission entrypoint
    use crate::mir::builder::emission::loop_predicate_scan::emit_bool_predicate_scan_edgecfg;

    if crate::config::env::is_joinir_debug() {
        eprintln!("[pattern8] using edgecfg (Frag版)");
    }

    emit_bool_predicate_scan_edgecfg(
        builder,
        header_bb,
        body_bb,
        step_bb,
        after_bb,
        ret_false_bb,
        cond_loop,
        cond_fail,
        ret_false_val,
    )?;

    // Step 6: Update variable_map to use final loop variable value
    // (This is the value when loop exits normally via i >= len)
    builder.variable_ctx.variable_map.insert(parts.loop_var.clone(), i_current);

    // Step 7: Setup after_bb for subsequent AST lowering (return true)
    // CRITICAL: Use start_new_block() to create actual block, not just set current_block
    builder.start_new_block(after_bb)?;

    // Step 7: Return Void (loop as statement, not expression)
    use crate::mir::builder::emission::constant::emit_void;
    let void_val = emit_void(builder);

    if ctx.debug {
        trace.debug("pattern8/lower", "Pattern8 Frag lowering complete");
    }

    Ok(Some(void_val))
}


/// Phase 269 P0: Pattern8 Frag 版 lowerer（test-only）
///
/// # Purpose
/// - JoinModule → MIR terminator 生成を EdgeCFG Fragment API で実装
/// - 既存実装（cf_loop_pattern8_bool_predicate_impl）と並走
/// - P1 以降で既存実装を置換する準備
///
/// # Architecture
/// ```
/// JoinModule (loop_step + k_exit)
///   ↓ 変換
/// Frag 構築（loop body + early exits）
///   ↓ compose::loop_()
/// Loop Frag
///   ↓ emit_frag()
/// MIR terminator（Branch/Jump/Return）
/// ```
///
/// # Arguments
/// - `builder`: MirBuilder
/// - `join_module`: Pattern8 の JoinModule（lower_scan_bool_predicate_minimal から生成）
/// - `boundary`: JoinInlineBoundary（host <-> join 境界情報）
/// - `debug`: デバッグフラグ
///
/// # Returns
/// - `Ok(Some(ValueId))`: expr_result（ret_bool の host ValueId）
/// - `Err(String)`: Frag 構築失敗
#[cfg(test)]
pub(crate) fn lower_pattern8_frag(
    _builder: &mut MirBuilder,
    _join_module: crate::mir::join_ir::JoinModule,
    _boundary: &crate::mir::join_ir::lowering::inline_boundary::JoinInlineBoundary,
    _debug: bool,
) -> Result<Option<ValueId>, String> {
    // Phase 269 P0: Stub implementation
    // TODO: Implement Frag-based lowering
    //
    // Implementation Plan:
    // 1. Extract loop_step function from join_module
    // 2. Build loop body Frag from loop_step instructions
    // 3. Handle early exits (return false) as Return exit
    // 4. Handle tail recursion as Continue exit
    // 5. Handle loop exit (return true) as Break exit
    // 6. Use compose::loop_() to create loop Frag
    // 7. Use emit_frag() to generate MIR terminators
    // 8. Return boundary.expr_result

    Err("[pattern8_frag] Phase 269 P0 stub - not yet implemented".to_string())
}

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

    /// Phase 269 P0: Test Pattern8 Frag lowering (minimal test)
    ///
    /// Verifies that lower_pattern8_frag() can:
    /// - Accept JoinModule from lower_scan_bool_predicate_minimal()
    /// - Return appropriate error (stub implementation)
    #[test]
    fn test_pattern8_frag_lowering_stub() {
        use crate::mir::builder::MirBuilder;
        use crate::mir::join_ir::lowering::join_value_space::JoinValueSpace;
        use crate::mir::join_ir::lowering::scan_bool_predicate_minimal::lower_scan_bool_predicate_minimal;
        use crate::mir::join_ir::lowering::JoinInlineBoundaryBuilder;

        // 1. Create minimal JoinModule
        let mut join_value_space = JoinValueSpace::new();
        let join_module = lower_scan_bool_predicate_minimal(
            &mut join_value_space,
            "me",
            "is_digit",
        );

        // 2. Create minimal boundary (stub)
        let boundary = JoinInlineBoundaryBuilder::new()
            .with_inputs(vec![], vec![])
            .build();

        // 3. Create MirBuilder
        let mut builder = MirBuilder::new();

        // 4. Call lower_pattern8_frag (should return stub error)
        let result = lower_pattern8_frag(&mut builder, join_module, &boundary, false);

        // 5. Verify stub error
        assert!(result.is_err());
        assert!(result.unwrap_err().contains("stub"));
    }
}
