// Asset Naming Manager — a safety net for bulk renames.
using System;
using System.Collections.Generic;
using System.IO;
using UnityEditor;
using UnityEngine;

namespace AssetNamingManager
{
    /// <summary>
    /// Records every rename and move the Cop performs so you can put them back.
    /// Unity's own Undo stack does not cover AssetDatabase moves, so a bulk rename would otherwise
    /// be a one-way door. The journal lives in <c>UserSettings/</c>, which Unity's default
    /// .gitignore already excludes, so it never pollutes a commit.
    /// </summary>
    public static class FixJournal
    {
        const string Folder = "UserSettings/AssetNamingManager";
        const string FileName = "FixJournal.json";
        const int MaxBatches = 20;

        /// <summary>One file that moved from A to B.</summary>
        [Serializable]
        public class Entry
        {
            public string from;
            public string to;
        }

        /// <summary>One "Fix Selected" press: everything it changed, and when.</summary>
        [Serializable]
        public class Batch
        {
            public string id;
            public string timestampUtc;
            public List<Entry> entries = new List<Entry>();

            /// <summary>Local time label for the UI, e.g. "14 Mar 2026, 16:20".</summary>
            public string DisplayTime
            {
                get
                {
                    DateTime parsed;
                    if (DateTime.TryParse(timestampUtc, null,
                            System.Globalization.DateTimeStyles.RoundtripKind, out parsed))
                        return parsed.ToLocalTime().ToString("dd MMM yyyy, HH:mm");
                    return timestampUtc;
                }
            }
        }

        [Serializable]
        class Store
        {
            public List<Batch> batches = new List<Batch>();
        }

        static Store _cache;

        // With Fast Enter Play Mode - the default for new Unity 6.6 projects - the domain is
        // not reloaded, so statics survive between Play sessions. Clear the cache here so a
        // session never starts holding something the previous one left behind.
        [InitializeOnEnterPlayMode]
        static void ResetOnEnterPlayMode()
        {
            _cache = null;
        }

        static string FilePath
        {
            get { return Folder + "/" + FileName; }
        }

        // ---------------------------------------------------------------- read

        /// <summary>Every recorded batch, newest last.</summary>
        public static List<Batch> All()
        {
            Load();
            return _cache.batches;
        }

        /// <summary>The most recent batch, or null when nothing has been fixed yet.</summary>
        public static Batch Latest()
        {
            Load();
            return _cache.batches.Count == 0 ? null : _cache.batches[_cache.batches.Count - 1];
        }

        /// <summary>Looks a batch up by the id returned in <see cref="FixResult.batchId"/>.</summary>
        public static Batch Find(string id)
        {
            if (string.IsNullOrEmpty(id)) return null;
            Load();
            foreach (var batch in _cache.batches)
            {
                if (batch.id == id) return batch;
            }
            return null;
        }

        // ---------------------------------------------------------------- write

        /// <summary>Stores a batch and returns its id.</summary>
        public static string Record(List<Entry> entries)
        {
            if (entries == null || entries.Count == 0) return string.Empty;

            Load();

            var batch = new Batch
            {
                id = Guid.NewGuid().ToString("N").Substring(0, 12),
                timestampUtc = DateTime.UtcNow.ToString("o"),
                entries = new List<Entry>(entries)
            };

            _cache.batches.Add(batch);
            while (_cache.batches.Count > MaxBatches) _cache.batches.RemoveAt(0);

            Save();
            return batch.id;
        }

        /// <summary>Deletes the whole history. The files stay where they are.</summary>
        public static void Clear()
        {
            _cache = new Store();
            Save();
        }

        // ---------------------------------------------------------------- revert

        /// <summary>
        /// Moves every asset in a batch back where it came from, newest change first.
        /// Returns how many were restored; failures are reported through <paramref name="problems"/>.
        /// </summary>
        public static int Revert(Batch batch, List<string> problems = null, bool showProgress = true)
        {
            if (batch == null || batch.entries.Count == 0) return 0;

            int restored = 0;

            try
            {
                for (int i = batch.entries.Count - 1; i >= 0; i--)
                {
                    var entry = batch.entries[i];

                    if (showProgress)
                    {
                        EditorUtility.DisplayProgressBar(
                            "Asset Naming Manager",
                            "Reverting " + NamingScanner.GetFileName(entry.to),
                            1f - (float)i / batch.entries.Count);
                    }

                    if (string.IsNullOrEmpty(AssetDatabase.AssetPathToGUID(entry.to)))
                    {
                        if (problems != null)
                            problems.Add(entry.to + " — not found, it may have been renamed again since.");
                        continue;
                    }

                    if (!NamingFixer.EnsureFolder(FixPlan.ParentFolder(entry.from)))
                    {
                        if (problems != null) problems.Add(entry.from + " — original folder could not be recreated.");
                        continue;
                    }

                    string error = AssetDatabase.MoveAsset(entry.to, entry.from);
                    if (string.IsNullOrEmpty(error)) restored++;
                    else if (problems != null) problems.Add(entry.to + " — " + error);
                }
            }
            finally
            {
                if (showProgress) EditorUtility.ClearProgressBar();
            }

            if (restored > 0)
            {
                AssetDatabase.SaveAssets();
                AssetDatabase.Refresh();

                Load();
                _cache.batches.Remove(batch);
                Save();
            }

            return restored;
        }

        // ---------------------------------------------------------------- storage

        static void Load()
        {
            if (_cache != null) return;

            _cache = new Store();
            try
            {
                if (File.Exists(FilePath))
                {
                    string json = File.ReadAllText(FilePath);
                    if (!string.IsNullOrEmpty(json))
                    {
                        var parsed = JsonUtility.FromJson<Store>(json);
                        if (parsed != null && parsed.batches != null) _cache = parsed;
                    }
                }
            }
            catch (Exception e)
            {
                UnityEngine.Debug.LogWarning("[Asset Naming Manager] Could not read the fix journal: " + e.Message);
                _cache = new Store();
            }
        }

        static void Save()
        {
            try
            {
                if (!Directory.Exists(Folder)) Directory.CreateDirectory(Folder);
                File.WriteAllText(FilePath, JsonUtility.ToJson(_cache, true));
            }
            catch (Exception e)
            {
                UnityEngine.Debug.LogWarning("[Asset Naming Manager] Could not write the fix journal: " + e.Message);
            }
        }
    }
}
