/**
 * BeamManager — лучи (Beam) и следы (Trail) как объекты сцены (Фаза 5.2).
 *
 * Beam  — светящаяся линия между двумя точками. Точки могут быть
 *         фиксированными координатами или ref объектов — тогда луч
 *         следует за объектами каждый кадр (лазеры, мосты света,
 *         соединения, цепи).
 * Trail — шлейф, тянущийся за движущимся объектом (Babylon TrailMesh).
 *
 * Живут только в Play-режиме. Управляются скриптом через game.fx.* —
 * каждый вызов возвращает прокси-объект.
 */

import {
    MeshBuilder, StandardMaterial, Color3, Vector3,
} from '@babylonjs/core';
import { TrailMesh } from '@babylonjs/core/Meshes/trailMesh';

let _fxIdSeq = 1;

export class BeamManager {
    constructor(scene3d) {
        this.scene3d = scene3d;
        this.scene = scene3d.scene;
        /** @type {Map<number, object>} id → fx state (beam | trail) */
        this.items = new Map();
        this._renderHook = null;
    }

    start() {
        if (this._renderHook) return;
        this._renderHook = () => this._tick();
        this.scene.registerBeforeRender(this._renderHook);
    }

    stop() {
        if (this._renderHook) {
            try { this.scene.unregisterBeforeRender(this._renderHook); } catch (e) {}
            this._renderHook = null;
        }
        for (const it of this.items.values()) this._disposeItem(it);
        this.items.clear();
    }

    _disposeItem(it) {
        try {
            if (it.mesh) it.mesh.dispose();
            if (it.mat) it.mat.dispose();
        } catch (e) { /* ignore */ }
    }

    /**
     * Создать луч между двумя точками.
     * opts: { from, to — {x,y,z} или ref-строка объекта;
     *         color: '#hex', width: толщина (м) }.
     * Возвращает id.
     */
    addBeam(opts = {}) {
        const id = _fxIdSeq++;
        const width = Number.isFinite(opts.width) ? opts.width : 0.15;
        const mat = new StandardMaterial('beamMat_' + id, this.scene);
        const col = Color3.FromHexString(opts.color || '#66ccff');
        mat.diffuseColor = col;
        mat.emissiveColor = col;
        mat.disableLighting = true;
        // Цилиндр-заготовка единичной высоты — масштабируем под длину луча.
        const mesh = MeshBuilder.CreateCylinder('beam_' + id,
            { height: 1, diameter: width, tessellation: 8 }, this.scene);
        mesh.material = mat;
        mesh.isPickable = false;
        mesh.renderingGroupId = 1;
        const it = {
            id, type: 'beam', mesh, mat,
            from: opts.from, to: opts.to,
        };
        this.items.set(id, it);
        this._updateBeam(it);   // сразу позиционируем
        return id;
    }

    /** Сменить цвет луча. */
    setBeamColor(id, color) {
        const it = this.items.get(Number(id));
        if (!it || it.type !== 'beam' || !it.mat) return;
        const col = Color3.FromHexString(color || '#66ccff');
        it.mat.diffuseColor = col;
        it.mat.emissiveColor = col;
    }

    /** Сменить концы луча (координаты или ref). */
    setBeamEndpoints(id, from, to) {
        const it = this.items.get(Number(id));
        if (!it || it.type !== 'beam') return;
        if (from !== undefined) it.from = from;
        if (to !== undefined) it.to = to;
    }

    /**
     * Создать шлейф за объектом.
     * ref — ref-строка объекта. opts: { color, width, lifetime (сек) }.
     * Возвращает id.
     */
    addTrail(ref, opts = {}) {
        const data = this._resolve(ref);
        const mesh = data && (data.mesh || data.rootMesh || data.rootNode);
        if (!mesh) return null;
        const id = _fxIdSeq++;
        const width = Number.isFinite(opts.width) ? opts.width : 0.4;
        const lifetime = Number.isFinite(opts.lifetime) ? opts.lifetime : 1.5;
        // TrailMesh(name, generator, scene, diameter, length, autoStart).
        // length — сколько сегментов хранить; считаем из lifetime (≈60 fps).
        const segments = Math.max(10, Math.round(lifetime * 60));
        const trail = new TrailMesh('trail_' + id, mesh, this.scene,
            width, segments, true);
        const mat = new StandardMaterial('trailMat_' + id, this.scene);
        const col = Color3.FromHexString(opts.color || '#ffcc44');
        mat.diffuseColor = col;
        mat.emissiveColor = col;
        mat.disableLighting = true;
        mat.alpha = 0.7;
        trail.material = mat;
        trail.isPickable = false;
        trail.renderingGroupId = 1;
        const it = { id, type: 'trail', mesh: trail, mat };
        this.items.set(id, it);
        return id;
    }

    /** Убрать луч/след по id. */
    remove(id) {
        const it = this.items.get(Number(id));
        if (!it) return;
        this._disposeItem(it);
        this.items.delete(it.id);
    }

    // ===== внутреннее =====

    _tick() {
        if (this.items.size === 0) return;
        for (const it of this.items.values()) {
            // Trail обновляется самим Babylon (autoStart). Beam — мы.
            if (it.type === 'beam') this._updateBeam(it);
        }
    }

    _updateBeam(it) {
        const a = this._point(it.from);
        const b = this._point(it.to);
        if (!a || !b || !it.mesh) return;
        const dx = b.x - a.x, dy = b.y - a.y, dz = b.z - a.z;
        const len = Math.hypot(dx, dy, dz);
        if (len < 0.001) { it.mesh.setEnabled(false); return; }
        it.mesh.setEnabled(true);
        // Цилиндр единичной высоты вдоль локальной оси Y. Растягиваем по длине.
        it.mesh.scaling.y = len;
        // Центр луча.
        it.mesh.position.set((a.x + b.x) / 2, (a.y + b.y) / 2, (a.z + b.z) / 2);
        // Ориентируем ось Y цилиндра вдоль вектора a→b.
        const dir = new Vector3(dx, dy, dz).normalize();
        // yaw + pitch так, чтобы +Y смотрел вдоль dir.
        const yaw = Math.atan2(dir.x, dir.z);
        const pitch = Math.acos(Math.max(-1, Math.min(1, dir.y)));
        it.mesh.rotation.set(pitch, yaw, 0);
    }

    /** Точка из {x,y,z} или ref-строки объекта. */
    _point(p) {
        if (!p) return null;
        if (typeof p === 'object' && Number.isFinite(p.x)) {
            return { x: p.x, y: p.y, z: p.z };
        }
        if (typeof p === 'string') {
            const d = this._resolve(p);
            if (d) return { x: d.x, y: d.y, z: d.z };
        }
        return null;
    }

    /** Резолв ref в data-объект менеджера. */
    _resolve(ref) {
        if (typeof ref !== 'string') return null;
        const rt = this.scene3d.gameRuntime;
        let r = ref;
        if (rt && rt._localToReal && rt._localToReal.has(r)) {
            r = rt._localToReal.get(r);
        }
        const colon = r.indexOf(':');
        if (colon < 0) return null;
        const kind = r.slice(0, colon);
        const rest = r.slice(colon + 1);
        const getFrom = (mgr) => {
            if (!mgr || !mgr.instances) return null;
            let d = mgr.instances.get(rest);
            if (!d) {
                const n = Number(rest);
                if (Number.isFinite(n)) d = mgr.instances.get(n);
            }
            return d || null;
        };
        if (kind === 'primitive') return getFrom(this.scene3d.primitiveManager);
        if (kind === 'model') return getFrom(this.scene3d.modelManager);
        return null;
    }
}
