import { describe, it, expect, beforeEach, afterEach } from 'vitest';
import {
	setHitTestProvider,
	hitTestNode,
	hitTestPort,
} from '../utils/hit-test';

// --- Mock Element factory ---

function mockElement(attrs: Record<string, string>, parent?: any): any {
	return {
		getAttribute: (name: string) => attrs[name] ?? null,
		closest: (selector: string) => {
			// Match data attribute selectors like [data-node-id]
			const match = selector.match(/\[([^\]]+)\]/);
			if (match) {
				const attrName = match[1];
				if (attrs[attrName] !== undefined) return mockElement(attrs);
				if (parent && parent.getAttribute(attrName) !== undefined) return parent;
			}
			return null;
		},
		dataset: Object.fromEntries(
			Object.entries(attrs)
				.filter(([k]) => k.startsWith('data-'))
				.map(([k, v]) => [k.replace('data-', '').replace(/-([a-z])/g, (_, c) => c.toUpperCase()), v])
		),
	};
}

describe('hit-test', () => {
	afterEach(() => {
		setHitTestProvider(null); // restore default
	});

	describe('hitTestNode', () => {
		it('finds node by data-node-id', () => {
			const nodeEl = mockElement({ 'data-node-id': 'n1' });
			setHitTestProvider({
				elementFromPoint: () => nodeEl,
				elementsFromPoint: () => [nodeEl],
			});

			const result = hitTestNode(100, 200);
			expect(result.nodeId).toBe('n1');
		});

		it('returns null for miss', () => {
			setHitTestProvider({
				elementFromPoint: () => null,
				elementsFromPoint: () => [],
			});

			const result = hitTestNode(100, 200);
			expect(result.nodeId).toBeNull();
			expect(result.element).toBeNull();
		});
	});

	describe('hitTestPort', () => {
		it('finds port with data-drag-port-id', () => {
			const portBar = mockElement({ 'data-port-bar': '', 'data-node-id': 'n1' });
			// The port element's closest('[data-drag-port-id]') returns itself
			// The port element's closest('[data-port-bar]') returns portBar
			const portEl = {
				getAttribute: (name: string) => name === 'data-drag-port-id' ? 'p1' : null,
				closest: (selector: string) => {
					if (selector === '[data-drag-port-id]') return {
						dataset: { dragPortId: 'p1' },
						closest: (sel2: string) => sel2 === '[data-port-bar]' ? { dataset: { nodeId: 'n1' } } : null,
					};
					return null;
				},
			};

			setHitTestProvider({
				elementFromPoint: () => portEl as any,
				elementsFromPoint: () => [portEl as any],
			});

			const result = hitTestPort(100, 200);
			expect(result).not.toBeNull();
			expect(result!.portId).toBe('p1');
			expect(result!.nodeId).toBe('n1');
		});

		it('returns null when no port found', () => {
			const el = mockElement({});
			setHitTestProvider({
				elementFromPoint: () => el,
				elementsFromPoint: () => [el],
			});

			const result = hitTestPort(100, 200);
			expect(result).toBeNull();
		});
	});

	describe('setHitTestProvider', () => {
		it('accepts custom provider', () => {
			let called = false;
			setHitTestProvider({
				elementFromPoint: () => { called = true; return null; },
				elementsFromPoint: () => [],
			});
			hitTestNode(0, 0);
			expect(called).toBe(true);
		});
	});
});
