import { describe, expect, mock, test } from "bun:test";
import * as React from "react";
import { imageFileKey } from "@/lib/multi-image";
import {
	createClipboardData,
	createDocumentKeydownEvent,
	getDocumentListener,
	getDocumentListenerMocks,
	registerAIToolbarEffectHooks,
	resetRegisteredDocumentListeners,
	renderToolbar,
	renderToolbarBoundary,
} from "./helpers/ai-toolbar-test-helpers";

registerAIToolbarEffectHooks();

describe("AI toolbar paste capture", () => {
	test("textarea paste path forwards clipboard images to onImagesSelect through the component boundary", async () => {
		const onImagesSelect = mock();
		const image = new File(["image-bytes"], "clipboard.png", {
			type: "image/png",
		});

		const { textareaProps } = await renderToolbarBoundary({ onImagesSelect });
		const preventDefault = mock();

		await textareaProps.onPaste?.({
			clipboardData: createClipboardData([image]),
			preventDefault,
		} as unknown as React.ClipboardEvent<HTMLTextAreaElement>);
		await new Promise((resolve) => setTimeout(resolve, 0));

		expect(preventDefault).toHaveBeenCalledTimes(1);
		expect(onImagesSelect).toHaveBeenCalledTimes(1);
		expect(onImagesSelect.mock.calls[0]?.[0][0]?.name).toMatch(
			/^clipboard-image-/,
		);
	});

	test("textarea-targeted paste bypasses the document listeners so the focused textarea owns the paste", async () => {
		const onImagesSelect = mock();
		const image = new File(["image-bytes"], "clipboard.png", {
			type: "image/png",
		});

		const { textareaProps } = await renderToolbarBoundary({ onImagesSelect });
		const handleCapturePaste = getDocumentListener(
			"paste",
			(entry) => entry.options?.capture === true,
		);
		const handleDocumentPaste = getDocumentListener(
			"paste",
			(entry) => !entry.options?.capture,
		);
		const preventDefault = mock();
		const textareaTarget = {
			tagName: "TEXTAREA",
			isContentEditable: false,
		} as HTMLTextAreaElement;
		const pasteEvent = {
			target: textareaTarget,
			currentTarget: textareaTarget,
			clipboardData: createClipboardData([image]),
			preventDefault,
		};

		handleCapturePaste(pasteEvent as unknown as Event);
		expect(preventDefault).not.toHaveBeenCalled();
		expect(onImagesSelect).not.toHaveBeenCalled();

		await textareaProps.onPaste?.(
			pasteEvent as unknown as React.ClipboardEvent<HTMLTextAreaElement>,
		);
		await new Promise((resolve) => setTimeout(resolve, 0));
		expect(preventDefault).toHaveBeenCalledTimes(1);
		expect(onImagesSelect).toHaveBeenCalledTimes(1);
		expect(onImagesSelect.mock.calls[0]?.[0][0]?.name).toMatch(
			/^clipboard-image-/,
		);

		handleDocumentPaste(pasteEvent as unknown as Event);
		expect(preventDefault).toHaveBeenCalledTimes(1);
		expect(onImagesSelect).toHaveBeenCalledTimes(1);
	});

	test("document paste forwards clipboard images only for non-editable targets", async () => {
		const onImagesSelect = mock();
		const image = new File(["image-bytes"], "clipboard.png", {
			type: "image/png",
		});

		await renderToolbar({ onImagesSelect });

		const handleDocumentPaste = getDocumentListener(
			"paste",
			(entry) => !entry.options?.capture,
		);
		const preventDefault = mock();

		handleDocumentPaste({
			target: { tagName: "DIV", isContentEditable: false },
			clipboardData: createClipboardData([image]),
			preventDefault,
		} as unknown as Event);
		await new Promise((resolve) => setTimeout(resolve, 0));

		expect(preventDefault).toHaveBeenCalledTimes(1);
		expect(onImagesSelect).toHaveBeenCalledTimes(1);
		expect(onImagesSelect.mock.calls[0]?.[0][0]?.name).toMatch(
			/^clipboard-image-/,
		);
	});

	test("Ctrl/Cmd+V fallback forwards clipboard images for non-editable targets", async () => {
		const onImagesSelect = mock();
		const clipboardRead = mock(async () => [
			{
				types: ["image/png"],
				getType: async () => new Blob(["image-bytes"], { type: "image/png" }),
			},
		]);

		(globalThis as { navigator?: Navigator }).navigator = {
			clipboard: { read: clipboardRead },
		} as unknown as Navigator;

		await renderToolbar({ onImagesSelect });

		const handleDocumentKeydown = getDocumentListener("keydown");

		await handleDocumentKeydown({
			key: "v",
			ctrlKey: true,
			metaKey: false,
			shiftKey: false,
			altKey: false,
			target: { tagName: "DIV", isContentEditable: false },
		} as unknown as Event);

		expect(clipboardRead).toHaveBeenCalledTimes(1);
		expect(onImagesSelect).toHaveBeenCalledTimes(1);
		expect(onImagesSelect.mock.calls[0]?.[0]).toHaveLength(1);
		expect(onImagesSelect.mock.calls[0]?.[0][0]).toBeInstanceOf(File);
		expect(onImagesSelect.mock.calls[0]?.[0][0]?.type).toBe("image/png");
	});

	test("Cmd+V fallback forwards clipboard images for non-editable targets", async () => {
		const onImagesSelect = mock();
		const clipboardRead = mock(async () => [
			{
				types: ["image/png"],
				getType: async () => new Blob(["image-bytes"], { type: "image/png" }),
			},
		]);

		(globalThis as { navigator?: Navigator }).navigator = {
			clipboard: { read: clipboardRead },
		} as unknown as Navigator;

		await renderToolbar({ onImagesSelect });

		const handleDocumentKeydown = getDocumentListener("keydown");

		await handleDocumentKeydown(createDocumentKeydownEvent({ metaKey: true }));

		expect(clipboardRead).toHaveBeenCalledTimes(1);
		expect(onImagesSelect).toHaveBeenCalledTimes(1);
	});

	test("Shift+Mod+V does not trigger the async clipboard fallback", async () => {
		const onImagesSelect = mock();
		const clipboardRead = mock(async () => []);

		(globalThis as { navigator?: Navigator }).navigator = {
			clipboard: { read: clipboardRead },
		} as unknown as Navigator;

		await renderToolbar({ onImagesSelect });

		const handleDocumentKeydown = getDocumentListener("keydown");

		await handleDocumentKeydown(
			createDocumentKeydownEvent({ ctrlKey: true, shiftKey: true }),
		);

		expect(clipboardRead).not.toHaveBeenCalled();
		expect(onImagesSelect).not.toHaveBeenCalled();
	});

	test("Alt+Mod+V does not trigger the async clipboard fallback", async () => {
		const onImagesSelect = mock();
		const clipboardRead = mock(async () => []);

		(globalThis as { navigator?: Navigator }).navigator = {
			clipboard: { read: clipboardRead },
		} as unknown as Navigator;

		await renderToolbar({ onImagesSelect });

		const handleDocumentKeydown = getDocumentListener("keydown");

		await handleDocumentKeydown(
			createDocumentKeydownEvent({ ctrlKey: true, altKey: true }),
		);

		expect(clipboardRead).not.toHaveBeenCalled();
		expect(onImagesSelect).not.toHaveBeenCalled();
	});

	test("async clipboard fallback ignores editable targets", async () => {
		const onImagesSelect = mock();
		const clipboardRead = mock(async () => []);

		(globalThis as { navigator?: Navigator }).navigator = {
			clipboard: { read: clipboardRead },
		} as unknown as Navigator;

		await renderToolbar({ onImagesSelect });

		const handleDocumentKeydown = getDocumentListener("keydown");

		await handleDocumentKeydown(
			createDocumentKeydownEvent({
				ctrlKey: true,
				target: { tagName: "DIV", isContentEditable: true } as unknown as EventTarget,
			}),
		);

		expect(clipboardRead).not.toHaveBeenCalled();
		expect(onImagesSelect).not.toHaveBeenCalled();
	});

	test("Ctrl+Meta+V does not trigger the async clipboard fallback", async () => {
		const onImagesSelect = mock();
		const clipboardRead = mock(async () => []);

		(globalThis as { navigator?: Navigator }).navigator = {
			clipboard: { read: clipboardRead },
		} as unknown as Navigator;

		await renderToolbar({ onImagesSelect });

		const handleDocumentKeydown = getDocumentListener("keydown");

		await handleDocumentKeydown(
			createDocumentKeydownEvent({ ctrlKey: true, metaKey: true }),
		);

		expect(clipboardRead).not.toHaveBeenCalled();
		expect(onImagesSelect).not.toHaveBeenCalled();
	});

	test("async clipboard fallback ignores clipboard.read() rejections", async () => {
		const onImagesSelect = mock();
		const clipboardRead = mock(async () => {
			throw new Error("permission denied");
		});

		(globalThis as { navigator?: Navigator }).navigator = {
			clipboard: { read: clipboardRead },
		} as unknown as Navigator;

		await renderToolbar({ onImagesSelect });

		const handleDocumentKeydown = getDocumentListener("keydown");

		await handleDocumentKeydown(createDocumentKeydownEvent({ ctrlKey: true }));

		expect(clipboardRead).toHaveBeenCalledTimes(1);
		expect(onImagesSelect).not.toHaveBeenCalled();
	});

	test("async clipboard fallback ignores clipboard items when every getType probe misses", async () => {
		const onImagesSelect = mock();
		const clipboardItem = {
			types: ["image/png"],
			getType: mock(async () => {
				throw new Error("NotFoundError");
			}),
		};
		const clipboardRead = mock(async () => [clipboardItem]);

		(globalThis as { navigator?: Navigator }).navigator = {
			clipboard: { read: clipboardRead },
		} as unknown as Navigator;

		await renderToolbar({ onImagesSelect });

		const handleDocumentKeydown = getDocumentListener("keydown");

		await handleDocumentKeydown(createDocumentKeydownEvent({ ctrlKey: true }));

		expect(clipboardRead).toHaveBeenCalledTimes(1);
		expect(clipboardItem.getType).toHaveBeenCalledTimes(6);
		expect(onImagesSelect).not.toHaveBeenCalled();
	});

	test("clipboard fallback creates distinct files for same-sized clipboard images", async () => {
		const onImagesSelect = mock();
		const clipboardRead = mock(async () => [
			{
				types: ["image/png"],
				getType: async () => new Blob(["abcd"], { type: "image/png" }),
			},
			{
				types: ["image/png"],
				getType: async () => new Blob(["wxyz"], { type: "image/png" }),
			},
		]);

		(globalThis as { navigator?: Navigator }).navigator = {
			clipboard: { read: clipboardRead },
		} as unknown as Navigator;

		await renderToolbar({ onImagesSelect });

		const handleDocumentKeydown = getDocumentListener("keydown");

		await handleDocumentKeydown(createDocumentKeydownEvent({ ctrlKey: true }));

		expect(onImagesSelect).toHaveBeenCalledTimes(1);
		expect(onImagesSelect.mock.calls[0]?.[0]).toHaveLength(2);
		expect(onImagesSelect.mock.calls[0]?.[0][0]?.name).not.toBe(
			onImagesSelect.mock.calls[0]?.[0][1]?.name,
		);
		expect(onImagesSelect.mock.calls[0]?.[0][0]?.size).toBe(
			onImagesSelect.mock.calls[0]?.[0][1]?.size,
		);
	});

	test("clipboard fallback reuses the same synthesized file name for identical clipboard content", async () => {
		const onImagesSelect = mock();
		const clipboardRead = mock(async () => [
			{
				types: ["image/png"],
				getType: async () => new Blob(["same-image"], { type: "image/png" }),
			},
		]);

		(globalThis as { navigator?: Navigator }).navigator = {
			clipboard: { read: clipboardRead },
		} as unknown as Navigator;

		await renderToolbar({ onImagesSelect });

		const handleDocumentKeydown = getDocumentListener("keydown");

		await handleDocumentKeydown(createDocumentKeydownEvent({ ctrlKey: true }));
		await handleDocumentKeydown(createDocumentKeydownEvent({ ctrlKey: true }));

		expect(onImagesSelect).toHaveBeenCalledTimes(2);
		expect(onImagesSelect.mock.calls[0]?.[0][0]?.name).toBe(
			onImagesSelect.mock.calls[1]?.[0][0]?.name,
		);
	});

	test("clipboard fallback keeps the same synthesized identity across MIME aliases for identical content", async () => {
		const onImagesSelect = mock();
		const reads = [
			async () => [
				{
					types: ["image/png"],
					getType: async () =>
						new Blob(["same-image"], { type: "image/png" }),
				},
			],
			async () => [
				{
					types: ["image/x-png"],
					getType: async () =>
						new Blob(["same-image"], { type: "image/x-png" }),
				},
			],
		];
		const clipboardRead = mock(async () => (await reads.shift()?.()) ?? []);

		(globalThis as { navigator?: Navigator }).navigator = {
			clipboard: { read: clipboardRead },
		} as unknown as Navigator;

		await renderToolbar({ onImagesSelect });

		const handleDocumentKeydown = getDocumentListener("keydown");

		await handleDocumentKeydown(createDocumentKeydownEvent({ ctrlKey: true }));
		await handleDocumentKeydown(createDocumentKeydownEvent({ ctrlKey: true }));

		expect(onImagesSelect).toHaveBeenCalledTimes(2);
		expect(onImagesSelect.mock.calls[0]?.[0][0]?.name).toBe(
			onImagesSelect.mock.calls[1]?.[0][0]?.name,
		);
	});

	test("synchronous paste and async fallback normalize identical clipboard payloads to the same file identity", async () => {
		const syncOnImagesSelect = mock();
		const asyncOnImagesSelect = mock();
		const clipboardRead = mock(async () => [
			{
				types: ["image/png"],
				getType: async () => new Blob(["same-image"], { type: "image/png" }),
			},
		]);

		(globalThis as { navigator?: Navigator }).navigator = {
			clipboard: { read: clipboardRead },
		} as unknown as Navigator;

		const { textareaProps } = await renderToolbarBoundary({
			onImagesSelect: syncOnImagesSelect,
		});
		await textareaProps.onPaste?.({
			clipboardData: createClipboardData([
				new File(["same-image"], "clipboard.png", { type: "image/png" }),
			]),
			preventDefault: mock(),
		} as unknown as React.ClipboardEvent<HTMLTextAreaElement>);
		await new Promise((resolve) => setTimeout(resolve, 0));

		resetRegisteredDocumentListeners();
		const documentListeners = getDocumentListenerMocks();
		documentListeners.addEventListener.mockClear();
		documentListeners.removeEventListener.mockClear();
		await renderToolbar({ onImagesSelect: asyncOnImagesSelect });
		const handleDocumentKeydown = getDocumentListener("keydown");

		await handleDocumentKeydown(createDocumentKeydownEvent({ ctrlKey: true }));

		expect(syncOnImagesSelect).toHaveBeenCalledTimes(1);
		expect(asyncOnImagesSelect).toHaveBeenCalledTimes(1);
		expect(syncOnImagesSelect.mock.calls[0]?.[0][0]?.name).toBe(
			asyncOnImagesSelect.mock.calls[0]?.[0][0]?.name,
		);
		expect(syncOnImagesSelect.mock.calls[0]?.[0][0]?.size).toBe(
			asyncOnImagesSelect.mock.calls[0]?.[0][0]?.size,
		);
		expect(syncOnImagesSelect.mock.calls[0]?.[0][0]?.lastModified).toBe(
			asyncOnImagesSelect.mock.calls[0]?.[0][0]?.lastModified,
		);
		expect(imageFileKey(syncOnImagesSelect.mock.calls[0]?.[0][0])).toBe(
			imageFileKey(asyncOnImagesSelect.mock.calls[0]?.[0][0]),
		);
	});

	test("async clipboard fallback does not double-handle a paste already handled by the synchronous document paste flow", async () => {
		const onImagesSelect = mock();
		const clipboardRead = mock(async () => [
			{
				types: ["image/png"],
				getType: async () => new Blob(["image-bytes"], { type: "image/png" }),
			},
		]);

		(globalThis as { navigator?: Navigator }).navigator = {
			clipboard: { read: clipboardRead },
		} as unknown as Navigator;

		await renderToolbar({ onImagesSelect });

		const handleCapturePaste = getDocumentListener(
			"paste",
			(entry) => entry.options?.capture === true,
		);
		const handleDocumentPaste = getDocumentListener(
			"paste",
			(entry) => !entry.options?.capture,
		);
		const handleDocumentKeydown = getDocumentListener("keydown");
		const image = new File(["image-bytes"], "clipboard.png", {
			type: "image/png",
		});
		const preventDefault = mock();

		const keydownPromise = handleDocumentKeydown(
			createDocumentKeydownEvent({ ctrlKey: true }),
		);

		const pasteEvent = {
			target: { tagName: "DIV", isContentEditable: false },
			clipboardData: createClipboardData([image]),
			preventDefault,
		} as unknown as Event;

		handleCapturePaste(pasteEvent);
		handleDocumentPaste(pasteEvent);

		await keydownPromise;

		expect(preventDefault).toHaveBeenCalledTimes(1);
		expect(clipboardRead).not.toHaveBeenCalled();
		expect(onImagesSelect).toHaveBeenCalledTimes(1);
		expect(onImagesSelect.mock.calls[0]?.[0][0]?.name).toMatch(
			/^clipboard-image-/,
		);
	});

	test("global paste listeners only arm when AI paste capture is usable, and disarm on cleanup", async () => {
		await renderToolbar();

		const documentListeners = getDocumentListenerMocks();

		expect(documentListeners.addEventListener).toHaveBeenCalledTimes(3);
		expect(documentListeners.addEventListener).toHaveBeenCalledWith(
			"paste",
			expect.any(Function),
		);
		expect(documentListeners.addEventListener).toHaveBeenCalledWith(
			"paste",
			expect.any(Function),
			{ capture: true },
		);
		expect(documentListeners.addEventListener).toHaveBeenCalledWith(
			"keydown",
			expect.any(Function),
		);

		documentListeners.lastEffectCleanup?.();

		expect(documentListeners.removeEventListener).toHaveBeenCalledTimes(3);
		expect(documentListeners.removeEventListener).toHaveBeenCalledWith(
			"paste",
			expect.any(Function),
		);
		expect(documentListeners.removeEventListener).toHaveBeenCalledWith(
			"paste",
			expect.any(Function),
			{ capture: true },
		);
		expect(documentListeners.removeEventListener).toHaveBeenCalledWith(
			"keydown",
			expect.any(Function),
		);
	});

	test("global paste listeners stay disarmed when AI cannot be used", async () => {
		await renderToolbar({ isAuthenticated: false });
		await renderToolbar({ isPending: true });
		await renderToolbar({ adminAiEnabled: false });
		await renderToolbar({ aiEnabled: false });
		await renderToolbar({ aiLoading: true });

		const documentListeners = getDocumentListenerMocks();

		expect(documentListeners.addEventListener).not.toHaveBeenCalled();
		expect(documentListeners.removeEventListener).not.toHaveBeenCalled();
	});
});
