//! Phase 252: User-Defined Method Policy Box
//!
//! This box provides a Single Source of Truth (SSOT) for determining whether
//! user-defined static box methods are allowed in JoinIR contexts.
//!
//! ## Design Philosophy
//!
//! **Box-First Design**: UserMethodPolicy is a single-responsibility box that
//! answers one question: "Can this static box method be safely lowered to JoinIR?"
//!
//! **Metadata-Driven**: Uses a policy table to determine allowed methods.
//! NO method name hardcoding in lowering logic - all decisions made here.
//!
//! **Fail-Fast**: If a method is not in the policy table, immediately returns false.
//! No silent fallbacks or guessing.
//!
//! **Future Extension**: This SSOT can be moved to .hako annotations or nyash.toml
//! in the future without breaking lowering logic.
//!
//! ## Supported Static Boxes
//!
//! - **StringUtils**: String utility functions (trim, character checks, etc.)
//!
//! ## Example Usage
//!
//! ```ignore
//! // Check if StringUtils.is_whitespace is allowed in condition
//! if UserMethodPolicy::allowed_in_condition("StringUtils", "is_whitespace") {
//!     // Lower this.is_whitespace(...) to JoinIR
//! }
//! ```

/// Phase 252: User-Defined Method Policy Box
///
/// Provides metadata for user-defined static box methods to determine
/// their eligibility for JoinIR lowering in different contexts.
pub struct UserMethodPolicy;

impl UserMethodPolicy {
    /// Check if a user-defined method is allowed in loop condition context
    ///
    /// # Requirements for Condition Context
    ///
    /// - Method must be pure (no side effects)
    /// - Method should return boolean (for use in conditions)
    /// - Method should be deterministic (same inputs → same outputs)
    ///
    /// # Arguments
    ///
    /// * `box_name` - Name of the static box (e.g., "StringUtils")
    /// * `method_name` - Name of the method (e.g., "is_whitespace")
    ///
    /// # Returns
    ///
    /// * `true` - Method is whitelisted for condition context
    /// * `false` - Method is not whitelisted or unknown
    ///
    /// # Example
    ///
    /// ```ignore
    /// // Loop condition: loop(i < n && not this.is_whitespace(ch))
    /// assert!(UserMethodPolicy::allowed_in_condition("StringUtils", "is_whitespace"));
    /// assert!(!UserMethodPolicy::allowed_in_condition("StringUtils", "trim_start"));
    /// ```
    pub fn allowed_in_condition(box_name: &str, method_name: &str) -> bool {
        match box_name {
            "StringUtils" => Self::stringutils_allowed_in_condition(method_name),
            _ => false, // Unknown static box - fail-fast
        }
    }

    /// Check if a user-defined method is allowed in LoopBodyLocal init context
    ///
    /// # Requirements for Init Context
    ///
    /// - Method must be pure (no side effects)
    /// - Method can return any type (strings, integers, etc.)
    /// - Method should be deterministic
    ///
    /// # Arguments
    ///
    /// * `box_name` - Name of the static box (e.g., "StringUtils")
    /// * `method_name` - Name of the method (e.g., "trim_start")
    ///
    /// # Returns
    ///
    /// * `true` - Method is whitelisted for init context
    /// * `false` - Method is not whitelisted or unknown
    ///
    /// # Example
    ///
    /// ```ignore
    /// // LoopBodyLocal init: local ch = s.substring(i, i + 1)
    /// // (substring is allowed in init but not in condition)
    /// assert!(UserMethodPolicy::allowed_in_init("StringUtils", "trim_start"));
    /// ```
    pub fn allowed_in_init(box_name: &str, method_name: &str) -> bool {
        match box_name {
            "StringUtils" => Self::stringutils_allowed_in_init(method_name),
            _ => false, // Unknown static box - fail-fast
        }
    }

    // ========================================================================
    // StringUtils Policy Table
    // ========================================================================

    /// StringUtils methods allowed in condition context
    ///
    /// All methods here are pure boolean-returning functions suitable for
    /// use in loop conditions and conditional expressions.
    ///
    /// # StringUtils Source
    ///
    /// See: `apps/lib/json_native/utils/string.hako`
    fn stringutils_allowed_in_condition(method_name: &str) -> bool {
        matches!(
            method_name,
            // Character classification (pure boolean functions)
            "is_whitespace"      // ch == " " or ch == "\t" or ...
            | "is_digit"         // ch == "0" or ch == "1" or ...
            | "is_hex_digit"     // is_digit(ch) or ch == "a" or ...
            | "is_alpha"         // (ch >= "a" and ch <= "z") or ...
            | "is_alphanumeric"  // is_alpha(ch) or is_digit(ch)

            // String validation (pure boolean functions)
            | "is_integer"       // Checks if string represents an integer
            | "is_empty_or_whitespace"  // trim(s).length() == 0

            // String matching (pure boolean functions)
            | "starts_with"      // s.substring(0, prefix.length()) == prefix
            | "ends_with"        // s.substring(s.length() - suffix.length(), ...) == suffix
            | "contains"         // index_of_string(s, substr) != -1
        )
    }

    /// StringUtils methods allowed in init context
    ///
    /// All methods here are pure functions but may return non-boolean types
    /// (strings, integers). Suitable for LoopBodyLocal initialization.
    ///
    /// # StringUtils Source
    ///
    /// See: `apps/lib/json_native/utils/string.hako`
    fn stringutils_allowed_in_init(method_name: &str) -> bool {
        matches!(
            method_name,
            // Whitespace handling (pure string functions)
            "trim"               // s.trim() (VM StringBox method)
            | "trim_start"       // Remove leading whitespace
            | "trim_end"         // Remove trailing whitespace

            // String search (pure integer-returning functions)
            | "index_of"         // First occurrence of character (-1 if not found)
            | "last_index_of"    // Last occurrence of character (-1 if not found)
            | "index_of_string"  // First occurrence of substring (-1 if not found)

            // String transformation (pure string functions)
            | "to_upper"         // Convert string to uppercase
            | "to_lower"         // Convert string to lowercase
            | "char_to_upper"    // Convert single character to uppercase
            | "char_to_lower"    // Convert single character to lowercase

            // String manipulation (pure functions)
            | "join"             // Join array elements with separator
            | "split"            // Split string by separator

            // Numeric parsing (pure functions)
            | "parse_float"      // Parse floating-point number (currently identity)
            | "parse_integer"    // Parse integer from string

            // Character classification (also allowed in init)
            | "is_whitespace"
            | "is_digit"
            | "is_hex_digit"
            | "is_alpha"
            | "is_alphanumeric"
            | "is_integer"
            | "is_empty_or_whitespace"
            | "starts_with"
            | "ends_with"
            | "contains"
        )
    }
}

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

    // ===== Condition Context Tests =====

    #[test]
    fn test_stringutils_character_classification_in_condition() {
        // Pure boolean character classification methods should be allowed
        assert!(UserMethodPolicy::allowed_in_condition("StringUtils", "is_whitespace"));
        assert!(UserMethodPolicy::allowed_in_condition("StringUtils", "is_digit"));
        assert!(UserMethodPolicy::allowed_in_condition("StringUtils", "is_hex_digit"));
        assert!(UserMethodPolicy::allowed_in_condition("StringUtils", "is_alpha"));
        assert!(UserMethodPolicy::allowed_in_condition("StringUtils", "is_alphanumeric"));
    }

    #[test]
    fn test_stringutils_validation_in_condition() {
        // Pure boolean validation methods should be allowed
        assert!(UserMethodPolicy::allowed_in_condition("StringUtils", "is_integer"));
        assert!(UserMethodPolicy::allowed_in_condition("StringUtils", "is_empty_or_whitespace"));
    }

    #[test]
    fn test_stringutils_matching_in_condition() {
        // Pure boolean matching methods should be allowed
        assert!(UserMethodPolicy::allowed_in_condition("StringUtils", "starts_with"));
        assert!(UserMethodPolicy::allowed_in_condition("StringUtils", "ends_with"));
        assert!(UserMethodPolicy::allowed_in_condition("StringUtils", "contains"));
    }

    #[test]
    fn test_stringutils_string_functions_not_in_condition() {
        // String-returning functions should NOT be allowed in condition
        assert!(!UserMethodPolicy::allowed_in_condition("StringUtils", "trim"));
        assert!(!UserMethodPolicy::allowed_in_condition("StringUtils", "trim_start"));
        assert!(!UserMethodPolicy::allowed_in_condition("StringUtils", "trim_end"));
        assert!(!UserMethodPolicy::allowed_in_condition("StringUtils", "to_upper"));
        assert!(!UserMethodPolicy::allowed_in_condition("StringUtils", "to_lower"));
    }

    #[test]
    fn test_stringutils_search_not_in_condition() {
        // Integer-returning search functions should NOT be allowed in condition
        assert!(!UserMethodPolicy::allowed_in_condition("StringUtils", "index_of"));
        assert!(!UserMethodPolicy::allowed_in_condition("StringUtils", "last_index_of"));
        assert!(!UserMethodPolicy::allowed_in_condition("StringUtils", "index_of_string"));
    }

    #[test]
    fn test_unknown_static_box_in_condition() {
        // Unknown static boxes should fail-fast
        assert!(!UserMethodPolicy::allowed_in_condition("UnknownBox", "some_method"));
        assert!(!UserMethodPolicy::allowed_in_condition("MathUtils", "abs"));
    }

    // ===== Init Context Tests =====

    #[test]
    fn test_stringutils_all_pure_methods_in_init() {
        // All pure methods should be allowed in init (more permissive than condition)
        // Character classification
        assert!(UserMethodPolicy::allowed_in_init("StringUtils", "is_whitespace"));
        assert!(UserMethodPolicy::allowed_in_init("StringUtils", "is_digit"));

        // String manipulation
        assert!(UserMethodPolicy::allowed_in_init("StringUtils", "trim"));
        assert!(UserMethodPolicy::allowed_in_init("StringUtils", "trim_start"));
        assert!(UserMethodPolicy::allowed_in_init("StringUtils", "trim_end"));
        assert!(UserMethodPolicy::allowed_in_init("StringUtils", "to_upper"));
        assert!(UserMethodPolicy::allowed_in_init("StringUtils", "to_lower"));

        // String search
        assert!(UserMethodPolicy::allowed_in_init("StringUtils", "index_of"));
        assert!(UserMethodPolicy::allowed_in_init("StringUtils", "last_index_of"));
        assert!(UserMethodPolicy::allowed_in_init("StringUtils", "index_of_string"));

        // Numeric parsing
        assert!(UserMethodPolicy::allowed_in_init("StringUtils", "parse_integer"));
        assert!(UserMethodPolicy::allowed_in_init("StringUtils", "parse_float"));
    }

    #[test]
    fn test_unknown_static_box_in_init() {
        // Unknown static boxes should fail-fast
        assert!(!UserMethodPolicy::allowed_in_init("UnknownBox", "some_method"));
        assert!(!UserMethodPolicy::allowed_in_init("MathUtils", "sqrt"));
    }

    // ===== Real-World Pattern Tests =====

    #[test]
    fn test_trim_end_pattern() {
        // Phase 252 P0: StringUtils.trim_end/1 pattern
        // loop(i >= 0) { if not this.is_whitespace(s.substring(i, i + 1)) { break } ... }

        // is_whitespace should be allowed in condition (boolean check)
        assert!(UserMethodPolicy::allowed_in_condition("StringUtils", "is_whitespace"));

        // trim_end itself should NOT be allowed in condition (string function)
        assert!(!UserMethodPolicy::allowed_in_condition("StringUtils", "trim_end"));

        // But trim_end should be allowed in init
        assert!(UserMethodPolicy::allowed_in_init("StringUtils", "trim_end"));
    }

    #[test]
    fn test_index_of_pattern() {
        // Pattern: local pos = this.index_of(s, ch)
        // index_of returns integer (-1 or index), not boolean

        // Should NOT be allowed in condition
        assert!(!UserMethodPolicy::allowed_in_condition("StringUtils", "index_of"));

        // But should be allowed in init
        assert!(UserMethodPolicy::allowed_in_init("StringUtils", "index_of"));
    }
}
