// @vitest-environment happy-dom
import { describe, expect, it, vi, beforeEach } from 'vitest';
import { renderHook } from '@testing-library/react';
import { useKeyboardShortcuts } from '../index.js';
import type { ShortcutDef } from '../index.js';

function fireKey(key: string, opts: Partial<KeyboardEventInit> = {}) {
  const event = new KeyboardEvent('keydown', {
    key,
    bubbles: true,
    cancelable: true,
    ...opts,
  });
  window.dispatchEvent(event);
}

beforeEach(() => {
  vi.clearAllMocks();
});

describe('useKeyboardShortcuts', () => {
  it('fires handler on matching key combo', () => {
    const handler = vi.fn();
    const shortcuts: ShortcutDef[] = [{ key: 'k', meta: true, handler }];

    renderHook(() => useKeyboardShortcuts(shortcuts));
    fireKey('k', { metaKey: true });

    expect(handler).toHaveBeenCalledTimes(1);
  });

  it('does not fire without matching meta modifier', () => {
    const handler = vi.fn();
    const shortcuts: ShortcutDef[] = [{ key: 'k', meta: true, handler }];

    renderHook(() => useKeyboardShortcuts(shortcuts));
    fireKey('k'); // no meta

    expect(handler).not.toHaveBeenCalled();
  });

  it('respects shift modifier', () => {
    const handler = vi.fn();
    const shortcuts: ShortcutDef[] = [{ key: 's', meta: true, shift: true, handler }];

    renderHook(() => useKeyboardShortcuts(shortcuts));

    // Without shift — should NOT fire
    fireKey('s', { metaKey: true });
    expect(handler).not.toHaveBeenCalled();

    // With shift — should fire
    fireKey('s', { metaKey: true, shiftKey: true });
    expect(handler).toHaveBeenCalledTimes(1);
  });

  it('respects alt modifier', () => {
    const handler = vi.fn();
    const shortcuts: ShortcutDef[] = [{ key: 'p', alt: true, handler }];

    renderHook(() => useKeyboardShortcuts(shortcuts));

    fireKey('p'); // no alt
    expect(handler).not.toHaveBeenCalled();

    fireKey('p', { altKey: true });
    expect(handler).toHaveBeenCalledTimes(1);
  });

  it('skips handler when focus is in input (allowInInput: false)', () => {
    const handler = vi.fn();
    const shortcuts: ShortcutDef[] = [{ key: 'n', meta: true, handler }];

    renderHook(() => useKeyboardShortcuts(shortcuts));

    // Simulate focus in an input element
    const input = document.createElement('input');
    document.body.appendChild(input);
    input.focus();

    const event = new KeyboardEvent('keydown', {
      key: 'n',
      metaKey: true,
      bubbles: true,
      cancelable: true,
    });
    input.dispatchEvent(event);

    expect(handler).not.toHaveBeenCalled();

    document.body.removeChild(input);
  });

  it('fires handler in input when allowInInput: true', () => {
    const handler = vi.fn();
    const shortcuts: ShortcutDef[] = [{ key: 'k', meta: true, handler, allowInInput: true }];

    renderHook(() => useKeyboardShortcuts(shortcuts));

    const input = document.createElement('input');
    document.body.appendChild(input);
    input.focus();

    const event = new KeyboardEvent('keydown', {
      key: 'k',
      metaKey: true,
      bubbles: true,
      cancelable: true,
    });
    input.dispatchEvent(event);

    expect(handler).toHaveBeenCalledTimes(1);

    document.body.removeChild(input);
  });

  it('skips handler when focus is in textarea', () => {
    const handler = vi.fn();
    const shortcuts: ShortcutDef[] = [{ key: 'n', meta: true, handler }];

    renderHook(() => useKeyboardShortcuts(shortcuts));

    const textarea = document.createElement('textarea');
    document.body.appendChild(textarea);
    textarea.focus();

    const event = new KeyboardEvent('keydown', {
      key: 'n',
      metaKey: true,
      bubbles: true,
      cancelable: true,
    });
    textarea.dispatchEvent(event);

    expect(handler).not.toHaveBeenCalled();

    document.body.removeChild(textarea);
  });

  it('handles multiple shortcuts simultaneously', () => {
    const searchHandler = vi.fn();
    const newHandler = vi.fn();
    const escHandler = vi.fn();

    const shortcuts: ShortcutDef[] = [
      { key: 'k', meta: true, handler: searchHandler },
      { key: 'n', meta: true, handler: newHandler },
      { key: 'Escape', handler: escHandler },
    ];

    renderHook(() => useKeyboardShortcuts(shortcuts));

    fireKey('k', { metaKey: true });
    expect(searchHandler).toHaveBeenCalledTimes(1);
    expect(newHandler).not.toHaveBeenCalled();

    fireKey('n', { metaKey: true });
    expect(newHandler).toHaveBeenCalledTimes(1);

    fireKey('Escape');
    expect(escHandler).toHaveBeenCalledTimes(1);
  });

  it('cleans up event listener on unmount', () => {
    const handler = vi.fn();
    const shortcuts: ShortcutDef[] = [{ key: 'k', meta: true, handler }];

    const { unmount } = renderHook(() => useKeyboardShortcuts(shortcuts));
    unmount();

    fireKey('k', { metaKey: true });
    expect(handler).not.toHaveBeenCalled();
  });

  it('handles empty shortcuts array without errors', () => {
    expect(() => {
      renderHook(() => useKeyboardShortcuts([]));
    }).not.toThrow();
  });

  it('matches keys case-insensitively', () => {
    const handler = vi.fn();
    const shortcuts: ShortcutDef[] = [{ key: 'K', meta: true, handler }];

    renderHook(() => useKeyboardShortcuts(shortcuts));
    fireKey('k', { metaKey: true });

    expect(handler).toHaveBeenCalledTimes(1);
  });

  it('fires on ctrlKey as well as metaKey when meta: true', () => {
    const handler = vi.fn();
    const shortcuts: ShortcutDef[] = [{ key: 'k', meta: true, handler }];

    renderHook(() => useKeyboardShortcuts(shortcuts));
    fireKey('k', { ctrlKey: true });

    expect(handler).toHaveBeenCalledTimes(1);
  });

  it('does not fire Cmd+K shortcut when Shift is also pressed', () => {
    const handler = vi.fn();
    const shortcuts: ShortcutDef[] = [{ key: 'k', meta: true, handler }];

    renderHook(() => useKeyboardShortcuts(shortcuts));
    fireKey('k', { metaKey: true, shiftKey: true });

    expect(handler).not.toHaveBeenCalled();
  });

  it('does not fire Cmd+K shortcut when Alt is also pressed', () => {
    const handler = vi.fn();
    const shortcuts: ShortcutDef[] = [{ key: 'k', meta: true, handler }];

    renderHook(() => useKeyboardShortcuts(shortcuts));
    fireKey('k', { metaKey: true, altKey: true });

    expect(handler).not.toHaveBeenCalled();
  });

  it('does not fire bare-key shortcut when meta is pressed', () => {
    const handler = vi.fn();
    const shortcuts: ShortcutDef[] = [{ key: 'Escape', handler }];

    renderHook(() => useKeyboardShortcuts(shortcuts));
    fireKey('Escape', { metaKey: true });

    expect(handler).not.toHaveBeenCalled();
  });

  it('does not fire bare-key shortcut when shift is pressed', () => {
    const handler = vi.fn();
    const shortcuts: ShortcutDef[] = [{ key: '/', handler }];

    renderHook(() => useKeyboardShortcuts(shortcuts));
    fireKey('/', { shiftKey: true });

    expect(handler).not.toHaveBeenCalled();
  });
});
