import { describe, it, expect } from 'vitest';
import type { Vec2 } from '../Vec2';
import {
    worldToScreen,
    screenToWorld,
    unitsToPx,
    pxToUnits,
    worldLen,
    worldNorm,
    worldDist,
    moveTowardsW,
    tileToWorld,
    worldToTile,
    worldRectToScreen,
    DEFAULT_ISO
} from '../iso';

describe('проекция мир↔экран', () => {
    it('совпадает с iso-формулой', () => {
        // Центр тайла (2, 3) на экране: (2.5-3.5)*16, (2.5+3.5)*8
        const s = worldToScreen(2.5, 3.5);
        expect(s.x).toBeCloseTo(-16, 10);
        expect(s.y).toBeCloseTo(48, 10);
    });

    it('round-trip worldToScreen ∘ screenToWorld', () => {
        for (let wx = -5; wx <= 5; wx += 0.7) {
            for (let wy = -5; wy <= 5; wy += 0.7) {
                const s = worldToScreen(wx, wy);
                const back = screenToWorld(s.x, s.y);
                expect(back.x).toBeCloseTo(wx, 10);
                expect(back.y).toBeCloseTo(wy, 10);
            }
        }
    });

    it('учитывает origin', () => {
        const iso = { ...DEFAULT_ISO, originX: 100, originY: 50 };
        const s = worldToScreen(1, 0, iso);
        expect(s.x).toBeCloseTo(100 + 16, 10);
        expect(s.y).toBeCloseTo(50 + 8, 10);
    });
});

describe('скаляры: юниты↔пиксели', () => {
    it('линейка проекции — tileW px на юнит', () => {
        expect(unitsToPx(1)).toBe(32);
        expect(unitsToPx(1.5)).toBe(48);
        expect(pxToUnits(32)).toBe(1);
        expect(pxToUnits(48)).toBe(1.5);
        expect(pxToUnits(unitsToPx(2.75))).toBeCloseTo(2.75, 10);
    });
});

describe('метрика проекции', () => {
    it('длины характерных смещений', () => {
        // Экранные оси — мировые диагонали: «вправо» = (1,-1) -> 1 юнит.
        expect(worldLen(1, -1)).toBeCloseTo(1, 10);
        expect(worldLen(-1, 1)).toBeCloseTo(1, 10);
        // Диагональ сетки (1,1) — вертикальный шаг: на экране 8+8=16 px -> 0.5 юнита.
        expect(worldLen(1, 1)).toBeCloseTo(0.5, 10);
        // По оси сетки: соседние тайлы — sqrt(16² + 8²)/32 ≈ 0.56 юнита.
        expect(worldLen(1, 0)).toBeCloseTo(Math.hypot(16, 8) / 32, 10);
    });

    it('worldDist совпадает с worldLen', () => {
        expect(worldDist({ x: 0, y: 0 }, { x: 3, y: -3 })).toBeCloseTo(3, 10);
        expect(worldDist({ x: 1, y: 2 }, { x: 1, y: 2 })).toBeCloseTo(0, 10);
    });

    it('worldNorm даёт постоянную экранную скорость', () => {
        // Нормализованный вектор в юнитах при скорости s юнит/с даёт ровно 32·s px/с.
        const s = 1.75;
        const dirs: Vec2[] = [
            { x: 1, y: 0 },
            { x: 0, y: 1 },
            { x: 1, y: 1 },
            { x: -1, y: 1 },
            { x: 2, y: -1 }
        ];
        for (const d of dirs) {
            const n = worldNorm(d.x, d.y);
            const step = { x: n.x * s, y: n.y * s };
            const px = worldToScreen(step.x, step.y);
            expect(Math.hypot(px.x, px.y)).toBeCloseTo(32 * s, 6);
        }
    });

    it('worldNorm нулевого вектора', () => {
        expect(worldNorm(0, 0)).toEqual({ x: 0, y: 0 });
    });
});

describe('moveTowardsW', () => {
    const a = { x: 0, y: 0 };
    const b = { x: 3, y: -3 }; // 3 юнита

    it('не проскакивает цель', () => {
        expect(moveTowardsW(a, b, 10)).toEqual(b);
    });

    it('постоянная экранная скорость на любом направлении', () => {
        const step = moveTowardsW(a, b, 1.75);
        const px = worldToScreen(step.x, step.y);
        expect(Math.hypot(px.x, px.y)).toBeCloseTo(32 * 1.75, 6);
    });

    it('последовательность шагов достигает цели', () => {
        let p = a;
        for (let i = 0; i < 100 && p !== b; i++) {
            const next = moveTowardsW(p, b, 1.75);
            if (next === p) break;
            p = next;
        }
        expect(p).toEqual(b);
    });
});

describe('тайлы↔мир', () => {
    it('tileToWorld — центр тайла', () => {
        expect(tileToWorld(2, 3)).toEqual({ x: 2.5, y: 3.5 });
    });

    it('worldToTile — floor внутри карты', () => {
        expect(worldToTile(2.5, 3.5, 10, 10)).toEqual({ x: 2, y: 3 });
        expect(worldToTile(0, 0, 10, 10)).toEqual({ x: 0, y: 0 });
    });

    it('worldToTile вне карты -> null', () => {
        expect(worldToTile(-0.1, 5, 10, 10)).toBeNull();
        expect(worldToTile(5, 10, 10, 10)).toBeNull();
    });
});

describe('worldRectToScreen', () => {
    it('карта 28x28 — границы от западного угла', () => {
        const r = worldRectToScreen(0, 0, 28, 28);
        expect(r).toEqual({ x: -448, y: 0, width: 896, height: 448 });
    });

    it('произвольный прямоугольник', () => {
        // Углы (2,0) и (2,1): x от (2-1)*16 до 2*16.
        const r = worldRectToScreen(2, 0, 1, 1);
        expect(r.x).toBeCloseTo((2 - 1) * 16, 10);
        expect(r.width).toBeCloseTo(2 * 16, 10);
    });
});