<script lang="ts">
	import { type Snippet } from 'svelte';
	import {
		type SizeConstraints,
		type ResizeDirection,
		type DropZoneState,
		type BlockReceiver,
		type BlockShellEvents,
		DEFAULT_CONSTRAINTS,
		MOBILE_BREAKPOINT,
	} from './types.js';
	import { getDragPayload, type DragPayload } from '$lib/transfer.js';

	/**
	 * BlockShell — wrapper component for tool panels on the spatial workspace.
	 *
	 * Provides:
	 * - Header with title, icon, and control buttons (fullscreen, close)
	 * - Drag handle for repositioning (header acts as drag handle)
	 * - Resize handles on edges and corners
	 * - Drop-zone rendering with compatibility feedback
	 * - Responsive: min/max size constraints, mobile-friendly
	 *
	 * Ref: docs/retninger/arbeidsflaten.md
	 * Ref: docs/features/universell_overfoering.md § 8.1
	 */

	interface Props extends BlockShellEvents {
		/** Panel title shown in header */
		title: string;
		/** Optional icon (emoji or text) */
		icon?: string;
		/** Current width (controlled by parent / canvas) */
		width?: number;
		/** Current height (controlled by parent / canvas) */
		height?: number;
		/** Size constraints */
		constraints?: SizeConstraints;
		/** Receiver for drag-and-drop compatibility checks */
		receiver?: BlockReceiver;
		/** Whether to show close button */
		closable?: boolean;
		/** Whether panel is minimized (collapsed to header only) */
		minimized?: boolean;
		/** Extra CSS class */
		class?: string;
		/** Panel content */
		children?: Snippet;
	}

	let {
		title,
		icon,
		width = 400,
		height = 300,
		constraints = DEFAULT_CONSTRAINTS,
		receiver,
		closable = true,
		minimized = false,
		class: extraClass = '',
		children,
		onClose,
		onDragMove,
		onResize,
		onFullscreenChange,
		onMinimizeChange,
		onDrop,
	}: Props = $props();

	// --- State ---
	let isFullscreen = $state(false);
	let dropZoneState = $state<DropZoneState>('idle');
	let dropFeedback = $state<string>('');
	let isResizing = $state(false);
	let isDragging = $state(false);
	let containerEl: HTMLDivElement | undefined = $state();
	let containerWidth = $state(0);

	// Track resize interaction
	let resizeDir: ResizeDirection | null = null;
	let resizeStartX = 0;
	let resizeStartY = 0;
	let resizeStartW = 0;
	let resizeStartH = 0;
	let resizeLastW = 0;
	let resizeLastH = 0;

	// Track drag interaction
	let dragStartX = 0;
	let dragStartY = 0;

	// --- Derived ---
	let isMobile = $derived(containerWidth < MOBILE_BREAKPOINT);

	let clampedWidth = $derived(
		Math.min(constraints.maxWidth, Math.max(constraints.minWidth, width))
	);
	let clampedHeight = $derived(
		Math.min(constraints.maxHeight, Math.max(constraints.minHeight, height))
	);

	// --- Viewport tracking ---
	$effect(() => {
		if (!containerEl) return;
		// Use parent's width for mobile detection
		const ro = new ResizeObserver((entries) => {
			for (const entry of entries) {
				containerWidth = entry.contentRect.width;
			}
		});
		// Observe parent element or self
		const target = containerEl.parentElement ?? containerEl;
		ro.observe(target);
		return () => ro.disconnect();
	});

	// --- Keyboard: Escape exits fullscreen ---
	$effect(() => {
		if (!isFullscreen) return;
		function onKeyDown(e: KeyboardEvent) {
			if (e.code === 'Escape') {
				isFullscreen = false;
				onFullscreenChange?.(false);
			}
		}
		window.addEventListener('keydown', onKeyDown);
		return () => window.removeEventListener('keydown', onKeyDown);
	});

	// --- Fullscreen toggle ---
	// Fullscreen panels must escape Canvas's transform context,
	// which breaks position:fixed. We move the DOM element to
	// document.body when entering fullscreen, and back when exiting.
	let originalParent: HTMLElement | null = null;
	let originalNextSibling: Node | null = null;

	function toggleFullscreen() {
		isFullscreen = !isFullscreen;
		if (containerEl) {
			if (isFullscreen) {
				originalParent = containerEl.parentElement;
				originalNextSibling = containerEl.nextSibling;
				document.body.appendChild(containerEl);
			} else if (originalParent) {
				if (originalNextSibling) {
					originalParent.insertBefore(containerEl, originalNextSibling);
				} else {
					originalParent.appendChild(containerEl);
				}
				originalParent = null;
				originalNextSibling = null;
			}
		}
		onFullscreenChange?.(isFullscreen);
	}

	// --- Minimize toggle (double-click header) ---
	let hasDragged = false;

	function handleHeaderDblClick() {
		if (hasDragged) return;
		if (isFullscreen) return;
		onMinimizeChange?.(!minimized);
	}

	// --- Drag (repositioning via header) ---
	function handleDragStart(e: PointerEvent) {
		// Only left button, not on buttons or controls
		if (e.button !== 0) return;
		if ((e.target as HTMLElement).closest('button')) return;
		if ((e.target as HTMLElement).closest('.blockshell-controls')) return;

		isDragging = true;
		hasDragged = false;
		dragStartX = e.clientX;
		dragStartY = e.clientY;

		const el = e.currentTarget as HTMLElement;
		el.setPointerCapture(e.pointerId);
		e.preventDefault();
	}

	function handleDragMove(e: PointerEvent) {
		if (!isDragging) return;
		const dx = e.clientX - dragStartX;
		const dy = e.clientY - dragStartY;
		if (Math.abs(dx) > 3 || Math.abs(dy) > 3) {
			hasDragged = true;
		}
		dragStartX = e.clientX;
		dragStartY = e.clientY;
		onDragMove?.(dx, dy);
	}

	function handleDragEnd(_e: PointerEvent) {
		isDragging = false;
	}

	// --- Resize handles ---
	function handleResizeStart(dir: ResizeDirection, e: PointerEvent) {
		if (e.button !== 0 || isFullscreen) return;

		isResizing = true;
		resizeDir = dir;
		resizeStartX = e.clientX;
		resizeStartY = e.clientY;
		resizeStartW = clampedWidth;
		resizeStartH = clampedHeight;

		resizeLastW = clampedWidth;
		resizeLastH = clampedHeight;

		// Capture on the document level for smooth resizing
		window.addEventListener('pointermove', handleResizeMove);
		window.addEventListener('pointerup', handleResizeEnd);
		e.preventDefault();
		e.stopPropagation();
	}

	function handleResizeMove(e: PointerEvent) {
		if (!isResizing || !resizeDir) return;

		const dx = e.clientX - resizeStartX;
		const dy = e.clientY - resizeStartY;

		let newW = resizeStartW;
		let newH = resizeStartH;

		// Horizontal
		if (resizeDir.includes('e')) newW = resizeStartW + dx;
		if (resizeDir.includes('w')) newW = resizeStartW - dx;

		// Vertical
		if (resizeDir.includes('s')) newH = resizeStartH + dy;
		if (resizeDir.includes('n')) newH = resizeStartH - dy;

		// Clamp
		const clampedW = Math.min(constraints.maxWidth, Math.max(constraints.minWidth, newW));
		const clampedH = Math.min(constraints.maxHeight, Math.max(constraints.minHeight, newH));

		// Incremental position delta for n/w dragging
		let posDx = 0;
		let posDy = 0;
		if (resizeDir.includes('w')) posDx = resizeLastW - clampedW;
		if (resizeDir.includes('n')) posDy = resizeLastH - clampedH;

		resizeLastW = clampedW;
		resizeLastH = clampedH;

		onResize?.(clampedW, clampedH, posDx, posDy);
	}

	function handleResizeEnd() {
		isResizing = false;
		resizeDir = null;
		window.removeEventListener('pointermove', handleResizeMove);
		window.removeEventListener('pointerup', handleResizeEnd);
	}

	// --- Drop zone (HTML5 drag-and-drop between panels) ---
	let dragOverCount = 0; // Track nested enter/leave

	function handleDragEnter(e: DragEvent) {
		e.preventDefault();
		dragOverCount++;
		if (dragOverCount === 1) {
			updateDropState(e);
		}
	}

	function handleDragOver(e: DragEvent) {
		e.preventDefault();
		// Set dropEffect based on compatibility
		if (e.dataTransfer) {
			e.dataTransfer.dropEffect = dropZoneState === 'compatible' ? 'copy' : 'none';
		}
	}

	function handleDragLeave(e: DragEvent) {
		e.preventDefault();
		dragOverCount--;
		if (dragOverCount <= 0) {
			dragOverCount = 0;
			dropZoneState = 'idle';
			dropFeedback = '';
		}
	}

	function handleDropEvent(e: DragEvent) {
		e.preventDefault();
		dragOverCount = 0;

		if (dropZoneState === 'compatible' && e.dataTransfer) {
			const payload = getDragPayload(e.dataTransfer);
			if (payload) {
				// Validate on actual drop (we couldn't read payload during drag)
				if (receiver) {
					const result = receiver.canReceive(payload);
					if (!result.compatible) {
						// Show incompatible feedback briefly
						dropZoneState = 'incompatible';
						dropFeedback = result.reason ?? 'Kan ikke motta dette innholdet';
						setTimeout(() => {
							dropZoneState = 'idle';
							dropFeedback = '';
						}, 1500);
						return;
					}
				}
				// Pass shiftKey so transfer service can override mode
				onDrop?.(payload, e.shiftKey);
			}
		}

		dropZoneState = 'idle';
		dropFeedback = '';
	}

	function updateDropState(e: DragEvent) {
		if (!receiver || !e.dataTransfer) {
			dropZoneState = 'idle';
			return;
		}

		// During dragenter we can't read payload data (security restriction),
		// but we can check types to see if it's a synops transfer
		const types = e.dataTransfer.types;
		const hasSynopsPayload = types.includes('application/x-synops-transfer');

		if (!hasSynopsPayload) {
			dropZoneState = 'idle';
			return;
		}

		// We can't fully validate during drag (security), so show as compatible
		// and validate on actual drop. For now, show compatible state.
		dropZoneState = 'compatible';
		dropFeedback = '';
	}

	// Cursor style for resize directions
	function resizeCursor(dir: ResizeDirection): string {
		const cursors: Record<ResizeDirection, string> = {
			n: 'ns-resize', s: 'ns-resize',
			e: 'ew-resize', w: 'ew-resize',
			ne: 'nesw-resize', sw: 'nesw-resize',
			nw: 'nwse-resize', se: 'nwse-resize',
		};
		return cursors[dir];
	}

	const resizeDirections: ResizeDirection[] = ['n', 's', 'e', 'w', 'ne', 'nw', 'se', 'sw'];
</script>

<!-- svelte-ignore a11y_no_static_element_interactions -->
<div
	bind:this={containerEl}
	class="blockshell {extraClass}"
	class:blockshell-fullscreen={isFullscreen}
	class:blockshell-minimized={minimized && !isFullscreen}
	class:blockshell-dragging={isDragging}
	class:blockshell-resizing={isResizing}
	class:blockshell-drop-compatible={dropZoneState === 'compatible'}
	class:blockshell-drop-incompatible={dropZoneState === 'incompatible'}
	style:width={isFullscreen ? undefined : `${clampedWidth}px`}
	style:height={isFullscreen ? undefined : minimized ? undefined : `${clampedHeight}px`}
	ondragenter={handleDragEnter}
	ondragover={handleDragOver}
	ondragleave={handleDragLeave}
	ondrop={handleDropEvent}
	role="region"
	aria-label={title}
>
	<!-- Header / drag handle -->
	<!-- svelte-ignore a11y_no_static_element_interactions -->
	<div
		class="blockshell-header"
		onpointerdown={handleDragStart}
		onpointermove={handleDragMove}
		onpointerup={handleDragEnd}
		ondblclick={handleHeaderDblClick}
	>
		<div class="blockshell-header-title">
			{#if icon}
				<span class="blockshell-icon" aria-hidden="true">{icon}</span>
			{/if}
			<span class="blockshell-title">{title}</span>
		</div>

		<!-- svelte-ignore a11y_no_static_element_interactions -->
		<div class="blockshell-controls" onpointerdown={(e) => e.stopPropagation()}>
			{#if !isFullscreen}
				<button
					class="blockshell-btn"
					onclick={(e) => { e.stopPropagation(); onMinimizeChange?.(!minimized); }}
					title={minimized ? 'Gjenopprett' : 'Minimer'}
					aria-label={minimized ? 'Gjenopprett panel' : 'Minimer panel'}
				>
					{minimized ? '+' : '−'}
				</button>
			{/if}
			<button
				class="blockshell-btn"
				onclick={(e) => { e.stopPropagation(); toggleFullscreen(); }}
				title={isFullscreen ? 'Avslutt fullskjerm' : 'Fullskjerm'}
				aria-label={isFullscreen ? 'Avslutt fullskjerm' : 'Fullskjerm'}
			>
				{isFullscreen ? '−' : '+'}
			</button>
			{#if closable}
				<button
					class="blockshell-btn blockshell-btn-close"
					onclick={(e) => {
					e.stopPropagation();
					// Flytt tilbake fra body før Svelte fjerner komponenten
					if (isFullscreen && containerEl && originalParent) {
						if (originalNextSibling) {
							originalParent.insertBefore(containerEl, originalNextSibling);
						} else {
							originalParent.appendChild(containerEl);
						}
						originalParent = null;
						originalNextSibling = null;
					}
					isFullscreen = false;
					onClose?.();
				}}
					title="Lukk"
					aria-label="Lukk panel"
				>
					×
				</button>
			{/if}
		</div>
	</div>

	<!-- Content area (hidden when minimized) -->
	{#if !minimized || isFullscreen}
		<!-- svelte-ignore a11y_no_static_element_interactions -->
		<div
			class="blockshell-content"
			onwheel={(e) => {
				if (e.ctrlKey || e.metaKey) {
					e.preventDefault();
					e.stopPropagation();
					const factor = e.deltaY > 0 ? 0.95 : 1.05;
					const newW = Math.round(clampedWidth * factor);
					const newH = Math.round(clampedHeight * factor);
					onResize?.(newW, newH);
				}
			}}
		>
			{#if children}
				{@render children()}
			{:else}
				<p class="blockshell-empty">Ingen innhold ennå.</p>
			{/if}
		</div>
	{/if}

	<!-- Drop zone overlay -->
	{#if dropZoneState !== 'idle'}
		<div class="blockshell-drop-overlay">
			{#if dropZoneState === 'compatible'}
				<span class="blockshell-drop-label">Slipp her</span>
			{:else if dropZoneState === 'incompatible'}
				<span class="blockshell-drop-label blockshell-drop-label-bad">
					{dropFeedback || 'Kan ikke motta dette innholdet'}
				</span>
			{/if}
		</div>
	{/if}

	<!-- Resize handles (hidden in fullscreen, mobile, and minimized) -->
	{#if !isFullscreen && !minimized}
		{#each resizeDirections as dir}
			<!-- svelte-ignore a11y_no_static_element_interactions -->
			<div
				class="blockshell-resize blockshell-resize-{dir}"
				style:cursor={resizeCursor(dir)}
				onpointerdown={(e) => handleResizeStart(dir, e)}
			></div>
		{/each}
	{/if}
</div>

<style>
	.blockshell {
		position: relative;
		display: flex;
		flex-direction: column;
		background: var(--color-panel, var(--color-surface, #1c1c20));
		border: 1px solid var(--color-border, #2a2a2e);
		border-radius: 8px;
		box-shadow: 0 1px 3px rgba(0, 0, 0, 0.08);
		overflow: visible;
	}

	.blockshell-fullscreen {
		position: fixed;
		inset: 0;
		z-index: 9999;
		width: 100% !important;
		height: 100% !important;
		border-radius: 0;
		border: none;
		background: var(--color-bg, #0a0a0b);
	}

	.blockshell-minimized {
		height: auto !important;
		min-height: 0;
	}

	.blockshell-minimized .blockshell-header {
		border-bottom: none;
	}

	.blockshell-dragging {
		opacity: 0.95;
		box-shadow: 0 4px 16px rgba(0, 0, 0, 0.15);
	}

	.blockshell-resizing {
		user-select: none;
	}

	/* --- Drop zone visual feedback --- */
	.blockshell-drop-compatible {
		border-color: #3b82f6;
		box-shadow: 0 0 0 2px rgba(59, 130, 246, 0.3), 0 0 12px rgba(59, 130, 246, 0.15);
	}

	.blockshell-drop-incompatible {
		border-color: #ef4444;
		box-shadow: 0 0 0 2px rgba(239, 68, 68, 0.3);
	}

	/* --- Header --- */
	.blockshell-header {
		display: flex;
		align-items: center;
		justify-content: space-between;
		gap: 8px;
		padding: 6px 8px 6px 12px;
		border-bottom: 1px solid var(--color-border, #2a2a2e);
		cursor: grab;
		user-select: none;
		flex-shrink: 0;
		min-height: 36px;
		background: var(--color-bg, #141416);
		border-radius: 8px 8px 0 0;
	}

	.blockshell-dragging .blockshell-header {
		cursor: grabbing;
	}

	.blockshell-header-title {
		display: flex;
		align-items: center;
		gap: 6px;
		min-width: 0;
		overflow: hidden;
	}

	.blockshell-icon {
		font-size: 14px;
		flex-shrink: 0;
	}

	.blockshell-title {
		font-size: 13px;
		font-weight: 600;
		color: #8a8a96;
		white-space: nowrap;
		overflow: hidden;
		text-overflow: ellipsis;
	}

	.blockshell-controls {
		display: flex;
		align-items: center;
		gap: 2px;
		flex-shrink: 0;
	}

	.blockshell-btn {
		display: flex;
		align-items: center;
		justify-content: center;
		width: 26px;
		height: 26px;
		border: none;
		background: transparent;
		border-radius: 4px;
		cursor: pointer;
		font-size: 16px;
		color: #8a8a96;
		transition: background 0.12s, color 0.12s;
	}

	.blockshell-btn:hover {
		background: #242428;
		color: #8a8a96;
	}

	.blockshell-btn-close:hover {
		background: #fee2e2;
		color: #dc2626;
	}

	/* --- Content --- */
	.blockshell-content {
		flex: 1;
		overflow: auto;
		min-height: 0;
		border-radius: 0 0 8px 8px;
	}

	.blockshell-empty {
		padding: 16px;
		text-align: center;
		font-size: 13px;
		color: #5a5a66;
	}

	/* --- Drop overlay --- */
	.blockshell-drop-overlay {
		position: absolute;
		inset: 0;
		display: flex;
		align-items: center;
		justify-content: center;
		background: rgba(59, 130, 246, 0.06);
		pointer-events: none;
		z-index: 10;
		border-radius: 7px;
	}

	.blockshell-drop-incompatible .blockshell-drop-overlay {
		background: rgba(239, 68, 68, 0.06);
	}

	.blockshell-drop-label {
		padding: 6px 14px;
		background: rgba(59, 130, 246, 0.9);
		color: white;
		font-size: 13px;
		font-weight: 500;
		border-radius: 6px;
	}

	.blockshell-drop-label-bad {
		background: rgba(239, 68, 68, 0.9);
	}

	/* --- Resize handles --- */
	.blockshell-resize {
		position: absolute;
		z-index: 5;
	}

	/* Resize-handles: kun cursor-endring, ingen synlige indikatorer */

	/* Edge handles */
	.blockshell-resize-n {
		top: -3px;
		left: 8px;
		right: 8px;
		height: 6px;
	}

	.blockshell-resize-s {
		bottom: -3px;
		left: 8px;
		right: 8px;
		height: 6px;
	}

	.blockshell-resize-e {
		right: -3px;
		top: 8px;
		bottom: 8px;
		width: 6px;
	}

	.blockshell-resize-w {
		left: -3px;
		top: 8px;
		bottom: 8px;
		width: 6px;
	}

	/* Corner handles */
	.blockshell-resize-ne {
		top: -3px;
		right: -3px;
		width: 12px;
		height: 12px;
	}

	.blockshell-resize-nw {
		top: -3px;
		left: -3px;
		width: 12px;
		height: 12px;
	}

	.blockshell-resize-se {
		bottom: -3px;
		right: -3px;
		width: 12px;
		height: 12px;
	}

	.blockshell-resize-sw {
		bottom: -3px;
		left: -3px;
		width: 12px;
		height: 12px;
	}

	/* --- Responsive --- */
	@media (max-width: 768px) {
		.blockshell {
			border-radius: 0;
			border-left: none;
			border-right: none;
			width: 100% !important;
		}

		.blockshell-header {
			padding: 8px 12px;
			min-height: 44px;
		}

		.blockshell-btn {
			width: 36px;
			height: 36px;
			font-size: 20px;
		}

		.blockshell-title {
			font-size: 15px;
		}
	}

	@media (min-width: 769px) and (max-width: 1024px) {
		.blockshell-header {
			min-height: 40px;
		}

		.blockshell-btn {
			width: 30px;
			height: 30px;
		}
	}
</style>
