#if UNITY_EDITOR using System; using System.Collections; using System.Collections.Generic; using System.Globalization; using System.Linq; using System.Reflection; using System.Text.RegularExpressions; using ShiroginSDK.Runtime.Core.SDK; using ShiroginSDK.Runtime.Modules.Data.Scripts; using ShiroginSDK.Runtime.Modules.RemoteConfig.Scripts.SO; using ShiroginSDK.Runtime.Shared.Constants; using UnityEditor; using UnityEngine; namespace ShiroginSDK.Editor.RemoteConfig { [CustomEditor(typeof(RemoteConfigDefinition))] public class RemoteConfigDefinitionEditor : UnityEditor.Editor { private bool showRemotePreview; public override void OnInspectorGUI() { var def = (RemoteConfigDefinition)target; // Auto sync group name with SDKConfig var sdkConfig = Resources.Load(SDKConstants.ConfigResourcePath); if (sdkConfig != null && def.GroupName != sdkConfig.selectedRemoteGroup) { def.SetGroupName(sdkConfig.selectedRemoteGroup); EditorUtility.SetDirty(def); } EditorGUILayout.Space(6); EditorGUILayout.LabelField("Remote Config Info", EditorStyles.boldLabel); using (new EditorGUI.DisabledScope(true)) { EditorGUILayout.TextField("Group Name", def.GroupName); } EditorGUILayout.Space(8); DrawDefaultInspector(); EditorGUILayout.Space(8); EditorGUILayout.LabelField("Remote Config Tools", EditorStyles.boldLabel); using (new EditorGUILayout.HorizontalScope()) { if (GUILayout.Button("✅ Validate Keys")) { if (def.ValidateKeys(out var err)) EditorUtility.DisplayDialog("Validation", "All keys valid ✅", "OK"); else EditorUtility.DisplayDialog("Validation Failed", err, "OK"); } if (GUILayout.Button("🔤 Sort by Key")) { Undo.RecordObject(def, "Sort Remote Config Entries"); def.Entries = def.Entries.OrderBy(e => e.Key).ToList(); EditorUtility.SetDirty(def); } } if (GUILayout.Button("📋 Copy JSON to Clipboard")) { if (!def.ValidateKeys(out var err)) { EditorUtility.DisplayDialog("Error", err, "OK"); return; } var json = def.GenerateJson(); EditorGUIUtility.systemCopyBuffer = json; Debug.Log($"[RemoteConfig] JSON copied to clipboard ({json.Length} chars)"); EditorUtility.DisplayDialog("Success", "JSON copied to clipboard ✅", "OK"); } // 🔸 Import button if (GUILayout.Button("⬆️ Import from RemoteConfigData")) ImportFromRemoteConfigData(def); // 🌐 Remote Data Preview (Cached) EditorGUILayout.Space(12); showRemotePreview = EditorGUILayout.Foldout(showRemotePreview, "🌐 Remote Data Preview (Cached)", true); if (showRemotePreview) { if (GUILayout.Button("🔄 Refresh from Cache")) { def.RefreshFromCache(); Debug.Log("[RemoteConfigDefinition] 🔄 Refreshed from cached RemoteConfigData."); } if (def.RemoteEntries == null || def.RemoteEntries.Count == 0) { EditorGUILayout.HelpBox("No cached remote data found.", MessageType.Info); } else { EditorGUILayout.Space(4); EditorGUILayout.LabelField("Cached Entries:", EditorStyles.boldLabel); EditorGUILayout.BeginVertical("box"); foreach (var entry in def.RemoteEntries) { EditorGUILayout.BeginHorizontal(); EditorGUILayout.LabelField(entry.Key, GUILayout.Width(160)); switch (entry.Type) { case RemoteConfigDefinition.ValueType.Boolean: EditorGUILayout.Toggle(entry.BoolValue); break; case RemoteConfigDefinition.ValueType.Number: EditorGUILayout.LabelField(entry.NumberValue.ToString(CultureInfo.InvariantCulture)); break; default: EditorGUILayout.LabelField(entry.StringValue); break; } EditorGUILayout.EndHorizontal(); } EditorGUILayout.EndVertical(); } } if (GUI.changed) EditorUtility.SetDirty(def); } /// /// Imports instance fields from RemoteConfigData as RemoteConfigDefinition entries. /// Works with BaseData-based RemoteConfigData (non-static). /// private void ImportFromRemoteConfigData(RemoteConfigDefinition def) { // Onay penceresi var proceed = EditorUtility.DisplayDialog( "Confirm Import", "Are you sure you want to import all fields from RemoteConfigData?\n\nThis will overwrite current entries.", "Yes, Import", "Cancel" ); if (!proceed) { Debug.Log("[RemoteConfigDefinitionEditor] 🚫 Import canceled by user."); return; } Undo.RecordObject(def, "Import from RemoteConfigData"); def.Entries.Clear(); var remoteDataType = typeof(RemoteConfigData); // Public INSTANCE fields (BaseData yapısıyla uyumlu) var fields = remoteDataType.GetFields(BindingFlags.Public | BindingFlags.Instance); if (fields == null || fields.Length == 0) { Debug.LogWarning( "[RemoteConfigDefinitionEditor] ⚠️ RemoteConfigData içinde public instance field bulunamadı."); return; } // Geçici örnek yarat (BaseData default değerlerini okumak için) var instance = Activator.CreateInstance(remoteDataType); var ci = CultureInfo.InvariantCulture; foreach (var f in fields) { if (f.IsLiteral || f.IsInitOnly) continue; // const/readonly atla var entry = new RemoteConfigDefinition.Entry(); entry.Key = ToSnakeCasePreserveUnderscores(f.Name); // 🔸 snake_case + '_' korunur var val = f.GetValue(instance); if (val == null) { entry.Type = RemoteConfigDefinition.ValueType.String; entry.StringValue = string.Empty; } else if (val is bool b) { entry.Type = RemoteConfigDefinition.ValueType.Boolean; entry.BoolValue = b; } else if (val is int i) { entry.Type = RemoteConfigDefinition.ValueType.Number; entry.NumberValue = i; } else if (val is float fl) { entry.Type = RemoteConfigDefinition.ValueType.Number; entry.NumberValue = fl; } else if (val is Array arr) { // Dizileri "1,1.4,2" şeklinde string'e çevir var values = arr.Cast().Select(x => Convert.ToString(x, ci)).ToArray(); entry.Type = RemoteConfigDefinition.ValueType.String; entry.StringValue = string.Join(",", values); } else if (val is IEnumerable list && !(val is string)) { // Listeleri string'e çevir var values = new List(); foreach (var item in list) values.Add(Convert.ToString(item, ci)); entry.Type = RemoteConfigDefinition.ValueType.String; entry.StringValue = string.Join(",", values); } else { entry.Type = RemoteConfigDefinition.ValueType.String; entry.StringValue = val.ToString(); } def.Entries.Add(entry); } def.Entries = def.Entries.OrderBy(e => e.Key).ToList(); EditorUtility.SetDirty(def); Debug.Log($"[RemoteConfigDefinitionEditor] ✅ Imported {def.Entries.Count} entries from RemoteConfigData."); EditorUtility.DisplayDialog("Import Completed", $"Successfully imported {def.Entries.Count} entries from RemoteConfigData.", "OK"); } // Pascal/camelCase → snake_case, mevcut '_' karakterlerini KORUR // Örn: "TargetFps" -> "target_fps", "Enemy_HpMultiplier" -> "enemy_hp_multiplier" private static string ToSnakeCasePreserveUnderscores(string input) { if (string.IsNullOrEmpty(input)) return input; // Büyük harf grupları ve geçişleri doğru bölmek için regex // (mevcut '_' karakterlerine dokunmuyoruz) var s = Regex.Replace(input, "([A-Z]+)([A-Z][a-z])", "$1_$2"); s = Regex.Replace(s, "([a-z0-9])([A-Z])", "$1_$2"); // Çift alt çizgileri tek'e indir (varsa) while (s.Contains("__")) s = s.Replace("__", "_"); return s.ToLowerInvariant(); } } } #endif