//! Phase 231: Expression Lowering with Unified Scope Management
//!
//! This module provides a pilot implementation of expression lowering that uses
//! ScopeManager for variable resolution. It's a thin wrapper around existing
//! condition_lowerer logic, focusing on API unification rather than reimplementation.
//!
//! ## Design Philosophy
//!
//! **Box-First**: ExprLowerer is a "box" that encapsulates expression lowering
//! logic with clean boundaries: takes AST + ScopeManager, returns ValueId + instructions.
//!
//! **Incremental Adoption**: Phase 231 starts with Condition context only.
//! Future phases will expand to support General expressions (method calls, etc.).
//!
//! **Fail-Safe**: Unsupported AST nodes return explicit errors, allowing callers
//! to fall back to legacy paths.

use super::condition_lowerer::lower_condition_to_joinir_no_body_locals;
use super::scope_manager::ScopeManager;
use crate::ast::ASTNode;
use crate::mir::builder::MirBuilder;
use crate::mir::join_ir::JoinInst;
use crate::mir::ValueId;

mod ast_support;
mod scope_resolution;
#[cfg(test)]
mod test_helpers;

/// Phase 231: Expression lowering context
///
/// Defines the context in which an expression is being lowered, which affects
/// what AST nodes are supported and how they're translated.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ExprContext {
    /// Loop condition expression (limited subset: comparisons, logical ops)
    Condition,

    /// General expression (future: method calls, box ops, etc.)
    #[allow(dead_code)] // Phase 231: Not yet implemented
    General,
}

/// Phase 231: Expression lowering error
///
/// Explicit error types allow callers to handle different failure modes
/// (e.g., fall back to legacy path for unsupported nodes).
#[derive(Debug)]
pub enum ExprLoweringError {
    /// AST node type not supported in this context
    UnsupportedNode(String),

    /// Variable not found in any scope
    VariableNotFound(String),

    /// Type error during lowering (e.g., non-boolean in condition)
    TypeError(String),

    /// Internal lowering error (from condition_lowerer)
    LoweringError(String),
}

impl std::fmt::Display for ExprLoweringError {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            ExprLoweringError::UnsupportedNode(msg) => write!(f, "Unsupported node: {}", msg),
            ExprLoweringError::VariableNotFound(name) => write!(f, "Variable not found: {}", name),
            ExprLoweringError::TypeError(msg) => write!(f, "Type error: {}", msg),
            ExprLoweringError::LoweringError(msg) => write!(f, "Lowering error: {}", msg),
        }
    }
}

/// Phase 231: Expression lowerer (pilot implementation)
///
/// This struct provides a unified interface for lowering AST expressions to
/// JoinIR instructions, using ScopeManager for variable resolution.
///
/// ## Current Scope (Phase 231)
///
/// - **Context**: Condition only (loop/break conditions)
/// - **Supported**: Literals, variables, comparisons (<, >, ==, !=, <=, >=), logical ops (and, or, not)
/// - **Not Supported**: Method calls, NewBox, complex expressions
///
/// ## Usage Pattern
///
/// ```ignore
/// let scope = Pattern2ScopeManager { ... };
/// let mut expr_lowerer = ExprLowerer::new(&scope, ExprContext::Condition, builder);
///
/// match expr_lowerer.lower(&break_condition_ast) {
///     Ok(value_id) => {
///         // Use value_id in JoinIR
///     }
///     Err(ExprLoweringError::UnsupportedNode(_)) => {
///         // Fall back to legacy condition_to_joinir path
///     }
///     Err(e) => {
///         // Handle other errors (variable not found, etc.)
///     }
/// }
/// ```
pub struct ExprLowerer<'env, 'builder, S: ScopeManager> {
    /// Scope manager for variable resolution
    scope: &'env S,

    /// Expression context (Condition vs General)
    context: ExprContext,

    /// MIR builder (for ValueId allocation, not used in Phase 231)
    #[allow(dead_code)] // Phase 231: Reserved for future use
    builder: &'builder mut MirBuilder,

    /// Debug flag (inherited from caller)
    debug: bool,

    /// Last lowered instruction sequence (for testing/inspection)
    ///
    /// Phase 235: Tests can inspect this to assert that appropriate Compare / BinOp / Not
    /// instructions are emitted for supported patterns. Productionコードからは未使用。
    last_instructions: Vec<JoinInst>,
}

impl<'env, 'builder, S: ScopeManager> ExprLowerer<'env, 'builder, S> {
    /// Create a new expression lowerer
    ///
    /// # Arguments
    ///
    /// * `scope` - ScopeManager for variable resolution
    /// * `context` - Expression context (Condition or General)
    /// * `builder` - MIR builder (for future use)
    pub fn new(scope: &'env S, context: ExprContext, builder: &'builder mut MirBuilder) -> Self {
        Self {
            scope,
            context,
            builder,
            debug: false,
            last_instructions: Vec::new(),
        }
    }

    /// Enable debug output
    pub fn with_debug(mut self, debug: bool) -> Self {
        self.debug = debug;
        self
    }

    /// Take the last lowered instruction sequence (mainly for tests)
    ///
    /// Phase 235: This allows unit tests to validate that Compare / BinOp / Not
    /// instructions are present without影響を与えずに ExprLowerer の外から観察できる。
    pub fn take_last_instructions(&mut self) -> Vec<JoinInst> {
        std::mem::take(&mut self.last_instructions)
    }

    /// Lower an expression to JoinIR ValueId
    ///
    /// Phase 231: This is the main entry point. Currently delegates to
    /// lower_condition for Condition context.
    ///
    /// # Returns
    ///
    /// * `Ok(ValueId)` - Expression result ValueId
    /// * `Err(ExprLoweringError)` - Lowering failed (caller can fall back to legacy)
    pub fn lower(&mut self, ast: &ASTNode) -> Result<ValueId, ExprLoweringError> {
        match self.context {
            ExprContext::Condition => self.lower_condition(ast),
            ExprContext::General => Err(ExprLoweringError::UnsupportedNode(
                "General expression context not yet implemented (Phase 231)".to_string(),
            )),
        }
    }

    /// Lower a condition expression to JoinIR ValueId
    ///
    /// Phase 231: Thin wrapper around condition_lowerer. The main innovation
    /// is using ScopeManager for variable resolution instead of direct ConditionEnv.
    ///
    /// # Returns
    ///
    /// * `Ok(ValueId)` - Condition result ValueId (boolean)
    /// * `Err(ExprLoweringError)` - Lowering failed
    fn lower_condition(&mut self, ast: &ASTNode) -> Result<ValueId, ExprLoweringError> {
        // 1. Check if AST is supported in condition context
        if !ast_support::is_supported_condition(ast) {
            return Err(ExprLoweringError::UnsupportedNode(format!(
                "Unsupported condition node: {:?}",
                ast
            )));
        }

        // 2. Build ConditionEnv from ScopeManager
        // Phase 79-1: Use BindingId-aware version when available
        #[cfg(feature = "normalized_dev")]
        let condition_env = scope_resolution::build_condition_env_from_scope_with_binding(
            self.scope,
            ast,
            self.builder,
        )?;

        #[cfg(not(feature = "normalized_dev"))]
        let condition_env = scope_resolution::build_condition_env_from_scope(self.scope, ast)?;

        // 3. Delegate to existing condition_lowerer
        // Phase 231: We use the existing, well-tested lowering logic.
        let mut value_counter = 1000u32; // Phase 231: Start high to avoid collisions
        let mut alloc_value = || {
            let id = ValueId(value_counter);
            value_counter += 1;
            id
        };

        let (result_value, instructions) =
            lower_condition_to_joinir_no_body_locals(ast, &mut alloc_value, &condition_env) // Phase 92 P2-2
                .map_err(|e| ExprLoweringError::LoweringError(e))?;

        // Phase 235: 保存しておき、テストから観察できるようにする
        self.last_instructions = instructions;

        if self.debug {
            eprintln!(
                "[expr_lowerer/phase231] Lowered condition → ValueId({:?})",
                result_value
            );
        }

        Ok(result_value)
    }

    /// Public helper used by Pattern2/3 callers to gate ExprLowerer usage.
    pub fn is_supported_condition(ast: &ASTNode) -> bool {
        ast_support::is_supported_condition(ast)
    }
}

// ============================================================================
// Phase 244: ConditionLoweringBox trait implementation
// ============================================================================

use super::condition_lowering_box::{ConditionContext, ConditionLoweringBox};

impl<'env, 'builder, S: ScopeManager> ConditionLoweringBox<S> for ExprLowerer<'env, 'builder, S> {
    /// Phase 244: Implement ConditionLoweringBox trait for ExprLowerer
    ///
    /// This allows ExprLowerer to be used interchangeably with other condition
    /// lowering implementations through the unified ConditionLoweringBox interface.
    ///
    /// # Design
    ///
    /// This implementation is a thin wrapper around the existing `lower()` method.
    /// The `ConditionContext` parameter is currently unused because ExprLowerer
    /// already has access to ScopeManager through its constructor.
    ///
    /// # Example
    ///
    /// ```ignore
    /// // Pattern 2: Use ExprLowerer via ConditionLoweringBox trait
    /// let mut lowerer = ExprLowerer::new(&scope, ExprContext::Condition, &mut builder);
    ///
    /// let context = ConditionContext {
    ///     loop_var_name: "i".to_string(),
    ///     loop_var_id: ValueId(1),
    ///     scope: &scope,
    ///     alloc_value: &mut alloc_fn,
    /// };
    ///
    /// let cond_value = lowerer.lower_condition(&break_cond_ast, &context)?;
    /// ```
    fn lower_condition(
        &mut self,
        condition: &ASTNode,
        context: &mut ConditionContext<S>,
    ) -> Result<ValueId, String> {
        // Phase 244+ / Phase 201 SSOT: ValueId allocation must be coordinated by the caller.
        //
        // JoinIR lowering uses JoinValueSpace as SSOT for ValueId regions.
        // If we allocate locally here (e.g. starting from 1000), we can collide with
        // other JoinIR value users (main params, carrier slots), and after remapping
        // this becomes a MIR-level ValueId collision.
        if !ast_support::is_supported_condition(condition) {
            return Err(format!("Unsupported condition node: {:?}", condition));
        }

        // Build ConditionEnv from the provided scope (the caller controls the scope + allocator).
        #[cfg(feature = "normalized_dev")]
        let condition_env = scope_resolution::build_condition_env_from_scope_with_binding(
            context.scope,
            condition,
            self.builder,
        )
        .map_err(|e| e.to_string())?;

        #[cfg(not(feature = "normalized_dev"))]
        let condition_env =
            scope_resolution::build_condition_env_from_scope(context.scope, condition)
                .map_err(|e| e.to_string())?;

        // Delegate to the well-tested lowerer, but use the caller-provided allocator (SSOT).
        let (result_value, instructions) =
            lower_condition_to_joinir_no_body_locals(condition, &mut *context.alloc_value, &condition_env) // Phase 92 P2-2
                .map_err(|e| e.to_string())?;

        self.last_instructions = instructions;

        if self.debug {
            eprintln!(
                "[expr_lowerer/phase244] Lowered condition → ValueId({:?}) (context alloc)",
                result_value
            );
        }

        Ok(result_value)
    }

    /// Phase 244: Check if ExprLowerer supports a given condition pattern
    ///
    /// This delegates to the existing `is_supported_condition()` static method,
    /// allowing callers to check support before attempting lowering.
    fn supports(&self, condition: &ASTNode) -> bool {
        ast_support::is_supported_condition(condition)
    }
}

#[cfg(test)]
mod tests {
    use super::test_helpers::{bin, lit_i, span, var};
    use super::*;
    use crate::ast::{BinaryOperator, LiteralValue, Span, UnaryOperator};
    use crate::mir::join_ir::lowering::carrier_info::CarrierInfo;
    use crate::mir::join_ir::lowering::condition_env::ConditionEnv;
    use crate::mir::join_ir::lowering::scope_manager::Pattern2ScopeManager;
    use crate::mir::join_ir::{BinOpKind, MirLikeInst, UnaryOp as JoinUnaryOp};

    // Helper to create a test MirBuilder (Phase 231: minimal stub)
    fn create_test_builder() -> MirBuilder {
        MirBuilder::new()
    }

    fn not(expr: ASTNode) -> ASTNode {
        ASTNode::UnaryOp {
            operator: UnaryOperator::Not,
            operand: Box::new(expr),
            span: span(),
        }
    }

    #[test]
    fn test_expr_lowerer_simple_comparison() {
        let mut condition_env = ConditionEnv::new();
        condition_env.insert("i".to_string(), ValueId(100));

        let carrier_info = CarrierInfo {
            loop_var_name: "i".to_string(),
            loop_var_id: ValueId(1),
            carriers: vec![],
            trim_helper: None,
            promoted_loopbodylocals: vec![],
            #[cfg(feature = "normalized_dev")]
            promoted_bindings: std::collections::BTreeMap::new(),
        };

        let scope = Pattern2ScopeManager {
            condition_env: &condition_env,
            loop_body_local_env: None,
            captured_env: None,
            carrier_info: &carrier_info,
        };

        let mut builder = create_test_builder();

        // AST: i < 10
        let ast = ASTNode::BinaryOp {
            operator: BinaryOperator::Less,
            left: Box::new(ASTNode::Variable {
                name: "i".to_string(),
                span: Span::unknown(),
            }),
            right: Box::new(ASTNode::Literal {
                value: LiteralValue::Integer(10),
                span: Span::unknown(),
            }),
            span: Span::unknown(),
        };

        let mut expr_lowerer = ExprLowerer::new(&scope, ExprContext::Condition, &mut builder);
        let result = expr_lowerer.lower(&ast);

        assert!(
            result.is_ok(),
            "Should lower simple comparison successfully"
        );
    }

    #[test]
    fn test_expr_lowerer_variable_not_found() {
        let condition_env = ConditionEnv::new();

        let carrier_info = CarrierInfo {
            loop_var_name: "i".to_string(),
            loop_var_id: ValueId(1),
            carriers: vec![],
            trim_helper: None,
            promoted_loopbodylocals: vec![],
            #[cfg(feature = "normalized_dev")]
            promoted_bindings: std::collections::BTreeMap::new(),
        };

        let scope = Pattern2ScopeManager {
            condition_env: &condition_env,
            loop_body_local_env: None,
            captured_env: None,
            carrier_info: &carrier_info,
        };

        let mut builder = create_test_builder();

        // AST: unknown_var < 10
        let ast = ASTNode::BinaryOp {
            operator: BinaryOperator::Less,
            left: Box::new(ASTNode::Variable {
                name: "unknown_var".to_string(),
                span: Span::unknown(),
            }),
            right: Box::new(ASTNode::Literal {
                value: LiteralValue::Integer(10),
                span: Span::unknown(),
            }),
            span: Span::unknown(),
        };

        let mut expr_lowerer = ExprLowerer::new(&scope, ExprContext::Condition, &mut builder);
        let result = expr_lowerer.lower(&ast);

        assert!(matches!(
            result,
            Err(ExprLoweringError::VariableNotFound(_))
        ));
    }

    #[test]
    fn test_expr_lowerer_unsupported_node() {
        let condition_env = ConditionEnv::new();

        let carrier_info = CarrierInfo {
            loop_var_name: "i".to_string(),
            loop_var_id: ValueId(1),
            carriers: vec![],
            trim_helper: None,
            promoted_loopbodylocals: vec![],
            #[cfg(feature = "normalized_dev")]
            promoted_bindings: std::collections::BTreeMap::new(),
        };

        let scope = Pattern2ScopeManager {
            condition_env: &condition_env,
            loop_body_local_env: None,
            captured_env: None,
            carrier_info: &carrier_info,
        };

        let mut builder = create_test_builder();

        // AST: Break (unsupported in condition context)
        let ast = ASTNode::Break {
            span: Span::unknown(),
        };

        let mut expr_lowerer = ExprLowerer::new(&scope, ExprContext::Condition, &mut builder);
        let result = expr_lowerer.lower(&ast);

        assert!(matches!(result, Err(ExprLoweringError::UnsupportedNode(_))));
    }

    #[test]
    fn test_is_supported_condition() {
        // Supported: i < 10
        let ast = ASTNode::BinaryOp {
            operator: BinaryOperator::Less,
            left: Box::new(ASTNode::Variable {
                name: "i".to_string(),
                span: Span::unknown(),
            }),
            right: Box::new(ASTNode::Literal {
                value: LiteralValue::Integer(10),
                span: Span::unknown(),
            }),
            span: Span::unknown(),
        };
        assert!(ExprLowerer::<Pattern2ScopeManager>::is_supported_condition(
            &ast
        ));

        // Supported: MethodCall
        let ast = ASTNode::MethodCall {
            object: Box::new(ASTNode::Variable {
                name: "s".to_string(),
                span: Span::unknown(),
            }),
            method: "length".to_string(),
            arguments: vec![],
            span: Span::unknown(),
        };
        assert!(ExprLowerer::<Pattern2ScopeManager>::is_supported_condition(
            &ast
        ));

        // Unsupported: Break node
        let ast = ASTNode::Break {
            span: Span::unknown(),
        };
        assert!(!ExprLowerer::<Pattern2ScopeManager>::is_supported_condition(&ast));
    }

    // Phase 235: Additional patterns for condition lowering

    fn make_basic_scope() -> Pattern2ScopeManager<'static> {
        // NOTE: we leak these small envs for the duration of the test to satisfy lifetimes simply.
        // テスト専用なので許容する。
        let mut condition_env = ConditionEnv::new();
        condition_env.insert("i".to_string(), ValueId(1));
        condition_env.insert("j".to_string(), ValueId(2));

        let boxed_env: Box<ConditionEnv> = Box::new(condition_env);
        let condition_env_ref: &'static ConditionEnv = Box::leak(boxed_env);

        let carrier_info = CarrierInfo {
            loop_var_name: "i".to_string(),
            loop_var_id: ValueId(1),
            carriers: vec![],
            trim_helper: None,
            promoted_loopbodylocals: vec![],
            #[cfg(feature = "normalized_dev")]
            promoted_bindings: std::collections::BTreeMap::new(),
        };
        let boxed_carrier: Box<CarrierInfo> = Box::new(carrier_info);
        let carrier_ref: &'static CarrierInfo = Box::leak(boxed_carrier);

        Pattern2ScopeManager {
            condition_env: condition_env_ref,
            loop_body_local_env: None,
            captured_env: None,
            carrier_info: carrier_ref,
        }
    }

    fn make_scope_with_p_and_s() -> Pattern2ScopeManager<'static> {
        // Leak these tiny envs for test lifetime convenience only.
        let mut condition_env = ConditionEnv::new();
        condition_env.insert("p".to_string(), ValueId(1));
        condition_env.insert("s".to_string(), ValueId(2));

        let boxed_env: Box<ConditionEnv> = Box::new(condition_env);
        let condition_env_ref: &'static ConditionEnv = Box::leak(boxed_env);

        let carrier_info = CarrierInfo {
            loop_var_name: "p".to_string(),
            loop_var_id: ValueId(1),
            carriers: vec![],
            trim_helper: None,
            promoted_loopbodylocals: vec![],
            #[cfg(feature = "normalized_dev")]
            promoted_bindings: std::collections::BTreeMap::new(),
        };
        let boxed_carrier: Box<CarrierInfo> = Box::new(carrier_info);
        let carrier_ref: &'static CarrierInfo = Box::leak(boxed_carrier);

        Pattern2ScopeManager {
            condition_env: condition_env_ref,
            loop_body_local_env: None,
            captured_env: None,
            carrier_info: carrier_ref,
        }
    }

    fn assert_has_compare(instructions: &[JoinInst]) {
        assert!(
            instructions
                .iter()
                .any(|inst| matches!(inst, JoinInst::Compute(MirLikeInst::Compare { .. }))),
            "Expected at least one Compare instruction, got {:?}",
            instructions
        );
    }

    fn assert_has_binop(instructions: &[JoinInst], op: BinOpKind) {
        assert!(
            instructions.iter().any(|inst| matches!(
                inst,
                JoinInst::Compute(MirLikeInst::BinOp { op: o, .. } ) if *o == op
            )),
            "Expected at least one BinOp {:?}, got {:?}",
            op,
            instructions
        );
    }

    fn assert_has_not(instructions: &[JoinInst]) {
        assert!(
            instructions.iter().any(|inst| matches!(
                inst,
                JoinInst::Compute(MirLikeInst::UnaryOp {
                    op: JoinUnaryOp::Not,
                    ..
                })
            )),
            "Expected at least one UnaryOp::Not, got {:?}",
            instructions
        );
    }

    #[test]
    fn test_expr_lowerer_var_less_literal_generates_compare() {
        let scope = make_basic_scope();
        let mut builder = create_test_builder();

        // i < 10
        let ast = bin(BinaryOperator::Less, var("i"), lit_i(10));

        let mut expr_lowerer = ExprLowerer::new(&scope, ExprContext::Condition, &mut builder);
        let result = expr_lowerer.lower(&ast);
        assert!(result.is_ok(), "i < 10 should lower successfully");

        let instructions = expr_lowerer.take_last_instructions();
        assert!(!instructions.is_empty(), "instructions should not be empty");
        assert_has_compare(&instructions);
    }

    #[test]
    fn test_expr_lowerer_literal_less_var_generates_compare() {
        let scope = make_basic_scope();
        let mut builder = create_test_builder();

        // 0 < i
        let ast = bin(BinaryOperator::Less, lit_i(0), var("i"));

        let mut expr_lowerer = ExprLowerer::new(&scope, ExprContext::Condition, &mut builder);
        let result = expr_lowerer.lower(&ast);
        assert!(result.is_ok(), "0 < i should lower successfully");

        let instructions = expr_lowerer.take_last_instructions();
        assert!(!instructions.is_empty(), "instructions should not be empty");
        assert_has_compare(&instructions);
    }

    #[test]
    fn test_expr_lowerer_greater_than_between_vars() {
        let scope = make_basic_scope();
        let mut builder = create_test_builder();

        // i > j
        let ast = bin(BinaryOperator::Greater, var("i"), var("j"));

        let mut expr_lowerer = ExprLowerer::new(&scope, ExprContext::Condition, &mut builder);
        let result = expr_lowerer.lower(&ast);
        assert!(result.is_ok(), "i > j should lower successfully");

        let instructions = expr_lowerer.take_last_instructions();
        assert!(!instructions.is_empty(), "instructions should not be empty");
        assert_has_compare(&instructions);
    }

    #[test]
    fn test_expr_lowerer_and_combination() {
        let scope = make_basic_scope();
        let mut builder = create_test_builder();

        // i > 0 && j < 5
        let left = bin(BinaryOperator::Greater, var("i"), lit_i(0));
        let right = bin(BinaryOperator::Less, var("j"), lit_i(5));
        let ast = bin(BinaryOperator::And, left, right);

        let mut expr_lowerer = ExprLowerer::new(&scope, ExprContext::Condition, &mut builder);
        let result = expr_lowerer.lower(&ast);
        assert!(result.is_ok(), "i > 0 && j < 5 should lower successfully");

        let instructions = expr_lowerer.take_last_instructions();
        assert!(!instructions.is_empty(), "instructions should not be empty");
        assert_has_compare(&instructions);
        assert_has_binop(&instructions, BinOpKind::And);
    }

    #[test]
    fn test_expr_lowerer_not_of_comparison() {
        let scope = make_basic_scope();
        let mut builder = create_test_builder();

        // !(i < 10)
        let inner = bin(BinaryOperator::Less, var("i"), lit_i(10));
        let ast = not(inner);

        let mut expr_lowerer = ExprLowerer::new(&scope, ExprContext::Condition, &mut builder);
        let result = expr_lowerer.lower(&ast);
        assert!(result.is_ok(), "!(i < 10) should lower successfully");

        let instructions = expr_lowerer.take_last_instructions();
        assert!(!instructions.is_empty(), "instructions should not be empty");
        assert_has_compare(&instructions);
        assert_has_not(&instructions);
    }

    #[test]
    fn test_expr_lowerer_pattern2_break_digit_pos_less_zero_generates_compare() {
        let mut condition_env = ConditionEnv::new();
        condition_env.insert("digit_pos".to_string(), ValueId(10));

        let carrier_info = CarrierInfo {
            loop_var_name: "i".to_string(),
            loop_var_id: ValueId(1),
            carriers: vec![],
            trim_helper: None,
            promoted_loopbodylocals: vec![],
            #[cfg(feature = "normalized_dev")]
            promoted_bindings: std::collections::BTreeMap::new(),
        };

        let scope = Pattern2ScopeManager {
            condition_env: &condition_env,
            loop_body_local_env: None,
            captured_env: None,
            carrier_info: &carrier_info,
        };

        let mut builder = create_test_builder();

        // digit_pos < 0
        let ast = bin(BinaryOperator::Less, var("digit_pos"), lit_i(0));
        assert!(
            ExprLowerer::<Pattern2ScopeManager>::is_supported_condition(&ast),
            "digit_pos < 0 should be supported in Pattern2 break condition"
        );

        let mut expr_lowerer = ExprLowerer::new(&scope, ExprContext::Condition, &mut builder);
        let result = expr_lowerer.lower(&ast);
        assert!(result.is_ok(), "digit_pos < 0 should lower successfully");

        let instructions = expr_lowerer.take_last_instructions();
        assert!(
            !instructions.is_empty(),
            "instructions for digit_pos < 0 should not be empty"
        );
        assert_has_compare(&instructions);
    }

    #[test]
    fn test_expr_lowerer_methodcall_unknown_method_is_rejected() {
        let scope = make_scope_with_p_and_s();
        let mut builder = create_test_builder();

        // Unknown method name should fail through MethodCallLowerer
        let ast = ASTNode::MethodCall {
            object: Box::new(var("s")),
            method: "unknown_method".to_string(),
            arguments: vec![],
            span: span(),
        };

        assert!(
            ExprLowerer::<Pattern2ScopeManager>::is_supported_condition(&ast),
            "MethodCall nodes should be routed to MethodCallLowerer for validation"
        );

        let mut expr_lowerer = ExprLowerer::new(&scope, ExprContext::Condition, &mut builder);
        let result = expr_lowerer.lower(&ast);

        assert!(
            matches!(result, Err(ExprLoweringError::LoweringError(msg)) if msg.contains("MethodCall")),
            "Unknown method should fail-fast via MethodCallLowerer"
        );
    }

    // Phase 240-EX: Header condition patterns

    #[test]
    fn test_expr_lowerer_supports_simple_header_condition_i_less_literal() {
        // header pattern: i < 10
        let ast = bin(BinaryOperator::Less, var("i"), lit_i(10));
        assert!(
            ExprLowerer::<Pattern2ScopeManager>::is_supported_condition(&ast),
            "i < 10 should be supported for Pattern2 header condition"
        );

        // lower and verify success
        let scope = make_basic_scope();
        let mut builder = create_test_builder();
        let mut lowerer = ExprLowerer::new(&scope, ExprContext::Condition, &mut builder);

        let result = lowerer.lower(&ast);
        assert!(result.is_ok(), "i < 10 should lower successfully");

        // Compare instruction should be present
        let instructions = lowerer.take_last_instructions();
        assert_has_compare(&instructions);
    }

    #[test]
    fn test_expr_lowerer_supports_header_condition_var_less_var() {
        // header pattern: i < n (variable vs variable)
        let ast = bin(BinaryOperator::Less, var("i"), var("j"));
        assert!(
            ExprLowerer::<Pattern2ScopeManager>::is_supported_condition(&ast),
            "i < n should be supported for Pattern2 header condition"
        );

        // lower and verify success
        let scope = make_basic_scope();
        let mut builder = create_test_builder();
        let mut lowerer = ExprLowerer::new(&scope, ExprContext::Condition, &mut builder);

        let result = lowerer.lower(&ast);
        assert!(result.is_ok(), "i < n should lower successfully");

        // Compare instruction should be present
        let instructions = lowerer.take_last_instructions();
        assert_has_compare(&instructions);
    }

    #[test]
    fn test_expr_lowerer_supports_header_condition_with_length_call() {
        // header pattern: p < s.length()
        let length_call = ASTNode::MethodCall {
            object: Box::new(var("s")),
            method: "length".to_string(),
            arguments: vec![],
            span: span(),
        };
        let ast = bin(BinaryOperator::Less, var("p"), length_call);

        assert!(
            ExprLowerer::<Pattern2ScopeManager>::is_supported_condition(&ast),
            "p < s.length() should be supported for Pattern2 header condition"
        );

        let scope = make_scope_with_p_and_s();
        let mut builder = create_test_builder();
        let mut lowerer = ExprLowerer::new(&scope, ExprContext::Condition, &mut builder);

        let result = lowerer.lower(&ast);
        assert!(result.is_ok(), "p < s.length() should lower successfully");

        let instructions = lowerer.take_last_instructions();
        assert_has_compare(&instructions);
        assert!(
            instructions.iter().any(|inst| matches!(
                inst,
                JoinInst::Compute(MirLikeInst::BoxCall { method, .. }) if method == "length"
            )),
            "Expected BoxCall for length receiver in lowered instructions"
        );
    }

    #[test]
    fn test_expr_lowerer_header_condition_generates_expected_instructions() {
        // Test that header condition i < 10 generates proper Compare instruction
        let scope = make_basic_scope();
        let mut builder = create_test_builder();

        let ast = bin(BinaryOperator::Less, var("i"), lit_i(10));
        let mut lowerer = ExprLowerer::new(&scope, ExprContext::Condition, &mut builder);

        let result = lowerer.lower(&ast);
        assert!(result.is_ok());

        let instructions = lowerer.take_last_instructions();
        assert!(!instructions.is_empty(), "Should generate instructions");

        // Should have Compare instruction
        assert_has_compare(&instructions);
    }
}
