import { describe, it, expect, afterEach } from 'vitest';
import { cleanup, render } from 'vitest-browser-svelte';
import { page } from 'vitest/browser';
import YearBand from './YearBand.svelte';
import { makeEntry, makeYear } from './test-factories';

afterEach(async () => {
	cleanup();
	await page.viewport(1280, 720);
});

function manyLetters(year: number, count: number) {
	return Array.from({ length: count }, (_, i) =>
		makeEntry({ eventDate: `${year}-01-10`, documentId: `doc-${i}` })
	);
}

describe('YearBand', () => {
	it('renders a section with a sticky h2 at top:4rem showing the year (REQ-006)', () => {
		render(YearBand, { year: makeYear(1914, [makeEntry()]) });
		const section = document.querySelector('section');
		expect(section).not.toBeNull();
		const h2 = section?.querySelector('h2');
		expect(h2?.textContent).toContain('1914');
		const cs = getComputedStyle(h2 as HTMLElement);
		expect(cs.position).toBe('sticky');
		expect(cs.top).toBe('64px');
	});

	it('renders each letter as a card when the band holds <= 12 letters (REQ-011)', () => {
		render(YearBand, { year: makeYear(1909, manyLetters(1909, 3)) });
		expect(document.querySelectorAll('a')).toHaveLength(3);
		expect(document.querySelector('[data-testid="strip-expand"]')).toBeNull();
	});

	it('renders a single strip when the band holds > 12 letters (REQ-012)', () => {
		render(YearBand, { year: makeYear(1915, manyLetters(1915, 30)) });
		expect(document.querySelector('[data-testid="strip-expand"]')).not.toBeNull();
		// collapsed: no individual letter links yet
		expect(document.querySelectorAll('a')).toHaveLength(0);
	});

	it('renders entries in DTO order — DAY-precision letter above a YEAR-precision letter (REQ-003)', () => {
		const dayLetter = makeEntry({
			precision: 'DAY',
			eventDate: '1923-04-12',
			title: 'Tagesgenau',
			documentId: 'day'
		});
		const yearLetter = makeEntry({
			precision: 'YEAR',
			eventDate: '1923-01-01',
			title: 'Nur Jahr',
			documentId: 'year'
		});
		render(YearBand, { year: makeYear(1923, [dayLetter, yearLetter]) });
		const links = Array.from(document.querySelectorAll('a'));
		expect(links[0].getAttribute('href')).toBe('/documents/day');
		expect(links[1].getAttribute('href')).toBe('/documents/year');
	});

	it('renders an EVENT as a pill and a HISTORICAL event as a band', () => {
		const pill = makeEntry({
			kind: 'EVENT',
			derived: true,
			derivedType: 'MARRIAGE',
			title: 'Heirat',
			senderName: '',
			receiverName: '',
			documentId: undefined
		});
		const band = makeEntry({
			kind: 'EVENT',
			derived: false,
			type: 'HISTORICAL',
			precision: 'RANGE',
			eventDate: '1914-01-01',
			eventDateEnd: '1918-12-31',
			title: 'Erster Weltkrieg',
			senderName: '',
			receiverName: '',
			documentId: undefined
		});
		render(YearBand, { year: makeYear(1914, [pill, band]) });
		expect(document.body.textContent).toContain('Heirat');
		expect(document.querySelector('[data-testid="world-range"]')).not.toBeNull();
	});

	it('centers the year badge on the axis at desktop (REQ-003)', async () => {
		await page.viewport(1440, 900);
		render(YearBand, { year: makeYear(1914, [makeEntry()]) });
		const section = document.querySelector('section') as HTMLElement;
		const badge = document.querySelector('[data-testid="year-label"]') as HTMLElement;
		const s = section.getBoundingClientRect();
		const b = badge.getBoundingClientRect();
		const sectionCenter = s.left + s.width / 2;
		const badgeCenter = b.left + b.width / 2;
		expect(Math.abs(badgeCenter - sectionCenter)).toBeLessThan(8);
	});

	it('left-aligns the year badge at phone width (REQ-003)', async () => {
		await page.viewport(375, 800);
		render(YearBand, { year: makeYear(1914, [makeEntry()]) });
		const section = document.querySelector('section') as HTMLElement;
		const badge = document.querySelector('[data-testid="year-label"]') as HTMLElement;
		const s = section.getBoundingClientRect();
		const b = badge.getBoundingClientRect();
		// hugs the left spine — clearly not centered
		expect(b.left - s.left).toBeLessThan(s.width / 3);
	});

	it('keeps the sticky year heading at top:4rem (REQ-003)', () => {
		render(YearBand, { year: makeYear(1914, [makeEntry()]) });
		const h2 = document.querySelector('h2') as HTMLElement;
		const cs = getComputedStyle(h2);
		expect(cs.position).toBe('sticky');
		expect(cs.top).toBe('64px');
	});

	it('renders a year-badge node marker that clears the badge text on phone (REQ-004)', async () => {
		await page.viewport(375, 800);
		render(YearBand, { year: makeYear(1914, [makeEntry()]) });
		const node = document.querySelector('[data-testid="year-node"]') as HTMLElement;
		const label = document.querySelector('[data-testid="year-label"]') as HTMLElement;
		expect(node).not.toBeNull();
		const n = node.getBoundingClientRect();
		const l = label.getBoundingClientRect();
		expect(n.right).toBeLessThanOrEqual(l.left + 0.5);
		// The badge must paint above the node so the centered desktop pill never
		// occludes the white year text (regression guard).
		expect(Number(getComputedStyle(label).zIndex)).toBeGreaterThan(
			Number(getComputedStyle(node).zIndex)
		);
	});

	it('renders one connector dot per letter row, each clearing its card on phone (REQ-005)', async () => {
		await page.viewport(375, 800);
		render(YearBand, { year: makeYear(1909, manyLetters(1909, 3)) });
		const dots = document.querySelectorAll('[data-testid="letter-dot"]');
		expect(dots).toHaveLength(3);
		const row = document.querySelector('.letter-row') as HTMLElement;
		const dot = row.querySelector('[data-testid="letter-dot"]') as HTMLElement;
		const card = row.querySelector('a') as HTMLElement;
		expect(dot.getBoundingClientRect().right).toBeLessThanOrEqual(
			card.getBoundingClientRect().left + 0.5
		);
	});

	it('positions the year-node from the inherited --spine-x token (REQ-003/004)', async () => {
		await page.viewport(375, 800);
		// The spine X lives once on .timeline-axis as --spine-x; the markers must
		// track that token, not a hard-coded offset, so they never desync.
		document.documentElement.style.setProperty('--spine-x', '3rem');
		try {
			render(YearBand, { year: makeYear(1914, [makeEntry()]) });
			const node = document.querySelector('[data-testid="year-node"]') as HTMLElement;
			const section = document.querySelector('section') as HTMLElement;
			const n = node.getBoundingClientRect();
			const s = section.getBoundingClientRect();
			const nodeCenter = n.left + n.width / 2;
			// --spine-x:3rem = 48px from the band's left edge
			expect(Math.abs(nodeCenter - s.left - 48)).toBeLessThan(2);
		} finally {
			document.documentElement.style.removeProperty('--spine-x');
		}
	});
});

describe('YearBand — inline event clustering (#850)', () => {
	const EV_ID = 'aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa';

	function curatedEvent(overrides = {}) {
		return makeEntry({
			kind: 'EVENT',
			derived: false,
			type: 'PERSONAL',
			eventId: EV_ID,
			eventDate: '1916-07-06',
			precision: 'DAY',
			title: 'Ein gewaltiger Stadtbrand',
			senderName: '',
			receiverName: '',
			documentId: undefined,
			...overrides
		});
	}

	function linkedLetters(year: number, count: number, eventId = EV_ID) {
		return Array.from({ length: count }, (_, i) =>
			makeEntry({
				eventDate: `${year}-05-10`,
				documentId: `linked-${i}`,
				title: `Brief ${i}`,
				linkedEventId: eventId
			})
		);
	}

	const lookup = new Map([[EV_ID, 'Ein gewaltiger Stadtbrand']]);

	it('renders a curated event with a same-year linked letter as one event-card, title once, no separate pill (REQ-002)', () => {
		render(YearBand, {
			year: makeYear(1916, [curatedEvent(), ...linkedLetters(1916, 1)]),
			eventLookup: lookup
		});
		expect(document.querySelectorAll('[data-testid="event-card"]')).toHaveLength(1);
		const titles = (document.body.textContent?.match(/Ein gewaltiger Stadtbrand/g) ?? []).length;
		expect(titles).toBe(1);
		// the letter is inside the card, not a loose .letter-row
		expect(document.querySelector('a.lcard.ev')).not.toBeNull();
		expect(document.querySelector('.letter-row')).toBeNull();
		// no plain EventPill for it (the pill is the only floating .rounded-full wrapper)
		expect(document.querySelector('.justify-center .rounded-full.border-brand-mint')).toBeNull();
	});

	it('renders a curated event with NO linked letters as a plain EventPill, no card (REQ-005)', () => {
		render(YearBand, {
			year: makeYear(1916, [curatedEvent()]),
			eventLookup: lookup
		});
		expect(document.querySelector('[data-testid="event-card"]')).toBeNull();
		// the curated EventPill is the bordered floating rounded-full wrapper
		expect(
			document.querySelector('.justify-center .rounded-full.border-brand-mint')
		).not.toBeNull();
		expect(document.body.textContent).toContain('Ein gewaltiger Stadtbrand');
	});

	it('renders loose (unlinked) letters in a sparse year as alternating .letter-row, no card (REQ-006)', () => {
		const loose = manyLetters(1916, 3); // no linkedEventId
		render(YearBand, { year: makeYear(1916, loose), eventLookup: lookup });
		expect(document.querySelectorAll('.letter-row')).toHaveLength(3);
		expect(document.querySelector('[data-testid="event-card"]')).toBeNull();
	});

	it('counts only loose letters in the density strip; event letters stay in the card (REQ-006/007)', () => {
		// 15 loose letters fold into one strip; a 3-letter event card shows its 3.
		const loose = manyLetters(1916, 15);
		const year = makeYear(1916, [curatedEvent(), ...linkedLetters(1916, 3), ...loose]);
		render(YearBand, { year, eventLookup: lookup });
		// the event card holds 3 letters
		expect(document.querySelectorAll('a.lcard.ev')).toHaveLength(3);
		// the loose letters fold into exactly one density strip
		const strips = document.querySelectorAll('[data-testid="strip-expand"]');
		expect(strips).toHaveLength(1);
		// the strip card's count text is 15 (the loose letters), not 18 (REQ-006/007)
		const stripCard = strips[0].closest('.max-w-md') as HTMLElement;
		expect(stripCard.textContent).toContain('15');
		expect(stripCard.textContent).not.toContain('18');
	});

	it('renders a cross-year cluster (event not in this band) as a ✉ text-header card holding it (REQ-004)', () => {
		// The event id is in eventLookup but no matching EVENT entry sits in this band.
		render(YearBand, {
			year: makeYear(1917, linkedLetters(1917, 2)),
			eventLookup: lookup
		});
		const card = document.querySelector('[data-testid="event-card"]');
		expect(card).not.toBeNull();
		expect(document.body.textContent).toContain('✉');
		expect(document.body.textContent).toContain('Ein gewaltiger Stadtbrand');
		// cross-year card carries no edit link and no pill
		expect(document.querySelector('[data-testid="event-edit"]')).toBeNull();
		expect(document.querySelector('.justify-center .rounded-full.border-brand-mint')).toBeNull();
	});
});
