//! PromoteStepBox (Phase 106)
//!
//! Responsibility:
//! - promotion / read-only slot routing for body-local vars in conditions
//! - carrier preparation for JoinIR emission

use crate::ast::ASTNode;
use crate::mir::builder::MirBuilder;
use crate::mir::loop_pattern_detection::error_messages;

use super::super::body_local_policy::{classify_for_pattern2, BodyLocalRoute};
use super::super::pattern2_inputs_facts_box::Pattern2Inputs;
use super::super::policies::PolicyDecision;

pub(crate) struct PromoteStepResult {
    pub inputs: Pattern2Inputs,
}

pub(crate) struct PromoteStepBox;

impl PromoteStepBox {
    pub(crate) fn run(
        builder: &mut MirBuilder,
        condition: &ASTNode,
        body: &[ASTNode],
        mut inputs: Pattern2Inputs,
        debug: bool,
        verbose: bool,
    ) -> Result<PromoteStepResult, String> {
        Self::promote_and_prepare_carriers(builder, condition, body, &mut inputs, debug, verbose)?;
        Ok(PromoteStepResult { inputs })
    }

    pub(in crate::mir::builder) fn promote_and_prepare_carriers(
        builder: &mut MirBuilder,
        condition: &ASTNode,
        body: &[ASTNode],
        inputs: &mut Pattern2Inputs,
        debug: bool,
        verbose: bool,
    ) -> Result<(), String> {
        use crate::mir::join_ir::lowering::digitpos_condition_normalizer::DigitPosConditionNormalizer;
        use crate::mir::loop_pattern_detection::loop_condition_scope::LoopConditionScopeBox;

        let cond_scope = LoopConditionScopeBox::analyze(
            &inputs.loop_var_name,
            &vec![condition, &inputs.break_condition_node],
            Some(&inputs.scope),
        );

        let mut promoted_pairs: Vec<(String, String)> = Vec::new();
        let cond_body_local_vars: Vec<String> = cond_scope
            .vars
            .iter()
            .filter(|v| matches!(v.scope, crate::mir::loop_pattern_detection::loop_condition_scope::CondVarScope::LoopBodyLocal))
            .map(|v| v.name.clone())
            .collect();

        if cond_scope.has_loop_body_local() {
            // Policy-controlled: some families must not run promotion/slot heuristics here.
            // Example: balanced depth-scan uses derived vars and doesn't have a break-guard node.
            if matches!(
                inputs.body_local_handling,
                crate::mir::builder::control_flow::joinir::patterns::pattern2_inputs_facts_box::BodyLocalHandlingPolicy::SkipPromotion
            ) {
                // no-op: lowerers will populate LoopBodyLocalEnv via init/derived emission.
            } else if !inputs.is_loop_true_read_digits {
                match classify_for_pattern2(
                    builder,
                    &inputs.loop_var_name,
                    &inputs.scope,
                    &inputs.break_condition_node,
                    &cond_scope,
                    body,
                ) {
                    PolicyDecision::Use(BodyLocalRoute::Promotion {
                        promoted_carrier,
                        promoted_var,
                        carrier_name,
                    }) => {
                        let is_trim_promotion = promoted_carrier.trim_helper().is_some();
                        if !is_trim_promotion {
                            promoted_pairs.push((promoted_var.clone(), carrier_name.clone()));
                        }

                        #[cfg(feature = "normalized_dev")]
                        {
                            use crate::mir::join_ir::lowering::carrier_binding_assigner::CarrierBindingAssigner;
                            let mut promoted_carrier = promoted_carrier;
                            CarrierBindingAssigner::assign_promoted_binding(
                                builder,
                                &mut promoted_carrier,
                                &promoted_var,
                                &carrier_name,
                            )
                            .map_err(|e| format!("[phase78/binding_assign] {:?}", e))?;
                            inputs.carrier_info.merge_from(&promoted_carrier);
                        }
                        #[cfg(not(feature = "normalized_dev"))]
                        {
                            inputs.carrier_info.merge_from(&promoted_carrier);
                        }

                        inputs
                            .carrier_info
                            .promoted_loopbodylocals
                            .push(promoted_var.clone());

                        if !is_trim_promotion {
                            inputs.break_condition_node = DigitPosConditionNormalizer::normalize(
                                &inputs.break_condition_node,
                                &promoted_var,
                                &carrier_name,
                            );
                        }
                    }
                    PolicyDecision::Use(BodyLocalRoute::ReadOnlySlot(slot)) => {
                        inputs.allowed_body_locals_for_conditions = vec![slot.name.clone()];
                        inputs.read_only_body_local_slot = Some(slot);
                    }
                    PolicyDecision::Reject(reason) => {
                        return Err(error_messages::format_error_pattern2_promotion_failed(
                            &cond_body_local_vars,
                            &reason,
                        ));
                    }
                    PolicyDecision::None => {}
                }
            }
        }

        // Allocate join_ids for carriers and register bindings.
        for carrier in &mut inputs.carrier_info.carriers {
            let carrier_join_id = inputs.join_value_space.alloc_param();
            carrier.join_id = Some(carrier_join_id);
            #[cfg(feature = "normalized_dev")]
            if let Some(binding_id) = carrier.binding_id {
                use crate::mir::join_ir::lowering::carrier_info::CarrierRole;
                match carrier.role {
                    CarrierRole::ConditionOnly => inputs.env.register_condition_binding(binding_id, carrier_join_id),
                    CarrierRole::LoopState => inputs.env.register_carrier_binding(binding_id, carrier_join_id),
                }
            }
        }

        for (promoted_var, promoted_carrier_name) in promoted_pairs {
            let join_id = inputs
                .carrier_info
                .find_carrier(&promoted_carrier_name)
                .and_then(|c| c.join_id)
                .ok_or_else(|| format!("[phase229] promoted carrier '{}' has no join_id", promoted_carrier_name))?;
            inputs.env.insert(promoted_var, join_id);
        }

        // ExprLowerer validation (best-effort; unchanged behavior)
        {
            use crate::mir::join_ir::lowering::expr_lowerer::{ExprContext, ExprLowerer, ExprLoweringError};
            use crate::mir::join_ir::lowering::scope_manager::Pattern2ScopeManager;

            let scope_manager = Pattern2ScopeManager {
                condition_env: &inputs.env,
                loop_body_local_env: Some(&inputs.body_local_env),
                captured_env: Some(&inputs.captured_env),
                carrier_info: &inputs.carrier_info,
            };

            match ExprLowerer::new(&scope_manager, ExprContext::Condition, builder)
                .with_debug(debug)
                .lower(&inputs.break_condition_node)
            {
                Ok(_) => {}
                Err(ExprLoweringError::UnsupportedNode(_)) => {}
                Err(_) => {}
            }
        }

        Ok(())
    }

}
