import type { Vec2 } from '@rpg/engine';
import type { ItemId } from './items';
import type { AreaId } from './locations';
import { FLAGS, VARS } from './ids';
import { QUEST_TONES, QUEST_TONE_TILES } from './quests';

/**
 * Интерактивные объекты области: сундуки, очаги, записки, прилавки, столбы
 * у троп, подборные сгустки, рычаги. Первая подходящая реакция (по `when`)
 * срабатывает; эффекты применяет рантайм systems/Interactables.ts через sink.
 */

/** Условие реакции: все указанные поля должны выполняться. */
export interface InteractWhen {
    /** Флаг поднят. */
    flag?: string;
    /** Флаг не поднят. */
    notFlag?: string;
    /** Вар не меньше значения. */
    varAtLeast?: { id: string; value: number };
}

/** Реакция объекта: текст (тост) + необязательные эффекты. */
export interface InteractResponse {
    /** Первая реакция с выполненным условием срабатывает (без when — всегда). */
    when?: InteractWhen;
    text: string;
    setFlags?: string[];
    clearFlags?: string[];
    setVar?: { id: string; value: number };
    /** Прибавить к вару (подбор мотов: +1 за каждый нарост). */
    addVar?: { id: string; by: number };
    /** Выдать предмет (в сумку). */
    gives?: ItemId;
    /** Сколько штук gives (по умолчанию 1). */
    count?: number;
    /** Ключ звука (sfx/...). */
    sound?: string;
    /** Тост вместо text (после эффектов); с varKey — дописывает « (N/goal)». */
    toast?: { text: string; varKey?: string; goal?: number };
    /** Сюжетный эффект (DIALOGUE_CUSTOM): сцена запускает катсцену/событие. */
    custom?: string;
}

/** Вьюха объекта из ассетов: если задана — рисуется вместо плейсхолдера Graphics. */
export interface InteractableView {
    /** Ключ текстуры в ассетах (tiles/…, chars/…). */
    tex: string;
    /** Якорь по Y в пикселях кадра от низа (по умолчанию низ спрайта). */
    anchorY?: number;
}

/** Объект мира, с которым герой взаимодействует кликом. */
export interface InteractableDef {
    id: string;
    /** Позиция в мировых юнитах (центр тайла = координата + 0.5). */
    pos: Vec2;
    /** Тайл привязки (floor от pos; вычисляется — в контенте не писать). */
    tile: { x: number; y: number };
    kind: 'examine' | 'pickup' | 'container' | 'lever' | 'signpost';
    /** Вьюха из ассетов (без неё — плейсхолдер Graphics по kind). */
    view?: InteractableView;
    /** Подпись при близости (и в снапшоте). */
    label?: string;
    /** Одноразовый: помечается флагом used:<id> и больше не реагирует. */
    once?: boolean;
    /** Без предмета — отказ с lockedText. */
    requiresItem?: ItemId;
    lockedText?: string;
    responses: InteractResponse[];
}

/** Что резолверу нужно знать о герое и мире (подставляется из GameState/сумки). */
export interface InteractCtx {
    hasFlag(flag: string): boolean;
    hasItem(id: ItemId): boolean;
    getVar(id: string): number;
    /** Одноразовый объект уже использован (флаг used:<id>). */
    used(id: string): boolean;
}

/** Результат взаимодействия: реакция / отказ (заперто) / ничего (пусто). */
export type InteractPick =
    | { ok: true; response: InteractResponse }
    | { ok: false; lockedText: string }
    | null;

/** Условие when выполнено (все указанные поля). */
function matches(when: InteractWhen | undefined, ctx: InteractCtx): boolean {
    if (!when) return true;
    if (when.flag !== undefined && !ctx.hasFlag(when.flag)) return false;
    if (when.notFlag !== undefined && ctx.hasFlag(when.notFlag)) return false;
    if (when.varAtLeast && ctx.getVar(when.varAtLeast.id) < when.varAtLeast.value) return false;
    return true;
}

/**
 * Чистый выбор реакции: первая подходящая по `when`; одноразовые объекты
 * после использования молчат (null); requiresItem без предмета — отказ.
 */
export function resolveInteractable(def: InteractableDef, ctx: InteractCtx): InteractPick {
    if (def.requiresItem && !ctx.hasItem(def.requiresItem)) {
        return { ok: false, lockedText: def.lockedText ?? 'Ничего не выйдет.' };
    }
    if (def.once && ctx.used(def.id)) return null;
    const response = def.responses.find((r) => matches(r.when, ctx));
    if (!response) return null;
    return { ok: true, response };
}

/** Контент по областям (см. docs/world.md): интерьеры и улицы (pos — первичен). */
const INTERACTABLES_RAW: Partial<Record<AreaId, Omit<InteractableDef, 'tile'>[]>> = {
    // --- луга: столбы у троп и подборные пепельные моты (зачаток экономики) ---
    meadows: [
        {
            id: 'sign_meadows_east',
            pos: { x: 25.5, y: 13.5 },
            kind: 'signpost',
            label: 'Столб у тропы',
            responses: [
                {
                    text: '«Звенец» — стёрто, но различимо. Дальше на восток: иди на звон.',
                    sound: 'sfx/note'
                }
            ]
        },
        {
            id: 'sign_meadows_west',
            pos: { x: 2.5, y: 3.5 },
            kind: 'signpost',
            label: 'Столб у тропы',
            responses: [
                {
                    text: '«Серые пруды» — стрелка на северо-запад. Ниже приписка: «туман лжёт».',
                    sound: 'sfx/note'
                }
            ]
        },
        {
            id: 'mote_1',
            pos: { x: 11.5, y: 14.5 },
            kind: 'pickup',
            label: 'Пепельный мот',
            once: true,
            responses: [
                {
                    text: 'Сгусток серой земли с тёплой искрой. Мот — то, ради чего сажают колокольчики.',
                    gives: 'mote',
                    addVar: { id: VARS.motes, by: 1 },
                    sound: 'sfx/mote_pickup'
                }
            ]
        },
        {
            id: 'mote_2',
            pos: { x: 14.5, y: 20.5 },
            kind: 'pickup',
            label: 'Пепельный мот',
            once: true,
            responses: [
                {
                    text: 'Ещё один мот. Земля здесь отдаёт их неохотно.',
                    gives: 'mote',
                    addVar: { id: VARS.motes, by: 1 },
                    sound: 'sfx/mote_pickup'
                }
            ]
        },
        {
            id: 'mote_3',
            pos: { x: 14.5, y: 6.5 },
            kind: 'pickup',
            label: 'Пепельный мот',
            once: true,
            responses: [
                {
                    text: 'Третий мот за день. Сумка тихо позвякивает.',
                    gives: 'mote',
                    addVar: { id: VARS.motes, by: 1 },
                    sound: 'sfx/mote_pickup'
                }
            ]
        }
    ],
    // --- роща (акт 2): деревья-механизмы для звона и резонатор Клинта ---
    // Реакция дерева-механизма: до квеста молчит; с квестом — звон и тон
    // (свой флаг на дерево — addVar без повтора); после настройки гудит в лад.
    rust_grove: [
        ...(['tone_tree_1', 'tone_tree_2', 'tone_tree_3'] as const).map((id, i): Omit<InteractableDef, 'tile'> => {
            const [tx, ty] = QUEST_TONE_TILES[i];
            return {
                id,
                pos: { x: tx + 0.5, y: ty + 0.5 },
                kind: 'examine',
                label: 'Дерево-механизм',
                responses: [
                    {
                        when: { flag: FLAGS.quest_resonator_done },
                        text: 'Дерево гудит в лад с резонатором. Кабели тёплые.',
                        sound: 'sfx/bell_low'
                    },
                    {
                        when: { notFlag: FLAGS.quest_resonator_taken },
                        text: 'Кабели оплетают ствол, но дерево молчит. Не сейчас.',
                        sound: 'sfx/ash_hiss'
                    },
                    {
                        when: { notFlag: FLAGS[id] },
                        text: 'Звон колокольчика — и кабели отвечают: дерево отдаёт свой тон.',
                        toast: { text: 'Тон', varKey: VARS.tones, goal: QUEST_TONES },
                        setFlags: [FLAGS[id]],
                        addVar: { id: VARS.tones, by: 1 },
                        sound: 'sfx/bell_hit'
                    },
                    { text: 'Это дерево уже прозвенело. Тон унесён.', sound: 'sfx/note' }
                ]
            };
        }),
        {
            id: 'clint_resonator',
            pos: { x: 12.5, y: 8.5 },
            kind: 'lever',
            label: 'Резонатор Клинта',
            responses: [
                {
                    when: { flag: FLAGS.quest_resonator_done },
                    text: 'Резонатор держит тон ровно. Клинт поддел его под пол-тона.',
                    sound: 'sfx/bell_low'
                },
                {
                    text: 'Медные колокола на стальной раме. Ржавый, молчит. Клинту нужен верный звон.',
                    sound: 'sfx/ash_hiss'
                }
            ]
        }
    ],
    ponds: [
        {
            id: 'sign_ponds',
            pos: { x: 3.5, y: 2.5 },
            kind: 'signpost',
            label: 'Столб у берега',
            responses: [
                {
                    text: '«Не дышать накатом» — буквы вросли в ржавчину. Кто-то дописал: «голос дороже соли».',
                    sound: 'sfx/note'
                }
            ]
        }
    ],
    zvenets: [
        {
            id: 'sign_zvenets',
            pos: { x: 3.5, y: 9.5 },
            kind: 'signpost',
            label: 'Столб у входа',
            responses: [
                {
                    text: '«Звенец. Мастерская мира». Ниже чужим почерком: «кузня чинит резонаторы, лавка меняет соль».',
                    sound: 'sfx/note'
                }
            ]
        },
        {
            id: 'bell_rope',
            pos: { x: 13.5, y: 7.5 },
            kind: 'lever',
            label: 'Верёвка колокола',
            responses: [
                {
                    when: { notFlag: FLAGS.rang_tower },
                    text: 'Верёвка в ладони. Колокол качнулся — низкий тон ушёл в землю. Кто-то услышит.',
                    setFlags: [FLAGS.rang_tower],
                    sound: 'sfx/bell_low'
                },
                {
                    text: 'Колокол качнулся снова. Звон тот же: пол-тона ниже, чем в звонной книге.',
                    sound: 'sfx/bell_low'
                }
            ]
        }
    ],
    house_elder: [
        {
            id: 'chest_elder',
            pos: { x: 3.5, y: 3.5 },
            kind: 'container',
            view: { tex: 'tiles/chest' },
            label: 'Сундук',
            once: true,
            responses: [
                {
                    text: 'В сундуке — запас вощёного полотна.',
                    gives: 'cloth',
                    sound: 'sfx/chest_open'
                }
            ]
        },
        {
            id: 'hearth_elder',
            pos: { x: 8.5, y: 0.5 },
            kind: 'examine',
            label: 'Очаг',
            responses: [
                {
                    when: { flag: FLAGS.quest_bells_done },
                    text: 'Очаг горит ровнее. Пепел отступает — и тепло возвращается.',
                    sound: 'sfx/bell_low'
                },
                { text: 'В очаге тлеют щепки. Тепло — редкость теперь.', sound: 'sfx/ash_hiss' }
            ]
        },
        {
            id: 'note_elder',
            pos: { x: 10.5, y: 4.5 },
            kind: 'examine',
            label: 'Записка',
            once: true,
            responses: [
                {
                    text: '«Тоны уходят вниз. Если колокол смолкнет — не буди Молчание».',
                    setFlags: [FLAGS.read_note],
                    sound: 'sfx/note'
                }
            ]
        }
    ],
    shop: [
        {
            id: 'counter_shop',
            pos: { x: 5.5, y: 3.5 },
            kind: 'examine',
            label: 'Прилавок',
            responses: [
                {
                    when: { flag: FLAGS.quest_bells_done },
                    text: 'На прилавке — соль и цветы в плошке. Поляна гудит — Мила звонит чаще.',
                    sound: 'sfx/bell_hit'
                },
                {
                    text: 'Соль, вощёное полотно, вода в бутлях. Цен Мила не пишет — звонит.',
                    sound: 'sfx/bell_hit'
                }
            ]
        }
    ],
    // --- зал Машины (финал акта 2): усыпить её правильным звоном ---
    machine_hall: [
        {
            id: 'old_machine',
            pos: { x: 6.5, y: 1.5 },
            kind: 'lever',
            label: 'Старая Машина',
            responses: [
                {
                    when: { flag: FLAGS.machine_lulled },
                    text: 'Машина спит. Угольки в устье погасли — впервые за годы.',
                    sound: 'sfx/bell_low'
                },
                {
                    when: { notFlag: FLAGS.quest_resonator_done },
                    text: 'Устье дышит жаром сквозь рёбра. Резонатор не настроен — Машине не спеть.',
                    sound: 'sfx/ash_hiss'
                },
                {
                    // Правильный звон: настроенный резонатор + верный тон.
                    text: 'Резонатор ловит дыхание. Пол-тона вниз — и Машина отвечает сном.',
                    setFlags: [FLAGS.machine_lulled],
                    custom: 'lull_machine',
                    sound: 'sfx/bell_low'
                }
            ]
        }
    ]
};
/** Тайл привязки вычисляется из pos при загрузке модуля. */
export const INTERACTABLES: Partial<Record<AreaId, InteractableDef[]>> = Object.fromEntries(
    Object.entries(INTERACTABLES_RAW).map(([area, defs]) => [
        area,
        defs.map((d) => ({ ...d, tile: { x: Math.floor(d.pos.x), y: Math.floor(d.pos.y) } }))
    ])
);
