import { describe, it, expect } from 'vitest';
import { Container } from 'pixi.js';
import { Camera } from '../Camera';
import { screenToWorld, worldToScreen } from '../../math/iso';

/** Смещение цели от центра камеры в экранных пикселях: мировая точка. */
function targetAtScreenOffset(cam: Camera, dxPx: number, dyPx: number): { x: number; y: number } {
    const c = worldToScreen(cam.x, cam.y);
    return screenToWorld(c.x + dxPx, c.y + dyPx);
}

describe('Camera dead zone', () => {
    it('без dead zone — камера центрируется на цели', () => {
        const cam = new Camera(480, 270);
        cam.follow(3, 5);
        expect(cam.x).toBe(3);
        expect(cam.y).toBe(5);
    });

    it('цель внутри окна — камера стоит', () => {
        const cam = new Camera(480, 270);
        cam.snap(3, 5);
        cam.deadZonePx = { width: 160, height: 100 };
        // Цель на 60 px правее и 30 px ниже центра экрана — внутри окна.
        const t = targetAtScreenOffset(cam, 60, 30);
        cam.follow(t.x, t.y);
        expect(cam.x).toBe(3);
        expect(cam.y).toBe(5);
    });

    it('цель толкает камеру ровно на величину выхода', () => {
        const cam = new Camera(480, 270);
        cam.snap(3, 5);
        cam.deadZonePx = { width: 160, height: 100 };
        // Выход за окно: 20 px вправо и 20 px вверх.
        const t = targetAtScreenOffset(cam, 100, -70);
        cam.follow(t.x, t.y);
        // Центр проекции сместился ровно на (20, −20) экранных пикселей.
        const c0 = worldToScreen(3, 5);
        const c = worldToScreen(cam.x, cam.y);
        expect(c.x - c0.x).toBeCloseTo(20, 6);
        expect(c.y - c0.y).toBeCloseTo(-20, 6);
    });

    it('snap ставит камеру на точку, минуя окно', () => {
        const cam = new Camera(480, 270);
        cam.deadZonePx = { width: 160, height: 100 };
        cam.snap(5, 2);
        expect(cam.x).toBe(5);
        expect(cam.y).toBe(2);
        // Окно сохранено: цель чуть правее центра — камера не сдвигается.
        const t = targetAtScreenOffset(cam, 50, 0);
        cam.follow(t.x, t.y);
        expect(cam.x).toBe(5);
    });
});

describe('Camera bounds (мировые юниты)', () => {
    it('границы мира держат камеру прижатой', () => {
        const cam = new Camera(480, 270);
        cam.bounds = { x: 0, y: 0, width: 64, height: 64 };
        cam.snap(-1000, 500); // за границей по обеим осям
        // bbox карты: x = (0 - 64)*16, y = 0; клэмп по проекции центра.
        const c = worldToScreen(cam.x, cam.y);
        expect(c.x).toBeCloseTo(-1024 + 240, 6);
        expect(c.y).toBeCloseTo(135, 6);
    });

    it('apply ставит worldRoot по округлённой проекции', () => {
        const cam = new Camera(480, 270);
        cam.snap(3, 5);
        const view = new Container();
        cam.apply(view);
        const applied = worldToScreen(cam.x, cam.y);
        expect(view.x).toBe(240 - Math.round(applied.x));
        expect(view.y).toBe(135 - Math.round(applied.y));
    });

    it('toScreen согласована с apply: точка мира проецируется в то же место', () => {
        const cam = new Camera(480, 270);
        cam.snap(3, 5);
        const view = new Container();
        cam.apply(view);
        // Точка мира в worldRoot: worldToScreen(p) + позиция worldRoot.
        const wx = 3.5, wy = 5.5;
        const p = worldToScreen(wx, wy);
        expect(view.x + p.x).toBeCloseTo(cam.toScreen(wx, wy).x, 6);
        expect(view.y + p.y).toBeCloseTo(cam.toScreen(wx, wy).y, 6);
    });

    it('toScreen учитывает тряску так же, как apply', () => {
        const cam = new Camera(480, 270);
        cam.snap(3, 5);
        cam.addShake(8, 1); // тряска активна, offset фиксирован до update
        const view = new Container();
        cam.apply(view);
        const p = worldToScreen(3.5, 5.5);
        expect(view.x + p.x).toBeCloseTo(cam.toScreen(3.5, 5.5).x, 6);
        expect(view.y + p.y).toBeCloseTo(cam.toScreen(3.5, 5.5).y, 6);
    });
});