<script lang="ts">
	import { goto } from '$app/navigation';
	import { getToken, getRole } from '$lib/auth';
	import { api } from '$lib/api';
	import { onMount } from 'svelte';
	import { toast, toastError } from '$lib/toast-store';
	import ConfirmSheet from '$lib/components/ConfirmSheet.svelte';
	import Modal from '$lib/components/Modal.svelte';
	import IconButton from '$lib/components/IconButton.svelte';

	interface StatsDto {
		user_count: number;
		upload_count: number;
		comment_count: number;
		disk_total_bytes: number;
		disk_used_bytes: number;
		disk_free_bytes: number;
	}

	interface ExportJob {
		id: string;
		type: string;
		status: string;
		progress_pct: number;
		error_message: string | null;
		created_at: string;
		completed_at: string | null;
	}

	interface UserSummary {
		id: string;
		display_name: string;
		role: string;
		is_banned: boolean;
		uploads_hidden: boolean;
		upload_count: number;
		total_upload_bytes: number;
		created_at: string;
	}

	type ConfigKind = 'number' | 'bool' | 'text';
	interface ConfigField {
		key: string;
		label: string;
		kind: ConfigKind;
		hint?: string;
	}
	interface ConfigGroup {
		title: string;
		fields: ConfigField[];
	}

	// Grouped sections — adding a new key is one entry in the right group, no other
	// code changes required. The form renders each field based on `kind`.
	const CONFIG_GROUPS: ConfigGroup[] = [
		{
			title: 'Limits & Größen',
			fields: [
				{ key: 'max_image_size_mb',       label: 'Max. Bildgröße (MB)',     kind: 'number' },
				{ key: 'max_video_size_mb',       label: 'Max. Videogröße (MB)',    kind: 'number' }
				// compression_concurrency is set via COMPRESSION_WORKER_CONCURRENCY at
				// boot, not live — omitted so it isn't a dead no-op control.
			]
		},
		{
			title: 'Rate-Limits',
			fields: [
				{ key: 'rate_limits_enabled', label: 'Rate-Limits aktiv',           kind: 'bool', hint: 'Hauptschalter — wenn aus, sind alle Rate-Limits deaktiviert.' },
				{ key: 'upload_rate_enabled', label: 'Upload-Limit aktiv',          kind: 'bool' },
				{ key: 'feed_rate_enabled',   label: 'Feed-Limit aktiv',            kind: 'bool' },
				{ key: 'export_rate_enabled', label: 'Export-Limit aktiv',          kind: 'bool' },
				{ key: 'join_rate_enabled',   label: 'Join-Limit aktiv',            kind: 'bool' },
				{ key: 'upload_rate_per_hour', label: 'Upload-Limit pro Stunde',    kind: 'number' },
				{ key: 'feed_rate_per_min',    label: 'Feed-Anfragen pro Minute',   kind: 'number' },
				{ key: 'export_rate_per_day',  label: 'Export-Downloads pro Tag',   kind: 'number' }
			]
		},
		{
			title: 'Quoten',
			fields: [
				{ key: 'quota_enabled',              label: 'Quoten aktiv', kind: 'bool', hint: 'Hauptschalter — wenn aus, wird nichts geprüft.' },
				{ key: 'storage_quota_enabled',      label: 'Speicher-Quote aktiv', kind: 'bool' },
				{ key: 'upload_count_quota_enabled', label: 'Upload-Anzahl-Quote aktiv', kind: 'bool', hint: 'Reserviert für künftige Anzahl-Limits.' },
				{ key: 'quota_tolerance',            label: 'Toleranz (0–1)', kind: 'number' },
				{ key: 'estimated_guest_count',      label: 'Geschätzte Gästezahl', kind: 'number' }
			]
		},
		{
			title: 'Datenschutzhinweis',
			fields: [
				{ key: 'privacy_note', label: 'Datenschutzhinweis (freier Text)', kind: 'text', hint: 'Wird wörtlich im Konto-Bereich angezeigt. Kein HTML — Leerzeichen und Zeilenumbrüche werden übernommen.' }
			]
		}
	];

	function isTrue(v: string | undefined): boolean {
		if (!v) return false;
		return ['true', '1', 'yes', 'on'].includes(v.trim().toLowerCase());
	}

	function toggleBool(key: string) {
		configDraft = { ...configDraft, [key]: isTrue(configDraft[key]) ? 'false' : 'true' };
	}

	type AdminTab = 'stats' | 'config' | 'export' | 'users';
	const TAB_LABELS: Record<AdminTab, string> = { stats: 'Stats', config: 'Config', export: 'Export', users: 'Nutzer' };

	let activeTab = $state<AdminTab>('stats');

	let stats = $state<StatsDto | null>(null);
	let config = $state<Record<string, string>>({});
	let configDraft = $state<Record<string, string>>({});
	let exportJobs = $state<ExportJob[]>([]);
	let users = $state<UserSummary[]>([]);
	let loading = $state(true);
	let saving = $state(false);
	let error = $state<string | null>(null);
	let exportJobsRefreshing = $state(false);

	// Nutzer tab state
	let userSearch = $state('');
	let filteredUsers = $derived(
		userSearch.trim()
			? users.filter((u) => u.display_name.toLowerCase().includes(userSearch.toLowerCase()))
			: users
	);

	// Ban modal state
	let banTarget = $state<UserSummary | null>(null);
	let banSubmitting = $state(false);

	// PIN reset state — `pinModal` holds the freshly-issued plaintext PIN. We forget it
	// the moment the modal closes.
	let pinResetTarget = $state<UserSummary | null>(null);
	let pinResetSubmitting = $state(false);
	let pinModal = $state<{ name: string; pin: string } | null>(null);

	const myRole = getRole();

	// Generic confirm-then-run for irreversible / privilege-changing actions
	// (promote, demote, unban, release gallery). Reuses the shared ConfirmSheet.
	interface PendingConfirm {
		title: string;
		message: string;
		confirmLabel: string;
		tone: 'default' | 'danger';
		run: () => Promise<void>;
	}
	let confirmAction = $state<PendingConfirm | null>(null);

	async function runConfirmAction() {
		const action = confirmAction;
		if (!action) return;
		await action.run();
		confirmAction = null;
	}

	onMount(async () => {
		const token = getToken();
		if (!token) {
			goto('/admin/login');
			return;
		}
		// Trust the *live* role, not the JWT claim: a mid-session demote leaves a
		// stale 'admin' in the token, but the backend now 403s every admin call.
		// Bounce a demoted admin to the feed instead of leaving them on a dashboard
		// that errors on load (mirrors the host page).
		try {
			const ctx = await api.get<{ role: string }>('/me/context');
			if (ctx.role !== 'admin') {
				goto('/feed');
				return;
			}
		} catch {
			// Expired/invalid session (api.ts cleared it) — send them to re-auth.
			goto('/admin/login');
			return;
		}
		await reload();
	});

	async function reload() {
		loading = true;
		error = null;
		try {
			[stats, config, exportJobs, users] = await Promise.all([
				api.get<StatsDto>('/admin/stats'),
				api.get<Record<string, string>>('/admin/config'),
				api.get<ExportJob[]>('/admin/export/jobs'),
				api.get<UserSummary[]>('/host/users')
			]);
			configDraft = { ...config };
		} catch (e: unknown) {
			error = e instanceof Error ? e.message : 'Fehler beim Laden.';
		} finally {
			loading = false;
		}
	}

	async function refreshExportJobs() {
		exportJobsRefreshing = true;
		try {
			exportJobs = await api.get<ExportJob[]>('/admin/export/jobs');
		} finally {
			exportJobsRefreshing = false;
		}
	}

	// Keys rendered as number inputs — used to reject empty/invalid numeric saves.
	const NUMBER_KEYS = new Set(
		CONFIG_GROUPS.flatMap((g) => g.fields.filter((f) => f.kind === 'number').map((f) => f.key))
	);

	async function saveConfig() {
		// Don't let a cleared number field persist as an empty/NaN config value.
		for (const key of NUMBER_KEYS) {
			const v = configDraft[key];
			if (v !== undefined && v !== config[key] && (String(v).trim() === '' || !Number.isFinite(Number(v)))) {
				toastError(new Error('Bitte gib für alle Zahlenfelder einen gültigen Wert ein.'));
				return;
			}
		}
		saving = true;
		try {
			const changes: Record<string, string> = {};
			for (const key of Object.keys(configDraft)) {
				if (configDraft[key] !== config[key]) {
					changes[key] = String(configDraft[key]);
				}
			}
			if (Object.keys(changes).length === 0) {
				toast('Keine Änderungen.', 'info');
				return;
			}
			await api.patch('/admin/config', changes);
			config = { ...configDraft };
			toast('Konfiguration gespeichert.', 'success');
		} catch (e: unknown) {
			toastError(e);
		} finally {
			saving = false;
		}
	}

	async function releaseGallery() {
		try {
			await api.post('/host/gallery/release');
			toast('Galerie wurde freigegeben. Export wird vorbereitet…', 'success');
		} catch (e: unknown) {
			toastError(e);
		}
	}

	function openBanModal(user: UserSummary) {
		banTarget = user;
	}

	async function confirmBan() {
		if (!banTarget) return;
		banSubmitting = true;
		try {
			await api.post(`/host/users/${banTarget.id}/ban`, {});
			toast(`${banTarget.display_name} wurde gesperrt.`, 'success');
			banTarget = null;
			users = await api.get<UserSummary[]>('/host/users');
		} catch (e: unknown) {
			toastError(e);
		} finally {
			banSubmitting = false;
		}
	}

	async function unban(user: UserSummary) {
		try {
			await api.post(`/host/users/${user.id}/unban`);
			toast(`Sperre für ${user.display_name} aufgehoben.`, 'success');
			users = await api.get<UserSummary[]>('/host/users');
		} catch (e: unknown) {
			toastError(e);
		}
	}

	async function promoteToHost(user: UserSummary) {
		try {
			await api.patch(`/host/users/${user.id}/role`, { role: 'host' });
			toast(`${user.display_name} ist jetzt Host.`, 'success');
			users = await api.get<UserSummary[]>('/host/users');
		} catch (e: unknown) {
			toastError(e);
		}
	}

	async function demoteToGuest(user: UserSummary) {
		try {
			await api.patch(`/host/users/${user.id}/role`, { role: 'guest' });
			toast(`${user.display_name} ist jetzt Gast.`, 'success');
			users = await api.get<UserSummary[]>('/host/users');
		} catch (e: unknown) {
			toastError(e);
		}
	}

	function askResetPin(user: UserSummary) {
		pinResetTarget = user;
	}

	async function confirmResetPin() {
		if (!pinResetTarget) return;
		pinResetSubmitting = true;
		try {
			const res = await api.post<{ pin: string }>(`/host/users/${pinResetTarget.id}/pin-reset`);
			pinModal = { name: pinResetTarget.display_name, pin: res.pin };
			pinResetTarget = null;
		} catch (e: unknown) {
			toastError(e);
		} finally {
			pinResetSubmitting = false;
		}
	}

	function copyPinModal() {
		if (!pinModal) return;
		navigator.clipboard.writeText(pinModal.pin);
		toast('PIN kopiert.', 'success');
	}

	/** True iff the current caller may reset this target's PIN. Mirrors the backend
	 *  rules in `handlers::host::reset_user_pin`. */
	function canResetPinFor(target: UserSummary): boolean {
		if (target.role === 'admin') return false;
		if (myRole === 'admin') return true; // any non-admin
		if (myRole === 'host') return target.role === 'guest';
		return false;
	}

	function formatBytes(bytes: number): string {
		if (bytes >= 1024 ** 3) return `${(bytes / 1024 ** 3).toFixed(1)} GB`;
		if (bytes >= 1024 ** 2) return `${(bytes / 1024 ** 2).toFixed(1)} MB`;
		return `${(bytes / 1024).toFixed(1)} KB`;
	}

	function diskPct(s: StatsDto): number {
		if (s.disk_total_bytes === 0) return 0;
		return Math.round((s.disk_used_bytes / s.disk_total_bytes) * 100);
	}

	function jobLabel(type: string): string {
		return type === 'zip' ? 'ZIP-Archiv' : 'HTML-Viewer';
	}

	function statusBadgeClass(status: string): string {
		switch (status) {
			case 'done':    return 'bg-green-100 text-green-700 dark:bg-green-900/40 dark:text-green-200';
			case 'running': return 'bg-blue-100 text-blue-700 dark:bg-blue-900/40 dark:text-blue-200';
			case 'failed':  return 'bg-red-100 text-red-700 dark:bg-red-900/40 dark:text-red-200';
			default:        return 'bg-gray-100 text-gray-600 dark:bg-gray-700 dark:text-gray-300';
		}
	}

	function statusLabel(status: string): string {
		switch (status) {
			case 'pending': return 'Ausstehend';
			case 'running': return 'Läuft';
			case 'done': return 'Fertig';
			case 'failed': return 'Fehlgeschlagen';
			default: return status;
		}
	}
</script>

<!-- Confirmation for irreversible / privilege-changing actions (promote/demote/unban/release). -->
<ConfirmSheet
	open={confirmAction !== null}
	title={confirmAction?.title ?? ''}
	message={confirmAction?.message ?? ''}
	confirmLabel={confirmAction?.confirmLabel ?? 'Bestätigen'}
	tone={confirmAction?.tone ?? 'default'}
	onConfirm={runConfirmAction}
	onCancel={() => (confirmAction = null)}
/>

<!-- PIN reset confirmation — pure yes/no, uses the shared ConfirmSheet. -->
<ConfirmSheet
	open={pinResetTarget !== null}
	title="PIN zurücksetzen"
	message={pinResetTarget
		? `Eine neue PIN für ${pinResetTarget.display_name} wird erzeugt. Die alte PIN funktioniert dann nicht mehr.`
		: ''}
	confirmLabel={pinResetSubmitting ? 'Wird erzeugt…' : 'Neue PIN erzeugen'}
	tone="danger"
	onConfirm={confirmResetPin}
	onCancel={() => (pinResetTarget = null)}
/>

<!-- One-time PIN display modal — focus-trapped, aria-modal, Escape-dismissable. -->
<Modal open={pinModal !== null} titleId="admin-pin-modal-title" onClose={() => (pinModal = null)}>
	{#if pinModal}
		<h2 id="admin-pin-modal-title" class="mb-1 text-lg font-bold text-gray-900 dark:text-gray-100">Neue PIN für {pinModal.name}</h2>
		<p class="mb-4 text-sm text-gray-600 dark:text-gray-400">
			Zeige diese PIN dem Benutzer. Sie wird nur einmal angezeigt — beim Schließen wird sie verworfen.
		</p>
		<div class="mb-4 flex items-center justify-between rounded-lg bg-amber-50 px-4 py-3 dark:bg-amber-950/30">
			<span class="font-mono text-3xl font-bold tracking-widest text-gray-900 dark:text-gray-100">{pinModal.pin}</span>
			<button onclick={copyPinModal} class="rounded-md bg-amber-100 px-3 py-1.5 text-sm font-medium text-amber-800 hover:bg-amber-200 active:bg-amber-200 dark:bg-amber-900/40 dark:text-amber-200 dark:hover:bg-amber-900/60 dark:active:bg-amber-900/60">
				Kopieren
			</button>
		</div>
		<button
			onclick={() => (pinModal = null)}
			class="w-full rounded-lg bg-blue-600 py-2 text-sm font-semibold text-white hover:bg-blue-700 active:bg-blue-700 dark:bg-blue-500 dark:hover:bg-blue-400 dark:active:bg-blue-400"
		>
			Schließen
		</button>
	{/if}
</Modal>

<!-- Ban modal — ban always hides now, so this is a plain confirm (no checkbox). -->
<Modal open={banTarget !== null} titleId="admin-ban-modal-title" onClose={() => (banTarget = null)}>
	{#if banTarget}
		<h2 id="admin-ban-modal-title" class="mb-1 text-lg font-bold text-gray-900 dark:text-gray-100">Benutzer sperren</h2>
		<p class="mb-4 text-sm text-gray-600 dark:text-gray-400">
			<strong>{banTarget.display_name}</strong> wird gesperrt: alle Uploads verschwinden aus
			Galerie, Diashow und Export, und die Sitzung wird beendet. Rückgängig machbar über „Entsperren“.
		</p>
		<div class="flex gap-2">
			<button onclick={() => (banTarget = null)} class="flex-1 rounded-lg border border-gray-300 py-2 text-sm text-gray-700 hover:bg-gray-50 active:bg-gray-100 dark:border-gray-700 dark:text-gray-300 dark:hover:bg-gray-800 dark:active:bg-gray-800">Abbrechen</button>
			<button onclick={confirmBan} disabled={banSubmitting} class="flex-1 rounded-lg bg-red-600 py-2 text-sm font-medium text-white hover:bg-red-700 active:bg-red-700 disabled:opacity-50 dark:bg-red-500 dark:hover:bg-red-400 dark:active:bg-red-400">
				{banSubmitting ? 'Wird gesperrt…' : 'Sperren'}
			</button>
		</div>
	{/if}
</Modal>

<div class="min-h-screen bg-gray-50 pb-24 dark:bg-gray-950">
	<!-- Header -->
	<div class="border-b border-gray-200 bg-white pt-[env(safe-area-inset-top)] dark:border-gray-800 dark:bg-gray-900">
		<div class="mx-auto flex max-w-3xl items-center gap-3 px-4 py-4">
			<IconButton label="Zurück" onclick={() => goto('/account')} class="shrink-0">
				<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.5 19.5L3 12m0 0l7.5-7.5M3 12h18" />
				</svg>
			</IconButton>
			<h1 class="text-xl font-bold text-gray-900 dark:text-gray-100">Admin-Dashboard</h1>
		</div>
	</div>

	<!-- Inner tab bar -->
	<div class="sticky top-0 z-20 overflow-x-auto border-b border-gray-200 bg-white dark:border-gray-800 dark:bg-gray-900">
		<div class="mx-auto flex max-w-3xl min-w-max">
			{#each Object.entries(TAB_LABELS) as [tab, label]}
				<button
					onclick={() => (activeTab = tab as AdminTab)}
					class="px-5 py-3 text-sm font-medium whitespace-nowrap border-b-2 transition-colors
						{activeTab === tab ? 'border-blue-600 text-blue-600 dark:border-blue-400 dark:text-blue-400' : 'border-transparent text-gray-500 hover:text-gray-700 dark:text-gray-400 dark:hover:text-gray-200'}"
				>
					{label}
				</button>
			{/each}
		</div>
	</div>

	<div class="mx-auto max-w-3xl p-4">
		{#if loading}
			<div class="py-16 text-center text-gray-400 dark:text-gray-500">Laden…</div>
		{:else if error}
			<div role="alert" class="rounded-lg bg-red-50 p-4 text-sm text-red-700 dark:bg-red-950/40 dark:text-red-300">{error}</div>
		{:else}

			<!-- ── Stats tab ────────────────────────────────────────────────── -->
			{#if activeTab === 'stats'}
				<div class="space-y-3">
					{#if stats}
						<div class="grid grid-cols-2 gap-3 sm:grid-cols-4">
							<div class="rounded-xl bg-white border border-gray-200 p-4 text-center dark:border-gray-700 dark:bg-gray-800">
								<p class="text-3xl font-bold text-gray-900 dark:text-gray-100">{stats.user_count}</p>
								<p class="mt-0.5 text-xs text-gray-500 dark:text-gray-400">Gäste</p>
							</div>
							<div class="rounded-xl bg-white border border-gray-200 p-4 text-center dark:border-gray-700 dark:bg-gray-800">
								<p class="text-3xl font-bold text-gray-900 dark:text-gray-100">{stats.upload_count}</p>
								<p class="mt-0.5 text-xs text-gray-500 dark:text-gray-400">Uploads</p>
							</div>
							<div class="rounded-xl bg-white border border-gray-200 p-4 text-center dark:border-gray-700 dark:bg-gray-800">
								<p class="text-3xl font-bold text-gray-900 dark:text-gray-100">{stats.comment_count}</p>
								<p class="mt-0.5 text-xs text-gray-500 dark:text-gray-400">Kommentare</p>
							</div>
							<div class="rounded-xl bg-white border border-gray-200 p-4 text-center dark:border-gray-700 dark:bg-gray-800">
								<p class="text-3xl font-bold text-gray-900 dark:text-gray-100">{diskPct(stats)} %</p>
								<p class="mt-0.5 text-xs text-gray-500 dark:text-gray-400">Speicher</p>
							</div>
						</div>
						<!-- Disk bar -->
						<div class="rounded-xl border border-gray-200 bg-white p-5 dark:border-gray-700 dark:bg-gray-800">
							<div class="mb-1 flex items-center justify-between text-xs text-gray-500 dark:text-gray-400">
								<span>Speicherauslastung</span>
								<span>{formatBytes(stats.disk_used_bytes)} / {formatBytes(stats.disk_total_bytes)}</span>
							</div>
							<div class="h-2.5 overflow-hidden rounded-full bg-gray-200 dark:bg-gray-700">
								<div
									class="h-full rounded-full transition-all {diskPct(stats) >= 90 ? 'bg-red-500' : diskPct(stats) >= 75 ? 'bg-amber-500' : 'bg-blue-500'}"
									style="width: {diskPct(stats)}%"
								></div>
							</div>
							<p class="mt-1.5 text-xs text-gray-400 dark:text-gray-500">{formatBytes(stats.disk_free_bytes)} frei</p>
						</div>
					{/if}
				</div>

			<!-- ── Config tab ───────────────────────────────────────────────── -->
			{:else if activeTab === 'config'}
				<div class="relative space-y-3 pb-20">
					{#each CONFIG_GROUPS as group (group.title)}
						<div class="rounded-xl border border-gray-200 bg-white dark:border-gray-700 dark:bg-gray-800">
							<div class="border-b border-gray-100 px-5 py-3 dark:border-gray-700">
								<h3 class="text-xs font-semibold uppercase tracking-wide text-gray-500 dark:text-gray-400">{group.title}</h3>
							</div>
							<div class="space-y-4 px-5 py-4">
								{#each group.fields as field (field.key)}
									<div>
										{#if field.kind === 'bool'}
											<label class="flex cursor-pointer items-start gap-3" for={field.key}>
												<input
													id={field.key}
													type="checkbox"
													class="mt-1 h-4 w-4 rounded border-gray-300 text-blue-600 focus:ring-blue-500 dark:border-gray-600 dark:bg-gray-700"
													checked={isTrue(configDraft[field.key])}
													onchange={() => toggleBool(field.key)}
												/>
												<div class="min-w-0">
													<span class="text-sm font-medium text-gray-900 dark:text-gray-100">{field.label}</span>
													{#if field.hint}
														<p class="text-xs text-gray-500 dark:text-gray-400">{field.hint}</p>
													{/if}
												</div>
											</label>
										{:else if field.kind === 'text'}
											<label for={field.key} class="mb-1 block text-sm font-medium text-gray-700 dark:text-gray-300">{field.label}</label>
											<textarea
												id={field.key}
												rows="6"
												bind:value={configDraft[field.key]}
												class="w-full resize-none rounded-lg border border-gray-300 bg-white px-3 py-2 font-mono text-sm text-gray-900 focus:border-blue-500 focus:outline-none focus:ring-1 focus:ring-blue-200 dark:border-gray-700 dark:bg-gray-900 dark:text-gray-100"
											></textarea>
											{#if field.hint}
												<p class="mt-1 text-xs text-gray-500 dark:text-gray-400">{field.hint}</p>
											{/if}
										{:else}
											<label for={field.key} class="mb-1 block text-sm font-medium text-gray-700 dark:text-gray-300">{field.label}</label>
											<input
												id={field.key}
												type="number"
												step="any"
												min="0"
												inputmode="decimal"
												bind:value={configDraft[field.key]}
												class="w-full rounded-lg border border-gray-300 bg-white px-3 py-2 text-sm text-gray-900 focus:border-blue-500 focus:outline-none focus:ring-1 focus:ring-blue-200 dark:border-gray-700 dark:bg-gray-900 dark:text-gray-100"
											/>
											{#if field.hint}
												<p class="mt-1 text-xs text-gray-500 dark:text-gray-400">{field.hint}</p>
											{/if}
										{/if}
									</div>
								{/each}
							</div>
						</div>
					{/each}

					<!-- Sticky save button -->
					<div class="sticky bottom-0 -mx-4 border-t border-gray-100 bg-white px-5 py-3 dark:border-gray-800 dark:bg-gray-900 sm:mx-0 sm:rounded-b-xl">
						<button
							onclick={saveConfig}
							disabled={saving}
							class="w-full rounded-xl bg-blue-600 py-3 text-sm font-semibold text-white transition hover:bg-blue-700 disabled:opacity-50 dark:bg-blue-500 dark:hover:bg-blue-400"
						>
							{saving ? 'Wird gespeichert…' : 'Speichern'}
						</button>
					</div>
				</div>

			<!-- ── Export tab ───────────────────────────────────────────────── -->
			{:else if activeTab === 'export'}
				<div class="space-y-3">
					<!-- Gallery release -->
					<div class="rounded-xl border border-gray-200 bg-white p-5 dark:border-gray-700 dark:bg-gray-800">
						<h3 class="mb-3 font-semibold text-gray-900 dark:text-gray-100">Galerie</h3>
						<button
							onclick={() => (confirmAction = {
								title: 'Galerie freigeben?',
								message: 'Gäste können dann alle Fotos herunterladen. Das kann nicht rückgängig gemacht werden.',
								confirmLabel: 'Freigeben',
								tone: 'danger',
								run: releaseGallery
							})}
							class="rounded-lg bg-blue-600 px-4 py-2 text-sm font-medium text-white transition hover:bg-blue-700 dark:bg-blue-500 dark:hover:bg-blue-400"
						>
							Galerie freigeben
						</button>
					</div>

					<!-- Export jobs -->
					<div class="rounded-xl border border-gray-200 bg-white p-5 dark:border-gray-700 dark:bg-gray-800">
						<div class="mb-4 flex items-center justify-between">
							<h3 class="font-semibold text-gray-900 dark:text-gray-100">Export-Jobs</h3>
							<button
								onclick={refreshExportJobs}
								disabled={exportJobsRefreshing}
								class="text-xs text-blue-600 hover:underline disabled:opacity-50 dark:text-blue-400"
							>
								{exportJobsRefreshing ? 'Lädt…' : 'Aktualisieren'}
							</button>
						</div>
						{#if exportJobs.length === 0}
							<p class="text-sm text-gray-400 dark:text-gray-500">Noch keine Export-Jobs.</p>
						{:else}
							<div class="space-y-3">
								{#each exportJobs as job}
									<div class="rounded-lg border border-gray-100 p-3 dark:border-gray-700">
										<div class="flex items-center justify-between">
											<span class="text-sm font-medium text-gray-900 dark:text-gray-100">{jobLabel(job.type)}</span>
											<span class="rounded-full px-2 py-0.5 text-xs font-medium {statusBadgeClass(job.status)}">
												{statusLabel(job.status)}
											</span>
										</div>
										{#if job.status === 'running'}
											<div class="mt-2">
												<div class="mb-1 flex justify-between text-xs text-gray-500 dark:text-gray-400">
													<span>Fortschritt</span><span>{job.progress_pct} %</span>
												</div>
												<div class="h-1.5 overflow-hidden rounded-full bg-gray-200 dark:bg-gray-700">
													<div class="h-full rounded-full bg-blue-500 transition-all" style="width: {job.progress_pct}%"></div>
												</div>
											</div>
										{/if}
										{#if job.error_message}
											<p class="mt-1 text-xs text-red-600 dark:text-red-400">{job.error_message}</p>
										{/if}
									</div>
								{/each}
							</div>
						{/if}
					</div>
				</div>

			<!-- ── Nutzer tab ───────────────────────────────────────────────── -->
			{:else if activeTab === 'users'}
				<div class="overflow-hidden rounded-xl border border-gray-200 bg-white dark:border-gray-700 dark:bg-gray-800">
					<!-- Search -->
					<div class="p-4">
						<div class="flex items-center gap-2 rounded-lg border border-gray-200 bg-gray-50 px-3 py-2 dark:border-gray-700 dark:bg-gray-900">
							<svg class="h-4 w-4 shrink-0 text-gray-400 dark:text-gray-500" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
								<path stroke-linecap="round" stroke-linejoin="round" d="M21 21l-5.197-5.197m0 0A7.5 7.5 0 105.196 5.196a7.5 7.5 0 0010.607 10.607z" />
							</svg>
							<input
								type="search"
								placeholder="Nutzer suchen…"
								bind:value={userSearch}
								class="min-w-0 flex-1 bg-transparent text-sm text-gray-900 placeholder-gray-400 outline-none dark:text-gray-100 dark:placeholder-gray-500"
							/>
						</div>
					</div>
					{#if filteredUsers.length === 0}
						<p class="px-5 py-8 text-center text-sm text-gray-400 dark:text-gray-500">Keine Treffer.</p>
					{:else}
						<div class="divide-y divide-gray-100 dark:divide-gray-700">
							{#each filteredUsers as user}
								<div class="flex items-center gap-3 px-5 py-3">
									<div class="min-w-0 flex-1">
										<div class="flex flex-wrap items-center gap-1.5">
											<span class="font-medium text-gray-900 dark:text-gray-100">{user.display_name}</span>
											{#if user.role === 'host'}
												<span class="rounded-full bg-blue-100 px-2 py-0.5 text-xs font-medium text-blue-700 dark:bg-blue-900/40 dark:text-blue-200">Host</span>
											{:else if user.role === 'admin'}
												<span class="rounded-full bg-purple-100 px-2 py-0.5 text-xs font-medium text-purple-700 dark:bg-purple-900/40 dark:text-purple-200">Admin</span>
											{/if}
											{#if user.is_banned}
												<span class="rounded-full bg-red-100 px-2 py-0.5 text-xs font-medium text-red-700 dark:bg-red-900/40 dark:text-red-200">Gesperrt</span>
											{/if}
										</div>
										<p class="text-xs text-gray-400 dark:text-gray-500">
											{user.upload_count} Upload{user.upload_count !== 1 ? 's' : ''} · {formatBytes(user.total_upload_bytes)}
										</p>
									</div>
								<div class="flex shrink-0 flex-wrap justify-end gap-1.5">
									{#if user.role !== 'admin'}
										{#if user.is_banned}
											<button onclick={() => (confirmAction = {
												title: 'Sperre aufheben?',
												message: `${user.display_name} kann danach wieder hochladen, liken und kommentieren.`,
												confirmLabel: 'Entsperren',
												tone: 'default',
												run: () => unban(user)
											})} class="rounded-lg bg-gray-100 px-3 py-1.5 text-xs font-medium text-gray-700 hover:bg-gray-200 dark:bg-gray-700 dark:text-gray-200 dark:hover:bg-gray-600">
												Entsperren
											</button>
										{:else}
											{#if user.role === 'guest'}
												<button onclick={() => (confirmAction = {
													title: 'Zum Host befördern?',
													message: `${user.display_name} erhält Host-Rechte: sperren, PIN zurücksetzen und Galerie verwalten. Das lässt sich nur durch Degradieren rückgängig machen.`,
													confirmLabel: 'Befördern',
													tone: 'default',
													run: () => promoteToHost(user)
												})} class="rounded-lg bg-blue-50 px-3 py-1.5 text-xs font-medium text-blue-700 hover:bg-blue-100 dark:bg-blue-900/40 dark:text-blue-200 dark:hover:bg-blue-900/60">
													Host
												</button>
											{/if}
											{#if user.role === 'host'}
												<button onclick={() => (confirmAction = {
													title: 'Zum Gast degradieren?',
													message: `${user.display_name} verliert alle Host-Rechte.`,
													confirmLabel: 'Degradieren',
													tone: 'danger',
													run: () => demoteToGuest(user)
												})} class="rounded-lg bg-gray-100 px-3 py-1.5 text-xs font-medium text-gray-700 hover:bg-gray-200 dark:bg-gray-700 dark:text-gray-200 dark:hover:bg-gray-600">
													Degradieren
												</button>
											{/if}
											{#if canResetPinFor(user)}
												<button onclick={() => askResetPin(user)} class="rounded-lg bg-amber-50 px-3 py-1.5 text-xs font-medium text-amber-700 hover:bg-amber-100 dark:bg-amber-900/40 dark:text-amber-200 dark:hover:bg-amber-900/60">
													PIN zurücksetzen
												</button>
											{/if}
											<button onclick={() => openBanModal(user)} class="rounded-lg bg-red-50 px-3 py-1.5 text-xs font-medium text-red-700 hover:bg-red-100 dark:bg-red-950/40 dark:text-red-300 dark:hover:bg-red-950/60">
												Sperren
											</button>
										{/if}
									{/if}
								</div>
								</div>
							{/each}
						</div>
					{/if}
				</div>
			{/if}
		{/if}
	</div>
</div>
