import { renderHook } from '@testing-library/react';
import { usePlayerKeyboardNavigation } from '../usePlayerKeyboardNavigation';

// ---------------------------------------------------------------------------
// Helpers
// ---------------------------------------------------------------------------

const makeContainerRef = () => {
  const el = document.createElement('div');
  document.body.appendChild(el);
  return { current: el as HTMLElement | null };
};

const makeOptions = (overrides: Partial<Parameters<typeof usePlayerKeyboardNavigation>[0]> = {}) => ({
  containerRef:      makeContainerRef(),
  isVertical:        false,
  currentPostType:   'image' as string,
  handleClose:       jest.fn(),
  handleNextPost:    jest.fn(),
  handlePreviousPost: jest.fn(),
  togglePlay:        jest.fn(),
  toggleMute:        jest.fn(),
  ...overrides,
});

/** Fire a synthetic keydown on window. */
const fireKey = (key: string, target?: EventTarget) => {
  const event = new KeyboardEvent('keydown', { key, bubbles: true, cancelable: true });
  if (target) {
    Object.defineProperty(event, 'target', { value: target });
  }
  window.dispatchEvent(event);
  return event;
};

const fireKeyOn = (key: string, el: HTMLElement) => {
  el.focus();
  return fireKey(key);
};

afterEach(() => {
  document.body.innerHTML = '';
  jest.restoreAllMocks();
});

// ---------------------------------------------------------------------------
// Registration / cleanup
// ---------------------------------------------------------------------------

describe('usePlayerKeyboardNavigation – registration', () => {
  it('registers a keydown listener on window', () => {
    const spy = jest.spyOn(window, 'addEventListener');
    const options = makeOptions();

    renderHook(() => usePlayerKeyboardNavigation(options));

    expect(spy).toHaveBeenCalledWith('keydown', expect.any(Function));
  });

  it('removes the keydown listener on unmount', () => {
    const spy = jest.spyOn(window, 'removeEventListener');
    const options = makeOptions();

    const { unmount } = renderHook(() => usePlayerKeyboardNavigation(options));
    unmount();

    expect(spy).toHaveBeenCalledWith('keydown', expect.any(Function));
  });
});

// ---------------------------------------------------------------------------
// Escape
// ---------------------------------------------------------------------------

describe('usePlayerKeyboardNavigation – Escape', () => {
  it('calls handleClose', () => {
    const options = makeOptions();
    renderHook(() => usePlayerKeyboardNavigation(options));

    fireKey('Escape');

    expect(options.handleClose).toHaveBeenCalledTimes(1);
  });

  it('does not call any navigation handler', () => {
    const options = makeOptions();
    renderHook(() => usePlayerKeyboardNavigation(options));

    fireKey('Escape');

    expect(options.handleNextPost).not.toHaveBeenCalled();
    expect(options.handlePreviousPost).not.toHaveBeenCalled();
  });
});

// ---------------------------------------------------------------------------
// Horizontal mode (isVertical = false)
// ---------------------------------------------------------------------------

describe('usePlayerKeyboardNavigation – horizontal mode', () => {
  it('ArrowRight calls handleNextPost', () => {
    const options = makeOptions({ isVertical: false });
    renderHook(() => usePlayerKeyboardNavigation(options));

    fireKey('ArrowRight');

    expect(options.handleNextPost).toHaveBeenCalledTimes(1);
    expect(options.handlePreviousPost).not.toHaveBeenCalled();
  });

  it('ArrowLeft calls handlePreviousPost', () => {
    const options = makeOptions({ isVertical: false });
    renderHook(() => usePlayerKeyboardNavigation(options));

    fireKey('ArrowLeft');

    expect(options.handlePreviousPost).toHaveBeenCalledTimes(1);
    expect(options.handleNextPost).not.toHaveBeenCalled();
  });

  it('ArrowDown does nothing in horizontal mode', () => {
    const options = makeOptions({ isVertical: false });
    renderHook(() => usePlayerKeyboardNavigation(options));

    fireKey('ArrowDown');

    expect(options.handleNextPost).not.toHaveBeenCalled();
  });

  it('ArrowUp does nothing in horizontal mode', () => {
    const options = makeOptions({ isVertical: false });
    renderHook(() => usePlayerKeyboardNavigation(options));

    fireKey('ArrowUp');

    expect(options.handlePreviousPost).not.toHaveBeenCalled();
  });
});

// ---------------------------------------------------------------------------
// Vertical mode (isVertical = true)
// ---------------------------------------------------------------------------

describe('usePlayerKeyboardNavigation – vertical mode', () => {
  it('ArrowDown calls handleNextPost', () => {
    const options = makeOptions({ isVertical: true });
    renderHook(() => usePlayerKeyboardNavigation(options));

    fireKey('ArrowDown');

    expect(options.handleNextPost).toHaveBeenCalledTimes(1);
    expect(options.handlePreviousPost).not.toHaveBeenCalled();
  });

  it('ArrowUp calls handlePreviousPost', () => {
    const options = makeOptions({ isVertical: true });
    renderHook(() => usePlayerKeyboardNavigation(options));

    fireKey('ArrowUp');

    expect(options.handlePreviousPost).toHaveBeenCalledTimes(1);
    expect(options.handleNextPost).not.toHaveBeenCalled();
  });

  it('ArrowRight does nothing in vertical mode', () => {
    const options = makeOptions({ isVertical: true });
    renderHook(() => usePlayerKeyboardNavigation(options));

    fireKey('ArrowRight');

    expect(options.handleNextPost).not.toHaveBeenCalled();
  });

  it('ArrowLeft does nothing in vertical mode', () => {
    const options = makeOptions({ isVertical: true });
    renderHook(() => usePlayerKeyboardNavigation(options));

    fireKey('ArrowLeft');

    expect(options.handlePreviousPost).not.toHaveBeenCalled();
  });
});

// ---------------------------------------------------------------------------
// Video-only shortcuts (Space, k, m)
// ---------------------------------------------------------------------------

describe('usePlayerKeyboardNavigation – video shortcuts', () => {
  it('Space calls togglePlay for a video post', () => {
    const options = makeOptions({ currentPostType: 'video' });
    renderHook(() => usePlayerKeyboardNavigation(options));

    fireKey(' ');

    expect(options.togglePlay).toHaveBeenCalledTimes(1);
  });

  it('k calls togglePlay for a video post', () => {
    const options = makeOptions({ currentPostType: 'video' });
    renderHook(() => usePlayerKeyboardNavigation(options));

    fireKey('k');

    expect(options.togglePlay).toHaveBeenCalledTimes(1);
  });

  it('m calls toggleMute for a video post', () => {
    const options = makeOptions({ currentPostType: 'video' });
    renderHook(() => usePlayerKeyboardNavigation(options));

    fireKey('m');

    expect(options.toggleMute).toHaveBeenCalledTimes(1);
  });

  it('Space does NOT call togglePlay for a non-video post', () => {
    const options = makeOptions({ currentPostType: 'image' });
    renderHook(() => usePlayerKeyboardNavigation(options));

    fireKey(' ');

    expect(options.togglePlay).not.toHaveBeenCalled();
  });

  it('k does NOT call togglePlay for a non-video post', () => {
    const options = makeOptions({ currentPostType: 'image' });
    renderHook(() => usePlayerKeyboardNavigation(options));

    fireKey('k');

    expect(options.togglePlay).not.toHaveBeenCalled();
  });

  it('m does NOT call toggleMute for a non-video post', () => {
    const options = makeOptions({ currentPostType: 'image' });
    renderHook(() => usePlayerKeyboardNavigation(options));

    fireKey('m');

    expect(options.toggleMute).not.toHaveBeenCalled();
  });
});

// ---------------------------------------------------------------------------
// Interactive-element guard (Space / Enter on buttons/links)
// ---------------------------------------------------------------------------

describe('usePlayerKeyboardNavigation – interactive element guard', () => {
  const interactiveTags = ['button', 'a'] as const;

  it.each(interactiveTags)(
    'Space on a focused <%s> does not call togglePlay',
    (tag) => {
      const options = makeOptions({ currentPostType: 'video' });
      renderHook(() => usePlayerKeyboardNavigation(options));

      const el = document.createElement(tag);
      // <a> without href is not focusable in JSDOM; href makes it a real link.
      if (tag === 'a') (el as HTMLAnchorElement).href = '#';
      document.body.appendChild(el);
      fireKeyOn(' ', el);

      expect(options.togglePlay).not.toHaveBeenCalled();
    },
  );

  it.each(interactiveTags)(
    'Enter on a focused <%s> does not call handleClose',
    (tag) => {
      // Enter is not mapped to handleClose, but we verify the guard doesn't
      // accidentally suppress unrelated keys that happen to match the guard condition.
      const options = makeOptions({ currentPostType: 'video' });
      renderHook(() => usePlayerKeyboardNavigation(options));

      const el = document.createElement(tag);
      document.body.appendChild(el);
      fireKeyOn('Enter', el);

      // handleClose is only triggered by Escape, not Enter
      expect(options.handleClose).not.toHaveBeenCalled();
    },
  );

  it('Space on the container itself (non-interactive) still calls togglePlay', () => {
    const options = makeOptions({ currentPostType: 'video' });
    renderHook(() => usePlayerKeyboardNavigation(options));

    // container is not matched by the interactive selector, so Space should fire
    fireKeyOn(' ', options.containerRef.current!);

    expect(options.togglePlay).toHaveBeenCalledTimes(1);
  });
});

// ---------------------------------------------------------------------------
// Unrelated keys
// ---------------------------------------------------------------------------

describe('usePlayerKeyboardNavigation – unrelated keys', () => {
  it('does not call any handler for Tab', () => {
    const options = makeOptions();
    renderHook(() => usePlayerKeyboardNavigation(options));

    fireKey('Tab');

    expect(options.handleClose).not.toHaveBeenCalled();
    expect(options.handleNextPost).not.toHaveBeenCalled();
    expect(options.handlePreviousPost).not.toHaveBeenCalled();
    expect(options.togglePlay).not.toHaveBeenCalled();
    expect(options.toggleMute).not.toHaveBeenCalled();
  });
});
