// Asset Naming Manager — the main window shell: header, tabs, status bar, scanning and fixing.
using System.Collections.Generic;
using UnityEditor;
using UnityEditor.UIElements;
using UnityEngine;
using UnityEngine.UIElements;

namespace AssetNamingManager.UI
{
    /// <summary>
    /// The window everything happens in. Four tabs: Report, Dashboard, Rules, History.
    /// This file owns the shell and the shared state; each tab lives in its own partial file.
    /// </summary>
    public partial class NamingManagerWindow : EditorWindow
    {
        /// <summary>Which tab is showing.</summary>
        public enum Tab { Report = 0, Dashboard = 1, Rules = 2, History = 3 }

        // ---------------------------------------------------------------- state

        ScanReport _report;
        NamingRuleSet _ruleSet;
        Tab _tab = Tab.Report;

        readonly HashSet<AssetNamingViolation> _checked = new HashSet<AssetNamingViolation>();
        AssetNamingViolation _selected;

        string _search = string.Empty;
        int _severityMask = 7;                 // 1 Info | 2 Warning | 4 Error
        GroupMode _group = GroupMode.Rule;

        // ---------------------------------------------------------------- elements

        VisualElement _tabsBar;
        VisualElement _content;
        VisualElement _sheetHost;
        Label _statusText;
        Label _statusHint;
        Label _headerSubtitle;
        ObjectField _ruleSetField;
        Button _themeButton;
        readonly Dictionary<Tab, Button> _tabButtons = new Dictionary<Tab, Button>();

        // ---------------------------------------------------------------- entry points

        /// <summary>Opens the window, or focuses it if it is already open.</summary>
        [MenuItem(NamingManagerAbout.MenuRoot + "Open Window", false, 0)]
        public static NamingManagerWindow Open()
        {
            var window = GetWindow<NamingManagerWindow>();
            window.minSize = new Vector2(460f, 340f);

            var icon = NamingManagerTheme.Icon("AssetLabelIcon");
            window.titleContent = icon != null
                ? new GUIContent("Naming Manager", icon)
                : new GUIContent("Naming Manager");

            window.Show();
            return window;
        }

        /// <summary>Opens the window and immediately runs a full scan.</summary>
        [MenuItem(NamingManagerAbout.MenuRoot + "Scan Project %#n", false, 1)]
        public static void OpenAndScan()
        {
            Open().Scan();
        }

        // ---------------------------------------------------------------- lifecycle

        void OnEnable()
        {
            _severityMask = NamingManagerSettings.SeverityMask;
            _group = NamingManagerSettings.GroupMode;
        }

        void OnDisable()
        {
            NamingManagerSettings.SeverityMask = _severityMask;
            NamingManagerSettings.GroupMode = _group;
        }

        void CreateGUI()
        {
            var root = rootVisualElement;
            root.Clear();
            root.AddToClassList("anm-root");
            root.AddToClassList(EditorGUIUtility.isProSkin ? "anm-dark" : "anm-light");

            NamingManagerThemes.Apply(root);

            // UI Toolkit has no media queries, so the window creates its own breakpoints.
            root.RegisterCallback<GeometryChangedEvent>(OnGeometryChanged);

            root.Add(BuildHeader());
            root.Add(BuildTabs());

            _content = new VisualElement();
            _content.style.flexGrow = 1f;
            _content.style.overflow = Overflow.Hidden;
            root.Add(_content);

            root.Add(BuildStatusBar());

            // The sheet host sits last so any overlay draws above everything else.
            _sheetHost = new VisualElement();
            _sheetHost.AddToClassList("anm-sheet__scrim");
            _sheetHost.style.display = DisplayStyle.None;
            root.Add(_sheetHost);

            _ruleSet = NamingManagerSettings.ActiveRuleSet;
            if (_ruleSetField != null) _ruleSetField.SetValueWithoutNotify(_ruleSet);

            RefreshThemeButton();
            ShowTab(_tab);

            if (_report == null && _ruleSet != null && NamingManagerSettings.ScanOnOpen)
                EditorApplication.delayCall += () => { if (this != null) Scan(); };
        }

        void OnGeometryChanged(GeometryChangedEvent evt)
        {
            float w = evt.newRect.width;
            rootVisualElement.EnableInClassList("anm--narrow", w < 520f);
            rootVisualElement.EnableInClassList("anm--wide", w >= 900f);
        }

        // ---------------------------------------------------------------- chrome

        VisualElement BuildHeader()
        {
            var header = new VisualElement();
            header.AddToClassList("anm-header");

            var badge = new VisualElement();
            badge.AddToClassList("anm-badge");
            var badgeText = new Label("AN");
            badgeText.AddToClassList("anm-badge__text");
            badge.Add(badgeText);
            header.Add(badge);

            var titles = new VisualElement();
            titles.AddToClassList("anm-header__titles");

            var title = new Label(NamingManagerAbout.ProductName);
            title.AddToClassList("anm-header__title");
            titles.Add(title);

            _headerSubtitle = new Label("Not scanned yet");
            _headerSubtitle.AddToClassList("anm-header__subtitle");
            titles.Add(_headerSubtitle);

            header.Add(titles);

            var actions = new VisualElement();
            actions.AddToClassList("anm-header__actions");

            _ruleSetField = new ObjectField { objectType = typeof(NamingRuleSet), allowSceneObjects = false };
            _ruleSetField.AddToClassList("anm-ruleset-field");
            _ruleSetField.tooltip = "The rule set this project scans with. Commit it so the whole team shares one convention.";
            _ruleSetField.RegisterValueChangedCallback(evt =>
            {
                var next = evt.newValue as NamingRuleSet;
                if (next == null) return;
                _ruleSet = next;
                NamingManagerSettings.ActiveRuleSet = next;
                RefreshRulesTab();
                SetStatus("Rule set changed to " + next.name + ". Scan again to refresh the report.");
            });
            actions.Add(_ruleSetField);

            _themeButton = MakeButton(string.Empty, ShowThemeMenu, "Switch the window theme.");
            _themeButton.AddToClassList("anm-theme-button");
            actions.Add(_themeButton);

            actions.Add(MakeButton("Selection", ScanSelection,
                "Scan only what is selected in the Project window.", false));
            actions.Add(MakeButton("Scan", Scan, "Read-only. Nothing is modified.", true));

            header.Add(actions);
            return header;
        }

        VisualElement BuildTabs()
        {
            _tabsBar = new VisualElement();
            _tabsBar.AddToClassList("anm-tabs");

            AddTab(Tab.Report, "Report");
            AddTab(Tab.Dashboard, "Dashboard");
            AddTab(Tab.Rules, "Rules");
            AddTab(Tab.History, "History");

            return _tabsBar;
        }

        void AddTab(Tab tab, string label)
        {
            // The tab text is a CHILD Label, never Button.text. Yoga only measures text on leaf
            // nodes, so the moment a Button has any child its own text stops contributing to the
            // layout: the button collapses to the width of that child and the text draws on top
            // of whatever sits next to it.
            var button = new Button(() => ShowTab(tab));
            button.AddToClassList("anm-tab");

            var caption = new Label(label);
            caption.AddToClassList("anm-tab__label");
            caption.pickingMode = PickingMode.Ignore;
            button.Add(caption);

            var badge = new Label(string.Empty) { name = "badge" };
            badge.AddToClassList("anm-tab__badge");
            badge.pickingMode = PickingMode.Ignore;
            badge.style.display = DisplayStyle.None;
            button.Add(badge);

            _tabButtons[tab] = button;
            _tabsBar.Add(button);
        }

        void SetTabBadge(Tab tab, int count, bool alert)
        {
            Button button;
            if (!_tabButtons.TryGetValue(tab, out button)) return;

            var badge = button.Q<Label>("badge");
            if (badge == null) return;

            if (count <= 0)
            {
                badge.style.display = DisplayStyle.None;
                return;
            }

            badge.style.display = DisplayStyle.Flex;
            badge.text = count.ToString();
            badge.EnableInClassList("anm-tab__badge--alert", alert);
        }

        VisualElement BuildStatusBar()
        {
            var bar = new VisualElement();
            bar.AddToClassList("anm-status");

            _statusText = new Label("Ready.");
            _statusText.AddToClassList("anm-status__text");
            bar.Add(_statusText);

            _statusHint = new Label(NamingManagerAbout.Version);
            _statusHint.AddToClassList("anm-status__hint");
            bar.Add(_statusHint);

            return bar;
        }

        /// <summary>Switches tabs and rebuilds that tab's content.</summary>
        public void ShowTab(Tab tab)
        {
            _tab = tab;

            foreach (var pair in _tabButtons)
                pair.Value.EnableInClassList("anm-tab--active", pair.Key == tab);

            if (_content == null) return;
            _content.Clear();

            switch (tab)
            {
                case Tab.Dashboard: _content.Add(BuildDashboardTab()); break;
                case Tab.Rules:     _content.Add(BuildRulesTab());     break;
                case Tab.History:   _content.Add(BuildHistoryTab());   break;
                default:            _content.Add(BuildReportTab());    break;
            }
        }

        // ---------------------------------------------------------------- scanning

        /// <summary>Runs a full scan against the active rule set. Never modifies an asset.</summary>
        public void Scan()
        {
            _ruleSet = NamingManagerSettings.ActiveRuleSet;
            if (_ruleSetField != null) _ruleSetField.SetValueWithoutNotify(_ruleSet);

            if (_ruleSet == null)
            {
                _report = null;
                ShowTab(Tab.Report);
                SetStatus("No rule set yet. Create one to get started.");
                return;
            }

            RunScan(new ScanOptions { onProgress = ReportProgress });
        }

        /// <summary>Scans only the assets currently selected in the Project window.</summary>
        public void ScanSelection()
        {
            _ruleSet = NamingManagerSettings.ActiveRuleSet;
            if (_ruleSet == null) { Scan(); return; }

            var paths = new List<string>();
            foreach (var obj in Selection.objects)
            {
                string path = AssetDatabase.GetAssetPath(obj);
                if (!string.IsNullOrEmpty(path)) paths.Add(path);
            }

            if (paths.Count == 0)
            {
                SetStatus("Nothing selected in the Project window.");
                return;
            }

            RunScan(new ScanOptions
            {
                explicitPaths = paths.ToArray(),
                includeDuplicates = false,   // a scoped scan cannot see the whole project
                onProgress = ReportProgress
            });
        }

        void RunScan(ScanOptions options)
        {
            try
            {
                _report = NamingScanner.Scan(_ruleSet, options);
            }
            finally
            {
                EditorUtility.ClearProgressBar();
            }

            _checked.Clear();
            _selected = null;

            if (NamingManagerSettings.LogToConsole) LogReport();

            NamingManagerProjectOverlay.SetReport(_report);
            AfterReportChanged();
        }

        static bool ReportProgress(float progress, string status)
        {
            return !EditorUtility.DisplayCancelableProgressBar(
                NamingManagerAbout.ProductName, "Scanning " + status, progress);
        }

        void AfterReportChanged()
        {
            if (_report != null)
            {
                _headerSubtitle.text = string.Format("{0} assets · {1} findings · health {2:0}/100",
                    _report.assetsScanned, _report.violations.Count, _report.HealthScore);

                SetTabBadge(Tab.Report, _report.violations.Count, _report.ErrorCount > 0);
                SetStatus(_report.Summary());
            }
            else
            {
                _headerSubtitle.text = "Not scanned yet";
                SetTabBadge(Tab.Report, 0, false);
            }

            SetTabBadge(Tab.History, FixJournal.All().Count, false);
            ShowTab(_tab);
            EditorApplication.RepaintProjectWindow();
        }

        void LogReport()
        {
            if (_report == null) return;

            foreach (var v in _report.violations)
            {
                var asset = AssetDatabase.LoadMainAssetAtPath(v.assetPath);
                string message = NamingManagerAbout.LogPrefix + v.assetPath + " — " + v.message;

                switch (v.severity)
                {
                    case NamingSeverity.Error:   Debug.LogError(message, asset);   break;
                    case NamingSeverity.Warning: Debug.LogWarning(message, asset); break;
                    default:                     Debug.Log(message, asset);        break;
                }
            }

            Debug.Log(NamingManagerAbout.LogPrefix + _report.Summary());
        }

        // ---------------------------------------------------------------- helpers

        void SetStatus(string text)
        {
            if (_statusText != null) _statusText.text = text;
        }

        static Button MakeButton(string text, System.Action onClick, string tooltip = null, bool primary = false)
        {
            var button = new Button(onClick) { text = text };
            button.AddToClassList("anm-button");
            if (primary) button.AddToClassList("anm-button--primary");
            if (!string.IsNullOrEmpty(tooltip)) button.tooltip = tooltip;
            return button;
        }

        static Button MakeDangerButton(string text, System.Action onClick, string tooltip = null)
        {
            var button = MakeButton(text, onClick, tooltip);
            button.AddToClassList("anm-button--danger");
            return button;
        }

        static VisualElement Spacer()
        {
            var spacer = new VisualElement();
            spacer.AddToClassList("anm-toolbar__spacer");
            return spacer;
        }

        static Label Caption(string text, string ussClass)
        {
            var label = new Label(text);
            label.AddToClassList(ussClass);
            return label;
        }

        // ---------------------------------------------------------------- theming

        void RefreshThemeButton()
        {
            if (_themeButton == null) return;
            _themeButton.text = "Theme: " + NamingManagerThemes.Active().displayName;
        }

        void ShowThemeMenu()
        {
            var menu = new GenericMenu();
            string active = NamingManagerSettings.ActiveThemeGuid;

            foreach (var entry in NamingManagerThemes.Discover())
            {
                var chosen = entry;
                menu.AddItem(new GUIContent(chosen.displayName), chosen.guid == active,
                    () => ApplyTheme(chosen.guid));
            }

            menu.AddSeparator(string.Empty);
            menu.AddItem(new GUIContent("Pick a stylesheet…"), false, PickThemeFromDisk);
            menu.AddItem(new GUIContent("Open themes folder"), false, () =>
            {
                var folder = AssetDatabase.LoadAssetAtPath<Object>(NamingManagerThemes.ThemeFolder);
                if (folder != null) EditorGUIUtility.PingObject(folder);
            });

            menu.ShowAsContext();
        }

        void ApplyTheme(string guid)
        {
            NamingManagerSettings.ActiveThemeGuid = guid;
            NamingManagerThemes.Apply(rootVisualElement);
            RefreshThemeButton();
            ShowTab(_tab);
            SetStatus("Theme: " + NamingManagerThemes.Active().displayName +
                      ". Themes override colour tokens only, so a package update still reaches you.");
        }

        void PickThemeFromDisk()
        {
            string path = EditorUtility.OpenFilePanel("Pick a theme stylesheet", Application.dataPath, "uss");
            if (string.IsNullOrEmpty(path)) return;

            // Unity can only load a StyleSheet that lives inside the project, and FileUtil already
            // knows how to work that out on every platform.
            string relative = FileUtil.GetProjectRelativePath(GlobMatcher.NormalizePath(path));
            if (string.IsNullOrEmpty(relative))
            {
                SetStatus("That stylesheet is outside the project, so Unity cannot load it. " +
                          "Copy it under Assets and pick it again.");
                return;
            }

            string guid = AssetDatabase.AssetPathToGUID(relative);
            if (string.IsNullOrEmpty(guid))
            {
                SetStatus("Could not resolve " + relative + ". Try reimporting it.");
                return;
            }

            ApplyTheme(guid);
        }

        /// <summary>Repaints any open window. Called after an external change such as an import.</summary>
        public static void RepaintIfOpen()
        {
            foreach (var window in Resources.FindObjectsOfTypeAll<NamingManagerWindow>())
                window.Repaint();
        }
    }
}
