// Asset Naming Manager — theme discovery, and reading theme colours back out of USS.
using System.Collections.Generic;
using UnityEditor;
using UnityEngine;
using UnityEngine.UIElements;

namespace AssetNamingManager.UI
{
    /// <summary>One selectable theme: the built-in default, or an override stylesheet.</summary>
    public class NamingManagerThemeEntry
    {
        /// <summary>Name shown in the menu.</summary>
        public string displayName;

        /// <summary>Asset GUID of the override stylesheet. Empty for the built-in default.</summary>
        public string guid;

        /// <summary>The stylesheet to layer on top of the base. Null for the built-in default.</summary>
        public StyleSheet sheet;

        /// <summary>True when this entry is the built-in look with no override applied.</summary>
        public bool IsDefault
        {
            get { return string.IsNullOrEmpty(guid); }
        }
    }

    /// <summary>
    /// Finds theme stylesheets and layers the chosen one over the base sheet.
    /// </summary>
    /// <remarks>
    /// A theme overrides USS <em>tokens only</em>. Because stylesheets cascade, a theme file never
    /// repeats the layout rules — which is what lets a package update still reach someone who is
    /// running their own theme.
    /// </remarks>
    public static class NamingManagerThemes
    {
        /// <summary>Folder the shipped themes live in.</summary>
        public static string ThemeFolder
        {
            get { return NamingManagerPaths.EditorRoot + "/UI/Themes"; }
        }

        /// <summary>
        /// Every theme that can be picked: the built-in default first, then every stylesheet in the
        /// Themes folder, then the user's own if it lives somewhere else.
        /// </summary>
        public static List<NamingManagerThemeEntry> Discover()
        {
            var list = new List<NamingManagerThemeEntry>
            {
                new NamingManagerThemeEntry { displayName = "Unity Default", guid = string.Empty }
            };

            var seen = new HashSet<string>(System.StringComparer.Ordinal);

            if (AssetDatabase.IsValidFolder(ThemeFolder))
            {
                foreach (var guid in AssetDatabase.FindAssets("t:StyleSheet", new[] { ThemeFolder }))
                {
                    if (!seen.Add(guid)) continue;
                    AddEntry(list, guid);
                }
            }

            // A theme the user keeps outside the package survives a package update, so it must
            // still appear in the menu even though it is not in the Themes folder.
            string active = NamingManagerSettings.ActiveThemeGuid;
            if (!string.IsNullOrEmpty(active) && seen.Add(active)) AddEntry(list, active);

            return list;
        }

        static void AddEntry(List<NamingManagerThemeEntry> list, string guid)
        {
            string path = AssetDatabase.GUIDToAssetPath(guid);
            if (string.IsNullOrEmpty(path)) return;

            var sheet = AssetDatabase.LoadAssetAtPath<StyleSheet>(path);
            if (sheet == null) return;

            list.Add(new NamingManagerThemeEntry
            {
                displayName = System.IO.Path.GetFileNameWithoutExtension(path),
                guid = guid,
                sheet = sheet
            });
        }

        /// <summary>The theme currently selected, falling back to the default when it has gone missing.</summary>
        public static NamingManagerThemeEntry Active()
        {
            string guid = NamingManagerSettings.ActiveThemeGuid;
            if (string.IsNullOrEmpty(guid)) return new NamingManagerThemeEntry { displayName = "Unity Default" };

            foreach (var entry in Discover())
            {
                if (entry.guid == guid) return entry;
            }

            return new NamingManagerThemeEntry { displayName = "Unity Default" };
        }

        /// <summary>
        /// Rebuilds the stylesheet stack on <paramref name="root"/>: the base sheet, then the
        /// chosen override on top. Safe to call repeatedly.
        /// </summary>
        /// <remarks>
        /// It takes off our own sheets one by one and never clears the list. An EditorWindow's
        /// rootVisualElement arrives with Unity's own editor theme stylesheet already on it, and
        /// that sheet is what supplies <c>-unity-font-definition</c>. Clear it and every label in
        /// the window resolves a null font, measures NaN, and is laid out as a zero-sized box —
        /// while still being painted. The result is every line of text drawn on top of every
        /// other, which reads as the window "smearing" until something puts Unity's sheet back.
        /// <c>_StoreSubmission/Verification/Probe-Layout.ps1</c> measures this.
        /// </remarks>
        public static void Apply(VisualElement root)
        {
            if (root == null) return;

            var active = Active();

            // Ours are the sheets that live inside the package, plus whichever override is
            // selected — a theme the user keeps outside the package is still ours to remove.
            // Walk backwards: removing shifts everything after it down.
            for (int i = root.styleSheets.count - 1; i >= 0; i--)
            {
                var sheet = root.styleSheets[i];
                if (sheet == null) continue;

                if (sheet == active.sheet ||
                    AssetDatabase.GetAssetPath(sheet).StartsWith(NamingManagerPaths.PackageRoot,
                                                                 System.StringComparison.Ordinal))
                {
                    root.styleSheets.Remove(sheet);
                }
            }

            var baseSheet = AssetDatabase.LoadAssetAtPath<StyleSheet>(NamingManagerPaths.StyleSheetPath);
            if (baseSheet != null)
            {
                root.styleSheets.Add(baseSheet);
            }
            else
            {
                Debug.LogWarning(NamingManagerAbout.LogPrefix + "Stylesheet not found at " +
                                 NamingManagerPaths.StyleSheetPath + ". The window will look unstyled.");
            }

            if (active.sheet != null) root.styleSheets.Add(active.sheet);
        }
    }

    /// <summary>
    /// The theme colours, read back out of USS so anything drawn in C# follows the active theme.
    /// </summary>
    /// <remarks>
    /// USS custom properties cannot be read directly from C#, but a <see cref="VisualElement"/> can
    /// resolve the ones it inherits through <see cref="CustomStyleResolvedEvent"/>. That is how the
    /// Painter2D gauge and donut stay in step with a theme they know nothing about. Every value
    /// falls back to <see cref="NamingManagerTheme"/> when a theme leaves it out.
    /// </remarks>
    public class NamingManagerPalette
    {
        static readonly CustomStyleProperty<Color> ErrorProp   = new CustomStyleProperty<Color>("--anm-error");
        static readonly CustomStyleProperty<Color> WarningProp = new CustomStyleProperty<Color>("--anm-warning");
        static readonly CustomStyleProperty<Color> InfoProp    = new CustomStyleProperty<Color>("--anm-info");
        static readonly CustomStyleProperty<Color> GoodProp    = new CustomStyleProperty<Color>("--anm-good");
        static readonly CustomStyleProperty<Color> AccentProp  = new CustomStyleProperty<Color>("--anm-accent");
        static readonly CustomStyleProperty<Color> TrackProp   = new CustomStyleProperty<Color>("--anm-track");

        /// <summary>Error fill.</summary>
        public Color error = NamingManagerTheme.Error;

        /// <summary>Warning fill.</summary>
        public Color warning = NamingManagerTheme.Warning;

        /// <summary>Info fill.</summary>
        public Color info = NamingManagerTheme.Info;

        /// <summary>Good fill.</summary>
        public Color good = NamingManagerTheme.Good;

        /// <summary>Accent fill.</summary>
        public Color accent = NamingManagerTheme.Accent;

        /// <summary>Track colour behind a gauge or bar.</summary>
        public Color track = NamingManagerTheme.Track;

        /// <summary>Pulls whatever the active stylesheet defines, leaving the rest at their defaults.</summary>
        public void ResolveFrom(ICustomStyle style)
        {
            Color value;
            if (style.TryGetValue(ErrorProp, out value))   error = value;
            if (style.TryGetValue(WarningProp, out value)) warning = value;
            if (style.TryGetValue(InfoProp, out value))    info = value;
            if (style.TryGetValue(GoodProp, out value))    good = value;
            if (style.TryGetValue(AccentProp, out value))  accent = value;
            if (style.TryGetValue(TrackProp, out value))   track = value;
        }

        /// <summary>The fill for a severity, from the active theme.</summary>
        public Color For(NamingSeverity severity)
        {
            switch (severity)
            {
                case NamingSeverity.Error:   return error;
                case NamingSeverity.Warning: return warning;
                default:                     return info;
            }
        }

        /// <summary>
        /// Colour for a health score, matching <see cref="ScanReport.HealthLabel"/>'s thresholds.
        /// </summary>
        public Color ForScore(float score)
        {
            if (score >= 95f) return good;
            if (score >= 70f) return warning;
            return error;
        }
    }
}
