// Asset Naming Manager — the Rules tab: edit the convention without leaving the window.
using System.Collections.Generic;
using UnityEditor;
using UnityEngine;
using UnityEngine.UIElements;

namespace AssetNamingManager.UI
{
    public partial class NamingManagerWindow
    {
        VisualElement _rulesRoot;

        VisualElement BuildRulesTab()
        {
            if (_ruleSet == null) return BuildEmptyNoRuleSet();

            var scroll = new ScrollView();
            scroll.AddToClassList("anm-scroll");

            _rulesRoot = new VisualElement();
            _rulesRoot.AddToClassList("anm-rules");
            scroll.Add(_rulesRoot);

            PopulateRules();
            return scroll;
        }

        void RefreshRulesTab()
        {
            if (_tab == Tab.Rules) ShowTab(Tab.Rules);
        }

        void PopulateRules()
        {
            _rulesRoot.Clear();

            var info = new VisualElement();
            info.AddToClassList("anm-info-box");
            var infoText = new Label(
                "Rules live in " + _ruleSet.name + ", a ScriptableObject. Commit it and the whole team shares " +
                "one convention. When several rules match one asset the most specific wins — a path filter " +
                "counts 8, extensions 4, type names 4, and ties go to whichever rule sits higher in the list.");
            infoText.AddToClassList("anm-info-box__text");
            info.Add(infoText);
            _rulesRoot.Add(info);

            var problems = _ruleSet.Validate();
            if (problems.Count > 0)
            {
                var box = new VisualElement();
                box.AddToClassList("anm-info-box");
                box.style.borderLeftColor = NamingManagerTheme.Warning;   // one-off emphasis, not themed

                var text = new Label("Configuration to look at:\n• " + string.Join("\n• ", problems.ToArray()));
                text.AddToClassList("anm-info-box__text");
                box.Add(text);
                _rulesRoot.Add(box);
            }

            _rulesRoot.Add(BuildPresetBar());

            var header = new VisualElement();
            header.style.flexDirection = FlexDirection.Row;
            header.style.marginBottom = 8f;
            header.Add(MakeButton("Edit in Inspector", () =>
            {
                Selection.activeObject = _ruleSet;
                EditorGUIUtility.PingObject(_ruleSet);
            }, "Open the full rule set in the Inspector for detailed editing."));
            header.Add(Spacer());
            header.Add(MakeButton("Rescan", Scan, null, true));
            _rulesRoot.Add(header);

            if (_ruleSet.rules == null || _ruleSet.rules.Count == 0)
            {
                var empty = new Label("This rule set has no rules. Load a preset to get started.");
                empty.AddToClassList("anm-info-box__text");
                _rulesRoot.Add(empty);
                return;
            }

            foreach (var rule in _ruleSet.rules)
            {
                if (rule != null) _rulesRoot.Add(BuildRuleCard(rule));
            }
        }

        /// <summary>
        /// One button per preset, with the one you are on marked. A menu hides which convention is
        /// active; a row of buttons answers "what am I running?" without a click.
        /// </summary>
        VisualElement BuildPresetBar()
        {
            var box = new VisualElement();
            box.style.marginBottom = 10f;

            string current = _ruleSet.lastAppliedPreset ?? string.Empty;

            var caption = new Label(string.IsNullOrEmpty(current)
                ? "Convention — no preset recorded, these rules were edited by hand"
                : "Convention — based on " + ObjectNames.NicifyVariableName(current));
            caption.AddToClassList("anm-field-label");
            caption.style.marginBottom = 4f;
            box.Add(caption);

            var row = new VisualElement();
            row.style.flexDirection = FlexDirection.Row;
            row.style.flexWrap = Wrap.Wrap;

            foreach (NamingPreset preset in System.Enum.GetValues(typeof(NamingPreset)))
            {
                var value = preset;
                bool active = string.Equals(current, value.ToString(), System.StringComparison.Ordinal);

                var button = MakeButton(
                    ObjectNames.NicifyVariableName(value.ToString()),
                    () => LoadPreset(value),
                    NamingRulePresets.Describe(value),
                    active);

                if (active) button.SetEnabled(false);   // already on it, nothing to apply
                row.Add(button);
            }

            box.Add(row);

            var note = new Label(
                "Switching a preset rewrites the rules only. No asset is renamed or moved — you still " +
                "choose what to fix afterwards. Files already renamed stay renamed; use the History tab " +
                "to put those back.");
            note.AddToClassList("anm-info-box__text");
            note.style.marginTop = 6f;
            box.Add(note);

            return box;
        }

        void LoadPreset(NamingPreset preset)
        {
            bool ok = EditorUtility.DisplayDialog(
                "Switch to " + ObjectNames.NicifyVariableName(preset.ToString()) + "?",
                NamingRulePresets.Describe(preset) + "\n\n" +
                "This replaces all " + (_ruleSet.rules != null ? _ruleSet.rules.Count : 0) +
                " rules in " + _ruleSet.name + ".\n\n" +
                "No assets are touched. Anything already renamed stays renamed.",
                "Switch convention", "Cancel");

            if (!ok) return;

            Undo.RecordObject(_ruleSet, "Switch naming preset");
            NamingRulePresets.Apply(_ruleSet, preset);
            EditorUtility.SetDirty(_ruleSet);
            AssetDatabase.SaveAssets();

            SetStatus("Now on " + ObjectNames.NicifyVariableName(preset.ToString()) +
                      ". Rescanning against the new rules.");
            // Scan() rebuilds whatever tab is open, and this button only exists on the Rules
            // tab, so the new cards come back from here. Rebuilding again would be redundant.
            Scan();
        }

        VisualElement BuildRuleCard(NamingRule rule)
        {
            var card = new VisualElement();
            card.AddToClassList("anm-rule-card");
            if (!rule.enabled) card.AddToClassList("anm-rule-card--off");

            // ---- head
            var head = new VisualElement();
            head.AddToClassList("anm-rule-card__head");

            var toggle = new Toggle { value = rule.enabled };
            toggle.tooltip = "Turn this rule off without deleting it.";
            toggle.RegisterValueChangedCallback(evt =>
            {
                Undo.RecordObject(_ruleSet, "Toggle naming rule");
                rule.enabled = evt.newValue;
                EditorUtility.SetDirty(_ruleSet);
                card.EnableInClassList("anm-rule-card--off", !rule.enabled);
            });
            head.Add(toggle);

            var name = new Label(rule.displayName);
            name.AddToClassList("anm-rule-card__name");
            head.Add(name);

            var summary = new Label(SummariseRule(rule));
            summary.AddToClassList("anm-rule-card__summary");
            head.Add(summary);

            var pill = new Label(rule.severity.ToString());
            pill.AddToClassList("anm-pill");
            pill.AddToClassList("anm-pill--" + NamingManagerTheme.ClassFor(rule.severity));
            head.Add(pill);

            card.Add(head);

            // ---- body
            var body = new VisualElement();
            body.AddToClassList("anm-rule-card__body");

            // A check that is switched off must LOOK switched off. Hiding the row makes an inactive
            // check indistinguishable from a passing one, and the first question that gets asked is
            // "why did it not flag this file?".
            body.Add(BuildActiveChecksLine(rule));

            AddField(body, "Extensions", string.IsNullOrWhiteSpace(rule.extensions) ? "any" : rule.extensions,
                     string.IsNullOrWhiteSpace(rule.extensions));
            AddField(body, "Type names", string.IsNullOrWhiteSpace(rule.typeNames) ? "any" : rule.typeNames,
                     string.IsNullOrWhiteSpace(rule.typeNames));

            bool scoped = rule.pathFilters != null && rule.pathFilters.Length > 0;
            AddField(body, "Only looks under", scoped ? string.Join(", ", rule.pathFilters) : "the whole project", !scoped);

            AddField(body, "Required prefix", rule.Prefixes.Length > 0 ? string.Join(" or ", rule.Prefixes) : "none",
                     rule.Prefixes.Length == 0);
            AddField(body, "Required suffix", rule.Suffixes.Length > 0 ? string.Join(" or ", rule.Suffixes) : "none",
                     rule.Suffixes.Length == 0);

            AddField(body, "Convention", rule.convention == NamingConvention.Any
                ? "any casing accepted"
                : rule.convention + "  (" + NameUtility.Example(rule.convention) + ")",
                rule.convention == NamingConvention.Any);

            AddField(body, "Length", (rule.minLength > 0 ? rule.minLength.ToString() : "0") + " – " +
                                     (rule.maxLength > 0 ? rule.maxLength.ToString() : "unlimited"));

            bool hasAllowed = rule.allowedFolders != null && rule.allowedFolders.Length > 0;
            AddField(body, "Allowed folders",
                     hasAllowed
                         ? string.Join(", ", rule.allowedFolders)
                         : "anywhere — placement is NOT checked",
                     !hasAllowed);

            bool hasForbidden = rule.forbiddenFolders != null && rule.forbiddenFolders.Length > 0;
            if (hasForbidden) AddField(body, "Forbidden folders", string.Join(", ", rule.forbiddenFolders));

            AddField(body, "Moves to",
                     string.IsNullOrWhiteSpace(rule.preferredFolder)
                         ? "nowhere — folder findings are report-only"
                         : rule.preferredFolder,
                     string.IsNullOrWhiteSpace(rule.preferredFolder));

            if (!string.IsNullOrWhiteSpace(rule.customRegex))
                AddField(body, rule.regexMustMatch ? "Must match" : "Must NOT match", rule.customRegex);

            if (!rule.allowAutoFix) AddField(body, "Auto-fix", "off — reports only");

            if (!string.IsNullOrWhiteSpace(rule.note))
            {
                var note = new VisualElement();
                note.style.marginTop = 6f;
                note.Add(Caption("Team note", "anm-field-label"));
                var text = new Label(rule.note);
                text.AddToClassList("anm-field-value");
                text.style.whiteSpace = WhiteSpace.Normal;
                text.AddToClassList("anm-text-muted");
                note.Add(text);
                body.Add(note);
            }

            card.Add(body);
            return card;
        }

        static void AddField(VisualElement parent, string label, string value, bool inactive = false)
        {
            var field = new VisualElement();
            field.style.marginBottom = 4f;

            var caption = new Label(label);
            caption.AddToClassList("anm-field-label");
            field.Add(caption);

            var text = new Label(value);
            text.AddToClassList("anm-field-value");
            text.style.whiteSpace = WhiteSpace.Normal;
            if (inactive) text.AddToClassList("anm-text-dim");
            field.Add(text);

            parent.Add(field);
        }

        /// <summary>
        /// A one-line "this rule actually checks X, Y and Z" summary. Without it a rule with an empty
        /// allowed-folders list looks exactly like one whose folder check is passing.
        /// </summary>
        static VisualElement BuildActiveChecksLine(NamingRule rule)
        {
            var active = new List<string>();
            var inactive = new List<string>();

            (rule.Prefixes.Length > 0 ? active : inactive).Add("prefix");
            (rule.Suffixes.Length > 0 ? active : inactive).Add("suffix");
            (rule.convention != NamingConvention.Any ? active : inactive).Add("casing");
            (rule.minLength > 0 || rule.maxLength > 0 ? active : inactive).Add("length");
            (rule.allowedFolders != null && rule.allowedFolders.Length > 0 ? active : inactive).Add("folder");
            (!string.IsNullOrWhiteSpace(rule.customRegex) ? active : inactive).Add("pattern");

            var box = new VisualElement();
            box.style.marginBottom = 8f;

            var on = new Label("Checks: " + (active.Count > 0 ? string.Join(", ", active.ToArray()) : "nothing"));
            on.AddToClassList("anm-field-value");
            on.style.whiteSpace = WhiteSpace.Normal;
            box.Add(on);

            if (inactive.Count > 0)
            {
                var off = new Label("Not checked: " + string.Join(", ", inactive.ToArray()));
                off.AddToClassList("anm-field-value");
                off.style.whiteSpace = WhiteSpace.Normal;
                off.AddToClassList("anm-text-dim");
                box.Add(off);
            }

            return box;
        }

        static string SummariseRule(NamingRule rule)
        {
            var parts = new List<string>();

            if (!string.IsNullOrWhiteSpace(rule.extensions)) parts.Add(rule.extensions);
            if (rule.Prefixes.Length > 0) parts.Add(rule.Prefixes[0]);
            if (rule.convention != NamingConvention.Any) parts.Add(rule.convention.ToString());
            if (rule.allowedFolders != null && rule.allowedFolders.Length > 0) parts.Add(rule.allowedFolders[0]);
            if (!rule.allowAutoFix) parts.Add("no auto-fix");

            return parts.Count == 0 ? "matches everything" : string.Join("  ·  ", parts.ToArray());
        }

    }
}
