/* @vitest-environment jsdom */

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

import { ASSET_DRAG_MIME_TYPE } from './ImageCanvasEditorModel';
import type { EditorAsset, EditorAssetFolder } from './ImageCanvasEditorTypes';
import { useImageCanvasCanvasDropWorkflow } from './useImageCanvasCanvasDropWorkflow';

function createDataTransferStub({
  files = [],
}: {
  files?: File[];
} = {}) {
  const store = new Map<string, string>();
  return {
    files,
    types: files.length ? ['Files'] : ([] as string[]),
    dropEffect: 'none',
    setData(type: string, value: string) {
      store.set(type, value);
      if (!this.types.includes(type)) {
        this.types.push(type);
      }
    },
    getData(type: string) {
      return store.get(type) ?? '';
    },
  };
}

function dispatchDragEvent(
  target: Element,
  type: string,
  options: {
    dataTransfer: ReturnType<typeof createDataTransferStub>;
    clientX?: number;
    clientY?: number;
    relatedTarget?: EventTarget | null;
  },
) {
  const event = new Event(type, { bubbles: true, cancelable: true });
  Object.defineProperty(event, 'dataTransfer', {
    configurable: true,
    value: options.dataTransfer,
  });
  Object.defineProperty(event, 'clientX', {
    configurable: true,
    value: options.clientX ?? 0,
  });
  Object.defineProperty(event, 'clientY', {
    configurable: true,
    value: options.clientY ?? 0,
  });
  Object.defineProperty(event, 'relatedTarget', {
    configurable: true,
    value: options.relatedTarget ?? null,
  });
  fireEvent(target, event);
}

function DropWorkflowHarness({
  assets = [
    {
      id: 'asset-1',
      label: '素材一',
      src: 'data:image/png;base64,asset',
      width: 100,
      height: 80,
      folderId: 'project',
      sourceKind: 'uploaded',
      sourceType: 'uploaded',
      persisted: true,
    },
  ],
  folders = [
    {
      id: 'project',
      label: '项目素材',
      collapsed: false,
      systemDefault: true,
      persisted: true,
    },
  ],
  addAssetLayer = vi.fn(),
  addUploadedFiles = vi.fn(),
  updateAssetMoveDropFolder = vi.fn(),
  getCanvasDropPoint = vi.fn((clientX: number, clientY: number) => ({
    x: clientX - 10,
    y: clientY - 20,
  })),
}: {
  assets?: EditorAsset[];
  folders?: EditorAssetFolder[];
  addAssetLayer?: (asset: EditorAsset, position?: { x: number; y: number }) => void;
  addUploadedFiles?: (
    files: FileList | File[],
    options: {
      folderId?: string;
      canvasPoint: { x: number; y: number };
      addToCanvas: true;
    },
  ) => void;
  updateAssetMoveDropFolder?: (folderId: string | null) => void;
  getCanvasDropPoint?: (clientX: number, clientY: number) => {
    x: number;
    y: number;
  };
}) {
  const [uploadDropTarget, setUploadDropTarget] = useState<
    'canvas' | 'assets' | null
  >(null);
  const workflow = useImageCanvasCanvasDropWorkflow({
    assets,
    assetFolders: folders,
    setUploadDropTarget,
    updateAssetMoveDropFolder,
    getCanvasDropPoint,
    addAssetLayer,
    addUploadedFiles,
  });

  return (
    <div>
      <div
        data-testid="outside"
        onDragEnter={() => setUploadDropTarget('assets')}
      />
      <div
        data-testid="canvas"
        onDragOver={workflow.handleCanvasDragOver}
        onDragLeave={workflow.handleCanvasDragLeave}
        onDrop={workflow.handleCanvasDrop}
      >
        canvas
        <span data-testid="drop-target">{uploadDropTarget ?? '-'}</span>
        <span data-testid="inner">inner</span>
      </div>
    </div>
  );
}

describe('useImageCanvasCanvasDropWorkflow', () => {
  it('shows a canvas drop target for dragged asset library images', () => {
    render(<DropWorkflowHarness />);
    const dataTransfer = createDataTransferStub();
    dataTransfer.setData(ASSET_DRAG_MIME_TYPE, 'asset-1');

    dispatchDragEvent(screen.getByTestId('canvas'), 'dragover', {
      dataTransfer,
    });

    expect(dataTransfer.dropEffect).toBe('copy');
    expect(screen.getByTestId('drop-target').textContent).toBe('canvas');
  });

  it('adds an existing asset to the canvas at the drop point', () => {
    const addAssetLayer = vi.fn();
    const updateAssetMoveDropFolder = vi.fn();
    render(
      <DropWorkflowHarness
        addAssetLayer={addAssetLayer}
        updateAssetMoveDropFolder={updateAssetMoveDropFolder}
      />,
    );
    const dataTransfer = createDataTransferStub();
    dataTransfer.setData(ASSET_DRAG_MIME_TYPE, 'asset-1');

    dispatchDragEvent(screen.getByTestId('canvas'), 'drop', {
      clientX: 120,
      clientY: 90,
      dataTransfer,
    });

    expect(addAssetLayer).toHaveBeenCalledWith(
      expect.objectContaining({ id: 'asset-1' }),
      { x: 110, y: 70 },
    );
    expect(updateAssetMoveDropFolder).toHaveBeenCalledWith(null);
    expect(screen.getByTestId('drop-target').textContent).toBe('-');
  });

  it('uploads dropped files to the default folder and adds them to the canvas', () => {
    const addUploadedFiles = vi.fn();
    render(<DropWorkflowHarness addUploadedFiles={addUploadedFiles} />);
    const imageFile = new File(['image'], '画布上传.png', {
      type: 'image/png',
    });
    const dataTransfer = createDataTransferStub({ files: [imageFile] });

    dispatchDragEvent(screen.getByTestId('canvas'), 'dragover', {
      dataTransfer,
    });
    expect(screen.getByTestId('drop-target').textContent).toBe('canvas');

    dispatchDragEvent(screen.getByTestId('canvas'), 'drop', {
      clientX: 300,
      clientY: 220,
      dataTransfer,
    });

    expect(addUploadedFiles).toHaveBeenCalledWith([imageFile], {
      folderId: 'project',
      canvasPoint: { x: 290, y: 200 },
      addToCanvas: true,
    });
    expect(screen.getByTestId('drop-target').textContent).toBe('-');
  });

  it('keeps unrelated drags untouched and clears only canvas overlays on leave', () => {
    render(<DropWorkflowHarness />);
    const dataTransfer = createDataTransferStub();

    dispatchDragEvent(screen.getByTestId('canvas'), 'dragover', {
      dataTransfer,
    });

    expect(dataTransfer.dropEffect).toBe('none');
    expect(screen.getByTestId('drop-target').textContent).toBe('-');

    fireEvent.dragEnter(screen.getByTestId('outside'));
    expect(screen.getByTestId('drop-target').textContent).toBe('assets');

    dispatchDragEvent(screen.getByTestId('canvas'), 'dragover', {
      dataTransfer: createDataTransferStub({
        files: [new File(['image'], '画布上传.png', { type: 'image/png' })],
      }),
    });
    expect(screen.getByTestId('drop-target').textContent).toBe('canvas');

    dispatchDragEvent(screen.getByTestId('canvas'), 'dragleave', {
      dataTransfer: createDataTransferStub(),
      relatedTarget: screen.getByTestId('inner'),
    });
    expect(screen.getByTestId('drop-target').textContent).toBe('canvas');

    dispatchDragEvent(screen.getByTestId('canvas'), 'dragleave', {
      dataTransfer: createDataTransferStub(),
      relatedTarget: screen.getByTestId('outside'),
    });
    expect(screen.getByTestId('drop-target').textContent).toBe('-');
  });
});
