using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using FoxxRecovery.Materials;
using UnityEditor;
using UnityEngine;

namespace FoxxRecovery.EditorTools
{
    [InitializeOnLoad]
    public static class FoxxMaterialRepair
    {
        public const string ConvertedRoot = "Assets/FoxxRecovery/Converted";
        private static bool scheduled;
        private static bool running;

        static FoxxMaterialRepair() { Schedule(); }

        public static void Schedule()
        {
            if (scheduled || running || Application.isBatchMode) return;
            scheduled = true;
            EditorApplication.delayCall += () =>
            {
                scheduled = false;
                if (EditorApplication.isCompiling || EditorApplication.isUpdating) { Schedule(); return; }
                if (AssetDatabase.IsValidFolder(ConvertedRoot)) Repair(new[] { ConvertedRoot }, false);
            };
        }

        [MenuItem("Tools/Foxx Recovery/Repair Selected Materials with Poiyomi")]
        public static void RepairSelected()
        {
            string[] paths = Selection.objects.Select(AssetDatabase.GetAssetPath)
                .Where(path => path.StartsWith("Assets/", StringComparison.Ordinal)).Distinct().ToArray();
            if (paths.Length == 0)
            {
                EditorUtility.DisplayDialog("Foxx Material Repair", "Select an imported material or its folder in the Project window.", "OK");
                return;
            }
            Repair(paths, true);
        }

        [MenuItem("Tools/Foxx Recovery/Repair Converted Materials")]
        public static void RepairConverted() { Repair(new[] { ConvertedRoot }, true); }

        public static int Repair(string[] selection, bool interactive)
        {
            if (running) return 0;
            running = true;
            var report = new List<string>();
            int changed = 0;
            int needsPoiyomi = 0;
            string backupRoot = null;
            try
            {
                string[] materials = CollectMaterials(selection);
                if (materials.Length == 0) return 0;
                var roots = materials.Select(PackageRoot).Distinct().ToArray();
                Dictionary<string, ShaderSource> sources = ReadSources(roots);
                Dictionary<string, Shader> installed = FindInstalledShaders(sources.Values.Select(source => source.Name));
                Shader poiyomi = installed.Values.Where(shader =>
                    shader.name.IndexOf("poiyomi", StringComparison.OrdinalIgnoreCase) >= 0 &&
                    shader.name.EndsWith("Poiyomi Toon", StringComparison.OrdinalIgnoreCase) &&
                    !shader.name.StartsWith("Hidden/", StringComparison.OrdinalIgnoreCase))
                    .OrderByDescending(shader => shader.name == ".poiyomi/Poiyomi Toon")
                    .ThenBy(shader => shader.name, StringComparer.Ordinal).FirstOrDefault();

                foreach (string path in materials)
                {
                    try
                    {
                        Material material = AssetDatabase.LoadAssetAtPath<Material>(path);
                        if (material == null) continue;
                        string yaml = File.ReadAllText(path);
                        Match reference = Regex.Match(yaml, @"m_Shader:\s*\{[^}\r\n]*guid:\s*([a-fA-F0-9]{32})");
                        ShaderSource source = null;
                        if (reference.Success) sources.TryGetValue(reference.Groups[1].Value, out source);
                        Shader old = material.shader;
                        bool placeholder = source != null && FoxxShaderPolicy.IsPlaceholder(source.Text);
                        bool broken = old == null || old.name == "Hidden/InternalErrorShader" || ShaderUtil.ShaderHasError(old);
                        if (!broken && !placeholder) continue;

                        string originalName = source != null ? source.Name : (old == null ? "" : old.name);
                        Shader target;
                        bool exact = installed.TryGetValue(originalName, out target);
                        if (!exact)
                        {
                            if (!FoxxShaderPolicy.CanUsePoiyomi(originalName, yaml, source == null ? "" : source.Text))
                            {
                                report.Add(path + ": original shader required: " + originalName + "; left unchanged.");
                                continue;
                            }
                            target = poiyomi;
                            if (target == null)
                            {
                                needsPoiyomi++;
                                report.Add(path + ": install Poiyomi Toon, then run repair again; left unchanged.");
                                continue;
                            }
                        }

                        if (backupRoot == null)
                            backupRoot = Path.Combine("FoxxRecoveryBackups", "Shaders", DateTime.UtcNow.ToString("yyyyMMdd-HHmmss") + "-" + Guid.NewGuid().ToString("N").Substring(0, 8));
                        string backup = Path.Combine(backupRoot, path);
                        Directory.CreateDirectory(Path.GetDirectoryName(backup));
                        File.Copy(path, backup, false);
                        if (File.Exists(path + ".meta")) File.Copy(path + ".meta", backup + ".meta", false);

                        Undo.RecordObject(material, "Foxx shader repair");
                        int queue = material.renderQueue;
                        string renderType = material.GetTag("RenderType", false, "");
                        // Assigning the shader keeps serialized textures, colors, floats, and material GUIDs.
                        material.shader = target;
                        material.renderQueue = queue;
                        if (!string.IsNullOrEmpty(renderType)) material.SetOverrideTag("RenderType", renderType);
                        if (target.name.IndexOf("poiyomi", StringComparison.OrdinalIgnoreCase) >= 0 && material.HasProperty("_ShaderOptimizerEnabled"))
                            material.SetFloat("_ShaderOptimizerEnabled", 0);
                        EditorUtility.SetDirty(material);
#if UNITY_2021_2_OR_NEWER
                        AssetDatabase.SaveAssetIfDirty(material);
#else
                        AssetDatabase.SaveAssets();
#endif
                        changed++;
                        report.Add(path + ": " + originalName + " -> " + target.name + (exact ? " (installed original)" : " (appearance fallback; custom effects are not reconstructed)"));
                    }
                    catch (Exception error) { report.Add(path + ": not repaired: " + error.Message); }
                }

                string summary = "Foxx shader repair: " + changed + " material(s) repaired. " + needsPoiyomi + " need Poiyomi Toon installed.";
                if (backupRoot != null)
                {
                    File.WriteAllLines(Path.Combine(backupRoot, "repair-report.txt"), new[] { summary }.Concat(report).ToArray());
                    summary += " Backups: " + Path.GetFullPath(backupRoot);
                }
                if (interactive || changed > 0 || (needsPoiyomi > 0 && !SessionState.GetBool("Foxx.PoiyomiMissing", false)))
                {
                    Debug.Log(summary + "\n" + string.Join("\n", report.ToArray()));
                    if (needsPoiyomi > 0) SessionState.SetBool("Foxx.PoiyomiMissing", true);
                }
                if (interactive) EditorUtility.DisplayDialog("Foxx Material Repair", summary, "OK");
                return changed;
            }
            finally { running = false; }
        }

        private static string[] CollectMaterials(IEnumerable<string> selected)
        {
            var paths = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
            foreach (string path in selected)
            {
                if (!path.StartsWith("Assets/", StringComparison.Ordinal) || path.Contains("..")) continue;
                if (path.EndsWith(".mat", StringComparison.OrdinalIgnoreCase)) paths.Add(path);
                else if (AssetDatabase.IsValidFolder(path))
                    foreach (string guid in AssetDatabase.FindAssets("t:Material", new[] { path })) paths.Add(AssetDatabase.GUIDToAssetPath(guid));
            }
            return paths.OrderBy(path => path, StringComparer.Ordinal).ToArray();
        }

        private static string PackageRoot(string material)
        {
            if (material.StartsWith(ConvertedRoot + "/", StringComparison.Ordinal))
            {
                string[] segments = material.Split('/');
                if (segments.Length >= 6) return string.Join("/", segments.Take(5).ToArray());
            }
            return Path.GetDirectoryName(material).Replace('\\', '/');
        }

        private static Dictionary<string, ShaderSource> ReadSources(IEnumerable<string> roots)
        {
            var result = new Dictionary<string, ShaderSource>(StringComparer.OrdinalIgnoreCase);
            foreach (string root in roots)
            foreach (string file in Directory.EnumerateFiles(root, "*.shader", SearchOption.AllDirectories))
            {
                try
                {
                    if (!File.Exists(file + ".meta")) continue;
                    Match guid = Regex.Match(File.ReadAllText(file + ".meta"), @"(?m)^guid:\s*([a-fA-F0-9]{32})");
                    if (!guid.Success) continue;
                    string text = File.ReadAllText(file);
                    result[guid.Groups[1].Value] = new ShaderSource { Name = FoxxShaderPolicy.ReadShaderName(text), Text = text };
                }
                catch (IOException) { }
            }
            return result;
        }

        private static Dictionary<string, Shader> FindInstalledShaders(IEnumerable<string> originalNames)
        {
            var result = new Dictionary<string, Shader>(StringComparer.Ordinal);
            var candidates = new HashSet<Shader>();
            foreach (string name in originalNames.Distinct())
            {
                Shader shader = Shader.Find(name);
                if (shader != null) candidates.Add(shader);
            }
            foreach (string guid in AssetDatabase.FindAssets("Poiyomi t:Shader"))
            {
                Shader shader = AssetDatabase.LoadAssetAtPath<Shader>(AssetDatabase.GUIDToAssetPath(guid));
                if (shader != null) candidates.Add(shader);
            }
            foreach (Shader shader in candidates.OrderBy(item => item.name, StringComparer.Ordinal))
            {
                string path = AssetDatabase.GetAssetPath(shader);
                if (path.StartsWith(ConvertedRoot + "/", StringComparison.Ordinal)) continue;
                if (shader == null || shader.name.StartsWith("Hidden/", StringComparison.OrdinalIgnoreCase) || ShaderUtil.ShaderHasError(shader)) continue;
                if (File.Exists(path) && FoxxShaderPolicy.IsPlaceholder(File.ReadAllText(path))) continue;
                if (!result.ContainsKey(shader.name)) result.Add(shader.name, shader);
            }
            return result;
        }

        private sealed class ShaderSource { public string Name; public string Text; }
    }

    public sealed class FoxxMaterialImportObserver : AssetPostprocessor
    {
        private static void OnPostprocessAllAssets(string[] imported, string[] deleted, string[] moved, string[] previous)
        {
            if (imported.Any(path => path.EndsWith(".shader", StringComparison.OrdinalIgnoreCase) ||
                path.StartsWith(FoxxMaterialRepair.ConvertedRoot + "/", StringComparison.Ordinal))) FoxxMaterialRepair.Schedule();
        }
    }
}
