<script lang="ts">
	import { onMount, onDestroy } from 'svelte';
	import { page } from '$app/stores';
	import type { Job, Segment } from '$lib/types.js';
	import SourceIcon from '$lib/components/SourceIcon.svelte';
	import Waveform from '$lib/components/Waveform.svelte';
	import { accent } from '$lib/accent.js';

	const ACCENT = $derived($accent.value);

	const jobId = $derived($page.params.id);
	let job = $state<Job | null>(null);
	let segments = $state<Segment[]>([]);
	let error = $state('');
	let chunkInfo = $state({ chunk: 0, total: 0 });
	let eventSource: EventSource | null = null;

	const statusLabel: Record<string, string> = {
		pending: 'Pending',
		downloading: 'Downloading…',
		preparing: 'Preparing audio…',
		transcribing: 'Transcribing…',
		processing: 'Post-processing…',
		done: 'Done',
		failed: 'Failed',
		cancelled: 'Cancelled'
	};

	// Pipeline stages derived from job status
	const pipelineStages = $derived.by(() => {
		const status = job?.status ?? 'pending';
		const stages = [
			{ k: 'fetch', label: 'Fetch source' },
			{ k: 'extract', label: 'Extract audio track' },
			{ k: 'process', label: `Audio processing · ${job?.audioMode ?? 'auto'}` },
			{ k: 'transcribe', label: 'Transcribing' },
			{ k: 'finalize', label: 'Format &amp; save' }
		];
		const order = ['pending', 'downloading', 'preparing', 'transcribing', 'processing', 'done'];
		const idx = order.indexOf(status);
		return stages.map((s, i) => ({
			...s,
			done: i < idx - 1 || status === 'done',
			active: i === idx - 1 && status !== 'done' && status !== 'failed',
			pending: i > idx - 1 && status !== 'done'
		}));
	});

	function jobKind(job: Job): 'youtube' | 'audio' | 'video' | 'file' {
		const s = job.source ?? '';
		if (s.includes('youtube') || s.includes('youtu.be')) return 'youtube';
		if (/\.(mp3|m4a|wav|ogg|flac|aac)$/i.test(s)) return 'audio';
		if (/\.(mp4|mov|mkv|webm|avi)$/i.test(s)) return 'video';
		return 'file';
	}

	onMount(async () => {
		await loadJob();
		if (job && !['done', 'failed', 'cancelled'].includes(job.status)) {
			openStream();
		}
	});

	onDestroy(() => eventSource?.close());

	async function loadJob() {
		const res = await fetch(`/api/jobs/${jobId}`);
		if (!res.ok) {
			error = 'Job not found';
			return;
		}
		job = await res.json();
		if (job?.segmentsJson) {
			try {
				segments = JSON.parse(job.segmentsJson);
			} catch { /* ignore */ }
		}
	}

	function openStream() {
		eventSource = new EventSource(`/api/jobs/${jobId}/stream`);
		eventSource.onmessage = (e) => {
			try {
				const data = JSON.parse(e.data);
				if (data.type === 'progress') {
					chunkInfo = { chunk: data.chunk ?? 0, total: data.total ?? 0 };
					if (job) job = { ...job, progress: data.progress ?? job.progress, status: 'transcribing' };
				} else if (data.type === 'status') {
					if (job) job = { ...job, status: data.status, progress: data.progress ?? job.progress };
				} else if (data.type === 'done') {
					eventSource?.close();
					loadJob();
				} else if (data.type === 'error') {
					if (job) job = { ...job, status: 'failed', error: data.message };
					eventSource?.close();
				}
			} catch { /* ignore */ }
		};
	}

	function secToTimestamp(sec: number): string {
		const h = Math.floor(sec / 3600);
		const m = Math.floor((sec % 3600) / 60);
		const s = Math.floor(sec % 60);
		return h > 0
			? `${String(h).padStart(2, '0')}:${String(m).padStart(2, '0')}:${String(s).padStart(2, '0')}`
			: `${String(m).padStart(2, '0')}:${String(s).padStart(2, '0')}`;
	}

	const formats = ['srt', 'txt', 'md', 'json'] as const;
	const isActive = $derived(!job || !['done', 'failed', 'cancelled'].includes(job.status));
	const isTerminal = $derived(job !== null && ['done', 'failed', 'cancelled'].includes(job.status));
	const canRetry = $derived(
		job !== null &&
		['failed', 'cancelled'].includes(job.status) &&
		(job.source?.startsWith('http') ?? false)
	);

	async function cancelJob() {
		if (!job) return;
		await fetch(`/api/jobs/${job.id}`, { method: 'DELETE' });
		await loadJob();
	}

	async function deleteJob() {
		if (!job || !confirm(`Delete job "${job.title || job.id}"?`)) return;
		const res = await fetch(`/api/jobs/${job.id}`, { method: 'DELETE' });
		if (res.ok) window.location.href = '/';
	}

	async function retryJobAction() {
		if (!job) return;
		const res = await fetch(`/api/jobs/${job.id}/retry`, { method: 'POST' });
		if (res.ok) {
			await loadJob();
			openStream();
		} else {
			const body = await res.json().catch(() => ({}));
			alert(body.message ?? 'Retry failed');
		}
	}
</script>

<svelte:head>
	<title>{job?.title ?? 'Job'} — Tonemark</title>
</svelte:head>

<div class="page">
	{#if error}
		<div class="error-banner" role="alert">{error}</div>
	{:else if !job}
		<div class="loading" aria-busy="true">
			<svg width="20" height="20" viewBox="0 0 20 20" style="animation: spin 1s linear infinite">
				<circle cx="10" cy="10" r="8" stroke="var(--text-muted)" stroke-width="2" fill="none" stroke-dasharray="30 14"/>
			</svg>
			Loading…
		</div>
	{:else}
		<!-- ── Breadcrumb ─────────────────────────────────────── -->
		<div class="breadcrumb mono">
			<a href="/" class="crumb-link">Home</a>
			<span>›</span>
			<span style="color: #fff">{job.id.slice(0, 8)}</span>
		</div>

		<!-- ── Job header ────────────────────────────────────── -->
		<div class="job-header">
			<SourceIcon kind={jobKind(job)} size={52} accent={ACCENT} />
			<div class="job-header-text">
				<h1 class="job-title">{job.title || job.source}</h1>
				<div class="job-meta mono">
					{job.source?.includes('http') ? job.source : (job.source ?? '')}
					{#if job.audioMode}· {job.audioMode}{/if}
					{#if job.meanVolume != null}· {job.meanVolume.toFixed(1)} dBFS{/if}
				</div>
			</div>
			<div class="job-actions">
			{#if isActive}
				<button class="btn-job-action danger" onclick={cancelJob} aria-label="Cancel job">Cancel</button>
			{/if}
			{#if canRetry}
				<button class="btn-job-action accent" onclick={retryJobAction} aria-label="Retry job">↺ Retry</button>
			{/if}
			{#if isTerminal}
				<button class="btn-job-action danger" onclick={deleteJob} aria-label="Delete job">Delete</button>
			{/if}
		</div>
		</div>

		<!-- ── Progress block ────────────────────────────────── -->
		{#if isActive || job.status === 'done'}
			<div class="progress-card glass">
				<!-- Waveform coloured by progress -->
				<div class="progress-wave">
					<Waveform
						bars={140}
						progress={job.progress}
						accent={ACCENT}
						height={80}
						pattern="default"
					/>
				</div>

				<div class="progress-footer">
					<div class="progress-left">
						<span class="progress-pct mono">
							{job.progress}<span style="color: var(--text-dim); font-weight: 400">%</span>
						</span>
						<span class="progress-status">{statusLabel[job.status] ?? job.status}</span>
					</div>
					{#if chunkInfo.total > 0}
						<span class="progress-chunks mono">
							chunk {chunkInfo.chunk} / {chunkInfo.total}
						</span>
					{/if}
				</div>

				<!-- Progress bar -->
				<div class="progress-bar-track">
					<div
						class="progress-bar-fill"
						style="width: {job.progress}%; background: {ACCENT}; box-shadow: 0 0 12px {ACCENT}80;"
					></div>
				</div>
			</div>
		{/if}

		<!-- ── Error ─────────────────────────────────────────── -->
		{#if job.error}
			<div class="error-banner" role="alert">{job.error}</div>
		{/if}

		<!-- ── Two-column: pipeline + downloads/transcript ───── -->
		<div class="two-col">
			<!-- Pipeline stages -->
			<div class="glass stage-card">
				<div class="label" style="margin-bottom: 16px;">Pipeline</div>
				<div class="stages">
					{#each pipelineStages as stage}
						<div class="stage-row">
							<div
								class="stage-dot"
								style={stage.done
									? `background: ${ACCENT};`
									: stage.active
										? `background: transparent; border: 2px solid ${ACCENT};`
										: 'background: rgba(255,255,255,0.05);'}
							>
								{#if stage.done}
									<svg width="10" height="10" viewBox="0 0 10 10" fill="none">
										<path d="M2 5l2 2 4-4" stroke="#0c0d10" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"/>
									</svg>
								{:else if stage.active}
									<div class="stage-dot-inner" style="background: {ACCENT}"></div>
								{/if}
							</div>
							<span
								class="stage-label"
								style={stage.pending ? 'color: var(--text-dim)' : stage.active ? 'color: #fff; font-weight: 500' : ''}
							>
								{@html stage.label}
							</span>
							{#if stage.active}
								<span class="mono" style="font-size: 11.5px; color: {ACCENT}">{job.progress}%</span>
							{/if}
						</div>
					{/each}
				</div>
			</div>

			<!-- Downloads or live preview -->
			<div class="glass side-card">
				{#if job.status === 'done'}
					<div class="label" style="margin-bottom: 16px;">Download transcript</div>
					<div class="dl-grid">
						{#each formats as fmt, i}
							<a
								href="/api/jobs/{job.id}/download/{fmt}"
								download
								class="dl-btn mono"
								style={i === 0
									? `background: color-mix(in oklab, ${ACCENT} 12%, transparent); color: ${ACCENT}; border-color: color-mix(in oklab, ${ACCENT} 30%, transparent);`
									: ''}
							>
								<svg width="11" height="11" viewBox="0 0 11 11" fill="none">
									<path d="M5.5 1v7M2 5l3.5 3.5L9 5M1.5 9.5h8" stroke="currentColor" stroke-width="1.4" stroke-linecap="round" stroke-linejoin="round"/>
								</svg>
								{fmt.toUpperCase()}
							</a>
						{/each}
					</div>

					{#if job.outputDir}
						<div class="output-dir mono">{job.outputDir}</div>
					{/if}
				{:else if isActive}
					<div class="live-header">
						<div class="label">Live preview</div>
						<div class="streaming-badge" style="color: {ACCENT}">
							<div class="stream-dot" style="background: {ACCENT}; animation: pulse 1.4s infinite"></div>
							Streaming
						</div>
					</div>
					{#if segments.length > 0}
						{@const last = segments[segments.length - 1]}
						<div class="live-text">
							<span class="mono" style="color: var(--text-dim); margin-right: 8px;">
								{secToTimestamp(last.start)}
							</span>
							{last.text}<span style="color: {ACCENT}; animation: blink 1s infinite; margin-left: 3px;">▍</span>
						</div>
					{:else}
						<div style="font-size: 13px; color: var(--text-muted); font-style: italic;">
							Waiting for segments…
						</div>
					{/if}
				{/if}
			</div>
		</div>

		<!-- ── Transcript viewer ──────────────────────────────── -->
		{#if segments.length > 0}
			<section class="glass transcript-card">
				<div class="transcript-header">
					<div class="label">Transcript</div>
					<span class="mono" style="font-size: 12px; color: var(--text-muted);">
						{segments.length} segments
					</span>
				</div>
				<div class="transcript-body">
					{#each segments as seg}
						<div class="seg-row">
							<span class="seg-ts mono">{secToTimestamp(seg.start)}</span>
							<p class="seg-text">{seg.text}</p>
						</div>
					{/each}
				</div>
			</section>
		{/if}
	{/if}
</div>

<style>
	.page {
		padding: 32px 40px;
		display: flex;
		flex-direction: column;
		gap: 20px;
		max-width: 1000px;
	}

	/* ── Loading / errors ──────────────────────────────────── */
	.loading {
		display: flex;
		align-items: center;
		gap: 10px;
		color: var(--text-muted);
		font-size: 14px;
	}

	.error-banner {
		padding: 12px 16px;
		border-radius: 10px;
		background: rgba(255, 90, 90, 0.08);
		border: 1px solid rgba(255, 90, 90, 0.2);
		color: #ff8a8a;
		font-size: 13px;
	}

	/* ── Breadcrumb ─────────────────────────────────────────── */
	.breadcrumb {
		display: flex;
		align-items: center;
		gap: 8px;
		font-size: 12px;
		color: var(--text-muted);
	}

	.crumb-link {
		color: var(--text-muted);
		text-decoration: none;
	}
	.crumb-link:hover {
		color: var(--text);
	}

	/* ── Job header ─────────────────────────────────────────── */
	.job-header {
		display: flex;
		align-items: center;
		gap: 18px;
	}

	.job-header-text {
		flex: 1;
		min-width: 0;
	}

	.job-title {
		margin: 0 0 4px;
		font-size: 26px;
		font-weight: 600;
		letter-spacing: -0.02em;
		white-space: nowrap;
		overflow: hidden;
		text-overflow: ellipsis;
	}

	.job-meta {
		font-size: 12.5px;
		color: var(--text-muted);
		white-space: nowrap;
		overflow: hidden;
		text-overflow: ellipsis;
	}

	.job-actions {
		display: flex;
		gap: 8px;
		flex-shrink: 0;
	}

	.btn-job-action {
		padding: 8px 14px;
		border-radius: 8px;
		font-size: 12.5px;
		font-family: inherit;
		cursor: pointer;
		white-space: nowrap;
		transition: background 0.15s;
	}
	.btn-job-action.danger {
		border: 1px solid rgba(255, 90, 90, 0.3);
		background: rgba(255, 90, 90, 0.08);
		color: #ff8a8a;
	}
	.btn-job-action.danger:hover {
		background: rgba(255, 90, 90, 0.15);
	}
	.btn-job-action.accent {
		border: 1px solid color-mix(in oklab, var(--accent) 40%, transparent);
		background: color-mix(in oklab, var(--accent) 10%, transparent);
		color: var(--accent);
	}
	.btn-job-action.accent:hover {
		background: color-mix(in oklab, var(--accent) 18%, transparent);
	}

	/* ── Progress card ──────────────────────────────────────── */
	.progress-card {
		padding: 28px;
	}

	.progress-wave {
		margin-bottom: 20px;
	}

	.progress-footer {
		display: flex;
		justify-content: space-between;
		align-items: baseline;
		margin-bottom: 8px;
	}

	.progress-left {
		display: flex;
		align-items: baseline;
		gap: 12px;
	}

	.progress-pct {
		font-size: 36px;
		font-weight: 600;
		letter-spacing: -0.02em;
	}

	.progress-status {
		font-size: 14px;
		color: var(--text-muted);
	}

	.progress-chunks {
		font-size: 12.5px;
		color: var(--text-muted);
	}

	.progress-bar-track {
		height: 4px;
		border-radius: 2px;
		background: rgba(255, 255, 255, 0.06);
		overflow: hidden;
	}

	.progress-bar-fill {
		height: 100%;
		border-radius: 2px;
		transition: width 0.5s ease;
	}

	/* ── Two column ─────────────────────────────────────────── */
	.two-col {
		display: grid;
		grid-template-columns: 1fr 1fr;
		gap: 20px;
	}

	.stage-card,
	.side-card {
		padding: 22px;
	}

	/* ── Pipeline stages ────────────────────────────────────── */
	.stages {
		display: flex;
		flex-direction: column;
		gap: 14px;
	}

	.stage-row {
		display: flex;
		align-items: center;
		gap: 14px;
	}

	.stage-dot {
		width: 18px;
		height: 18px;
		border-radius: 9px;
		display: flex;
		align-items: center;
		justify-content: center;
		flex-shrink: 0;
	}

	.stage-dot-inner {
		width: 6px;
		height: 6px;
		border-radius: 3px;
	}

	.stage-label {
		flex: 1;
		font-size: 13.5px;
		color: rgba(232, 233, 236, 0.85);
	}

	/* ── Live preview / downloads ───────────────────────────── */
	.live-header {
		display: flex;
		justify-content: space-between;
		align-items: center;
		margin-bottom: 14px;
	}

	.streaming-badge {
		display: flex;
		align-items: center;
		gap: 6px;
		font-size: 11px;
		font-family: var(--font-mono);
	}

	.stream-dot {
		width: 6px;
		height: 6px;
		border-radius: 3px;
	}

	.live-text {
		font-size: 13.5px;
		line-height: 1.7;
		color: rgba(232, 233, 236, 0.85);
	}

	.dl-grid {
		display: grid;
		grid-template-columns: repeat(2, 1fr);
		gap: 8px;
	}

	.dl-btn {
		padding: 11px;
		border-radius: 9px;
		border: 1px solid var(--border);
		background: rgba(255, 255, 255, 0.025);
		color: rgba(232, 233, 236, 0.85);
		font-size: 11.5px;
		font-weight: 600;
		letter-spacing: 0.04em;
		text-decoration: none;
		display: flex;
		align-items: center;
		justify-content: center;
		gap: 6px;
		transition: background 0.15s;
	}

	.dl-btn:hover {
		background: rgba(255, 255, 255, 0.04);
	}

	.output-dir {
		margin-top: 14px;
		padding-top: 14px;
		border-top: 1px solid var(--border);
		font-size: 11px;
		color: var(--text-dim);
		word-break: break-all;
	}

	/* ── Transcript ─────────────────────────────────────────── */
	.transcript-card {
		padding: 22px;
	}

	.transcript-header {
		display: flex;
		justify-content: space-between;
		align-items: baseline;
		margin-bottom: 16px;
	}

	.transcript-body {
		max-height: 480px;
		overflow-y: auto;
		display: flex;
		flex-direction: column;
		gap: 16px;
		padding-right: 4px;
	}

	.seg-row {
		display: flex;
		gap: 14px;
	}

	.seg-ts {
		font-size: 11px;
		color: var(--text-dim);
		flex-shrink: 0;
		margin-top: 3px;
		width: 50px;
		text-align: right;
	}

	.seg-text {
		margin: 0;
		font-size: 14px;
		line-height: 1.65;
		color: rgba(232, 233, 236, 0.85);
	}

	/* ── Responsive ─────────────────────────────────────────── */
	@media (max-width: 768px) {
		.page {
			padding: 20px 16px;
		}
		.job-title {
			font-size: 20px;
		}
		.two-col {
			grid-template-columns: 1fr;
		}
		.dl-grid {
			grid-template-columns: repeat(4, 1fr);
		}
	}
</style>

