/* @vitest-environment jsdom */

import { act, fireEvent, render, screen } from '@testing-library/react';
import { useRef, useState } from 'react';
import { describe, expect, it, vi } from 'vitest';

import type {
  CanvasTool,
  GenerateDialogState,
  QuickEditPanelState,
} from './ImageCanvasEditorTypes';
import { useImageCanvasKeyboardShortcuts } from './useImageCanvasKeyboardShortcuts';

function createPlaceholderDialog(
  mode: GenerateDialogState['mode'] = 'generate',
): GenerateDialogState {
  return {
    id: 'dialog-1',
    mode,
    prompt: '生成一张图片',
    status: 'idle',
    composerOpen: true,
    placeholder: {
      x: 10,
      y: 20,
      width: 320,
      height: 240,
      originalWidth: 320,
      originalHeight: 240,
    },
  };
}

function applyGenerateDialogUpdater(
  updater:
    | GenerateDialogState
    | null
    | ((dialog: GenerateDialogState | null) => GenerateDialogState | null),
  currentDialog: GenerateDialogState | null,
) {
  return typeof updater === 'function' ? updater(currentDialog) : updater;
}

function applyQuickEditPanelUpdater(
  updater:
    | QuickEditPanelState
    | null
    | ((panel: QuickEditPanelState | null) => QuickEditPanelState | null),
  currentPanel: QuickEditPanelState | null,
) {
  return typeof updater === 'function' ? updater(currentPanel) : updater;
}

function KeyboardShortcutsHarness({
  selectedLayerId = null,
  initialGenerateDialog = null,
  initialQuickEditPanel = null,
  initialTool = 'select',
  undoCanvasChange = vi.fn(),
  redoCanvasChange = vi.fn(),
  deleteLayerById = vi.fn(),
  closeEditorChromePanels = vi.fn(),
}: {
  selectedLayerId?: string | null;
  initialGenerateDialog?: GenerateDialogState | null;
  initialQuickEditPanel?: QuickEditPanelState | null;
  initialTool?: CanvasTool;
  undoCanvasChange?: () => void;
  redoCanvasChange?: () => void;
  deleteLayerById?: (layerId: string | null) => void;
  closeEditorChromePanels?: () => void;
}) {
  const [activeTool, setActiveTool] = useState<CanvasTool>(initialTool);
  const [generateDialog, setGenerateDialogState] =
    useState<GenerateDialogState | null>(initialGenerateDialog);
  const [quickEditPanel, setQuickEditPanelState] =
    useState<QuickEditPanelState | null>(initialQuickEditPanel);
  const [imageContextMenuOpen, setImageContextMenuOpen] = useState(true);
  const [contextMenuOpen, setContextMenuOpen] = useState(true);
  const [isSpecMenuOpen, setIsSpecMenuOpen] = useState(true);
  const [isCharacterSpecMenuOpen, setIsCharacterSpecMenuOpen] = useState(true);
  const [isCharacterReferenceMenuOpen, setIsCharacterReferenceMenuOpen] =
    useState(true);
  const [isPickingCharacterSpecFromCanvas, setIsPickingCharacterSpecFromCanvas] =
    useState(true);
  const [
    isPickingCharacterReferenceFromCanvas,
    setIsPickingCharacterReferenceFromCanvas,
  ] = useState(true);
  const [isIconSpecMenuOpen, setIsIconSpecMenuOpen] = useState(true);
  const [isPickingIconSpecFromCanvas, setIsPickingIconSpecFromCanvas] =
    useState(true);
  const [isSpacePanning, setIsSpacePanning] = useState(false);
  const [shiftPressed, setShiftPressed] = useState(false);
  const generateDialogRef = useRef<GenerateDialogState | null>(generateDialog);
  const selectedLayerIdRef = useRef<string | null>(selectedLayerId);
  generateDialogRef.current = generateDialog;
  selectedLayerIdRef.current = selectedLayerId;

  useImageCanvasKeyboardShortcuts({
    generateDialogRef,
    selectedLayerIdRef,
    redoCanvasChange,
    undoCanvasChange,
    deleteLayerById,
    setActiveTool: (tool) => setActiveTool(tool),
    setGenerateDialog: (updater) =>
      setGenerateDialogState((currentDialog) =>
        applyGenerateDialogUpdater(updater, currentDialog),
      ),
    setImageContextMenu: () => setImageContextMenuOpen(false),
    setContextMenu: () => setContextMenuOpen(false),
    setQuickEditPanel: (updater) =>
      setQuickEditPanelState((currentPanel) =>
        applyQuickEditPanelUpdater(updater, currentPanel),
      ),
    closeEditorChromePanels,
    setIsSpecMenuOpen,
    setIsCharacterSpecMenuOpen,
    setIsCharacterReferenceMenuOpen,
    setIsPickingCharacterSpecFromCanvas,
    setIsPickingCharacterReferenceFromCanvas,
    setIsIconSpecMenuOpen,
    setIsPickingIconSpecFromCanvas,
    setIsSpacePanning,
    setShiftPressed,
  });

  return (
    <div>
      <input aria-label="快捷键输入框" />
      <span data-testid="active-tool">{activeTool}</span>
      <span data-testid="generate-dialog">
        {generateDialog
          ? `${generateDialog.mode}:${generateDialog.status}:${String(
              generateDialog.composerOpen,
            )}`
          : 'none'}
      </span>
      <span data-testid="quick-edit">
        {quickEditPanel ? quickEditPanel.status : 'none'}
      </span>
      <span data-testid="image-context">{String(imageContextMenuOpen)}</span>
      <span data-testid="context-menu">{String(contextMenuOpen)}</span>
      <span data-testid="spec-menu">{String(isSpecMenuOpen)}</span>
      <span data-testid="character-menu">
        {String(isCharacterSpecMenuOpen)}
      </span>
      <span data-testid="character-reference-menu">
        {String(isCharacterReferenceMenuOpen)}
      </span>
      <span data-testid="character-picking">
        {String(isPickingCharacterSpecFromCanvas)}
      </span>
      <span data-testid="character-reference-picking">
        {String(isPickingCharacterReferenceFromCanvas)}
      </span>
      <span data-testid="icon-menu">{String(isIconSpecMenuOpen)}</span>
      <span data-testid="icon-picking">
        {String(isPickingIconSpecFromCanvas)}
      </span>
      <span data-testid="space-panning">{String(isSpacePanning)}</span>
      <span data-testid="shift-pressed">{String(shiftPressed)}</span>
    </div>
  );
}

describe('useImageCanvasKeyboardShortcuts', () => {
  it('routes undo and redo shortcuts while ignoring editable inputs', () => {
    const undoCanvasChange = vi.fn();
    const redoCanvasChange = vi.fn();
    render(
      <KeyboardShortcutsHarness
        undoCanvasChange={undoCanvasChange}
        redoCanvasChange={redoCanvasChange}
      />,
    );

    act(() => {
      fireEvent.keyDown(window, { key: 'z', code: 'KeyZ', ctrlKey: true });
    });
    expect(undoCanvasChange).toHaveBeenCalledTimes(1);

    act(() => {
      fireEvent.keyDown(window, {
        key: 'Z',
        code: 'KeyZ',
        ctrlKey: true,
        shiftKey: true,
      });
    });
    expect(redoCanvasChange).toHaveBeenCalledTimes(1);

    act(() => {
      fireEvent.keyDown(screen.getByLabelText('快捷键输入框'), {
        key: 'z',
        code: 'KeyZ',
        ctrlKey: true,
      });
    });
    expect(undoCanvasChange).toHaveBeenCalledTimes(1);
  });

  it('deletes the selected layer with Backspace outside editable inputs', () => {
    const deleteLayerById = vi.fn();
    render(
      <KeyboardShortcutsHarness
        selectedLayerId="layer-selected"
        deleteLayerById={deleteLayerById}
      />,
    );

    act(() => {
      fireEvent.keyDown(window, { key: 'Backspace', code: 'Backspace' });
    });

    expect(deleteLayerById).toHaveBeenCalledWith('layer-selected');
  });

  it('removes editable generation placeholders with Backspace', () => {
    render(
      <KeyboardShortcutsHarness
        initialTool="character"
        initialGenerateDialog={createPlaceholderDialog('character')}
      />,
    );

    act(() => {
      fireEvent.keyDown(window, { key: 'Backspace', code: 'Backspace' });
    });

    expect(screen.getByTestId('generate-dialog').textContent).toBe('none');
    expect(screen.getByTestId('active-tool').textContent).toBe('select');
    expect(screen.getByTestId('character-menu').textContent).toBe('false');
    expect(screen.getByTestId('character-picking').textContent).toBe('false');
    expect(screen.getByTestId('icon-menu').textContent).toBe('false');
    expect(screen.getByTestId('icon-picking').textContent).toBe('false');
  });

  it('closes transient editor panels with Escape and collapses idle composers', () => {
    const closeEditorChromePanels = vi.fn();
    render(
      <KeyboardShortcutsHarness
        closeEditorChromePanels={closeEditorChromePanels}
        initialGenerateDialog={createPlaceholderDialog('generate')}
        initialQuickEditPanel={{
          sourceLayerId: 'layer-source',
          prompt: '局部修改',
          size: '1024x1024',
          model: 'mock',
          status: 'idle',
        }}
      />,
    );

    act(() => {
      fireEvent.keyDown(window, { key: 'Escape', code: 'Escape' });
    });

    expect(closeEditorChromePanels).toHaveBeenCalledTimes(1);
    expect(screen.getByTestId('generate-dialog').textContent).toBe(
      'generate:idle:false',
    );
    expect(screen.getByTestId('quick-edit').textContent).toBe('none');
    expect(screen.getByTestId('image-context').textContent).toBe('false');
    expect(screen.getByTestId('context-menu').textContent).toBe('false');
    expect(screen.getByTestId('spec-menu').textContent).toBe('false');
    expect(screen.getByTestId('character-menu').textContent).toBe('false');
    expect(screen.getByTestId('icon-menu').textContent).toBe('false');
  });

  it('keeps generating panels open when Escape closes transient chrome', () => {
    const generatingDialog = createPlaceholderDialog('generate');
    generatingDialog.status = 'generating';
    render(
      <KeyboardShortcutsHarness
        initialGenerateDialog={generatingDialog}
        initialQuickEditPanel={{
          sourceLayerId: 'layer-source',
          prompt: '继续修改',
          size: '1024x1024',
          model: 'mock',
          status: 'generating',
        }}
      />,
    );

    act(() => {
      fireEvent.keyDown(window, { key: 'Escape', code: 'Escape' });
    });

    expect(screen.getByTestId('generate-dialog').textContent).toBe(
      'generate:generating:true',
    );
    expect(screen.getByTestId('quick-edit').textContent).toBe('generating');
  });

  it('toggles temporary panning with Space outside editable inputs', () => {
    render(<KeyboardShortcutsHarness />);

    act(() => {
      fireEvent.keyDown(window, { key: ' ', code: 'Space' });
    });
    expect(screen.getByTestId('space-panning').textContent).toBe('true');

    act(() => {
      fireEvent.keyUp(window, { key: ' ', code: 'Space' });
    });
    expect(screen.getByTestId('space-panning').textContent).toBe('false');

    act(() => {
      fireEvent.keyDown(screen.getByLabelText('快捷键输入框'), {
        key: ' ',
        code: 'Space',
      });
    });
    expect(screen.getByTestId('space-panning').textContent).toBe('false');
  });

  it('tracks Shift key state', () => {
    render(<KeyboardShortcutsHarness />);

    act(() => {
      fireEvent.keyDown(window, { key: 'Shift', code: 'ShiftLeft' });
    });
    expect(screen.getByTestId('shift-pressed').textContent).toBe('true');

    act(() => {
      fireEvent.keyUp(window, { key: 'Shift', code: 'ShiftLeft' });
    });
    expect(screen.getByTestId('shift-pressed').textContent).toBe('false');
  });
});
