import { gridOf, ENGINE_SNAPSHOT_KEYS, type SnapshotLayer, type TileMapData } from '@rpg/engine';

/**
 * Контентный слой снапшота агентного моста. Чистые функции — тестируются
 * в Vitest без браузера; LocationScene/GameAgent подставляют живые данные.
 */

/** Top-level ключи полного снапшота игры: движковый слой + сцена + слой игры. */
export const AGENT_SNAPSHOT_KEYS: readonly string[] = [
    ...ENGINE_SNAPSHOT_KEYS,
    // слой сцены локации (LocationSnapshot)
    'scene',
    'area',
    'areaName',
    'hero',
    'enemies',
    'npcs',
    'transitions',
    'interactables',
    'collision',
    'lighting',
    'dialogue',
    'cutscene',
    'lastToast',
    'interactHint',
    // слой игры (gameLayer)
    'flags',
    'vars',
    'inventory',
    'time'
];

/*
 * Все снапшот-типы ниже — type alias'ы, не interface: у alias'ов есть неявная
 * индексная подпись, поэтому они присваиваются JsonValue/SnapshotLayer без
 * кастов (interface — нет). Рассинхрон слоя и типа — ошибка компиляции.
 */

/** Герой. */
export type HeroSnapshot = {
    tile: { x: number; y: number };
    pos: { x: number; y: number };
    hp: number;
    maxHp: number;
    facing: string;
    moving: boolean;
    invuln: boolean;
    inHazard: string | null;
};

/** Враг. */
export type EnemySnapshot = {
    kind: string;
    state: string | null;
    hp: number;
    pos: { x: number; y: number };
    asleep: boolean;
    dead: boolean;
};

/** NPC. */
export type NpcSnapshot = {
    id: string;
    name: string;
    tile: { x: number; y: number };
    met: boolean;
};

/** Активный диалог (или null). */
export type DialogueSnapshot = {
    id: string | null;
    nodeId: string | null;
    speaker: string | null;
    text: string | null;
    /** Настроение реплики (презентационное; на геймплей не влияет). */
    mood: string | null;
    /** Свободные метки узла — для фильтров проверок. */
    tags: string[];
    choices: string[];
    /** Показанные узлы по порядку — как диалог был пройден. */
    path: string[];
    waitingForChoice: boolean;
}

/** Переход (дверь/выход/портал) — из данных локации. */
export type TransitionSnapshot = {
    tile: { x: number; y: number };
    to: string;
    /** Как срабатывает: шагом на тайл или кликом. */
    trigger: 'step' | 'click';
    label: string | null;
}

/** Интерактивный объект области (used — для once-объектов). */
export type InteractableSnapshot = {
    id: string;
    tile: { x: number; y: number };
    label: string | null;
    used: boolean;
}

/** Источник света (пост-мерцание). */
export type LightSourceSnapshot = {
    id: string;
    x: number;
    y: number;
    color: number;
    intensity: number;
}

/** Освещение сцены: ambient (multiply-цвет) + виньетка + активные источники. */
export type LightingSnapshot = {
    ambient: number;
    /** Целевая интенсивность виньетки 0..1. */
    vignette: number;
    sources: LightSourceSnapshot[];
}

/** Карта коллизий для агента: стены/вода по тайлам + крупные пропы. */
export type CollisionSnapshot = {
    width: number;
    height: number;
    /** Проходимость по тайлам, построчно: 0 — свободен, 1 — блок (включая footprint пропов). */
    blocked: number[];
    /** Крупные объекты с footprint'ом (дома и т.п.). */
    props: { x: number; y: number; w: number; h: number }[];
}

/** Контентный слой сцены локации (структурно = SnapshotLayer). */
export type LocationSnapshot = {
    scene: 'location';
    area: string;
    areaName: string;
    hero: HeroSnapshot;
    enemies: EnemySnapshot[];
    npcs: NpcSnapshot[];
    transitions: TransitionSnapshot[];
    interactables: InteractableSnapshot[];
    collision: CollisionSnapshot;
    /** Освещение области (ambient + активные источники). */
    lighting: LightingSnapshot;
    dialogue: DialogueSnapshot | null;
    cutscene: { active: boolean } | null;
    /** Последний тост (текст + тик) — единственный канал текста реакций. */
    lastToast: { text: string; tick: number } | null;
    /** Ближайшая цель кнопки действия (или null — вне радиуса взаимодействия). */
    interactHint: { kind: string; label: string; tile: { x: number; y: number } } | null;
}

/** Время суток (ход игры — непрерывный, 1 с реального = 1 игровая минута). */
export type TimeSnapshot = {
    /** Целые минуты суток 0..1439. */
    minutes: number;
    /** Строка «8:24» (как на часах предмета). */
    label: string;
    /** Фактор ночи 0..1 (dayNightFactor по часам). */
    night: number;
}

/** Полный снапшот игры: движковый слой + сценический + слой игры. */
export type GameSnapshot = SnapshotLayer & {
    /** Флаги прохождения. */
    flags: string[];
    /** Переменные прохождения. */
    vars: Record<string, number | string | boolean>;
    /** Сумка. */
    inventory: { id: string; count: number }[];
    /** Время суток. */
    time: TimeSnapshot;
};

/** Округление до 3 знаков — JSON читабельнее, точность в юнитах не теряется. */
function r(v: number): number {
    return Math.round(v * 1000) / 1000;
}

/** Слой карты коллизий: blocked по тайлам (gridOf учитывает footprint пропов). */
export function collisionLayer(data: TileMapData): SnapshotLayer & { collision: CollisionSnapshot } {
    const grid = gridOf(data);
    const blocked: number[] = [];
    for (let y = 0; y < data.height; y++) {
        for (let x = 0; x < data.width; x++) {
            blocked.push(grid.isWalkable(x, y) ? 0 : 1);
        }
    }
    const collision: CollisionSnapshot = {
        width: data.width,
        height: data.height,
        blocked,
        props: (data.props ?? []).map((p) => ({ x: p.x, y: p.y, w: p.w ?? 1, h: p.h ?? 1 }))
    };
    return { collision };
}

/** Слой героя. */
export function heroLayer(o: HeroSnapshot): SnapshotLayer & { hero: HeroSnapshot } {
    return {
        hero: {
            tile: { x: o.tile.x, y: o.tile.y },
            pos: { x: r(o.pos.x), y: r(o.pos.y) },
            hp: o.hp,
            maxHp: o.maxHp,
            facing: o.facing,
            moving: o.moving,
            invuln: o.invuln,
            inHazard: o.inHazard
        }
    };
}

/** Слой врагов. */
export function enemiesLayer(enemies: EnemySnapshot[]): SnapshotLayer & { enemies: EnemySnapshot[] } {
    return {
        enemies: enemies.map((e) => ({
            kind: e.kind,
            state: e.state,
            hp: e.hp,
            pos: { x: r(e.pos.x), y: r(e.pos.y) },
            asleep: e.asleep,
            dead: e.dead
        }))
    };
}

/** Слой NPC. */
export function npcsLayer(npcs: NpcSnapshot[]): SnapshotLayer & { npcs: NpcSnapshot[] } {
    return {
        npcs: npcs.map((n) => ({
            id: n.id,
            name: n.name,
            tile: { x: n.tile.x, y: n.tile.y },
            met: n.met
        }))
    };
}

/** Слой диалога. */
export function dialogueLayer(d: DialogueSnapshot | null): SnapshotLayer & { dialogue: DialogueSnapshot | null } {
    return { dialogue: d ? { ...d } : null };
}

/** Слой освещения: ambient + виньетка + активные источники (координаты экранные px). */
export function lightingLayer(o: LightingSnapshot): SnapshotLayer & { lighting: LightingSnapshot } {
    return {
        lighting: {
            ambient: o.ambient,
            vignette: r(o.vignette),
            sources: o.sources.map((s) => ({
                id: s.id,
                x: r(s.x),
                y: r(s.y),
                color: s.color,
                intensity: r(s.intensity)
            }))
        }
    };
}

/** Слой уровня игры (вне сцен). */
export function gameLayer(o: {
    flags: string[];
    vars: Record<string, number | string | boolean>;
    inventory: { id: string; count: number }[];
    time: TimeSnapshot;
}): GameSnapshot {
    return {
        flags: [...o.flags],
        vars: { ...o.vars },
        inventory: o.inventory.map((s) => ({ id: s.id, count: s.count })),
        time: { ...o.time }
    };
}