//! Phase 87: Core Box ID 定義
//!
//! Nyash の core Box を型安全な enum で管理する。
//! ハードコード文字列からの脱却により、コンパイル時検証を実現。

use crate::runtime::runtime_profile::RuntimeProfile;

/// Phase 85 調査結果に基づく Core Box ID
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum CoreBoxId {
    // ===== Phase 85: core_required (6個) =====
    /// StringBox - 文字列基本型
    String,
    /// IntegerBox - 整数基本型
    Integer,
    /// BoolBox - 真偽値基本型
    Bool,
    /// ArrayBox - 配列基本型
    Array,
    /// MapBox - マップ基本型
    Map,
    /// ConsoleBox - コンソール入出力
    Console,

    // ===== Phase 85: core_optional (9個) =====
    /// FloatBox - 浮動小数点数
    Float,
    /// NullBox - null値
    Null,
    /// FileBox - ファイル操作
    File,
    /// PathBox - パス操作
    Path,
    /// RegexBox - 正規表現
    Regex,
    /// MathBox - 数学関数
    Math,
    /// TimeBox - 時刻操作
    Time,
    /// JsonBox - JSON操作
    Json,
    /// TomlBox - TOML操作
    Toml,

    // ===== 特殊型 =====
    /// FunctionBox - 第一級関数
    Function,
    /// ResultBox - Result型（QMark対応）
    Result,
    /// MethodBox - メソッド
    Method,
    /// MissingBox - 欠損値
    Missing,
}

/// Phase 87: Core Box カテゴリ
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum CoreBoxCategory {
    /// Phase 85: 必須（予約名保護）
    CoreRequired,
    /// Phase 85: 推奨（デフォルトロード）
    CoreOptional,
    /// 特殊型
    Special,
}

impl CoreBoxId {
    /// Box名を返す（例: "StringBox"）
    pub fn name(&self) -> &'static str {
        use CoreBoxId::*;
        match self {
            String => "StringBox",
            Integer => "IntegerBox",
            Bool => "BoolBox",
            Array => "ArrayBox",
            Map => "MapBox",
            Console => "ConsoleBox",
            Float => "FloatBox",
            Null => "NullBox",
            File => "FileBox",
            Path => "PathBox",
            Regex => "RegexBox",
            Math => "MathBox",
            Time => "TimeBox",
            Json => "JsonBox",
            Toml => "TomlBox",
            Function => "FunctionBox",
            Result => "ResultBox",
            Method => "MethodBox",
            Missing => "MissingBox",
        }
    }

    /// 全CoreBoxIdを反復
    pub fn iter() -> impl Iterator<Item = CoreBoxId> {
        use CoreBoxId::*;
        [
            String, Integer, Bool, Array, Map, Console, Float, Null, File, Path, Regex, Math, Time,
            Json, Toml, Function, Result, Method, Missing,
        ]
        .into_iter()
    }

    /// 名前からCoreBoxIdを取得
    pub fn from_name(name: &str) -> Option<CoreBoxId> {
        Self::iter().find(|id| id.name() == name)
    }

    /// Phase 106: core_required チェック
    ///
    /// FileBox は Phase 85 では core_optional として分類していたが、
    /// selfhost/通常ランタイムでは事実上必須（ログ・ツール・ハコチェック等で常用）
    /// であることが明確になったため、「core_required 相当」として扱う設計に統一した。
    ///
    /// **設計原則**:
    /// - 必須判定は CoreBoxId に一本化（provider_lock は「登録・読む」だけ）
    /// - 将来 minimal/no-fs プロファイルを導入する場合は、ここで profile パラメータを追加可能
    pub fn is_core_required(&self) -> bool {
        use CoreBoxId::*;
        matches!(self, String | Integer | Bool | Array | Map | Console | File)
    }

    /// Phase 109: profile-aware required check
    ///
    /// Determines if this CoreBox is required in the given RuntimeProfile.
    ///
    /// - Default: Same as is_core_required() (FileBox is required)
    /// - NoFs: FileBox becomes optional (only String/Integer/Bool/Array/Map/Console required)
    ///
    /// **Future expansion**: TestMock/Sandbox/ReadOnly/Embedded profiles will extend this logic
    pub fn is_required_in(&self, profile: &RuntimeProfile) -> bool {
        match profile {
            RuntimeProfile::Default => {
                // Phase 106: FileBox is required in Default profile
                self.is_core_required()
            }
            RuntimeProfile::NoFs => {
                // Phase 109: FileBox is optional in NoFs profile
                // In NoFs profile, only non-FileBox core required boxes
                self.is_core_required() && *self != Self::File
            }
        }
    }

    /// Phase 87: カテゴリ分類
    pub fn category(&self) -> CoreBoxCategory {
        use CoreBoxId::*;
        match self {
            // Phase 106: File を CoreRequired 側に移動（selfhost/通常ランタイムでは必須）
            String | Integer | Bool | Array | Map | Console | File => CoreBoxCategory::CoreRequired,
            Float | Null | Path | Regex | Math | Time | Json | Toml => {
                CoreBoxCategory::CoreOptional
            }
            Function | Result | Method | Missing => CoreBoxCategory::Special,
        }
    }
}

/// Phase 87: Core Method ID 定義
///
/// Box のメソッドを型安全に管理。
/// Phase 84-4-B のハードコード型情報を統合。
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum CoreMethodId {
    // ===== StringBox methods =====
    StringLength,
    StringUpper,
    StringLower,
    StringConcat,
    StringSubstring,
    StringIndexOf,
    StringIndexOfFrom,
    StringReplace,
    StringTrim,
    StringSplit,

    // ===== IntegerBox methods =====
    IntegerAbs,
    IntegerMin,
    IntegerMax,

    // ===== BoolBox methods =====
    BoolNot,
    BoolAnd,
    BoolOr,

    // ===== ArrayBox methods =====
    ArrayLength,
    ArrayPush,
    ArrayPop,
    ArrayGet,

    // ===== MapBox methods =====
    MapGet,
    MapSet,
    MapHas,
    MapKeys,

    // ===== ConsoleBox methods =====
    ConsolePrintln,
    ConsoleLog,
    ConsoleError,

    // ===== FileBox methods =====
    FileRead,
    FileWrite,
    FileOpen,

    // ===== ResultBox methods (QMark 対応) =====
    ResultIsOk,
    ResultGetValue,
}

/// SSOT for CoreMethodId metadata (name/arity/return types and policy flags).
#[derive(Debug, Clone, Copy)]
struct CoreMethodSpec {
    id: CoreMethodId,
    box_id: CoreBoxId,
    name: &'static str,
    arity: usize,
    return_type_name: &'static str,
    is_pure: bool,
    allowed_in_condition: bool,
    allowed_in_init: bool,
    vtable_slot: Option<u16>,
}

const CORE_METHOD_SPECS: &[CoreMethodSpec] = &[
    // StringBox methods
    CoreMethodSpec {
        id: CoreMethodId::StringLength,
        box_id: CoreBoxId::String,
        name: "length",
        arity: 0,
        return_type_name: "IntegerBox",
        is_pure: true,
        allowed_in_condition: true,
        allowed_in_init: true,
        vtable_slot: Some(300),
    },
    CoreMethodSpec {
        id: CoreMethodId::StringUpper,
        box_id: CoreBoxId::String,
        name: "toUpper",
        arity: 0,
        return_type_name: "StringBox",
        is_pure: true,
        allowed_in_condition: false,
        allowed_in_init: true,
        vtable_slot: None,
    },
    CoreMethodSpec {
        id: CoreMethodId::StringLower,
        box_id: CoreBoxId::String,
        name: "toLower",
        arity: 0,
        return_type_name: "StringBox",
        is_pure: true,
        allowed_in_condition: false,
        allowed_in_init: true,
        vtable_slot: None,
    },
    CoreMethodSpec {
        id: CoreMethodId::StringConcat,
        box_id: CoreBoxId::String,
        name: "concat",
        arity: 1,
        return_type_name: "StringBox",
        is_pure: true,
        allowed_in_condition: false,
        allowed_in_init: true,
        vtable_slot: Some(302),
    },
    CoreMethodSpec {
        id: CoreMethodId::StringSubstring,
        box_id: CoreBoxId::String,
        name: "substring",
        arity: 2,
        return_type_name: "StringBox",
        is_pure: true,
        allowed_in_condition: false,
        allowed_in_init: true,
        vtable_slot: Some(301),
    },
    CoreMethodSpec {
        id: CoreMethodId::StringIndexOf,
        box_id: CoreBoxId::String,
        name: "indexOf",
        arity: 1,
        return_type_name: "IntegerBox",
        is_pure: true,
        allowed_in_condition: false,
        allowed_in_init: true,
        vtable_slot: Some(303),
    },
    CoreMethodSpec {
        id: CoreMethodId::StringIndexOfFrom,
        box_id: CoreBoxId::String,
        name: "indexOf",
        arity: 2,
        return_type_name: "IntegerBox",
        is_pure: true,
        allowed_in_condition: false,
        allowed_in_init: true,
        vtable_slot: Some(303),
    },
    CoreMethodSpec {
        id: CoreMethodId::StringReplace,
        box_id: CoreBoxId::String,
        name: "replace",
        arity: 2,
        return_type_name: "StringBox",
        is_pure: true,
        allowed_in_condition: false,
        allowed_in_init: true,
        vtable_slot: Some(304),
    },
    CoreMethodSpec {
        id: CoreMethodId::StringTrim,
        box_id: CoreBoxId::String,
        name: "trim",
        arity: 0,
        return_type_name: "StringBox",
        is_pure: true,
        allowed_in_condition: false,
        allowed_in_init: true,
        vtable_slot: Some(305),
    },
    CoreMethodSpec {
        id: CoreMethodId::StringSplit,
        box_id: CoreBoxId::String,
        name: "split",
        arity: 1,
        return_type_name: "Unknown",
        is_pure: true,
        allowed_in_condition: false,
        allowed_in_init: true,
        vtable_slot: None,
    },
    // IntegerBox methods
    CoreMethodSpec {
        id: CoreMethodId::IntegerAbs,
        box_id: CoreBoxId::Integer,
        name: "abs",
        arity: 0,
        return_type_name: "IntegerBox",
        is_pure: true,
        allowed_in_condition: false,
        allowed_in_init: true,
        vtable_slot: None,
    },
    CoreMethodSpec {
        id: CoreMethodId::IntegerMin,
        box_id: CoreBoxId::Integer,
        name: "min",
        arity: 1,
        return_type_name: "IntegerBox",
        is_pure: true,
        allowed_in_condition: false,
        allowed_in_init: true,
        vtable_slot: None,
    },
    CoreMethodSpec {
        id: CoreMethodId::IntegerMax,
        box_id: CoreBoxId::Integer,
        name: "max",
        arity: 1,
        return_type_name: "IntegerBox",
        is_pure: true,
        allowed_in_condition: false,
        allowed_in_init: true,
        vtable_slot: None,
    },
    // BoolBox methods
    CoreMethodSpec {
        id: CoreMethodId::BoolNot,
        box_id: CoreBoxId::Bool,
        name: "not",
        arity: 0,
        return_type_name: "BoolBox",
        is_pure: true,
        allowed_in_condition: false,
        allowed_in_init: false,
        vtable_slot: None,
    },
    CoreMethodSpec {
        id: CoreMethodId::BoolAnd,
        box_id: CoreBoxId::Bool,
        name: "and",
        arity: 1,
        return_type_name: "BoolBox",
        is_pure: true,
        allowed_in_condition: false,
        allowed_in_init: false,
        vtable_slot: None,
    },
    CoreMethodSpec {
        id: CoreMethodId::BoolOr,
        box_id: CoreBoxId::Bool,
        name: "or",
        arity: 1,
        return_type_name: "BoolBox",
        is_pure: true,
        allowed_in_condition: false,
        allowed_in_init: false,
        vtable_slot: None,
    },
    // ArrayBox methods
    CoreMethodSpec {
        id: CoreMethodId::ArrayLength,
        box_id: CoreBoxId::Array,
        name: "length",
        arity: 0,
        return_type_name: "IntegerBox",
        is_pure: true,
        allowed_in_condition: true,
        allowed_in_init: true,
        vtable_slot: Some(102),
    },
    CoreMethodSpec {
        id: CoreMethodId::ArrayPush,
        box_id: CoreBoxId::Array,
        name: "push",
        arity: 1,
        return_type_name: "Void",
        is_pure: false,
        allowed_in_condition: false,
        allowed_in_init: false,
        vtable_slot: Some(103),
    },
    CoreMethodSpec {
        id: CoreMethodId::ArrayPop,
        box_id: CoreBoxId::Array,
        name: "pop",
        arity: 0,
        return_type_name: "Void",
        is_pure: false,
        allowed_in_condition: false,
        allowed_in_init: false,
        vtable_slot: Some(104),
    },
    CoreMethodSpec {
        id: CoreMethodId::ArrayGet,
        box_id: CoreBoxId::Array,
        name: "get",
        arity: 1,
        return_type_name: "Unknown",
        is_pure: true,
        allowed_in_condition: true,
        allowed_in_init: true,
        vtable_slot: Some(100),
    },
    // MapBox methods
    CoreMethodSpec {
        id: CoreMethodId::MapGet,
        box_id: CoreBoxId::Map,
        name: "get",
        arity: 1,
        return_type_name: "Unknown",
        is_pure: true,
        allowed_in_condition: false,
        allowed_in_init: true,
        vtable_slot: Some(203),
    },
    CoreMethodSpec {
        id: CoreMethodId::MapSet,
        box_id: CoreBoxId::Map,
        name: "set",
        arity: 2,
        return_type_name: "Void",
        is_pure: false,
        allowed_in_condition: false,
        allowed_in_init: false,
        vtable_slot: Some(204),
    },
    CoreMethodSpec {
        id: CoreMethodId::MapHas,
        box_id: CoreBoxId::Map,
        name: "has",
        arity: 1,
        return_type_name: "BoolBox",
        is_pure: true,
        allowed_in_condition: true,
        allowed_in_init: true,
        vtable_slot: Some(202),
    },
    CoreMethodSpec {
        id: CoreMethodId::MapKeys,
        box_id: CoreBoxId::Map,
        name: "keys",
        arity: 0,
        return_type_name: "Unknown",
        is_pure: true,
        allowed_in_condition: false,
        allowed_in_init: true,
        vtable_slot: Some(206),
    },
    // ConsoleBox methods
    CoreMethodSpec {
        id: CoreMethodId::ConsolePrintln,
        box_id: CoreBoxId::Console,
        name: "println",
        arity: 1,
        return_type_name: "Void",
        is_pure: false,
        allowed_in_condition: false,
        allowed_in_init: false,
        vtable_slot: Some(400),
    },
    CoreMethodSpec {
        id: CoreMethodId::ConsoleLog,
        box_id: CoreBoxId::Console,
        name: "log",
        arity: 1,
        return_type_name: "Void",
        is_pure: false,
        allowed_in_condition: false,
        allowed_in_init: false,
        vtable_slot: Some(400),
    },
    CoreMethodSpec {
        id: CoreMethodId::ConsoleError,
        box_id: CoreBoxId::Console,
        name: "error",
        arity: 1,
        return_type_name: "Void",
        is_pure: false,
        allowed_in_condition: false,
        allowed_in_init: false,
        vtable_slot: Some(402),
    },
    // FileBox methods
    CoreMethodSpec {
        id: CoreMethodId::FileRead,
        box_id: CoreBoxId::File,
        name: "read",
        arity: 1,
        return_type_name: "StringBox",
        is_pure: false,
        allowed_in_condition: false,
        allowed_in_init: false,
        vtable_slot: None,
    },
    CoreMethodSpec {
        id: CoreMethodId::FileWrite,
        box_id: CoreBoxId::File,
        name: "write",
        arity: 1,
        return_type_name: "Void",
        is_pure: false,
        allowed_in_condition: false,
        allowed_in_init: false,
        vtable_slot: None,
    },
    CoreMethodSpec {
        id: CoreMethodId::FileOpen,
        box_id: CoreBoxId::File,
        name: "open",
        arity: 1,
        return_type_name: "FileBox",
        is_pure: false,
        allowed_in_condition: false,
        allowed_in_init: false,
        vtable_slot: None,
    },
    // ResultBox methods
    CoreMethodSpec {
        id: CoreMethodId::ResultIsOk,
        box_id: CoreBoxId::Result,
        name: "isOk",
        arity: 0,
        return_type_name: "BoolBox",
        is_pure: true,
        allowed_in_condition: true,
        allowed_in_init: true,
        vtable_slot: None,
    },
    CoreMethodSpec {
        id: CoreMethodId::ResultGetValue,
        box_id: CoreBoxId::Result,
        name: "getValue",
        arity: 0,
        return_type_name: "Unknown",
        is_pure: true,
        allowed_in_condition: false,
        allowed_in_init: true,
        vtable_slot: None,
    },
];

impl CoreMethodId {
    fn spec(&self) -> &'static CoreMethodSpec {
        CORE_METHOD_SPECS
            .iter()
            .find(|spec| spec.id == *self)
            .expect("CoreMethodSpec missing for CoreMethodId")
    }

    /// メソッドが属する Box ID
    pub fn box_id(&self) -> CoreBoxId {
        self.spec().box_id
    }

    /// メソッド名（例: "length"）
    pub fn name(&self) -> &'static str {
        self.spec().name
    }

    /// 引数の数
    pub fn arity(&self) -> usize {
        self.spec().arity
    }

    /// Phase 84-4-B: 戻り値型（型推論用）
    pub fn return_type_name(&self) -> &'static str {
        self.spec().return_type_name
    }

    /// VTable slot for TypeRegistry (None when not exposed via vtable).
    pub fn vtable_slot(&self) -> Option<u16> {
        self.spec().vtable_slot
    }

    /// 全CoreMethodIdを反復
    pub fn iter() -> impl Iterator<Item = CoreMethodId> {
        CORE_METHOD_SPECS.iter().map(|spec| spec.id)
    }

    /// Box名とメソッド名から CoreMethodId を取得
    pub fn from_box_and_method(box_id: CoreBoxId, method: &str) -> Option<CoreMethodId> {
        let canonical = crate::runtime::core_method_aliases::canonical_method_name(method);
        CORE_METHOD_SPECS
            .iter()
            .find(|spec| spec.box_id == box_id && spec.name == canonical)
            .map(|spec| spec.id)
    }

    /// メソッド名とアリティから CoreMethodId を解決
    pub fn resolve_by_name_and_arity(
        method_name: &str,
        arg_len: usize,
    ) -> Result<CoreMethodId, Vec<usize>> {
        let canonical = crate::runtime::core_method_aliases::canonical_method_name(method_name);
        let mut expected = Vec::new();
        for spec in CORE_METHOD_SPECS.iter().filter(|spec| spec.name == canonical) {
            expected.push(spec.arity);
            if spec.arity == arg_len {
                return Ok(spec.id);
            }
        }
        expected.sort_unstable();
        expected.dedup();
        Err(expected)
    }

    /// Phase 224-B: Pure function (no side effects, deterministic)
    ///
    /// Pure functions can be safely:
    /// - Used in loop conditions
    /// - Called multiple times without changing behavior
    /// - Eliminated by dead code elimination if result unused
    ///
    /// Examples:
    /// - `StringLength`: Pure - always returns same length for same string
    /// - `ArrayPush`: Not pure - mutates the array (side effect)
    pub fn is_pure(&self) -> bool {
        self.spec().is_pure
    }

    /// Phase 224-B: Allowed in loop condition expressions
    ///
    /// Methods allowed in loop conditions must be:
    /// 1. Pure (no side effects)
    /// 2. Cheap to compute (no expensive I/O)
    /// 3. Deterministic (same input → same output)
    ///
    /// This is a whitelist approach - default to false for safety.
    pub fn allowed_in_condition(&self) -> bool {
        self.spec().allowed_in_condition
    }

    /// Phase 224-B: Allowed in loop body init expressions
    ///
    /// Methods allowed for LoopBodyLocal initialization.
    /// Similar to condition requirements but slightly more permissive.
    pub fn allowed_in_init(&self) -> bool {
        self.spec().allowed_in_init
    }
}

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

    // ===== CoreBoxId tests =====

    #[test]
    fn test_core_box_id_from_name() {
        assert_eq!(CoreBoxId::from_name("StringBox"), Some(CoreBoxId::String));
        assert_eq!(CoreBoxId::from_name("IntegerBox"), Some(CoreBoxId::Integer));
        assert_eq!(CoreBoxId::from_name("BoolBox"), Some(CoreBoxId::Bool));
        assert_eq!(CoreBoxId::from_name("UnknownBox"), None);
    }

    #[test]
    fn test_core_box_id_name() {
        assert_eq!(CoreBoxId::String.name(), "StringBox");
        assert_eq!(CoreBoxId::Integer.name(), "IntegerBox");
        assert_eq!(CoreBoxId::Console.name(), "ConsoleBox");
    }

    #[test]
    fn test_core_box_id_iter() {
        let count = CoreBoxId::iter().count();
        assert_eq!(count, 19); // Phase 87: 19個の CoreBox
    }

    #[test]
    fn test_core_box_id_is_core_required() {
        // Phase 85: core_required (6個) + FileBox を実質必須扱いに拡張
        assert!(CoreBoxId::String.is_core_required());
        assert!(CoreBoxId::Integer.is_core_required());
        assert!(CoreBoxId::Bool.is_core_required());
        assert!(CoreBoxId::Array.is_core_required());
        assert!(CoreBoxId::Map.is_core_required());
        assert!(CoreBoxId::Console.is_core_required());
        assert!(CoreBoxId::File.is_core_required());

        // core_optional の代表例
        assert!(!CoreBoxId::Float.is_core_required());
    }

    #[test]
    fn test_core_box_id_category() {
        assert_eq!(CoreBoxId::String.category(), CoreBoxCategory::CoreRequired);
        // Phase 106: File の分類を修正
        assert_eq!(CoreBoxId::File.category(), CoreBoxCategory::CoreRequired);
        assert_eq!(CoreBoxId::Function.category(), CoreBoxCategory::Special);
    }

    #[test]
    fn test_core_box_id_is_required_in_default() {
        use crate::runtime::runtime_profile::RuntimeProfile;
        let profile = RuntimeProfile::Default;
        assert!(CoreBoxId::String.is_required_in(&profile));
        assert!(CoreBoxId::Integer.is_required_in(&profile));
        assert!(CoreBoxId::Bool.is_required_in(&profile));
        assert!(CoreBoxId::Array.is_required_in(&profile));
        assert!(CoreBoxId::Map.is_required_in(&profile));
        assert!(CoreBoxId::Console.is_required_in(&profile));
        assert!(CoreBoxId::File.is_required_in(&profile)); // FileBox required in Default
    }

    #[test]
    fn test_core_box_id_is_required_in_nofs() {
        use crate::runtime::runtime_profile::RuntimeProfile;
        let profile = RuntimeProfile::NoFs;
        assert!(CoreBoxId::String.is_required_in(&profile));
        assert!(CoreBoxId::Integer.is_required_in(&profile));
        assert!(CoreBoxId::Bool.is_required_in(&profile));
        assert!(CoreBoxId::Array.is_required_in(&profile));
        assert!(CoreBoxId::Map.is_required_in(&profile));
        assert!(CoreBoxId::Console.is_required_in(&profile));
        assert!(!CoreBoxId::File.is_required_in(&profile)); // FileBox optional in NoFs
    }

    // ===== CoreMethodId tests =====

    #[test]
    fn test_core_method_id_box_id() {
        assert_eq!(CoreMethodId::StringLength.box_id(), CoreBoxId::String);
        assert_eq!(CoreMethodId::ArrayPush.box_id(), CoreBoxId::Array);
        assert_eq!(CoreMethodId::ConsolePrintln.box_id(), CoreBoxId::Console);
    }

    #[test]
    fn test_core_method_id_name() {
        assert_eq!(CoreMethodId::StringLength.name(), "length");
        assert_eq!(CoreMethodId::ArrayPush.name(), "push");
        assert_eq!(CoreMethodId::ConsolePrintln.name(), "println");
    }

    #[test]
    fn test_core_method_id_arity() {
        assert_eq!(CoreMethodId::StringLength.arity(), 0);
        assert_eq!(CoreMethodId::StringConcat.arity(), 1);
        assert_eq!(CoreMethodId::StringIndexOfFrom.arity(), 2);
        assert_eq!(CoreMethodId::MapSet.arity(), 2);
    }

    #[test]
    fn test_core_method_id_return_type() {
        assert_eq!(CoreMethodId::StringLength.return_type_name(), "IntegerBox");
        assert_eq!(CoreMethodId::StringUpper.return_type_name(), "StringBox");
        assert_eq!(CoreMethodId::BoolNot.return_type_name(), "BoolBox");
        assert_eq!(CoreMethodId::ArrayPush.return_type_name(), "Void");
    }

    #[test]
    fn test_core_method_id_from_box_and_method() {
        assert_eq!(
            CoreMethodId::from_box_and_method(CoreBoxId::String, "length"),
            Some(CoreMethodId::StringLength)
        );
        assert_eq!(
            CoreMethodId::from_box_and_method(CoreBoxId::Array, "push"),
            Some(CoreMethodId::ArrayPush)
        );
        assert_eq!(
            CoreMethodId::from_box_and_method(CoreBoxId::String, "unknown_method"),
            None
        );
    }

    #[test]
    fn test_core_method_id_iter() {
        let count = CoreMethodId::iter().count();
        assert!(count >= 27); // Phase 87: 27個以上のメソッド
    }

    #[test]
    fn test_core_method_spec_uniqueness() {
        let mut ids = HashSet::new();
        let mut signatures = HashSet::new();
        for spec in CORE_METHOD_SPECS {
            assert!(ids.insert(spec.id), "duplicate CoreMethodId in specs");
            let key = (spec.box_id, spec.name, spec.arity);
            assert!(
                signatures.insert(key),
                "duplicate CoreMethodSpec signature: {:?}",
                key
            );
        }
        assert_eq!(ids.len(), CoreMethodId::iter().count());
    }

    // ===== Phase 224-B tests =====

    #[test]
    fn test_core_method_id_is_pure() {
        // Pure string methods
        assert!(CoreMethodId::StringLength.is_pure());
        assert!(CoreMethodId::StringUpper.is_pure());
        assert!(CoreMethodId::StringSubstring.is_pure());

        // Pure array read methods
        assert!(CoreMethodId::ArrayLength.is_pure());
        assert!(CoreMethodId::ArrayGet.is_pure());

        // Impure - side effects
        assert!(!CoreMethodId::ArrayPush.is_pure());
        assert!(!CoreMethodId::ConsolePrintln.is_pure());
        assert!(!CoreMethodId::FileWrite.is_pure());
    }

    #[test]
    fn test_core_method_id_allowed_in_condition() {
        // Allowed - cheap and pure
        assert!(CoreMethodId::StringLength.allowed_in_condition());
        assert!(CoreMethodId::ArrayLength.allowed_in_condition());
        assert!(CoreMethodId::MapHas.allowed_in_condition());

        // Not allowed - not whitelisted (conservative)
        assert!(!CoreMethodId::StringUpper.allowed_in_condition());
        assert!(!CoreMethodId::StringSubstring.allowed_in_condition());

        // Not allowed - side effects
        assert!(!CoreMethodId::ArrayPush.allowed_in_condition());
        assert!(!CoreMethodId::ConsolePrintln.allowed_in_condition());
        assert!(!CoreMethodId::FileRead.allowed_in_condition());
    }

    #[test]
    fn test_core_method_id_allowed_in_init() {
        // Allowed - useful for LoopBodyLocal init
        assert!(CoreMethodId::StringLength.allowed_in_init());
        assert!(CoreMethodId::StringSubstring.allowed_in_init());
        assert!(CoreMethodId::StringUpper.allowed_in_init());
        assert!(CoreMethodId::ArrayGet.allowed_in_init());
        assert!(CoreMethodId::MapGet.allowed_in_init());

        // Not allowed - side effects
        assert!(!CoreMethodId::ArrayPush.allowed_in_init());
        assert!(!CoreMethodId::ConsolePrintln.allowed_in_init());
        assert!(!CoreMethodId::FileWrite.allowed_in_init());
    }
}
