<script lang="ts">
	import { auth } from '$lib/core/stores/auth';
	import { addNotification } from '$lib/core/stores/noti';
	import Button from '$lib/components/ui/button/button.svelte';
	import Label from '$lib/components/ui/label/label.svelte';
	import Input from '$lib/components/ui/input/input.svelte';
	import * as Card from '$lib/components/ui/card/index.js';
	import * as Dialog from '$lib/components/ui/dialog/index.js';
	import * as Select from '$lib/components/ui/select/index.js';
	import Badge from '$lib/components/ui/badge/badge.svelte';
	import Spinner from '$lib/components/ui/spinner/spinner.svelte';
	import Progress from '$lib/components/ui/progress/progress.svelte';
	import {
		Upload,
		X,
		Film,
		MonitorPlay,
		CoffeeIcon,
		Pencil,
		Lock,
		RefreshCw,
		CalendarDays,
		Clock,
		ChevronDown,
		ImageIcon
	} from '@lucide/svelte/icons';
	import * as adb from '$lib/core/adb/adb';
	import { env } from '$env/dynamic/public';
	import { AdbInstance } from '../../../state.svelte';

	const CREATE_ENDPOINT = '/api/video-mainpage';
	const LIST_ENDPOINT = '/api/video-mainpage/list';
	const UPDATE_ENDPOINT = '/api/video-mainpage/update';
	const MACHINE_PROJECT_DIR = '/sdcard/coffeevending/taobin_project';
	const GET_IMAGE = env.PUBLIC_GET_IMAGE;
	const DURATION_TRIM = 4; // brewing play seconds = video length − 4

	// taobin_project-relative path -> served URL (works for video/ and inter/<c>/video/).
	const videoUrl = (path: string) => `${GET_IMAGE}/${path}`;

	// Only Thailand is enabled for now. To re-enable a country later, uncomment it
	// here (the backend already supports inter/<country>/video for all of these).
	const COUNTRIES = [
		{ value: 'tha', label: 'Thailand (tha)' }
		// { value: 'aus', label: 'Australia (aus)' },
		// { value: 'gbr', label: 'United Kingdom (gbr)' },
		// { value: 'gbr_premium', label: 'UK Premium (gbr_premium)' },
		// { value: 'hkg', label: 'Hong Kong (hkg)' },
		// { value: 'ltu', label: 'Lithuania (ltu)' },
		// { value: 'mys', label: 'Malaysia (mys)' },
		// { value: 'rou', label: 'Romania (rou)' },
		// { value: 'sgp', label: 'Singapore (sgp)' },
		// { value: 'tha_premium', label: 'Thailand Premium (tha_premium)' },
		// { value: 'uae_dubai', label: 'UAE Dubai (uae_dubai)' },
		// { value: 'usa', label: 'USA (usa)' }
	];
	let country = $state('tha');
	const countryLabel = $derived(COUNTRIES.find((c) => c.value === country)?.label ?? country);

	interface MediaInfo {
		filename: string;
		video: string;
		size: number | null;
		duration?: number | null;
	}
	interface ManagedVideo {
		n: number;
		slug: string;
		name: string;
		start: string;
		end: string;
		range_label: string;
		main: MediaInfo | null;
		brewing: MediaInfo | null;
		editable: true;
	}
	interface ReadonlyVideo {
		filename: string;
		video: string;
		size: number | null;
		source: string;
	}

	// ── create form ─────────────────────────────────────────────────────────
	let name = $state('');
	let startDate = $state('');
	let endDate = $state('');
	let mainFile = $state<File | null>(null);
	let mainPreview = $state('');
	let brewingFile = $state<File | null>(null);
	let brewingPreview = $state('');
	let brewingRawSeconds = $state(0);
	let brewingTxtFile = $state<File | null>(null);
	let brewingTxtPreview = $state('');
	let brewingTxtEnFile = $state<File | null>(null);
	let brewingTxtEnPreview = $state('');

	const brewingPlaySeconds = $derived(Math.max(1, Math.round(brewingRawSeconds) - DURATION_TRIM));

	let submitting = $state(false);
	let connecting = $state(false);
	let pushProgress = $state({ percent: 0, name: '', active: false });
	let isAdbConnected = $derived(Boolean(AdbInstance.instance));

	// ── list ────────────────────────────────────────────────────────────────
	let managed = $state<ManagedVideo[]>([]);
	let readonlyList = $state<ReadonlyVideo[]>([]);
	let loadingList = $state(false);
	let showReadonly = $state(false);

	// ── edit dialog ───────────────────────────────────────────────────────────
	let editOpen = $state(false);
	let editTarget = $state<ManagedVideo | null>(null);
	let editName = $state('');
	let editStart = $state('');
	let editEnd = $state('');
	let editMainFile = $state<File | null>(null);
	let editBrewingFile = $state<File | null>(null);
	let editBrewingRaw = $state(0);
	let editBrewingTxtFile = $state<File | null>(null);
	let editBrewingTxtEnFile = $state<File | null>(null);
	let editSaving = $state(false);

	const editBrewingPlaySeconds = $derived(Math.max(1, Math.round(editBrewingRaw) - DURATION_TRIM));

	function toIso(d: string): string {
		return d ? `${d}T00:00:00` : '';
	}

	function fmtMB(bytes: number | null | undefined): string {
		return bytes ? `${(bytes / (1024 * 1024)).toFixed(1)} MB` : '—';
	}

	function readVideoDuration(file: File): Promise<number> {
		return new Promise((resolve) => {
			const v = document.createElement('video');
			v.preload = 'metadata';
			const url = URL.createObjectURL(file);
			v.onloadedmetadata = () => {
				URL.revokeObjectURL(url);
				resolve(Number.isFinite(v.duration) ? v.duration : 0);
			};
			v.onerror = () => {
				URL.revokeObjectURL(url);
				resolve(0);
			};
			v.src = url;
		});
	}

	async function machineVideoNumbers(): Promise<string> {
		try {
			const res = await adb.executeCmd(`ls ${MACHINE_PROJECT_DIR}/video`);
			const out = typeof res === 'object' && res ? ((res as { output?: string }).output ?? '') : '';
			const nums = [...out.matchAll(/brewing_adv(\d+)/g)].map((m) => m[1]);
			return [...new Set(nums)].join(',');
		} catch {
			return '';
		}
	}

	async function connectMachine() {
		if (AdbInstance.instance) {
			addNotification('INFO:Machine already connected');
			return;
		}
		connecting = true;
		try {
			await adb.connnectViaWebUSB(false);
			addNotification(AdbInstance.instance ? 'INFO:Machine connected' : 'WARN:No machine selected');
		} catch (error) {
			addNotification(`ERR:Connect failed: ${error instanceof Error ? error.message : 'unknown'}`);
		} finally {
			connecting = false;
		}
	}

	function pickMain(event: Event) {
		const input = event.target as HTMLInputElement;
		const file = input.files?.[0];
		input.value = '';
		if (!file) return;
		if (!file.name.toLowerCase().endsWith('.mp4')) return addNotification('WARN:Only .mp4 allowed');
		if (mainPreview) URL.revokeObjectURL(mainPreview);
		mainFile = file;
		mainPreview = URL.createObjectURL(file);
	}

	async function pickBrewing(event: Event) {
		const input = event.target as HTMLInputElement;
		const file = input.files?.[0];
		input.value = '';
		if (!file) return;
		if (!file.name.toLowerCase().endsWith('.mp4')) return addNotification('WARN:Only .mp4 allowed');
		if (brewingPreview) URL.revokeObjectURL(brewingPreview);
		brewingFile = file;
		brewingPreview = URL.createObjectURL(file);
		brewingRawSeconds = await readVideoDuration(file);
	}

	function pickPng(event: Event, set: (f: File, url: string) => void) {
		const input = event.target as HTMLInputElement;
		const file = input.files?.[0];
		input.value = '';
		if (!file) return;
		if (!file.name.toLowerCase().endsWith('.png')) return addNotification('WARN:Text overlay must be .png');
		set(file, URL.createObjectURL(file));
	}
	function pickBrewingTxt(e: Event) {
		pickPng(e, (f, url) => {
			if (brewingTxtPreview) URL.revokeObjectURL(brewingTxtPreview);
			brewingTxtFile = f;
			brewingTxtPreview = url;
		});
	}
	function pickBrewingTxtEn(e: Event) {
		pickPng(e, (f, url) => {
			if (brewingTxtEnPreview) URL.revokeObjectURL(brewingTxtEnPreview);
			brewingTxtEnFile = f;
			brewingTxtEnPreview = url;
		});
	}

	function clearMain() {
		if (mainPreview) URL.revokeObjectURL(mainPreview);
		mainFile = null;
		mainPreview = '';
	}
	function clearBrewing() {
		if (brewingPreview) URL.revokeObjectURL(brewingPreview);
		brewingFile = null;
		brewingPreview = '';
		brewingRawSeconds = 0;
	}
	function clearBrewingTxt() {
		if (brewingTxtPreview) URL.revokeObjectURL(brewingTxtPreview);
		brewingTxtFile = null;
		brewingTxtPreview = '';
	}
	function clearBrewingTxtEn() {
		if (brewingTxtEnPreview) URL.revokeObjectURL(brewingTxtEnPreview);
		brewingTxtEnFile = null;
		brewingTxtEnPreview = '';
	}

	async function pushBinaries(items: { rel: string; file: File }[]) {
		const dirs = [...new Set(items.map((it) => it.rel.slice(0, it.rel.lastIndexOf('/'))))];
		for (const d of dirs) await adb.executeCmd(`mkdir -p "${MACHINE_PROJECT_DIR}/${d}"`);
		for (const it of items) {
			const label = it.rel.split('/').pop() ?? it.rel;
			pushProgress = { percent: 0, name: label, active: true };
			const bytes = new Uint8Array(await it.file.arrayBuffer());
			const ok = await adb.pushBinary(`${MACHINE_PROJECT_DIR}/${it.rel}`, bytes, (sent, total) => {
				pushProgress = {
					percent: total > 0 ? Math.round((sent / total) * 100) : 0,
					name: label,
					active: true
				};
			});
			if (!ok) throw new Error(`push ${label} failed`);
		}
	}

	// Push each uploaded file to every target path the backend returned (one per base).
	function targetItems(
		targets: Record<string, string[]>,
		files: Record<string, File | null>
	): { rel: string; file: File }[] {
		const items: { rel: string; file: File }[] = [];
		for (const kind of ['main', 'brewing', 'txt', 'txt_en']) {
			const f = files[kind];
			if (!f) continue;
			for (const rel of targets?.[kind] ?? []) items.push({ rel, file: f });
		}
		return items;
	}
	async function pushScripts(scripts: { path: string; content: string }[]) {
		for (const s of scripts) {
			if (!s?.path || typeof s?.content !== 'string') continue;
			pushProgress = { percent: 100, name: s.path.split('/').pop() ?? s.path, active: true };
			await adb.push(`${MACHINE_PROJECT_DIR}/${s.path}`, s.content);
		}
	}

	async function handleSubmit() {
		const user = $auth;
		if (!user) return addNotification('ERR:Not logged in');
		if (!AdbInstance.instance) return addNotification('ERR:Connect a machine first');
		if (!name.trim() || !startDate || !mainFile || !brewingFile || !brewingTxtFile || !brewingTxtEnFile)
			return addNotification(
				'ERR:Need a name, start date, both videos, and both brewing text overlays (TH + EN)'
			);

		submitting = true;
		try {
			const fd = new FormData();
			fd.append('uid', user.uid);
			fd.append('displayName', user.displayName || 'unknown');
			fd.append('email', user.email || 'unknown@email.com');
			fd.append('name', name.trim());
			fd.append('country', country);
			fd.append('start', toIso(startDate));
			fd.append('end', endDate ? toIso(endDate) : 'NONE');
			fd.append('machine_numbers', await machineVideoNumbers());
			fd.append('brewing_duration', String(brewingPlaySeconds));
			fd.append('video', mainFile);
			fd.append('brewing_video', brewingFile);
			fd.append('brewing_txt', brewingTxtFile);
			fd.append('brewing_txt_en', brewingTxtEnFile);

			const res = await fetch(CREATE_ENDPOINT, { method: 'POST', body: fd });
			if (!res.ok) {
				const e = await res.json().catch(() => ({ detail: res.statusText }));
				throw new Error(e.detail || e.message || 'Add video failed');
			}
			const result = await res.json();

			await pushBinaries(
				targetItems(result.targets, {
					main: mainFile,
					brewing: brewingFile,
					txt: brewingTxtFile,
					txt_en: brewingTxtEnFile
				})
			);
			await pushScripts(result?.content?.scripts ?? []);

			if (result?.sftp?.error)
				addNotification(`WARN:Uploaded but FTP sync failed: ${result.sftp.error}`);
			addNotification(`INFO:Added "${name.trim()}" as brewing_adv${result.n} (${country})`);
			clearMain();
			clearBrewing();
			clearBrewingTxt();
			clearBrewingTxtEn();
			name = '';
			startDate = '';
			endDate = '';
			await loadList();
		} catch (error) {
			addNotification(`ERR:${error instanceof Error ? error.message : 'unknown'}`);
		} finally {
			submitting = false;
			pushProgress = { percent: 0, name: '', active: false };
		}
	}

	async function loadList() {
		loadingList = true;
		try {
			const res = await fetch(`${LIST_ENDPOINT}?country=${encodeURIComponent(country)}`, {
				method: 'POST'
			});
			if (!res.ok) throw new Error('list failed');
			const data = await res.json();
			managed = data.managed ?? [];
			readonlyList = data.readonly ?? [];
		} catch (error) {
			addNotification(`ERR:Load list failed: ${error instanceof Error ? error.message : 'unknown'}`);
		} finally {
			loadingList = false;
		}
	}

	function openEdit(v: ManagedVideo) {
		editTarget = v;
		editName = v.name;
		editStart = v.start ? v.start.slice(0, 10) : '';
		editEnd = v.end && v.end !== 'NONE' ? v.end.slice(0, 10) : '';
		editMainFile = null;
		editBrewingFile = null;
		editBrewingRaw = 0;
		editBrewingTxtFile = null;
		editBrewingTxtEnFile = null;
		editOpen = true;
	}

	function pickEditMain(event: Event) {
		const input = event.target as HTMLInputElement;
		const file = input.files?.[0];
		input.value = '';
		if (file && file.name.toLowerCase().endsWith('.mp4')) editMainFile = file;
		else if (file) addNotification('WARN:Only .mp4 allowed');
	}
	async function pickEditBrewing(event: Event) {
		const input = event.target as HTMLInputElement;
		const file = input.files?.[0];
		input.value = '';
		if (!file) return;
		if (!file.name.toLowerCase().endsWith('.mp4')) return addNotification('WARN:Only .mp4 allowed');
		editBrewingFile = file;
		editBrewingRaw = await readVideoDuration(file);
	}
	function pickEditTxt(event: Event, en: boolean) {
		const input = event.target as HTMLInputElement;
		const file = input.files?.[0];
		input.value = '';
		if (!file) return;
		if (!file.name.toLowerCase().endsWith('.png')) return addNotification('WARN:Text overlay must be .png');
		if (en) editBrewingTxtEnFile = file;
		else editBrewingTxtFile = file;
	}

	async function submitEdit() {
		const user = $auth;
		if (!user || !editTarget) return;
		if (!AdbInstance.instance) return addNotification('ERR:Connect a machine first');
		if (!editStart) return addNotification('ERR:Start date required');
		const target = editTarget;
		editSaving = true;
		try {
			const fd = new FormData();
			fd.append('slug', target.slug);
			fd.append('uid', user.uid);
			fd.append('displayName', user.displayName || 'unknown');
			fd.append('email', user.email || 'unknown@email.com');
			fd.append('country', country);
			fd.append('name', editName.trim() || target.name);
			fd.append('start', toIso(editStart));
			fd.append('end', editEnd ? toIso(editEnd) : 'NONE');
			if (editBrewingFile) fd.append('brewing_duration', String(editBrewingPlaySeconds));
			else if (target.brewing?.duration) fd.append('brewing_duration', String(target.brewing.duration));
			if (editMainFile) fd.append('video', editMainFile);
			if (editBrewingFile) fd.append('brewing_video', editBrewingFile);
			if (editBrewingTxtFile) fd.append('brewing_txt', editBrewingTxtFile);
			if (editBrewingTxtEnFile) fd.append('brewing_txt_en', editBrewingTxtEnFile);

			const res = await fetch(UPDATE_ENDPOINT, { method: 'POST', body: fd });
			if (!res.ok) {
				const e = await res.json().catch(() => ({ detail: res.statusText }));
				throw new Error(e.detail || 'Update failed');
			}
			const result = await res.json();

			await pushBinaries(
				targetItems(result.targets ?? {}, {
					main: editMainFile,
					brewing: editBrewingFile,
					txt: editBrewingTxtFile,
					txt_en: editBrewingTxtEnFile
				})
			);
			await pushScripts(result?.content?.scripts ?? []);

			if (result?.sftp?.error)
				addNotification(`WARN:Updated but FTP sync failed: ${result.sftp.error}`);
			addNotification(`INFO:Updated "${target.name}" (${country})`);
			editOpen = false;
			await loadList();
		} catch (error) {
			addNotification(`ERR:${error instanceof Error ? error.message : 'unknown'}`);
		} finally {
			editSaving = false;
			pushProgress = { percent: 0, name: '', active: false };
		}
	}

	// Load (and reload) the list whenever the selected country changes; runs on mount.
	$effect(() => {
		void country;
		loadList();
	});

	$effect(() => {
		return () => {
			if (mainPreview) URL.revokeObjectURL(mainPreview);
			if (brewingPreview) URL.revokeObjectURL(brewingPreview);
			if (brewingTxtPreview) URL.revokeObjectURL(brewingTxtPreview);
			if (brewingTxtEnPreview) URL.revokeObjectURL(brewingTxtEnPreview);
		};
	});
</script>

<div class="flex min-h-screen flex-col">
	<!-- Header -->
	<div class="sticky top-0 z-10 border-b bg-background">
		<div class="flex items-center justify-between px-8 py-4">
			<div>
				<h1 class="text-2xl font-bold">Advertisement Videos</h1>
				<p class="text-sm text-muted-foreground">Main-page &amp; brewing-page videos, scheduled by date</p>
			</div>
			<div class="flex items-center gap-3">
				<Badge variant={isAdbConnected ? 'default' : 'secondary'}>
					{isAdbConnected ? 'Machine connected' : 'Machine offline'}
				</Badge>
				{#if !isAdbConnected}
					<Button variant="outline" onclick={connectMachine} disabled={connecting}>
						{#if connecting}<Spinner class="mr-2 h-4 w-4" />Connecting...{:else}<MonitorPlay class="mr-2 h-4 w-4" />Connect Machine{/if}
					</Button>
				{/if}
			</div>
		</div>
	</div>

	<div class="flex-1 overflow-y-auto p-8">
		<div class="mx-auto max-w-5xl space-y-8">
			<!-- Create -->
			<Card.Root class="overflow-hidden shadow-sm">
				<Card.Header>
					<Card.Title class="flex items-center gap-2 text-lg">
						<Upload class="h-5 w-5 text-muted-foreground" /> Add a new video
					</Card.Title>
					<Card.Description>
						Upload the same clip twice — the main-page version and the brewing-page
						<code class="font-mono">_long</code> version. Auto-named
						<code class="font-mono">brewing_adv&lt;N&gt;</code> (next free 1–40, never overwrites a video in use).
					</Card.Description>
				</Card.Header>
				<Card.Content class="space-y-6">
					<div class="grid gap-4 sm:grid-cols-2">
						<div class="space-y-2">
							<Label>Country</Label>
							<Select.Root type="single" bind:value={country}>
								<Select.Trigger class="w-full">{countryLabel}</Select.Trigger>
								<Select.Content>
									{#each COUNTRIES as c (c.value)}
										<Select.Item value={c.value}>{c.label}</Select.Item>
									{/each}
								</Select.Content>
							</Select.Root>
							<p class="text-xs text-muted-foreground">
								Writes to <code class="font-mono">inter/{country}/video</code>{country === 'tha' ? ' + flat video/' : ''}.
							</p>
						</div>
						<div class="space-y-2">
							<Label for="v-name">Name</Label>
							<Input id="v-name" bind:value={name} placeholder="e.g. Bas Bew Bow Brewing" />
							<p class="text-xs text-muted-foreground">Used for the comment &amp; the <code class="font-mono">…VideoEnable</code> variable.</p>
						</div>
						<div class="space-y-2">
							<Label for="v-start" class="flex items-center gap-1.5"><CalendarDays class="h-3.5 w-3.5" /> Start date</Label>
							<Input id="v-start" type="date" bind:value={startDate} />
						</div>
						<div class="space-y-2">
							<Label for="v-end" class="flex items-center gap-1.5"><CalendarDays class="h-3.5 w-3.5" /> End date <span class="text-muted-foreground">(optional)</span></Label>
							<Input id="v-end" type="date" bind:value={endDate} />
							{#if !endDate}<p class="text-xs text-muted-foreground">Blank = open-ended</p>{/if}
						</div>
					</div>

					<!-- two upload tiles -->
					<div class="grid gap-4 md:grid-cols-2">
						<!-- main -->
						<div class="rounded-xl border p-3">
							<div class="mb-2 flex items-center gap-2 text-sm font-semibold">
								<Film class="h-4 w-4 text-muted-foreground" /> Main-page video
								<Badge variant="outline" class="ml-auto font-mono text-[10px]">brewing_adv&lt;N&gt;.mp4</Badge>
							</div>
							{#if mainFile}
								<div class="relative aspect-video overflow-hidden rounded-lg bg-black">
									<!-- svelte-ignore a11y_media_has_caption -->
									<video src={mainPreview} class="h-full w-full object-contain" muted controls></video>
									<button class="absolute right-1.5 top-1.5 rounded-full bg-black/60 p-1 text-white" onclick={clearMain}><X class="h-3.5 w-3.5" /></button>
								</div>
								<p class="mt-2 truncate text-xs text-muted-foreground" title={mainFile.name}>{mainFile.name} · {fmtMB(mainFile.size)}</p>
							{:else}
								<label class="flex aspect-video cursor-pointer flex-col items-center justify-center rounded-lg border-2 border-dashed text-muted-foreground transition hover:bg-muted/50 hover:text-foreground">
									<input type="file" accept=".mp4,video/mp4" class="hidden" onchange={pickMain} />
									<Film class="mb-2 h-8 w-8" />
									<span class="text-sm font-medium">Click to select .mp4</span>
								</label>
							{/if}
						</div>

						<!-- brewing -->
						<div class="rounded-xl border p-3">
							<div class="mb-2 flex items-center gap-2 text-sm font-semibold">
								<CoffeeIcon class="h-4 w-4 text-muted-foreground" /> Brewing-page video
								<Badge variant="outline" class="ml-auto font-mono text-[10px]">brewing_adv&lt;N&gt;_long.mp4</Badge>
							</div>
							{#if brewingFile}
								<div class="relative aspect-video overflow-hidden rounded-lg bg-black">
									<!-- svelte-ignore a11y_media_has_caption -->
									<video src={brewingPreview} class="h-full w-full object-contain" muted controls></video>
									<button class="absolute right-1.5 top-1.5 rounded-full bg-black/60 p-1 text-white" onclick={clearBrewing}><X class="h-3.5 w-3.5" /></button>
								</div>
								<p class="mt-2 truncate text-xs text-muted-foreground" title={brewingFile.name}>{brewingFile.name} · {fmtMB(brewingFile.size)}</p>
								<p class="mt-1 flex items-center gap-1.5 text-xs font-medium">
									<Clock class="h-3.5 w-3.5 text-muted-foreground" />
									Plays {brewingPlaySeconds}s
									<span class="text-muted-foreground">(length {Math.round(brewingRawSeconds)}s − {DURATION_TRIM})</span>
								</p>
							{:else}
								<label class="flex aspect-video cursor-pointer flex-col items-center justify-center rounded-lg border-2 border-dashed text-muted-foreground transition hover:bg-muted/50 hover:text-foreground">
									<input type="file" accept=".mp4,video/mp4" class="hidden" onchange={pickBrewing} />
									<CoffeeIcon class="mb-2 h-8 w-8" />
									<span class="text-sm font-medium">Click to select _long .mp4</span>
								</label>
							{/if}

							<!-- text overlays (required) -->
							<div class="mt-3 border-t pt-3">
								<p class="mb-2 flex items-center gap-1.5 text-xs font-semibold">
									<ImageIcon class="h-3.5 w-3.5 text-muted-foreground" /> Text overlays (.png, required)
								</p>
								<div class="grid grid-cols-2 gap-2">
									{#each [{ label: 'Thai', name: 'brewing_txt_adv<N>.png', file: brewingTxtFile, preview: brewingTxtPreview, pick: pickBrewingTxt, clear: clearBrewingTxt }, { label: 'English', name: 'brewing_txt_adv<N>_en.png', file: brewingTxtEnFile, preview: brewingTxtEnPreview, pick: pickBrewingTxtEn, clear: clearBrewingTxtEn }] as t (t.label)}
										<div>
											<p class="mb-1 text-[11px] font-medium text-muted-foreground">{t.label}</p>
											{#if t.file}
												<div class="relative overflow-hidden rounded-md border bg-muted/30">
													<img src={t.preview} alt={t.label} class="h-20 w-full object-contain" />
													<button class="absolute right-1 top-1 rounded-full bg-black/60 p-0.5 text-white" onclick={t.clear}><X class="h-3 w-3" /></button>
												</div>
												<p class="mt-1 truncate text-[10px] text-muted-foreground" title={t.file.name}>{t.file.name}</p>
											{:else}
												<label class="flex h-20 cursor-pointer flex-col items-center justify-center gap-1 rounded-md border border-dashed text-[11px] text-muted-foreground transition hover:bg-muted/50">
													<input type="file" accept=".png,image/png" class="hidden" onchange={t.pick} />
													<ImageIcon class="h-4 w-4" /> {t.label} .png
												</label>
											{/if}
										</div>
									{/each}
								</div>
							</div>
						</div>
					</div>

					{#if pushProgress.active && submitting}
						<div class="space-y-1">
							<Progress value={pushProgress.percent} max={100} class="h-2" />
							<p class="text-center text-xs text-muted-foreground">Pushing {pushProgress.name} ({pushProgress.percent}%)</p>
						</div>
					{/if}
				</Card.Content>
				<Card.Footer class="justify-end gap-2 border-t bg-muted/30 py-4">
					<Button size="lg" onclick={handleSubmit} disabled={submitting || !isAdbConnected}>
						{#if submitting}<Spinner class="mr-2 h-4 w-4" />Saving...{:else}<Upload class="mr-2 h-4 w-4" />Create &amp; Push{/if}
					</Button>
				</Card.Footer>
			</Card.Root>

			<!-- Existing -->
			<div class="space-y-3">
				<div class="flex items-center justify-between">
					<h2 class="text-lg font-semibold">Existing videos</h2>
					<Button variant="ghost" size="sm" onclick={loadList} disabled={loadingList}>
						{#if loadingList}<Spinner class="mr-2 h-3.5 w-3.5" />{:else}<RefreshCw class="mr-2 h-3.5 w-3.5" />{/if}Refresh
					</Button>
				</div>

				<!-- managed -->
				{#if managed.length === 0}
					<p class="rounded-lg border border-dashed p-4 text-sm text-muted-foreground">
						No web-managed videos yet. Ones you add above appear here and can be edited.
					</p>
				{:else}
					<div class="grid grid-cols-1 gap-4 lg:grid-cols-2">
						{#each managed as v (v.slug)}
							<Card.Root class="overflow-hidden">
								<div class="grid grid-cols-2 gap-px bg-border">
									<div class="bg-black">
										{#if v.main}
											<!-- svelte-ignore a11y_media_has_caption -->
											<video src={videoUrl(v.main.video)} class="aspect-video w-full object-contain" preload="metadata" muted controls></video>
										{:else}
											<div class="flex aspect-video items-center justify-center text-xs text-muted-foreground">no main</div>
										{/if}
									</div>
									<div class="bg-black">
										{#if v.brewing}
											<!-- svelte-ignore a11y_media_has_caption -->
											<video src={videoUrl(v.brewing.video)} class="aspect-video w-full object-contain" preload="metadata" muted controls></video>
										{:else}
											<div class="flex aspect-video items-center justify-center text-xs text-muted-foreground">no brewing</div>
										{/if}
									</div>
								</div>
								<div class="flex items-center justify-between gap-2 p-3">
									<div class="min-w-0">
										<p class="truncate text-sm font-semibold" title={v.name}>{v.name}</p>
										<div class="mt-1 flex flex-wrap items-center gap-1.5 text-[11px] text-muted-foreground">
											<Badge variant="secondary" class="font-mono">brewing_adv{v.n}</Badge>
											<span class="flex items-center gap-1"><CalendarDays class="h-3 w-3" />{v.range_label}</span>
											{#if v.brewing?.duration}<span class="flex items-center gap-1"><Clock class="h-3 w-3" />{v.brewing.duration}s</span>{/if}
											<Badge variant={v.main ? 'default' : 'outline'} class="text-[10px]">main</Badge>
											<Badge variant={v.brewing ? 'default' : 'outline'} class="text-[10px]">brewing</Badge>
										</div>
									</div>
									<Button variant="outline" size="sm" onclick={() => openEdit(v)}>
										<Pencil class="mr-1.5 h-3.5 w-3.5" />Edit
									</Button>
								</div>
							</Card.Root>
						{/each}
					</div>
				{/if}

				<!-- read-only -->
				<div class="rounded-lg border bg-card">
					<button
						type="button"
						class="flex w-full items-center gap-2 p-3 text-sm font-semibold text-muted-foreground transition hover:bg-muted/40"
						onclick={() => (showReadonly = !showReadonly)}
					>
						<ChevronDown class="h-4 w-4 shrink-0 transition-transform {showReadonly ? 'rotate-180' : ''}" />
						<Lock class="h-3.5 w-3.5" />
						Hand-maintained videos ({readonlyList.length})
						<Badge variant="secondary" class="ml-1 text-[10px]">read-only</Badge>
						<span class="ml-auto text-xs font-normal">{showReadonly ? 'Click to hide' : 'Click to show'}</span>
					</button>
					{#if showReadonly}
						<div class="grid grid-cols-2 gap-3 p-3 pt-0 sm:grid-cols-3 lg:grid-cols-5">
							{#each readonlyList as v (v.filename)}
								<div class="overflow-hidden rounded-md border bg-muted/30">
									<!-- svelte-ignore a11y_media_has_caption -->
									<video src={videoUrl(v.video)} class="aspect-video w-full bg-black object-contain" preload="metadata" muted controls></video>
									<p class="truncate px-1.5 py-1 font-mono text-[10px]" title={v.filename}>{v.filename}</p>
								</div>
							{/each}
						</div>
					{/if}
				</div>
			</div>
		</div>
	</div>
</div>

<!-- Edit dialog -->
<Dialog.Root bind:open={editOpen}>
	<Dialog.Content class="sm:max-w-2xl">
		<Dialog.Header>
			<Dialog.Title>Edit video</Dialog.Title>
			<Dialog.Description>
				{#if editTarget}<code class="font-mono">brewing_adv{editTarget.n}</code> · change dates, rename, or replace either clip{/if}
			</Dialog.Description>
		</Dialog.Header>

		<div class="space-y-4 py-2">
			<div class="space-y-2">
				<Label for="e-name">Name</Label>
				<Input id="e-name" bind:value={editName} />
			</div>
			<div class="grid gap-4 sm:grid-cols-2">
				<div class="space-y-2">
					<Label for="e-start">Start date</Label>
					<Input id="e-start" type="date" bind:value={editStart} />
				</div>
				<div class="space-y-2">
					<Label for="e-end">End date <span class="text-muted-foreground">(optional)</span></Label>
					<Input id="e-end" type="date" bind:value={editEnd} />
				</div>
			</div>

			<div class="grid gap-3 sm:grid-cols-2">
				<div class="rounded-lg border p-2">
					<p class="mb-1.5 flex items-center gap-1.5 text-xs font-semibold"><Film class="h-3.5 w-3.5 text-muted-foreground" /> Main-page</p>
					{#if editTarget?.main}
						<!-- svelte-ignore a11y_media_has_caption -->
						<video src={videoUrl(editTarget.main.video)} class="aspect-video w-full rounded bg-black object-contain" preload="metadata" muted controls></video>
					{/if}
					<label class="mt-2 flex cursor-pointer items-center justify-center gap-1.5 rounded border border-dashed p-1.5 text-xs text-muted-foreground hover:bg-muted/50">
						<input type="file" accept=".mp4,video/mp4" class="hidden" onchange={pickEditMain} />
						<Upload class="h-3.5 w-3.5" /> {editMainFile ? editMainFile.name : 'Replace (optional)'}
					</label>
				</div>
				<div class="rounded-lg border p-2">
					<p class="mb-1.5 flex items-center gap-1.5 text-xs font-semibold"><CoffeeIcon class="h-3.5 w-3.5 text-muted-foreground" /> Brewing-page</p>
					{#if editTarget?.brewing}
						<!-- svelte-ignore a11y_media_has_caption -->
						<video src={videoUrl(editTarget.brewing.video)} class="aspect-video w-full rounded bg-black object-contain" preload="metadata" muted controls></video>
					{/if}
					<label class="mt-2 flex cursor-pointer items-center justify-center gap-1.5 rounded border border-dashed p-1.5 text-xs text-muted-foreground hover:bg-muted/50">
						<input type="file" accept=".mp4,video/mp4" class="hidden" onchange={pickEditBrewing} />
						<Upload class="h-3.5 w-3.5" /> {editBrewingFile ? editBrewingFile.name : 'Replace (optional)'}
					</label>
					{#if editBrewingFile}
						<p class="mt-1 flex items-center gap-1 text-[11px] text-muted-foreground"><Clock class="h-3 w-3" />Plays {editBrewingPlaySeconds}s</p>
					{/if}
					<div class="mt-2 grid grid-cols-2 gap-1.5">
						<label class="flex cursor-pointer items-center justify-center gap-1 rounded border border-dashed p-1.5 text-[11px] text-muted-foreground hover:bg-muted/50">
							<input type="file" accept=".png,image/png" class="hidden" onchange={(e) => pickEditTxt(e, false)} />
							<ImageIcon class="h-3 w-3" /> {editBrewingTxtFile ? 'TH ✓' : 'Text TH (.png)'}
						</label>
						<label class="flex cursor-pointer items-center justify-center gap-1 rounded border border-dashed p-1.5 text-[11px] text-muted-foreground hover:bg-muted/50">
							<input type="file" accept=".png,image/png" class="hidden" onchange={(e) => pickEditTxt(e, true)} />
							<ImageIcon class="h-3 w-3" /> {editBrewingTxtEnFile ? 'EN ✓' : 'Text EN (.png)'}
						</label>
					</div>
				</div>
			</div>

			{#if pushProgress.active}
				<div class="space-y-1">
					<Progress value={pushProgress.percent} max={100} class="h-2" />
					<p class="text-center text-xs text-muted-foreground">Pushing {pushProgress.name} ({pushProgress.percent}%)</p>
				</div>
			{/if}
		</div>

		<Dialog.Footer>
			<Button variant="outline" onclick={() => (editOpen = false)} disabled={editSaving}>Cancel</Button>
			<Button onclick={submitEdit} disabled={editSaving || !isAdbConnected}>
				{#if editSaving}<Spinner class="mr-2 h-4 w-4" />Saving...{:else}<Upload class="mr-2 h-4 w-4" />Save &amp; Push{/if}
			</Button>
		</Dialog.Footer>
	</Dialog.Content>
</Dialog.Root>
