using System;
using System.Collections.Generic;
using System.Drawing;
using System.Runtime.InteropServices;
using System.Text;
using System.Windows.Forms;

namespace MusicBeePlugin.Clouseau.Introspection
{
    /// <summary>
    /// Helper class for UI spy functionality.
    /// Provides Win32 API wrappers and control introspection utilities.
    /// </summary>
    public static class SpyHelper
    {
        #region Win32 API Imports

        [DllImport("user32.dll")]
        private static extern bool GetCursorPos(out POINT lpPoint);

        [DllImport("user32.dll")]
        private static extern IntPtr WindowFromPoint(POINT point);

        [DllImport("user32.dll", CharSet = CharSet.Auto)]
        private static extern int GetClassName(IntPtr hWnd, StringBuilder lpClassName, int nMaxCount);

        [DllImport("user32.dll", CharSet = CharSet.Auto)]
        private static extern int GetWindowText(IntPtr hWnd, StringBuilder lpString, int nMaxCount);

        [DllImport("user32.dll")]
        private static extern bool GetWindowRect(IntPtr hWnd, out RECT lpRect);

        [DllImport("user32.dll")]
        private static extern IntPtr GetParent(IntPtr hWnd);

        [DllImport("user32.dll")]
        private static extern int GetWindowLong(IntPtr hWnd, int nIndex);

        [DllImport("user32.dll")]
        private static extern bool IsWindowVisible(IntPtr hWnd);

        [DllImport("user32.dll")]
        private static extern bool IsWindowEnabled(IntPtr hWnd);

        [DllImport("user32.dll")]
        private static extern IntPtr GetAncestor(IntPtr hwnd, uint gaFlags);

        [DllImport("user32.dll")]
        private static extern uint GetWindowThreadProcessId(IntPtr hWnd, out uint processId);

        [DllImport("user32.dll")]
        private static extern bool ScreenToClient(IntPtr hWnd, ref POINT lpPoint);

        [DllImport("user32.dll")]
        private static extern IntPtr ChildWindowFromPointEx(IntPtr hwndParent, POINT pt, uint uFlags);

        private const int GWL_STYLE = -16;
        private const int GWL_EXSTYLE = -20;
        private const uint GA_ROOT = 2;
        private const uint CWP_SKIPINVISIBLE = 0x0001;
        private const uint CWP_SKIPDISABLED = 0x0002;
        private const uint CWP_SKIPTRANSPARENT = 0x0004;

        [StructLayout(LayoutKind.Sequential)]
        public struct POINT
        {
            public int X;
            public int Y;

            public POINT(int x, int y)
            {
                X = x;
                Y = y;
            }

            public static implicit operator Point(POINT p) => new Point(p.X, p.Y);
            public static implicit operator POINT(Point p) => new POINT(p.X, p.Y);
        }

        [StructLayout(LayoutKind.Sequential)]
        public struct RECT
        {
            public int Left;
            public int Top;
            public int Right;
            public int Bottom;

            public Rectangle ToRectangle() => Rectangle.FromLTRB(Left, Top, Right, Bottom);
        }

        #endregion

        #region Public Methods

        /// <summary>
        /// Gets the current cursor position in screen coordinates.
        /// </summary>
        public static Point GetCursorPosition()
        {
            GetCursorPos(out POINT point);
            return point;
        }

        /// <summary>
        /// Gets the window handle at the specified screen point.
        /// </summary>
        public static IntPtr GetWindowAtPoint(Point screenPoint)
        {
            return WindowFromPoint(new POINT(screenPoint.X, screenPoint.Y));
        }

        /// <summary>
        /// Gets the deepest child window at the specified screen point.
        /// </summary>
        public static IntPtr GetDeepestWindowAtPoint(Point screenPoint)
        {
            IntPtr hwnd = WindowFromPoint(new POINT(screenPoint.X, screenPoint.Y));
            if (hwnd == IntPtr.Zero)
                return IntPtr.Zero;

            // Try to find the deepest child
            IntPtr lastChild = hwnd;
            while (true)
            {
                POINT clientPoint = new POINT(screenPoint.X, screenPoint.Y);
                ScreenToClient(lastChild, ref clientPoint);

                IntPtr child = ChildWindowFromPointEx(lastChild, clientPoint,
                    CWP_SKIPINVISIBLE | CWP_SKIPTRANSPARENT);

                if (child == IntPtr.Zero || child == lastChild)
                    break;

                lastChild = child;
            }

            return lastChild;
        }

        /// <summary>
        /// Gets the .NET Control at the specified screen point, if any.
        /// </summary>
        public static Control GetControlAtPoint(Point screenPoint)
        {
            IntPtr hwnd = GetDeepestWindowAtPoint(screenPoint);
            if (hwnd == IntPtr.Zero)
                return null;

            return Control.FromHandle(hwnd);
        }

        /// <summary>
        /// Gets detailed information about a window from its handle.
        /// </summary>
        public static SpyControlInfo GetControlInfo(IntPtr hwnd)
        {
            if (hwnd == IntPtr.Zero)
                return null;

            var info = new SpyControlInfo
            {
                Handle = hwnd
            };

            // Get class name
            var classNameBuilder = new StringBuilder(256);
            GetClassName(hwnd, classNameBuilder, 256);
            info.ClassName = classNameBuilder.ToString();

            // Get window text
            var windowTextBuilder = new StringBuilder(256);
            GetWindowText(hwnd, windowTextBuilder, 256);
            info.WindowText = windowTextBuilder.ToString();

            // Get bounds
            if (GetWindowRect(hwnd, out RECT rect))
            {
                info.ScreenBounds = rect.ToRectangle();
            }

            // Get visibility and enabled state
            info.IsVisible = IsWindowVisible(hwnd);
            info.IsEnabled = IsWindowEnabled(hwnd);

            // Get styles
            info.Style = GetWindowLong(hwnd, GWL_STYLE);
            info.ExStyle = GetWindowLong(hwnd, GWL_EXSTYLE);

            // Get process info
            GetWindowThreadProcessId(hwnd, out uint processId);
            info.ProcessId = (int)processId;

            // Try to get .NET control info
            Control control = Control.FromHandle(hwnd);
            if (control != null)
            {
                info.IsManagedControl = true;
                info.ControlType = control.GetType().FullName;
                info.ControlName = control.Name;
                info.ManagedControl = control;

                // Get client bounds
                info.ClientBounds = control.ClientRectangle;

                // Get text from control
                if (string.IsNullOrEmpty(info.WindowText))
                {
                    info.WindowText = control.Text;
                }
            }

            return info;
        }

        /// <summary>
        /// Gets the parent chain for a window handle.
        /// </summary>
        public static List<SpyControlInfo> GetParentChain(IntPtr hwnd)
        {
            var chain = new List<SpyControlInfo>();

            IntPtr current = hwnd;
            while (current != IntPtr.Zero)
            {
                var info = GetControlInfo(current);
                if (info != null)
                {
                    chain.Add(info);
                }
                current = GetParent(current);
            }

            return chain;
        }

        /// <summary>
        /// Gets the parent chain for a .NET Control.
        /// </summary>
        public static List<string> GetParentChain(Control control)
        {
            var chain = new List<string>();

            Control current = control;
            while (current != null)
            {
                var name = string.IsNullOrEmpty(current.Name)
                    ? $"[{current.GetType().Name}]"
                    : current.Name;
                chain.Add($"{name} ({current.GetType().Name})");
                current = current.Parent;
            }

            return chain;
        }

        /// <summary>
        /// Gets the root window for a given handle.
        /// </summary>
        public static IntPtr GetRootWindow(IntPtr hwnd)
        {
            return GetAncestor(hwnd, GA_ROOT);
        }

        /// <summary>
        /// Checks if a handle belongs to the specified process.
        /// </summary>
        public static bool BelongsToProcess(IntPtr hwnd, int processId)
        {
            GetWindowThreadProcessId(hwnd, out uint pid);
            return pid == processId;
        }

        /// <summary>
        /// Finds all controls of a specific type under a parent control.
        /// </summary>
        public static IEnumerable<T> FindControls<T>(Control parent) where T : Control
        {
            foreach (Control child in parent.Controls)
            {
                if (child is T typedChild)
                {
                    yield return typedChild;
                }

                foreach (var nested in FindControls<T>(child))
                {
                    yield return nested;
                }
            }
        }

        /// <summary>
        /// Gets a formatted string description of control info.
        /// </summary>
        public static string FormatControlInfo(SpyControlInfo info)
        {
            if (info == null)
                return "No control";

            var sb = new StringBuilder();

            if (info.IsManagedControl)
            {
                sb.AppendLine($"Type: {info.ControlType}");
                if (!string.IsNullOrEmpty(info.ControlName))
                    sb.AppendLine($"Name: {info.ControlName}");
            }
            else
            {
                sb.AppendLine($"Class: {info.ClassName}");
            }

            if (!string.IsNullOrEmpty(info.WindowText) && info.WindowText.Length < 50)
                sb.AppendLine($"Text: {info.WindowText}");

            sb.AppendLine($"Bounds: {info.ScreenBounds.X}, {info.ScreenBounds.Y} - {info.ScreenBounds.Width}x{info.ScreenBounds.Height}");
            sb.AppendLine($"Visible: {info.IsVisible}  Enabled: {info.IsEnabled}");
            sb.AppendLine($"Handle: 0x{info.Handle.ToInt64():X8}");

            return sb.ToString();
        }

        #endregion
    }

    /// <summary>
    /// Contains detailed information about a UI control/window.
    /// </summary>
    public class SpyControlInfo
    {
        /// <summary>Window handle.</summary>
        public IntPtr Handle { get; set; }

        /// <summary>Win32 class name.</summary>
        public string ClassName { get; set; }

        /// <summary>Window text/caption.</summary>
        public string WindowText { get; set; }

        /// <summary>Bounds in screen coordinates.</summary>
        public Rectangle ScreenBounds { get; set; }

        /// <summary>Client area bounds.</summary>
        public Rectangle ClientBounds { get; set; }

        /// <summary>Whether the window is visible.</summary>
        public bool IsVisible { get; set; }

        /// <summary>Whether the window is enabled.</summary>
        public bool IsEnabled { get; set; }

        /// <summary>Window style flags.</summary>
        public int Style { get; set; }

        /// <summary>Extended window style flags.</summary>
        public int ExStyle { get; set; }

        /// <summary>Process ID owning the window.</summary>
        public int ProcessId { get; set; }

        /// <summary>Whether this is a .NET managed control.</summary>
        public bool IsManagedControl { get; set; }

        /// <summary>Full type name if managed.</summary>
        public string ControlType { get; set; }

        /// <summary>Control.Name if managed.</summary>
        public string ControlName { get; set; }

        /// <summary>Reference to the managed control if available.</summary>
        public Control ManagedControl { get; set; }

        /// <summary>
        /// Gets a display-friendly type name.
        /// </summary>
        public string DisplayTypeName
        {
            get
            {
                if (IsManagedControl && !string.IsNullOrEmpty(ControlType))
                {
                    var parts = ControlType.Split('.');
                    return parts[parts.Length - 1];
                }
                return ClassName;
            }
        }

        /// <summary>
        /// Gets a display-friendly name.
        /// </summary>
        public string DisplayName
        {
            get
            {
                if (!string.IsNullOrEmpty(ControlName))
                    return ControlName;
                if (!string.IsNullOrEmpty(WindowText) && WindowText.Length < 30)
                    return WindowText;
                return $"[{DisplayTypeName}]";
            }
        }

        public override string ToString()
        {
            return $"{DisplayName} ({DisplayTypeName}) at {ScreenBounds.Location}";
        }
    }
}
