//! 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,
    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,
}

impl CoreMethodId {
    /// メソッドが属する Box ID
    pub fn box_id(&self) -> CoreBoxId {
        use CoreMethodId::*;
        match self {
            StringLength | StringUpper | StringLower | StringConcat | StringSubstring
            | StringIndexOf | StringReplace | StringTrim | StringSplit => CoreBoxId::String,

            IntegerAbs | IntegerMin | IntegerMax => CoreBoxId::Integer,

            BoolNot | BoolAnd | BoolOr => CoreBoxId::Bool,

            ArrayLength | ArrayPush | ArrayPop | ArrayGet => CoreBoxId::Array,

            MapGet | MapSet | MapHas | MapKeys => CoreBoxId::Map,

            ConsolePrintln | ConsoleLog | ConsoleError => CoreBoxId::Console,

            FileRead | FileWrite | FileOpen => CoreBoxId::File,

            ResultIsOk | ResultGetValue => CoreBoxId::Result,
        }
    }

    /// メソッド名（例: "length"）
    pub fn name(&self) -> &'static str {
        use CoreMethodId::*;
        match self {
            StringLength => "length",
            StringUpper => "upper",
            StringLower => "lower",
            StringConcat => "concat",
            StringSubstring => "substring",
            StringIndexOf => "indexOf",
            StringReplace => "replace",
            StringTrim => "trim",
            StringSplit => "split",

            IntegerAbs => "abs",
            IntegerMin => "min",
            IntegerMax => "max",

            BoolNot => "not",
            BoolAnd => "and",
            BoolOr => "or",

            ArrayLength => "length",
            ArrayPush => "push",
            ArrayPop => "pop",
            ArrayGet => "get",

            MapGet => "get",
            MapSet => "set",
            MapHas => "has",
            MapKeys => "keys",

            ConsolePrintln => "println",
            ConsoleLog => "log",
            ConsoleError => "error",

            FileRead => "read",
            FileWrite => "write",
            FileOpen => "open",

            ResultIsOk => "isOk",
            ResultGetValue => "getValue",
        }
    }

    /// 引数の数
    pub fn arity(&self) -> usize {
        use CoreMethodId::*;
        match self {
            StringLength | StringUpper | StringLower | StringTrim | IntegerAbs | BoolNot
            | ArrayLength | ArrayPop | MapKeys | ResultIsOk | ResultGetValue => 0,

            StringConcat | StringIndexOf | StringReplace | StringSplit | IntegerMin
            | IntegerMax | BoolAnd | BoolOr | ArrayGet | ArrayPush | MapGet | MapHas
            | ConsolePrintln | ConsoleLog | ConsoleError | FileRead | FileWrite | FileOpen => 1,

            StringSubstring | MapSet => 2,
        }
    }

    /// Phase 84-4-B: 戻り値型（型推論用）
    pub fn return_type_name(&self) -> &'static str {
        use CoreMethodId::*;
        match self {
            StringLength | StringIndexOf | ArrayLength => "IntegerBox",

            StringUpper | StringLower | StringConcat | StringSubstring | StringReplace
            | StringTrim => "StringBox",

            IntegerAbs | IntegerMin | IntegerMax => "IntegerBox",

            BoolNot | BoolAnd | BoolOr | MapHas | ResultIsOk => "BoolBox",

            ArrayPush | ArrayPop | MapSet | ConsolePrintln | ConsoleLog | ConsoleError
            | FileWrite => "Void",

            ArrayGet | MapGet | MapKeys | StringSplit => "Unknown",

            FileRead => "StringBox",
            FileOpen => "FileBox",
            ResultGetValue => "Unknown",
        }
    }

    /// 全CoreMethodIdを反復
    pub fn iter() -> impl Iterator<Item = CoreMethodId> {
        use CoreMethodId::*;
        [
            StringLength,
            StringUpper,
            StringLower,
            StringConcat,
            StringSubstring,
            StringIndexOf,
            StringReplace,
            StringTrim,
            StringSplit,
            IntegerAbs,
            IntegerMin,
            IntegerMax,
            BoolNot,
            BoolAnd,
            BoolOr,
            ArrayLength,
            ArrayPush,
            ArrayPop,
            ArrayGet,
            MapGet,
            MapSet,
            MapHas,
            MapKeys,
            ConsolePrintln,
            ConsoleLog,
            ConsoleError,
            FileRead,
            FileWrite,
            FileOpen,
            ResultIsOk,
            ResultGetValue,
        ]
        .into_iter()
    }

    /// Box名とメソッド名から CoreMethodId を取得
    pub fn from_box_and_method(box_id: CoreBoxId, method: &str) -> Option<CoreMethodId> {
        Self::iter().find(|m| m.box_id() == box_id && m.name() == method)
    }

    /// 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 {
        use CoreMethodId::*;
        match self {
            // String methods (pure - return new values, don't mutate)
            StringLength | StringUpper | StringLower | StringConcat | StringSubstring
            | StringIndexOf | StringReplace | StringTrim | StringSplit => true,

            // Integer/Bool methods (pure - mathematical operations)
            IntegerAbs | IntegerMin | IntegerMax | BoolNot | BoolAnd | BoolOr => true,

            // Array/Map read operations (pure - don't mutate)
            ArrayLength | ArrayGet => true,
            MapGet | MapHas => true,
            MapKeys => true,

            // ResultBox read operations (pure)
            ResultIsOk | ResultGetValue => true,

            // Impure - mutate state or have side effects
            ArrayPush | ArrayPop | MapSet => false,
            ConsolePrintln | ConsoleLog | ConsoleError => false,
            FileRead | FileWrite | FileOpen => false,
        }
    }

    /// 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 {
        use CoreMethodId::*;
        match self {
            // String read operations - allowed
            StringLength => true,

            // Array read operations - allowed
            ArrayLength | ArrayGet => true,

            // Map read operations - allowed
            MapHas => true,

            // ResultBox operations - allowed
            ResultIsOk => true,

            // Not yet whitelisted - be conservative
            StringUpper | StringLower | StringConcat | StringSubstring | StringIndexOf
            | StringReplace | StringTrim | StringSplit => false,

            IntegerAbs | IntegerMin | IntegerMax => false,
            BoolNot | BoolAnd | BoolOr => false,

            MapGet => false,
            MapKeys => false,
            ResultGetValue => false,

            // Obviously disallowed - side effects
            ArrayPush | ArrayPop | MapSet => false,
            ConsolePrintln | ConsoleLog | ConsoleError => false,
            FileRead | FileWrite | FileOpen => false,
        }
    }

    /// 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 {
        use CoreMethodId::*;
        match self {
            // String operations - allowed
            StringLength | StringSubstring | StringIndexOf => true,

            // String transformations - allowed for init
            StringUpper | StringLower | StringTrim => true,

            // Array operations - allowed
            ArrayLength | ArrayGet => true,

            // Map operations - allowed
            MapGet | MapHas | MapKeys => true,

            // ResultBox operations - allowed
            ResultIsOk | ResultGetValue => true,

            // String operations that create new strings - allowed
            StringConcat | StringReplace | StringSplit => true,

            // Math operations - allowed
            IntegerAbs | IntegerMin | IntegerMax => true,

            // Not allowed - side effects
            ArrayPush | ArrayPop | MapSet => false,
            ConsolePrintln | ConsoleLog | ConsoleError => false,
            FileRead | FileWrite | FileOpen => false,

            // Bool operations - technically pure but unusual in init
            BoolNot | BoolAnd | BoolOr => false,
        }
    }
}

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

    // ===== 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::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個以上のメソッド
    }

    // ===== 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());
    }
}
