// Copyright (c) 2026 EzAdmin. All rights reserved.
// Licensed under the EzAdmin Client License. See LICENSE-CLIENT.txt.

using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
using EzAdmin.Client;
using UnityEditor;
using UnityEditor.SceneManagement;
using UnityEngine;

namespace EzAdmin.EditorTools
{

    public sealed class EzAdminSetupWizard : EditorWindow
    {
        private enum Platform { VR, Flat, VRAndFlat }
        private enum Corner { BottomLeft, BottomRight, TopLeft, TopRight }
        private enum Items { None, CatalogLegacy, EconomyV2 }

        private const string HooksFolder = "Assets/EzAdmin/MyGame";
        private const string HooksPath = HooksFolder + "/EzAdminHooks.cs";
        private const string PendingHooksKey = "EzAdmin.Wizard.AddHooksAfterCompile";
        private const string ShownKey = "EzAdmin.Wizard.Shown";

        private Platform _platform = Platform.VR;
        private Corner _corner = Corner.BottomLeft;
        private bool _staffButton = true;
        private LayerMask _handLayers = ~0;
        private string _address = EzAdminClient.SiteAddress;
        private bool _hasOwnLogin = true;
        private Items _items = Items.CatalogLegacy;
        private bool _mutes = true;
        private bool _antiCheat = true;
        private bool _discordLink;
        private bool _writeHooks = true;
        private string _loginFoundIn;
        private bool _useMeta = true;
        private bool _secureMeta = true;
        private string _metaPrefix = EzAdminAuth.DefaultMetaPrefix;
        private string _metaPrefixFound;
        private const string MetaDefine = "EZADMIN_META";
        private Vector2 _scroll;
        private string _report = "";
        private int _loading = 1;
        private string _loadName = "";
        private Sprite _loadLogo;
        private Sprite _loadBackground;
        private Color _loadAccent = new Color32(0x22, 0xC5, 0x5E, 0xFF);
        private int _loadNext;

        [MenuItem("Window/EzAdmin/Setup Wizard", false, 0)]
        [MenuItem("Tools/EzAdmin/Setup Wizard", false, 0)]
        public static void Open()
        {
            var window = GetWindow<EzAdminSetupWizard>(true, "EzAdmin Setup", true);
            window.minSize = new Vector2(560, 700);
            window.titleContent = new GUIContent("EzAdmin Setup", EzAdminBrand.Logo(32));
            window.LoadFromScene();
        }

        [InitializeOnLoadMethod]
        private static void OpenOnFirstImport()
        {
            if (EditorPrefs.GetBool(ShownKey + "." + Application.productName, false)) return;
            EditorPrefs.SetBool(ShownKey + "." + Application.productName, true);
            EditorApplication.delayCall += Open;
        }

        private void LoadFromScene()
        {
            var client = FindObjectOfType<EzAdminClient>();
            if (client != null)
            {
                _address = EzAdminClient.SiteAddress;
            }
            _loading = EzAdminLoadingSceneMaker.Exists ? 0 : 1;
            _loginFoundIn = FindOwnLogin();
            _hasOwnLogin = _loginFoundIn != null && FindObjectOfType<EzAdminAuth>() == null;
            _useMeta = MetaSdkFound();
            _metaPrefixFound = FindMetaPrefix(_loginFoundIn);
            var existing = FindObjectOfType<EzAdminAuth>();
            _metaPrefix = existing != null
                ? new SerializedObject(existing).FindProperty("_metaPrefix").stringValue
                : _metaPrefixFound ?? EzAdminAuth.DefaultMetaPrefix;
            _platform = HasPackage("com.unity.xr.management") || HasPackage("com.meta.xr.sdk.core")
                        || HasPackage("com.unity.xr.oculus") || AssetDatabase.IsValidFolder("Assets/Oculus")
                ? Platform.VR : Platform.Flat;
        }

        private static string FindOwnLogin()
        {
            foreach (string guid in AssetDatabase.FindAssets("t:MonoScript", new[] { "Assets" }))
            {
                string path = AssetDatabase.GUIDToAssetPath(guid);
                if (!path.EndsWith(".cs") || path.StartsWith("Assets/EzAdmin/") || path.Contains("/PlayFabSDK/")
                    || path.Contains("/PlayFabEditorExtensions/"))
                {
                    continue;
                }
                try
                {
                    string text = File.ReadAllText(path);
                    if (text.Contains("PlayFabClientAPI.LoginWith") || text.Contains("PlayFabClientAPI .LoginWith"))
                    {
                        return path;
                    }
                }
                catch (IOException)
                {
                }
            }
            return null;
        }

        private static string FindMetaPrefix(string loginPath)
        {
            if (string.IsNullOrEmpty(loginPath)) return null;
            try
            {
                var match = System.Text.RegularExpressions.Regex.Match(File.ReadAllText(loginPath),
                    "CustomId\\s*=\\s*\"([A-Za-z0-9_\\-]{1,24})\"\\s*\\+");
                return match.Success ? match.Groups[1].Value : null;
            }
            catch (IOException)
            {
                return null;
            }
        }

        public static bool MetaSdkFound()
        {
            foreach (var assembly in AppDomain.CurrentDomain.GetAssemblies())
            {
                if (assembly.GetType("Oculus.Platform.Users", false) != null) return true;
            }
            return false;
        }

        public static bool MetaSdkOn()
        {
            return HasDefine(BuildTargetGroup.Android, MetaDefine);
        }

        private static bool HasDefine(BuildTargetGroup group, string define)
        {
            string defines = PlayerSettings.GetScriptingDefineSymbolsForGroup(group);
            return Array.IndexOf(defines.Split(';'), define) >= 0;
        }

        private static void SetDefine(BuildTargetGroup group, string define, bool on)
        {
            var list = new List<string>(PlayerSettings.GetScriptingDefineSymbolsForGroup(group)
                .Split(new[] { ';' }, StringSplitOptions.RemoveEmptyEntries));
            bool has = list.Contains(define);
            if (on == has) return;
            if (on) list.Add(define); else list.Remove(define);
            PlayerSettings.SetScriptingDefineSymbolsForGroup(group, string.Join(";", list));
        }

        private static bool HasPackage(string name)
        {
            string manifest = Path.Combine(Application.dataPath, "..", "Packages", "manifest.json");
            return File.Exists(manifest) && File.ReadAllText(manifest).Contains("\"" + name + "\"");
        }

        private static readonly Color Bg = new Color32(0x0D, 0x0F, 0x14, 0xFF);
        private static readonly Color CardBg = new Color32(0x16, 0x19, 0x21, 0xFF);
        private static readonly Color CardLine = new Color32(0x26, 0x2A, 0x34, 0xFF);
        private static readonly Color Text = new Color32(0xF2, 0xF4, 0xF8, 0xFF);
        private static readonly Color Dim = new Color32(0x9A, 0xA1, 0xAE, 0xFF);
        private static readonly Color Bad = new Color32(0xF8, 0x71, 0x71, 0xFF);
        private static readonly Color Warn = new Color32(0xFB, 0xBF, 0x24, 0xFF);

        private GUIStyle _title, _subtitle, _cardTitle, _body, _small, _chip, _segOn, _segOff, _primary,
                         _secondary, _cardStyle, _stepNumber, _row;
        private readonly System.Collections.Generic.Dictionary<Color, Texture2D> _solids =
            new System.Collections.Generic.Dictionary<Color, Texture2D>();

        private Texture2D Solid(Color colour)
        {
            Texture2D t;
            if (_solids.TryGetValue(colour, out t) && t != null) return t;
            t = new Texture2D(1, 1) { hideFlags = HideFlags.HideAndDontSave };
            t.SetPixel(0, 0, colour);
            t.Apply();
            _solids[colour] = t;
            return t;
        }

        private GUIStyle Style(GUIStyle from, int size, FontStyle weight, Color colour, bool wrap = true)
        {
            var style = new GUIStyle(from) { fontSize = size, fontStyle = weight, wordWrap = wrap, richText = true };
            style.normal.textColor = colour;
            return style;
        }

        private void EnsureStyles()
        {
            if (_title != null) return;
            _title = Style(EditorStyles.label, 22, FontStyle.Bold, Text, false);
            _subtitle = Style(EditorStyles.label, 12, FontStyle.Normal, Dim);
            _cardTitle = Style(EditorStyles.label, 14, FontStyle.Bold, Text, false);
            _body = Style(EditorStyles.label, 12, FontStyle.Normal, Text);
            _small = Style(EditorStyles.label, 11, FontStyle.Normal, Dim);
            _chip = Style(EditorStyles.label, 11, FontStyle.Bold, Text, false);
            _chip.alignment = TextAnchor.MiddleCenter;
            _chip.padding = new RectOffset(10, 10, 5, 5);
            _row = Style(EditorStyles.label, 12, FontStyle.Normal, Text);

            _segOff = new GUIStyle(GUI.skin.button) { fontSize = 12, fontStyle = FontStyle.Bold, fixedHeight = 34 };
            _segOff.normal.background = Solid(new Color32(0x20, 0x24, 0x2D, 0xFF));
            _segOff.hover.background = Solid(new Color32(0x2A, 0x2F, 0x3A, 0xFF));
            _segOff.active.background = _segOff.hover.background;
            _segOff.normal.textColor = _segOff.hover.textColor = _segOff.active.textColor = Dim;
            _segOff.border = new RectOffset(0, 0, 0, 0);
            _segOn = new GUIStyle(_segOff);
            _segOn.normal.background = _segOn.hover.background = _segOn.active.background = Solid(EzAdminBrand.GreenDeep);
            _segOn.normal.textColor = _segOn.hover.textColor = _segOn.active.textColor = Color.white;

            _primary = new GUIStyle(_segOn) { fontSize = 15, fixedHeight = 46 };
            _primary.hover.background = Solid(EzAdminBrand.Green);
            _secondary = new GUIStyle(_segOff) { fontSize = 11, fixedHeight = 28 };
            _secondary.normal.textColor = _secondary.hover.textColor = Text;

            _cardStyle = new GUIStyle { padding = new RectOffset(16, 16, 14, 16), margin = new RectOffset(14, 14, 6, 6) };
            _cardStyle.normal.background = Solid(CardBg);
            _stepNumber = Style(EditorStyles.label, 12, FontStyle.Bold, Color.white, false);
            _stepNumber.alignment = TextAnchor.MiddleCenter;
            _stepNumber.normal.background = Solid(EzAdminBrand.GreenDeep);
            _stepNumber.fixedWidth = 22;
            _stepNumber.fixedHeight = 22;
        }

        private void OnGUI()
        {
            EnsureStyles();
            EditorGUI.DrawRect(new Rect(0, 0, position.width, position.height), Bg);
            _scroll = EditorGUILayout.BeginScrollView(_scroll);

            Header();
            StatusStrip();

            Card("1", "Your game", "Where people play it. Players link on the EzAdmin website, with no code to type.", () =>
            {
                _platform = (Platform)Segmented((int)_platform, "VR", "Flat screen", "Both");
                GUILayout.Space(8);
                if (_platform != Platform.Flat)
                {
                    Label("Hand layers", "Which layers count as a hand pressing the VR board's buttons.");
                    _handLayers = LayerMaskField(_handLayers);
                }
                if (_platform != Platform.VR)
                {
                    Label("Button corner", "Where the Link Discord button sits on screen.");
                    _corner = (Corner)Segmented((int)_corner, "Bottom left", "Bottom right", "Top left", "Top right");
                }
                GUILayout.Space(6);
                _staffButton = Switch("Staff Panel button", "Staff open the web Staff Panel from inside the game.", _staffButton);
            });

            Card("2", "Your EzAdmin", "Built in. Your game always talks to EzAdmin at this address.", () =>
            {
                GUILayout.Label(_address, _small);
            });

            Card("3", "Signing players in", _loginFoundIn != null
                    ? "Found your PlayFab login in " + _loginFoundIn + "."
                    : "No PlayFab login found in your scripts.", () =>
            {
                _hasOwnLogin = Segmented(_hasOwnLogin ? 0 : 1, "My game signs players in", "EzAdmin Sign-in") == 0;
                GUILayout.Space(6);
                if (_hasOwnLogin)
                {
                    GUILayout.Label("EzAdmin waits for your login and uses that player.", _small);
                    return;
                }
                GUILayout.Label("EzAdmin Sign-in picks the right login on each platform. Nothing secret is in it, so " +
                                "reading the game's code (dnSpy and the like) gives nobody a way in.", _small);
                GUILayout.Space(6);
                bool meta = MetaSdkFound();
                using (new EditorGUI.DisabledScope(!meta))
                {
                    _useMeta = Switch("Meta Quest: sign in with the Meta account",
                        meta ? "Checks the player owns the game, then signs in with their Meta user id: the same " +
                               "PlayFab account on every headset and every reinstall."
                             : "Import the Meta Platform SDK (Meta XR Platform SDK) to use this.", _useMeta && meta);
                }
                if (_useMeta && meta)
                {
                    GUILayout.Space(4);
                    _secureMeta = Switch("Secure sign-in through EzAdmin",
                        "EzAdmin checks each player with Meta, then signs them in. Nobody can sign in as someone " +
                        "else. Add your Meta app token on the dashboard under Connect games.", _secureMeta);
                    GUILayout.Space(4);
                    _metaPrefix = EditorGUILayout.TextField(new GUIContent("Account prefix",
                        "Put before the Meta user id to make the PlayFab account id."), _metaPrefix);
                    GUILayout.Label(_metaPrefixFound != null
                        ? "Found " + _metaPrefixFound + " in your login, so players keep the accounts they have."
                        : "Players already signed in with Meta? Use the prefix your old login used, so they keep " +
                          "their accounts. A new game can leave it as it is.", _small);
                }
                GUILayout.Space(4);
                GUILayout.Label("Steam, Xbox and PlayStation: one line in your platform code hands EzAdmin the ticket " +
                                "(EzAdminAuth.SteamTicket, XboxToken, PlayStationAuthCode). Phones and PC: a random id " +
                                "kept on the device.", _small);
                if (_loginFoundIn != null)
                {
                    GUILayout.Space(4);
                    GUILayout.Label("<color=#fbbf24>Turn off your old login (" + Path.GetFileName(_loginFoundIn) +
                                    ") so players are not signed in twice.</color>", _small);
                }
            });

            Card("4", "What you will use", "Your starter script gets a method for each one you pick.", () =>
            {
                Label("Role items go into", null);
                _items = (Items)Segmented((int)_items, "No items", "Legacy catalog", "Economy v2");
                GUILayout.Space(4);
                GUILayout.Label(ItemsHelp(), _small);
                GUILayout.Space(8);
                _mutes = Switch("Discord mutes block my chat", "A Discord timeout mutes them in game too.", _mutes);
                _antiCheat = Switch("Anti tamper", "Mod loaders, injected code, memory editing and modified " +
                                                  "copies. Switch it on in Labs on the dashboard too.", _antiCheat);
                _discordLink = Switch("Discord link screen", "Lets players link Discord. Anti tamper works without it.", _discordLink);
                _writeHooks = Switch("Write my starter script", "Assets/EzAdmin/MyGame/EzAdminHooks.cs. Never overwritten.", _writeHooks);
            });

            Card("5", "Loading screen", EzAdminLoadingSceneMaker.Exists
                    ? "You already have one. Pick a design to make it again, or No loading screen to keep it."
                    : "A first scene that signs the player in, loads their items, then opens your game.", () =>
            {
                _loading = Segmented(Mathf.Min(_loading, 1), "No loading screen", "Add the loading screen");
                GUILayout.Space(6);
                if (_loading == 0) return;
                GUILayout.Label("Your logo, name, a progress bar, steps and tips. It is added to its own scene as normal " +
                                "objects you can move and restyle. In VR it floats in front of the player.", _small);
                GUILayout.Space(6);
                if (string.IsNullOrEmpty(_loadName)) _loadName = PlayerSettings.productName;
                var sceneChoices = EzAdminScenes.All(EzAdminLoadingSceneMaker.ScenePath);
                string openNow = UnityEngine.SceneManagement.SceneManager.GetActiveScene().path;
                _loadNext = Mathf.Clamp(_loadNext, 0, sceneChoices.Count);
                _loadNext = EditorGUILayout.Popup("Open after loading", _loadNext,
                    EzAdminScenes.Labels(sceneChoices, "This scene" + (string.IsNullOrEmpty(openNow) ? "" : " (" +
                    Path.GetFileNameWithoutExtension(openNow) + ")")));
                _loadName = EditorGUILayout.TextField("Game name", _loadName);
                _loadLogo = (Sprite)EditorGUILayout.ObjectField("Logo", _loadLogo, typeof(Sprite), false);
                _loadBackground = (Sprite)EditorGUILayout.ObjectField("Background picture", _loadBackground, typeof(Sprite), false);
                _loadAccent = EditorGUILayout.ColorField("Colour", _loadAccent);
                GUILayout.Label("It goes first in Build Settings. Your game scene stays open.", _small);
            });

            GUILayout.Space(10);
            GUILayout.BeginHorizontal();
            GUILayout.Space(14);
            using (new EditorGUI.DisabledScope(string.IsNullOrWhiteSpace(_address)))
            {
                if (GUILayout.Button("Set it up", _primary))
                {
                    _report = Apply();
                }
            }
            GUILayout.Space(14);
            GUILayout.EndHorizontal();

            if (!string.IsNullOrEmpty(_report))
            {
                Results();
            }

            GUILayout.Space(8);
            GUILayout.BeginHorizontal();
            GUILayout.Space(14);
            using (new EditorGUI.DisabledScope(string.IsNullOrWhiteSpace(_address)))
            {
                if (GUILayout.Button("Setup guide", _secondary)) Application.OpenURL(SiteLink("/setup-unity.html"));
                if (GUILayout.Button("Developer reference", _secondary)) Application.OpenURL(SiteLink("/developers.html"));
            }
            if (GUILayout.Button("Check for updates", _secondary)) EditorApplication.ExecuteMenuItem("Tools/EzAdmin/Check for Updates");
            if (GUILayout.Button("Protect build", _secondary)) EzAdminBuildGuard.ProtectBuild();
            GUILayout.Space(14);
            GUILayout.EndHorizontal();
            GUILayout.Space(16);
            EditorGUILayout.EndScrollView();
        }

        private static string LoadNextPath(int index)
        {
            var choices = EzAdminScenes.All(EzAdminLoadingSceneMaker.ScenePath);
            return index > 0 && index <= choices.Count ? choices[index - 1].Path : "";
        }

        private string SiteLink(string path)
        {
            return _address.Trim().TrimEnd('/') + path;
        }

        private void Header()
        {
            Rect band = GUILayoutUtility.GetRect(position.width, 92);
            EditorGUI.DrawRect(band, EzAdminBrand.Panel);
            EditorGUI.DrawRect(new Rect(band.x, band.yMax - 3, band.width, 3), EzAdminBrand.Green);
            GUI.DrawTexture(new Rect(band.x + 18, band.y + 16, 58, 58), EzAdminBrand.Logo(128), ScaleMode.ScaleToFit);
            GUI.Label(new Rect(band.x + 90, band.y + 18, band.width - 200, 30), "EzAdmin Setup", _title);
            GUI.Label(new Rect(band.x + 91, band.y + 50, band.width - 110, 34),
                      "Answer four things. EzAdmin sets up your scene and writes your starter script.", _subtitle);
            GUI.Label(new Rect(band.xMax - 84, band.y + 22, 66, 22), "v" + EzAdminClient.Version, _chip);
            GUILayout.Space(10);
        }

        private void StatusStrip()
        {
            string titleId = PlayFabTitleId();
            GUILayout.BeginHorizontal();
            GUILayout.Space(14);
            Pill(titleId != null, "PlayFab SDK");
            Pill(!string.IsNullOrEmpty(titleId), string.IsNullOrEmpty(titleId) ? "No Title ID" : "Title ID set");
            Pill(!string.IsNullOrWhiteSpace(_address), "Address");
            Pill(true, _hasOwnLogin ? "Your login" : "EzAdmin login");
            GUILayout.FlexibleSpace();
            GUILayout.Space(14);
            GUILayout.EndHorizontal();
            if (titleId == null)
            {
                Notice("Import the PlayFab SDK first: github.com/PlayFab/UnitySDK/releases, PlayFabSDK.unitypackage.", Bad,
                       "Open", () => Application.OpenURL("https://github.com/PlayFab/UnitySDK/releases"));
            }
            else if (titleId.Length == 0)
            {
                Notice("Type your PlayFab Title ID into PlayFabSharedSettings.", Warn, "Show it", SelectPlayFabSettings);
            }
            GUILayout.Space(4);
        }

        private void Pill(bool ok, string text)
        {
            var content = new GUIContent((ok ? "<color=#4ade80>●</color>  " : "<color=#f87171>●</color>  ") + text);
            Vector2 size = _chip.CalcSize(content);
            Rect r = GUILayoutUtility.GetRect(size.x, 26, GUILayout.ExpandWidth(false));
            EditorGUI.DrawRect(r, new Color32(0x1C, 0x20, 0x28, 0xFF));
            GUI.Label(r, content, _chip);
            GUILayout.Space(6);
        }

        private void Notice(string text, Color colour, string button, Action onClick)
        {
            GUILayout.Space(6);
            GUILayout.BeginHorizontal(_cardStyle);
            Rect bar = GUILayoutUtility.GetRect(3, 18, GUILayout.Width(3));
            EditorGUI.DrawRect(bar, colour);
            GUILayout.Space(8);
            GUILayout.Label(text, _body);
            if (GUILayout.Button(button, _secondary, GUILayout.Width(90))) onClick();
            GUILayout.EndHorizontal();
        }

        private void Card(string number, string title, string subtitle, Action body)
        {
            Rect area = EditorGUILayout.BeginVertical(_cardStyle);
            if (Event.current.type == EventType.Repaint)
            {
                EditorGUI.DrawRect(new Rect(area.x, area.y, area.width, 1), CardLine);
            }
            GUILayout.BeginHorizontal();
            GUILayout.Label(number, _stepNumber);
            GUILayout.Space(8);
            GUILayout.Label(title, _cardTitle);
            GUILayout.EndHorizontal();
            if (!string.IsNullOrEmpty(subtitle))
            {
                GUILayout.Space(2);
                GUILayout.Label(subtitle, _small);
            }
            GUILayout.Space(10);
            body();
            EditorGUILayout.EndVertical();
        }

        private void Label(string text, string hint)
        {
            GUILayout.Space(4);
            GUILayout.Label("<b>" + text + "</b>", _body);
            if (!string.IsNullOrEmpty(hint)) GUILayout.Label(hint, _small);
        }

        private int Segmented(int value, params string[] options)
        {
            GUILayout.BeginHorizontal();
            for (int i = 0; i < options.Length; i++)
            {
                if (GUILayout.Button(options[i], i == value ? _segOn : _segOff)) value = i;
                if (i < options.Length - 1) GUILayout.Space(4);
            }
            GUILayout.EndHorizontal();
            return value;
        }

        private bool Switch(string label, string hint, bool value)
        {
            GUILayout.BeginHorizontal();
            GUILayout.BeginVertical();
            GUILayout.Label("<b>" + label + "</b>", _body);
            if (!string.IsNullOrEmpty(hint)) GUILayout.Label(hint, _small);
            GUILayout.EndVertical();
            GUILayout.FlexibleSpace();
            if (GUILayout.Button(value ? "On" : "Off", value ? _segOn : _segOff, GUILayout.Width(64))) value = !value;
            GUILayout.EndHorizontal();
            GUILayout.Space(6);
            return value;
        }

        private void Results()
        {
            GUILayout.Space(6);
            EditorGUILayout.BeginVertical(_cardStyle);
            GUILayout.BeginHorizontal();
            GUILayout.Label("√", _stepNumber);
            GUILayout.Space(8);
            GUILayout.Label("Set up", _cardTitle);
            GUILayout.EndHorizontal();
            GUILayout.Space(8);
            bool todo = false;
            foreach (string raw in _report.Split('\n'))
            {
                string line = raw.Trim();
                if (line.StartsWith("Still for you"))
                {
                    todo = true;
                    GUILayout.Space(6);
                    GUILayout.Label("<b>Still for you</b>", _body);
                    continue;
                }
                if (!line.StartsWith("·")) continue;
                string text = line.TrimStart('·', ' ');
                GUILayout.Label((todo ? "<color=#fbbf24>○</color>  " : "<color=#4ade80>√</color>  ") + text, _row);
            }
            EditorGUILayout.EndVertical();
        }

        private string ItemsHelp()
        {
            switch (_items)
            {
                case Items.CatalogLegacy: return "PlayFab Catalog (Legacy): items land in the player's inventory. To " +
                                                 "show one in game, add EzAdminItemUnlock to any object, type the item " +
                                                 "id or name, and drag the hat or sword into Show When Owned.";
                case Items.EconomyV2: return "PlayFab Economy v2: items land in the v2 inventory. To show one in game, " +
                                             "add EzAdminItemUnlock, type the item id or name, and drag the object in.";
                default: return "No items. Linking, mutes and moderation still work.";
            }
        }

        private static LayerMask LayerMaskField(LayerMask mask)
        {
            var names = UnityEditorInternal.InternalEditorUtility.layers;
            int shown = 0;
            for (int i = 0; i < names.Length; i++)
            {
                if ((mask.value & (1 << LayerMask.NameToLayer(names[i]))) != 0) shown |= 1 << i;
            }
            shown = EditorGUILayout.MaskField(shown, names, GUILayout.Height(22));
            int result = 0;
            for (int i = 0; i < names.Length; i++)
            {
                if ((shown & (1 << i)) != 0) result |= 1 << LayerMask.NameToLayer(names[i]);
            }
            return shown == -1 ? (LayerMask)~0 : (LayerMask)result;
        }

        private static string PlayFabTitleId()
        {
            var found = AssetDatabase.FindAssets("t:PlayFabSharedSettings");
            if (found.Length == 0) return null;
            var asset = AssetDatabase.LoadMainAssetAtPath(AssetDatabase.GUIDToAssetPath(found[0]));
            var property = asset != null ? new SerializedObject(asset).FindProperty("TitleId") : null;
            return property != null ? property.stringValue ?? "" : "";
        }

        private static void SelectPlayFabSettings()
        {
            var found = AssetDatabase.FindAssets("t:PlayFabSharedSettings");
            if (found.Length == 0) return;
            Selection.activeObject = AssetDatabase.LoadMainAssetAtPath(AssetDatabase.GUIDToAssetPath(found[0]));
            EditorGUIUtility.PingObject(Selection.activeObject);
        }

        private string Apply()
        {
            var done = new StringBuilder("Done:\n");
            var todo = new StringBuilder();

            var client = FindObjectOfType<EzAdminClient>();
            if (client == null)
            {
                var go = new GameObject("EzAdmin");
                Undo.RegisterCreatedObjectUndo(go, "EzAdmin setup");
                client = go.AddComponent<EzAdminClient>();
                done.AppendLine("  · added the EzAdmin object");
            }

            var sign = new SerializedObject(client);
            sign.FindProperty("_signInIfNeeded").boolValue = false;
            sign.ApplyModifiedProperties();
            var auth = client.GetComponent<EzAdminAuth>();
            if (_hasOwnLogin)
            {
                if (auth != null) Undo.DestroyObjectImmediate(auth);
                done.AppendLine("  · uses your own PlayFab login");
            }
            else
            {
                if (auth == null) auth = Undo.AddComponent<EzAdminAuth>(client.gameObject);
                bool meta = _useMeta && MetaSdkFound();
                if (meta)
                {
                    var authProps = new SerializedObject(auth);
                    authProps.FindProperty("_secureMeta").boolValue = _secureMeta;
                    authProps.FindProperty("_metaPrefix").stringValue = string.IsNullOrWhiteSpace(_metaPrefix)
                        ? EzAdminAuth.DefaultMetaPrefix : _metaPrefix.Trim();
                    authProps.ApplyModifiedProperties();
                }
                SetDefine(BuildTargetGroup.Android, MetaDefine, meta);
                SetDefine(BuildTargetGroup.Standalone, MetaDefine, meta);
                done.AppendLine("  · added EzAdmin Sign-in" + (meta ? " with Meta Quest accounts" : ""));
                if (meta && _secureMeta)
                    todo.AppendLine("  · paste your Meta app token on the EzAdmin dashboard › Connect games › Meta app");
                if (_loginFoundIn != null)
                    todo.AppendLine("  · turn off your old login in " + _loginFoundIn + " (or delete it)");
            }

            var screen = FindObjectOfType<EzAdminLinkScreen>();
            if (screen == null && _discordLink)
            {
                var go = new GameObject("EzAdmin Link Screen");
                Undo.RegisterCreatedObjectUndo(go, "EzAdmin setup");
                screen = go.AddComponent<EzAdminLinkScreen>();
                if (_platform != Platform.Flat) PlaceInFrontOfView(go.transform);
                done.AppendLine("  · added the link screen" + (_platform != Platform.Flat ? " (move the board to your lobby wall)" : ""));
            }
            if (screen != null)
            {
                var ss = new SerializedObject(screen);
                ss.FindProperty("_mode").enumValueIndex = _platform == Platform.VR ? 2 : _platform == Platform.Flat ? 1 : 0;
                ss.FindProperty("_handLayers").intValue = _handLayers.value;
                ss.FindProperty("_corner").enumValueIndex = (int)_corner;
                ss.FindProperty("_showStaffButton").boolValue = _staffButton;
                ss.ApplyModifiedProperties();
                done.AppendLine("  · Link Discord button set for " + ObjectNames.NicifyVariableName(_platform.ToString()));
            }

            var antiCheat = client.GetComponent<EzAdminAntiCheat>();
            if (_antiCheat && antiCheat == null)
            {
                Undo.AddComponent<EzAdminAntiCheat>(client.gameObject);
                done.AppendLine("  · added anti tamper");
                todo.AppendLine("  · switch on Anti tamper for Unity in Labs on the dashboard, then mark your build official");
            }
            else if (!_antiCheat && antiCheat != null)
            {
                Undo.DestroyObjectImmediate(antiCheat);
                done.AppendLine("  · removed anti tamper");
            }

            if (_writeHooks)
            {
                if (File.Exists(HooksPath))
                {
                    done.AppendLine("  · kept your existing " + HooksPath + " (it is never overwritten)");
                }
                else
                {
                    Directory.CreateDirectory(HooksFolder);
                    File.WriteAllText(HooksPath, HooksScript(), Encoding.UTF8);
                    EditorPrefs.SetBool(PendingHooksKey, true);
                    AssetDatabase.ImportAsset(HooksPath);
                    done.AppendLine("  · wrote " + HooksPath + " (added to the EzAdmin object once Unity compiles it)");
                }
            }

            EditorSceneManager.MarkSceneDirty(client.gameObject.scene);

            if (_loading > 0)
            {
                var gameScene = client.gameObject.scene;
                if (!string.IsNullOrEmpty(gameScene.path)) EditorSceneManager.SaveScene(gameScene);
                string made = EzAdminLoadingSceneMaker.Create(new LoadingSceneOptions
                {
                    VR = _platform != Platform.Flat,
                    SignIn = !_hasOwnLogin,
                    GameName = _loadName,
                    Logo = _loadLogo,
                    Background = _loadBackground,
                    Accent = _loadAccent,
                    NextScene = _loadNext > 0 ? LoadNextPath(_loadNext) : client.gameObject.scene.path,
                    ReturnToGame = true
                }, false);
                if (made.StartsWith("Made "))
                    done.AppendLine("  · made the loading screen and put it first in Build Settings");
                else if (!string.IsNullOrEmpty(made))
                    todo.AppendLine("  · loading screen: " + made);
                if (_hasOwnLogin && made.StartsWith("Made "))
                    todo.AppendLine("  · put your own sign-in script in the EzAdmin Loading scene too, so players sign " +
                                    "in while it shows");
            }

            todo.AppendLine("  · save the scene");
            todo.AppendLine("  · paste EzAdmin.CloudScript.js into PlayFab (Automation › CloudScript) and deploy");
            todo.AppendLine("  · in Discord: /setup with your PlayFab Secret Key, then /config linked-role");
            todo.AppendLine("  · want the website INSIDE the game? Add a web view plugin and set EzAdminWeb.Opener " +
                            "(see EzAdminWeb.cs)");
            return done.ToString().Replace("\n\n", "\n") + "\nStill for you:\n" + todo;
        }

        private static void PlaceInFrontOfView(Transform target)
        {
            var view = SceneView.lastActiveSceneView;
            if (view == null) return;
            var camera = view.camera.transform;
            target.position = camera.position + camera.forward * 2f;
            target.rotation = Quaternion.LookRotation(camera.forward, Vector3.up);
        }

        [UnityEditor.Callbacks.DidReloadScripts]
        private static void AddHooksAfterCompile()
        {
            if (!EditorPrefs.GetBool(PendingHooksKey, false)) return;
            EditorPrefs.DeleteKey(PendingHooksKey);
            Type hooks = null;
            foreach (var assembly in AppDomain.CurrentDomain.GetAssemblies())
            {
                hooks = assembly.GetType("EzAdminHooks");
                if (hooks != null) break;
            }
            var client = FindObjectOfType<EzAdminClient>();
            if (hooks == null || client == null || client.GetComponent(hooks) != null) return;
            Undo.AddComponent(client.gameObject, hooks);
            EditorSceneManager.MarkSceneDirty(client.gameObject.scene);
        }

        private string HooksScript()
        {
            var s = new StringBuilder();
            s.AppendLine("using EzAdmin.Client;");
            s.AppendLine("using PlayFab;");
            s.AppendLine(_items == Items.EconomyV2 ? "using PlayFab.EconomyModels;" : "using PlayFab.ClientModels;");
            s.AppendLine("using UnityEngine;");
            s.AppendLine();
            s.AppendLine("public class EzAdminHooks : MonoBehaviour");
            s.AppendLine("{");
            s.AppendLine("    private void Start()");
            s.AppendLine("    {");
            s.AppendLine("        EzAdminLink.WhenLinked(OnLinked);");
            s.AppendLine("        EzAdminLink.StatusChanged += message => Debug.Log(\"EzAdmin: \" + message);");
            s.AppendLine("    }");
            s.AppendLine();
            s.AppendLine("    private void OnLinked(string discordName)");
            s.AppendLine("    {");
            s.AppendLine("        Debug.Log(\"Linked to Discord as \" + discordName);");
            if (_items != Items.None) s.AppendLine("        LoadItems();");
            if (_mutes) s.AppendLine("        CheckMute();");
            s.AppendLine("    }");

            if (_items == Items.CatalogLegacy)
            {
                s.AppendLine();
                s.AppendLine("    public void LoadItems()");
                s.AppendLine("    {");
                s.AppendLine("        PlayFabClientAPI.GetUserInventory(new GetUserInventoryRequest(), result =>");
                s.AppendLine("        {");
                s.AppendLine("            foreach (ItemInstance item in result.Inventory)");
                s.AppendLine("            {");
                s.AppendLine("                Debug.Log(\"Has item: \" + item.ItemId);");
                s.AppendLine("            }");
                s.AppendLine("        }, error => Debug.LogWarning(error.GenerateErrorReport()));");
                s.AppendLine("    }");
            }
            else if (_items == Items.EconomyV2)
            {
                s.AppendLine();
                s.AppendLine("    public void LoadItems()");
                s.AppendLine("    {");
                s.AppendLine("        PlayFabEconomyAPI.GetInventoryItems(new GetInventoryItemsRequest { Count = 50 }, result =>");
                s.AppendLine("        {");
                s.AppendLine("            foreach (InventoryItem item in result.Items)");
                s.AppendLine("            {");
                s.AppendLine("                Debug.Log(\"Has item: \" + item.Id + \" x\" + item.Amount);");
                s.AppendLine("            }");
                s.AppendLine("        }, error => Debug.LogWarning(error.GenerateErrorReport()));");
                s.AppendLine("    }");
            }

            if (_mutes)
            {
                s.AppendLine();
                s.AppendLine("    public void CheckMute()");
                s.AppendLine("    {");
                s.AppendLine("        EzAdminClient.Instance.GetMuteStatus((muted, secondsLeft) =>");
                s.AppendLine("        {");
                s.AppendLine("            if (muted) Debug.Log(\"Muted for \" + secondsLeft + \"s\");");
                s.AppendLine("        });");
                s.AppendLine("    }");
            }

            s.AppendLine("}");
            return s.ToString();
        }
    }
}
