/**
 * GizmoController — обёртка над Babylon GizmoManager.
 *
 * Поддерживает 3 режима манипуляции:
 *   'move'   — стрелки X/Y/Z для перемещения (PositionGizmo)
 *   'rotate' — кольца X/Y/Z для поворота (RotationGizmo)
 *   'scale'  — кубики X/Y/Z для масштабирования (ScaleGizmo)
 *
 * Также режим 'select' = ничего не показывать (просто выделение без гизмо).
 *
 * Snap-режим (для move): 1.0 / 0.5 / 0.25 / 0 (off).
 *
 * При завершении drag вызывает onDragEnd колбэк.
 *
 * ВАЖНО: GizmoManager создаёт sub-gizmos (positionGizmo и т.п.) лениво
 * при первом включении соответствующего режима. Поэтому slушатели и snap
 * нужно ставить ПОСЛЕ включения режима, не до.
 */
import { GizmoManager } from '@babylonjs/core';

export class GizmoController {
    constructor(layer, scene) {
        this.scene = scene;
        this.layer = layer;
        // 1.5 — общий размер гизмо. usePointerToAttach=false чтобы клик по объекту
        // не назначал гизмо автоматически — это делает наш SelectionManager.
        this.manager = new GizmoManager(scene, 1.5, layer);
        this.manager.usePointerToAttachGizmos = false;

        this._mode = 'select';
        this._snap = 1.0;
        this._attached = null;        // mesh или TransformNode на который указывает гизмо
        this._onDragEnd = null;
        this._onDragStart = null;
        // Set гизмо у которых уже привязаны слушатели (чтобы не делать повторно)
        this._listenersBound = new Set();
    }

    setOnDragEnd(cb) { this._onDragEnd = cb; }
    setOnDragStart(cb) { this._onDragStart = cb; }

    /** Главный метод — переключить режим. */
    setMode(mode) {
        this._mode = mode;
        this.manager.positionGizmoEnabled = false;
        this.manager.rotationGizmoEnabled = false;
        this.manager.scaleGizmoEnabled = false;
        if (mode === 'move')   this.manager.positionGizmoEnabled = true;
        if (mode === 'rotate') this.manager.rotationGizmoEnabled = true;
        if (mode === 'scale')  this.manager.scaleGizmoEnabled = true;
        this._bindListenersFor(mode);
        this._applySnap();
        this._reattach();
    }

    /**
     * Переустановить активный режим — пересоздаёт sub-gizmo с привязкой
     * к текущему _attached. Нужен после смены выделения, чтобы стрелки
     * гарантированно отвечали на драг.
     */
    refreshMode() {
        const m = this._mode;
        if (m === 'select') return;
        // Сначала выключаем все sub-gizmo, потом включаем нужный заново —
        // Babylon при включении sub-gizmo возьмёт актуальный attachedMesh.
        this.manager.positionGizmoEnabled = false;
        this.manager.rotationGizmoEnabled = false;
        this.manager.scaleGizmoEnabled = false;
        if (m === 'move')   this.manager.positionGizmoEnabled = true;
        if (m === 'rotate') this.manager.rotationGizmoEnabled = true;
        if (m === 'scale')  this.manager.scaleGizmoEnabled = true;
        this._bindListenersFor(m);
        this._applySnap();
        // _reattach уже сделан в setMode, но повторим — на случай что
        // attached изменился между вызовами.
        this._reattach();
    }

    getMode() { return this._mode; }

    /** Привязать drag-листенеры к gizmo текущего режима если ещё не привязаны. */
    _bindListenersFor(mode) {
        let gizmo = null;
        if (mode === 'move')   gizmo = this.manager.gizmos.positionGizmo;
        if (mode === 'rotate') gizmo = this.manager.gizmos.rotationGizmo;
        if (mode === 'scale')  gizmo = this.manager.gizmos.scaleGizmo;
        if (!gizmo || this._listenersBound.has(gizmo)) return;
        gizmo.onDragStartObservable.add(() => {
            if (this._onDragStart) this._onDragStart();
        });
        gizmo.onDragEndObservable.add(() => {
            if (this._onDragEnd) this._onDragEnd();
        });
        this._listenersBound.add(gizmo);
    }

    /**
     * Прикрепить гизмо к mesh-объекту (или null чтобы отключить).
     * Должен вызываться после setMode (или setMode сам делает _reattach).
     */
    attachTo(mesh) {
        this._attached = mesh;
        this._reattach();
    }

    _reattach() {
        const target = this._attached;
        // Безопасно отвязываем гизмо
        const detach = () => {
            try { this.manager.attachToMesh(null); } catch (e) { /* ignore */ }
            if (typeof this.manager.attachToNode === 'function') {
                try { this.manager.attachToNode(null); } catch (e) { /* ignore */ }
            }
        };

        if (!target) { detach(); return; }

        // Proxy-меши блоков (thin-instances) и liquid-proxy не имеют API Mesh
        // (нет removeBehavior, getWorldMatrix). Гизмо к ним не подвязывается —
        // блоки и так стоят на сетке, перемещать их через гизмо некуда.
        const isProxy = !!(target._isBlockProxy || target.metadata?._liquidProxy);
        if (isProxy) { detach(); return; }

        // Проверяем что у узла есть нужный API (Mesh / TransformNode полноценный).
        const hasNodeApi = typeof target.removeBehavior === 'function'
            && typeof target.getWorldMatrix === 'function';
        if (!hasNodeApi) { detach(); return; }

        const isMesh = typeof target.getTotalVertices === 'function'
            && target.getTotalVertices() > 0;

        try {
            if (isMesh) {
                this.manager.attachToMesh(target);
            } else if (typeof this.manager.attachToNode === 'function') {
                this.manager.attachToNode(target);
            } else {
                detach();
            }
        } catch (e) {
            // eslint-disable-next-line no-console
            console.warn('[GizmoController] attach failed', e);
            detach();
        }
    }

    /** Установить snap step. */
    setSnap(step) {
        this._snap = step;
        this._applySnap();
    }

    /** Применить snap к текущим sub-gizmo (если они созданы). */
    _applySnap() {
        const step = this._snap;
        const pg = this.manager.gizmos.positionGizmo;
        if (pg) {
            pg.snapDistance = step > 0 ? step : 0;
            // У PositionGizmo есть отдельные xGizmo/yGizmo/zGizmo,
            // и planarGizmoEnabled (плоскостные). Babylon 5+ автоматически
            // прокидывает snapDistance на оси. На всякий случай — явно:
            if (pg.xGizmo) pg.xGizmo.snapDistance = pg.snapDistance;
            if (pg.yGizmo) pg.yGizmo.snapDistance = pg.snapDistance;
            if (pg.zGizmo) pg.zGizmo.snapDistance = pg.snapDistance;
        }
        const rg = this.manager.gizmos.rotationGizmo;
        if (rg) {
            // Для rotation snap — в радианах. 15° = π/12.
            const rad = step > 0 ? Math.PI / 12 : 0;
            rg.snapDistance = rad;
            if (rg.xGizmo) rg.xGizmo.snapDistance = rad;
            if (rg.yGizmo) rg.yGizmo.snapDistance = rad;
            if (rg.zGizmo) rg.zGizmo.snapDistance = rad;
        }
    }

    getSnap() { return this._snap; }

    dispose() {
        try { this.manager.dispose(); } catch (e) { /* ignore */ }
    }
}
