<script lang="ts">
import * as m from '$lib/paraglide/messages.js';
import YearBand from './YearBand.svelte';
import GapSpan from './GapSpan.svelte';
import LetterCard from './LetterCard.svelte';
import EventPill from './EventPill.svelte';
import WorldBand from './WorldBand.svelte';
import { entryKey } from './entryKey';
import { buildEventLookup } from './eventClustering';
import type { components } from '$lib/generated/api';

type TimelineDTO = components['schemas']['TimelineDTO'];
type TimelineYearDTO = components['schemas']['TimelineYearDTO'];

/**
 * Orchestrates the global timeline (REQ-001/003). Renders the year bands the DTO
 * delivers in order — never re-sorting — interleaving a folded GapSpan for each
 * interior run of empty years (REQ-015), then the undated bucket (REQ-016). An
 * empty timeline shows a calm message (REQ-017). `personId` is a declared seam
 * for the per-person rail (issue #10) and is undefined here; it is not passed to
 * leaf cards (REQ-025). Owns no <main> — the layout does.
 *
 * The event lookup is built once over the whole (already layer-filtered) timeline
 * and threaded to every band so a curated event's letters cluster under it inline
 * (#850, REQ-002). The undated bucket stays plain (events as pills, letters as
 * cards) — out of clustering scope.
 */
let {
	timeline,
	personId = undefined,
	canWrite = false
}: { timeline: TimelineDTO; personId?: string; canWrite?: boolean } = $props();

const eventLookup = $derived(buildEventLookup(timeline));

type Row = { t: 'band'; year: TimelineYearDTO } | { t: 'gap'; from: number; to: number };

const rows = $derived.by<Row[]>(() => {
	const out: Row[] = [];
	const years = timeline.years;
	for (let i = 0; i < years.length; i++) {
		if (i > 0) {
			const prev = years[i - 1].year;
			const cur = years[i].year;
			if (cur - prev > 1) out.push({ t: 'gap', from: prev + 1, to: cur - 1 });
		}
		out.push({ t: 'band', year: years[i] });
	}
	return out;
});

const isEmpty = $derived(timeline.years.length === 0 && timeline.undated.length === 0);
</script>

{#if isEmpty}
	<p class="py-12 text-center font-serif text-base text-ink-2">{m.timeline_empty_state()}</p>
{:else}
	<!-- personId is a declared seam for the per-person Lebensweg rail (issue #10);
	     undefined in the global view, surfaced only on the root, never passed to
	     leaf cards (REQ-025). -->
	<ol class="timeline-axis relative mx-auto max-w-3xl" data-person-id={personId}>
		{#each rows as row (row.t === 'band' ? `band-${row.year.year}` : `gap-${row.from}`)}
			<li>
				{#if row.t === 'band'}
					<YearBand year={row.year} canWrite={canWrite} eventLookup={eventLookup} />
				{:else}
					<GapSpan from={row.from} to={row.to} />
				{/if}
			</li>
		{/each}
	</ol>

	{#if timeline.undated.length > 0}
		<section
			data-testid="undated-section"
			class="mx-auto mt-8 max-w-3xl rounded-sm border border-dashed border-line bg-surface p-4"
		>
			<h2 class="mb-3 font-serif text-sm font-bold text-ink-2">
				{m.timeline_undated_section()} · {timeline.undated.length}
			</h2>
			<ul class="space-y-2">
				<!-- The undated bucket is filtered from ALL entries, so it can hold
				     events as well as letters. Dispatch on kind/type exactly like
				     YearBand — an event rendered as a LetterCard would link to
				     /documents/undefined and read "Unknown → Unknown" (REQ-007/008/009). -->
				{#each timeline.undated as entry (entryKey(entry))}
					<li>
						{#if entry.kind === 'EVENT'}
							{#if entry.type === 'HISTORICAL'}
								<WorldBand entry={entry} canWrite={canWrite} />
							{:else}
								<EventPill entry={entry} canWrite={canWrite} />
							{/if}
						{:else}
							<LetterCard entry={entry} />
						{/if}
					</li>
				{/each}
			</ul>
		</section>
	{/if}
{/if}

<style>
/* Establish a stacking context so the spine (z-index: -1) sits behind the
	   in-flow cards/pills/strips but still in front of the canvas — the line is
	   always background; the badges, dots and markers ride on top of it.
	   --spine-x is the single source of truth for the spine's X position; the
	   year-node and connector dots in YearBand consume it via inheritance so the
	   markers can never desync from the line. */
.timeline-axis {
	--spine-x: 0.5rem;
	position: relative;
	z-index: 0;
}

/* Phone (< 1024px): a single left-anchored spine. Desktop (≥ 1024px): a
	   centered spine the bands' alternating cards sit on either side of. The
	   spine is decorative — the chronology lives in the <ol> DOM order. */
.timeline-axis::before {
	content: '';
	position: absolute;
	top: 0;
	bottom: 0;
	left: var(--spine-x);
	width: 2px;
	z-index: -1;
	/* Three-stop life-thread: mint → navy → slate. Slate lives only as
	   --c-tag-slate (there is no --palette-slate). REQ-006/013. */
	background: linear-gradient(var(--palette-mint), var(--palette-navy), var(--c-tag-slate));
}

@media (min-width: 1024px) {
	.timeline-axis {
		--spine-x: 50%;
	}
	.timeline-axis::before {
		transform: translateX(-50%);
	}
}
</style>
