/**
 * LeaderstatsManager — лидерборды (leaderstats) как в Roblox (задача 20).
 *
 * Хранит статы игроков и рендерит HUD-таблицу в правом-верхнем углу.
 * В одиночной игре — один игрок ('me'). Поля сортируются по primary-стату.
 *
 * API (через game.leaderstats.*):
 *   define(name, opts)                 — зарегистрировать стат (initial/format/icon/color/primary)
 *   set(playerId, name, value) / add   — изменить стат игрока
 *   get(playerId, name)                — прочитать
 *   me.set/add(name, value)            — для текущего игрока
 *   onChange(fn)                       — подписка (для bindToStat достижений)
 *
 * format: 'number' (42) | 'time' (mm:ss) | 'short' (1.2K).
 * DOM-оверлей крепится к canvas.parentElement (как LoadingScreenOverlay).
 *
 * Фича-парность: тот же модуль в rublox-player/src/engine/.
 */

function fmt(value, format) {
    const v = Number(value) || 0;
    if (format === 'time') {
        const m = Math.floor(v / 60), s = Math.floor(v % 60);
        return (m < 10 ? '0' : '') + m + ':' + (s < 10 ? '0' : '') + s;
    }
    if (format === 'short') {
        if (v >= 1e9) return (v / 1e9).toFixed(1).replace(/\.0$/, '') + 'B';
        if (v >= 1e6) return (v / 1e6).toFixed(1).replace(/\.0$/, '') + 'M';
        if (v >= 1e3) return (v / 1e3).toFixed(1).replace(/\.0$/, '') + 'K';
        return String(Math.round(v));
    }
    return String(Math.round(v));
}

export class LeaderstatsManager {
    constructor(scene3d) {
        this.s = scene3d;
        this._defs = [];                 // [{name, initial, format, icon, color, primary}]
        this._stats = new Map();         // playerId → Map(name → value)
        this._players = new Map();       // playerId → displayName
        this._onChange = [];
        this.root = null;
        this._dirty = false;
        this._meId = 'me';
    }

    /** id текущего игрока (одиночка = 'me'). */
    _resolveMe() {
        try {
            const p = this.s?.gameRuntime?._players?.me;
            if (p && p.id != null) return String(p.id);
        } catch (e) { /* ignore */ }
        return 'me';
    }

    define(name, opts = {}) {
        if (typeof name !== 'string' || !name) return;
        if (this._defs.some(d => d.name === name)) return;   // уже есть
        this._defs.push({
            name,
            initial: Number(opts.initial) || 0,
            format: opts.format || 'number',
            icon: opts.icon || '',
            color: opts.color || '#e8ecf2',
            primary: !!opts.primary,
        });
        // Если ни один не primary — первый становится primary.
        if (!this._defs.some(d => d.primary)) this._defs[0].primary = true;
        // Инициализируем стат у уже известных игроков.
        for (const [pid] of this._players) this._ensure(pid, name);
        this._ensureMe();
        if (this.s?._isPlaying) this._mount();   // HUD только в Play
        this._dirty = true;
    }

    _ensureMe() {
        const me = this._resolveMe();
        this._meId = me;
        if (!this._players.has(me)) {
            let nm = 'Ты';
            try { nm = this.s?.gameRuntime?._players?.me?.name || 'Ты'; } catch (e) {}
            this._players.set(me, nm);
        }
        for (const d of this._defs) this._ensure(me, d.name);
    }

    _ensure(pid, name) {
        if (!this._stats.has(pid)) this._stats.set(pid, new Map());
        const m = this._stats.get(pid);
        if (!m.has(name)) {
            const def = this._defs.find(d => d.name === name);
            m.set(name, def ? def.initial : 0);
        }
    }

    set(playerId, name, value) {
        const pid = playerId == null ? this._resolveMe() : String(playerId);
        if (!this._players.has(pid)) this._players.set(pid, pid === this._resolveMe() ? 'Ты' : ('Игрок ' + pid));
        this._ensure(pid, name);
        const m = this._stats.get(pid);
        const old = m.get(name);
        const nv = Number(value) || 0;
        if (old === nv) return;
        m.set(name, nv);
        this._dirty = true;
        this._flash = this._flash || {};
        this._flash[pid + '|' + name] = performance.now ? performance.now() : Date.now();
        for (const fn of this._onChange) {
            try { fn(pid, name, nv, old); } catch (e) { /* ignore */ }
        }
        // Сохраняем статы текущего игрока в БД (дебаунс 1с) — между сессиями.
        if (pid === this._resolveMe()) this._scheduleSave();
    }

    _scheduleSave() {
        if (this._saveTimer) clearTimeout(this._saveTimer);
        this._saveTimer = setTimeout(() => {
            this._saveTimer = null;
            try {
                const me = this._resolveMe();
                const m = this._stats.get(me);
                if (!m) return;
                const obj = {}; for (const [k, v] of m) obj[k] = v;
                this.s?.gameRuntime?.saveProgress?.('_leaderstats', obj);
            } catch (e) { /* ignore */ }
        }, 1000);
    }

    /** Загрузить статы текущего игрока из БД (вызывать при Play, после define). */
    loadFromDB() {
        const rt = this.s?.gameRuntime;
        if (!rt || !rt.loadProgress) return;
        rt.loadProgress('_leaderstats', (data) => {
            if (data && typeof data === 'object') {
                const me = this._resolveMe();
                for (const name of Object.keys(data)) {
                    // Применяем только к зарегистрированным статам, без повторного сейва.
                    if (this._defs.some(d => d.name === name)) {
                        this._ensure(me, name);
                        this._stats.get(me).set(name, Number(data[name]) || 0);
                    }
                }
                this._dirty = true;
            }
        });
    }

    add(playerId, name, delta) {
        const pid = playerId == null ? this._resolveMe() : String(playerId);
        this._ensure(pid, name);
        const cur = this._stats.get(pid).get(name) || 0;
        this.set(pid, name, cur + (Number(delta) || 0));
    }

    get(playerId, name) {
        const pid = playerId == null ? this._resolveMe() : String(playerId);
        const m = this._stats.get(pid);
        return m ? (m.get(name) || 0) : 0;
    }

    onChange(fn) { if (typeof fn === 'function') this._onChange.push(fn); }

    /** Активны ли leaderstats (хотя бы один define). */
    get active() { return this._defs.length > 0; }

    // ── HUD ──────────────────────────────────────────────────────────────
    _mount() {
        if (this.root) return;
        const parent = (this.s && this.s.canvas && this.s.canvas.parentElement) || document.body;
        const root = document.createElement('div');
        root.style.cssText = [
            'position:absolute', 'top:14px', 'right:14px', 'z-index:50',
            'min-width:230px', 'max-width:300px',
            'background:rgba(18,22,33,0.55)', 'backdrop-filter:blur(8px)',
            '-webkit-backdrop-filter:blur(8px)',
            'border:1px solid rgba(255,255,255,0.12)', 'border-radius:12px',
            'padding:10px 12px', 'font-family:Inter,system-ui,sans-serif',
            'color:#e8ecf2', 'pointer-events:none', 'user-select:none',
            'box-shadow:0 6px 24px rgba(0,0,0,0.35)',
        ].join(';');
        parent.appendChild(root);
        this.root = root;
        this._sortBy = null;   // имя стата для сортировки (null = primary)
    }

    /** Вызывать каждый кадр (рендер при изменениях + затухание flash). */
    tick() {
        if (!this.active) return;
        if (!this.root) { this._mount(); this._dirty = true; }
        if (this._dirty) { this._render(); this._dirty = false; }
        // flash затухает ~600мс — перерисуем пока активен.
        if (this._flash && Object.keys(this._flash).length) {
            const now = performance.now ? performance.now() : Date.now();
            let any = false;
            for (const k of Object.keys(this._flash)) {
                if (now - this._flash[k] < 600) any = true; else delete this._flash[k];
            }
            if (any) this._render();
        }
    }

    _render() {
        const defs = this._defs;
        if (!defs.length) { this.root.innerHTML = ''; return; }
        const sortStat = this._sortBy || (defs.find(d => d.primary) || defs[0]).name;
        const me = this._resolveMe();
        // Строки игроков, сортировка по убыванию sortStat, топ-10.
        const rows = [...this._players.keys()]
            .map(pid => ({ pid, name: this._players.get(pid) }))
            .sort((a, b) => (this.get(b.pid, sortStat) - this.get(a.pid, sortStat)))
            .slice(0, 10);
        const now = performance.now ? performance.now() : Date.now();

        let html = '<div style="display:flex;align-items:center;gap:6px;font-weight:800;font-size:13px;margin-bottom:8px;color:#ffd23a">🏆 Таблица лидеров</div>';
        // Шапка столбцов.
        html += '<div style="display:grid;grid-template-columns:1fr ' + defs.map(() => 'auto').join(' ') + ';gap:8px;font-size:11px;color:#9aa3b2;font-weight:700;padding-bottom:4px;border-bottom:1px solid rgba(255,255,255,0.1)">';
        html += '<span>Игрок</span>';
        for (const d of defs) html += '<span style="text-align:right;color:' + d.color + '">' + (d.icon ? d.icon + ' ' : '') + d.name + '</span>';
        html += '</div>';
        // Строки.
        for (const r of rows) {
            const mine = r.pid === me;
            html += '<div style="display:grid;grid-template-columns:1fr ' + defs.map(() => 'auto').join(' ') + ';gap:8px;font-size:13px;padding:4px 2px;border-radius:6px;' + (mine ? 'background:rgba(51,87,255,0.22);' : '') + '">';
            html += '<span style="overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-weight:' + (mine ? '800' : '600') + '">' + this._esc(r.name) + '</span>';
            for (const d of defs) {
                const flashed = this._flash && (now - (this._flash[r.pid + '|' + d.name] || 0) < 600);
                const col = flashed ? '#ffe066' : d.color;
                html += '<span style="text-align:right;font-weight:700;color:' + col + ';transition:color .2s">' + fmt(this.get(r.pid, d.name), d.format) + '</span>';
            }
            html += '</div>';
        }
        this.root.innerHTML = html;
    }

    _esc(s) { return String(s).replace(/[&<>]/g, c => ({ '&': '&amp;', '<': '&lt;', '>': '&gt;' }[c])); }

    /** Сериализация определений в project_data. */
    serialize() {
        return this._defs.map(d => ({ ...d }));
    }
    load(arr) {
        if (!Array.isArray(arr)) return;
        for (const d of arr) this.define(d.name, d);
    }

    dispose() {
        if (this.root) { try { this.root.remove(); } catch (e) {} this.root = null; }
        this._stats.clear(); this._players.clear(); this._onChange = [];
    }
    /** Сброс рантайм-значений при exitPlayMode (определения остаются). */
    resetRuntime() {
        this._stats.clear(); this._players.clear(); this._flash = {};
        if (this.root) this.root.innerHTML = '';
    }
}
