<script lang="ts">
	import { page } from '$app/stores';
	import { connectionState, nodeStore, edgeStore } from '$lib/spacetime';
	import {
		casUrl,
		audioAnalyze,
		audioProcess,
		fetchSegments,
		createNode,
		createEdge,
		type EdlOperation,
		type EdlDocument,
		type LoudnessInfo,
		type SilenceRegion,
		type AudioInfo,
		type Segment,
	} from '$lib/api';
	import StudioWaveform from '$lib/components/studio/StudioWaveform.svelte';
	import OperationPanel from '$lib/components/studio/OperationPanel.svelte';
	import RenderDialog from '$lib/components/studio/RenderDialog.svelte';

	const session = $derived($page.data.session as Record<string, unknown> | undefined);
	const accessToken = $derived(session?.accessToken as string | undefined);
	const nodeId = $derived(session?.nodeId as string | undefined);
	const connected = $derived(connectionState.current === 'connected');
	const mediaNodeId = $derived($page.params.id ?? '');

	// Media node from store
	const mediaNode = $derived(connected ? nodeStore.get(mediaNodeId) : undefined);
	const metadata = $derived.by(() => {
		if (!mediaNode?.metadata) return null;
		try {
			return JSON.parse(mediaNode.metadata);
		} catch {
			console.error('Ugyldig metadata JSON for medienode:', mediaNodeId);
			return null;
		}
	});
	const casHash = $derived(metadata?.cas_hash as string | undefined);
	const audioSrc = $derived(casHash ? casUrl(casHash) : '');

	// Studio state
	let operations: EdlOperation[] = $state([]);
	let loudness: LoudnessInfo | null = $state(null);
	let silenceRegions: SilenceRegion[] = $state([]);
	let audioInfo: AudioInfo | null = $state(null);
	let analyzing = $state(false);
	let waveformRef: StudioWaveform | undefined = $state();
	let currentTime = $state(0);

	// Render state
	let showRenderDialog = $state(false);
	let rendering = $state(false);
	let renderJobId: string | null = $state(null);
	let resultNodeId: string | null = $state(null);
	let renderError: string | null = $state(null);

	// Polling cleanup
	let pollIntervalId: ReturnType<typeof setInterval> | null = null;
	let pollTimeoutId: ReturnType<typeof setTimeout> | null = null;

	function cleanupPolling() {
		if (pollIntervalId !== null) { clearInterval(pollIntervalId); pollIntervalId = null; }
		if (pollTimeoutId !== null) { clearTimeout(pollTimeoutId); pollTimeoutId = null; }
	}

	// Mobile tool panel sheet
	let showToolSheet = $state(false);

	// Session persistence
	let sessionNodeId: string | null = $state(null);
	let saving = $state(false);

	// Version history: processed nodes derived from this media node
	const versions = $derived.by(() => {
		if (!connected || !mediaNodeId) return [];
		const nodes: { id: string; title: string; createdAt: bigint }[] = [];
		for (const edge of edgeStore.byTarget(mediaNodeId)) {
			if (edge.edgeType !== 'derived_from') continue;
			const node = nodeStore.get(edge.sourceId);
			if (node) {
				nodes.push({
					id: node.id,
					title: node.title ?? 'Prosessert',
					createdAt: node.createdAt?.microsSinceUnixEpoch ?? 0n,
				});
			}
		}
		nodes.sort((a, b) => (b.createdAt > a.createdAt ? 1 : -1));
		return nodes;
	});

	// Transcription
	let segments: Segment[] = $state([]);
	let segmentsLoaded = $state(false);

	// Load existing studio session (if any)
	$effect(() => {
		if (!connected || !mediaNodeId) return;
		for (const edge of edgeStore.byTarget(mediaNodeId)) {
			if (edge.edgeType !== 'has_studio') continue;
			const node = nodeStore.get(edge.sourceId);
			if (node) {
				try {
					const meta = JSON.parse(node.metadata ?? '{}');
					if (meta.edl?.operations) {
						operations = meta.edl.operations;
						sessionNodeId = node.id;
					}
				} catch { /* ignore */ }
				break; // Use first session found
			}
		}
	});

	// Load segments on mount
	$effect(() => {
		if (accessToken && mediaNodeId && !segmentsLoaded) {
			segmentsLoaded = true;
			fetchSegments(accessToken, mediaNodeId)
				.then((res) => {
					segments = res.segments || [];
				})
				.catch(() => {
					segments = [];
				});
		}
	});

	async function handleAnalyze() {
		if (!accessToken || !casHash) return;
		analyzing = true;
		try {
			const result = await audioAnalyze(accessToken, casHash);
			loudness = result.loudness;
			silenceRegions = result.silence_regions;
			audioInfo = result.info;
			// Show silence regions on waveform
			waveformRef?.showSilenceRegions(silenceRegions);
		} catch (e) {
			console.error('Analyse feilet:', e);
		} finally {
			analyzing = false;
		}
	}

	function handleCut() {
		const region = waveformRef?.getActiveRegion();
		if (!region) return;
		operations = [
			...operations,
			{
				type: 'cut',
				start_ms: Math.round(region.start * 1000),
				end_ms: Math.round(region.end * 1000),
			},
		];
		// Convert selection to cut overlay
		waveformRef?.clearActiveRegion();
		waveformRef?.addCutRegion(
			Math.round(region.start * 1000),
			Math.round(region.end * 1000)
		);
	}

	function handleAddOp(op: EdlOperation) {
		// Prevent duplicate types for non-cut operations
		if (op.type !== 'cut') {
			operations = operations.filter((o) => o.type !== op.type);
		}
		operations = [...operations, op];
	}

	function handleRemoveOp(index: number) {
		operations = operations.filter((_, i) => i !== index);
	}

	function handleUndo() {
		if (operations.length === 0) return;
		operations = operations.slice(0, -1);
	}

	function handleTrimSilence() {
		operations = [
			...operations,
			{
				type: 'trim_silence',
				threshold_db: -30,
				min_duration_ms: 500,
			},
		];
	}

	function handleRenderStart() {
		showRenderDialog = true;
	}

	async function handleRenderConfirm(format: string) {
		if (!accessToken || !casHash) return;
		rendering = true;

		const edl: EdlDocument = {
			source_hash: casHash,
			operations,
		};

		try {
			const result = await audioProcess(accessToken, mediaNodeId, edl, format);
			renderJobId = result.job_id;
			// Poll for completion (simple approach)
			pollJobResult(result.job_id);
		} catch (e) {
			console.error('Render feilet:', e);
			rendering = false;
		}
	}

	const MAX_POLL_FAILURES = 5;

	function pollJobResult(jobId: string) {
		// Poll job_queue for completion via simple interval
		// In production this would use SSE or WebSocket
		cleanupPolling();
		let failures = 0;

		pollIntervalId = setInterval(async () => {
			try {
				const res = await fetch(`/api/query/job_status?job_id=${encodeURIComponent(jobId)}`, {
					headers: { Authorization: `Bearer ${accessToken}` },
				});
				if (res.ok) {
					const data = await res.json();
					failures = 0; // Reset on success
					if (data.status === 'completed') {
						cleanupPolling();
						resultNodeId = data.result?.processed_node_id ?? null;
						rendering = false;
					} else if (data.status === 'error') {
						cleanupPolling();
						rendering = false;
						renderError = data.error_msg ?? 'Jobb feilet';
					}
				} else {
					failures++;
					if (failures >= MAX_POLL_FAILURES) {
						cleanupPolling();
						rendering = false;
						renderError = `Statussjekk feilet ${MAX_POLL_FAILURES} ganger — prøv igjen senere`;
					}
				}
			} catch {
				failures++;
				if (failures >= MAX_POLL_FAILURES) {
					cleanupPolling();
					rendering = false;
					renderError = `Mistet kontakt med serveren etter ${MAX_POLL_FAILURES} forsøk`;
				}
			}
		}, 2000);

		// Timeout after 5 minutes
		pollTimeoutId = setTimeout(() => {
			cleanupPolling();
			if (rendering) {
				rendering = false;
				renderError = 'Rendering tok for lang tid (tidsavbrudd etter 5 minutter)';
			}
		}, 5 * 60 * 1000);
	}

	// Cleanup polling on component destroy (navigation away)
	$effect(() => {
		return () => cleanupPolling();
	});

	function handleKeydown(e: KeyboardEvent) {
		if (e.target instanceof HTMLInputElement || e.target instanceof HTMLTextAreaElement) return;
		if (e.key === 'Delete' || e.key === 'Backspace') {
			const region = waveformRef?.getActiveRegion();
			if (region) {
				e.preventDefault();
				handleCut();
			}
		}
		if (e.ctrlKey && e.key === 'z') {
			e.preventDefault();
			handleUndo();
		}
	}

	async function handleSaveSession() {
		if (!accessToken || !casHash || operations.length === 0) return;
		saving = true;
		try {
			const edl: EdlDocument = { source_hash: casHash, operations };
			if (sessionNodeId) {
				// Update existing session node
				const { updateNode } = await import('$lib/api');
				await updateNode(accessToken, {
					node_id: sessionNodeId,
					metadata: { edl },
				});
			} else {
				// Create new session node + has_studio edge
				const res = await createNode(accessToken, {
					node_kind: 'content',
					title: `Studio-sesjon: ${mediaNode?.title ?? 'Ukjent'}`,
					visibility: 'hidden',
					metadata: { edl },
				});
				sessionNodeId = res.node_id;
				await createEdge(accessToken, {
					source_id: res.node_id,
					target_id: mediaNodeId,
					edge_type: 'has_studio',
				});
			}
		} catch (e) {
			console.error('Kunne ikke lagre sesjon:', e);
		} finally {
			saving = false;
		}
	}

	function handleSegmentClick(segment: Segment) {
		waveformRef?.seekTo(segment.start_ms / 1000);
	}
</script>

<svelte:window onkeydown={handleKeydown} />

<div class="flex min-h-screen flex-col bg-gray-50">
	<!-- Header -->
	<header class="flex items-center justify-between border-b border-gray-200 bg-white px-3 py-2 sm:px-4 sm:py-3">
		<div class="flex min-w-0 items-center gap-2 sm:gap-3">
			<a href="/" class="shrink-0 text-gray-400 hover:text-gray-600">
				<svg class="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
					<path stroke-linecap="round" stroke-linejoin="round" d="M10 19l-7-7m0 0l7-7m-7 7h18" />
				</svg>
			</a>
			<h1 class="truncate text-base font-semibold text-gray-800 sm:text-lg">
				{mediaNode?.title ?? 'Lydstudio'}
			</h1>
			{#if audioInfo}
				<span class="hidden rounded bg-gray-100 px-2 py-0.5 text-xs text-gray-500 sm:inline">
					{audioInfo.codec} / {audioInfo.sample_rate}Hz / {audioInfo.channels}ch
				</span>
			{/if}
		</div>
		<div class="flex items-center gap-2">
			{#if operations.length > 0}
				<button
					onclick={handleSaveSession}
					disabled={saving}
					class="rounded bg-gray-100 px-3 py-1.5 text-sm text-gray-700 transition-colors hover:bg-gray-200 disabled:opacity-50"
				>
					{saving ? 'Lagrer...' : sessionNodeId ? 'Oppdater sesjon' : 'Lagre sesjon'}
				</button>
			{/if}
		</div>
	</header>

	{#if !audioSrc}
		<div class="flex flex-1 items-center justify-center">
			<p class="text-gray-400">
				{#if !connected}
					Kobler til...
				{:else if !mediaNode}
					Finner ikke medienoden
				{:else}
					Ingen lydfil tilgjengelig
				{/if}
			</p>
		</div>
	{:else}
		<div class="flex flex-1 flex-col gap-4 p-4 lg:flex-row">
			<!-- Main area: waveform + transcription -->
			<div class="flex flex-1 flex-col gap-4 min-w-0">
				<StudioWaveform
					bind:this={waveformRef}
					src={audioSrc}
					onready={(d) => { audioInfo = audioInfo ?? { duration_ms: d * 1000, sample_rate: 0, channels: 0, codec: '', format: '', bit_rate: null }; }}
					ontimeupdate={(t) => { currentTime = t; }}
				/>

				<!-- Transcription segments -->
				{#if segments.length > 0}
					<div class="rounded-lg border border-gray-200 bg-white p-3">
						<h3 class="mb-2 text-sm font-semibold text-gray-700">Transkripsjon</h3>
						<div class="max-h-64 space-y-0.5 overflow-y-auto">
							{#each segments as seg}
								{@const active = currentTime >= seg.start_ms / 1000 && currentTime < seg.end_ms / 1000}
								<button
									onclick={() => handleSegmentClick(seg)}
									class="flex w-full items-start gap-2 rounded px-2 py-1 text-left transition-colors
										{active ? 'bg-blue-50 text-blue-800' : 'hover:bg-gray-50 text-gray-600'}"
								>
									<span class="mt-0.5 shrink-0 font-mono text-[10px] text-gray-400">
										{Math.floor(seg.start_ms / 60000)}:{Math.floor((seg.start_ms % 60000) / 1000).toString().padStart(2, '0')}
									</span>
									<span class="text-xs">{seg.content}</span>
								</button>
							{/each}
						</div>
					</div>
				{/if}
			</div>

			<!-- Sidebar: operation panel (desktop) -->
			<div class="hidden w-72 shrink-0 flex-col gap-4 overflow-y-auto lg:flex lg:w-80">
				<OperationPanel
					{loudness}
					{silenceRegions}
					{audioInfo}
					{analyzing}
					{operations}
					activeRegion={waveformRef?.getActiveRegion() ?? null}
					onanalyze={handleAnalyze}
					oncut={handleCut}
					onaddop={handleAddOp}
					onremoveop={handleRemoveOp}
					onrender={handleRenderStart}
					onundo={handleUndo}
					ontrimsilence={handleTrimSilence}
				/>

				<!-- Version history -->
				{#if versions.length > 0}
					<div class="rounded-lg border border-gray-200 bg-white p-4">
						<h3 class="mb-2 text-sm font-semibold text-gray-700">Versjoner</h3>
						<ul class="space-y-1">
							<li class="flex items-center justify-between rounded bg-blue-50 px-2 py-1">
								<span class="text-xs font-medium text-blue-700">Original</span>
								<span class="text-[10px] text-blue-500">Navarende</span>
							</li>
							{#each versions as ver, i}
								<li class="flex items-center justify-between rounded px-2 py-1 hover:bg-gray-50">
									<a href="/studio/{ver.id}" class="text-xs text-gray-600 hover:text-blue-600">
										v{i + 1}: {ver.title}
									</a>
								</li>
							{/each}
						</ul>
					</div>
				{/if}
			</div>
		</div>

		<!-- Mobile: floating tool button + bottom sheet -->
		<div class="lg:hidden">
			<!-- Floating action button -->
			<button
				onclick={() => { showToolSheet = !showToolSheet; }}
				class="fixed bottom-4 right-4 z-40 flex h-14 w-14 items-center justify-center rounded-full bg-blue-600 text-white shadow-lg transition-transform hover:bg-blue-700 active:scale-95"
				aria-label="Verktoy"
			>
				{#if showToolSheet}
					<!-- Close icon -->
					<svg class="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
						<path stroke-linecap="round" stroke-linejoin="round" d="M6 18L18 6M6 6l12 12" />
					</svg>
				{:else}
					<!-- Settings/tool icon -->
					<svg class="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
						<path stroke-linecap="round" stroke-linejoin="round" d="M10.325 4.317c.426-1.756 2.924-1.756 3.35 0a1.724 1.724 0 002.573 1.066c1.543-.94 3.31.826 2.37 2.37a1.724 1.724 0 001.066 2.573c1.756.426 1.756 2.924 0 3.35a1.724 1.724 0 00-1.066 2.573c.94 1.543-.826 3.31-2.37 2.37a1.724 1.724 0 00-2.573 1.066c-.426 1.756-2.924 1.756-3.35 0a1.724 1.724 0 00-2.573-1.066c-1.543.94-3.31-.826-2.37-2.37a1.724 1.724 0 00-1.066-2.573c-1.756-.426-1.756-2.924 0-3.35a1.724 1.724 0 001.066-2.573c-.94-1.543.826-3.31 2.37-2.37.996.608 2.296.07 2.572-1.065z" />
						<path stroke-linecap="round" stroke-linejoin="round" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z" />
					</svg>
				{/if}
				{#if operations.length > 0 && !showToolSheet}
					<span class="absolute -right-1 -top-1 flex h-5 w-5 items-center justify-center rounded-full bg-red-500 text-[10px] font-bold">
						{operations.length}
					</span>
				{/if}
			</button>

			<!-- Bottom sheet backdrop -->
			{#if showToolSheet}
				<!-- svelte-ignore a11y_no_static_element_interactions -->
				<div
					class="fixed inset-0 z-40 bg-black/30"
					onclick={() => { showToolSheet = false; }}
					onkeydown={(e) => { if (e.key === 'Escape') showToolSheet = false; }}
				></div>

				<!-- Bottom sheet -->
				<div class="fixed inset-x-0 bottom-0 z-50 flex max-h-[85vh] flex-col rounded-t-2xl bg-gray-50 shadow-2xl">
					<!-- Handle -->
					<div class="flex justify-center py-2">
						<div class="h-1 w-10 rounded-full bg-gray-300"></div>
					</div>

					<!-- Sheet content -->
					<div class="flex-1 overflow-y-auto px-4 pb-8">
						<OperationPanel
							{loudness}
							{silenceRegions}
							{audioInfo}
							{analyzing}
							{operations}
							activeRegion={waveformRef?.getActiveRegion() ?? null}
							onanalyze={handleAnalyze}
							oncut={handleCut}
							onaddop={handleAddOp}
							onremoveop={handleRemoveOp}
							onrender={handleRenderStart}
							onundo={handleUndo}
							ontrimsilence={handleTrimSilence}
						/>

						<!-- Version history -->
						{#if versions.length > 0}
							<div class="mt-4 rounded-lg border border-gray-200 bg-white p-4">
								<h3 class="mb-2 text-sm font-semibold text-gray-700">Versjoner</h3>
								<ul class="space-y-1">
									<li class="flex items-center justify-between rounded bg-blue-50 px-2 py-1">
										<span class="text-xs font-medium text-blue-700">Original</span>
										<span class="text-[10px] text-blue-500">Navarende</span>
									</li>
									{#each versions as ver, i}
										<li class="flex items-center justify-between rounded px-2 py-1 hover:bg-gray-50">
											<a href="/studio/{ver.id}" class="text-xs text-gray-600 hover:text-blue-600">
												v{i + 1}: {ver.title}
											</a>
										</li>
									{/each}
								</ul>
							</div>
						{/if}
					</div>
				</div>
			{/if}
		</div>
	{/if}
</div>

<!-- Render error toast (only when dialog is closed) -->
{#if renderError && !showRenderDialog}
	<div class="fixed bottom-4 left-4 z-50 max-w-sm rounded-lg border border-red-200 bg-red-50 px-4 py-3 shadow-lg">
		<div class="flex items-start gap-2">
			<svg class="mt-0.5 h-5 w-5 shrink-0 text-red-500" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
				<path stroke-linecap="round" stroke-linejoin="round" d="M12 9v2m0 4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
			</svg>
			<div class="flex-1">
				<p class="text-sm font-medium text-red-800">Rendering feilet</p>
				<p class="mt-0.5 text-xs text-red-600">{renderError}</p>
			</div>
			<button onclick={() => { renderError = null; }} class="shrink-0 text-red-400 hover:text-red-600">
				<svg class="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
					<path stroke-linecap="round" stroke-linejoin="round" d="M6 18L18 6M6 6l12 12" />
				</svg>
			</button>
		</div>
	</div>
{/if}

<!-- Render dialog -->
{#if showRenderDialog}
	<RenderDialog
		{operations}
		{rendering}
		jobId={renderJobId}
		{resultNodeId}
		errorMessage={renderError}
		onconfirm={handleRenderConfirm}
		onclose={() => { showRenderDialog = false; rendering = false; renderJobId = null; resultNodeId = null; renderError = null; }}
	/>
{/if}
