/* @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 {
  AssetPointerDragState,
  EditorAsset,
} from './ImageCanvasEditorTypes';
import { useImageCanvasAssetPointerDragBridge } from './useImageCanvasAssetPointerDragBridge';

const defaultAssets: EditorAsset[] = [
  {
    id: 'asset-1',
    label: '素材一',
    src: 'data:image/png;base64,asset-1',
    width: 320,
    height: 240,
    folderId: 'project',
    sourceKind: 'uploaded',
    sourceType: 'uploaded',
    persisted: true,
  },
];

function dispatchPointerEvent(
  type: string,
  init: MouseEventInit & { pointerId: number },
) {
  const event = new MouseEvent(type, {
    bubbles: true,
    cancelable: true,
    ...init,
  });
  Object.defineProperty(event, 'pointerId', { value: init.pointerId });
  window.dispatchEvent(event);
}

function AssetPointerDragBridgeHarness({
  initialDrag,
  assets = defaultAssets,
  resolveAssetFolderId = vi.fn(() => null),
  resolveCanvasPoint = vi.fn(() => null),
  moveAssetToFolder = vi.fn(),
  addAssetLayer = vi.fn(),
}: {
  initialDrag?: AssetPointerDragState | null;
  assets?: EditorAsset[];
  resolveAssetFolderId?: (clientX: number, clientY: number) => string | null;
  resolveCanvasPoint?: (
    clientX: number,
    clientY: number,
  ) => { x: number; y: number } | null;
  moveAssetToFolder?: (assetId: string, folderId: string) => void;
  addAssetLayer?: (asset: EditorAsset, position?: { x: number; y: number }) => void;
}) {
  const assetPointerDragRef = useRef<AssetPointerDragState | null>(
    initialDrag ?? {
      assetId: 'asset-1',
      pointerId: 7,
      startClientX: 10,
      startClientY: 10,
      currentClientX: 10,
      currentClientY: 10,
      active: false,
      dropFolderId: null,
    },
  );
  const suppressAssetClickRef = useRef(false);
  const [assetPointerDrag, setAssetPointerDrag] =
    useState<AssetPointerDragState | null>(assetPointerDragRef.current);
  const [uploadDropTarget, setUploadDropTarget] = useState<
    'canvas' | 'assets' | null
  >(null);
  const [dropFolderId, updateAssetMoveDropFolder] = useState<string | null>(
    null,
  );

  useImageCanvasAssetPointerDragBridge({
    assetPointerDragRef,
    suppressAssetClickRef,
    assets,
    resolveAssetFolderId,
    resolveCanvasPoint,
    setAssetPointerDrag,
    setUploadDropTarget,
    updateAssetMoveDropFolder,
    moveAssetToFolder,
    addAssetLayer,
  });

  return (
    <div>
      <span data-testid="drag-active">
        {assetPointerDrag ? String(assetPointerDrag.active) : 'none'}
      </span>
      <span data-testid="drag-x">
        {assetPointerDrag ? assetPointerDrag.currentClientX : 'none'}
      </span>
      <span data-testid="drop-target">{uploadDropTarget ?? '-'}</span>
      <span data-testid="drop-folder">{dropFolderId ?? '-'}</span>
      <span data-testid="suppressed">{String(suppressAssetClickRef.current)}</span>
    </div>
  );
}

describe('useImageCanvasAssetPointerDragBridge', () => {
  it('activates sidebar asset drags and updates canvas and folder drop hints', () => {
    const resolveAssetFolderId = vi.fn(() => 'folder-1');
    const resolveCanvasPoint = vi.fn(() => ({ x: 100, y: 80 }));
    render(
      <AssetPointerDragBridgeHarness
        resolveAssetFolderId={resolveAssetFolderId}
        resolveCanvasPoint={resolveCanvasPoint}
      />,
    );

    act(() => {
      dispatchPointerEvent('pointermove', {
        pointerId: 7,
        clientX: 18,
        clientY: 18,
      });
    });

    expect(screen.getByTestId('drag-active').textContent).toBe('true');
    expect(screen.getByTestId('drag-x').textContent).toBe('18');
    expect(screen.getByTestId('drop-target').textContent).toBe('canvas');
    expect(screen.getByTestId('drop-folder').textContent).toBe('folder-1');
  });

  it('moves an active asset drag into a different folder on pointer up', () => {
    const moveAssetToFolder = vi.fn();
    render(
      <AssetPointerDragBridgeHarness
        initialDrag={{
          assetId: 'asset-1',
          pointerId: 7,
          startClientX: 10,
          startClientY: 10,
          currentClientX: 30,
          currentClientY: 30,
          active: true,
          dropFolderId: 'folder-1',
        }}
        resolveAssetFolderId={() => 'folder-1'}
        moveAssetToFolder={moveAssetToFolder}
      />,
    );

    act(() => {
      dispatchPointerEvent('pointerup', {
        pointerId: 7,
        clientX: 30,
        clientY: 30,
      });
    });

    expect(moveAssetToFolder).toHaveBeenCalledWith('asset-1', 'folder-1');
    expect(screen.getByTestId('drag-active').textContent).toBe('none');
    expect(screen.getByTestId('drop-target').textContent).toBe('-');
    expect(screen.getByTestId('drop-folder').textContent).toBe('-');
    expect(screen.getByTestId('suppressed').textContent).toBe('true');
  });

  it('adds an active dragged asset to the canvas when released over the canvas', () => {
    const addAssetLayer = vi.fn();
    render(
      <AssetPointerDragBridgeHarness
        initialDrag={{
          assetId: 'asset-1',
          pointerId: 7,
          startClientX: 10,
          startClientY: 10,
          currentClientX: 48,
          currentClientY: 64,
          active: true,
          dropFolderId: null,
        }}
        resolveCanvasPoint={() => ({ x: 480, y: 640 })}
        addAssetLayer={addAssetLayer}
      />,
    );

    act(() => {
      dispatchPointerEvent('pointerup', {
        pointerId: 7,
        clientX: 48,
        clientY: 64,
      });
    });

    expect(addAssetLayer).toHaveBeenCalledWith(
      expect.objectContaining({ id: 'asset-1' }),
      { x: 480, y: 640 },
    );
  });

  it('cleans up inactive drags without moving or adding assets', () => {
    const moveAssetToFolder = vi.fn();
    const addAssetLayer = vi.fn();
    render(
      <AssetPointerDragBridgeHarness
        initialDrag={{
          assetId: 'asset-1',
          pointerId: 7,
          startClientX: 10,
          startClientY: 10,
          currentClientX: 10,
          currentClientY: 10,
          active: false,
          dropFolderId: 'folder-1',
        }}
        moveAssetToFolder={moveAssetToFolder}
        addAssetLayer={addAssetLayer}
      />,
    );

    act(() => {
      dispatchPointerEvent('pointerup', {
        pointerId: 7,
        clientX: 10,
        clientY: 10,
      });
    });

    expect(moveAssetToFolder).not.toHaveBeenCalled();
    expect(addAssetLayer).not.toHaveBeenCalled();
    expect(screen.getByTestId('drag-active').textContent).toBe('none');
  });

  it('uses the same finish path for pointer cancellation', () => {
    const moveAssetToFolder = vi.fn();
    const addAssetLayer = vi.fn();
    render(
      <AssetPointerDragBridgeHarness
        initialDrag={{
          assetId: 'asset-1',
          pointerId: 8,
          startClientX: 10,
          startClientY: 10,
          currentClientX: 24,
          currentClientY: 24,
          active: true,
          dropFolderId: 'folder-1',
        }}
        moveAssetToFolder={moveAssetToFolder}
        addAssetLayer={addAssetLayer}
      />,
    );

    act(() => {
      dispatchPointerEvent('pointercancel', {
        pointerId: 8,
        clientX: 24,
        clientY: 24,
      });
    });

    expect(moveAssetToFolder).toHaveBeenCalledWith('asset-1', 'folder-1');
  });
});
