import { describe, it, expect } from 'vitest';
import { render } from 'vitest-browser-svelte';
import { page } from 'vitest/browser';
import AnnotationLayer from './AnnotationLayer.svelte';
import type { Annotation } from '$lib/shared/types';

const annotation: Annotation = {
	id: 'ann-1',
	documentId: 'doc-1',
	pageNumber: 1,
	x: 0.1,
	y: 0.2,
	width: 0.3,
	height: 0.1,
	color: '#00c7b1',
	createdAt: '2026-01-01T00:00:00Z'
};

const polygonAnnotation: Annotation = {
	...annotation,
	id: 'ann-poly',
	polygon: [
		[0.1, 0.2],
		[0.4, 0.21],
		[0.39, 0.29],
		[0.11, 0.28]
	]
};

describe('AnnotationLayer', () => {
	describe('dimmed prop', () => {
		it('should hide block number badges when dimmed is true', async () => {
			render(AnnotationLayer, {
				annotations: [annotation],
				canDraw: false,
				color: '#00c7b1',
				blockNumbers: { 'ann-1': 1 },
				dimmed: true,
				onDraw: () => {}
			});

			const badge = page.getByText('1');
			await expect.element(badge).not.toBeInTheDocument();
		});

		it('should show block number badges when dimmed is false', async () => {
			render(AnnotationLayer, {
				annotations: [annotation],
				canDraw: false,
				color: '#00c7b1',
				blockNumbers: { 'ann-1': 1 },
				dimmed: false,
				onDraw: () => {}
			});

			const badge = page.getByText('1');
			await expect.element(badge).toBeInTheDocument();
		});

		it('should still fire onAnnotationClick when dimmed', async () => {
			let clickedId: string | undefined;
			render(AnnotationLayer, {
				annotations: [annotation],
				canDraw: false,
				color: '#00c7b1',
				dimmed: true,
				onDraw: () => {},
				onAnnotationClick: (id: string) => {
					clickedId = id;
				}
			});

			const el = document.querySelector('[data-testid="annotation-ann-1"]')!;
			el.dispatchEvent(new MouseEvent('click', { bubbles: true }));
			expect(clickedId).toBe('ann-1');
		});
	});

	describe('isResizable computation', () => {
		it('passes isResizable=true when canDraw, annotation is active, and has no polygon', async () => {
			render(AnnotationLayer, {
				annotations: [annotation],
				canDraw: true,
				color: '#00c7b1',
				activeAnnotationId: 'ann-1',
				onDraw: () => {}
			});

			const handles = document.querySelectorAll('[data-handle]');
			expect(handles).toHaveLength(8);
		});

		it('passes isResizable=false when annotation has a polygon', async () => {
			render(AnnotationLayer, {
				annotations: [polygonAnnotation],
				canDraw: true,
				color: '#00c7b1',
				activeAnnotationId: 'ann-poly',
				onDraw: () => {}
			});

			const handles = document.querySelectorAll('[data-handle]');
			expect(handles).toHaveLength(0);
		});

		it('passes isResizable=false when canDraw is false', async () => {
			render(AnnotationLayer, {
				annotations: [annotation],
				canDraw: false,
				color: '#00c7b1',
				activeAnnotationId: 'ann-1',
				onDraw: () => {}
			});

			const handles = document.querySelectorAll('[data-handle]');
			expect(handles).toHaveLength(0);
		});

		it('passes isResizable=false when annotation is not active', async () => {
			render(AnnotationLayer, {
				annotations: [annotation],
				canDraw: true,
				color: '#00c7b1',
				activeAnnotationId: 'other-id',
				onDraw: () => {}
			});

			const handles = document.querySelectorAll('[data-handle]');
			expect(handles).toHaveLength(0);
		});
	});

	describe('flashAnnotationId prop', () => {
		it('should apply annotation-flash class when flashAnnotationId matches', async () => {
			render(AnnotationLayer, {
				annotations: [annotation],
				canDraw: false,
				color: '#00c7b1',
				flashAnnotationId: 'ann-1',
				onDraw: () => {}
			});

			const el = document.querySelector('[data-testid="annotation-ann-1"]')!;
			expect(el.classList.contains('annotation-flash')).toBe(true);
		});

		it('should not apply annotation-flash class when flashAnnotationId does not match', async () => {
			render(AnnotationLayer, {
				annotations: [annotation],
				canDraw: false,
				color: '#00c7b1',
				flashAnnotationId: 'other-id',
				onDraw: () => {}
			});

			const el = document.querySelector('[data-testid="annotation-ann-1"]')!;
			expect(el.classList.contains('annotation-flash')).toBe(false);
		});
	});

	describe('container style', () => {
		it('uses crosshair cursor when canDraw is true', async () => {
			render(AnnotationLayer, {
				annotations: [],
				canDraw: true,
				color: '#00c7b1',
				onDraw: () => {}
			});

			const wrapper = document.querySelector('[role="presentation"]') as HTMLElement;
			expect(wrapper.style.cursor).toContain('crosshair');
			expect(wrapper.style.touchAction).toBe('none');
		});

		it('omits crosshair cursor when canDraw is false', async () => {
			render(AnnotationLayer, {
				annotations: [],
				canDraw: false,
				color: '#00c7b1',
				onDraw: () => {}
			});

			const wrapper = document.querySelector('[role="presentation"]') as HTMLElement;
			expect(wrapper.style.cursor).not.toContain('crosshair');
		});
	});

	describe('annotation pointer hover', () => {
		it('updates hoveredId on pointerenter and clears on pointerleave', async () => {
			render(AnnotationLayer, {
				annotations: [annotation],
				canDraw: false,
				color: '#00c7b1',
				onDraw: () => {}
			});

			const ann = document.querySelector('[data-testid="annotation-ann-1"]') as HTMLElement;
			ann.dispatchEvent(new PointerEvent('pointerenter', { bubbles: true }));
			await new Promise((r) => setTimeout(r, 30));
			ann.dispatchEvent(new PointerEvent('pointerleave', { bubbles: true }));
			await new Promise((r) => setTimeout(r, 30));
			// No throw is the assertion
			expect(true).toBe(true);
		});

		it('renders both annotations with activeAnnotationId set', async () => {
			const second: Annotation = {
				...annotation,
				id: 'ann-other',
				x: 0.5,
				y: 0.5
			};
			render(AnnotationLayer, {
				annotations: [annotation, second],
				canDraw: false,
				color: '#00c7b1',
				activeAnnotationId: 'ann-1',
				dimmed: false,
				onDraw: () => {}
			});

			const otherEl = document.querySelector('[data-testid="annotation-ann-other"]');
			const activeEl = document.querySelector('[data-testid="annotation-ann-1"]');
			expect(otherEl).not.toBeNull();
			expect(activeEl).not.toBeNull();
		});

		it('skips faded styling when dimmed is true (dimmed wins over faded)', async () => {
			const second: Annotation = { ...annotation, id: 'ann-other' };
			render(AnnotationLayer, {
				annotations: [annotation, second],
				canDraw: false,
				color: '#00c7b1',
				activeAnnotationId: 'ann-1',
				dimmed: true,
				onDraw: () => {}
			});

			// Dimmed mode: badge hidden but renders
			expect(document.querySelector('[data-testid="annotation-ann-1"]')).not.toBeNull();
		});

		it('renders without throwing when canDraw is true (no delete button)', async () => {
			expect(() =>
				render(AnnotationLayer, {
					annotations: [annotation],
					canDraw: true,
					color: '#00c7b1',
					onDraw: () => {}
				})
			).not.toThrow();
		});

		it('renders without throwing when blockNumbers map has entries', async () => {
			expect(() =>
				render(AnnotationLayer, {
					annotations: [annotation],
					canDraw: false,
					color: '#00c7b1',
					blockNumbers: { 'ann-1': 5 },
					onDraw: () => {}
				})
			).not.toThrow();
			expect(document.body.textContent).toContain('5');
		});
	});

	describe('drawing pointer flow', () => {
		it('does not start a draw when canDraw is false', async () => {
			render(AnnotationLayer, {
				annotations: [],
				canDraw: false,
				color: '#00c7b1',
				onDraw: () => {}
			});

			const wrapper = document.querySelector('[role="presentation"]') as HTMLElement;
			(wrapper as unknown as { setPointerCapture: (id: number) => void }).setPointerCapture =
				() => {};

			wrapper.dispatchEvent(
				new PointerEvent('pointerdown', {
					bubbles: true,
					clientX: 50,
					clientY: 50,
					pointerId: 1
				})
			);

			// No preview rect rendered
			const preview = wrapper.querySelector('div[style*="border: 2px dashed"]');
			expect(preview).toBeNull();
		});

		it('does not start a draw when pointerdown lands on an existing annotation', async () => {
			render(AnnotationLayer, {
				annotations: [annotation],
				canDraw: true,
				color: '#00c7b1',
				onDraw: () => {}
			});

			const ann = document.querySelector('[data-testid="annotation-ann-1"]') as HTMLElement;
			(ann as unknown as { setPointerCapture: (id: number) => void }).setPointerCapture = () => {};

			// pointerdown bubbles to the layer; layer should refuse to draw because
			// closest('[data-annotation]') matches.
			ann.dispatchEvent(
				new PointerEvent('pointerdown', {
					bubbles: true,
					clientX: 0,
					clientY: 0,
					pointerId: 1
				})
			);

			const preview = document.querySelector('div[style*="border: 2px dashed"]');
			expect(preview).toBeNull();
		});

		it('renders no preview rect when no draw is in progress', async () => {
			render(AnnotationLayer, {
				annotations: [],
				canDraw: true,
				color: '#00c7b1',
				onDraw: () => {}
			});

			const preview = document.querySelector('div[style*="border: 2px dashed"]');
			expect(preview).toBeNull();
		});

		it('handles pointermove without a started draw (early-return)', async () => {
			render(AnnotationLayer, {
				annotations: [],
				canDraw: true,
				color: '#00c7b1',
				onDraw: () => {}
			});

			const wrapper = document.querySelector('[role="presentation"]') as HTMLElement;
			expect(() =>
				wrapper.dispatchEvent(
					new PointerEvent('pointermove', { bubbles: true, clientX: 0, clientY: 0 })
				)
			).not.toThrow();
		});

		it('handles pointerup without a started draw (early-return)', async () => {
			let drawn = false;
			render(AnnotationLayer, {
				annotations: [],
				canDraw: true,
				color: '#00c7b1',
				onDraw: () => {
					drawn = true;
				}
			});

			const wrapper = document.querySelector('[role="presentation"]') as HTMLElement;
			wrapper.dispatchEvent(
				new PointerEvent('pointerup', { bubbles: true, clientX: 0, clientY: 0 })
			);

			expect(drawn).toBe(false);
		});
	});
});
