import { Container, Graphics } from 'pixi.js';
import { PixelText } from './PixelText';
import { ListCursor } from './listCursor';
import { revealText } from './reveal';

/**
 * Универсальное окно диалога: имя говорящего, текст, варианты ответа, подсказка «далее».
 * Жанронезависимо: игра сама решает, чей это диалог и что идёт дальше.
 * Подходит как view для DialogueRunner.
 *
 * v2: typewriter (чистый revealText), цвета настроений, клавиатурный курсор
 * выбора (ListCursor), высота панели растёт от контента вверх.
 * В update обязательно звать update(dt), если задан typewriter.
 */
export interface DialogueLine {
    speaker: string;
    text: string;
}

export interface DialogueChoiceItem {
    index: number;
    text: string;
}

export interface DialogueBoxOptions {
    /** Виртуальные пиксели. */
    width: number;
    height: number;
    /** Отступы панели от краёв экрана. */
    margin: number;
    /** Выбор варианта ответа (из DialogueRunner.pick). */
    onChoice?: (index: number) => void;
    /** Размер шрифта (виртуальные пиксели). */
    size?: number;
    /** Скорость печати, символов в секунду; не задана — текст сразу. */
    typewriter?: number;
    /** mood реплики → цвет текста (по умолчанию белый). */
    moodColors?: Record<string, number>;
}

const COLOR_BODY = 0xffffff;
const COLOR_CHOICE = 0xcccccc;
const COLOR_CHOICE_ACTIVE = 0xf0d878;

export class DialogueBox {
    readonly view: Container;
    visible = false;

    private panel: Graphics;
    private nameText: PixelText;
    private bodyText: PixelText;
    private hintText: PixelText;
    private choiceViews: { item: DialogueChoiceItem; label: PixelText }[] = [];
    private onChoice: ((index: number) => void) | null;
    private readonly width: number;
    private readonly height: number;
    private readonly margin: number;
    private readonly size: number;
    private readonly cps: number;
    private readonly moodColors: Record<string, number>;

    // typewriter
    private fullText = '';
    private revealElapsed = 0;
    private revealingNow = false;
    // клавиатурный курсор по вариантам
    private cursor: ListCursor = new ListCursor(0);

    constructor(options: DialogueBoxOptions) {
        this.width = options.width;
        this.height = options.height;
        this.margin = options.margin;
        // 8–9px у VT323 нечитаемы (тонкие штрихи размазываются) — минимум 10.
        this.size = Math.max(options.size ?? 10, 10);
        this.onChoice = options.onChoice ?? null;
        this.cps = options.typewriter ?? 0;
        this.moodColors = options.moodColors ?? {};

        this.view = new Container();
        this.view.visible = false;
        this.view.eventMode = 'static';

        this.panel = new Graphics();
        // Имя — с переносом: длинное имя говорящего не вылезет за панель.
        this.nameText = new PixelText({
            text: '',
            size: this.size,
            color: 0xf0d878,
            wordWrapWidth: options.width - options.margin * 4 - 10
        });
        this.bodyText = new PixelText({
            text: '',
            size: this.size,
            color: COLOR_BODY,
            wordWrapWidth: options.width - options.margin * 4,
            lineHeight: this.size + 3
        });
        this.hintText = new PixelText({ text: 'далее ▸', size: this.size, color: 0xaaaaaa });

        this.view.addChild(this.panel, this.nameText, this.bodyText, this.hintText);
        this.relayout();
    }

    /**
     * Показать реплику. Если есть choices — рисуются варианты с курсором
     * (клик и moveCursor/activate). mood подкрашивает текст, typewriter
     * печатает текст постепенно (обновлять update(dt)).
     */
    show(line: {
        speaker?: string;
        text: string;
        mood?: string;
        choices?: DialogueChoiceItem[];
    }): void {
        this.nameText.text = line.speaker ?? '';
        this.fullText = line.text;
        this.bodyText.style.fill = (line.mood && this.moodColors[line.mood]) || COLOR_BODY;
        this.view.visible = true;
        this.visible = true;

        this.rebuildChoices(line.choices ?? []);

        this.cursor = new ListCursor(this.choiceViews.length);
        this.refreshChoiceColors();

        // печать с начала; без typewriter — весь текст сразу
        this.revealElapsed = 0;
        this.revealingNow = this.cps > 0 && this.fullText.length > 0;
        // Панель сразу полной высоты: измеряем полный текст и строим панель,
        // только потом прячем текст в печать — иначе relayout видит пустую
        // строку, панель схлопывается и текст печатается поверх мира.
        this.bodyText.text = this.fullText;
        this.relayout();
        if (this.revealingNow) this.bodyText.text = '';
    }

    /** Печать и анимации; звать из update сцены при typewriter (dt в секундах). */
    update(dt: number): void {
        if (!this.revealingNow) return;
        this.revealElapsed += dt * 1000;
        const r = revealText(this.fullText, this.revealElapsed, this.cps);
        this.bodyText.text = this.fullText.slice(0, r.shown);
        if (r.done) {
            this.revealingNow = false;
            this.relayout(); // панель подросла — подсказка встанет под текст
        }
    }

    /** Идёт ли печать текста. */
    get revealing(): boolean {
        return this.revealingNow;
    }

    /** Показать текст целиком (пропуск печати). */
    skipReveal(): void {
        if (!this.revealingNow) return;
        this.revealingNow = false;
        this.bodyText.text = this.fullText;
        this.relayout();
    }

    /** Листать выбор курсором (up/down); без вариантов — no-op. */
    moveCursor(delta: number): void {
        if (this.choiceViews.length === 0) return;
        this.cursor.move(delta);
        this.refreshChoiceColors();
    }

    /** Выбрать вариант под курсором (Enter); без вариантов — no-op. */
    activate(): void {
        if (this.choiceViews.length === 0 || this.cursor.index < 0) return;
        const current = this.choiceViews[this.cursor.index];
        if (current) this.onChoice?.(current.item.index);
    }

    /** Индекс варианта под курсором (-1 — выбора нет). */
    get cursorIndex(): number {
        return this.choiceViews.length > 0 ? this.cursor.index : -1;
    }

    hide(): void {
        this.view.visible = false;
        this.visible = false;
        for (const c of this.choiceViews) c.label.destroy();
        this.choiceViews = [];
        this.revealingNow = false;
        this.fullText = '';
    }

    // --- внутреннее ---

    private rebuildChoices(choices: DialogueChoiceItem[]): void {
        for (const c of this.choiceViews) c.label.destroy();
        this.choiceViews = [];
        for (const item of choices) {
            const label = new PixelText({
                text: `▸ ${item.text}`,
                size: this.size,
                color: COLOR_CHOICE,
                wordWrapWidth: this.width - this.margin * 4
            });
            label.eventMode = 'static';
            label.cursor = 'pointer';
            label.on('pointerover', () => {
                const i = this.choiceViews.findIndex((c) => c.label === label);
                if (i >= 0) {
                    this.cursor.index = i;
                    this.refreshChoiceColors();
                }
            });
            label.on('pointerout', () => this.refreshChoiceColors());
            label.on('pointertap', () => this.onChoice?.(item.index));
            this.view.addChild(label);
            this.choiceViews.push({ item, label });
        }
    }

    private refreshChoiceColors(): void {
        this.choiceViews.forEach((c, i) => {
            c.label.style.fill = i === this.cursor.index ? COLOR_CHOICE_ACTIVE : COLOR_CHOICE;
        });
    }

    /** Панель от контента: растёт вверх от нижнего края. */
    private relayout(): void {
        const w = this.width;
        const h = this.height;
        const m = this.margin;
        // Место под «далее ▸» резервируется всегда (без выбора) — панель не
        // прыгает в конце печати; сам текст виден только после печати.
        const withHint = this.choiceViews.length === 0;
        this.hintText.visible = withHint && !this.revealingNow;

        this.panel.clear();
        const pad = 6;
        const nameH = this.nameText.text ? this.nameText.height + 2 : 0;
        const bodyH = this.fullText || this.bodyText.text ? this.bodyText.height : 0;
        let choicesH = 0;
        for (const c of this.choiceViews) choicesH += c.label.height + 3;
        const hintH = withHint ? this.hintText.height + 2 : 0;
        const panelH = Math.round(pad * 2 + nameH + bodyH + choicesH + hintH);
        const top = h - m - panelH;

        this.panel.rect(m, top, w - m * 2, panelH).fill({ color: 0x101018, alpha: 0.92 });
        this.panel.rect(m, top, w - m * 2, panelH).stroke({ color: 0x8899aa, width: 1 });

        let y = top + pad;
        if (nameH > 0) {
            this.nameText.position.set(m + 5, y);
            y += nameH;
        }
        this.bodyText.position.set(m + 5, y);
        y += bodyH;
        for (const c of this.choiceViews) {
            c.label.position.set(m + 5, y);
            y += c.label.height + 3;
        }
        if (withHint) {
            this.hintText.position.set(w - m - 5 - this.hintText.width, y + 2);
        }
    }
}