import { describe, it, expect } from 'vitest';
import { findPath, findPathToNeighbor, type Grid } from '../pathfinding';

function makeGrid(width: number, height: number, walls: string[] = []): Grid {
    const wallSet = new Set(walls);
    return {
        width,
        height,
        isWalkable: (x, y) => !wallSet.has(`${x},${y}`)
    };
}

describe('A*', () => {
    it('прямой путь по пустой сетке', () => {
        const path = findPath(makeGrid(10, 10), { x: 0, y: 0 }, { x: 3, y: 0 });
        expect(path).toEqual([
            { x: 1, y: 0 },
            { x: 2, y: 0 },
            { x: 3, y: 0 }
        ]);
    });

    it('обходит стену', () => {
        const grid = makeGrid(5, 5, ['1,0', '1,1', '1,2']);
        const path = findPath(grid, { x: 0, y: 0 }, { x: 2, y: 0 });
        expect(path).not.toBeNull();
        expect(path!.some((p) => p.x === 1 && (p.y === 0 || p.y === 1 || p.y === 2))).toBe(false);
        expect(path![path!.length - 1]).toEqual({ x: 2, y: 0 });
    });

    it('цель заблокирована — null', () => {
        const grid = makeGrid(5, 5, ['2,2']);
        expect(findPath(grid, { x: 0, y: 0 }, { x: 2, y: 2 })).toBeNull();
    });

    it('цель вне сетки — null', () => {
        const grid = makeGrid(5, 5);
        expect(findPath(grid, { x: 0, y: 0 }, { x: 9, y: 9 })).toBeNull();
    });

    it('старт == цель — пустой путь', () => {
        const grid = makeGrid(5, 5);
        expect(findPath(grid, { x: 2, y: 2 }, { x: 2, y: 2 })).toEqual([]);
    });

    it('диагональ без среза углов', () => {
        // Стена (1,0) срезает угол диагонали (0,0)->(1,1) — она запрещена,
        // путь идёт в обход через (0,1).
        const grid = makeGrid(5, 5, ['1,0']);
        const path = findPath(grid, { x: 0, y: 0 }, { x: 1, y: 1 }, true);
        expect(path).not.toBeNull();
        expect(path![0]).toEqual({ x: 0, y: 1 });
    });

    it('угол срезан со всех сторон — пути нет', () => {
        // Из (0,0) заблокированы оба ортогональных соседа: без среза углов
        // ни шага, ни диагонали сделать нельзя.
        const grid = makeGrid(5, 5, ['1,0', '0,1']);
        expect(findPath(grid, { x: 0, y: 0 }, { x: 2, y: 2 }, true)).toBeNull();
    });

    it('диагональ разрешена, когда соседи свободны', () => {
        const grid = makeGrid(5, 5);
        const path = findPath(grid, { x: 0, y: 0 }, { x: 1, y: 1 }, true);
        expect(path).toEqual([{ x: 1, y: 1 }]);
    });
});
describe('findPathToNeighbor', () => {
    it('цель проходима — всё равно останавливаемся рядом', () => {
        const path = findPathToNeighbor(makeGrid(10, 10), { x: 0, y: 0 }, { x: 2, y: 0 });
        const end = path!.at(-1)!;
        expect(Math.max(Math.abs(end.x - 2), Math.abs(end.y - 0))).toBe(1);
    });

    it('цель занята — путь к соседней клетке', () => {
        const grid = makeGrid(10, 10, ['5,5']);
        const path = findPathToNeighbor(grid, { x: 0, y: 5 }, { x: 5, y: 5 });
        expect(path).not.toBeNull();
        const end = path!.at(-1)!;
        expect(Math.max(Math.abs(end.x - 5), Math.abs(end.y - 5))).toBe(1);
    });

    it('цель окружена — путь не найден', () => {
        const grid = makeGrid(5, 5, [
            '1,1', '2,1', '3,1',
            '1,2', '2,2', '3,2',
            '1,3', '2,3', '3,3'
        ]);
        expect(findPathToNeighbor(grid, { x: 0, y: 4 }, { x: 2, y: 2 })).toBeNull();
    });

    it('выбирает ближайшего из 8 соседей за один прогон', () => {
        // Цель в углу сетки, старт по диагонали: из трёх соседей цели
        // ближайший к старту — (4,4), путь к нему кратчайший возможный.
        const grid = makeGrid(6, 6);
        const path = findPathToNeighbor(grid, { x: 0, y: 0 }, { x: 5, y: 5 });
        expect(path).not.toBeNull();
        expect(path!.at(-1)).toEqual({ x: 4, y: 4 });
        expect(path!.length).toBe(8); // манхэттен (0,0)->(4,4), без крюка
    });

    it('старт уже сосед цели — пустой путь', () => {
        const grid = makeGrid(10, 10);
        expect(findPathToNeighbor(grid, { x: 3, y: 5 }, { x: 4, y: 5 })).toEqual([]);
    });
});
