import { describe, it, expect, vi, afterEach } from 'vitest';
import { cleanup, render } from 'vitest-browser-svelte';
import { page as browserPage } from 'vitest/browser';
import { SvelteMap } from 'svelte/reactivity';

const mockPage = { url: new URL('http://localhost/admin/tags') };

vi.mock('$app/state', () => ({
	get page() {
		return mockPage;
	}
}));

afterEach(cleanup);

async function loadComponent() {
	return (await import('./TagTreeNode.svelte')).default;
}

const leafNode = (overrides: Record<string, unknown> = {}) => ({
	id: 't1',
	name: 'Personen',
	color: 'sage',
	documentCount: 5,
	subtreeDocumentCount: 5,
	parentId: null,
	children: [],
	...overrides
});

const parentNode = (overrides: Record<string, unknown> = {}) => ({
	id: 'tp1',
	name: 'Orte',
	color: 'sienna',
	documentCount: 0,
	subtreeDocumentCount: 2,
	parentId: null,
	children: [
		{
			id: 'tc1',
			name: 'Berlin',
			color: null,
			documentCount: 2,
			subtreeDocumentCount: 2,
			children: []
		}
	],
	...overrides
});

describe('TagTreeNode', () => {
	it('renders the tag name as a link', async () => {
		mockPage.url = new URL('http://localhost/admin/tags');
		const Node = await loadComponent();
		render(Node, { props: { node: leafNode(), depth: 0, collapseMap: new SvelteMap() } });

		await expect
			.element(browserPage.getByRole('link', { name: /personen/i }))
			.toHaveAttribute('href', '/admin/tags/t1');
	});

	it('renders the document count when greater than 0', async () => {
		mockPage.url = new URL('http://localhost/admin/tags');
		const Node = await loadComponent();
		render(Node, {
			props: { node: leafNode({ documentCount: 7 }), depth: 0, collapseMap: new SvelteMap() }
		});

		await expect.element(browserPage.getByText('(7)')).toBeVisible();
	});

	it('omits the document count when 0', async () => {
		mockPage.url = new URL('http://localhost/admin/tags');
		const Node = await loadComponent();
		render(Node, {
			props: { node: leafNode({ documentCount: 0 }), depth: 0, collapseMap: new SvelteMap() }
		});

		await expect.element(browserPage.getByText('(0)')).not.toBeInTheDocument();
	});

	it('shows the color dot at depth 0 when color is set', async () => {
		mockPage.url = new URL('http://localhost/admin/tags');
		const Node = await loadComponent();
		render(Node, { props: { node: leafNode(), depth: 0, collapseMap: new SvelteMap() } });

		const dot = document.querySelector('[data-testid="tag-list-color-dot"]');
		expect(dot).not.toBeNull();
		expect(dot?.getAttribute('data-color')).toBe('sage');
	});

	it('omits the color dot when depth > 0', async () => {
		mockPage.url = new URL('http://localhost/admin/tags');
		const Node = await loadComponent();
		render(Node, { props: { node: leafNode(), depth: 1, collapseMap: new SvelteMap() } });

		expect(document.querySelector('[data-testid="tag-list-color-dot"]')).toBeNull();
	});

	it('marks the link as aria-current=page when on the matching route', async () => {
		mockPage.url = new URL('http://localhost/admin/tags/t1');
		const Node = await loadComponent();
		render(Node, { props: { node: leafNode(), depth: 0, collapseMap: new SvelteMap() } });

		await expect
			.element(browserPage.getByRole('link', { name: /personen/i }))
			.toHaveAttribute('aria-current', 'page');
	});

	it('renders the children list for parent nodes when not collapsed', async () => {
		mockPage.url = new URL('http://localhost/admin/tags');
		const Node = await loadComponent();
		render(Node, { props: { node: parentNode(), depth: 0, collapseMap: new SvelteMap() } });

		await expect.element(browserPage.getByText('Berlin')).toBeVisible();
	});

	it('hides children when the node is collapsed', async () => {
		mockPage.url = new URL('http://localhost/admin/tags');
		const Node = await loadComponent();
		const map = new SvelteMap<string, boolean>([['tp1', true]]);
		render(Node, { props: { node: parentNode(), depth: 0, collapseMap: map } });

		await expect.element(browserPage.getByText('Berlin')).not.toBeInTheDocument();
	});

	it('exposes the expand/collapse toggle for nodes with children', async () => {
		mockPage.url = new URL('http://localhost/admin/tags');
		const Node = await loadComponent();
		render(Node, { props: { node: parentNode(), depth: 0, collapseMap: new SvelteMap() } });

		await expect
			.element(browserPage.getByRole('button', { name: /einklappen|ausklappen/i }))
			.toBeVisible();
	});

	it('hides the color dot when depth > 0 (only top-level shows colors)', async () => {
		mockPage.url = new URL('http://localhost/admin/tags');
		const Node = await loadComponent();
		render(Node, { props: { node: leafNode(), depth: 1, collapseMap: new SvelteMap() } });

		const dot = document.querySelector('[data-testid="tag-list-color-dot"]');
		expect(dot).toBeNull();
	});

	it('hides the color dot when node.color is null', async () => {
		mockPage.url = new URL('http://localhost/admin/tags');
		const Node = await loadComponent();
		render(Node, {
			props: { node: leafNode({ color: null }), depth: 0, collapseMap: new SvelteMap() }
		});

		const dot = document.querySelector('[data-testid="tag-list-color-dot"]');
		expect(dot).toBeNull();
	});

	it('omits the document count badge when documentCount is 0', async () => {
		mockPage.url = new URL('http://localhost/admin/tags');
		const Node = await loadComponent();
		render(Node, {
			props: { node: leafNode({ documentCount: 0 }), depth: 0, collapseMap: new SvelteMap() }
		});

		const link = document.querySelector('a[href="/admin/tags/t1"]');
		expect(link?.textContent).not.toMatch(/\(\d+\)/);
	});

	it('toggles the collapse state when the toggle button is clicked', async () => {
		mockPage.url = new URL('http://localhost/admin/tags');
		const Node = await loadComponent();
		const map = new SvelteMap<string, boolean>();
		render(Node, { props: { node: parentNode(), depth: 0, collapseMap: map } });

		const toggle = Array.from(document.querySelectorAll('button')).find((b) =>
			/einklappen|ausklappen/i.test(b.getAttribute('aria-label') ?? '')
		) as HTMLButtonElement;
		toggle?.click();
		// Map should have been mutated to opposite of current isCollapsed (false → true).
		await vi.waitFor(() => expect(map.get('tp1')).toBe(true));
	});
});
