import { GameState } from '../core/GameState';

/**
 * Модель диалоговых графов и чистые предикаты над ней (без рантайма).
 * Граф — данные (TS/JSON): узлы с репликами, выборами и условиями по GameState.
 * Рантайм обхода графа — DialogueRunner; валидатор/dry-run/квесты пользуются
 * типами и evalConditions отсюда.
 */

/** Условие по переменной состояния. */
export interface VarCondition {
    key: string;
    op: 'eq' | 'ne' | 'gt' | 'lt' | 'ge' | 'le';
    value: number | string;
}

/** Действия, применяемые к GameState при входе в узел/выборе. */
export interface DialogueEffects {
    setFlags?: string[];
    clearFlags?: string[];
    setVars?: Record<string, number | string | boolean>;
    /**
     * Игровые эффекты как данные: движок только эмитит их через onEffect,
     * исполнение — на стороне игры (EffectSink). Неизвестный kind — ошибка
     * валидатора, рантайм его игнорирует.
     */
    do?: DialogueEffectOp[];
}

/** Одна игровая операция эффекта (что именно она значит — знает игра). */
export interface DialogueEffectOp {
    kind: 'giveItem' | 'takeItem' | 'sound' | 'toast' | 'custom';
    /** id предмета (give/take), звука (sound) или имя сюжетного эффекта (custom). */
    id?: string;
    /** Количество (give/take), по умолчанию 1. */
    count?: number;
    /** Текст всплывашки (toast). */
    text?: string;
    /** Свободные параметры (custom). */
    payload?: Record<string, number | string | boolean>;
}

/** Итог пройденного диалога: где остановились, как дошли, что выбрали. */
export interface DialogueResult {
    /** id последнего показанного узла (null — ни одного). */
    lastNodeId: string | null;
    /** ids узлов по порядку показа. */
    path: string[];
    /** Выборы игрока: узел, индекс в узле, текст варианта. */
    picks: { nodeId: string; index: number; text: string }[];
}

/** Хуки мира: предикаты условий и резолв ключей строк (локализация). */
export interface DialogueHooks {
    world?: DialogueWorld;
    /** Ключ строки → текст (textKey); нет резолва — ключ и есть текст. */
    resolve?: (key: string) => string;
}

/**
 * Предикаты мира, которых движок знать не может (сумка, репутация...).
 * Игра передаёт реализацию в раннер; без неё hasItem-условия ложны.
 */
export interface DialogueWorld {
    hasItem(id: string): boolean;
}

/** Условия показа узла/варианта (все перечисленные группы — AND). */
export interface DialogueConditions {
    /** Все эти флаги должны быть установлены. */
    when?: string[];
    /** Ни один из этих флагов не должен быть установлен. */
    whenNot?: string[];
    /** Условие по переменной. */
    whenVar?: VarCondition;
    /** Несколько условий по переменным, AND. */
    whenVars?: VarCondition[];
    /** Все эти предметы должны быть в сумке (резолв — DialogueWorld). */
    hasItem?: string[];
}

export interface DialogueChoice extends DialogueEffects, DialogueConditions {
    text: string;
    /** Следующий узел (по умолчанию — конец диалога). */
    next?: string;
    /** Настроение реплики (игра мапит на цвет/портрет). */
    mood?: string;
    /** Свободные метки для агента/инструментов (на геймплей не влияют). */
    tags?: string[];
    /** Ключ строки в таблице локализации; переопределяет inline text. */
    textKey?: string;
}

export interface DialogueNode extends DialogueEffects, DialogueConditions {
    /** Имя говорящего (опционально). */
    speaker?: string;
    /** Ключ имени говорящего (локализация, как textKey). */
    speakerKey?: string;
    /** Текст реплики. Узел без текста — «действие»: применяет эффекты и уходит в next. */
    text?: string;
    /** Варианты ответа игрока. */
    choices?: DialogueChoice[];
    /** Следующий узел. */
    next?: string;
    /** Явный конец диалога (для конечных узлов без choices/next). */
    end?: boolean;
    /** Настроение реплики (игра мапит на цвет/портрет). */
    mood?: string;
    /** Свободные метки для агента/инструментов (на геймплей не влияют). */
    tags?: string[];
    /** Ключ строки в таблице локализации; переопределяет inline text. */
    textKey?: string;
}

export interface DialogueGraph {
    /** id стартового узла. */
    start: string;
    nodes: Record<string, DialogueNode>;
}

/** Есть ли в условиях хоть что-то для проверки. */
export function hasConditions(c: DialogueConditions): boolean {
    return Boolean(
        c.when?.length || c.whenNot?.length || c.whenVar || c.whenVars?.length || c.hasItem?.length
    );
}

/** Узел-реплика: есть inline text или ключ строки. */
export function hasText(n: { text?: string; textKey?: string }): boolean {
    return n.text !== undefined || n.textKey !== undefined;
}

/** Сравнение переменной по op; undefined (переменной нет) не проходит gt/lt/ge/le. */
function checkVar(state: GameState, cond: VarCondition): boolean {
    const v = state.getVar(cond.key);
    switch (cond.op) {
        case 'eq':
            return v === cond.value;
        case 'ne':
            return v !== cond.value;
        case 'gt':
            return Number(v) > Number(cond.value);
        case 'lt':
            return Number(v) < Number(cond.value);
        case 'ge':
            return Number(v) >= Number(cond.value);
        case 'le':
            return Number(v) <= Number(cond.value);
    }
}

/**
 * Чистая проверка условий узла/выбора без рантайма: удобна для квестовых
 * стадий, dry-run и валидатора. world не задан → hasItem ложен.
 */
export function evalConditions(c: DialogueConditions, state: GameState, world?: DialogueWorld): boolean {
    for (const f of c.when ?? []) {
        if (!state.hasFlag(f)) return false;
    }
    for (const f of c.whenNot ?? []) {
        if (state.hasFlag(f)) return false;
    }
    if (c.whenVar && !checkVar(state, c.whenVar)) return false;
    for (const cond of c.whenVars ?? []) {
        if (!checkVar(state, cond)) return false;
    }
    if (c.hasItem) {
        if (!world) return false;
        for (const id of c.hasItem) {
            if (!world.hasItem(id)) return false;
        }
    }
    return true;
}