<script lang="ts">
import { getContext } from 'svelte';
import type { Annotation } from '$lib/shared/types';
import { m } from '$lib/paraglide/messages.js';

type UpdateAnnotationFn = (
	id: string,
	coords: { x: number; y: number; width: number; height: number }
) => Promise<void>;

const updateAnnotation: UpdateAnnotationFn =
	getContext('annotationUpdate') ?? (() => Promise.resolve());

let { annotation }: { annotation: Annotation } = $props();

let liveX = $state<number>(0);
let liveY = $state<number>(0);
let liveWidth = $state<number>(0);
let liveHeight = $state<number>(0);

$effect(() => {
	liveX = annotation.x;
	liveY = annotation.y;
	liveWidth = annotation.width;
	liveHeight = annotation.height;
});

let svgEl = $state<SVGSVGElement | null>(null);

// Actual rendered pixel dimensions of the SVG — updated by ResizeObserver.
// Used as the viewBox so handles are always physically 16×16px regardless of annotation aspect ratio.
let svgWidth = $state(1);
let svgHeight = $state(1);

$effect(() => {
	if (!svgEl) return;
	const ro = new ResizeObserver(([entry]) => {
		svgWidth = entry.contentRect.width || 1;
		svgHeight = entry.contentRect.height || 1;
	});
	ro.observe(svgEl);
	return () => ro.disconnect();
});

// Auto-focus the SVG when the overlay mounts so arrow keys work immediately.
$effect(() => {
	svgEl?.focus({ preventScroll: true });
});

type HandleId = 'nw' | 'ne' | 'sw' | 'se' | 'n' | 's' | 'e' | 'w';

// L-bracket arm length in pixels. Each corner shows two short lines meeting at 90°.
const ARM = 10;

type DragState = {
	type: 'handle' | 'move';
	handleId?: HandleId;
	startPointerX: number;
	startPointerY: number;
	preDragX: number;
	preDragY: number;
	preDragWidth: number;
	preDragHeight: number;
};

let dragState = $state<DragState | null>(null);

// 8 handles: 4 L-bracket corners + 4 tick-mark edge midpoints.
// Each `path` is relative to the handle centre (0,0).
const handles = $derived<
	Array<{ id: HandleId; cx: number; cy: number; cursor: string; path: string }>
>([
	{ id: 'nw', cx: 0, cy: 0, cursor: 'nwse-resize', path: `M ${ARM},0 L 0,0 L 0,${ARM}` },
	{ id: 'ne', cx: svgWidth, cy: 0, cursor: 'nesw-resize', path: `M ${-ARM},0 L 0,0 L 0,${ARM}` },
	{ id: 'sw', cx: 0, cy: svgHeight, cursor: 'nesw-resize', path: `M ${ARM},0 L 0,0 L 0,${-ARM}` },
	{
		id: 'se',
		cx: svgWidth,
		cy: svgHeight,
		cursor: 'nwse-resize',
		path: `M ${-ARM},0 L 0,0 L 0,${-ARM}`
	},
	{ id: 'n', cx: svgWidth / 2, cy: 0, cursor: 'ns-resize', path: `M ${-ARM},0 L ${ARM},0` },
	{
		id: 's',
		cx: svgWidth / 2,
		cy: svgHeight,
		cursor: 'ns-resize',
		path: `M ${-ARM},0 L ${ARM},0`
	},
	{ id: 'e', cx: svgWidth, cy: svgHeight / 2, cursor: 'ew-resize', path: `M 0,${-ARM} L 0,${ARM}` },
	{ id: 'w', cx: 0, cy: svgHeight / 2, cursor: 'ew-resize', path: `M 0,${-ARM} L 0,${ARM}` }
]);

function pixelToNorm(dx: number, dy: number): { nx: number; ny: number } {
	if (!svgEl) return { nx: 0, ny: 0 };
	const rect = svgEl.getBoundingClientRect();
	return {
		nx: (dx / rect.width) * annotation.width,
		ny: (dy / rect.height) * annotation.height
	};
}

function applyHandleDrag(handleId: HandleId, nx: number, ny: number, ds: DragState): void {
	const MIN = 0.01;
	let x = ds.preDragX,
		y = ds.preDragY,
		w = ds.preDragWidth,
		h = ds.preDragHeight;

	const movesLeftEdge = handleId === 'nw' || handleId === 'sw' || handleId === 'w';
	const movesRightEdge = handleId === 'ne' || handleId === 'se' || handleId === 'e';
	const movesTopEdge = handleId === 'nw' || handleId === 'ne' || handleId === 'n';
	const movesBottomEdge = handleId === 'sw' || handleId === 'se' || handleId === 's';

	if (movesLeftEdge) {
		const newX = Math.max(0, Math.min(x + w - MIN, x + nx));
		w = w - (newX - x);
		x = newX;
	} else if (movesRightEdge) {
		w = Math.max(MIN, Math.min(1 - x, w + nx));
	}

	if (movesTopEdge) {
		const newY = Math.max(0, Math.min(y + h - MIN, y + ny));
		h = h - (newY - y);
		y = newY;
	} else if (movesBottomEdge) {
		h = Math.max(MIN, Math.min(1 - y, h + ny));
	}

	liveX = x;
	liveY = y;
	liveWidth = w;
	liveHeight = h;
}

function handlePointerDown(
	event: PointerEvent,
	type: 'handle' | 'move',
	handleId?: HandleId
): void {
	if (!event.isPrimary) return;
	event.stopPropagation();
	(event.currentTarget as Element).setPointerCapture(event.pointerId);
	dragState = {
		type,
		handleId,
		startPointerX: event.clientX,
		startPointerY: event.clientY,
		preDragX: liveX,
		preDragY: liveY,
		preDragWidth: liveWidth,
		preDragHeight: liveHeight
	};
}

function handlePointerMove(event: PointerEvent): void {
	if (!dragState || !event.isPrimary) return;
	const dx = event.clientX - dragState.startPointerX;
	const dy = event.clientY - dragState.startPointerY;
	const { nx, ny } = pixelToNorm(dx, dy);

	if (dragState.type === 'move') {
		liveX = Math.max(0, Math.min(1 - dragState.preDragWidth, dragState.preDragX + nx));
		liveY = Math.max(0, Math.min(1 - dragState.preDragHeight, dragState.preDragY + ny));
	} else if (dragState.handleId) {
		applyHandleDrag(dragState.handleId, nx, ny, dragState);
	}
}

async function handlePointerUp(event: PointerEvent): Promise<void> {
	if (!dragState || !event.isPrimary) return;
	const ds = dragState;
	dragState = null;

	if (
		liveX === ds.preDragX &&
		liveY === ds.preDragY &&
		liveWidth === ds.preDragWidth &&
		liveHeight === ds.preDragHeight
	) {
		return;
	}

	try {
		await updateAnnotation(annotation.id, {
			x: liveX,
			y: liveY,
			width: liveWidth,
			height: liveHeight
		});
	} catch (err) {
		console.error('annotation drag update failed', err);
		liveX = ds.preDragX;
		liveY = ds.preDragY;
		liveWidth = ds.preDragWidth;
		liveHeight = ds.preDragHeight;
	}
}

let keyDebounceTimer: ReturnType<typeof setTimeout> | null = null;

function handleKeyDown(event: KeyboardEvent): void {
	if (!['ArrowUp', 'ArrowDown', 'ArrowLeft', 'ArrowRight'].includes(event.key)) return;
	event.preventDefault();
	const STEP = event.shiftKey ? 0.05 : 0.005;

	if (event.key === 'ArrowLeft') liveX = Math.max(0, liveX - STEP);
	if (event.key === 'ArrowRight') liveX = Math.min(1 - liveWidth, liveX + STEP);
	if (event.key === 'ArrowUp') liveY = Math.max(0, liveY - STEP);
	if (event.key === 'ArrowDown') liveY = Math.min(1 - liveHeight, liveY + STEP);

	if (keyDebounceTimer) clearTimeout(keyDebounceTimer);
	keyDebounceTimer = setTimeout(async () => {
		try {
			await updateAnnotation(annotation.id, {
				x: liveX,
				y: liveY,
				width: liveWidth,
				height: liveHeight
			});
		} catch (err) {
			console.error('annotation keyboard update failed', err);
			liveX = annotation.x;
			liveY = annotation.y;
			liveWidth = annotation.width;
			liveHeight = annotation.height;
		}
	}, 300);
}

const directionLabels: Record<HandleId, string> = {
	nw: 'NW',
	ne: 'NE',
	sw: 'SW',
	se: 'SE',
	n: 'N',
	s: 'S',
	e: 'E',
	w: 'W'
};

// Preview rect in pixel space (maps live normalized coords back to SVG pixel coordinates).
// Shown during pointer drag and during keyboard nudging (whenever live coords differ from stored).
let previewX = $derived(((liveX - annotation.x) / annotation.width) * svgWidth);
let previewY = $derived(((liveY - annotation.y) / annotation.height) * svgHeight);
let previewW = $derived((liveWidth / annotation.width) * svgWidth);
let previewH = $derived((liveHeight / annotation.height) * svgHeight);
let hasLiveChanges = $derived(
	liveX !== annotation.x ||
		liveY !== annotation.y ||
		liveWidth !== annotation.width ||
		liveHeight !== annotation.height
);
</script>

<div aria-live="polite" class="sr-only">
	{m.annotation_edit_mode_active()}
</div>

<svg
	bind:this={svgEl}
	viewBox="0 0 {svgWidth} {svgHeight}"
	role="application"
	tabindex="0"
	aria-label={m.annotation_resize_area()}
	style="position: absolute; top: 0; left: 0; width: 100%; height: 100%; pointer-events: none; touch-action: none; overflow: visible;"
	onpointermove={handlePointerMove}
	onpointerup={handlePointerUp}
	onkeydown={handleKeyDown}
>
	<rect
		data-move-area
		role="none"
		x="0"
		y="0"
		width={svgWidth}
		height={svgHeight}
		fill="transparent"
		style="cursor: move; pointer-events: all;"
		onpointerdown={(e) => handlePointerDown(e, 'move')}
	/>

	{#if dragState || hasLiveChanges}
		<rect
			x={previewX}
			y={previewY}
			width={previewW}
			height={previewH}
			fill="none"
			stroke="#002850"
			stroke-width="1.5"
			stroke-dasharray="4 3"
			pointer-events="none"
		/>
	{/if}

	<!-- Dashed selection border — signals the annotation is in edit mode -->
	<rect
		x="0"
		y="0"
		width={svgWidth}
		height={svgHeight}
		fill="none"
		stroke="#002850"
		stroke-width="1"
		stroke-dasharray="4 3"
		opacity="0.6"
		pointer-events="none"
	/>

	{#each handles as handle (handle.id)}
		<g
			data-handle={handle.id}
			role="button"
			tabindex="0"
			aria-label={m.annotation_resize_handle({ direction: directionLabels[handle.id] })}
			transform="translate({handle.cx}, {handle.cy})"
			style="cursor: {handle.cursor}; pointer-events: all;"
			onpointerdown={(e) => handlePointerDown(e, 'handle', handle.id)}
			onkeydown={(e) => {
				if (e.key === 'Enter' || e.key === ' ') e.preventDefault();
			}}
		>
			<rect data-handle-hit x="-22" y="-22" width="44" height="44" fill="transparent" />
			<path d={handle.path} fill="none" stroke="#002850" stroke-width="2" stroke-linecap="square" />
		</g>
	{/each}
</svg>

<style>
svg[role='application']:focus-visible {
	outline: 2px solid #002850;
	outline-offset: 2px;
}

.sr-only {
	position: absolute;
	width: 1px;
	height: 1px;
	padding: 0;
	margin: -1px;
	overflow: hidden;
	clip: rect(0, 0, 0, 0);
	white-space: nowrap;
	border-width: 0;
}
</style>
