// Asset Naming Manager — the menu commands that live outside the window.
using System.Collections.Generic;
using UnityEditor;

namespace AssetNamingManager
{
    /// <summary>
    /// The commands the tool adds to Unity's own menus: creating a rule set from the Tools menu,
    /// and checking or fixing a selection from the Project window's right-click menu. The window
    /// owns Open Window and Scan Project; every command here delegates to the same
    /// <see cref="NamingScanner"/> and <see cref="NamingFixer"/> the window uses.
    /// </summary>
    public static class NamingManagerMenus
    {
        const string CreateRoot = NamingManagerAbout.MenuRoot + "Create Rule Set/";

        // ---------------------------------------------------------------- create a rule set

        // One entry per preset rather than a dialog: DisplayDialogComplex stops at three buttons and
        // there are five presets, and a submenu is reachable from Unity's own menu search, so a
        // buyer following the docs can type "Unreal" and land on it. Add a method when you add a
        // preset to NamingPreset — [MenuItem] needs a constant path, so the list cannot be generated.

        [MenuItem(CreateRoot + "Unity Standard", false, 20)]
        static void CreateUnityStandard()
        {
            Create(NamingPreset.UnityStandard);
        }

        [MenuItem(CreateRoot + "Unreal Style", false, 21)]
        static void CreateUnrealStyle()
        {
            Create(NamingPreset.UnrealStyle);
        }

        [MenuItem(CreateRoot + "Mobile 2D", false, 22)]
        static void CreateMobile2D()
        {
            Create(NamingPreset.Mobile2D);
        }

        [MenuItem(CreateRoot + "Minimal", false, 23)]
        static void CreateMinimal()
        {
            Create(NamingPreset.Minimal);
        }

        [MenuItem(CreateRoot + "Strict Studio", false, 24)]
        static void CreateStrictStudio()
        {
            Create(NamingPreset.StrictStudio);
        }

        /// <summary>
        /// Creates a rule set seeded with <paramref name="preset"/>, makes it the active one and
        /// selects it so the Inspector is already showing the rules.
        /// </summary>
        public static NamingRuleSet Create(NamingPreset preset)
        {
            NamingRuleSet created = NamingManagerSettings.CreateRuleSet(preset);
            if (created == null) return null;   // CreateRuleSet has already said why on the console

            Selection.activeObject = created;
            EditorGUIUtility.PingObject(created);

            UnityEngine.Debug.Log(
                NamingManagerAbout.LogPrefix + "Created " + created.name + " from the " +
                ObjectNames.NicifyVariableName(preset.ToString()) + " preset — " +
                NamingRulePresets.Describe(preset), created);

            return created;
        }

        // ---------------------------------------------------------------- Project window selection

        /// <summary>Scans the selected assets and folders and prints every finding to the console.</summary>
        [MenuItem(NamingManagerAbout.AssetMenuRoot + "Check Selected")]
        static void CheckSelected()
        {
            ScanReport report = ScanSelection();
            if (report == null) return;

            foreach (var violation in report.violations) LogViolation(violation);

            UnityEngine.Debug.Log(NamingManagerAbout.LogPrefix +
                (report.cancelled ? "Cancelled part way — " : "Checked the selection — ") + report.Summary());
        }

        [MenuItem(NamingManagerAbout.AssetMenuRoot + "Check Selected", true)]
        static bool CheckSelectedValidate()
        {
            return HasSelection();
        }

        /// <summary>Scans the selected assets and folders, then applies the fixes it can.</summary>
        [MenuItem(NamingManagerAbout.AssetMenuRoot + "Fix Selected")]
        static void FixSelected()
        {
            ScanReport report = ScanSelection();
            if (report == null) return;

            // A cancelled scan produces a partial list, and fixing a partial list is not what
            // somebody who just pressed Cancel asked for.
            if (report.cancelled)
            {
                UnityEngine.Debug.Log(NamingManagerAbout.LogPrefix + "Scan cancelled. Nothing was changed.");
                return;
            }

            List<FixPlan> plans = NamingFixer.BuildPlans(report.violations);
            if (plans.Count == 0)
            {
                UnityEngine.Debug.Log(NamingManagerAbout.LogPrefix +
                    "Nothing in the selection can be fixed automatically — " + report.Summary());
                return;
            }

            if (NamingManagerSettings.ConfirmBeforeFixing && !Confirm(plans)) return;

            FixResult result = NamingFixer.Apply(plans);

            foreach (var problem in result.problems)
                UnityEngine.Debug.LogWarning(NamingManagerAbout.LogPrefix + problem);

            UnityEngine.Debug.Log(NamingManagerAbout.LogPrefix + "Fixed the selection — " + result.Summary() +
                (string.IsNullOrEmpty(result.batchId)
                    ? string.Empty
                    : " Batch " + result.batchId + " can be reverted from the History tab."));
        }

        [MenuItem(NamingManagerAbout.AssetMenuRoot + "Fix Selected", true)]
        static bool FixSelectedValidate()
        {
            return HasSelection();
        }

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

        // Runs on every selection change and every time the menu opens, so it stays a length check:
        // no AssetDatabase lookups, and certainly no scan.
        static bool HasSelection()
        {
            return Selection.assetGUIDs.Length > 0;
        }

        /// <summary>
        /// Scans the Project window selection, or returns null after explaining on the console why
        /// it could not. Folders are handled by the scanner, which walks everything underneath them.
        /// The Project window overlay is deliberately left alone: it holds the last full scan, and a
        /// scoped check would wipe the dots off every asset it did not look at.
        /// </summary>
        static ScanReport ScanSelection()
        {
            NamingRuleSet ruleSet = NamingManagerSettings.ActiveRuleSet;
            if (ruleSet == null)
            {
                UnityEngine.Debug.LogWarning(NamingManagerAbout.LogPrefix +
                    "This project has no rule set yet. Create one from " + CreateRoot + "…");
                return null;
            }

            string[] paths = SelectedPaths();
            if (paths.Length == 0)
            {
                UnityEngine.Debug.LogWarning(NamingManagerAbout.LogPrefix + "Nothing is selected in the Project window.");
                return null;
            }

            try
            {
                return NamingScanner.Scan(ruleSet, new ScanOptions
                {
                    explicitPaths = paths,
                    includeDuplicates = false,   // a scoped scan cannot see the rest of the project
                    onProgress = ReportProgress
                });
            }
            finally
            {
                EditorUtility.ClearProgressBar();
            }
        }

        static string[] SelectedPaths()
        {
            string[] guids = Selection.assetGUIDs;
            var paths = new List<string>(guids.Length);

            foreach (var guid in guids)
            {
                string path = AssetDatabase.GUIDToAssetPath(guid);
                if (!string.IsNullOrEmpty(path)) paths.Add(path);
            }

            return paths.ToArray();
        }

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

        static bool Confirm(List<FixPlan> plans)
        {
            int renames = 0;
            int moves = 0;

            foreach (var plan in plans)
            {
                string source = FixPlan.ParentFolder(plan.sourcePath);
                string target = plan.targetFolder ?? source;
                if (string.Equals(source, target, System.StringComparison.Ordinal)) renames++;
                else moves++;
            }

            return EditorUtility.DisplayDialog(
                "Fix " + plans.Count + " assets?",
                plans.Count + " of the assets you selected will change: " +
                renames + " renamed, " + moves + " moved.\n\n" +
                "Every change goes through AssetDatabase.MoveAsset, so prefab, scene and script " +
                "references follow the asset. The whole batch is recorded and can be reverted from " +
                "the History tab.",
                "Fix " + plans.Count + " assets", "Cancel");
        }

        static void LogViolation(AssetNamingViolation violation)
        {
            // The context object is what makes the console line click back to the asset.
            UnityEngine.Object asset = AssetDatabase.LoadMainAssetAtPath(violation.assetPath);
            string message = NamingManagerAbout.LogPrefix + violation.assetPath + " — " + violation.message;

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