using System.Runtime.InteropServices;

namespace Olive.Services;

internal static class PasteShortcutService
{
  private const ushort ControlKey = 0x11;
  private const ushort VKey = 0x56;
  private const uint KeyUp = 0x0002;
  private const uint InputKeyboard = 1;
  private const int RestoreWindow = 9;
  private static readonly TimeSpan FocusSettleDelay = TimeSpan.FromMilliseconds(450);

  public static nint GetForegroundWindowHandle()
  {
    return GetForegroundWindow();
  }

  public static async Task SendPasteAfterRestoringFocusAsync(nint targetWindow, CancellationToken cancellationToken)
  {
    RestoreTargetWindow(targetWindow);
    await Task.Delay(FocusSettleDelay, cancellationToken).ConfigureAwait(false);
    SendKeyChord(ControlKey, VKey);
  }

  private static void RestoreTargetWindow(nint targetWindow)
  {
    if (targetWindow == 0 || !IsWindow(targetWindow)) return;

    if (IsIconic(targetWindow)) ShowWindow(targetWindow, RestoreWindow);
    SetForegroundWindow(targetWindow);
  }

  private static void SendKeyChord(ushort modifierKey, ushort key)
  {
    var inputs = new[]
    {
      KeyDown(modifierKey),
      KeyDown(key),
      KeyUpInput(key),
      KeyUpInput(modifierKey)
    };

    var sent = SendInput((uint)inputs.Length, inputs, Marshal.SizeOf<Input>());
    if (sent != inputs.Length) throw new InvalidOperationException("Could not send Ctrl+V to the active app.");
  }

  private static Input KeyDown(ushort key)
  {
    return new Input
    {
      Type = InputKeyboard,
      Data = new InputUnion { Keyboard = new KeyboardInput { VirtualKey = key } }
    };
  }

  private static Input KeyUpInput(ushort key)
  {
    return new Input
    {
      Type = InputKeyboard,
      Data = new InputUnion { Keyboard = new KeyboardInput { VirtualKey = key, Flags = KeyUp } }
    };
  }

  [DllImport("user32.dll", SetLastError = true)]
  private static extern uint SendInput(uint inputCount, Input[] inputs, int inputSize);

  [DllImport("user32.dll")]
  private static extern nint GetForegroundWindow();

  [DllImport("user32.dll")]
  private static extern bool IsWindow(nint windowHandle);

  [DllImport("user32.dll")]
  private static extern bool IsIconic(nint windowHandle);

  [DllImport("user32.dll")]
  private static extern bool ShowWindow(nint windowHandle, int commandShow);

  [DllImport("user32.dll")]
  private static extern bool SetForegroundWindow(nint windowHandle);

  [StructLayout(LayoutKind.Sequential)]
  private struct Input
  {
    public uint Type;
    public InputUnion Data;
  }

  [StructLayout(LayoutKind.Explicit)]
  private struct InputUnion
  {
    [FieldOffset(0)] public MouseInput Mouse;

    [FieldOffset(0)] public KeyboardInput Keyboard;
  }

  [StructLayout(LayoutKind.Sequential)]
  private struct MouseInput
  {
    public int X;
    public int Y;
    public uint MouseData;
    public uint Flags;
    public uint Time;
    public nint ExtraInfo;
  }

  [StructLayout(LayoutKind.Sequential)]
  private struct KeyboardInput
  {
    public ushort VirtualKey;
    public ushort ScanCode;
    public uint Flags;
    public uint Time;
    public nint ExtraInfo;
  }
}
