<script lang="ts">
	import { enhance } from '$app/forms';
	import { untrack, getContext } from 'svelte';
	import type { PageData, ActionData } from './$types';
	import { audioStore } from '$lib/audio.svelte';
	import { browser } from '$app/environment';
	import { page } from '$app/state';
	import type { Voice } from '$lib/types';
	import * as m from '$lib/paraglide/messages.js';

	let { data, form }: { data: PageData; form: ActionData } = $props();

	// ── Polar checkout ───────────────────────────────────────────────────────────
	// Customer portal: always link to the org portal
	const manageUrl = `https://polar.sh/libnovel/portal`;

	let checkoutLoading = $state<'monthly' | 'annual' | null>(null);
	let checkoutError = $state('');

	async function startCheckout(product: 'monthly' | 'annual') {
		checkoutLoading = product;
		checkoutError = '';
		try {
			const res = await fetch('/api/checkout', {
				method: 'POST',
				headers: { 'Content-Type': 'application/json' },
				body: JSON.stringify({ product })
			});
			if (!res.ok) {
				const body = await res.json().catch(() => ({})) as { message?: string };
				checkoutError = body.message ?? `Checkout failed (${res.status}). Please try again.`;
				return;
			}
			const { url } = await res.json() as { url: string };
			window.location.href = url;
		} catch {
			checkoutError = 'Network error. Please try again.';
		} finally {
			checkoutLoading = null;
		}
	}

	// ── Avatar ───────────────────────────────────────────────────────────────────
	// Show a welcome banner when Polar redirects back with ?subscribed=1
	const justSubscribed = $derived(browser && page.url.searchParams.get('subscribed') === '1');

	let avatarUrl = $state<string | null>(untrack(() => data.avatarUrl ?? null));
	let avatarUploading = $state(false);
	let avatarError = $state('');
	let fileInput: HTMLInputElement | null = null;

	let cropFile = $state<File | null>(null);

	function handleAvatarChange(e: Event) {
		const input = e.target as HTMLInputElement;
		const file = input.files?.[0];
		if (!file) return;
		if (fileInput) fileInput.value = '';
		cropFile = file;
	}

	async function handleCropConfirm(blob: Blob, mimeType: string) {
		cropFile = null;
		avatarUploading = true;
		avatarError = '';
		try {
			const res = await fetch('/api/profile/avatar', {
				method: 'POST',
				headers: { 'Content-Type': mimeType },
				body: blob
			});
			if (!res.ok) {
				const body = await res.json().catch(() => ({})) as { message?: string };
				avatarError = body.message ?? `Upload failed (${res.status})`;
				return;
			}
			const result = await res.json() as { avatar_url: string | null };
			avatarUrl = result.avatar_url;
		} catch {
			avatarError = 'Network error during upload';
		} finally {
			avatarUploading = false;
		}
	}

	function handleCropCancel() {
		cropFile = null;
	}

	// ── Voices ───────────────────────────────────────────────────────────────────
	let voices = $state<Voice[]>([]);
	let voicesLoaded = $state(false);

	const kokoroVoices = $derived(voices.filter((v) => v.engine === 'kokoro'));
	const pocketVoices = $derived(voices.filter((v) => v.engine === 'pocket-tts'));
	const cfaiVoices = $derived(voices.filter((v) => v.engine === 'cfai'));

	function voiceLabel(v: Voice): string {
		if (v.engine === 'cfai') {
			const speaker = v.id.startsWith('cfai:') ? v.id.slice(5) : v.id;
			return speaker.replace(/\b\w/g, (c) => c.toUpperCase()) + (v.gender ? ` (EN ${v.gender.toUpperCase()})` : '');
		}
		if (v.engine === 'pocket-tts') {
			const name = v.id.replace(/_/g, ' ').replace(/\b\w/g, (c) => c.toUpperCase());
			return name + (v.gender ? ` (EN ${v.gender.toUpperCase()})` : '');
		}
		// Kokoro: "af_bella" → "Bella (US F)"
		const langMap: Record<string, string> = {
			af: 'US', am: 'US', bf: 'UK', bm: 'UK',
			ef: 'ES', em: 'ES', ff: 'FR',
			hf: 'IN', hm: 'IN', 'if': 'IT', im: 'IT',
			jf: 'JP', jm: 'JP', pf: 'PT', pm: 'PT', zf: 'ZH', zm: 'ZH',
		};
		const prefix = v.id.slice(0, 2);
		const name = v.id.slice(3).replace(/^v0/, '').replace(/^([a-z])/, (c) => c.toUpperCase());
		const lang = langMap[prefix] ?? prefix.toUpperCase();
		const gender = v.gender ? v.gender.toUpperCase() : '?';
		return `${name} (${lang} ${gender})`;
	}

	$effect(() => {
		fetch('/api/voices')
			.then((r) => r.json())
			.then((d: { voices: Voice[] }) => { voices = d.voices ?? []; voicesLoaded = true; })
			.catch(() => { voicesLoaded = true; });
	});

	// ── Settings state ───────────────────────────────────────────────────────────
	let voice = $state(audioStore.voice);
	let speed = $state(audioStore.speed);
	let autoNext = $state(audioStore.autoNext);

	$effect(() => {
		voice = audioStore.voice;
		speed = audioStore.speed;
		autoNext = audioStore.autoNext;
	});

	const settingsCtx = getContext<{ current: string; fontFamily: string; fontSize: number } | undefined>('theme');
	let selectedTheme = $state(untrack(() => data.settings?.theme ?? settingsCtx?.current ?? 'amber'));
	let selectedFontFamily = $state(untrack(() => data.settings?.fontFamily ?? settingsCtx?.fontFamily ?? 'system'));
	let selectedFontSize = $state(untrack(() => data.settings?.fontSize ?? settingsCtx?.fontSize ?? 1.0));

	const THEMES: { id: string; label: () => string; swatch: string; light?: boolean }[] = [
		{ id: 'amber',       label: () => m.profile_theme_amber(),       swatch: '#f59e0b' },
		{ id: 'slate',       label: () => m.profile_theme_slate(),       swatch: '#818cf8' },
		{ id: 'rose',        label: () => m.profile_theme_rose(),        swatch: '#fb7185' },
		{ id: 'forest',      label: () => m.profile_theme_forest(),      swatch: '#4ade80' },
		{ id: 'mono',        label: () => m.profile_theme_mono(),        swatch: '#f4f4f5' },
		{ id: 'cyber',       label: () => m.profile_theme_cyber(),       swatch: '#22d3ee' },
		{ id: 'light',       label: () => m.profile_theme_light(),       swatch: '#d97706', light: true },
		{ id: 'light-slate', label: () => m.profile_theme_light_slate(), swatch: '#4f46e5', light: true },
		{ id: 'light-rose',  label: () => m.profile_theme_light_rose(),  swatch: '#e11d48', light: true },
	];

	const FONTS = [
		{ id: 'system', label: () => m.profile_font_system() },
		{ id: 'serif',  label: () => m.profile_font_serif() },
		{ id: 'mono',   label: () => m.profile_font_mono() },
	];

	const FONT_SIZES = [
		{ value: 0.9,  label: () => m.profile_text_size_sm() },
		{ value: 1.0,  label: () => m.profile_text_size_md() },
		{ value: 1.15, label: () => m.profile_text_size_lg() },
		{ value: 1.3,  label: () => m.profile_text_size_xl() },
	];

	// ── Auto-save ────────────────────────────────────────────────────────────────
	type SaveStatus = 'idle' | 'saving' | 'saved';
	let saveStatus = $state<SaveStatus>('idle');
	let saveTimer = 0;
	let savedTimer = 0;
	let initialized = false;

	$effect(() => {
		// Read all settings deps to subscribe
		const t = selectedTheme;
		const ff = selectedFontFamily;
		const fs = selectedFontSize;
		const v = voice;
		const sp = speed;
		const an = autoNext;

		// Apply context immediately (font/theme previews live without waiting for save)
		if (settingsCtx) {
			settingsCtx.current = t;
			settingsCtx.fontFamily = ff;
			settingsCtx.fontSize = fs;
		}
		audioStore.voice = v;
		audioStore.autoNext = an;

		if (!initialized) { initialized = true; return; }

		clearTimeout(saveTimer);
		saveTimer = setTimeout(async () => {
			saveStatus = 'saving';
			try {
				await fetch('/api/settings', {
					method: 'PUT',
					headers: { 'Content-Type': 'application/json' },
					body: JSON.stringify({ autoNext: an, voice: v, speed: sp, theme: t, fontFamily: ff, fontSize: fs })
				});
				saveStatus = 'saved';
				clearTimeout(savedTimer);
				savedTimer = setTimeout(() => (saveStatus = 'idle'), 2000) as unknown as number;
			} catch {
				saveStatus = 'idle';
			}
		}, 800) as unknown as number;
	});

	// ── Tab ──────────────────────────────────────────────────────────────────────
	let activeTab = $state<'profile' | 'stats' | 'history'>('profile');

	// ── Sessions ─────────────────────────────────────────────────────────────────
	type Session = {
		id: string;
		user_agent: string;
		ip: string;
		created_at: string;
		last_seen: string;
		is_current: boolean;
	};

	let sessions = $state<Session[]>(untrack(() => data.sessions ?? []));
	let revokingId = $state<string | null>(null);
	let revokeError = $state('');

	async function revokeSession(session: Session) {
		revokingId = session.id;
		revokeError = '';
		try {
			const res = await fetch(`/api/sessions/${session.id}`, { method: 'DELETE' });
			if (!res.ok) { revokeError = 'Failed to end session. Please try again.'; return; }
			if (session.is_current) {
				const logoutForm = document.getElementById('logout-form') as HTMLFormElement | null;
				if (logoutForm) logoutForm.submit();
				return;
			}
			sessions = sessions.filter((s) => s.id !== session.id);
		} catch {
			revokeError = 'Network error. Please try again.';
		} finally {
			revokingId = null;
		}
	}

	function formatDate(iso: string): string {
		if (!iso) return '—';
		try {
			return new Intl.DateTimeFormat(undefined, { dateStyle: 'medium', timeStyle: 'short' }).format(new Date(iso));
		} catch { return iso; }
	}

	function parseUA(ua: string): string {
		if (!ua) return 'Unknown browser';
		if (/Mobile/i.test(ua)) {
			const match = ua.match(/\(([^)]+)\)/);
			return match ? `Mobile — ${match[1].split(';')[0].trim()}` : 'Mobile device';
		}
		if (/Chrome\/(\d+)/i.test(ua)) return `Chrome ${ua.match(/Chrome\/(\d+)/i)![1]}`;
		if (/Firefox\/(\d+)/i.test(ua)) return `Firefox ${ua.match(/Firefox\/(\d+)/i)![1]}`;
		if (/Safari\/(\d+)/i.test(ua) && !/Chrome/i.test(ua)) return 'Safari';
		if (/Edg\/(\d+)/i.test(ua)) return `Edge ${ua.match(/Edg\/(\d+)/i)![1]}`;
		return ua.slice(0, 48) + (ua.length > 48 ? '…' : '');
	}
</script>

<svelte:head>
	<title>{m.profile_page_title()}</title>
</svelte:head>

{#if cropFile && browser}
	{#await import('$lib/components/AvatarCropModal.svelte') then { default: AvatarCropModal }}
		<AvatarCropModal file={cropFile} onconfirm={handleCropConfirm} oncancel={handleCropCancel} />
	{/await}
{/if}

<form id="logout-form" method="POST" action="/logout" class="hidden"></form>

<div class="max-w-2xl mx-auto space-y-6 pb-12">

	<!-- ── Post-checkout success banner ──────────────────────────────────────── -->
	{#if justSubscribed}
	<div class="rounded-xl bg-(--color-brand)/10 border border-(--color-brand)/40 px-5 py-4 flex items-start gap-3">
		<svg class="w-5 h-5 text-(--color-brand) shrink-0 mt-0.5" fill="currentColor" viewBox="0 0 24 24"><path d="M11.99 2C6.47 2 2 6.48 2 12s4.47 10 9.99 10C17.52 22 22 17.52 22 12S17.52 2 11.99 2zm4.24 16L12 15.45 7.77 18l1.12-4.81-3.73-3.23 4.92-.42L12 5l1.92 4.53 4.92.42-3.73 3.23L16.23 18z"/></svg>
		<div>
			<p class="text-sm font-semibold text-(--color-brand)">Welcome to Pro!</p>
			<p class="text-sm text-(--color-muted) mt-0.5">Your subscription is being activated. Refresh the page in a moment if the Pro badge doesn't appear yet.</p>
		</div>
	</div>
	{/if}

	<!-- ── Profile header ──────────────────────────────────────────────────────── -->
	<div class="flex items-center gap-5 pt-2">
		<div class="relative shrink-0">
			<button
				onclick={() => fileInput?.click()}
				class="group relative w-20 h-20 rounded-full overflow-hidden ring-2 ring-(--color-border) hover:ring-(--color-brand) transition-all focus:outline-none"
				title={m.profile_change_avatar()}
				disabled={avatarUploading}
			>
				{#if avatarUrl}
					<img src={avatarUrl} alt="Profile" class="w-full h-full object-cover" />
				{:else}
					<div class="w-full h-full bg-(--color-surface-3) flex items-center justify-center">
						<svg class="w-10 h-10 text-(--color-muted)" fill="currentColor" viewBox="0 0 24 24">
							<path d="M12 12c2.7 0 4.8-2.1 4.8-4.8S14.7 2.4 12 2.4 7.2 4.5 7.2 7.2 9.3 12 12 12zm0 2.4c-3.2 0-9.6 1.6-9.6 4.8v2.4h19.2v-2.4c0-3.2-6.4-4.8-9.6-4.8z"/>
						</svg>
					</div>
				{/if}
				<div class="absolute inset-0 bg-black/50 flex items-center justify-center opacity-0 group-hover:opacity-100 transition-opacity">
					{#if avatarUploading}
						<svg class="w-5 h-5 text-white animate-spin" fill="none" viewBox="0 0 24 24">
							<circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle>
							<path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8v8H4z"></path>
						</svg>
					{:else}
						<svg class="w-5 h-5 text-white" fill="none" stroke="currentColor" viewBox="0 0 24 24">
							<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 9a2 2 0 012-2h.93a2 2 0 001.664-.89l.812-1.22A2 2 0 0110.07 4h3.86a2 2 0 011.664.89l.812 1.22A2 2 0 0018.07 7H19a2 2 0 012 2v9a2 2 0 01-2 2H5a2 2 0 01-2-2V9z"/>
							<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 13a3 3 0 11-6 0 3 3 0 016 0z"/>
						</svg>
					{/if}
				</div>
			</button>
			<input bind:this={fileInput} type="file" accept="image/jpeg,image/png,image/webp" class="hidden" onchange={handleAvatarChange} />
		</div>

		<div class="min-w-0">
			<h1 class="text-2xl font-bold text-(--color-text) truncate">{data.user.username}</h1>
			<div class="flex items-center gap-2 mt-1 flex-wrap">
				<span class="text-xs font-semibold px-2 py-0.5 rounded-full bg-(--color-surface-3) text-(--color-muted) capitalize border border-(--color-border)">{data.user.role}</span>
				{#if data.isPro}
					<span class="inline-flex items-center gap-1 text-xs font-bold px-2 py-0.5 rounded-full bg-(--color-brand)/15 text-(--color-brand) border border-(--color-brand)/30 uppercase tracking-wide">
						<svg class="w-3 h-3" fill="currentColor" viewBox="0 0 24 24"><path d="M11.99 2C6.47 2 2 6.48 2 12s4.47 10 9.99 10C17.52 22 22 17.52 22 12S17.52 2 11.99 2zm4.24 16L12 15.45 7.77 18l1.12-4.81-3.73-3.23 4.92-.42L12 5l1.92 4.53 4.92.42-3.73 3.23L16.23 18z"/></svg>
						{m.profile_plan_pro()}
					</span>
				{/if}
			</div>
			{#if avatarError}
				<p class="text-(--color-danger) text-xs mt-1.5">{avatarError}</p>
			{:else}
				<p class="text-(--color-muted) text-xs mt-1.5">{m.profile_click_to_change()}</p>
			{/if}
		</div>
	</div>

	<!-- Tabs -->
	<div class="flex gap-1 bg-(--color-surface-2) rounded-xl p-1 border border-(--color-border)">
		{#each (['profile', 'stats', 'history'] as const) as tab}
			<button
				type="button"
				onclick={() => (activeTab = tab)}
				class="flex-1 py-2 rounded-lg text-sm font-medium transition-colors
					{activeTab === tab
						? 'bg-(--color-surface-3) text-(--color-text) shadow-sm'
						: 'text-(--color-muted) hover:text-(--color-text)'}"
			>
				{tab === 'profile' ? 'Profile' : tab === 'stats' ? 'Stats' : 'History'}
			</button>
		{/each}
	</div>

	{#if activeTab === 'profile'}
	<!-- ── Subscription ─────────────────────────────────────────────────────────── -->
	{#if !data.isPro}
	<section class="bg-(--color-surface-2) rounded-xl border border-(--color-border) p-6">
		<div class="flex items-start justify-between gap-4">
			<div class="space-y-1">
				<h2 class="text-base font-semibold text-(--color-text)">{m.profile_subscription_heading()}</h2>
				<p class="text-sm text-(--color-muted)">{m.profile_free_limits()}</p>
			</div>
			<span class="shrink-0 inline-flex items-center px-2.5 py-1 rounded-full text-xs font-semibold bg-(--color-surface-3) text-(--color-muted) border border-(--color-border) uppercase tracking-wide">
				{m.profile_plan_free()}
			</span>
		</div>
		<div class="mt-5 pt-5 border-t border-(--color-border)">
			<p class="text-sm font-medium text-(--color-text) mb-1">{m.profile_upgrade_heading()}</p>
			<p class="text-sm text-(--color-muted) mb-4">{m.profile_upgrade_desc()} <a href="/subscribe" class="text-(--color-brand) hover:underline">See plans →</a></p>
			{#if checkoutError}
				<p class="text-sm text-(--color-danger) mb-3">{checkoutError}</p>
			{/if}
			<div class="flex flex-wrap gap-3">
				<button
					type="button"
					onclick={() => startCheckout('monthly')}
					disabled={checkoutLoading !== null}
					class="inline-flex items-center gap-2 px-4 py-2 rounded-lg bg-(--color-brand) text-(--color-surface) font-semibold text-sm hover:bg-(--color-brand-dim) transition-colors disabled:opacity-60 disabled:cursor-wait">
					{#if checkoutLoading === 'monthly'}
						<svg class="w-4 h-4 shrink-0 animate-spin" fill="none" viewBox="0 0 24 24"><circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"/><path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8v8H4z"/></svg>
					{:else}
						<svg class="w-4 h-4 shrink-0" fill="currentColor" viewBox="0 0 24 24"><path d="M11.99 2C6.47 2 2 6.48 2 12s4.47 10 9.99 10C17.52 22 22 17.52 22 12S17.52 2 11.99 2zm4.24 16L12 15.45 7.77 18l1.12-4.81-3.73-3.23 4.92-.42L12 5l1.92 4.53 4.92.42-3.73 3.23L16.23 18z"/></svg>
					{/if}
					{m.profile_upgrade_monthly()}
				</button>
				<button
					type="button"
					onclick={() => startCheckout('annual')}
					disabled={checkoutLoading !== null}
					class="inline-flex items-center gap-2 px-4 py-2 rounded-lg border border-(--color-brand) text-(--color-brand) font-semibold text-sm hover:bg-(--color-brand)/10 transition-colors disabled:opacity-60 disabled:cursor-wait">
					{#if checkoutLoading === 'annual'}
						<svg class="w-4 h-4 shrink-0 animate-spin" fill="none" viewBox="0 0 24 24"><circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"/><path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8v8H4z"/></svg>
					{:else}
						{m.profile_upgrade_annual()}
						<span class="text-xs font-bold px-1.5 py-0.5 rounded bg-(--color-brand)/15 text-(--color-brand) border border-(--color-brand)/30">–33%</span>
					{/if}
				</button>
			</div>
		</div>
	</section>
	{:else}
	<section class="bg-(--color-surface-2) rounded-xl border border-(--color-border) p-5 flex items-center justify-between gap-4">
		<div>
			<p class="text-sm font-medium text-(--color-text)">{m.profile_pro_active()}</p>
			<p class="text-sm text-(--color-muted) mt-0.5">{m.profile_pro_perks()}</p>
		</div>
		<a href={manageUrl} target="_blank" rel="noopener noreferrer"
			class="shrink-0 inline-flex items-center gap-1.5 text-sm font-medium text-(--color-brand) hover:underline">
			{m.profile_manage_subscription()}
			<svg class="w-3.5 h-3.5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 6H6a2 2 0 00-2 2v10a2 2 0 002 2h10a2 2 0 002-2v-4M14 4h6m0 0v6m0-6L10 14"/></svg>
		</a>
	</section>
	{/if}

	<!-- ── Preferences ──────────────────────────────────────────────────────────── -->
	<section class="bg-(--color-surface-2) rounded-xl border border-(--color-border) divide-y divide-(--color-border)">

		<!-- Section header with auto-save indicator -->
		<div class="flex items-center justify-between px-6 py-4">
			<h2 class="text-base font-semibold text-(--color-text)">Preferences</h2>
			<span class="text-xs transition-all duration-300 {saveStatus === 'saving' ? 'text-(--color-muted)' : saveStatus === 'saved' ? 'text-(--color-success)' : 'opacity-0 pointer-events-none'}">
				{#if saveStatus === 'saving'}
					{m.profile_saving()}…
				{:else if saveStatus === 'saved'}
					✓ {m.profile_saved()}
				{:else}
					{m.profile_saved()}
				{/if}
			</span>
		</div>

		<!-- Theme -->
		<div class="px-6 py-5 space-y-3">
			<p class="text-sm font-medium text-(--color-text)">{m.profile_theme_label()}</p>
			<div class="flex gap-2 flex-wrap items-center">
				{#each THEMES as t, i}
					{#if i === 3}
						<span class="w-px h-6 bg-(--color-border) mx-1 self-center"></span>
					{/if}
					<button
						type="button"
						onclick={() => (selectedTheme = t.id)}
						class="flex items-center gap-2 px-3 py-2 rounded-lg border text-sm font-medium transition-colors
							{selectedTheme === t.id
								? 'border-(--color-brand) bg-(--color-brand)/10 text-(--color-brand)'
								: 'border-(--color-border) bg-(--color-surface-3) text-(--color-muted) hover:border-(--color-brand)/50 hover:text-(--color-text)'}"
						aria-pressed={selectedTheme === t.id}
					>
						<span class="w-3 h-3 rounded-full shrink-0 {t.light ? 'ring-1 ring-(--color-border)' : ''}" style="background: {t.swatch};"></span>
						{t.label()}
					</button>
				{/each}
			</div>
		</div>

		<!-- Font family -->
		<div class="px-6 py-5 space-y-3">
			<p class="text-sm font-medium text-(--color-text)">{m.profile_font_family()}</p>
			<div class="flex gap-2 flex-wrap">
				{#each FONTS as f}
					<button
						type="button"
						onclick={() => (selectedFontFamily = f.id)}
						class="px-3 py-2 rounded-lg border text-sm font-medium transition-colors
							{selectedFontFamily === f.id
								? 'border-(--color-brand) bg-(--color-brand)/10 text-(--color-brand)'
								: 'border-(--color-border) bg-(--color-surface-3) text-(--color-muted) hover:border-(--color-brand)/50 hover:text-(--color-text)'}"
						aria-pressed={selectedFontFamily === f.id}
					>
						{f.label()}
					</button>
				{/each}
			</div>
		</div>

		<!-- Text size -->
		<div class="px-6 py-5 space-y-3">
			<p class="text-sm font-medium text-(--color-text)">{m.profile_text_size()}</p>
			<div class="flex gap-2 flex-wrap">
				{#each FONT_SIZES as s}
					<button
						type="button"
						onclick={() => (selectedFontSize = s.value)}
						class="px-3 py-2 rounded-lg border text-sm font-medium transition-colors
							{selectedFontSize === s.value
								? 'border-(--color-brand) bg-(--color-brand)/10 text-(--color-brand)'
								: 'border-(--color-border) bg-(--color-surface-3) text-(--color-muted) hover:border-(--color-brand)/50 hover:text-(--color-text)'}"
						aria-pressed={selectedFontSize === s.value}
					>
						{s.label()}
					</button>
				{/each}
			</div>
		</div>

		<!-- TTS voice -->
		<div class="px-6 py-5 space-y-3">
			<label class="block text-sm font-medium text-(--color-text)" for="voice-select">{m.profile_tts_voice()}</label>
			{#if !voicesLoaded}
				<div class="h-9 bg-(--color-surface-3) rounded-lg animate-pulse"></div>
			{:else if voices.length === 0}
				<select id="voice-select" disabled class="w-full bg-(--color-surface-3) border border-(--color-border) rounded-lg px-3 py-2 text-(--color-muted) text-sm cursor-not-allowed">
					<option>{m.common_loading()}</option>
				</select>
			{:else}
				<select id="voice-select" bind:value={voice}
					class="w-full bg-(--color-surface-3) border border-(--color-border) rounded-lg px-3 py-2 text-(--color-text) text-sm focus:outline-none focus:ring-2 focus:ring-(--color-brand)">
					{#if kokoroVoices.length > 0}
						<optgroup label="Kokoro (GPU)">
							{#each kokoroVoices as v}<option value={v.id}>{voiceLabel(v)}</option>{/each}
						</optgroup>
					{/if}
					{#if pocketVoices.length > 0}
						<optgroup label="Pocket TTS (CPU)">
							{#each pocketVoices as v}<option value={v.id}>{voiceLabel(v)}</option>{/each}
						</optgroup>
					{/if}
					{#if cfaiVoices.length > 0}
						<optgroup label="Cloudflare AI">
							{#each cfaiVoices as v}<option value={v.id}>{voiceLabel(v)}</option>{/each}
						</optgroup>
					{/if}
				</select>
			{/if}
		</div>

		<!-- Playback speed -->
		<div class="px-6 py-5 space-y-3">
			<div class="flex items-center justify-between">
				<label class="text-sm font-medium text-(--color-text)" for="speed-range">{m.profile_playback_speed({ speed: '' })}</label>
				<span class="text-sm font-mono text-(--color-brand)">{speed.toFixed(1)}x</span>
			</div>
			<input id="speed-range" type="range" min="0.5" max="3.0" step="0.1" bind:value={speed}
				style="accent-color: var(--color-brand);" class="w-full" />
			<div class="flex justify-between text-xs text-(--color-muted)">
				<span>0.5x</span><span>3.0x</span>
			</div>
		</div>

		<!-- Auto-advance -->
		<div class="px-6 py-5 flex items-center justify-between">
			<div>
				<p class="text-sm font-medium text-(--color-text)">{m.profile_auto_advance()}</p>
				<p class="text-xs text-(--color-muted) mt-0.5">Automatically load the next chapter when audio finishes</p>
			</div>
			<button
				type="button"
				role="switch"
				aria-checked={autoNext}
				aria-label="Auto-advance to next chapter"
				onclick={() => (autoNext = !autoNext)}
				class="shrink-0 relative inline-flex h-6 w-11 items-center rounded-full transition-colors focus:outline-none focus:ring-2 focus:ring-(--color-brand) focus:ring-offset-2 focus:ring-offset-(--color-surface) {autoNext ? 'bg-(--color-brand)' : 'bg-(--color-surface-3) border border-(--color-border)'}"
			>
				<span class="inline-block h-4 w-4 transform rounded-full bg-white shadow transition-transform {autoNext ? 'translate-x-6' : 'translate-x-1'}"></span>
			</button>
		</div>

	</section>

	<!-- ── Active sessions ──────────────────────────────────────────────────────── -->
	<section class="bg-(--color-surface-2) rounded-xl border border-(--color-border) p-6 space-y-4">
		<div>
			<h2 class="text-base font-semibold text-(--color-text)">{m.profile_sessions_heading()}</h2>
			<p class="text-sm text-(--color-muted) mt-0.5">{m.profile_session_unrecognised()}</p>
		</div>

		{#if revokeError}
			<div class="rounded-lg bg-(--color-danger)/10 border border-(--color-danger) px-4 py-2.5 text-sm text-(--color-danger)">{revokeError}</div>
		{/if}

		{#if sessions.length === 0}
			<p class="text-sm text-(--color-muted) italic">{m.profile_no_sessions()}</p>
		{:else}
			<ul class="space-y-2">
				{#each sessions as session (session.id)}
					<li class="flex items-start justify-between gap-3 rounded-lg px-4 py-3 {session.is_current ? 'bg-(--color-brand)/10 border border-(--color-brand)/30' : 'bg-(--color-surface-3)/50 border border-(--color-border)/50'}">
						<div class="min-w-0 space-y-0.5">
							<div class="flex items-center gap-2 flex-wrap">
								<span class="text-sm font-medium text-(--color-text) truncate">{parseUA(session.user_agent)}</span>
								{#if session.is_current}
									<span class="shrink-0 text-xs font-semibold px-1.5 py-0.5 rounded bg-(--color-brand)/20 text-(--color-brand-dim) border border-(--color-brand)/40">{m.profile_session_this()}</span>
								{/if}
							</div>
							{#if session.ip}
								<p class="text-xs text-(--color-muted) font-mono">{session.ip}</p>
							{/if}
							<p class="text-xs text-(--color-muted)">
								{m.profile_session_signed_in({ date: formatDate(session.created_at) })}
								{#if session.last_seen && session.last_seen !== session.created_at}
									{m.profile_session_last_seen({ date: formatDate(session.last_seen) })}
								{/if}
							</p>
						</div>
						<button
							onclick={() => revokeSession(session)}
							disabled={revokingId === session.id}
							class="shrink-0 px-3 py-1.5 rounded-lg text-xs font-medium transition-colors disabled:opacity-50
								{session.is_current
									? 'bg-(--color-danger)/10 text-(--color-danger) border border-(--color-danger)/60 hover:bg-(--color-danger)/20'
									: 'bg-(--color-surface-3) text-(--color-text) border border-(--color-border) hover:bg-(--color-surface-2)'}"
						>
							{revokingId === session.id ? '…' : session.is_current ? m.profile_session_sign_out() : m.profile_session_end()}
						</button>
					</li>
				{/each}
			</ul>
		{/if}
	</section>
	{/if}

	{#if activeTab === 'stats'}
	<div class="space-y-4">

		<!-- Reading overview -->
		<section class="bg-(--color-surface-2) rounded-xl border border-(--color-border) p-5">
			<h2 class="text-sm font-semibold text-(--color-muted) uppercase tracking-wider mb-4">Reading Overview</h2>
			<div class="grid grid-cols-2 sm:grid-cols-4 gap-3">
				{#each [
					{ label: 'Chapters Read', value: data.stats.totalChaptersRead, icon: '📖' },
					{ label: 'Completed', value: data.stats.booksCompleted, icon: '✅' },
					{ label: 'Reading', value: data.stats.booksReading, icon: '📚' },
					{ label: 'Plan to Read', value: data.stats.booksPlanToRead, icon: '🔖' },
				] as stat}
					<div class="bg-(--color-surface-3) rounded-lg p-3 text-center">
						<p class="text-2xl font-bold text-(--color-text) tabular-nums">{stat.value}</p>
						<p class="text-xs text-(--color-muted) mt-0.5">{stat.label}</p>
					</div>
				{/each}
			</div>
		</section>

		<!-- Streak + rating -->
		<section class="bg-(--color-surface-2) rounded-xl border border-(--color-border) p-5">
			<h2 class="text-sm font-semibold text-(--color-muted) uppercase tracking-wider mb-4">Activity</h2>
			<div class="grid grid-cols-2 gap-3">
				<div class="flex items-center gap-3 bg-(--color-surface-3) rounded-lg p-3">
					<div class="w-9 h-9 rounded-full bg-orange-500/15 flex items-center justify-center text-lg flex-shrink-0">🔥</div>
					<div>
						<p class="text-xl font-bold text-(--color-text) tabular-nums">{data.stats.streak}</p>
						<p class="text-xs text-(--color-muted)">day streak</p>
					</div>
				</div>
				<div class="flex items-center gap-3 bg-(--color-surface-3) rounded-lg p-3">
					<div class="w-9 h-9 rounded-full bg-yellow-500/15 flex items-center justify-center text-lg flex-shrink-0">⭐</div>
					<div>
						<p class="text-xl font-bold text-(--color-text) tabular-nums">
							{data.stats.avgRatingGiven > 0 ? data.stats.avgRatingGiven.toFixed(1) : '—'}
						</p>
						<p class="text-xs text-(--color-muted)">avg rating given</p>
					</div>
				</div>
			</div>
		</section>

		<!-- Top genres -->
		{#if data.stats.topGenres.length > 0}
		<section class="bg-(--color-surface-2) rounded-xl border border-(--color-border) p-5">
			<h2 class="text-sm font-semibold text-(--color-muted) uppercase tracking-wider mb-3">Favourite Genres</h2>
			<div class="flex flex-wrap gap-2">
				{#each data.stats.topGenres as genre, i}
					<span class="flex items-center gap-1.5 px-3 py-1.5 rounded-full text-sm font-medium
						{i === 0 ? 'bg-(--color-brand)/20 text-(--color-brand) border border-(--color-brand)/30' : 'bg-(--color-surface-3) text-(--color-text) border border-(--color-border)'}">
						{#if i === 0}<span class="text-xs">🏆</span>{/if}
						{genre}
					</span>
				{/each}
			</div>
		</section>
		{/if}

		<!-- Dropped books (only if any) -->
		{#if data.stats.booksDropped > 0}
		<p class="text-xs text-(--color-muted) text-center">
			{data.stats.booksDropped} dropped book{data.stats.booksDropped !== 1 ? 's' : ''} —
			<a href="/books" class="text-(--color-brand) hover:underline">revisit your library</a>
		</p>
		{/if}

	</div>
	{/if}

	{#if activeTab === 'history'}
	<div class="space-y-2">
		{#if data.history.length === 0}
			<div class="py-12 text-center text-(--color-muted)">
				<svg class="w-10 h-10 mx-auto mb-3 text-(--color-border)" fill="none" stroke="currentColor" viewBox="0 0 24 24">
					<path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z" />
				</svg>
				<p class="text-sm">No reading history yet.</p>
			</div>
		{:else}
			{#each data.history as item}
				<a
					href="/books/{item.slug}/chapters/{item.chapter}"
					class="flex items-center gap-3 px-4 py-3 bg-(--color-surface-2) rounded-xl border border-(--color-border) hover:border-zinc-500 transition-colors group"
				>
					<!-- Cover thumbnail -->
					<div class="w-8 h-11 rounded overflow-hidden bg-(--color-surface-3) flex-shrink-0">
						{#if item.cover}
							<img src={item.cover} alt={item.title} class="w-full h-full object-cover" loading="lazy" />
						{:else}
							<div class="w-full h-full flex items-center justify-center text-(--color-muted)">
								<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
									<path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M12 6.253v13m0-13C10.832 5.477 9.246 5 7.5 5S4.168 5.477 3 6.253v13C4.168 18.477 5.754 18 7.5 18s3.332.477 4.5 1.253m0-13C13.168 5.477 14.754 5 16.5 5c1.747 0 3.332.477 4.5 1.253v13C19.832 18.477 18.247 18 16.5 18c-1.746 0-3.332.477-4.5 1.253" />
								</svg>
							</div>
						{/if}
					</div>

					<!-- Title + chapter -->
					<div class="flex-1 min-w-0">
						<p class="text-sm font-medium text-(--color-text) truncate group-hover:text-(--color-brand) transition-colors">{item.title}</p>
						<p class="text-xs text-(--color-muted) mt-0.5">Chapter {item.chapter}</p>
					</div>

					<!-- Relative time -->
					<p class="text-xs text-(--color-muted) shrink-0 tabular-nums">
						{#if item.updated}
							{(() => {
								const ms = Date.now() - new Date(item.updated).getTime();
								const mins = Math.floor(ms / 60000);
								if (mins < 60) return mins <= 1 ? 'just now' : `${mins}m ago`;
								const hrs = Math.floor(mins / 60);
								if (hrs < 24) return `${hrs}h ago`;
								const days = Math.floor(hrs / 24);
								if (days < 30) return `${days}d ago`;
								return new Date(item.updated).toLocaleDateString(undefined, { month: 'short', day: 'numeric' });
							})()}
						{/if}
					</p>
				</a>
			{/each}
		{/if}
	</div>
	{/if}

</div>
