import { describe, it, expect, afterEach, vi } from 'vitest';
import { cleanup, render } from 'vitest-browser-svelte';
import { page } from 'vitest/browser';

import DropZone from './DropZone.svelte';

// vi.hoisted lets the mock fn reference survive vi.mock's hoisting so tests
// can assert on it from below while the factory remains self-contained.
const { invalidateAllMock } = vi.hoisted(() => ({ invalidateAllMock: vi.fn(async () => {}) }));
vi.mock('$app/navigation', () => ({ invalidateAll: invalidateAllMock }));

afterEach(() => {
	cleanup();
	vi.restoreAllMocks();
});

function stubXhrWith(responseBody: string) {
	class FakeXhr {
		upload = { addEventListener: vi.fn() };
		status = 200;
		responseText = responseBody;
		private loadHandler: (() => void) | null = null;
		open = vi.fn();
		addEventListener = vi.fn((event: string, handler: () => void) => {
			if (event === 'load') this.loadHandler = handler;
		});
		send = vi.fn(() => {
			queueMicrotask(() => this.loadHandler?.());
		});
	}
	vi.stubGlobal('XMLHttpRequest', FakeXhr);
}

describe('DropZone onUploadComplete', () => {
	it('invokes callback with created.length after a successful upload', async () => {
		stubXhrWith(JSON.stringify({ created: [{ id: 'd1' }, { id: 'd2' }], updated: [], errors: [] }));

		const onUploadComplete = vi.fn();
		render(DropZone, { onUploadComplete });

		const input = document.querySelector('input[type="file"]') as HTMLInputElement | null;
		expect(input).not.toBeNull();
		const file = new File(['%PDF-1.4'], 'test.pdf', { type: 'application/pdf' });
		const dataTransfer = new DataTransfer();
		dataTransfer.items.add(file);
		input!.files = dataTransfer.files;
		input!.dispatchEvent(new Event('change', { bubbles: true }));

		await vi.waitFor(() => {
			expect(onUploadComplete).toHaveBeenCalledTimes(1);
		});
		expect(onUploadComplete).toHaveBeenCalledWith(2);
	});

	it('does not invoke callback when no files were created', async () => {
		stubXhrWith(JSON.stringify({ created: [], updated: [], errors: [] }));

		const onUploadComplete = vi.fn();
		render(DropZone, { onUploadComplete });

		const input = document.querySelector('input[type="file"]') as HTMLInputElement;
		const file = new File(['%PDF-1.4'], 'dupe.pdf', { type: 'application/pdf' });
		const dt = new DataTransfer();
		dt.items.add(file);
		input.files = dt.files;
		input.dispatchEvent(new Event('change', { bubbles: true }));

		// invalidateAll is the last async step of the upload handler — once it
		// has been called, the callback decision has already been made.
		await vi.waitFor(() => {
			expect(invalidateAllMock).toHaveBeenCalled();
		});
		expect(onUploadComplete).not.toHaveBeenCalled();
	});

	it('works when the onUploadComplete prop is not supplied', async () => {
		stubXhrWith(JSON.stringify({ created: [{ id: 'x' }], updated: [], errors: [] }));

		render(DropZone, {});
		const input = document.querySelector('input[type="file"]') as HTMLInputElement;
		const file = new File(['%PDF-1.4'], 'x.pdf', { type: 'application/pdf' });
		const dt = new DataTransfer();
		dt.items.add(file);
		// Should not throw when the optional callback is absent.
		input.files = dt.files;
		input.dispatchEvent(new Event('change', { bubbles: true }));
		await expect.element(page.getByText(/1 Dokument/)).toBeInTheDocument();
	});
});
