using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using UnityEditor;
using UnityEngine;
using UnityEngine.Networking;

namespace Furlumin.SDK.Editor
{
    public class FurluminSDKWindow : EditorWindow
    {
        private const string SdkVersion = "v1.0.4";

        private enum LoginState { LoggedOut, WaitingForBrowser, LoggedIn }

        private LoginState _loginState = LoginState.LoggedOut;
        private string     _loginError = "";
        private CancellationTokenSource _oauthCts;

        private SdkUser     _user;
        private AssetInfo[] _assets    = new AssetInfo[0];
        private string      _loadError = "";
        private bool        _loading   = false;
        private DateTime    _lastRefresh = DateTime.MinValue;

        private int     _topTab        = 0;
        private int     _selectedGroup = 0;
        private Vector2 _scroll;
        private Vector2 _settingsScroll;
        private Vector2 _toolsScroll;

        private Texture2D _logo;

        private enum UpdateStatus { None, Checking, UpToDate, UpdateAvailable, Error }
        private UpdateStatus _updateStatus      = UpdateStatus.None;
        private string       _latestVersion     = null;
        private string       _updateError       = null;
        private string       _updateDownloadUrl = null;

        private readonly Dictionary<string, Texture2D> _thumbCache   = new Dictionary<string, Texture2D>();
        private readonly HashSet<string>               _thumbLoading = new HashSet<string>();

        private static readonly string[] GroupKeys   = { "member", "vip", "supporter", "team" };
        private static readonly string[] GroupLabels = { "Member", "VIP", "Supporter", "Staff" };
        private static readonly string[] GroupDescs  =
        {
            "Assets available for Members",
            "Exclusive assets for VIP members",
            "Assets for Furlumin Supporters",
            "Internal assets for Staff members",
        };
        private static readonly Color[] GroupColors =
        {
            new Color(0.24f, 0.72f, 1.00f),  
            new Color(0.75f, 0.49f, 1.00f),  
            new Color(1.00f, 0.75f, 0.18f),  
            new Color(0.24f, 1.00f, 0.62f),  
        };

        [MenuItem("Furlumin/Asset Manager")]
        public static void ShowWindow()
        {
            var win = GetWindow<FurluminSDKWindow>("Furlumin SDK");
            win.minSize = new Vector2(460, 520);
        }

        private void OnEnable()
        {
            _logo = LoadLogo();
            _user = FurluminAuthManager.GetUser();
            if (FurluminAuthManager.IsLoggedIn())
            {
                _loginState = LoginState.LoggedIn;
                _ = RefreshAssets();
            }
        }

        private void OnDisable() => _oauthCts?.Cancel();

        private void OnGUI()
        {
            DrawLogoHeader();
            switch (_loginState)
            {
                case LoginState.LoggedOut:         DrawLoginPage();   break;
                case LoginState.WaitingForBrowser: DrawWaitingPage(); break;
                case LoginState.LoggedIn:          DrawLoggedIn();    break;
            }
            DrawWindowFooter();
        }

        private void DrawLogoHeader()
        {
            EditorGUILayout.Space(10);
            if (_logo != null)
            {
                float aspect = (float)_logo.width / Mathf.Max(1, _logo.height);
                float h = 44f;
                float w = Mathf.Min(h * aspect, position.width - 40f);
                using (new EditorGUILayout.HorizontalScope())
                {
                    GUILayout.FlexibleSpace();
                    GUILayout.Label(_logo, GUILayout.Width(w), GUILayout.Height(h));
                    GUILayout.FlexibleSpace();
                }
            }
            else
            {
                var s = new GUIStyle(EditorStyles.boldLabel) { fontSize = 18, alignment = TextAnchor.MiddleCenter };
                EditorGUILayout.LabelField("FURLUMIN", s, GUILayout.Height(40));
            }
            EditorGUILayout.Space(8);
            DrawHLine();
        }

        private void DrawWindowFooter()
        {
            var style = new GUIStyle(EditorStyles.miniLabel)
            {
                normal = { textColor = new Color(0.35f, 0.35f, 0.35f) }
            };
            GUI.Label(new Rect(8, position.height - 18, 130, 16),
                "Furlumin SDK " + SdkVersion, style);

            var rightStyle = new GUIStyle(style) { alignment = TextAnchor.MiddleRight };
            GUI.Label(new Rect(position.width - 90, position.height - 18, 82, 16),
                "furlumin.de", rightStyle);
        }

        private void DrawLoginPage()
        {
            GUILayout.FlexibleSpace();
            GUILayout.BeginHorizontal(); GUILayout.Space(28); GUILayout.BeginVertical();

            var title = new GUIStyle(EditorStyles.boldLabel) { fontSize = 14, alignment = TextAnchor.MiddleCenter };
            EditorGUILayout.LabelField("Furlumin Asset Manager", title);
            EditorGUILayout.Space(6);

            var sub = new GUIStyle(EditorStyles.wordWrappedMiniLabel) { alignment = TextAnchor.MiddleCenter };
            EditorGUILayout.LabelField("Sign in with Discord to access your available assets.", sub);
            EditorGUILayout.Space(20);

            if (!string.IsNullOrEmpty(_loginError))
            {
                EditorGUILayout.HelpBox(_loginError, MessageType.Error);
                EditorGUILayout.Space(8);
            }

            var btn = new GUIStyle(GUI.skin.button) { fontSize = 13, fontStyle = FontStyle.Bold, fixedHeight = 42 };
            if (GUILayout.Button("Sign in with Discord", btn))
                _ = StartBrowserLogin();

            EditorGUILayout.Space(10);
            EditorGUILayout.LabelField("A browser window will open.\nSign in and click 'Allow Access'.", sub);

            GUILayout.EndVertical(); GUILayout.Space(28); GUILayout.EndHorizontal();
            GUILayout.FlexibleSpace();
        }

        private void DrawWaitingPage()
        {
            GUILayout.FlexibleSpace();
            GUILayout.BeginHorizontal(); GUILayout.Space(28); GUILayout.BeginVertical();

            var bold = new GUIStyle(EditorStyles.boldLabel) { alignment = TextAnchor.MiddleCenter };
            EditorGUILayout.LabelField("Waiting for browser...", bold);
            EditorGUILayout.Space(8);

            var sub = new GUIStyle(EditorStyles.wordWrappedMiniLabel) { alignment = TextAnchor.MiddleCenter };
            EditorGUILayout.LabelField(
                "Sign in via browser and click 'Allow Access'.\nThe editor will update automatically.", sub);
            EditorGUILayout.Space(20);

            if (GUILayout.Button("Cancel", GUILayout.Height(34)))
            {
                _oauthCts?.Cancel();
                _loginState = LoginState.LoggedOut;
                _loginError = "";
                Repaint();
            }

            GUILayout.EndVertical(); GUILayout.Space(28); GUILayout.EndHorizontal();
            GUILayout.FlexibleSpace();
            EditorApplication.delayCall += Repaint;
        }

        private void DrawLoggedIn()
        {
            int newTop = GUILayout.Toolbar(_topTab, new[] { "Assets", "Tools", "Settings" });
            if (newTop != _topTab) { _topTab = newTop; Repaint(); }
            EditorGUILayout.Space(4);
            if (_topTab == 2) { DrawSettingsTab(); return; }
            if (_topTab == 1) { DrawToolsTab();    return; }
            DrawAssetsTab();
        }

        private int[] GetAccessibleGroupIndices()
        {
            string[] userGroups = _user?.groups ?? new string[0];
            var result = new System.Collections.Generic.List<int>();
            for (int i = 0; i < GroupKeys.Length; i++)
            {
                if (userGroups.Any(g => string.Equals(g, GroupKeys[i], StringComparison.OrdinalIgnoreCase)))
                    result.Add(i);
            }
            return result.ToArray();
        }

        private void DrawAssetsTab()
        {
            int[] accessible = GetAccessibleGroupIndices();
            if (accessible.Length == 0)
            {
                EditorGUILayout.Space(20);
                var c = new GUIStyle(EditorStyles.wordWrappedMiniLabel) { alignment = TextAnchor.MiddleCenter };
                EditorGUILayout.LabelField("You do not have access to any asset groups.", c);
                return;
            }

            if (_selectedGroup >= accessible.Length) _selectedGroup = 0;

            string[] tabLabels = accessible.Select(i =>
            {
                int count = _assets.Count(a =>
                    string.Equals(a.group, GroupKeys[i], StringComparison.OrdinalIgnoreCase));
                return count > 0 ? GroupLabels[i] + " (" + count + ")" : GroupLabels[i];
            }).ToArray();

            int newGroup = GUILayout.Toolbar(_selectedGroup, tabLabels);
            if (newGroup != _selectedGroup)
            {
                _selectedGroup = newGroup;
                _scroll        = Vector2.zero;
                Repaint();
            }

            EditorGUILayout.Space(4);

            using (new EditorGUILayout.HorizontalScope())
            {
                GUILayout.FlexibleSpace();
                using (new EditorGUI.DisabledScope(_loading))
                {
                    if (GUILayout.Button(_loading ? "Loading..." : "↺ Refresh", GUILayout.Width(90)))
                        _ = RefreshAssets();
                }
            }

            EditorGUILayout.Space(2);

            if (!string.IsNullOrEmpty(_loadError))
            {
                EditorGUILayout.HelpBox(_loadError, MessageType.Error);
                return;
            }

            string key = GroupKeys[accessible[_selectedGroup]];
            AssetInfo[] visible = _assets
                .Where(a => string.Equals(a.group, key, StringComparison.OrdinalIgnoreCase))
                .ToArray();

            if (visible.Length == 0)
            {
                EditorGUILayout.Space(20);
                var center = new GUIStyle(EditorStyles.wordWrappedMiniLabel) { alignment = TextAnchor.MiddleCenter };
                EditorGUILayout.LabelField("No assets available in this group.", center);
                return;
            }

            _scroll = EditorGUILayout.BeginScrollView(_scroll);
            foreach (var asset in visible)
                DrawAssetCard(asset);
            EditorGUILayout.Space(20);
            EditorGUILayout.EndScrollView();
        }

        private string[] BuildGroupTabLabels()
        {
            var labels = new string[GroupKeys.Length];
            for (int i = 0; i < GroupKeys.Length; i++)
            {
                int count = _assets.Count(a =>
                    string.Equals(a.group, GroupKeys[i], StringComparison.OrdinalIgnoreCase));
                labels[i] = count > 0 ? GroupLabels[i] + " (" + count + ")" : GroupLabels[i];
            }
            return labels;
        }

        private void DrawAssetCard(AssetInfo asset)
        {
            const float ThumbW = 130f;
            const float ThumbH = 90f;

            EditorGUILayout.Space(4);
            using (new EditorGUILayout.VerticalScope("box"))
            {
                EditorGUILayout.Space(6);

                using (new EditorGUILayout.HorizontalScope())
                {
                    EditorGUILayout.Space(6);

                    using (new EditorGUILayout.VerticalScope())
                    {
                        var nameStyle = new GUIStyle(EditorStyles.boldLabel) { fontSize = 11, wordWrap = true };
                        EditorGUILayout.LabelField(asset.name, nameStyle);
                        EditorGUILayout.Space(2);

                        if (!string.IsNullOrEmpty(asset.description))
                        {
                            var descStyle = new GUIStyle(EditorStyles.wordWrappedMiniLabel)
                            {
                                normal = { textColor = new Color(0.70f, 0.70f, 0.70f) }
                            };
                            EditorGUILayout.LabelField(asset.description, descStyle);
                        }

                        using (new EditorGUILayout.HorizontalScope())
                        {
                            var badge = new GUIStyle(EditorStyles.miniLabel)
                            {
                                normal = { textColor = new Color(0.50f, 0.50f, 0.50f) }
                            };
                            EditorGUILayout.LabelField("v" + (asset.version ?? "1.0.0"), badge, GUILayout.Width(52));
                            if (asset.sizeBytes > 0)
                                EditorGUILayout.LabelField(FormatSize(asset.sizeBytes), badge, GUILayout.Width(64));
                        }
                    }

                    GUILayout.FlexibleSpace();

                    if (!string.IsNullOrEmpty(asset.thumbnailUrl))
                    {
                        Texture2D thumb = GetOrLoadThumb(asset);
                        if (thumb != null)
                        {
                            GUILayout.Label(thumb, GUILayout.Width(ThumbW), GUILayout.Height(ThumbH));
                        }
                        else
                        {
                            var ph = new GUIStyle("box")
                            {
                                alignment = TextAnchor.MiddleCenter,
                                normal    = { textColor = new Color(0.40f, 0.40f, 0.40f) }
                            };
                            GUILayout.Label("...", ph, GUILayout.Width(ThumbW), GUILayout.Height(ThumbH));
                        }
                        EditorGUILayout.Space(6);
                    }
                }

                EditorGUILayout.Space(6);
                DrawHLine();

                if (GUILayout.Button("Download"))
                    _ = FurluminAssetDownloader.DownloadAndImport(asset);

                EditorGUILayout.Space(2);
            }
        }

        private Texture2D GetOrLoadThumb(AssetInfo asset)
        {
            if (_thumbCache.TryGetValue(asset.id, out var cached)) return cached;
            if (!_thumbLoading.Contains(asset.id))
            {
                _thumbLoading.Add(asset.id);
                _ = LoadThumbAsync(asset);
            }
            return null;
        }

        private async Task LoadThumbAsync(AssetInfo asset)
        {
            try
            {
                using var req = UnityWebRequestTexture.GetTexture(asset.thumbnailUrl);
                req.timeout = 10;
                var tcs = new TaskCompletionSource<bool>();
                req.SendWebRequest().completed += _ => tcs.TrySetResult(true);
                await tcs.Task;
                if (req.result == UnityWebRequest.Result.Success)
                {
                    _thumbCache[asset.id] = DownloadHandlerTexture.GetContent(req);
                    Repaint();
                }
            }
            catch { }
            finally { _thumbLoading.Remove(asset.id); }
        }

        // ── Tools Tab ────────────────────────────────────────────────────────────
        private void DrawToolsTab()
        {
            EditorGUILayout.Space(4);

            using (new EditorGUILayout.HorizontalScope())
            {
                GUILayout.FlexibleSpace();
                using (new EditorGUI.DisabledScope(_loading))
                {
                    if (GUILayout.Button(_loading ? "Loading..." : "↺ Refresh", GUILayout.Width(90)))
                        _ = RefreshAssets();
                }
            }

            EditorGUILayout.Space(2);

            if (!string.IsNullOrEmpty(_loadError))
            {
                EditorGUILayout.HelpBox(_loadError, MessageType.Error);
                return;
            }

            AssetInfo[] tools = _assets
                .Where(a => string.Equals(a.group, "tools", StringComparison.OrdinalIgnoreCase))
                .ToArray();

            if (tools.Length == 0)
            {
                EditorGUILayout.Space(20);
                var center = new GUIStyle(EditorStyles.wordWrappedMiniLabel) { alignment = TextAnchor.MiddleCenter };
                EditorGUILayout.LabelField("No tools available.", center);
                return;
            }

            _toolsScroll = EditorGUILayout.BeginScrollView(_toolsScroll);
            foreach (var asset in tools)
                DrawAssetCard(asset);
            EditorGUILayout.Space(20);
            EditorGUILayout.EndScrollView();
        }

        // ── Settings Tab ──────────────────────────────────────────────────────
        private void DrawSettingsTab()
        {
            _settingsScroll = EditorGUILayout.BeginScrollView(_settingsScroll);
            EditorGUILayout.Space(10);

            DrawSectionHeader("ACCOUNT");
            using (new EditorGUILayout.VerticalScope("box"))
            {
                EditorGUILayout.Space(4);

                string displayName = _user?.displayName ?? _user?.username ?? "–";
                var nameStyle = new GUIStyle(EditorStyles.boldLabel) { fontSize = 13 };
                EditorGUILayout.LabelField(displayName, nameStyle);

                if (!string.IsNullOrEmpty(_user?.username))
                {
                    var handleStyle = new GUIStyle(EditorStyles.miniLabel)
                        { normal = { textColor = new Color(0.48f, 0.48f, 0.48f) } };
                    EditorGUILayout.LabelField("@" + _user.username, handleStyle);
                }

                EditorGUILayout.Space(4);
                Rect divider = EditorGUILayout.GetControlRect(false, 1f);
                EditorGUI.DrawRect(divider, new Color(0.25f, 0.25f, 0.25f));
                EditorGUILayout.Space(4);

                DrawInfoRow("Discord ID", _user?.userId ?? "–");
                EditorGUILayout.Space(2);
            }

            EditorGUILayout.Space(12);

            DrawSectionHeader("ROLES & ACCESS");
            string[] userGroups = _user?.groups ?? new string[0];
            using (new EditorGUILayout.VerticalScope("box"))
            {
                for (int i = 0; i < GroupKeys.Length; i++)
                {
                    bool has = userGroups.Any(g =>
                        string.Equals(g, GroupKeys[i], StringComparison.OrdinalIgnoreCase));
                    DrawRoleRow(i, has);
                    if (i < GroupKeys.Length - 1)
                    {
                        Rect div = EditorGUILayout.GetControlRect(false, 1f);
                        EditorGUI.DrawRect(div, new Color(0.22f, 0.22f, 0.22f));
                    }
                }
            }

            EditorGUILayout.Space(12);

            DrawSectionHeader("SDK INFO");
            using (new EditorGUILayout.VerticalScope("box"))
            {
                DrawInfoRow("Version",  "Furlumin SDK " + SdkVersion);
                DrawInfoRow("API",      "api.furlumin.de");
                DrawInfoRow("Website",  "furlumin.de");
                DrawInfoRow("Assets",   _assets.Length + " loaded");
                DrawInfoRow("Last Sync", _lastRefresh == DateTime.MinValue ? "–"
                    : _lastRefresh.ToLocalTime().ToString("HH:mm:ss"));
            }

            EditorGUILayout.Space(12);

            DrawSectionHeader("UPDATES");
            using (new EditorGUILayout.VerticalScope("box"))
            {
                EditorGUILayout.Space(4);

                DrawInfoRow("Installed", SdkVersion);

                if (_updateStatus == UpdateStatus.UpToDate || _updateStatus == UpdateStatus.UpdateAvailable)
                    DrawInfoRow("Latest", _latestVersion ?? "–");

                EditorGUILayout.Space(4);

                if (_updateStatus == UpdateStatus.UpdateAvailable)
                {
                    var msgStyle = new GUIStyle(EditorStyles.miniLabel)
                    {
                        wordWrap = true,
                        normal   = { textColor = new Color(0.40f, 1.00f, 0.55f) }
                    };
                    EditorGUILayout.LabelField($"Version {_latestVersion} is available!", msgStyle);
                    EditorGUILayout.Space(2);
                }
                else if (_updateStatus == UpdateStatus.UpToDate)
                {
                    var msgStyle = new GUIStyle(EditorStyles.miniLabel)
                        { normal = { textColor = new Color(0.48f, 0.48f, 0.48f) } };
                    EditorGUILayout.LabelField("You're up to date.", msgStyle);
                    EditorGUILayout.Space(2);
                }
                else if (_updateStatus == UpdateStatus.Error)
                {
                    var msgStyle = new GUIStyle(EditorStyles.miniLabel)
                    {
                        wordWrap = true,
                        normal   = { textColor = new Color(1.00f, 0.45f, 0.45f) }
                    };
                    EditorGUILayout.LabelField("Error: " + _updateError, msgStyle);
                    EditorGUILayout.Space(2);
                }

                using (new EditorGUILayout.HorizontalScope())
                {
                    GUILayout.FlexibleSpace();

                    if (_updateStatus == UpdateStatus.UpdateAvailable && !string.IsNullOrEmpty(_updateDownloadUrl))
                    {
                        if (GUILayout.Button("Download Update", GUILayout.Width(120)))
                            Application.OpenURL(_updateDownloadUrl);
                        GUILayout.Space(4);
                    }

                    using (new EditorGUI.DisabledScope(_updateStatus == UpdateStatus.Checking))
                    {
                        string btnLabel = _updateStatus == UpdateStatus.Checking ? "Checking..." : "Check for Updates";
                        if (GUILayout.Button(btnLabel, GUILayout.Width(130)))
                            _ = CheckForUpdatesFromSettings();
                    }
                    GUILayout.Space(4);
                }

                EditorGUILayout.Space(2);
            }

            EditorGUILayout.Space(16);
            DrawHLine();
            EditorGUILayout.Space(8);

            var logoutStyle = new GUIStyle(GUI.skin.button)
            {
                fixedHeight = 36,
                fontStyle   = FontStyle.Bold,
                normal      = { textColor = new Color(1f, 0.38f, 0.38f) },
                hover       = { textColor = new Color(1f, 0.55f, 0.55f) }
            };
            if (GUILayout.Button("Sign Out", logoutStyle))
            {
                FurluminAuthManager.Logout();
                _user          = null;
                _assets        = new AssetInfo[0];
                _loginState    = LoginState.LoggedOut;
                _loginError    = "";
                _topTab        = 0;
                _selectedGroup = 0;
                _lastRefresh   = DateTime.MinValue;
                _thumbCache.Clear();
                _thumbLoading.Clear();
                Repaint();
            }

            GUILayout.FlexibleSpace();
            EditorGUILayout.EndScrollView();
        }

        private static void DrawSectionHeader(string title)
        {
            using (new EditorGUILayout.HorizontalScope())
            {
                var style = new GUIStyle(EditorStyles.miniLabel)
                {
                    fontStyle = FontStyle.Bold,
                    normal    = { textColor = new Color(0.50f, 0.50f, 0.50f) }
                };
                GUILayout.Label(title, style, GUILayout.ExpandWidth(false));
                GUILayout.Space(6);
                Rect line = GUILayoutUtility.GetRect(0, 1, GUILayout.ExpandWidth(true), GUILayout.Height(1));
                line.y += 7;
                EditorGUI.DrawRect(line, new Color(0.25f, 0.25f, 0.25f));
            }
            EditorGUILayout.Space(2);
        }

        private static void DrawInfoRow(string label, string value)
        {
            using (new EditorGUILayout.HorizontalScope())
            {
                var lblStyle = new GUIStyle(EditorStyles.miniLabel)
                    { normal = { textColor = new Color(0.48f, 0.48f, 0.48f) } };
                var valStyle = new GUIStyle(EditorStyles.miniLabel)
                {
                    fontStyle = FontStyle.Bold,
                    normal    = { textColor = new Color(0.88f, 0.88f, 0.88f) }
                };
                EditorGUILayout.LabelField(label, lblStyle, GUILayout.Width(90));
                EditorGUILayout.LabelField(value, valStyle);
            }
        }

        private static void DrawRoleRow(int idx, bool hasRole)
        {
            Color accent = hasRole ? GroupColors[idx] : new Color(0.32f, 0.32f, 0.32f);

            Rect row = EditorGUILayout.BeginHorizontal(GUILayout.Height(26));
            EditorGUILayout.Space(2);

            EditorGUI.DrawRect(new Rect(row.x + 2, row.y + 3, 3, row.height - 6), accent);
            GUILayout.Space(10);

            var nameStyle = new GUIStyle(EditorStyles.label)
            {
                fontStyle = hasRole ? FontStyle.Bold : FontStyle.Normal,
                normal    = { textColor = hasRole ? new Color(0.92f, 0.92f, 0.92f) : new Color(0.38f, 0.38f, 0.38f) }
            };
            GUILayout.Label(GroupLabels[idx], nameStyle, GUILayout.Width(72));

            var descStyle = new GUIStyle(EditorStyles.miniLabel)
                { normal = { textColor = hasRole ? new Color(0.55f, 0.55f, 0.55f) : new Color(0.32f, 0.32f, 0.32f) } };
            GUILayout.Label(hasRole ? GroupDescs[idx] : "No Access", descStyle);

            GUILayout.FlexibleSpace();

            if (hasRole)
            {
                var badge = new GUIStyle(EditorStyles.miniLabel)
                {
                    fontStyle = FontStyle.Bold,
                    normal    = { textColor = accent }
                };
                GUILayout.Label("✓", badge, GUILayout.Width(14));
                GUILayout.Space(6);
            }

            EditorGUILayout.EndHorizontal();
        }

        private async Task CheckForUpdatesFromSettings()
        {
            _updateStatus      = UpdateStatus.Checking;
            _latestVersion     = null;
            _updateError       = null;
            _updateDownloadUrl = null;
            Repaint();

            try
            {
                using var req = UnityWebRequest.Get("https://api.furlumin.de/api/sdk/manifest");
                req.timeout = 10;
                var tcs = new TaskCompletionSource<bool>();
                req.SendWebRequest().completed += _ => tcs.TrySetResult(true);
                await tcs.Task;

                if (req.result != UnityWebRequest.Result.Success)
                {
                    _updateStatus = UpdateStatus.Error;
                    _updateError  = req.error;
                }
                else
                {
                    var manifest = JsonUtility.FromJson<SettingsManifest>(req.downloadHandler.text);
                    if (manifest?.packages != null)
                    {
                        var pkg = manifest.packages.FirstOrDefault(p => p.name == "com.furlumin.sdk");
                        if (pkg != null)
                        {
                            _latestVersion     = pkg.version;
                            _updateDownloadUrl = pkg.downloadUrl;
                            string current = SdkVersion.TrimStart('v');
                            bool isNewer;
                            try   { isNewer = new Version(pkg.version) > new Version(current); }
                            catch { isNewer = string.Compare(pkg.version, current, StringComparison.Ordinal) > 0; }
                            _updateStatus = isNewer ? UpdateStatus.UpdateAvailable : UpdateStatus.UpToDate;
                        }
                        else
                        {
                            _updateStatus = UpdateStatus.Error;
                            _updateError  = "Package not found in manifest.";
                        }
                    }
                    else
                    {
                        _updateStatus = UpdateStatus.Error;
                        _updateError  = "Invalid manifest response.";
                    }
                }
            }
            catch (Exception ex)
            {
                _updateStatus = UpdateStatus.Error;
                _updateError  = ex.Message;
            }

            Repaint();
        }

        [Serializable] private class SettingsManifest { public SettingsPkgInfo[] packages; }
        [Serializable] private class SettingsPkgInfo  { public string name; public string version; public string downloadUrl; }

        private async Task StartBrowserLogin()
        {
            _loginState = LoginState.WaitingForBrowser;
            _loginError = "";
            _oauthCts   = new CancellationTokenSource();
            Repaint();

            var result = await FurluminAuthManager.StartBrowserOAuthFlow(
                onListening: port => Debug.Log("[Furlumin SDK] Listener on port " + port),
                cancelToken: _oauthCts.Token);

            _oauthCts = null;

            if (!result.success)
            {
                _loginState = LoginState.LoggedOut;
                _loginError = result.error ?? "Login failed.";
                Repaint();
                return;
            }

            _user       = FurluminAuthManager.GetUser();
            _loginState = LoginState.LoggedIn;
            await RefreshAssets();
            Repaint();
        }

        private async Task RefreshAssets()
        {
            _loading   = true;
            _loadError = "";
            Repaint();

            var r = await FurluminAPIClient.GetAssetsAsync();

            if (!r.success)
            {
                _loadError = r.statusCode == 401
                    ? "Session expired - please sign in again."
                    : $"Failed to load assets (HTTP {r.statusCode}).";

                if (r.statusCode == 401)
                {
                    FurluminAuthManager.Logout();
                    _loginState = LoginState.LoggedOut;
                }
                _loading = false;
                Repaint();
                return;
            }

            _assets      = r.assets ?? new AssetInfo[0];
            _loading     = false;
            _lastRefresh = DateTime.UtcNow;
            Repaint();
        }

        private static Texture2D LoadLogo()
        {
            string[] guids = AssetDatabase.FindAssets("furlumin_logo t:Texture2D");
            if (guids.Length == 0) return null;
            return AssetDatabase.LoadAssetAtPath<Texture2D>(AssetDatabase.GUIDToAssetPath(guids[0]));
        }

        private static void DrawHLine()
        {
            Rect r = EditorGUILayout.GetControlRect(false, 1f);
            EditorGUI.DrawRect(r, new Color(0.25f, 0.25f, 0.25f, 1f));
        }

        private static string FormatSize(long bytes)
        {
            if (bytes <= 0) return "-";
            string[] u = { "B", "KB", "MB", "GB" };
            double s = bytes;
            int i = 0;
            while (s >= 1024 && i < u.Length - 1) { s /= 1024; i++; }
            return s.ToString("0.#") + " " + u[i];
        }
    }
}
