using System.Runtime.InteropServices;
using Microsoft.UI.Xaml;
using WinRT.Interop;

namespace YMhut.Box.WinUI.Services;

public sealed class WindowMovePerformanceHelper : IDisposable
{
    private const uint WmEnterSizeMove = 0x0231;
    private const uint WmExitSizeMove = 0x0232;
    private const uint WmNcDestroy = 0x0082;

    private static int s_nextSubclassId;

    private readonly Window _window;
    private readonly Func<string?> _backdropProvider;
    private readonly Action<bool>? _moveStateChanged;
    private readonly SubclassProc _subclassProc;
    private readonly nuint _subclassId;
    private readonly nint _hwnd;
    private bool _subclassInstalled;
    private bool _disposed;
    private bool _inMoveLoop;

    private WindowMovePerformanceHelper(
        Window window,
        Func<string?>? backdropProvider,
        Action<bool>? moveStateChanged)
    {
        _window = window;
        _backdropProvider = backdropProvider ?? (() => "mica");
        _moveStateChanged = moveStateChanged;
        _subclassProc = WindowSubclassProc;
        _subclassId = (nuint)Interlocked.Increment(ref s_nextSubclassId);
        _hwnd = WindowNative.GetWindowHandle(window);

        if (_hwnd != 0)
        {
            _subclassInstalled = SetWindowSubclass(_hwnd, _subclassProc, _subclassId, 0);
        }

        window.Closed += Window_Closed;
    }

    public static WindowMovePerformanceHelper Attach(
        Window window,
        Func<string?>? backdropProvider = null,
        Action<bool>? moveStateChanged = null)
    {
        return new WindowMovePerformanceHelper(window, backdropProvider, moveStateChanged);
    }

    public void Dispose()
    {
        if (_disposed)
        {
            return;
        }

        _disposed = true;
        _window.Closed -= Window_Closed;
        if (_inMoveLoop)
        {
            EndMoveLoop();
        }

        if (_subclassInstalled && _hwnd != 0)
        {
            RemoveWindowSubclass(_hwnd, _subclassProc, _subclassId);
            _subclassInstalled = false;
        }
    }

    private nint WindowSubclassProc(nint hwnd, uint msg, nint wParam, nint lParam, nuint subclassId, nint refData)
    {
        try
        {
            switch (msg)
            {
                case WmEnterSizeMove:
                    BeginMoveLoop();
                    break;
                case WmExitSizeMove:
                    EndMoveLoop();
                    break;
                case WmNcDestroy:
                    Dispose();
                    break;
            }
        }
        catch (Exception exception)
        {
            CrashLog.Write(exception);
        }

        return DefSubclassProc(hwnd, msg, wParam, lParam);
    }

    private void BeginMoveLoop()
    {
        if (_disposed || _inMoveLoop)
        {
            return;
        }

        _inMoveLoop = true;
        _moveStateChanged?.Invoke(true);
        if (UsesCompositionBackdrop(_backdropProvider()))
        {
            ThemeService.ApplyWindowBackdrop(_window, "solid");
        }
    }

    private void EndMoveLoop()
    {
        if (!_inMoveLoop)
        {
            return;
        }

        _inMoveLoop = false;
        var backdrop = _backdropProvider();
        if (UsesCompositionBackdrop(backdrop))
        {
            ThemeService.ApplyWindowBackdrop(_window, backdrop ?? "mica");
        }

        _moveStateChanged?.Invoke(false);
    }

    private void Window_Closed(object sender, WindowEventArgs args)
    {
        Dispose();
    }

    private static bool UsesCompositionBackdrop(string? value)
    {
        return (value ?? string.Empty).Trim().ToLowerInvariant() is not ("solid" or "none");
    }

    private delegate nint SubclassProc(nint hWnd, uint msg, nint wParam, nint lParam, nuint uIdSubclass, nint dwRefData);

    [DllImport("comctl32.dll", SetLastError = true)]
    private static extern bool SetWindowSubclass(nint hWnd, SubclassProc pfnSubclass, nuint uIdSubclass, nint dwRefData);

    [DllImport("comctl32.dll", SetLastError = true)]
    private static extern bool RemoveWindowSubclass(nint hWnd, SubclassProc pfnSubclass, nuint uIdSubclass);

    [DllImport("comctl32.dll")]
    private static extern nint DefSubclassProc(nint hWnd, uint uMsg, nint wParam, nint lParam);
}
