import { describe, it, expect } from 'vitest';
import { render } from 'vitest-browser-svelte';
import StammbaumTree from './StammbaumTree.svelte';

const ID_A = '00000000-0000-0000-0000-000000000001';
const ID_B = '00000000-0000-0000-0000-000000000002';

function parseViewBox(svg: SVGElement): [number, number, number, number] {
	const parts = svg.getAttribute('viewBox')!.split(/\s+/).map(Number);
	return [parts[0], parts[1], parts[2], parts[3]];
}

function rectsCentroid(svg: SVGElement): { x: number; y: number } {
	const rects = Array.from(svg.querySelectorAll('rect'));
	let sx = 0;
	let sy = 0;
	let n = 0;
	for (const r of rects) {
		const x = parseFloat(r.getAttribute('x') ?? '0');
		const y = parseFloat(r.getAttribute('y') ?? '0');
		const w = parseFloat(r.getAttribute('width') ?? '0');
		const h = parseFloat(r.getAttribute('height') ?? '0');
		// Skip the narrow accent stripe.
		if (w < 10) continue;
		// Each node rect lives inside <g transform="translate(...)">.
		const g = r.closest('g[transform]');
		const transform = g?.getAttribute('transform') ?? '';
		const match = transform.match(/translate\((-?[\d.]+)[,\s]+(-?[\d.]+)\)/);
		const tx = match ? parseFloat(match[1]) : 0;
		const ty = match ? parseFloat(match[2]) : 0;
		sx += tx + x + w / 2;
		sy += ty + y + h / 2;
		n++;
	}
	return { x: sx / n, y: sy / n };
}

describe('StammbaumTree viewBox', () => {
	it('uses the minimum size and centers a single node', async () => {
		render(StammbaumTree, {
			nodes: [{ id: ID_A, displayName: 'Anna', familyMember: true }],
			edges: [],
			selectedId: null,
			zoom: 1,
			onSelect: () => {}
		});

		const svg = document.querySelector('svg')!;
		const [x, y, w, h] = parseViewBox(svg);

		// Single 160x56 node fits inside the 1200x800 minimum viewBox.
		expect(w).toBe(1200);
		expect(h).toBe(800);

		// Whatever absolute coordinates the layout uses, the viewBox must
		// centre on the rendered content.
		const c = rectsCentroid(svg);
		expect(x + w / 2).toBeCloseTo(c.x, 1);
		expect(y + h / 2).toBeCloseTo(c.y, 1);
	});

	it('renders only orthogonal segments when two parents share two children', async () => {
		const PARENT_A = '00000000-0000-0000-0000-00000000000a';
		const PARENT_B = '00000000-0000-0000-0000-00000000000b';
		const CHILD_1 = '00000000-0000-0000-0000-00000000000c';
		const CHILD_2 = '00000000-0000-0000-0000-00000000000d';
		render(StammbaumTree, {
			nodes: [
				{ id: PARENT_A, displayName: 'Walter', familyMember: true },
				{ id: PARENT_B, displayName: 'Eugenie', familyMember: true },
				{ id: CHILD_1, displayName: 'Clara', familyMember: true },
				{ id: CHILD_2, displayName: 'Hans', familyMember: true }
			],
			edges: [
				{
					id: 'sp',
					personId: PARENT_A,
					relatedPersonId: PARENT_B,
					personDisplayName: 'Walter',
					relatedPersonDisplayName: 'Eugenie',
					relationType: 'SPOUSE_OF'
				},
				{
					id: 'p1a',
					personId: PARENT_A,
					relatedPersonId: CHILD_1,
					personDisplayName: 'Walter',
					relatedPersonDisplayName: 'Clara',
					relationType: 'PARENT_OF'
				},
				{
					id: 'p1b',
					personId: PARENT_B,
					relatedPersonId: CHILD_1,
					personDisplayName: 'Eugenie',
					relatedPersonDisplayName: 'Clara',
					relationType: 'PARENT_OF'
				},
				{
					id: 'p2a',
					personId: PARENT_A,
					relatedPersonId: CHILD_2,
					personDisplayName: 'Walter',
					relatedPersonDisplayName: 'Hans',
					relationType: 'PARENT_OF'
				},
				{
					id: 'p2b',
					personId: PARENT_B,
					relatedPersonId: CHILD_2,
					personDisplayName: 'Eugenie',
					relatedPersonDisplayName: 'Hans',
					relationType: 'PARENT_OF'
				}
			],
			selectedId: null,
			zoom: 1,
			onSelect: () => {}
		});

		const lines = Array.from(document.querySelectorAll('svg line'));
		// Every parent-child segment must be either vertical (x1==x2) or
		// horizontal (y1==y2) — no slanted segments allowed.
		const slanted = lines.filter(
			(l) =>
				l.getAttribute('x1') !== l.getAttribute('x2') &&
				l.getAttribute('y1') !== l.getAttribute('y2')
		);
		expect(slanted).toHaveLength(0);
		// Sibling bar must exist and span the children: at least one
		// horizontal line whose x1 != x2 and y1 == y2.
		const horizontalBars = lines.filter(
			(l) =>
				l.getAttribute('y1') === l.getAttribute('y2') &&
				l.getAttribute('x1') !== l.getAttribute('x2')
		);
		expect(horizontalBars.length).toBeGreaterThanOrEqual(1);
	});

	it('positions a single child at the midpoint of its two parents (vertical drop)', async () => {
		const PARENT_A = '00000000-0000-0000-0000-00000000000a';
		const PARENT_B = '00000000-0000-0000-0000-00000000000b';
		const CHILD = '00000000-0000-0000-0000-00000000000c';
		render(StammbaumTree, {
			nodes: [
				{ id: PARENT_A, displayName: 'Walter', familyMember: true },
				{ id: PARENT_B, displayName: 'Eugenie', familyMember: true },
				{ id: CHILD, displayName: 'Hans', familyMember: true }
			],
			edges: [
				{
					id: 'sp',
					personId: PARENT_A,
					relatedPersonId: PARENT_B,
					personDisplayName: 'Walter',
					relatedPersonDisplayName: 'Eugenie',
					relationType: 'SPOUSE_OF'
				},
				{
					id: 'p1',
					personId: PARENT_A,
					relatedPersonId: CHILD,
					personDisplayName: 'Walter',
					relatedPersonDisplayName: 'Hans',
					relationType: 'PARENT_OF'
				},
				{
					id: 'p2',
					personId: PARENT_B,
					relatedPersonId: CHILD,
					personDisplayName: 'Eugenie',
					relatedPersonDisplayName: 'Hans',
					relationType: 'PARENT_OF'
				}
			],
			selectedId: null,
			zoom: 1,
			onSelect: () => {}
		});

		const lines = Array.from(document.querySelectorAll('svg line'));
		// No slanted segments. With one child, no horizontal sibling bar
		// is needed because midX == child.center.x.
		const slanted = lines.filter(
			(l) =>
				l.getAttribute('x1') !== l.getAttribute('x2') &&
				l.getAttribute('y1') !== l.getAttribute('y2')
		);
		expect(slanted).toHaveLength(0);
	});

	it('places a loose spouse adjacent to their partner and demotes their child a generation', async () => {
		// Walter ↔ Eugenie (gen 0); their children Hans + Clara (gen 1).
		// Hans ↔ Hilde (Hilde has no parents in graph). Hans + Hilde have
		// child Lili. Hilde must sit next to Hans, and Lili must be on a
		// row below Hans/Hilde — not on the same row.
		const WALTER = '00000000-0000-0000-0000-000000000001';
		const EUGENIE = '00000000-0000-0000-0000-000000000002';
		const HANS = '00000000-0000-0000-0000-000000000003';
		const CLARA = '00000000-0000-0000-0000-000000000004';
		const HILDE = '00000000-0000-0000-0000-000000000005';
		const LILI = '00000000-0000-0000-0000-000000000006';

		render(StammbaumTree, {
			nodes: [
				{ id: WALTER, displayName: 'Walter', familyMember: true },
				{ id: EUGENIE, displayName: 'Eugenie', familyMember: true },
				{ id: HANS, displayName: 'Hans', familyMember: true },
				{ id: CLARA, displayName: 'Clara', familyMember: true },
				{ id: HILDE, displayName: 'Hilde', familyMember: true },
				{ id: LILI, displayName: 'Lili', familyMember: true }
			],
			edges: [
				{
					id: 's1',
					personId: WALTER,
					relatedPersonId: EUGENIE,
					personDisplayName: 'Walter',
					relatedPersonDisplayName: 'Eugenie',
					relationType: 'SPOUSE_OF'
				},
				{
					id: 'p1',
					personId: WALTER,
					relatedPersonId: HANS,
					personDisplayName: 'Walter',
					relatedPersonDisplayName: 'Hans',
					relationType: 'PARENT_OF'
				},
				{
					id: 'p2',
					personId: EUGENIE,
					relatedPersonId: HANS,
					personDisplayName: 'Eugenie',
					relatedPersonDisplayName: 'Hans',
					relationType: 'PARENT_OF'
				},
				{
					id: 'p3',
					personId: WALTER,
					relatedPersonId: CLARA,
					personDisplayName: 'Walter',
					relatedPersonDisplayName: 'Clara',
					relationType: 'PARENT_OF'
				},
				{
					id: 'p4',
					personId: EUGENIE,
					relatedPersonId: CLARA,
					personDisplayName: 'Eugenie',
					relatedPersonDisplayName: 'Clara',
					relationType: 'PARENT_OF'
				},
				{
					id: 's2',
					personId: HANS,
					relatedPersonId: HILDE,
					personDisplayName: 'Hans',
					relatedPersonDisplayName: 'Hilde',
					relationType: 'SPOUSE_OF'
				},
				{
					id: 'p5',
					personId: HANS,
					relatedPersonId: LILI,
					personDisplayName: 'Hans',
					relatedPersonDisplayName: 'Lili',
					relationType: 'PARENT_OF'
				},
				{
					id: 'p6',
					personId: HILDE,
					relatedPersonId: LILI,
					personDisplayName: 'Hilde',
					relatedPersonDisplayName: 'Lili',
					relationType: 'PARENT_OF'
				}
			],
			selectedId: null,
			zoom: 1,
			onSelect: () => {}
		});

		const ys = new Map<string, number>();
		for (const g of Array.from(document.querySelectorAll('g[transform]'))) {
			const aria = g.getAttribute('aria-label') ?? '';
			const transform = g.getAttribute('transform') ?? '';
			const match = transform.match(/translate\((-?[\d.]+)[,\s]+(-?[\d.]+)\)/);
			if (!match) continue;
			ys.set(aria.split(',')[0], parseFloat(match[2]));
		}

		// Lili must be on a deeper row than Hans / Hilde.
		expect(ys.get('Lili')).toBeGreaterThan(ys.get('Hans')!);
		expect(ys.get('Hans')).toEqual(ys.get('Hilde'));

		// Hans and Hilde must be horizontally adjacent (|Δx| == NODE_W + COL_GAP).
		const xs = new Map<string, number>();
		for (const g of Array.from(document.querySelectorAll('g[transform]'))) {
			const aria = g.getAttribute('aria-label') ?? '';
			const transform = g.getAttribute('transform') ?? '';
			const match = transform.match(/translate\((-?[\d.]+)[,\s]+(-?[\d.]+)\)/);
			if (!match) continue;
			xs.set(aria.split(',')[0], parseFloat(match[1]));
		}
		expect(Math.abs(xs.get('Hans')! - xs.get('Hilde')!)).toBe(160 + 40);

		// All parent-child segments must be orthogonal.
		const lines = Array.from(document.querySelectorAll('svg line'));
		const slanted = lines.filter(
			(l) =>
				l.getAttribute('x1') !== l.getAttribute('x2') &&
				l.getAttribute('y1') !== l.getAttribute('y2')
		);
		expect(slanted).toHaveLength(0);
	});

	it('centers two spouse nodes within the minimum viewBox', async () => {
		render(StammbaumTree, {
			nodes: [
				{ id: ID_A, displayName: 'Anna', familyMember: true },
				{ id: ID_B, displayName: 'Bertha', familyMember: true }
			],
			edges: [
				{
					id: 'e1',
					personId: ID_A,
					relatedPersonId: ID_B,
					personDisplayName: 'Anna',
					relatedPersonDisplayName: 'Bertha',
					relationType: 'SPOUSE_OF'
				}
			],
			selectedId: null,
			zoom: 1,
			onSelect: () => {}
		});

		const svg = document.querySelector('svg')!;
		const [x, y, w, h] = parseViewBox(svg);

		expect(w).toBe(1200);
		expect(h).toBe(800);

		const c = rectsCentroid(svg);
		expect(x + w / 2).toBeCloseTo(c.x, 1);
		expect(y + h / 2).toBeCloseTo(c.y, 1);
	});
});
