import { describe, it, expect } from 'vitest';
import { inCircle, inCircleW, inCone, inConeW, angleBetween, nearest } from '../shapes';
import { worldToScreen } from '../iso';

describe('inCircle', () => {
    it('точка внутри и на границе', () => {
        const c = { x: 0, y: 0 };
        expect(inCircle(c, 10, { x: 3, y: 4 })).toBe(true);
        expect(inCircle(c, 5, { x: 3, y: 4 })).toBe(true); // ровно 5
        expect(inCircle(c, 5, { x: 3, y: 5 })).toBe(false);
        expect(inCircle(c, 0, { x: 0, y: 0 })).toBe(true);
    });

    it('точка вне центра', () => {
        expect(inCircle({ x: 100, y: 50 }, 10, { x: 0, y: 0 })).toBe(false);
        // расстояние sqrt(50² + 30²) ≈ 58.3
        expect(inCircle({ x: 100, y: 50 }, 60, { x: 50, y: 20 })).toBe(true);
    });
});

describe('inCone', () => {
    const from = { x: 0, y: 0 };
    const dir = { x: 1, y: 0 }; // смотрит вправо
    const halfAngle = Math.PI / 4; // 45°

    it('внутри конуса', () => {
        expect(inCone(from, dir, 50, halfAngle, { x: 30, y: 10 })).toBe(true);
        expect(inCone(from, dir, 50, halfAngle, { x: 30, y: -10 })).toBe(true);
    });

    it('за пределами дальности', () => {
        expect(inCone(from, dir, 50, halfAngle, { x: 60, y: 0 })).toBe(false);
    });

    it('за границей угла («слепая зона» за спиной)', () => {
        expect(inCone(from, dir, 50, halfAngle, { x: 30, y: 40 })).toBe(false);
        expect(inCone(from, dir, 50, halfAngle, { x: -30, y: 0 })).toBe(false);
    });

    it('вблизи границы угла и радиуса', () => {
        // чуть внутри границы угла 45° (tan 45°=1)
        expect(inCone(from, dir, 50, halfAngle, { x: 10, y: 9.9 })).toBe(true);
        // чуть снаружи
        expect(inCone(from, dir, 50, halfAngle, { x: 10, y: 10.1 })).toBe(false);
        // расстояние ровно 50 — граница радиуса включена
        expect(inCone(from, dir, 50, halfAngle, { x: 50, y: 0 })).toBe(true);
        expect(inCone(from, dir, 50, halfAngle, { x: 50.1, y: 0 })).toBe(false);
    });

    it('нулевое направление — только дальность', () => {
        expect(inCone(from, { x: 0, y: 0 }, 50, halfAngle, { x: 0, y: -40 })).toBe(true);
        expect(inCone(from, { x: 0, y: 0 }, 50, halfAngle, { x: 60, y: 0 })).toBe(false);
    });
});

describe('angleBetween', () => {
    it('известные углы', () => {
        expect(angleBetween({ x: 1, y: 0 }, { x: 0, y: 1 })).toBeCloseTo(Math.PI / 2);
        expect(angleBetween({ x: 1, y: 0 }, { x: -1, y: 0 })).toBeCloseTo(Math.PI);
        expect(angleBetween({ x: 2, y: 0 }, { x: 5, y: 0 })).toBeCloseTo(0);
    });

    it('нулевой вектор даёт 0 (без NaN)', () => {
        expect(angleBetween({ x: 0, y: 0 }, { x: 1, y: 0 })).toBe(0);
        expect(angleBetween({ x: 1, y: 0 }, { x: 0, y: 0 })).toBe(0);
    });
});

describe('nearest', () => {
    it('выбирает ближайшую', () => {
        const list = [
            { x: 50, y: 0 },
            { x: 10, y: 0 },
            { x: 30, y: 0 }
        ];
        expect(nearest(list, { x: 0, y: 0 })).toEqual({ x: 10, y: 0 });
    });

    it('учитывает maxRange', () => {
        const list = [{ x: 100, y: 0 }];
        expect(nearest(list, { x: 0, y: 0 }, 50)).toBeNull();
        expect(nearest(list, { x: 0, y: 0 }, 100)).toEqual({ x: 100, y: 0 });
    });

    it('пустой список — null', () => {
        expect(nearest([], { x: 0, y: 0 })).toBeNull();
    });
});

describe('W-варианты (мировые юниты)', () => {
    const iso = { tileW: 32, tileH: 16, originX: 0, originY: 0 };

    it('inCircleW эквивалентен проекции в экранные px', () => {
        const center = { x: 3, y: 1 };
        for (let wx = 0; wx <= 6; wx += 0.5) {
            for (let wy = 0; wy <= 2; wy += 0.5) {
                const point = { x: wx, y: wy };
                expect(inCircleW(center, 1.5, point, iso)).toBe(
                    inCircle(worldToScreen(center.x, center.y, iso), 48, worldToScreen(point.x, point.y, iso))
                );
            }
        }
    });

    it('inConeW эквивалентен проекции в экранные px', () => {
        const from = { x: 3, y: 1 };
        const dir = { x: 1, y: -1 }; // «вправо» в мировых юнитах
        for (let wx = 0; wx <= 6; wx += 0.5) {
            for (let wy = 0; wy <= 2; wy += 0.5) {
                const point = { x: wx, y: wy };
                expect(inConeW(from, dir, 2, Math.PI / 3, point, iso)).toBe(
                    inCone(
                        worldToScreen(from.x, from.y, iso),
                        worldToScreen(dir.x, dir.y, iso),
                        64,
                        Math.PI / 3,
                        worldToScreen(point.x, point.y, iso)
                    )
                );
            }
        }
    });
});