/**
 * FloaterManager — всплывающие цифры урона (Damage Floaters), задача 40.
 *
 * game.fx.damageFloater(position, value, opts) → над точкой всплывает число,
 * поднимается вверх, покачивается, плавно гаснет. Цвета: damage/crit/heal/
 * mana/miss. Object pool из переиспользуемых billboard-планов (без create/
 * destroy на каждый удар). Стек одинаковых по stackKey («×N»). Комикс-стиль
 * (BAM!/KAPOW!/POW!).
 *
 * Билборд = плоскость с DynamicTexture (как LabelManager), billboardMode=7,
 * renderingGroupId=1 (всегда поверх геометрии), disableDepthWrite.
 *
 * Фича-парность: тот же модуль в rublox-player/src/engine/.
 */
import { DynamicTexture } from '@babylonjs/core/Materials/Textures/dynamicTexture';
import { StandardMaterial } from '@babylonjs/core/Materials/standardMaterial';
import { MeshBuilder } from '@babylonjs/core/Meshes/meshBuilder';
import { Color3 } from '@babylonjs/core/Maths/math.color';
import { Mesh } from '@babylonjs/core/Meshes/mesh';

const POOL_SIZE = 30;
const TEX_W = 512, TEX_H = 256;

// Пресеты типов урона: цвет текста + множители.
const PRESETS = {
    damage: { color: '#ff5a4a', stroke: '#3a0000' },
    crit:   { color: '#ffd23a', stroke: '#5a3a00' },
    heal:   { color: '#46e06a', stroke: '#063a14' },
    mana:   { color: '#4aa8ff', stroke: '#001a3a' },
    miss:   { color: '#b8b8b8', stroke: '#222222' },
};

function easeOutQuad(t) { return 1 - (1 - t) * (1 - t); }

export class FloaterManager {
    constructor(scene3d) {
        this.s = scene3d;
        this.scene = scene3d.scene;
        this.pool = [];
        this._initialized = false;
        this._stacks = new Map();   // stackKey → slot (для накопления ×N)
    }

    _init() {
        if (this._initialized) return;
        this._initialized = true;
        for (let i = 0; i < POOL_SIZE; i++) {
            const tex = new DynamicTexture(`floaterTex_${i}`, { width: TEX_W, height: TEX_H }, this.scene, true);
            tex.hasAlpha = true;
            const plane = MeshBuilder.CreatePlane(`floater_${i}`, { width: 2.4, height: 1.2, sideOrientation: Mesh.DOUBLESIDE }, this.scene);
            const mat = new StandardMaterial(`floaterMat_${i}`, this.scene);
            mat.diffuseTexture = tex;
            mat.diffuseTexture.hasAlpha = true;
            mat.emissiveColor = new Color3(1, 1, 1);
            mat.diffuseColor = new Color3(0, 0, 0);
            mat.disableLighting = true;
            mat.backFaceCulling = false;
            mat.disableDepthWrite = true;
            mat.useAlphaFromDiffuseTexture = true;
            plane.material = mat;
            plane.billboardMode = 7;
            plane.renderingGroupId = 1;
            plane.isPickable = false;
            plane.setEnabled(false);
            this.pool.push({ plane, tex, mat, active: false, age: 0, lifetime: 0.8 });
        }
    }

    _acquire() {
        for (const slot of this.pool) if (!slot.active) return slot;
        return null;   // все заняты — пропускаем новый floater (норма)
    }

    /**
     * Главный API. position: {x,y,z}; value: число|строка; opts — см. задачу 40.
     */
    spawn(position, value, opts = {}) {
        this._init();
        if (!position) return;
        opts = opts || {};

        // Стек: одинаковый stackKey за время жизни накапливает счётчик.
        if (opts.stackKey && this._stacks.has(opts.stackKey)) {
            const slot = this._stacks.get(opts.stackKey);
            if (slot.active) {
                slot.stackCount = (slot.stackCount || 1) + 1;
                slot.age = Math.min(slot.age, slot.lifetime * 0.3);   // продлеваем
                this._draw(slot, slot.baseText, slot.preset, slot.fontSize, slot.comic, slot.stackCount);
                return;
            }
        }

        const slot = this._acquire();
        if (!slot) return;

        // Тип floater'а.
        let kind = 'damage';
        if (opts.isCrit) kind = 'crit';
        else if (opts.isHeal) kind = 'heal';
        else if (opts.isMana) kind = 'mana';
        else if (opts.isMiss) kind = 'miss';
        const preset = PRESETS[kind];
        const color = opts.color || preset.color;
        const stroke = opts.strokeColor || preset.stroke;

        let fontSize = Number.isFinite(opts.fontSize) ? opts.fontSize : 60;
        let floatHeight = Number.isFinite(opts.floatHeight) ? opts.floatHeight : 2;
        let lifetime = Number.isFinite(opts.lifetime) ? opts.lifetime : 0.9;
        const randomOffset = Number.isFinite(opts.randomOffset) ? opts.randomOffset : (opts.isCrit ? 0.5 : 0.25);

        // Текст: число с минусом (урон) или как есть (строка / heal с плюсом).
        let baseText;
        if (typeof value === 'string') baseText = value;
        else if (opts.isHeal) baseText = '+' + value;
        else if (opts.isMiss) baseText = String(value);
        else baseText = '-' + Math.abs(value);

        if (opts.isCrit) { fontSize = Math.round(fontSize * 1.4); floatHeight *= 1.2; }

        slot.active = true;
        slot.age = 0;
        slot.lifetime = lifetime;
        slot.floatHeight = floatHeight;
        slot.isCrit = !!opts.isCrit;
        slot.color = color; slot.stroke = stroke;
        slot.preset = { color, stroke };
        slot.fontSize = fontSize;
        slot.comic = !!opts.comicStyle;
        slot.baseText = baseText;
        slot.stackCount = 1;
        slot.stackKey = opts.stackKey || null;

        const rx = (Math.random() - 0.5) * 2 * randomOffset;
        const rz = (Math.random() - 0.5) * 2 * randomOffset;
        slot.startX = position.x + rx;
        slot.startY = position.y + (Number.isFinite(opts.yOffset) ? opts.yOffset : 1.5);
        slot.startZ = position.z + rz;
        slot.plane.position.set(slot.startX, slot.startY, slot.startZ);
        slot.plane.scaling.set(1, 1, 1);
        slot.plane.setEnabled(true);

        this._draw(slot, baseText, slot.preset, fontSize, slot.comic, 1);

        if (opts.stackKey) this._stacks.set(opts.stackKey, slot);
    }

    _draw(slot, baseText, preset, fontSize, comic, stackCount) {
        const ctx = slot.tex.getContext();
        ctx.clearRect(0, 0, TEX_W, TEX_H);

        let text = baseText;
        if (comic) {
            const num = parseInt(String(baseText).replace(/[^0-9]/g, ''), 10) || 0;
            if (slot.isCrit) text = 'POW!';
            else if (num > 100) text = 'KAPOW!';
            else if (num > 50) text = 'BAM!';
        }
        if (stackCount > 1) text = baseText + '  ×' + stackCount;

        const fs = comic ? Math.round(fontSize * 1.1) : fontSize;
        ctx.font = `900 ${fs}px ${comic ? 'Bangers, Impact, sans-serif' : 'Inter, Arial, sans-serif'}`;
        ctx.textAlign = 'center';
        ctx.textBaseline = 'middle';
        ctx.lineJoin = 'round';

        // Комикс-фон: жёлтая звезда-вспышка.
        if (comic) {
            ctx.save();
            ctx.translate(TEX_W / 2, TEX_H / 2);
            ctx.fillStyle = 'rgba(255,210,60,0.9)';
            ctx.beginPath();
            const spikes = 10, outer = 130, inner = 70;
            for (let i = 0; i < spikes * 2; i++) {
                const r = i % 2 === 0 ? outer : inner;
                const a = (i / (spikes * 2)) * Math.PI * 2 - Math.PI / 2;
                const px = Math.cos(a) * r, py = Math.sin(a) * r * 0.55;
                i === 0 ? ctx.moveTo(px, py) : ctx.lineTo(px, py);
            }
            ctx.closePath(); ctx.fill();
            ctx.restore();
        }

        // Обводка + текст.
        ctx.strokeStyle = comic ? '#000' : preset.stroke;
        ctx.lineWidth = Math.max(6, fs * 0.16);
        ctx.strokeText(text, TEX_W / 2, TEX_H / 2);
        ctx.fillStyle = comic ? '#d22' : preset.color;
        ctx.fillText(text, TEX_W / 2, TEX_H / 2);

        slot.tex.update(true);
    }

    /** Вызывать каждый кадр (анимация подъёма + fade + покачивание + crit-pop). */
    tick(dt) {
        if (!this._initialized) return;
        for (const slot of this.pool) {
            if (!slot.active) continue;
            slot.age += dt;
            const t = slot.age / slot.lifetime;
            if (t >= 1) {
                slot.active = false;
                slot.plane.setEnabled(false);
                if (slot.stackKey && this._stacks.get(slot.stackKey) === slot) this._stacks.delete(slot.stackKey);
                continue;
            }
            const ease = easeOutQuad(t);
            slot.plane.position.y = slot.startY + slot.floatHeight * ease;
            slot.plane.position.x = slot.startX + Math.sin(slot.age * 5) * 0.12;

            // fade-in 0.12 / hold / fade-out 0.25
            let alpha = 1;
            if (t < 0.12) alpha = t / 0.12;
            else if (t > 0.75) alpha = 1 - (t - 0.75) / 0.25;
            slot.mat.alpha = Math.max(0, Math.min(1, alpha));

            // crit pop: scale 1 → 1.3 → 1 в первые 0.4 жизни
            if (slot.isCrit) {
                let s = 1;
                if (t < 0.2) s = 1 + (t / 0.2) * 0.3;
                else if (t < 0.4) s = 1.3 - ((t - 0.2) / 0.2) * 0.3;
                slot.plane.scaling.set(s, s, s);
            }
        }
    }

    dispose() {
        for (const slot of this.pool) {
            try { slot.plane.dispose(); slot.tex.dispose(); slot.mat.dispose(); } catch (e) {}
        }
        this.pool = []; this._stacks.clear(); this._initialized = false;
    }
    resetRuntime() {
        for (const slot of this.pool) { slot.active = false; slot.plane?.setEnabled(false); }
        this._stacks.clear();
    }
}
