/**
 * Физика тела в воксельном мире (docs/plan.md, открытый вопрос «физика/
 * коллизии»): AABB-тело против вокселей, занятый воксель = твёрдый.
 * Тело — центр низа (ноги) + полный размер [w,h,d] + скорость.
 * Разрешение коллизий ось-раздельное X→Z→Y с зажимом к граням вокселей —
 * стандартная схема без застревания на стыках. Сабстепы (смещение за шаг
 * < 1 вокселя) защищают от туннелирования сквозь тонкие стены.
 * Чистая математика без рендера (урок №3): мир — точечная выборка get().
 */
import type { Vec3 } from '../animation/mat';

/** Мир с точечной выборкой твёрдости (структурно подходит VoxelWorld). */
export interface SolidWorld {
    get(x: number, y: number, z: number): number;
}

export interface Body {
    /** Центр низа AABB (точка под ногами). Мутабельный кортеж — физика его двигает. */
    pos: [number, number, number];
    vel: [number, number, number];
    /** Полные размеры AABB: [w, h, d]. */
    size: [number, number, number];
    /** Стоит на твёрдом вокселе (обновляется за шаг). */
    grounded: boolean;
}

/** Гравитация, вокс/с². */
export const GRAVITY = 24;
const EPS = 1e-4;          // зазор от грани вокселя — не ловить свою же опору
const MAX_SUBSTEP = 0.9;   // макс смещение за сабстеп (доля вокселя)

export function makeBody(pos: Vec3, size: readonly [number, number, number]): Body {
    return { pos: [...pos], vel: [0, 0, 0], size: [...size], grounded: false };
}

/** Пересекает ли AABB тела хоть один твёрдый воксель (касание грани — нет). */
export function collides(world: SolidWorld, pos: Vec3, size: readonly [number, number, number]): boolean {
    const [w, h, d] = size;
    const x0 = Math.floor(pos[0] - w / 2), x1 = Math.ceil(pos[0] + w / 2) - 1;
    const y0 = Math.floor(pos[1]), y1 = Math.ceil(pos[1] + h) - 1;
    const z0 = Math.floor(pos[2] - d / 2), z1 = Math.ceil(pos[2] + d / 2) - 1;
    for (let y = y0; y <= y1; y++)
        for (let z = z0; z <= z1; z++)
            for (let x = x0; x <= x1; x++)
                if (world.get(x, y, z) !== 0) return true;
    return false;
}

/** Один шаг физики: гравитация + перемещение с разрешением коллизий. */
export function stepBody(world: SolidWorld, body: Body, dt: number): void {
    const maxDisp = Math.max(Math.abs(body.vel[0]), Math.abs(body.vel[1]), Math.abs(body.vel[2])) * dt;
    const n = Math.max(1, Math.ceil(maxDisp / MAX_SUBSTEP));
    const h = dt / n;
    for (let s = 0; s < n; s++) substep(world, body, h);
}

/** Подшаг: оси по очереди; зажим к грани вокселя при столкновении. */
function substep(world: SolidWorld, body: Body, h: number): void {
    const [w, hh, d] = body.size;
    // X
    if (body.vel[0] !== 0) {
        const nx = body.pos[0] + body.vel[0] * h;
        if (collides(world, [nx, body.pos[1], body.pos[2]], body.size)) {
            body.pos[0] = body.vel[0] > 0
                ? Math.floor(nx + w / 2) - w / 2 - EPS
                : Math.floor(nx - w / 2) + 1 + w / 2 + EPS;
            body.vel[0] = 0;
        } else body.pos[0] = nx;
    }
    // Z — симметрично X
    if (body.vel[2] !== 0) {
        const nz = body.pos[2] + body.vel[2] * h;
        if (collides(world, [body.pos[0], body.pos[1], nz], body.size)) {
            body.pos[2] = body.vel[2] > 0
                ? Math.floor(nz + d / 2) - d / 2 - EPS
                : Math.floor(nz - d / 2) + 1 + d / 2 + EPS;
            body.vel[2] = 0;
        } else body.pos[2] = nz;
    }
    // Y: гравитация, пол/потолок
    body.vel[1] -= GRAVITY * h;
    const ny = body.pos[1] + body.vel[1] * h;
    if (collides(world, [body.pos[0], ny, body.pos[2]], body.size)) {
        if (body.vel[1] < 0) {
            body.pos[1] = Math.floor(ny) + 1; // пол: встать ровно на грань
            body.grounded = true;
        } else {
            body.pos[1] = Math.floor(ny + hh) - hh - EPS; // потолок
        }
        body.vel[1] = 0;
    } else {
        body.pos[1] = ny;
        body.grounded = false;
    }
}