/* @vitest-environment jsdom */

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

import type {
  CanvasGenerationDialogState,
  CanvasLayer,
  CanvasViewport,
  GenerateDialogState,
} from './ImageCanvasEditorTypes';
import { useCanvasHistory } from './useCanvasHistory';

function createLayer(id: string, x: number): CanvasLayer {
  return {
    id,
    resourceId: `resource-${id}`,
    title: id,
    src: `data:image/png;base64,${id}`,
    x,
    y: 20,
    width: 100,
    height: 80,
    originalWidth: 100,
    originalHeight: 80,
    zIndex: 1,
    sourceType: 'uploaded',
  };
}

function HistoryHarness({ onClearDrag }: { onClearDrag: () => void }) {
  const [layers, setLayers] = useState<CanvasLayer[]>([
    createLayer('first', 10),
  ]);
  const [viewport, setViewport] = useState<CanvasViewport>({
    x: 1,
    y: 2,
    scale: 1,
  });
  const [generateDialog, setGenerateDialog] =
    useState<GenerateDialogState | null>({
      id: 'dialog-active',
      mode: 'generate',
      prompt: 'active prompt',
      status: 'idle',
      placeholder: {
        x: 10,
        y: 20,
        width: 320,
        height: 240,
        originalWidth: 320,
        originalHeight: 240,
      },
    });
  const [inactiveGenerateDialogs, setInactiveGenerateDialogs] = useState<
    CanvasGenerationDialogState[]
  >([
    {
      id: 'dialog-inactive',
      mode: 'character',
      prompt: 'archived prompt',
      status: 'idle',
      placeholder: {
        x: 30,
        y: 40,
        width: 512,
        height: 512,
        originalWidth: 512,
        originalHeight: 512,
      },
    },
  ]);
  const [selectedLayerId, setSelectedLayerId] = useState<string | null>('first');
  const [selectedLayerIds, setSelectedLayerIds] = useState<string[]>(['first']);

  const layersRef = useRef(layers);
  const viewportRef = useRef(viewport);
  const generateDialogRef = useRef(generateDialog);
  const inactiveGenerateDialogsRef = useRef(inactiveGenerateDialogs);
  const selectedLayerIdRef = useRef(selectedLayerId);
  const selectedLayerIdsRef = useRef(selectedLayerIds);

  layersRef.current = layers;
  viewportRef.current = viewport;
  generateDialogRef.current = generateDialog;
  inactiveGenerateDialogsRef.current = inactiveGenerateDialogs;
  selectedLayerIdRef.current = selectedLayerId;
  selectedLayerIdsRef.current = selectedLayerIds;

  const history = useCanvasHistory({
    refs: {
      layersRef,
      viewportRef,
      generateDialogRef,
      inactiveGenerateDialogsRef,
      selectedLayerIdRef,
      selectedLayerIdsRef,
    },
    setters: {
      setLayers,
      setViewport,
      setGenerateDialog,
      setInactiveGenerateDialogs,
      setSelectedLayerId,
      setSelectedLayerIds,
    },
    resetters: {
      setHoveredLayerId: () => {},
      setMetadataLayer: () => {},
      setCanvasMarquee: () => {},
      setSnapGuide: () => {},
      setImageContextMenu: () => {},
      setContextMenu: () => {},
      setIsPanning: () => {},
      clearDragState: onClearDrag,
    },
  });

  return (
    <div>
      <span data-testid="layers">
        {layers.map((layer) => `${layer.id}:${layer.x}`).join(',')}
      </span>
      <span data-testid="viewport">
        {viewport.x},{viewport.y},{viewport.scale}
      </span>
      <span data-testid="dialog">{generateDialog?.prompt ?? '-'}</span>
      <span data-testid="inactive">
        {inactiveGenerateDialogs.map((dialog) => dialog.prompt).join(',')}
      </span>
      <span data-testid="selection">{selectedLayerIds.join(',')}</span>
      <span data-testid="can-undo">{String(history.canUndo)}</span>
      <span data-testid="can-redo">{String(history.canRedo)}</span>
      <button
        type="button"
        onClick={() => {
          history.captureCanvasHistory();
        }}
      >
        capture
      </button>
      <button
        type="button"
        onClick={() => {
          setLayers([createLayer('second', 90)]);
          setViewport({ x: 9, y: 8, scale: 2 });
          setGenerateDialog({
            id: 'dialog-next',
            mode: 'spec',
            prompt: 'next prompt',
            status: 'idle',
          });
          setInactiveGenerateDialogs([]);
          setSelectedLayerId('second');
          setSelectedLayerIds(['second']);
        }}
      >
        mutate
      </button>
      <button
        type="button"
        onClick={() => {
          history.undoCanvasChange();
        }}
      >
        undo
      </button>
      <button
        type="button"
        onClick={() => {
          history.redoCanvasChange();
        }}
      >
        redo
      </button>
    </div>
  );
}

describe('useCanvasHistory', () => {
  it('captures, restores, and replays canvas history snapshots', () => {
    const clearDragState = vi.fn();
    render(<HistoryHarness onClearDrag={clearDragState} />);

    act(() => {
      screen.getByRole('button', { name: 'capture' }).click();
    });
    expect(screen.getByTestId('can-undo').textContent).toBe('true');

    act(() => {
      screen.getByRole('button', { name: 'mutate' }).click();
    });
    expect(screen.getByTestId('layers').textContent).toBe('second:90');
    expect(screen.getByTestId('viewport').textContent).toBe('9,8,2');

    act(() => {
      screen.getByRole('button', { name: 'undo' }).click();
    });

    expect(screen.getByTestId('layers').textContent).toBe('first:10');
    expect(screen.getByTestId('viewport').textContent).toBe('1,2,1');
    expect(screen.getByTestId('dialog').textContent).toBe('active prompt');
    expect(screen.getByTestId('inactive').textContent).toBe('archived prompt');
    expect(screen.getByTestId('selection').textContent).toBe('first');
    expect(screen.getByTestId('can-redo').textContent).toBe('true');
    expect(clearDragState).toHaveBeenCalledTimes(1);

    act(() => {
      screen.getByRole('button', { name: 'redo' }).click();
    });

    expect(screen.getByTestId('layers').textContent).toBe('second:90');
    expect(screen.getByTestId('viewport').textContent).toBe('9,8,2');
    expect(screen.getByTestId('dialog').textContent).toBe('next prompt');
    expect(screen.getByTestId('selection').textContent).toBe('second');
  });
});
