<script lang="ts">
	/**
	 * WorkloadSnapshotsPanel
	 *
	 * Per-workload capture of host-bind data volumes (tar.gz). Create / list /
	 * download / delete. Restore is intentionally NOT here yet — overwriting
	 * live data needs container quiesce + atomic swap and ships separately.
	 *
	 * "Snapshotable" coverage is shown up-front (and which volumes are skipped,
	 * with why) so users are never misled about what is actually captured.
	 */
	import * as api from '$lib/api';
	import { t } from '$lib/i18n';
	import { toasts } from '$lib/stores/toast';
	import { getAuthToken } from '$lib/auth';
	import { formatBytes } from '$lib/format/bytes';
	import ConfirmDialog from './ConfirmDialog.svelte';
	import { IconPlus, IconTrash } from './icons';

	interface Props {
		workloadId: string;
	}
	let { workloadId }: Props = $props();

	let snapshots = $state<api.SnapshotInfo[]>([]);
	let snapshotable = $state<api.SnapshotableInfo | null>(null);
	let loading = $state(true);
	let creating = $state(false);
	let error = $state('');
	let label = $state('');
	let confirmDeleteId = $state<string | null>(null);

	const canSnapshot = $derived((snapshotable?.volumes.length ?? 0) > 0);

	async function load(): Promise<void> {
		loading = true;
		error = '';
		try {
			[snapshots, snapshotable] = await Promise.all([
				api.listWorkloadSnapshots(workloadId),
				api.getWorkloadSnapshotable(workloadId)
			]);
		} catch (e) {
			error = e instanceof Error ? e.message : String(e);
		} finally {
			loading = false;
		}
	}

	// Reload whenever workloadId changes — the parent reuses this instance
	// across /apps/A → /apps/B navigation.
	$effect(() => {
		const _ = workloadId;
		load();
	});

	async function create(): Promise<void> {
		if (creating || !canSnapshot) return;
		creating = true;
		try {
			await api.createWorkloadSnapshot(workloadId, label.trim() || undefined);
			label = '';
			toasts.success($t('apps.detail.snapshots.created'));
			await load();
		} catch (e) {
			toasts.error(e instanceof Error ? e.message : $t('apps.detail.snapshots.createFailed'));
		} finally {
			creating = false;
		}
	}

	async function doDelete(id: string): Promise<void> {
		try {
			await api.deleteSnapshot(id);
			snapshots = snapshots.filter((s) => s.id !== id);
			toasts.success($t('apps.detail.snapshots.deleted'));
		} catch (e) {
			toasts.error(e instanceof Error ? e.message : $t('apps.detail.snapshots.deleteFailed'));
		} finally {
			confirmDeleteId = null;
		}
	}

	async function download(snap: api.SnapshotInfo): Promise<void> {
		try {
			const token = getAuthToken();
			const res = await fetch(api.snapshotDownloadUrl(snap.id), {
				headers: token ? { Authorization: `Bearer ${token}` } : {}
			});
			if (!res.ok) throw new Error(`HTTP ${res.status}`);
			const blob = await res.blob();
			const url = URL.createObjectURL(blob);
			const a = document.createElement('a');
			a.href = url;
			a.download = snap.filename;
			a.click();
			// Defer revocation: a.click() starts the download asynchronously, so
			// revoking synchronously can race the navigation in some engines.
			setTimeout(() => URL.revokeObjectURL(url), 1000);
		} catch (e) {
			toasts.error(e instanceof Error ? e.message : $t('apps.detail.snapshots.downloadFailed'));
		}
	}

	function volCount(manifest: string): number {
		try {
			return (JSON.parse(manifest) as unknown[]).length;
		} catch {
			return 0;
		}
	}
</script>

<section class="panel snap-panel" aria-labelledby="snap-heading">
	<span class="reg reg-tl" aria-hidden="true"></span>
	<span class="reg reg-tr" aria-hidden="true"></span>
	<span class="reg reg-bl" aria-hidden="true"></span>
	<span class="reg reg-br" aria-hidden="true"></span>

	<header class="panel-head">
		<h2 class="panel-title" id="snap-heading">
			{$t('apps.detail.snapshots.title')}<span class="title-accent">.</span>
		</h2>
		<span class="panel-sub">{$t('apps.detail.snapshots.sub')}</span>
	</header>

	{#if error}
		<div class="alert inline-alert" role="alert">
			<span class="alert-tag">ERR</span><span>{error}</span>
		</div>
	{/if}

	{#if loading}
		<p class="hint">{$t('apps.detail.snapshots.loading')}</p>
	{:else}
		<!-- Coverage summary -->
		{#if canSnapshot}
			<p class="hint">
				{$t('apps.detail.snapshots.coverage', { count: String(snapshotable?.volumes.length ?? 0) })}
			</p>
			<p class="hint warn">{$t('apps.detail.snapshots.liveWarning')}</p>
		{:else}
			<p class="hint">{$t('apps.detail.snapshots.noCoverage')}</p>
		{/if}

		{#if snapshotable && snapshotable.skipped.length > 0}
			<div class="skipped">
				<span class="skipped-title"
					>{$t('apps.detail.snapshots.skippedTitle', { count: String(snapshotable.skipped.length) })}</span
				>
				<ul>
					{#each snapshotable.skipped as sk (sk.target)}
						<li><span class="mono">{sk.target}</span> — {sk.reason}</li>
					{/each}
				</ul>
			</div>
		{/if}

		<!-- Create -->
		<div class="snap-create">
			<input
				type="text"
				class="input"
				bind:value={label}
				placeholder={$t('apps.detail.snapshots.labelPlaceholder')}
				autocomplete="off"
				disabled={!canSnapshot || creating}
			/>
			<button class="forge-btn" onclick={create} disabled={!canSnapshot || creating}>
				<IconPlus size={13} />
				<span>{creating ? $t('apps.detail.snapshots.creating') : $t('apps.detail.snapshots.create')}</span>
			</button>
		</div>

		<!-- List -->
		{#if snapshots.length === 0}
			<p class="hint">{$t('apps.detail.snapshots.empty')}</p>
		{:else}
			<table class="forge-table snap-table">
				<thead>
					<tr>
						<th>{$t('apps.detail.snapshots.colLabel')}</th>
						<th>{$t('apps.detail.snapshots.colCreated')}</th>
						<th>{$t('apps.detail.snapshots.colVolumes')}</th>
						<th>{$t('apps.detail.snapshots.colSize')}</th>
						<th></th>
					</tr>
				</thead>
				<tbody>
					{#each snapshots as snap (snap.id)}
						<tr>
							<td>{snap.label || $t('apps.detail.snapshots.unlabeled')}</td>
							<td class="mono-time">{snap.created_at}</td>
							<td>{volCount(snap.manifest)}</td>
							<td class="mono-time">{formatBytes(snap.size_bytes)}</td>
							<td class="snap-actions">
								<button class="forge-btn-ghost" onclick={() => download(snap)}>
									{$t('apps.detail.snapshots.download')}
								</button>
								<button
									class="forge-btn-ghost danger"
									onclick={() => (confirmDeleteId = snap.id)}
									aria-label={$t('apps.detail.snapshots.delete')}
								>
									<IconTrash size={13} />
								</button>
							</td>
						</tr>
					{/each}
				</tbody>
			</table>
		{/if}
	{/if}
</section>

{#if confirmDeleteId}
	<ConfirmDialog
		open={true}
		title={$t('apps.detail.snapshots.confirmDeleteTitle')}
		message={$t('apps.detail.snapshots.confirmDeleteMessage')}
		confirmLabel={$t('apps.detail.snapshots.delete')}
		confirmVariant="danger"
		onconfirm={() => confirmDeleteId && doDelete(confirmDeleteId)}
		oncancel={() => (confirmDeleteId = null)}
	/>
{/if}

<style>
	.snap-panel {
		margin-top: 1rem;
	}
	.hint {
		font-size: 0.72rem;
		color: var(--text-tertiary);
		margin: 0.3rem 0;
	}
	.hint.warn {
		color: var(--accent-warm, #c08458);
	}
	.skipped {
		margin: 0.5rem 0;
		padding: 0.5rem 0.7rem;
		border: 1px dashed var(--border-primary);
		border-radius: 4px;
	}
	.skipped-title {
		font-size: 0.7rem;
		letter-spacing: 0.08em;
		text-transform: uppercase;
		color: var(--text-secondary);
	}
	.skipped ul {
		margin: 0.35rem 0 0;
		padding-left: 1.1rem;
		font-size: 0.74rem;
		color: var(--text-secondary);
	}
	.snap-create {
		display: flex;
		gap: 0.5rem;
		margin: 0.85rem 0;
	}
	.snap-create .input {
		flex: 1;
	}
	.snap-table {
		width: 100%;
		margin-top: 0.5rem;
	}
	.snap-actions {
		display: flex;
		gap: 0.35rem;
		justify-content: flex-end;
	}
</style>
