using System;
using System.Collections.Generic;
using System.IO;
using System.Runtime.InteropServices;
using UnityEngine;

namespace FRVR
{
    public class DataModule : MonoBehaviour
    {
        private FRVRSDK _frvrSDK;

        // Storage op request tracking — correlates JS responses to C# callbacks by id
        // so concurrent calls don't overwrite each other, and stalled calls time out.
        // Shared between cloud and local storage ops.
        private const float StorageCallTimeoutSeconds = 30f;
        private readonly Dictionary<int, PendingStorageCall> _pendingStorageCalls = new Dictionary<int, PendingStorageCall>();
        private int _nextStorageRequestId = 1;

        private class PendingStorageCall
        {
            public float Deadline;
            public Action<StorageResponse> OnResponse;
            public Action OnTimeout;
        }

        [Serializable]
        private class StorageRequest
        {
            public int id;
            public string key;
            public string value;
            public string[] keys;
        }

        [Serializable]
        private class StorageResponse
        {
            public int id;
            public int result;
            public string value;
        }

        public void Init(FRVRSDK frvrSDK)
        {
            _frvrSDK = frvrSDK;
        }


    public void SetLocalJson(string json, Action<int> callback)
    {
        if (!Application.isEditor && Application.platform != RuntimePlatform.WebGLPlayer)
        {
            SafeInvoke(callback, 0);
            return;
        }

        _frvrSDK.DebugLog("Setting local JSON: " + json);

        int pendingId = 0;

        try
        {
            _frvrSDK.WrapSDKAction(
                () =>
                {
                    pendingId = RegisterPending(
                        resp => SafeInvoke(callback, resp.result),
                        () => SafeInvoke(callback, 0));
                    var req = new StorageRequest { id = pendingId, value = json ?? "" };
                    SetLocalJsonSDK(JsonUtility.ToJson(req));
                },
                () =>
                {
                    FRVREditorStore.SetLocal("json", json ?? "");
                    _frvrSDK.DebugLog("Editor: SetLocalJson -> " + FRVREditorStore.LocalPath);
                    SafeInvoke(callback, 1);
                }
            );
        }
        catch (Exception e)
        {
            _frvrSDK?.DebugLog($"SetLocalJson failed: {e.Message}");
            if (pendingId != 0) CancelPending(pendingId);
            SafeInvoke(callback, 0);
        }
    }

    public void GetLocalJson(Action<string> callback)
    {
        if (!Application.isEditor && Application.platform != RuntimePlatform.WebGLPlayer)
        {
            SafeInvoke(callback, "");
            return;
        }

        _frvrSDK.DebugLog("Getting local JSON");

        int pendingId = 0;

        try
        {
            _frvrSDK.WrapSDKAction(
                () =>
                {
                    pendingId = RegisterPending(
                        resp => SafeInvoke(callback, resp.value ?? ""),
                        () => SafeInvoke(callback, ""));
                    var req = new StorageRequest { id = pendingId };
                    GetLocalJsonSDK(JsonUtility.ToJson(req));
                },
                () =>
                {
                    string value = FRVREditorStore.GetLocal("json");
                    _frvrSDK.DebugLog("Editor: GetLocalJson -> " + FRVREditorStore.LocalPath);
                    SafeInvoke(callback, value ?? "");
                }
            );
        }
        catch (Exception e)
        {
            _frvrSDK?.DebugLog($"GetLocalJson failed: {e.Message}");
            if (pendingId != 0) CancelPending(pendingId);
            SafeInvoke(callback, "");
        }
    }

    // FRVR localStorage API wrapper methods
    public void SetItems(Dictionary<string, object> items, Action<int> callback)
    {
        if (!Application.isEditor && Application.platform != RuntimePlatform.WebGLPlayer)
        {
            SafeInvoke(callback, 0);
            return;
        }

        string json = JsonUtility.ToJson(new SerializableDictionary(items));
        _frvrSDK.DebugLog("Setting items: " + json);

        int pendingId = 0;

        try
        {
            _frvrSDK.WrapSDKAction(
                () =>
                {
                    pendingId = RegisterPending(
                        resp => SafeInvoke(callback, resp.result),
                        () => SafeInvoke(callback, 0));
                    var req = new StorageRequest { id = pendingId, value = json };
                    SetLocalJsonSDK(JsonUtility.ToJson(req));
                },
                () =>
                {
                    FRVREditorStore.SetLocal("json", json);
                    _frvrSDK.DebugLog("Editor: SetItems -> " + FRVREditorStore.LocalPath);
                    SafeInvoke(callback, 1);
                }
            );
        }
        catch (Exception e)
        {
            _frvrSDK?.DebugLog($"SetItems failed: {e.Message}");
            if (pendingId != 0) CancelPending(pendingId);
            SafeInvoke(callback, 0);
        }
    }

    public void GetItems(string[] keys, Action<Dictionary<string, object>> callback)
    {
        Action<string> parseAndInvoke = (stored) =>
        {
            try
            {
                var dict = string.IsNullOrEmpty(stored)
                    ? null
                    : JsonUtility.FromJson<SerializableDictionary>(stored);
                SafeInvoke(callback, dict?.ToDictionary() ?? new Dictionary<string, object>());
            }
            catch (Exception e)
            {
                _frvrSDK?.DebugLog("Error parsing GetItems result: " + e.Message);
                SafeInvoke(callback, new Dictionary<string, object>());
            }
        };

        if (!Application.isEditor && Application.platform != RuntimePlatform.WebGLPlayer)
        {
            SafeInvoke(callback, new Dictionary<string, object>());
            return;
        }

        _frvrSDK.DebugLog($"Getting items for {keys?.Length ?? 0} keys");

        int pendingId = 0;

        try
        {
            _frvrSDK.WrapSDKAction(
                () =>
                {
                    pendingId = RegisterPending(
                        resp => parseAndInvoke(resp.value ?? ""),
                        () => SafeInvoke(callback, new Dictionary<string, object>()));
                    var req = new StorageRequest { id = pendingId };
                    GetLocalJsonSDK(JsonUtility.ToJson(req));
                },
                () =>
                {
                    string stored = FRVREditorStore.GetLocal("json");
                    _frvrSDK.DebugLog("Editor: GetItems -> " + FRVREditorStore.LocalPath);
                    parseAndInvoke(stored);
                }
            );
        }
        catch (Exception e)
        {
            _frvrSDK?.DebugLog($"GetItems failed: {e.Message}");
            if (pendingId != 0) CancelPending(pendingId);
            SafeInvoke(callback, new Dictionary<string, object>());
        }
    }

    public void SetItem(string key, object value, Action<int> callback)
    {
        if (!Application.isEditor && Application.platform != RuntimePlatform.WebGLPlayer)
        {
            SafeInvoke(callback, 0);
            return;
        }

        _frvrSDK.DebugLog($"Setting item: {key} = {value}");

        string stringValue = value?.ToString() ?? "";
        int pendingId = 0;

        try
        {
            _frvrSDK.WrapSDKAction(
                () =>
                {
                    pendingId = RegisterPending(
                        resp => SafeInvoke(callback, resp.result),
                        () => SafeInvoke(callback, 0));
                    var req = new StorageRequest { id = pendingId, key = key, value = stringValue };
                    SetLocalItemSDK(JsonUtility.ToJson(req));
                },
                () =>
                {
                    FRVREditorStore.SetLocal(key, stringValue);
                    _frvrSDK.DebugLog($"Editor: SetItem '{key}' -> " + FRVREditorStore.LocalPath);
                    SafeInvoke(callback, 1);
                }
            );
        }
        catch (Exception e)
        {
            _frvrSDK?.DebugLog($"SetItem failed: {e.Message}");
            if (pendingId != 0) CancelPending(pendingId);
            SafeInvoke(callback, 0);
        }
    }

    public void GetItem(string key, Action<object> callback)
    {
        if (!Application.isEditor && Application.platform != RuntimePlatform.WebGLPlayer)
        {
            SafeInvoke<object>(callback, "");
            return;
        }

        _frvrSDK.DebugLog("Getting item: " + key);

        int pendingId = 0;

        try
        {
            _frvrSDK.WrapSDKAction(
                () =>
                {
                    pendingId = RegisterPending(
                        resp => SafeInvoke<object>(callback, resp.value ?? ""),
                        () => SafeInvoke<object>(callback, ""));
                    var req = new StorageRequest { id = pendingId, key = key };
                    GetLocalItemSDK(JsonUtility.ToJson(req));
                },
                () =>
                {
                    string stored = FRVREditorStore.GetLocal(key);
                    _frvrSDK.DebugLog($"Editor: GetItem '{key}' -> " + FRVREditorStore.LocalPath);
                    SafeInvoke<object>(callback, stored ?? "");
                }
            );
        }
        catch (Exception e)
        {
            _frvrSDK?.DebugLog($"GetItem failed: {e.Message}");
            if (pendingId != 0) CancelPending(pendingId);
            SafeInvoke<object>(callback, "");
        }
    }

    public void RemoveItem(string key, Action<int> callback)
    {
        if (!Application.isEditor && Application.platform != RuntimePlatform.WebGLPlayer)
        {
            SafeInvoke(callback, 0);
            return;
        }

        _frvrSDK.DebugLog($"Removing local item: {key}");

        int pendingId = 0;

        try
        {
            _frvrSDK.WrapSDKAction(
                () =>
                {
                    pendingId = RegisterPending(
                        resp => SafeInvoke(callback, resp.result),
                        () => SafeInvoke(callback, 0));
                    var req = new StorageRequest { id = pendingId, key = key };
                    RemoveLocalItemSDK(JsonUtility.ToJson(req));
                },
                () =>
                {
                    FRVREditorStore.RemoveLocal(key);
                    _frvrSDK.DebugLog($"Editor: RemoveItem '{key}' -> " + FRVREditorStore.LocalPath);
                    SafeInvoke(callback, 1);
                }
            );
        }
        catch (Exception e)
        {
            _frvrSDK?.DebugLog($"RemoveItem failed: {e.Message}");
            if (pendingId != 0) CancelPending(pendingId);
            SafeInvoke(callback, 0);
        }
    }

    public void RemoveItems(string[] keys, Action<int> callback)
    {
        if (!Application.isEditor && Application.platform != RuntimePlatform.WebGLPlayer)
        {
            SafeInvoke(callback, 0);
            return;
        }

        string[] keysSafe = keys ?? new string[0];
        _frvrSDK.DebugLog($"Removing local items: {keysSafe.Length} keys");

        int pendingId = 0;

        try
        {
            _frvrSDK.WrapSDKAction(
                () =>
                {
                    pendingId = RegisterPending(
                        resp => SafeInvoke(callback, resp.result),
                        () => SafeInvoke(callback, 0));
                    var req = new StorageRequest { id = pendingId, keys = keysSafe };
                    RemoveLocalItemsSDK(JsonUtility.ToJson(req));
                },
                () =>
                {
                    foreach (var k in keysSafe) FRVREditorStore.RemoveLocal(k);
                    _frvrSDK.DebugLog($"Editor: RemoveItems ({keysSafe.Length}) -> " + FRVREditorStore.LocalPath);
                    SafeInvoke(callback, 1);
                }
            );
        }
        catch (Exception e)
        {
            _frvrSDK?.DebugLog($"RemoveItems failed: {e.Message}");
            if (pendingId != 0) CancelPending(pendingId);
            SafeInvoke(callback, 0);
        }
    }

        // JSLib callback methods — all route through the unified request-id system.

        internal void HandleSetCloudItemComplete(string json) { RouteStorageResponse(json, "HandleSetCloudItemComplete"); }
        internal void HandleGetCloudItemComplete(string json) { RouteStorageResponse(json, "HandleGetCloudItemComplete"); }
        internal void HandleRemoveCloudItemComplete(string json) { RouteStorageResponse(json, "HandleRemoveCloudItemComplete"); }
        internal void HandleRemoveCloudItemsComplete(string json) { RouteStorageResponse(json, "HandleRemoveCloudItemsComplete"); }

        internal void HandleSetLocalJsonComplete(string json) { RouteStorageResponse(json, "HandleSetLocalJsonComplete"); }
        internal void HandleGetLocalJsonComplete(string json) { RouteStorageResponse(json, "HandleGetLocalJsonComplete"); }
        internal void HandleSetItemComplete(string json) { RouteStorageResponse(json, "HandleSetItemComplete"); }
        internal void HandleGetItemComplete(string json) { RouteStorageResponse(json, "HandleGetItemComplete"); }
        internal void HandleRemoveLocalItemComplete(string json) { RouteStorageResponse(json, "HandleRemoveLocalItemComplete"); }
        internal void HandleRemoveLocalItemsComplete(string json) { RouteStorageResponse(json, "HandleRemoveLocalItemsComplete"); }

        private void RouteStorageResponse(string json, string source)
        {
            try
            {
                if (string.IsNullOrEmpty(json))
                {
                    _frvrSDK?.DebugLog($"{source}: empty payload");
                    return;
                }
                var resp = JsonUtility.FromJson<StorageResponse>(json);
                if (resp == null || resp.id == 0)
                {
                    _frvrSDK?.DebugLog($"{source}: unparseable or id=0 payload '{json}'");
                    return;
                }
                CompletePending(resp.id, resp);
            }
            catch (Exception e)
            {
                _frvrSDK?.DebugLog($"{source} threw: " + e.Message);
            }
        }

    public void SetCloudItem(string key, object value, Action<int> callback)
    {
        if (!Application.isEditor && Application.platform != RuntimePlatform.WebGLPlayer)
        {
            SafeInvoke(callback, 0);
            return;
        }

        _frvrSDK.DebugLog($"Setting cloud item: {key} = {value}");

        string stringValue = value?.ToString() ?? "";
        int pendingId = 0;

        try
        {
            _frvrSDK.WrapSDKAction(
                () =>
                {
                    pendingId = RegisterPending(
                        resp => SafeInvoke(callback, resp.result),
                        () => SafeInvoke(callback, 0));
                    var req = new StorageRequest { id = pendingId, key = key, value = stringValue };
                    SetCloudItemSDK(JsonUtility.ToJson(req));
                },
                () =>
                {
                    FRVREditorStore.SetCloud(key, stringValue);
                    _frvrSDK.DebugLog($"Editor: SetCloudItem '{key}' -> " + FRVREditorStore.CloudPath);
                    SafeInvoke(callback, 1);
                }
            );
        }
        catch (Exception e)
        {
            _frvrSDK?.DebugLog($"SetCloudItem failed: {e.Message}");
            if (pendingId != 0) CancelPending(pendingId);
            SafeInvoke(callback, 0);
        }
    }

    public void GetCloudItem(string key, Action<object> callback)
    {
        if (!Application.isEditor && Application.platform != RuntimePlatform.WebGLPlayer)
        {
            SafeInvoke<object>(callback, "");
            return;
        }

        _frvrSDK.DebugLog("Getting cloud item: " + key);

        int pendingId = 0;

        try
        {
            _frvrSDK.WrapSDKAction(
                () =>
                {
                    pendingId = RegisterPending(
                        resp => SafeInvoke<object>(callback, resp.value ?? ""),
                        () => SafeInvoke<object>(callback, ""));
                    var req = new StorageRequest { id = pendingId, key = key };
                    GetCloudItemSDK(JsonUtility.ToJson(req));
                },
                () =>
                {
                    string stored = FRVREditorStore.GetCloud(key);
                    _frvrSDK.DebugLog($"Editor: GetCloudItem '{key}' -> " + FRVREditorStore.CloudPath);
                    SafeInvoke<object>(callback, stored ?? "");
                }
            );
        }
        catch (Exception e)
        {
            _frvrSDK?.DebugLog($"GetCloudItem failed: {e.Message}");
            if (pendingId != 0) CancelPending(pendingId);
            SafeInvoke<object>(callback, "");
        }
    }

    public void RemoveCloudItem(string key, Action<int> callback)
    {
        if (!Application.isEditor && Application.platform != RuntimePlatform.WebGLPlayer)
        {
            SafeInvoke(callback, 0);
            return;
        }

        _frvrSDK.DebugLog($"Removing cloud item: {key}");

        int pendingId = 0;

        try
        {
            _frvrSDK.WrapSDKAction(
                () =>
                {
                    pendingId = RegisterPending(
                        resp => SafeInvoke(callback, resp.result),
                        () => SafeInvoke(callback, 0));
                    var req = new StorageRequest { id = pendingId, key = key };
                    RemoveCloudItemSDK(JsonUtility.ToJson(req));
                },
                () =>
                {
                    FRVREditorStore.RemoveCloud(key);
                    _frvrSDK.DebugLog($"Editor: RemoveCloudItem '{key}' -> " + FRVREditorStore.CloudPath);
                    SafeInvoke(callback, 1);
                }
            );
        }
        catch (Exception e)
        {
            _frvrSDK?.DebugLog($"RemoveCloudItem failed: {e.Message}");
            if (pendingId != 0) CancelPending(pendingId);
            SafeInvoke(callback, 0);
        }
    }

    public void RemoveCloudItems(string[] keys, Action<int> callback)
    {
        if (!Application.isEditor && Application.platform != RuntimePlatform.WebGLPlayer)
        {
            SafeInvoke(callback, 0);
            return;
        }

        string[] keysSafe = keys ?? new string[0];
        _frvrSDK.DebugLog($"Removing cloud items: {keysSafe.Length} keys");

        int pendingId = 0;

        try
        {
            _frvrSDK.WrapSDKAction(
                () =>
                {
                    pendingId = RegisterPending(
                        resp => SafeInvoke(callback, resp.result),
                        () => SafeInvoke(callback, 0));
                    var req = new StorageRequest { id = pendingId, keys = keysSafe };
                    RemoveCloudItemsSDK(JsonUtility.ToJson(req));
                },
                () =>
                {
                    foreach (var k in keysSafe) FRVREditorStore.RemoveCloud(k);
                    _frvrSDK.DebugLog($"Editor: RemoveCloudItems ({keysSafe.Length}) -> " + FRVREditorStore.CloudPath);
                    SafeInvoke(callback, 1);
                }
            );
        }
        catch (Exception e)
        {
            _frvrSDK?.DebugLog($"RemoveCloudItems failed: {e.Message}");
            if (pendingId != 0) CancelPending(pendingId);
            SafeInvoke(callback, 0);
        }
    }

    private int RegisterPending(Action<StorageResponse> onResponse, Action onTimeout)
    {
        int id = _nextStorageRequestId++;
        if (_nextStorageRequestId <= 0) _nextStorageRequestId = 1; // wrap-safety; 0 reserved for "no id"
        _pendingStorageCalls[id] = new PendingStorageCall
        {
            Deadline = Time.realtimeSinceStartup + StorageCallTimeoutSeconds,
            OnResponse = onResponse,
            OnTimeout = onTimeout
        };
        return id;
    }

    private void CancelPending(int id)
    {
        _pendingStorageCalls.Remove(id);
    }

    private void CompletePending(int id, StorageResponse resp)
    {
        if (!_pendingStorageCalls.TryGetValue(id, out var call))
        {
            _frvrSDK?.DebugLog($"Storage response for unknown id {id} (timed out or duplicate)");
            return;
        }
        _pendingStorageCalls.Remove(id);
        try { call.OnResponse?.Invoke(resp); }
        catch (Exception e) { _frvrSDK?.DebugLog("Storage response callback threw: " + e.Message); }
    }

    private void Update()
    {
        if (_pendingStorageCalls.Count == 0) return;
        float now = Time.realtimeSinceStartup;
        List<int> expired = null;
        foreach (var kvp in _pendingStorageCalls)
        {
            if (kvp.Value.Deadline < now)
            {
                if (expired == null) expired = new List<int>();
                expired.Add(kvp.Key);
            }
        }
        if (expired == null) return;
        foreach (var id in expired)
        {
            if (!_pendingStorageCalls.TryGetValue(id, out var call)) continue;
            _pendingStorageCalls.Remove(id);
            _frvrSDK?.DebugLog($"Storage call id {id} timed out after {StorageCallTimeoutSeconds}s");
            try { call.OnTimeout?.Invoke(); }
            catch (Exception e) { _frvrSDK?.DebugLog("Storage timeout callback threw: " + e.Message); }
        }
    }

    private void SafeInvoke<T>(Action<T> callback, T value)
    {
        if (callback == null) return;
        try { callback(value); }
        catch (Exception e) { _frvrSDK?.DebugLog("Storage callback threw: " + e.Message); }
    }

#if UNITY_WEBGL
        [DllImport("__Internal")]
        private static extern void SetLocalJsonSDK(string jsonRequest);

        [DllImport("__Internal")]
        private static extern void GetLocalJsonSDK(string jsonRequest);

        [DllImport("__Internal")]
        private static extern void SetLocalItemSDK(string jsonRequest);

        [DllImport("__Internal")]
        private static extern void GetLocalItemSDK(string jsonRequest);

        [DllImport("__Internal")]
        private static extern void RemoveLocalItemSDK(string jsonRequest);

        [DllImport("__Internal")]
        private static extern void RemoveLocalItemsSDK(string jsonRequest);

        [DllImport("__Internal")]
        private static extern void SetCloudItemSDK(string jsonRequest);

        [DllImport("__Internal")]
        private static extern void GetCloudItemSDK(string jsonRequest);

        [DllImport("__Internal")]
        private static extern void RemoveCloudItemSDK(string jsonRequest);

        [DllImport("__Internal")]
        private static extern void RemoveCloudItemsSDK(string jsonRequest);
#else
        private void SetLocalJsonSDK(string jsonRequest) { }
        private void GetLocalJsonSDK(string jsonRequest) { }
        private void SetLocalItemSDK(string jsonRequest) { }
        private void GetLocalItemSDK(string jsonRequest) { }
        private void RemoveLocalItemSDK(string jsonRequest) { }
        private void RemoveLocalItemsSDK(string jsonRequest) { }
        private void SetCloudItemSDK(string jsonRequest) { }
        private void GetCloudItemSDK(string jsonRequest) { }
        private void RemoveCloudItemSDK(string jsonRequest) { }
        private void RemoveCloudItemsSDK(string jsonRequest) { }
#endif
    }

    [Serializable]
    public class SerializableDictionary
    {
        [Serializable]
        public class KeyValuePair
        {
            public string key;
            public string value;
        }

        public KeyValuePair[] items;

        public SerializableDictionary(Dictionary<string, object> dict)
        {
            if (dict == null)
            {
                items = new KeyValuePair[0];
                return;
            }

            items = new KeyValuePair[dict.Count];
            int index = 0;
            foreach (var kvp in dict)
            {
                items[index] = new KeyValuePair
                {
                    key = kvp.Key,
                    value = kvp.Value?.ToString() ?? "null"
                };
                index++;
            }
        }

        public Dictionary<string, object> ToDictionary()
        {
            var dict = new Dictionary<string, object>();
            if (items != null)
            {
                foreach (var item in items)
                {
                    dict[item.key] = item.value;
                }
            }
            return dict;
        }
    }

    [Serializable]
    public class KeysWrapper
    {
        public string[] keys;
    }

    // Editor-only persisted key/value store used by DataModule editor fallbacks.
    // Data is written as JSON under <Project>/Library/FRVRSim/{local,cloud}.json
    // so it's outside of Assets (not imported by Unity, not version-controlled).
    //
    // No in-memory cache — every call round-trips to disk. This keeps behavior
    // consistent across Play-mode restarts even when Domain Reload is disabled,
    // and across external edits of the JSON files while the Editor is running.
    internal static class FRVREditorStore
    {
        private const string DirName = "FRVRSim";
        private const string LocalFile = "local.json";
        private const string CloudFile = "cloud.json";

        public static string LocalPath => ResolvePath(LocalFile);
        public static string CloudPath => ResolvePath(CloudFile);

        public static void SetLocal(string key, string value)
        {
            var dict = Load(LocalPath);
            dict[key] = value ?? "";
            Save(LocalPath, dict);
        }

        public static string GetLocal(string key)
        {
            var dict = Load(LocalPath);
            return dict.TryGetValue(key, out var v) ? v : null;
        }

        public static void RemoveLocal(string key)
        {
            var dict = Load(LocalPath);
            if (dict.Remove(key)) Save(LocalPath, dict);
        }

        public static void SetCloud(string key, string value)
        {
            var dict = Load(CloudPath);
            dict[key] = value ?? "";
            Save(CloudPath, dict);
        }

        public static string GetCloud(string key)
        {
            var dict = Load(CloudPath);
            return dict.TryGetValue(key, out var v) ? v : null;
        }

        public static void RemoveCloud(string key)
        {
            var dict = Load(CloudPath);
            if (dict.Remove(key)) Save(CloudPath, dict);
        }

        public static void ClearLocal() => Save(LocalPath, new Dictionary<string, string>());
        public static void ClearCloud() => Save(CloudPath, new Dictionary<string, string>());

        private static string ResolvePath(string fileName)
        {
            string projectRoot = Path.GetDirectoryName(Application.dataPath) ?? ".";
            string dir = Path.Combine(projectRoot, "Library", DirName);
            Directory.CreateDirectory(dir);
            return Path.Combine(dir, fileName);
        }

        private static Dictionary<string, string> Load(string path)
        {
            var dict = new Dictionary<string, string>();
            if (!File.Exists(path)) return dict;
            try
            {
                var json = File.ReadAllText(path);
                if (string.IsNullOrEmpty(json)) return dict;
                var wrapper = JsonUtility.FromJson<StoreWrapper>(json);
                if (wrapper?.items != null)
                {
                    foreach (var item in wrapper.items)
                    {
                        if (!string.IsNullOrEmpty(item.key))
                            dict[item.key] = item.value ?? "";
                    }
                }
            }
            catch (Exception e)
            {
                Debug.LogWarning($"[FRVREditorStore] Failed to load {path}: {e.Message}");
            }
            return dict;
        }

        private static void Save(string path, Dictionary<string, string> dict)
        {
            try
            {
                var wrapper = new StoreWrapper { items = new StoreEntry[dict.Count] };
                int i = 0;
                foreach (var kvp in dict)
                {
                    wrapper.items[i++] = new StoreEntry { key = kvp.Key, value = kvp.Value };
                }
                File.WriteAllText(path, JsonUtility.ToJson(wrapper, true));
            }
            catch (Exception e)
            {
                Debug.LogWarning($"[FRVREditorStore] Failed to save {path}: {e.Message}");
            }
        }

        [Serializable]
        private class StoreWrapper
        {
            public StoreEntry[] items;
        }

        [Serializable]
        private class StoreEntry
        {
            public string key;
            public string value;
        }
    }
}
