import { describe, it, expect, vi, afterEach } from 'vitest';
import { cleanup, render } from 'vitest-browser-svelte';
import { page } from 'vitest/browser';
import AnnotationShape from './AnnotationShape.svelte';
import {
	transcribeShortcuts,
	type TranscribeShortcutOptions
} from '$lib/shared/actions/transcribeShortcuts';

afterEach(cleanup);

function noopShortcutOptions(
	overrides: Partial<TranscribeShortcutOptions> = {}
): TranscribeShortcutOptions {
	return {
		isPanelOpen: () => true,
		isCheatsheetOpen: () => false,
		panelMode: () => 'edit',
		goToNextRegion: () => {},
		goToPrevRegion: () => {},
		toggleMode: () => {},
		closePanel: () => {},
		startDrawMode: () => {},
		toggleTrainingMark: () => {},
		deleteCurrentRegion: () => {},
		openCheatsheet: () => {},
		...overrides
	};
}

function makeAnnotation(id = 'ann-1') {
	return {
		id,
		documentId: 'doc-1',
		pageNumber: 1,
		x: 0.1,
		y: 0.1,
		width: 0.3,
		height: 0.2,
		color: '#00C7B1',
		createdAt: new Date().toISOString()
	};
}

describe('AnnotationShape', () => {
	it('renders the annotation element', async () => {
		render(AnnotationShape, {
			annotation: makeAnnotation(),
			isHovered: false,
			isActive: false,
			onclick: () => {},
			onpointerenter: () => {},
			onpointerleave: () => {}
		});

		await expect.element(page.getByTestId('annotation-ann-1')).toBeInTheDocument();
	});

	// The on-canvas delete button was removed (issue #722) because it overlapped
	// the document text. Deletion now happens via the transcription panel or the
	// keyboard Delete shortcut. No visible delete button must ever render — even
	// when hovered and active in delete mode.
	it('never renders a delete button, even when hovered and active in delete mode', async () => {
		render(AnnotationShape, {
			annotation: makeAnnotation(),
			isHovered: true,
			isActive: true,
			showDelete: true,
			onclick: () => {},
			onpointerenter: () => {},
			onpointerleave: () => {}
		});

		await expect.element(page.getByTestId('annotation-ann-1')).toBeInTheDocument();
		// Positive control: the previously-removed testid must stay absent.
		await expect.element(page.getByTestId('annotation-delete-ann-1')).not.toBeInTheDocument();
		// Real invariant: the annotation must contain no clickable delete control at all.
		const annotationEl = page.getByTestId('annotation-ann-1').element() as HTMLElement;
		expect(annotationEl.querySelectorAll('button').length).toBe(0);
	});

	// Deletion is owned solely by the transcribeShortcuts action (issue #327,
	// decision: action is the single Delete owner). The shape must NOT handle
	// the Delete key itself, or the key would delete twice.
	it('does not act on the Delete key itself (the action owns deletion)', async () => {
		const onclick = vi.fn();
		render(AnnotationShape, {
			annotation: makeAnnotation(),
			isHovered: false,
			isActive: true,
			showDelete: true,
			onclick,
			onpointerenter: () => {},
			onpointerleave: () => {}
		});

		const annotationEl = page.getByTestId('annotation-ann-1').element() as HTMLElement;
		annotationEl.dispatchEvent(new KeyboardEvent('keydown', { key: 'Delete', bubbles: true }));

		// No side effect from the shape; it stays in the document for the action to act on.
		expect(onclick).not.toHaveBeenCalled();
		await expect.element(page.getByTestId('annotation-ann-1')).toBeInTheDocument();
	});

	it('announces the Delete affordance via aria when deletion is available', async () => {
		render(AnnotationShape, {
			annotation: makeAnnotation(),
			isHovered: false,
			isActive: true,
			showDelete: true,
			onclick: () => {},
			onpointerenter: () => {},
			onpointerleave: () => {}
		});

		const annotationEl = page.getByTestId('annotation-ann-1').element() as HTMLElement;
		expect(annotationEl.getAttribute('aria-keyshortcuts')).toBe('Delete');
		expect(annotationEl.getAttribute('aria-label')).toContain('Entf');
	});

	it('keeps the plain label and no key hint when deletion is unavailable', async () => {
		render(AnnotationShape, {
			annotation: makeAnnotation(),
			isHovered: false,
			isActive: false,
			showDelete: false,
			onclick: () => {},
			onpointerenter: () => {},
			onpointerleave: () => {}
		});

		const annotationEl = page.getByTestId('annotation-ann-1').element() as HTMLElement;
		expect(annotationEl.getAttribute('aria-keyshortcuts')).toBe(null);
		expect(annotationEl.getAttribute('aria-label')).toBe('Block anzeigen');
	});

	it('calls onfocus when the annotation receives focus', async () => {
		const onfocus = vi.fn();
		render(AnnotationShape, {
			annotation: makeAnnotation(),
			isHovered: false,
			isActive: false,
			onfocus,
			onclick: () => {},
			onpointerenter: () => {},
			onpointerleave: () => {}
		});

		const annotationEl = page.getByTestId('annotation-ann-1').element() as HTMLElement;
		annotationEl.dispatchEvent(new FocusEvent('focus'));

		expect(onfocus).toHaveBeenCalledOnce();
	});

	// Integration: a real rendered shape + the live transcribeShortcuts action.
	// Pressing Delete on the focused region must delete exactly once — proving the
	// action is the single owner and the shape contributes no competing handler.
	it('with the transcribeShortcuts action active, Delete deletes the focused region exactly once', () => {
		const deleteCurrentRegion = vi.fn();
		render(AnnotationShape, {
			annotation: makeAnnotation(),
			isHovered: false,
			isActive: true,
			showDelete: true,
			onclick: () => {},
			onpointerenter: () => {},
			onpointerleave: () => {}
		});

		const annotationEl = page.getByTestId('annotation-ann-1').element() as HTMLElement;
		const action = transcribeShortcuts(annotationEl, noopShortcutOptions({ deleteCurrentRegion }));

		annotationEl.focus();
		annotationEl.dispatchEvent(new KeyboardEvent('keydown', { key: 'Delete', bubbles: true }));

		expect(deleteCurrentRegion).toHaveBeenCalledTimes(1);
		action.destroy();
	});
});
