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

// Mock EventSource so the $effect doesn't open a real SSE connection.
class MockEventSource {
	url: string;
	listeners = new Map<string, EventListener>();
	onerror: (() => void) | null = null;
	close = vi.fn();
	constructor(url: string) {
		this.url = url;
	}
	addEventListener(type: string, fn: EventListener) {
		this.listeners.set(type, fn);
	}
	dispatch(type: string, data: unknown) {
		const fn = this.listeners.get(type);
		if (fn) fn({ data: JSON.stringify(data) } as MessageEvent);
	}
}

let lastSource: MockEventSource | null = null;

beforeEach(() => {
	const trackedFactory = function (url: string) {
		const src = new MockEventSource(url);
		lastSource = src;
		return src;
	};
	(globalThis as unknown as { EventSource: unknown }).EventSource = new Proxy(MockEventSource, {
		construct(_target, args) {
			return trackedFactory(args[0]);
		}
	});
});

afterEach(() => {
	cleanup();
	lastSource = null;
});

async function waitForSource(): Promise<MockEventSource> {
	await vi.waitFor(() => expect(lastSource).not.toBeNull());
	return lastSource as MockEventSource;
}

describe('OcrProgress', () => {
	it('renders the running progress block by default', async () => {
		render(OcrProgress, { props: { jobId: 'job-1', onDone: () => {} } });

		await expect.element(page.getByRole('heading', { name: /ocr läuft/i })).toBeVisible();
	});

	it('renders the progress bar with the running label', async () => {
		render(OcrProgress, { props: { jobId: 'job-1', onDone: () => {} } });

		expect(document.querySelector('[role="progressbar"]')).not.toBeNull();
	});

	it('updates the progress bar when document events arrive', async () => {
		render(OcrProgress, { props: { jobId: 'job-1', onDone: () => {} } });

		const src = await waitForSource();
		src.dispatch('document', { processed: 5, total: 10 });

		await vi.waitFor(async () => {
			const bar = (await page.getByRole('progressbar').element()) as HTMLElement;
			expect(bar.getAttribute('aria-valuenow')).toBe('50');
		});
	});

	it('switches to the done state and calls onDone when the done event arrives', async () => {
		const onDone = vi.fn();
		render(OcrProgress, { props: { jobId: 'job-1', onDone } });

		const src = await waitForSource();
		src.dispatch('done', {});

		await vi.waitFor(() => expect(onDone).toHaveBeenCalledOnce());
		await expect.element(page.getByRole('heading', { name: /ocr läuft/i })).not.toBeInTheDocument();
	});

	it('switches to the error state when the error event arrives', async () => {
		render(OcrProgress, { props: { jobId: 'job-1', onDone: () => {} } });

		const src = await waitForSource();
		src.dispatch('error', {});

		await expect.element(page.getByRole('heading', { name: /ocr fehlgeschlagen/i })).toBeVisible();
	});

	it('renders the retry button in the error state', async () => {
		render(OcrProgress, { props: { jobId: 'job-1', onDone: () => {} } });

		const src = await waitForSource();
		src.dispatch('error', {});

		await expect.element(page.getByRole('button', { name: /erneut versuchen/i })).toBeVisible();
	});
});
