//! Pattern 2: Loop with Conditional Break minimal lowerer

use super::super::trace;
use crate::ast::ASTNode;
use crate::mir::builder::MirBuilder;
use crate::mir::ValueId;

/// Phase 194: Detection function for Pattern 2
///
/// Phase 282 P4: Updated to ExtractionBased detection with safety valve
///
/// Pattern 2 matches:
/// - Pattern kind is Pattern2Break (safety valve, O(1) early rejection)
/// - Extraction validates: 比較条件/loop(true) + HAS break + NO continue/return
pub(crate) fn can_lower(builder: &MirBuilder, ctx: &super::router::LoopPatternContext) -> bool {
    use crate::mir::loop_pattern_detection::LoopPatternKind;

    // Step 1: Early rejection guard (safety valve, O(1))
    if ctx.pattern_kind != LoopPatternKind::Pattern2Break {
        if ctx.debug {
            trace::trace().debug(
                "pattern2/can_lower",
                &format!("reject: pattern_kind={:?}", ctx.pattern_kind),
            );
        }
        return false;
    }

    // Step 2: ExtractionBased validation (SSOT, deep check)
    use super::extractors::pattern2::extract_loop_with_break_parts;

    let parts = match extract_loop_with_break_parts(ctx.condition, ctx.body) {
        Ok(Some(p)) => p,
        Ok(None) => {
            if ctx.debug {
                trace::trace().debug(
                    "pattern2/can_lower",
                    "reject: not Pattern2 (no break or has continue/return)",
                );
            }
            return false;
        }
        Err(e) => {
            if ctx.debug {
                trace::trace().debug("pattern2/can_lower", &format!("error: {}", e));
            }
            return false;
        }
    };

    if ctx.debug {
        trace::trace().debug(
            "pattern2/can_lower",
            &format!(
                "accept: extractable (break_count={}, is_loop_true={}) (Phase 282 P4)",
                parts.break_count, parts.is_loop_true
            ),
        );
    }

    // Step 3: Extract loop variable (hybrid for loop(true))
    use super::loop_true_counter_extractor::LoopTrueCounterExtractorBox;

    let loop_var_name = if parts.is_loop_true {
        // loop(true): Use LoopTrueCounterExtractorBox
        match LoopTrueCounterExtractorBox::extract_loop_counter_from_body(
            ctx.body,
            &builder.variable_ctx.variable_map,
        ) {
            Ok((name, _)) => name,
            Err(_) => {
                if ctx.debug {
                    trace::trace().debug(
                        "pattern2/can_lower",
                        "reject: loop(true) but no counter found",
                    );
                }
                return false;
            }
        }
    } else {
        // Normal loop: Use condition-based extraction
        match builder.extract_loop_variable_from_condition(ctx.condition) {
            Ok(name) => name,
            Err(_) => {
                // Try parts.loop_var as fallback
                if parts.loop_var.is_empty() {
                    if ctx.debug {
                        trace::trace().debug(
                            "pattern2/can_lower",
                            "reject: cannot extract loop variable",
                        );
                    }
                    return false;
                }
                parts.loop_var.clone()
            }
        }
    };

    // Step 4: Carrier update validation (SSOT)
    use super::common_init::CommonPatternInitializer;
    match CommonPatternInitializer::check_carrier_updates_allowed(
        ctx.body,
        &loop_var_name,
        &builder.variable_ctx.variable_map,
    ) {
        true => true,
        false => {
            if ctx.debug {
                trace::trace().debug(
                    "pattern2/can_lower",
                    "reject: carrier updates not allowed (method calls)",
                );
            }
            false
        }
    }
}

/// Phase 194: Lowering function for Pattern 2
///
/// Phase 282 P4: Re-extracts for SSOT (no caching from can_lower)
///
/// Wrapper around cf_loop_pattern2_with_break_impl to match router signature
pub(crate) fn lower(
    builder: &mut MirBuilder,
    ctx: &super::router::LoopPatternContext,
) -> Result<Option<ValueId>, String> {
    use super::extractors::pattern2::extract_loop_with_break_parts;

    // Re-extract (SSOT principle - no caching from can_lower)
    let parts = extract_loop_with_break_parts(ctx.condition, ctx.body)?
        .ok_or_else(|| "[pattern2] Not a loop with break pattern in lower()".to_string())?;

    if ctx.debug {
        trace::trace().debug(
            "pattern2/lower",
            &format!(
                "loop_var={}, break_count={}, is_loop_true={} (Phase 282 P4)",
                parts.loop_var, parts.break_count, parts.is_loop_true
            ),
        );
    }

    // Call existing orchestrator implementation (completely unchanged)
    // Note: ctx.skeleton already provided by router if needed
    builder.cf_loop_pattern2_with_break_impl(
        ctx.condition,
        ctx.body,
        ctx.func_name,
        ctx.debug,
        ctx.fn_body,
        ctx.skeleton,  // Use existing ctx.skeleton (no new env var)
    )
}

impl MirBuilder {
    /// Phase 179-B: Pattern 2 (Loop with Conditional Break) minimal lowerer
    ///
    /// **Refactored**: Now uses PatternPipelineContext for unified preprocessing
    /// **Phase 200-C**: Added fn_body parameter for capture analysis
    /// **Phase 92 P0-3**: Added skeleton parameter for ConditionalStep support
    ///
    /// # Pipeline (Phase 179-B)
    /// 1. Build preprocessing context → PatternPipelineContext
    /// 2. Pattern 2-specific processing (Trim, carrier updates, break condition)
    /// 3. Call JoinIR lowerer → JoinModule
    /// 4. Create boundary from context → JoinInlineBoundary
    /// 5. Merge MIR blocks → JoinIRConversionPipeline
    ///
    /// Note: Pattern 2 has complex Trim pattern logic that remains inline
    /// for now. Future Phase 180+ will move Trim logic to dedicated module.
    #[allow(dead_code)]
    pub(in crate::mir::builder) fn cf_loop_pattern2_with_break(
        &mut self,
        condition: &ASTNode,
        _body: &[ASTNode],
        _func_name: &str,
        debug: bool,
    ) -> Result<Option<ValueId>, String> {
        // Phase 200-C: Delegate to impl function with fn_body=None for backward compatibility
        // Phase 92 P0-3: skeleton=None for backward compatibility
        self.cf_loop_pattern2_with_break_impl(condition, _body, _func_name, debug, None, None)
    }

    /// Phase 200-C: Pattern 2 implementation with optional fn_body for capture analysis
    /// Phase 92 P0-3: Added skeleton parameter for ConditionalStep support
    fn cf_loop_pattern2_with_break_impl(
        &mut self,
        condition: &ASTNode,
        _body: &[ASTNode],
        _func_name: &str,
        debug: bool,
        fn_body: Option<&[ASTNode]>,
        skeleton: Option<&crate::mir::loop_canonicalizer::LoopSkeleton>,
    ) -> Result<Option<ValueId>, String> {
        super::pattern2_lowering_orchestrator::Pattern2LoweringOrchestrator::run(
            self,
            condition,
            _body,
            _func_name,
            debug,
            fn_body,
            skeleton,
        )
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::ast::{BinaryOperator, LiteralValue, Span};
    use crate::mir::builder::control_flow::joinir::patterns::router::LoopPatternContext;
    use crate::mir::loop_pattern_detection::LoopPatternKind;

    fn span() -> Span {
        Span::unknown()
    }

    fn var(name: &str) -> ASTNode {
        ASTNode::Variable {
            name: name.to_string(),
            span: span(),
        }
    }

    fn lit_i(value: i64) -> ASTNode {
        ASTNode::Literal {
            value: LiteralValue::Integer(value),
            span: span(),
        }
    }

    fn bin(op: BinaryOperator, left: ASTNode, right: ASTNode) -> ASTNode {
        ASTNode::BinaryOp {
            operator: op,
            left: Box::new(left),
            right: Box::new(right),
            span: span(),
        }
    }

    #[test]
    #[cfg(feature = "normalized_dev")]
    fn phase78_promoted_binding_is_recorded_for_digitpos() {
        use super::super::pattern_pipeline::{build_pattern_context, PatternVariant};
        use crate::ast::Span;
        use crate::mir::ValueId;
        use super::super::pattern2_inputs_facts_box::Pattern2InputsFactsBox;
        use super::super::pattern2_steps::apply_policy_step_box::ApplyPolicyStepBox;

        let mut builder = MirBuilder::new();
        builder
            .variable_ctx
            .variable_map
            .insert("i".to_string(), ValueId(1));
        builder
            .variable_ctx
            .variable_map
            .insert("len".to_string(), ValueId(2));
        builder
            .variable_ctx
            .variable_map
            .insert("s".to_string(), ValueId(3));
        builder
            .variable_ctx
            .variable_map
            .insert("digits".to_string(), ValueId(4));
        builder
            .variable_ctx
            .variable_map
            .insert("result".to_string(), ValueId(5));

        let condition = bin(BinaryOperator::Less, var("i"), var("len"));

        let local_ch = ASTNode::Local {
            variables: vec!["ch".to_string()],
            initial_values: vec![Some(Box::new(ASTNode::MethodCall {
                object: Box::new(var("s")),
                method: "substring".to_string(),
                arguments: vec![],
                span: Span::unknown(),
            }))],
            span: Span::unknown(),
        };
        let local_digit_pos = ASTNode::Local {
            variables: vec!["digit_pos".to_string()],
            initial_values: vec![Some(Box::new(ASTNode::MethodCall {
                object: Box::new(var("digits")),
                method: "indexOf".to_string(),
                arguments: vec![var("ch")],
                span: Span::unknown(),
            }))],
            span: Span::unknown(),
        };

        let break_if = ASTNode::If {
            condition: Box::new(bin(BinaryOperator::Less, var("digit_pos"), lit_i(0))),
            then_body: vec![ASTNode::Break {
                span: Span::unknown(),
            }],
            else_body: None,
            span: Span::unknown(),
        };

        let body = vec![
            local_ch,
            local_digit_pos,
            break_if,
            ASTNode::Assignment {
                target: Box::new(var("result")),
                value: Box::new(bin(
                    BinaryOperator::Add,
                    bin(BinaryOperator::Multiply, var("result"), lit_i(10)),
                    var("digit_pos"),
                )),
                span: Span::unknown(),
            },
            ASTNode::Assignment {
                target: Box::new(var("i")),
                value: Box::new(bin(BinaryOperator::Add, var("i"), lit_i(1))),
                span: Span::unknown(),
            },
        ];

        let ctx = build_pattern_context(&mut builder, &condition, &body, PatternVariant::Pattern2)
            .expect("build_pattern_context");
        let facts =
            Pattern2InputsFactsBox::analyze(&builder, &condition, &body, None, &ctx, false)
                .expect("Pattern2InputsFactsBox::analyze");
        let mut inputs = ApplyPolicyStepBox::apply(&condition, &body, facts)
            .expect("ApplyPolicyStepBox::apply");
        PromoteStepBox::promote_and_prepare_carriers(&mut builder, &condition, &body, &mut inputs, false, false)
            .expect("promote_and_prepare_carriers");

        assert!(
            inputs
                .carrier_info
                .promoted_loopbodylocals
                .contains(&"digit_pos".to_string()),
            "digit_pos should be recorded as promoted"
        );
        assert_eq!(
            inputs.carrier_info.promoted_bindings.len(),
            1,
            "promoted_bindings should contain exactly one mapping"
        );

        let (original_bid, promoted_bid) = inputs
            .carrier_info
            .promoted_bindings
            .iter()
            .next()
            .map(|(k, v)| (*k, *v))
            .unwrap();

        let promoted_carrier = inputs
            .carrier_info
            .find_carrier("is_digit_pos")
            .expect("promoted carrier exists");

        assert_eq!(
            promoted_carrier.binding_id,
            Some(promoted_bid),
            "CarrierVar.binding_id should be set for promoted carrier"
        );

        let promoted_join_id = promoted_carrier.join_id.expect("join_id allocated");
        assert_eq!(
            inputs.env.binding_id_map.get(&promoted_bid).copied(),
            Some(promoted_join_id),
            "ConditionEnv should register promoted binding_id -> join_id"
        );
        assert_eq!(
            inputs.env.get("digit_pos"),
            Some(promoted_join_id),
            "Name-based alias (digit_pos -> is_digit_pos) should be installed for legacy paths"
        );

        // Ensure the mapping is not degenerate.
        assert_ne!(original_bid, promoted_bid);
    }

    #[test]
    fn parse_number_like_loop_is_routed_to_pattern2() {
        let condition = bin(BinaryOperator::Less, var("p"), var("len"));
        let break_cond = bin(BinaryOperator::Less, var("digit_pos"), lit_i(0));

        let body = vec![
            ASTNode::If {
                condition: Box::new(break_cond),
                then_body: vec![ASTNode::Break { span: span() }],
                else_body: None,
                span: span(),
            },
            ASTNode::Assignment {
                target: Box::new(var("p")),
                value: Box::new(bin(BinaryOperator::Add, var("p"), lit_i(1))),
                span: span(),
            },
            // num_str = num_str + ch (string append is allowed by CommonPatternInitializer)
            ASTNode::Assignment {
                target: Box::new(var("num_str")),
                value: Box::new(bin(BinaryOperator::Add, var("num_str"), var("ch"))),
                span: span(),
            },
        ];

        let ctx = LoopPatternContext::new(&condition, &body, "parse_number_like", true, false);
        let builder = MirBuilder::new();

        assert_eq!(ctx.pattern_kind, LoopPatternKind::Pattern2Break);
        assert!(
            can_lower(&builder, &ctx),
            "Pattern2 lowerer should accept JsonParser-like break loop"
        );
    }

    #[test]
    fn test_atoi_loop_routed_to_pattern2() {
        // Phase 246-EX Step 2: _atoi loop router integration test
        // loop(i < len) {
        //     if digit_pos < 0 { break }
        //     result = result * 10 + digit_pos  // NumberAccumulation
        //     i = i + 1
        // }

        let condition = bin(BinaryOperator::Less, var("i"), var("len"));
        let break_cond = bin(BinaryOperator::Less, var("digit_pos"), lit_i(0));

        // result = result * 10 + digit_pos (NumberAccumulation pattern)
        let mul_expr = bin(BinaryOperator::Multiply, var("result"), lit_i(10));
        let result_update_value = bin(BinaryOperator::Add, mul_expr, var("digit_pos"));

        let body = vec![
            ASTNode::If {
                condition: Box::new(break_cond),
                then_body: vec![ASTNode::Break { span: span() }],
                else_body: None,
                span: span(),
            },
            ASTNode::Assignment {
                target: Box::new(var("result")),
                value: Box::new(result_update_value),
                span: span(),
            },
            ASTNode::Assignment {
                target: Box::new(var("i")),
                value: Box::new(bin(BinaryOperator::Add, var("i"), lit_i(1))),
                span: span(),
            },
        ];

        let ctx = LoopPatternContext::new(&condition, &body, "_atoi", true, false);
        let builder = MirBuilder::new();

        // Verify pattern classification
        assert_eq!(
            ctx.pattern_kind,
            LoopPatternKind::Pattern2Break,
            "_atoi loop should be classified as Pattern2Break"
        );

        // Verify Pattern2 lowerer accepts it
        assert!(
            can_lower(&builder, &ctx),
            "Pattern2 lowerer should accept _atoi loop with NumberAccumulation"
        );
    }
}
