<!--
  Fase 17.2 — About-modal that renders the repo-root CHANGELOG.md.

  Architecture notes:
    * CHANGELOG is imported via Vite's `?raw` query so the file is
      bundled at build time — no fetch at runtime, no SSR + adapter-node
      filesystem read, no service-worker miss. Bundle size is ~6 KB
      today; revisit (dynamic import, marked, etc.) when it grows past
      ~30 KB or starts using non-trivial markdown features (tables,
      links, code blocks).
    * The path is 5 levels up from this file:
      components → lib → src → web → apps → repo-root. The plan
      estimated 3; documenting the off-by-two so anyone moving the
      component up/down the tree adjusts the import accordingly.
    * Markdown is parsed inline (`renderMarkdown` below). Input is
      static repo content, never user-supplied, so we're not exposed to
      the usual sanitisation concerns — we still HTML-escape every
      input line before adding our own tags to keep the surface clean
      and to defend against accidentally putting `<` in a future entry.
    * Esc + overlay-click both call `onClose`. We attach the keydown
      listener to `window` only while the modal is open; otherwise
      typing Escape outside the modal would still call back.
-->
<script lang="ts">
	import { onDestroy } from 'svelte';
	import * as m from '$lib/paraglide/messages';
	import changelogRaw from '../../../../../CHANGELOG.md?raw';

	interface Props {
		open: boolean;
		onClose: () => void;
	}
	let { open, onClose }: Props = $props();

	function escapeHtml(s: string): string {
		return s
			.replace(/&/g, '&amp;')
			.replace(/</g, '&lt;')
			.replace(/>/g, '&gt;')
			.replace(/"/g, '&quot;')
			.replace(/'/g, '&#39;');
	}

	// Tailwind class strings for each rendered element. Living here so
	// JIT scans them at build time — putting them inside a template
	// string in renderMarkdown also works, but keeping them as named
	// constants makes them grep-able and theme-able.
	const CLS_H1 = 'mb-3 text-xl font-bold text-slate-900 dark:text-slate-50';
	const CLS_H2 = 'mt-6 mb-2 text-base font-semibold text-slate-900 dark:text-slate-50';
	const CLS_H3 = 'mt-4 mb-1 text-sm font-semibold text-slate-800 dark:text-slate-100';
	const CLS_UL = 'mt-1 mb-3 list-disc pl-5 space-y-0.5';
	const CLS_LI = 'leading-relaxed';
	const CLS_P = 'my-2 leading-relaxed';
	const CLS_STRONG = 'font-semibold text-slate-900 dark:text-slate-50';

	function renderMarkdown(src: string): string {
		const lines = src.split('\n');
		const out: string[] = [];
		let inList = false;
		const closeList = () => {
			if (inList) {
				out.push('</ul>');
				inList = false;
			}
		};

		const inlineStrong = (s: string): string =>
			s.replace(/\*\*([^*]+)\*\*/g, `<strong class="${CLS_STRONG}">$1</strong>`);

		for (const raw of lines) {
			const line = raw.trimEnd();
			if (line.startsWith('### ')) {
				closeList();
				out.push(`<h3 class="${CLS_H3}">${inlineStrong(escapeHtml(line.slice(4)))}</h3>`);
			} else if (line.startsWith('## ')) {
				closeList();
				out.push(`<h2 class="${CLS_H2}">${inlineStrong(escapeHtml(line.slice(3)))}</h2>`);
			} else if (line.startsWith('# ')) {
				closeList();
				out.push(`<h1 class="${CLS_H1}">${inlineStrong(escapeHtml(line.slice(2)))}</h1>`);
			} else if (/^[-*]\s+/.test(line)) {
				if (!inList) {
					out.push(`<ul class="${CLS_UL}">`);
					inList = true;
				}
				const body = line.replace(/^[-*]\s+/, '');
				out.push(`<li class="${CLS_LI}">${inlineStrong(escapeHtml(body))}</li>`);
			} else if (line === '') {
				closeList();
			} else {
				closeList();
				out.push(`<p class="${CLS_P}">${inlineStrong(escapeHtml(line))}</p>`);
			}
		}
		closeList();
		return out.join('\n');
	}

	// Pre-render once on module evaluation — the source is build-time
	// static, so there's no point re-running per modal open. Placed
	// after the const class strings + renderMarkdown declaration so the
	// TDZ doesn't kick in.
	const html = renderMarkdown(changelogRaw);

	function handleKey(e: KeyboardEvent) {
		if (e.key === 'Escape') onClose();
	}

	// Attach the global keydown listener as soon as `open` flips true.
	// $effect runs after mount and re-runs on each prop change, so this
	// gives us subscribe-on-open and unsubscribe-on-close without an
	// extra `mounted` gate (which delayed subscription past the first
	// dispatched keydown in jsdom).
	$effect(() => {
		if (typeof window === 'undefined') return;
		if (!open) return;
		window.addEventListener('keydown', handleKey);
		return () => window.removeEventListener('keydown', handleKey);
	});

	onDestroy(() => {
		if (typeof window !== 'undefined') {
			window.removeEventListener('keydown', handleKey);
		}
	});

	function onOverlayClick(e: MouseEvent) {
		if (e.target === e.currentTarget) onClose();
	}
</script>

{#if open}
	<div
		data-testid="changelog-modal-overlay"
		class="fixed inset-0 z-50 flex items-center justify-center bg-black/40 p-4"
		onclick={onOverlayClick}
		role="presentation"
	>
		<div
			data-testid="changelog-modal"
			class="flex w-full max-w-2xl flex-col rounded-lg bg-surface shadow-[0px_20px_40px_rgba(15,23,42,0.2)] dark:bg-surface-raised"
			role="dialog"
			aria-modal="true"
			aria-label={m.changelog_modal_title()}
		>
			<header
				class="flex items-center justify-between border-b border-slate-200 px-5 py-3 dark:border-slate-700"
			>
				<h2 class="text-base font-semibold text-slate-900 dark:text-slate-50">
					{m.changelog_modal_title()}
				</h2>
				<button
					type="button"
					data-testid="changelog-modal-close"
					onclick={onClose}
					aria-label={m.changelog_modal_close()}
					class="rounded-md p-1 text-slate-500 hover:bg-slate-100 hover:text-slate-900 dark:text-slate-400 dark:hover:bg-slate-700 dark:hover:text-slate-50"
				>
					<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
						<line x1="18" y1="6" x2="6" y2="18" />
						<line x1="6" y1="6" x2="18" y2="18" />
					</svg>
				</button>
			</header>

			<div
				data-testid="changelog-modal-body"
				class="max-h-[70vh] overflow-y-auto px-5 py-4 text-sm text-slate-700 dark:text-slate-200"
			>
				{@html html}
			</div>
		</div>
	</div>
{/if}
