import { describe, it, expect } from 'vitest';
import { inCircle, inCircleW, inCone, inConeW, angleBetween, nearest, conePath } 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)
                    )
                );
            }
        }
    });
});
describe('conePath', () => {
    it('вершина в from, точки дуги на радиусе', () => {
        const from = { x: 10, y: 20 };
        const pts = conePath(from, { x: 1, y: 0 }, 50, Math.PI / 4, 8);
        expect(pts[0]).toEqual({ x: 10, y: 20 });
        expect(pts).toHaveLength(10); // вершина + segments + 1
        for (const p of pts.slice(1)) {
            const d = Math.hypot(p.x - from.x, p.y - from.y);
            expect(d).toBeCloseTo(50, 6);
        }
    });

    it('дуга симметрична относительно dir, крайние точки на ±halfAngle', () => {
        const from = { x: 0, y: 0 };
        const half = Math.PI / 3;
        const pts = conePath(from, { x: 0, y: -1 }, 100, half, 12);
        const first = pts[1]!;
        const last = pts[pts.length - 1]!;
        // зеркальность по экранному X (dir вверх — ось симметрии x = 0)
        expect(first.x).toBeCloseTo(-last.x, 6);
        expect(first.y).toBeCloseTo(last.y, 6);
        // средняя точка дуги — ровно по dir
        const mid = pts[1 + 6]!;
        expect(mid.x).toBeCloseTo(0, 6);
        expect(mid.y).toBeCloseTo(-100, 6);
    });

    it('нулевой halfAngle — вырожденный сектор (луч)', () => {
        const pts = conePath({ x: 1, y: 1 }, { x: 1, y: 0 }, 40, 0, 5);
        expect(pts).toHaveLength(7);
        for (const p of pts.slice(1)) expect(p.y).toBeCloseTo(1, 6);
    });
});
