/**
 * Сценарий collision — коллизии и карта коллизий агенту.
 * 1) слой collision в снапшоте: стены/вода/пропы, тайл героя свободен.
 * 2) scene:walkable: трава проходима, вода и дерево — нет.
 * 3) scene:raycast: дерево перекрывает луч, вода прозрачна (плевок летит над прудом).
 * 4) scene:walk в непроходимый — false; approach подводит к соседу.
 * 5) расталкивание тел: враг выталкивает себя из героя.
 * Запуск: node tools/agent.mjs run tools/checks/collision.mjs
 */
import { withChecks } from '../lib.mjs';

export default async function ({ pretty }) {
    return withChecks(
        'collision',
        async (t) => {
            const { c } = t;
            await c.run('снапшот: слой collision валиден', async () => {
                await t.boot();
                const s = await t.ctx.agent.snapshot();
                const col = s.collision;
                c.expect(col && col.width > 0 && col.height > 0, 'нет карты коллизий', col);
                c.expect(
                    col.blocked.length === col.width * col.height,
                    'blocked не накрывает карту',
                    { len: col.blocked.length, w: col.width, h: col.height }
                );
                c.expect(
                    col.blocked.every((v) => v === 0 || v === 1),
                    'blocked не бинарный',
                    col.blocked.filter((v) => v !== 0 && v !== 1)
                );
                c.expect(
                    col.props.every((p) => p.w >= 1 && p.h >= 1),
                    'проп без footprint',
                    col.props
                );
                const idx = s.hero.tile.y * col.width + s.hero.tile.x;
                c.expect(col.blocked[idx] === 0, 'герой заспавнился в блоке', s.hero.tile);
                return null;
            });
            await c.run('scene:walkable: вода и дерево непроходимы', async () => {
                const walk = (x, y) => t.ctx.agent.command('scene:walkable', { x, y });
                const s = await t.ctx.agent.snapshot();
                const heroWalk = await walk(s.hero.tile.x, s.hero.tile.y);
                c.expect(heroWalk === true, 'тайл героя непроходим', s.hero.tile);
                c.expect((await walk(19, 8)) === false, 'вода проходима?');
                c.expect((await walk(0, 4)) === false, 'дерево проходимо?');
                return null;
            });
            await c.run('scene:raycast: дерево перекрывает, вода прозрачна', async () => {
                const ray = (from, to) => t.ctx.agent.command('scene:raycast', { from, to });
                // Вертикальный луч (3,8)->(3,10) задевает дерево (3,9).
                c.expect((await ray({ x: 3, y: 8 }, { x: 3, y: 10 })) === false, 'дерево не перекрыло луч');
                // Берег-берег через пруд: вода прозрачна, луч чист.
                c.expect((await ray({ x: 18, y: 8 }, { x: 24, y: 8 })) === true, 'вода перекрыла луч');
                return null;
            });
            await c.run('scene:walk в непроходимый — false, approach — к соседу', async () => {
                await t.sleepEnemies();
                // Ближе к дереву: маршрут от (2,3) не задевает спящих ползунов у тропы
                // (сам (2,2) — тайл перехода на пруды, туда телепортировать нельзя).
                await t.ctx.agent.command('scene:teleport', { x: 2, y: 3 });
                await t.ctx.agent.waitFor('!s.transitioning', { timeoutTicks: 60 });
                // Путь в дерево (0,4) не строится: scene:walk — false.
                const walked = await t.ctx.agent.command('scene:walk', { x: 0, y: 4 });
                c.expect(walked === false, 'scene:walk провёл героя в дерево', walked);
                // approach подводит к проходимому соседу дерева.
                const dest = await t.ctx.agent.approach(0, 4);
                c.expect(dest !== null, 'approach к дереву не нашёл соседа');
                const s = await t.ctx.agent.snapshot();
                c.expect(
                    s.hero.tile.x === dest.x && s.hero.tile.y === dest.y,
                    'герой не на тайле прибытия approach',
                    { dest, after: s.hero.tile }
                );
                const col = s.collision;
                const idx = s.hero.tile.y * col.width + s.hero.tile.x;
                c.expect(col.blocked[idx] === 0, 'герой стоит в блоке', s.hero.tile);
                return null;
            });
            await c.run('расталкивание: враг выталкивает себя из героя', async () => {
                await t.sleepEnemies();
                // Ползун спит в (10.5,7.5); телепортируемся внутрь его тела.
                await t.ctx.agent.command('scene:teleport', { x: 10, y: 7 });
                await t.ctx.agent.waitFor('!s.transitioning', { timeoutTicks: 60 });
                const s = await t.ctx.agent.snapshot();
                const en = (s.enemies ?? []).find(
                    (e) => !e.dead && Math.hypot(e.pos.x - 10.5, e.pos.y - 7.5) < 1.2
                );
                c.expect(!!en, 'ползун у тропы не найден', s.enemies);
                const gap = Math.hypot(en.pos.x - s.hero.pos.x, en.pos.y - s.hero.pos.y);
                c.expect(gap >= 0.4, 'враг остался внутри тела героя', { gap, en: en.pos, hero: s.hero.pos });
                return null;
            });
        },
        { pretty }
    );
}