import { describe, it, expect } from 'vitest';
import {
	calculatePortPosition,
	getNodePorts,
	canPortAcceptConnection,
	arePortsCompatible,
	DEFAULT_PORT,
	type PortDefinition,
} from '../core/port-types';

function makePort(overrides: Partial<PortDefinition> = {}): PortDefinition {
	return { id: 'test', type: 'bidirectional', side: 'right', position: 0.5, ...overrides };
}

describe('port-types', () => {
	describe('calculatePortPosition', () => {
		it('left side places port at nodeX', () => {
			const pos = calculatePortPosition(100, 200, 300, 150, makePort({ side: 'left', position: 0.5 }));
			expect(pos.x).toBe(100);
			expect(pos.y).toBe(275); // 200 + 150*0.5
		});

		it('right side places port at nodeX + width', () => {
			const pos = calculatePortPosition(100, 200, 300, 150, makePort({ side: 'right', position: 0.5 }));
			expect(pos.x).toBe(400); // 100 + 300
			expect(pos.y).toBe(275);
		});

		it('top side places port at nodeY', () => {
			const pos = calculatePortPosition(100, 200, 300, 150, makePort({ side: 'top', position: 0.5 }));
			expect(pos.x).toBe(250); // 100 + 300*0.5
			expect(pos.y).toBe(200);
		});

		it('bottom side places port at nodeY + height', () => {
			const pos = calculatePortPosition(100, 200, 300, 150, makePort({ side: 'bottom', position: 0.5 }));
			expect(pos.x).toBe(250);
			expect(pos.y).toBe(350); // 200 + 150
		});

		it('position 0 is start of side', () => {
			const pos = calculatePortPosition(0, 0, 200, 100, makePort({ side: 'left', position: 0 }));
			expect(pos.y).toBe(0);
		});

		it('position 1 is end of side', () => {
			const pos = calculatePortPosition(0, 0, 200, 100, makePort({ side: 'left', position: 1 }));
			expect(pos.y).toBe(100);
		});
	});

	describe('DEFAULT_PORT', () => {
		it('is bidirectional on right side at center', () => {
			expect(DEFAULT_PORT.id).toBe('default');
			expect(DEFAULT_PORT.type).toBe('bidirectional');
			expect(DEFAULT_PORT.side).toBe('right');
			expect(DEFAULT_PORT.position).toBe(0.5);
		});
	});

	describe('getNodePorts', () => {
		it('returns default port when no ports specified', () => {
			const ports = getNodePorts(undefined);
			expect(ports).toHaveLength(1);
			expect(ports[0]).toBe(DEFAULT_PORT);
		});

		it('returns default port for empty array', () => {
			const ports = getNodePorts([]);
			expect(ports).toHaveLength(1);
		});

		it('returns configured ports when provided', () => {
			const custom = [makePort({ id: 'in' }), makePort({ id: 'out' })];
			const ports = getNodePorts(custom);
			expect(ports).toBe(custom);
		});
	});

	describe('canPortAcceptConnection', () => {
		it('input port cannot be source', () => {
			expect(canPortAcceptConnection(makePort({ type: 'input' }), 0, true)).toBe(false);
		});

		it('output port cannot be target', () => {
			expect(canPortAcceptConnection(makePort({ type: 'output' }), 0, false)).toBe(false);
		});

		it('bidirectional accepts both', () => {
			expect(canPortAcceptConnection(makePort({ type: 'bidirectional' }), 0, true)).toBe(true);
			expect(canPortAcceptConnection(makePort({ type: 'bidirectional' }), 0, false)).toBe(true);
		});

		it('rejects when maxConnections reached', () => {
			expect(canPortAcceptConnection(makePort({ maxConnections: 2 }), 2, true)).toBe(false);
		});

		it('accepts when under maxConnections', () => {
			expect(canPortAcceptConnection(makePort({ maxConnections: 2 }), 1, true)).toBe(true);
		});
	});

	describe('arePortsCompatible', () => {
		it('output → input is compatible', () => {
			expect(arePortsCompatible(makePort({ type: 'output' }), makePort({ type: 'input' }))).toBe(true);
		});

		it('input → output is incompatible (input cannot be source)', () => {
			expect(arePortsCompatible(makePort({ type: 'input' }), makePort({ type: 'output' }))).toBe(false);
		});

		it('bidirectional → bidirectional is compatible', () => {
			expect(arePortsCompatible(makePort({ type: 'bidirectional' }), makePort({ type: 'bidirectional' }))).toBe(true);
		});

		it('output → output is incompatible', () => {
			expect(arePortsCompatible(makePort({ type: 'output' }), makePort({ type: 'output' }))).toBe(false);
		});
	});
});
