#nullable enable
using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using UnityEditor;
using UnityEngine;

namespace io.github.hatayama.uLoopMCP
{
    /// <summary>
    /// Utility class for capturing any EditorWindow to a Texture2D.
    /// Uses InternalEditorUtilityBridge to access Unity internal GrabPixels method.
    /// </summary>
    public static class EditorWindowCaptureUtility
    {
        /// <summary>
        /// Find all EditorWindows matching the given name (title bar text).
        /// </summary>
        /// <param name="windowName">Window name displayed in the title bar (e.g., "Console", "Inspector")</param>
        /// <param name="matchMode">Matching mode: exact, prefix, or contains (all case-insensitive)</param>
        /// <returns>Array of matching EditorWindows (empty if none found)</returns>
        public static EditorWindow[] FindWindowsByName(string windowName, WindowMatchMode matchMode = WindowMatchMode.exact)
        {
            if (string.IsNullOrEmpty(windowName))
            {
                return Array.Empty<EditorWindow>();
            }

            List<EditorWindow> matchingWindows = new List<EditorWindow>();
            EditorWindow[] allWindows = Resources.FindObjectsOfTypeAll<EditorWindow>();
            foreach (EditorWindow window in allWindows)
            {
                if (window.titleContent == null)
                {
                    continue;
                }

                string title = window.titleContent.text;
                bool isMatch = matchMode switch
                {
                    WindowMatchMode.exact => title.Equals(windowName, StringComparison.OrdinalIgnoreCase),
                    WindowMatchMode.prefix => title.StartsWith(windowName, StringComparison.OrdinalIgnoreCase),
                    WindowMatchMode.contains => title.Contains(windowName, StringComparison.OrdinalIgnoreCase),
                    _ => title.Equals(windowName, StringComparison.OrdinalIgnoreCase)
                };

                if (isMatch)
                {
                    matchingWindows.Add(window);
                }
            }

            return matchingWindows.ToArray();
        }

        /// <summary>
        /// Capture an EditorWindow to a Texture2D asynchronously.
        /// Waits for 2 frames after showing the window to ensure it is fully rendered.
        /// </summary>
        /// <param name="window">The EditorWindow to capture</param>
        /// <param name="resolutionScale">Resolution scale (0.1 to 1.0)</param>
        /// <param name="ct">Cancellation token</param>
        /// <returns>Captured Texture2D, or null if capture failed</returns>
        public static async Task<Texture2D?> CaptureWindowAsync(EditorWindow window, float resolutionScale, CancellationToken ct)
        {
            if (window == null)
            {
                return null;
            }

            window.ShowTab();
            await EditorDelay.DelayFrame(2, ct);
            return CaptureWindowInternal(window, resolutionScale);
        }

        /// <summary>
        /// Internal capture logic shared by sync and async methods.
        /// </summary>
        private static Texture2D? CaptureWindowInternal(EditorWindow window, float resolutionScale)
        {
            float scale = EditorGUIUtility.pixelsPerPoint;
            int width = Mathf.RoundToInt(window.position.width * scale);
            int height = Mathf.RoundToInt(window.position.height * scale);

            if (width <= 0 || height <= 0)
            {
                return null;
            }

            // For Linear color space, disable the sRGB flag to prevent double gamma conversion
            RenderTextureDescriptor descriptor = new RenderTextureDescriptor(width, height, RenderTextureFormat.ARGB32, 24);
            if (QualitySettings.activeColorSpace == ColorSpace.Linear)
            {
                descriptor.sRGB = false;
            }
            RenderTexture rt = RenderTexture.GetTemporary(descriptor);

            InternalEditorUtilityBridge.CaptureEditorWindow(window, rt);

            RenderTexture previousActive = RenderTexture.active;
            RenderTexture.active = rt;

            Texture2D texture = new Texture2D(width, height, TextureFormat.RGB24, false);
            texture.ReadPixels(new Rect(0, 0, width, height), 0, 0);
            texture.Apply();

            RenderTexture.active = previousActive;
            RenderTexture.ReleaseTemporary(rt);

            if (!Mathf.Approximately(resolutionScale, 1.0f))
            {
                texture = ApplyResolutionScaling(texture, resolutionScale);
            }

            return texture;
        }

        /// <summary>
        /// Get a list of all open EditorWindow names.
        /// </summary>
        /// <returns>Array of window names</returns>
        public static string[] GetOpenWindowNames()
        {
            EditorWindow[] allWindows = Resources.FindObjectsOfTypeAll<EditorWindow>();
            List<string> names = new List<string>();

            foreach (EditorWindow window in allWindows)
            {
                if (window.titleContent != null && !string.IsNullOrEmpty(window.titleContent.text))
                {
                    names.Add(window.titleContent.text);
                }
            }

            return names.ToArray();
        }

        // Captures game rendering by reading GameView's composited RenderTexture (PlayMode only).
        // Contains all cameras + Screen Space Overlay Canvas, without tab bar or borders.
        // Coordinate mapping to simulate-mouse:
        //   sim_x = image_x / resolutionScale, sim_y = image_y / resolutionScale + YOffset
        // where YOffset = Screen.height - RenderTexture.height (returned in the tuple).
        public static async Task<(Texture2D? texture, int yOffset)> CaptureGameRenderingAsync(float resolutionScale, CancellationToken ct)
        {
            Debug.Assert(UnityEditor.EditorApplication.isPlaying, "CaptureGameRenderingAsync requires PlayMode");

            // Wait for the game camera to complete at least one full render cycle after any state change
            await EditorDelay.DelayFrame(2, ct);

            RenderTexture rt = GameViewBridge.GetRenderTexture();
            if (rt == null)
            {
                Debug.LogWarning("[EditorWindowCaptureUtility] GameView RenderTexture is not available");
                return (null, 0);
            }

            int yOffset = (int)Handles.GetMainGameViewSize().y - rt.height;

            // RenderTexture uses bottom-left origin; flip vertically for standard top-left image format
            RenderTextureDescriptor flipDescriptor = new RenderTextureDescriptor(rt.width, rt.height, rt.format, 0);
            if (QualitySettings.activeColorSpace == ColorSpace.Linear)
            {
                flipDescriptor.sRGB = false;
            }
            RenderTexture flipped = RenderTexture.GetTemporary(flipDescriptor);
            Graphics.Blit(rt, flipped, new Vector2(1f, -1f), new Vector2(0f, 1f));

            RenderTexture previousActive = RenderTexture.active;
            RenderTexture.active = flipped;

            Texture2D texture = new Texture2D(rt.width, rt.height, TextureFormat.RGB24, false);
            texture.ReadPixels(new Rect(0, 0, rt.width, rt.height), 0, 0);
            texture.Apply();

            RenderTexture.active = previousActive;
            RenderTexture.ReleaseTemporary(flipped);

            if (!Mathf.Approximately(resolutionScale, 1.0f))
            {
                texture = ApplyResolutionScaling(texture, resolutionScale);
            }

            return (texture, yOffset);
        }

        private static Texture2D ApplyResolutionScaling(Texture2D originalTexture, float scale)
        {
            int newWidth = Mathf.RoundToInt(originalTexture.width * scale);
            int newHeight = Mathf.RoundToInt(originalTexture.height * scale);

            Texture2D scaledTexture = new Texture2D(newWidth, newHeight, originalTexture.format, false);

            RenderTexture rt = RenderTexture.GetTemporary(newWidth, newHeight);
            Graphics.Blit(originalTexture, rt);

            RenderTexture.active = rt;
            scaledTexture.ReadPixels(new Rect(0, 0, newWidth, newHeight), 0, 0);
            scaledTexture.Apply();
            RenderTexture.active = null;

            RenderTexture.ReleaseTemporary(rt);
            UnityEngine.Object.DestroyImmediate(originalTexture);

            return scaledTexture;
        }
    }
}

