using System; using System.Collections.Generic; using System.Globalization; using System.Text; using ShiroginSDK.Runtime.Modules.Data.Scripts; using ShiroginSDK.Runtime.Services.Base; using ShiroginSDK.Runtime.Services.Interfaces; using UnityEngine; namespace ShiroginSDK.Runtime.Modules.RemoteConfig.Scripts.SO { [CreateAssetMenu(fileName = "RemoteConfigDefinition", menuName = "ShiroginSDK/Remote Config/Definition")] public class RemoteConfigDefinition : ScriptableObject { public enum ValueType { String, Number, Boolean } [SerializeField] [HideInInspector] private string _groupName = "DefaultGroup"; [Header("Game-specific Remote Config key-value pairs")] public List Entries = new(); [NonSerialized] public List RemoteEntries = new(); public string GroupName => _groupName; public void RefreshFromCache() { RemoteEntries.Clear(); var dataService = ServiceLocator.Get(); var data = dataService?.Get(); if (data == null || string.IsNullOrEmpty(data.CachedJson) || data.CachedJson == "{}") { Debug.Log("[RemoteConfigDefinition] ⚠️ No cached Remote Config data found."); return; } try { var json = data.CachedJson.Trim(); var groupStart = json.IndexOf('{', 1); var groupEnd = json.LastIndexOf('}'); if (groupStart < 0 || groupEnd <= groupStart) { Debug.LogWarning("[RemoteConfigDefinition] ⚠️ Invalid JSON format."); return; } var inner = json.Substring(groupStart + 1, groupEnd - groupStart - 1).Trim(); if (inner.StartsWith("{")) inner = inner.Substring(1); if (inner.EndsWith("}")) inner = inner.Substring(0, inner.Length - 1); var pairs = inner.Split(','); foreach (var pair in pairs) { var kv = pair.Split(':'); if (kv.Length < 2) continue; var key = kv[0].Trim().Trim('"'); var rawValue = kv[1].Trim().Trim('"'); var entry = new Entry { Key = key }; if (rawValue.Equals("true", StringComparison.OrdinalIgnoreCase) || rawValue.Equals("false", StringComparison.OrdinalIgnoreCase)) { entry.Type = ValueType.Boolean; entry.BoolValue = rawValue.Equals("true", StringComparison.OrdinalIgnoreCase); } else if (double.TryParse(rawValue, NumberStyles.Any, CultureInfo.InvariantCulture, out var num)) { entry.Type = ValueType.Number; entry.NumberValue = num; } else { entry.Type = ValueType.String; entry.StringValue = rawValue; } RemoteEntries.Add(entry); } Debug.Log( $"[RemoteConfigDefinition] ✅ Parsed {RemoteEntries.Count} entries from cached RemoteConfigData."); } catch (Exception ex) { Debug.LogError($"[RemoteConfigDefinition] ❌ Failed to parse cache: {ex.Message}"); } } public string GenerateJson(bool pretty = true) { var nl = pretty ? "\n" : string.Empty; var ind = pretty ? " " : string.Empty; var sb = new StringBuilder(); sb.Append('{').Append(nl); sb.Append(ind).Append('"').Append(_groupName).Append('"').Append(": {").Append(nl); for (var i = 0; i < Entries.Count; i++) { var e = Entries[i]; if (string.IsNullOrWhiteSpace(e.Key)) continue; sb.Append(ind).Append(ind) .Append('"').Append(Escape(e.Key)).Append('"') .Append(": "); switch (e.Type) { case ValueType.String: sb.Append('"').Append(Escape(e.StringValue ?? string.Empty)).Append('"'); break; case ValueType.Number: sb.Append(e.NumberValue.ToString(CultureInfo.InvariantCulture)); break; case ValueType.Boolean: sb.Append(e.BoolValue ? "true" : "false"); break; } if (i < Entries.Count - 1) sb.Append(','); sb.Append(nl); } sb.Append(ind).Append('}').Append(nl).Append('}'); return sb.ToString(); } public void SetGroupName(string group) { _groupName = group; } public bool ValidateKeys(out string error) { error = string.Empty; var seen = new HashSet(StringComparer.Ordinal); foreach (var e in Entries) { if (string.IsNullOrWhiteSpace(e.Key)) { error = "Empty key found."; return false; } if (!seen.Add(e.Key)) { error = $"Duplicate key: {e.Key}"; return false; } } return true; } private static string Escape(string s) { if (string.IsNullOrEmpty(s)) return string.Empty; var sb = new StringBuilder(); foreach (var c in s) switch (c) { case '"': sb.Append("\\\""); break; case '\\': sb.Append("\\\\"); break; case '\n': sb.Append("\\n"); break; case '\r': sb.Append("\\r"); break; case '\t': sb.Append("\\t"); break; default: sb.Append(c); break; } return sb.ToString(); } [Serializable] public class Entry { public string Key; public ValueType Type = ValueType.String; public string StringValue; public double NumberValue; public bool BoolValue; public object GetValue() { return Type switch { ValueType.String => StringValue ?? string.Empty, ValueType.Number => NumberValue, ValueType.Boolean => BoolValue, _ => string.Empty }; } } } }