import type { Invariant } from './types';

/**
 * Чистые хелперы инвариантов: сцены и системы собирают нарушения через них,
 * мост мержит и отдаёт списком. Без зависимостей — тестируется в Vitest.
 */

function inv(id: string, severity: 'error' | 'warn', message: string, where?: string): Invariant {
    return where ? { id, severity, message, where } : { id, severity, message };
}

/** NaN/Infinity в числовых значениях (позиции, hp и т.п.) — почти всегда сломанный мир. */
export function checkFinite(values: Record<string, number>, where: string): Invariant[] {
    const out: Invariant[] = [];
    for (const [name, v] of Object.entries(values)) {
        if (!Number.isFinite(v)) {
            out.push(inv('pos-nan', 'error', `${name} = ${v} (не конечное число)`, where));
        }
    }
    return out;
}

/** Число вне диапазона (hp < 0, скорость < 0, ...). */
export function checkRange(
    name: string,
    v: number,
    min: number,
    max: number,
    where: string
): Invariant | null {
    if (!Number.isFinite(v)) {
        return inv('pos-nan', 'error', `${name} = ${v} (не конечное число)`, where);
    }
    if (v < min || v > max) {
        return inv('range', 'error', `${name} = ${v} вне [${min}, ${max}]`, where);
    }
    return null;
}

/** Тайл в границах карты. */
export function checkBounds(
    name: string,
    tile: { x: number; y: number },
    w: number,
    h: number,
    where: string
): Invariant | null {
    if (!Number.isInteger(tile.x) || !Number.isInteger(tile.y) || tile.x < 0 || tile.y < 0
        || tile.x >= w || tile.y >= h) {
        return inv('out-of-bounds', 'error', `${name}: тайл (${tile.x},${tile.y}) вне карты ${w}×${h}`, where);
    }
    return null;
}

/** Тайл проходим (герой/NPC не должны стоять в стене). */
export function checkWalkable(
    name: string,
    tile: { x: number; y: number },
    map: { isWalkable(x: number, y: number): boolean },
    where: string
): Invariant | null {
    if (!map.isWalkable(tile.x, tile.y)) {
        return inv('in-wall', 'error', `${name} стоит в непроходимом тайле (${tile.x},${tile.y})`, where);
    }
    return null;
}

/** Мерж групп: null/false/undefined отбрасываются, группы — разворачиваются. */
export function mergeInvariants(
    ...groups: (Invariant | null | undefined | false | Invariant[])[]
): Invariant[] {
    const out: Invariant[] = [];
    for (const g of groups) {
        if (!g) continue;
        if (Array.isArray(g)) out.push(...g);
        else out.push(g);
    }
    return out;
}