import { describe, it, expect, afterEach } from 'vitest';
import { cleanup, render } from 'vitest-browser-svelte';
import * as m from '$lib/paraglide/messages.js';
import TimelineView from './TimelineView.svelte';
import { makeEntry, makeYear, makeTimelineDTO } from './test-factories';

afterEach(() => cleanup());

describe('TimelineView', () => {
	it('shows the empty state and no ol when there are no years and no undated (REQ-017)', () => {
		render(TimelineView, { timeline: makeTimelineDTO() });
		expect(document.body.textContent).toContain('Noch keine Ereignisse.');
		expect(document.querySelector('ol')).toBeNull();
		expect(document.querySelector('section')).toBeNull();
	});

	it('renders the timeline as a single <ol> with each band a <section>, ascending (REQ-006)', () => {
		render(TimelineView, {
			timeline: makeTimelineDTO({
				years: [
					makeYear(1914, [makeEntry({ documentId: 'a' })]),
					makeYear(1916, [makeEntry({ documentId: 'b' })])
				]
			})
		});
		expect(document.querySelectorAll('ol')).toHaveLength(1);
		const headings = Array.from(document.querySelectorAll('section h2')).map((h) => h.textContent);
		expect(headings.some((t) => t?.includes('1914'))).toBe(true);
		const order = headings.map((t) => t?.trim());
		expect(order.indexOf('1914')).toBeLessThan(order.indexOf('1916'));
	});

	it('folds an interior run of empty years into one GapSpan (REQ-015)', () => {
		render(TimelineView, {
			timeline: makeTimelineDTO({
				years: [
					makeYear(1909, [makeEntry({ documentId: 'a' })]),
					makeYear(1915, [makeEntry({ documentId: 'b' })])
				]
			})
		});
		expect(document.body.textContent).toContain('1910–1914');
		expect(document.body.textContent).toContain('keine Einträge');
	});

	it('folds a single empty interior year as a single year (REQ-015)', () => {
		render(TimelineView, {
			timeline: makeTimelineDTO({
				years: [
					makeYear(1911, [makeEntry({ documentId: 'a' })]),
					makeYear(1913, [makeEntry({ documentId: 'b' })])
				]
			})
		});
		expect(document.body.textContent).toContain('1912');
		expect(document.body.textContent).not.toContain('1912–1912');
	});

	it('renders an "Ohne Datum" section when undated is non-empty (REQ-016)', () => {
		render(TimelineView, {
			timeline: makeTimelineDTO({
				years: [makeYear(1914, [makeEntry({ documentId: 'a' })])],
				undated: [makeEntry({ precision: 'UNKNOWN', eventDate: undefined, documentId: 'u1' })]
			})
		});
		expect(document.querySelector('[data-testid="undated-section"]')).not.toBeNull();
		expect(document.body.textContent).toContain('Ohne Datum');
	});

	it('omits the "Ohne Datum" section from the DOM when undated is empty (REQ-016)', () => {
		render(TimelineView, {
			timeline: makeTimelineDTO({ years: [makeYear(1914, [makeEntry({ documentId: 'a' })])] })
		});
		expect(document.querySelector('[data-testid="undated-section"]')).toBeNull();
	});

	it('frames the undated section with a dashed border and shows the count (REQ-012)', () => {
		const undated = Array.from({ length: 11 }, (_, i) =>
			makeEntry({ precision: 'UNKNOWN', eventDate: undefined, documentId: `u-${i}` })
		);
		render(TimelineView, { timeline: makeTimelineDTO({ undated }) });
		const section = document.querySelector('[data-testid="undated-section"]') as HTMLElement;
		expect(section).not.toBeNull();
		expect(section.classList.contains('border-dashed')).toBe(true);
		const h2 = section.querySelector('h2');
		expect(h2?.textContent).toContain(m.timeline_undated_section());
		expect(h2?.textContent).toContain('11');
	});

	it('renders all years and undated entries with personId undefined, no filtering (REQ-025)', () => {
		render(TimelineView, {
			timeline: makeTimelineDTO({
				years: [
					makeYear(1914, [makeEntry({ documentId: 'a' })]),
					makeYear(1915, [makeEntry({ documentId: 'b' })])
				],
				undated: [makeEntry({ precision: 'UNKNOWN', eventDate: undefined, documentId: 'u1' })]
			}),
			personId: undefined
		});
		// Two year bands inside the <ol>, plus the separate undated section.
		expect(document.querySelectorAll('ol section h2')).toHaveLength(2);
		expect(document.querySelector('[data-testid="undated-section"]')).not.toBeNull();
	});

	it('renders an undated curated EVENT as a pill, not a broken letter card (REQ-008/016)', () => {
		render(TimelineView, {
			canWrite: true,
			timeline: makeTimelineDTO({
				undated: [
					makeEntry({
						kind: 'EVENT',
						type: 'PERSONAL',
						derived: false,
						eventId: 'e1',
						precision: 'UNKNOWN',
						eventDate: undefined,
						title: 'Auswanderung',
						senderName: '',
						receiverName: '',
						documentId: undefined
					})
				]
			})
		});
		// The event renders inside the undated section…
		expect(document.querySelector('[data-testid="undated-section"]')).not.toBeNull();
		expect(document.body.textContent).toContain('Auswanderung');
		// …as an EventPill (its edit affordance, threaded canWrite), never as a
		// letter card linking to /documents/undefined with "Unbekannt → Unbekannt".
		expect(document.querySelector('[data-testid="event-edit"]')).not.toBeNull();
		expect(document.querySelector('a[href="/documents/undefined"]')).toBeNull();
		expect(document.body.textContent).not.toContain('Unbekannt');
	});

	it('renders an undated HISTORICAL EVENT as a world band, not a letter card (REQ-009/016)', () => {
		render(TimelineView, {
			timeline: makeTimelineDTO({
				undated: [
					makeEntry({
						kind: 'EVENT',
						type: 'HISTORICAL',
						derived: false,
						precision: 'UNKNOWN',
						eventDate: undefined,
						title: 'Weltwirtschaftskrise',
						senderName: '',
						receiverName: '',
						documentId: undefined
					})
				]
			})
		});
		expect(document.querySelector('[data-testid="undated-section"]')).not.toBeNull();
		expect(document.body.textContent).toContain('Weltwirtschaftskrise');
		// HISTORICAL → WorldBand carries the sr-only "Weltgeschehen" cue (REQ-018),
		// not a broken document link.
		expect(document.body.textContent).toContain('Weltgeschehen');
		expect(document.querySelector('a[href="/documents/undefined"]')).toBeNull();
	});

	it('still renders an undated LETTER as a letter card (REQ-016)', () => {
		render(TimelineView, {
			timeline: makeTimelineDTO({
				undated: [makeEntry({ precision: 'UNKNOWN', eventDate: undefined, documentId: 'u1' })]
			})
		});
		expect(document.querySelector('a[href="/documents/u1"]')).not.toBeNull();
	});

	it('renders two derived events in one band without key collision (no-double-null-key)', () => {
		const a = makeEntry({
			kind: 'EVENT',
			derived: true,
			derivedType: 'BIRTH',
			title: 'Geburt: Anna',
			senderName: '',
			receiverName: '',
			documentId: undefined,
			eventId: undefined,
			linkedPersonIds: ['p1']
		});
		const b = makeEntry({
			kind: 'EVENT',
			derived: true,
			derivedType: 'BIRTH',
			title: 'Geburt: Bertha',
			senderName: '',
			receiverName: '',
			documentId: undefined,
			eventId: undefined,
			linkedPersonIds: ['p2']
		});
		render(TimelineView, { timeline: makeTimelineDTO({ years: [makeYear(1915, [a, b])] }) });
		expect(document.body.textContent).toContain('Geburt: Anna');
		expect(document.body.textContent).toContain('Geburt: Bertha');
	});

	it('shows the redundant non-color cue label for each layer (REQ-018)', () => {
		render(TimelineView, {
			timeline: makeTimelineDTO({
				years: [
					makeYear(1914, [
						makeEntry({
							kind: 'EVENT',
							derived: true,
							derivedType: 'BIRTH',
							title: 'Geburt: Hans',
							senderName: '',
							receiverName: '',
							documentId: undefined
						}),
						makeEntry({
							kind: 'EVENT',
							derived: false,
							type: 'PERSONAL',
							eventId: 'e1',
							title: 'Auswanderung',
							senderName: '',
							receiverName: '',
							documentId: undefined
						}),
						makeEntry({
							kind: 'EVENT',
							derived: false,
							type: 'HISTORICAL',
							precision: 'RANGE',
							eventDate: '1914-01-01',
							eventDateEnd: '1918-12-31',
							title: 'Erster Weltkrieg',
							senderName: '',
							receiverName: '',
							documentId: undefined
						})
					])
				]
			})
		});
		expect(document.body.textContent).toContain('Weltgeschehen');
		expect(document.body.textContent).toContain('Familie');
		expect(document.body.textContent).toContain('Geburt');
	});

	it('paints the axis with a three-stop mint→navy→slate gradient (REQ-006)', () => {
		// The palette tokens live in layout.css, which component tests don't load,
		// so define exactly the three the gradient must reference; an undefined
		// fourth token would invalidate the declaration and yield "none".
		const root = document.documentElement;
		root.style.setProperty('--palette-mint', '#a1dcd8');
		root.style.setProperty('--palette-navy', '#012851');
		root.style.setProperty('--c-tag-slate', '#607080');
		try {
			render(TimelineView, {
				timeline: makeTimelineDTO({ years: [makeYear(1914, [makeEntry()])] })
			});
			const axis = document.querySelector('.timeline-axis') as HTMLElement;
			expect(axis).not.toBeNull();
			const before = getComputedStyle(axis, '::before');
			const bg = before.backgroundImage;
			expect(bg).toContain('gradient');
			// three colour stops: mint, navy, slate
			expect((bg.match(/rgb/g) ?? []).length).toBe(3);
			// the spine is always background: behind the in-flow cards/pills/strips.
			expect(before.zIndex).toBe('-1');
		} finally {
			root.style.removeProperty('--palette-mint');
			root.style.removeProperty('--palette-navy');
			root.style.removeProperty('--c-tag-slate');
		}
	});

	it('places consecutive letter cards on alternating sides (REQ-004 surrogate)', () => {
		const letters = Array.from({ length: 4 }, (_, i) => makeEntry({ documentId: `d${i}` }));
		render(TimelineView, { timeline: makeTimelineDTO({ years: [makeYear(1909, letters)] }) });
		const sides = Array.from(document.querySelectorAll('.letter-row')).map((el) =>
			el.getAttribute('data-side')
		);
		expect(sides).toEqual(['left', 'right', 'left', 'right']);
	});

	// A curated PERSONAL event reachable through both dispatch paths: the year-band
	// path (TimelineView → YearBand → EventPill) and the undated bucket
	// (TimelineView → EventPill). canWrite must thread to both (REQ-009).
	const curated = (eventId: string, title: string) =>
		makeEntry({
			kind: 'EVENT',
			type: 'PERSONAL',
			derived: false,
			eventId,
			title,
			senderName: '',
			receiverName: '',
			documentId: undefined
		});

	const bothPaths = () =>
		makeTimelineDTO({
			years: [makeYear(1924, [curated('banded', 'Umzug nach Berlin')])],
			undated: [curated('undated', 'Auswanderung')]
		});

	it('threads canWrite to a curated event in both a year band and the undated bucket (REQ-009)', () => {
		render(TimelineView, { canWrite: true, timeline: bothPaths() });
		const hrefs = Array.from(document.querySelectorAll('[data-testid="event-edit"]')).map((a) =>
			a.getAttribute('href')
		);
		expect(hrefs).toContain('/zeitstrahl/events/banded/edit');
		expect(hrefs).toContain('/zeitstrahl/events/undated/edit');
	});

	it('renders no edit links in either path when canWrite is false (REQ-007/009)', () => {
		render(TimelineView, { canWrite: false, timeline: bothPaths() });
		expect(document.querySelectorAll('[data-testid="event-edit"]')).toHaveLength(0);
	});

	it('threads canWrite to a curated HISTORICAL world band in both paths (REQ-006/009)', () => {
		const world = (eventId: string, title: string) =>
			makeEntry({
				kind: 'EVENT',
				type: 'HISTORICAL',
				derived: false,
				eventId,
				precision: 'YEAR',
				eventDate: '1929-01-01',
				eventDateEnd: undefined,
				title,
				senderName: '',
				receiverName: '',
				documentId: undefined
			});
		render(TimelineView, {
			canWrite: true,
			timeline: makeTimelineDTO({
				years: [makeYear(1929, [world('wb', 'Weltwirtschaftskrise')])],
				undated: [world('wu', 'Unbekanntes Weltereignis')]
			})
		});
		const hrefs = Array.from(document.querySelectorAll('[data-testid="event-edit"]')).map((a) =>
			a.getAttribute('href')
		);
		expect(hrefs).toContain('/zeitstrahl/events/wb/edit');
		expect(hrefs).toContain('/zeitstrahl/events/wu/edit');
	});

	it('builds the event lookup and clusters a curated event + same-year linked letter into an event-card (#850)', () => {
		const evId = 'eeeeeeee-eeee-eeee-eeee-eeeeeeeeeeee';
		const event = makeEntry({
			kind: 'EVENT',
			type: 'PERSONAL',
			derived: false,
			eventId: evId,
			eventDate: '1916-07-06',
			precision: 'DAY',
			title: 'Ein gewaltiger Stadtbrand',
			senderName: '',
			receiverName: '',
			documentId: undefined
		});
		const letter = makeEntry({
			eventDate: '1916-05-10',
			documentId: 'doc-linked',
			title: 'Brief',
			linkedEventId: evId
		});
		render(TimelineView, {
			timeline: makeTimelineDTO({ years: [makeYear(1916, [event, letter])] })
		});
		expect(document.querySelector('[data-testid="event-card"]')).not.toBeNull();
		expect(document.querySelector('a.lcard.ev')).not.toBeNull();
		// the title reads once — the event is the card header, not also a loose pill
		const titles = (document.body.textContent?.match(/Ein gewaltiger Stadtbrand/g) ?? []).length;
		expect(titles).toBe(1);
	});
});
