<script lang="ts">
	import { onMount, onDestroy } from 'svelte';
	import MdiIcon from './MdiIcon.svelte';
	import { t } from '$lib/i18n';
	import { portal } from '$lib/portal';

	let {
		value = $bindable<string>('UTC'),
	}: {
		value: string;
	} = $props();

	// --- Catalog -----------------------------------------------------------

	const timezones = $derived.by<string[]>(() => {
		try {
			const intl = Intl as unknown as { supportedValuesOf?: (k: string) => string[] };
			if (typeof intl.supportedValuesOf === 'function') {
				return intl.supportedValuesOf('timeZone');
			}
		} catch { /* fall through */ }
		return ['UTC'];
	});

	const detectedTz = (() => {
		try { return Intl.DateTimeFormat().resolvedOptions().timeZone || 'UTC'; }
		catch { return 'UTC'; }
	})();

	// --- Live clock --------------------------------------------------------

	let now = $state(new Date());
	let tickHandle: ReturnType<typeof setInterval> | null = null;
	onMount(() => {
		tickHandle = setInterval(() => { now = new Date(); }, 1000);
	});
	onDestroy(() => { if (tickHandle) clearInterval(tickHandle); });

	function splitTz(tz: string): { region: string; city: string } {
		if (!tz || tz === 'UTC' || tz === 'Etc/UTC') return { region: 'UTC', city: 'UTC' };
		const parts = tz.split('/');
		if (parts.length === 1) return { region: 'Other', city: parts[0].replace(/_/g, ' ') };
		const city = parts[parts.length - 1].replace(/_/g, ' ');
		const region = parts.slice(0, -1).join(' / ').replace(/_/g, ' ');
		return { region, city };
	}

	function fmtTime(tz: string): string {
		try {
			return new Intl.DateTimeFormat('en-GB', {
				timeZone: tz,
				hour: '2-digit',
				minute: '2-digit',
				second: '2-digit',
				hour12: false,
			}).format(now);
		} catch { return '--:--:--'; }
	}

	function fmtDate(tz: string): string {
		try {
			return new Intl.DateTimeFormat(undefined, {
				timeZone: tz,
				weekday: 'short',
				day: 'numeric',
				month: 'short',
			}).format(now);
		} catch { return ''; }
	}

	function fmtOffset(tz: string): string {
		try {
			const parts = new Intl.DateTimeFormat('en-US', {
				timeZone: tz,
				timeZoneName: 'shortOffset',
			}).formatToParts(now);
			const off = parts.find(p => p.type === 'timeZoneName')?.value ?? '';
			return off || 'UTC';
		} catch { return ''; }
	}

	// --- Selected state ----------------------------------------------------

	const selected = $derived.by(() => {
		const s = splitTz(value || 'UTC');
		return {
			iana: value || 'UTC',
			region: s.region,
			city: s.city,
			time: fmtTime(value || 'UTC'),
			date: fmtDate(value || 'UTC'),
			offset: fmtOffset(value || 'UTC'),
		};
	});

	// --- Picker ------------------------------------------------------------

	let open = $state(false);
	let query = $state('');
	let highlightIdx = $state(0);
	let inputEl = $state<HTMLInputElement | null>(null);
	let panelEl = $state<HTMLDivElement | null>(null);

	const filtered = $derived.by(() => {
		const q = query.trim().toLowerCase().replace(/\s+/g, '_');
		if (!q) return timezones;
		return timezones.filter(tz => tz.toLowerCase().includes(q));
	});

	// Group filtered tz list by region prefix for visual hierarchy.
	interface Group { region: string; items: string[] }
	const groups = $derived.by<Group[]>(() => {
		const map = new Map<string, string[]>();
		for (const tz of filtered) {
			const region = tz.includes('/') ? tz.split('/')[0] : 'Other';
			if (!map.has(region)) map.set(region, []);
			map.get(region)!.push(tz);
		}
		const REGION_ORDER = ['UTC', 'Europe', 'America', 'Asia', 'Africa', 'Australia', 'Pacific', 'Atlantic', 'Indian', 'Antarctica', 'Arctic', 'Etc', 'Other'];
		return [...map.entries()]
			.sort(([a], [b]) => {
				const ai = REGION_ORDER.indexOf(a);
				const bi = REGION_ORDER.indexOf(b);
				return (ai === -1 ? 99 : ai) - (bi === -1 ? 99 : bi);
			})
			.map(([region, items]) => ({ region, items }));
	});

	// Flattened index for keyboard navigation.
	const flat = $derived<string[]>(groups.flatMap(g => g.items));

	function openPicker() {
		open = true;
		query = '';
		highlightIdx = Math.max(0, flat.indexOf(value));
		requestAnimationFrame(() => {
			inputEl?.focus();
			scrollToHighlight();
		});
	}

	function closePicker() {
		open = false;
		query = '';
	}

	function selectTz(tz: string) {
		value = tz;
		closePicker();
	}

	function onKeydown(e: KeyboardEvent) {
		if (e.key === 'Escape') { closePicker(); return; }
		if (e.key === 'ArrowDown') {
			e.preventDefault();
			highlightIdx = Math.min(highlightIdx + 1, flat.length - 1);
			scrollToHighlight();
		} else if (e.key === 'ArrowUp') {
			e.preventDefault();
			highlightIdx = Math.max(highlightIdx - 1, 0);
			scrollToHighlight();
		} else if (e.key === 'Enter') {
			e.preventDefault();
			if (flat[highlightIdx]) selectTz(flat[highlightIdx]);
		}
	}

	function scrollToHighlight() {
		requestAnimationFrame(() => {
			panelEl?.querySelector('.tz-opt-hl')?.scrollIntoView({ block: 'nearest' });
		});
	}

	$effect(() => { query; highlightIdx = 0; });

	/**
	 * The panel is portalled to <body> to escape Card's overflow:hidden +
	 * backdrop-filter (which would otherwise clip and stacking-trap the
	 * dropdown). Outside-click is detected via the dedicated overlay div
	 * rather than a document listener, so we don't need a global handler.
	 */
</script>

<div class="tz-root">
	<!-- Selected card -->
	<button
		type="button"
		class="tz-card"
		class:tz-card-open={open}
		onclick={() => (open ? closePicker() : openPicker())}
		aria-haspopup="listbox"
		aria-expanded={open}
	>
		<div class="tz-card-left">
			<div class="tz-region">{selected.region}</div>
			<div class="tz-city">{selected.city}</div>
			<div class="tz-sub">
				<span class="tz-iana">{selected.iana}</span>
				{#if selected.date}
					<span class="tz-dot">·</span>
					<span class="tz-date">{selected.date}</span>
				{/if}
			</div>
		</div>
		<div class="tz-card-right">
			<div class="tz-clock">{selected.time}</div>
			<div class="tz-offset">{selected.offset}</div>
		</div>
		<span class="tz-chev" aria-hidden="true">
			<MdiIcon name={open ? 'mdiChevronUp' : 'mdiChevronDown'} size={16} />
		</span>
	</button>

	{#if open}
		<div use:portal class="tz-portal-root">
			<div class="tz-overlay" onclick={closePicker} role="presentation"></div>

			<div class="tz-panel" bind:this={panelEl} role="listbox">
				<!-- Search -->
				<div class="tz-search-row">
					<MdiIcon name="mdiMagnify" size={14} />
					<input
						bind:this={inputEl}
						bind:value={query}
						onkeydown={onKeydown}
						placeholder={t('timezone.searchPlaceholder')}
						class="tz-search"
						autocomplete="off"
						spellcheck="false"
						type="text"
					/>
					<kbd class="tz-kbd">ESC</kbd>
				</div>

				<!-- Quick picks -->
				{#if !query}
					<div class="tz-quick">
						<button
							type="button"
							class="tz-quick-btn"
							class:tz-quick-active={value === detectedTz}
							onclick={() => selectTz(detectedTz)}
						>
							<MdiIcon name="mdiCrosshairsGps" size={12} />
							<span class="tz-quick-label">{t('timezone.detect')}</span>
							<span class="tz-quick-val">{detectedTz}</span>
						</button>
						<button
							type="button"
							class="tz-quick-btn"
							class:tz-quick-active={value === 'UTC' || value === 'Etc/UTC'}
							onclick={() => selectTz('UTC')}
						>
							<MdiIcon name="mdiEarth" size={12} />
							<span class="tz-quick-label">{t('timezone.utc')}</span>
							<span class="tz-quick-val">UTC+00</span>
						</button>
					</div>
				{/if}

				<!-- Grouped list -->
				<div class="tz-list">
					{#if filtered.length === 0}
						<div class="tz-empty">{t('timezone.noMatches')}</div>
					{:else}
						{#each groups as g (g.region)}
							<div class="tz-group">
								<div class="tz-group-head">
									<span class="tz-group-name">{g.region}</span>
									<span class="tz-group-count">{g.items.length}</span>
								</div>
								{#each g.items as tz (tz)}
									{@const parts = splitTz(tz)}
									{@const idx = flat.indexOf(tz)}
									{@const hl = idx === highlightIdx}
									{@const sel = tz === value}
									<button
										type="button"
										role="option"
										aria-selected={sel}
										class="tz-opt"
										class:tz-opt-hl={hl}
										class:tz-opt-sel={sel}
										onmouseenter={() => (highlightIdx = idx)}
										onclick={() => selectTz(tz)}
									>
										<span class="tz-opt-city">{parts.city}</span>
										<span class="tz-opt-iana">{tz}</span>
										<span class="tz-opt-offset">{fmtOffset(tz)}</span>
									</button>
								{/each}
							</div>
						{/each}
					{/if}
				</div>
			</div>
		</div>
	{/if}
</div>

<style>
	.tz-root {
		position: relative;
		width: 100%;
		max-width: 34rem;
	}

	/* ---- Selected card ------------------------------------------------ */
	.tz-card {
		position: relative;
		display: grid;
		grid-template-columns: 1fr auto auto;
		align-items: center;
		gap: 0.875rem;
		width: 100%;
		padding: 0.75rem 1rem 0.75rem 0.875rem;
		border: 1px solid var(--color-border);
		border-radius: 0.625rem;
		background:
			linear-gradient(135deg,
				color-mix(in srgb, var(--color-primary) 5%, transparent) 0%,
				transparent 55%),
			var(--color-background);
		color: var(--color-foreground);
		text-align: left;
		cursor: pointer;
		transition: border-color 0.15s, transform 0.15s, box-shadow 0.15s;
	}
	.tz-card:hover {
		border-color: color-mix(in srgb, var(--color-primary) 45%, var(--color-border));
	}
	.tz-card.tz-card-open {
		border-color: var(--color-primary);
		box-shadow: 0 0 0 3px color-mix(in srgb, var(--color-primary) 12%, transparent);
	}

	.tz-card-left {
		display: flex;
		flex-direction: column;
		gap: 0.1rem;
		min-width: 0;
	}
	.tz-region {
		font-size: 0.6rem;
		text-transform: uppercase;
		letter-spacing: 0.1em;
		font-weight: 600;
		color: color-mix(in srgb, var(--color-primary) 70%, var(--color-muted-foreground));
	}
	.tz-city {
		font-family: var(--font-sans);
		font-size: 1.25rem;
		font-weight: 500;
		line-height: 1.1;
		letter-spacing: -0.01em;
		overflow: hidden;
		text-overflow: ellipsis;
		white-space: nowrap;
	}
	.tz-sub {
		display: flex;
		align-items: center;
		gap: 0.4rem;
		font-size: 0.7rem;
		color: var(--color-muted-foreground);
		min-width: 0;
	}
	.tz-iana {
		font-family: var(--font-mono);
		overflow: hidden;
		text-overflow: ellipsis;
		white-space: nowrap;
	}
	.tz-dot { opacity: 0.5; }

	.tz-card-right {
		display: flex;
		flex-direction: column;
		align-items: flex-end;
		gap: 0.2rem;
	}
	.tz-clock {
		font-family: var(--font-mono);
		font-size: 1.25rem;
		font-weight: 500;
		letter-spacing: 0.02em;
		color: var(--color-foreground);
		line-height: 1;
		/* Stable width so seconds ticker doesn't shift layout */
		font-variant-numeric: tabular-nums;
	}
	.tz-offset {
		font-family: var(--font-mono);
		font-size: 0.6rem;
		font-weight: 600;
		letter-spacing: 0.04em;
		padding: 0.1rem 0.375rem;
		border-radius: 9999px;
		background: color-mix(in srgb, var(--color-primary) 12%, transparent);
		color: var(--color-primary);
		border: 1px solid color-mix(in srgb, var(--color-primary) 25%, transparent);
	}

	.tz-chev {
		color: var(--color-muted-foreground);
		display: inline-flex;
		align-items: center;
	}

	/* ---- Portal + overlay (escapes Card's overflow:hidden / backdrop-filter) ---- */
	.tz-portal-root {
		position: fixed;
		inset: 0;
		z-index: 9998;
		pointer-events: none;
	}
	.tz-overlay {
		position: absolute;
		inset: 0;
		pointer-events: auto;
		background: rgba(0, 0, 0, 0.55);
		backdrop-filter: blur(8px) saturate(120%);
		-webkit-backdrop-filter: blur(8px) saturate(120%);
	}

	/* ---- Panel (centered modal palette) -------------------------------- */
	.tz-panel {
		pointer-events: auto;
		position: absolute;
		top: min(20vh, 120px);
		left: 50%;
		transform: translateX(-50%);
		z-index: 1;
		width: min(540px, 92vw);
		max-height: min(60vh, 30rem);
		background: var(--tz-solid-bg);
		border: 1px solid var(--color-rule-strong, var(--color-border));
		border-radius: 16px;
		box-shadow: var(--shadow-card, 0 18px 40px rgba(0, 0, 0, 0.35)),
		            0 24px 48px -16px rgba(0, 0, 0, 0.55);
		overflow: hidden;
		display: flex;
		flex-direction: column;
		animation: tz-pop 0.15s ease-out;
		--tz-solid-bg: #131520;
	}
	:global([data-theme="light"]) .tz-panel { --tz-solid-bg: #fafafe; }
	.tz-panel::after {
		content: '';
		position: absolute; inset: 0;
		border-radius: inherit;
		pointer-events: none;
		background: linear-gradient(180deg, var(--color-highlight, transparent), transparent 30%);
		opacity: 0.4;
	}
	@keyframes tz-pop {
		from { opacity: 0; transform: translate(-50%, -3px); }
		to   { opacity: 1; transform: translate(-50%, 0); }
	}

	.tz-search-row {
		display: flex;
		align-items: center;
		gap: 0.5rem;
		padding: 0.85rem 1rem;
		border-bottom: 1px solid var(--color-border);
		color: var(--color-muted-foreground);
		position: relative;
		z-index: 1;
	}
	.tz-search {
		flex: 1;
		border: none;
		outline: none;
		background: transparent;
		font-size: 0.85rem;
		color: var(--color-foreground);
		padding: 0.125rem 0;
		min-width: 0;
	}
	.tz-kbd {
		font-size: 0.55rem;
		font-family: var(--font-mono);
		padding: 0.1rem 0.3rem;
		border-radius: 0.2rem;
		background: var(--color-muted);
		color: var(--color-muted-foreground);
		border: 1px solid var(--color-border);
	}

	.tz-quick {
		display: flex;
		gap: 0.375rem;
		padding: 0.5rem 0.625rem;
		border-bottom: 1px solid var(--color-border);
		flex-wrap: wrap;
		position: relative;
		z-index: 1;
	}
	.tz-quick-btn {
		display: inline-flex;
		align-items: center;
		gap: 0.375rem;
		padding: 0.25rem 0.5rem;
		border: 1px solid var(--color-border);
		border-radius: 9999px;
		background: var(--color-background);
		font-size: 0.7rem;
		color: var(--color-foreground);
		cursor: pointer;
		transition: border-color 0.12s, background 0.12s, color 0.12s;
	}
	.tz-quick-btn:hover {
		border-color: color-mix(in srgb, var(--color-primary) 40%, var(--color-border));
		color: var(--color-primary);
	}
	.tz-quick-active {
		border-color: var(--color-primary);
		background: color-mix(in srgb, var(--color-primary) 8%, transparent);
		color: var(--color-primary);
	}
	.tz-quick-label {
		font-weight: 500;
	}
	.tz-quick-val {
		font-family: var(--font-mono);
		font-size: 0.65rem;
		opacity: 0.7;
	}

	.tz-list {
		overflow-y: auto;
		overscroll-behavior: contain;
		/* No top padding — the sticky group head is at top:0 of the
		   scroll container, so any padding-top would let scrolling
		   items leak into the gap above the sticky header. */
		padding: 0 0 0.25rem;
		scrollbar-width: thin;
		position: relative;
		z-index: 1;
	}
	.tz-empty {
		padding: 1rem;
		text-align: center;
		font-size: 0.8rem;
		color: var(--color-muted-foreground);
	}

	.tz-group {
		margin-bottom: 0.125rem;
	}
	.tz-group-head {
		display: flex;
		align-items: baseline;
		justify-content: space-between;
		padding: 0.375rem 0.75rem 0.25rem;
		font-size: 0.55rem;
		text-transform: uppercase;
		letter-spacing: 0.12em;
		font-weight: 600;
		color: var(--color-muted-foreground);
		position: sticky;
		top: 0;
		background: var(--tz-solid-bg);
		border-bottom: 1px solid color-mix(in srgb, var(--color-border) 60%, transparent);
		z-index: 1;
	}
	.tz-group-count {
		font-family: var(--font-mono);
		opacity: 0.6;
	}

	.tz-opt {
		display: grid;
		grid-template-columns: 1fr 1fr auto;
		align-items: center;
		gap: 0.625rem;
		width: 100%;
		padding: 0.35rem 0.75rem;
		border: none;
		background: transparent;
		color: var(--color-foreground);
		cursor: pointer;
		text-align: left;
		transition: background 0.1s;
	}
	.tz-opt.tz-opt-hl {
		background: var(--color-muted);
	}
	.tz-opt.tz-opt-sel {
		background: color-mix(in srgb, var(--color-primary) 8%, transparent);
	}
	.tz-opt-city {
		font-size: 0.85rem;
		font-weight: 500;
		overflow: hidden;
		text-overflow: ellipsis;
		white-space: nowrap;
	}
	.tz-opt.tz-opt-sel .tz-opt-city {
		color: var(--color-primary);
	}
	.tz-opt-iana {
		font-family: var(--font-mono);
		font-size: 0.7rem;
		color: var(--color-muted-foreground);
		overflow: hidden;
		text-overflow: ellipsis;
		white-space: nowrap;
	}
	.tz-opt-offset {
		font-family: var(--font-mono);
		font-size: 0.65rem;
		color: var(--color-muted-foreground);
		padding: 0.1rem 0.375rem;
		border-radius: 0.25rem;
		background: var(--color-muted);
		white-space: nowrap;
	}
	.tz-opt.tz-opt-hl .tz-opt-offset {
		background: color-mix(in srgb, var(--color-primary) 15%, var(--color-muted));
	}
</style>
