/**
 * GuiManager — слой UI-элементов поверх viewport (как ScreenGui в Roblox).
 *
 * Хранит массив элементов: Frame, TextLabel, TextButton, ImageLabel.
 * Не зависит от Babylon — просто состояние, которое рендерит GuiOverlay (React).
 *
 * Координаты позиции/размера хранятся в процентах (0..100), чтобы интерфейс
 * масштабировался под любое разрешение. Также можно задавать в пикселях через
 * pxX/pxY/pxW/pxH (если нужна абсолютная привязка к углам).
 *
 * Элемент:
 *   {
 *     id: 'gui_1',           — уникальный
 *     type: 'frame'|'text'|'button'|'image',
 *     name: 'Frame 1',
 *     x: 50, y: 50,          — % позиции центра по экрану (по умолчанию)
 *     w: 30, h: 20,          — % размера
 *     anchor: 'center',      — 'top-left' | 'top-right' | 'bottom-left' | 'bottom-right' | 'center'
 *     visible: true,
 *     // визуальные стили
 *     bgColor: '#1f1810',
 *     bgOpacity: 0.85,
 *     borderColor: '#5a4a3a',
 *     borderWidth: 1,
 *     borderRadius: 6,
 *     // text/button only:
 *     text: 'Hello',
 *     textColor: '#f0e6d8',
 *     textSize: 16,
 *     textAlign: 'center',   — 'left' | 'center' | 'right'
 *     fontWeight: 600,
 *     // image only:
 *     imageUrl: '',          — внешний URL картинки
 *     imageAsset: null,      — id картинки из AssetManager (приоритет над imageUrl)
 *     // зарезервировано для следующих этапов
 *     onClickScriptId: null, — скрипт по клику (для button)
 *   }
 */

let _seq = 1;

/** True если элемент может быть родителем (контейнер): frame или scroll. */
function _isContainer(el) {
    return !!el && (el.type === 'frame' || el.type === 'scroll');
}

export class GuiManager {
    constructor() {
        /** @type {Array<object>} */
        this.elements = [];
        this._onChange = null;
    }

    setOnChange(cb) { this._onChange = cb; }
    _notify() { if (this._onChange) try { this._onChange(); } catch (e) { /* ignore */ } }

    /** Создать элемент. type: 'frame'|'text'|'button'|'image'. Возвращает id.
     *  opts.parentId — id Frame'а в который вкладывается элемент. По умолчанию null (на экране). */
    create(type, opts = {}) {
        const id = opts.id || `gui_${Date.now()}_${(_seq++).toString(36)}`;
        // Валидация parentId — должен существовать и быть Frame'ом
        let parentId = opts.parentId || null;
        if (parentId) {
            const parent = this.elements.find(e => e.id === parentId);
            if (!_isContainer(parent)) parentId = null;
        }
        // === GD HUD layout override (2026-05-19) ===
        // В скриптах L1-L20 счётчики 🪙 (Coins) и 💎 (Diamonds) изначально
        // налезали на кнопку меню ☰ (x=94, w=5.5). Сдвигаем без правки БД.
        // Узнаём по тексту-эмодзи (другие скрипты эти эмодзи не используют).
        //
        // Целевой layout HUD-полосы:
        //   Title (центр):  x=20  w=30  → [20..50]
        //   Diamonds:       x=52  w=10  → [52..62]   right-align
        //   Coins:          x=62  w=30  → [62..92]   right-align
        //   Menu ☰:         x=94  w=5.5 → [94..99.5] (не трогаем)
        if (type === 'text' && typeof opts.text === 'string') {
            // Coins: '🪙 N/3' на x=76 w=22 → x=62 w=30
            if (opts.text.indexOf('🪙') >= 0 && opts.x === 76 && opts.w === 22) {
                opts = { ...opts, x: 62, w: 30 };
            }
            // Diamonds: '💎 N/M' на x=58 w=18 → x=52 w=10 (узкая колонка)
            if ((opts.text.indexOf('\u{1F48E}') >= 0 || opts.text.indexOf('💎') >= 0)
                && opts.x === 58 && opts.w === 18) {
                opts = { ...opts, x: 52, w: 10 };
            }
            // Title 'L4 · Быстрее ветра' на x=30 w=40 → x=20 w=30 (центр сдвинут влево)
            if (/^L\d+\s/.test(opts.text) && opts.x === 30 && opts.w === 40) {
                opts = { ...opts, x: 20, w: 30 };
            }
        }
        const base = {
            id,
            type,
            name: opts.name || _defaultName(type, this.elements.length + 1),
            parentId,
            x: opts.x ?? 50,
            y: opts.y ?? 50,
            w: opts.w ?? _defaultSize(type).w,
            h: opts.h ?? _defaultSize(type).h,
            anchor: opts.anchor || 'center',
            // Phase 6.3.1: AnchorPoint -- точка ВНУТРИ элемента, относительно
            // которой считается позиция x/y. По умолчанию НЕ задан -- рендерер
            // вычислит дефолт из anchor (center → {0.5,0.5}, top-left → {0,0}, ...).
            // Юзер может override через opts.anchorPoint = {x:0..1, y:0..1}.
            anchorPoint: opts.anchorPoint || null,
            visible: opts.visible !== false,
            bgColor: opts.bgColor ?? _defaultBgColor(type),
            bgOpacity: opts.bgOpacity ?? _defaultBgOpacity(type),
            borderColor: opts.borderColor ?? '#5a4a3a',
            borderWidth: opts.borderWidth ?? (type === 'frame' || type === 'button' ? 1 : 0),
            borderRadius: opts.borderRadius ?? 6,
            text: opts.text ?? _defaultText(type),
            textColor: opts.textColor ?? '#f0e6d8',
            textSize: opts.textSize ?? 16,
            textAlign: opts.textAlign ?? (type === 'textbox' ? 'left' : 'center'),
            fontWeight: opts.fontWeight ?? (type === 'button' ? 700 : 500),
            imageUrl: opts.imageUrl ?? '',
            // imageAsset — id картинки из AssetManager (этап 3.6). Если задан,
            // имеет приоритет над imageUrl: GuiOverlay показывает dataURL ассета.
            imageAsset: opts.imageAsset ?? null,
            // placeholder — серый текст-подсказка в пустом поле ввода (textbox)
            placeholder: opts.placeholder ?? (type === 'textbox' ? 'Введите текст…' : ''),
            onClickScriptId: opts.onClickScriptId ?? null,
            zIndex: opts.zIndex ?? this.elements.length + 1,
            // Авто-раскладка детей (Фаза 5.3 + 6.3.2):
            //   'none' -- дети как есть (по своим x/y);
            //   'vertical' -- дети в столбик; 'horizontal' -- в строку;
            //   'grid' -- сетка (требует layoutCellW/H/Cols).
            layout: opts.layout ?? 'none',
            // Отступ между детьми и внутреннее поле контейнера (в % размера контейнера).
            layoutGap: opts.layoutGap ?? 2,
            layoutPad: opts.layoutPad ?? 3,
            // Phase 6.3.2: параметры grid-layout.
            // cellW/H -- размер каждой ячейки в %; cols -- количество колонок (0 = авто).
            layoutCellW: opts.layoutCellW ?? 18,
            layoutCellH: opts.layoutCellH ?? 18,
            layoutCols: opts.layoutCols ?? 0,
            // Текущая прокрутка scroll-контейнера (в %, только для type='scroll').
            scrollY: opts.scrollY ?? 0,
            // Тень под элементом (Фаза 5.4) — мягкая drop-shadow.
            shadow: opts.shadow ?? false,
            // === Задача 03: расширения для красивого UI + анимаций ===
            // Линейный градиент фона. Формат: { stops: ['#a','#b'] | [{c,p}], angle: 0..360 }.
            bgGradient: opts.bgGradient ?? null,
            // Обводка текста (для крупных подписей "X2 ДЕНЕГ"). { color, width }.
            textStroke: opts.textStroke ?? null,
            // Поворот элемента в градусах (transform: rotate).
            rotation: opts.rotation ?? 0,
            // Scale-множитель (transform: scale). 1 = нормальный размер.
            scaleX: opts.scaleX ?? 1,
            scaleY: opts.scaleY ?? 1,
            // Бейдж-маркер в углу: { corner, icon, color, text }.
            badge: opts.badge ?? null,
            // Hover-реакция (только для button/image-button): { scale, rotation, brightness, duration, easing }.
            hover: opts.hover ?? null,
            // Active-реакция (зажатие ЛКМ): { scale, duration }.
            active: opts.active ?? null,
            // Анимация-пресет: 'none'|'pulse'|'rotate'|'sway'|'glow'|'bounce'|'custom'.
            // Раскрывается в реальный tween при applyAnimationPreset(id) в Play.
            animationPreset: opts.animationPreset ?? 'none',
            // Создан скриптом в Play (game.gui.create) — НЕ сериализуется
            // в проект, удаляется при Stop.
            _scriptCreated: opts._scriptCreated === true,
        };
        this.elements.push(base);
        this._notify();
        return id;
    }

    get(id) {
        return this.elements.find(e => e.id === id) || null;
    }

    update(id, patch) {
        const el = this.get(id);
        if (!el) return;
        // Валидация parentId — нельзя сделать элемент потомком самого себя
        // или своего же потомка (циклы), и parent должен быть Frame
        if ('parentId' in patch) {
            const next = patch.parentId || null;
            if (next === id) { delete patch.parentId; }
            else if (next != null && this._isDescendant(next, id)) { delete patch.parentId; }
            else if (next != null) {
                const p = this.get(next);
                if (!_isContainer(p)) delete patch.parentId;
            }
        }
        Object.assign(el, patch);
        this._notify();
    }

    /** True если `candidate` является (косвенным) потомком `ancestorId`. */
    _isDescendant(candidate, ancestorId) {
        let cur = this.get(candidate);
        let safety = 0;
        while (cur && cur.parentId && safety++ < 100) {
            if (cur.parentId === ancestorId) return true;
            cur = this.get(cur.parentId);
        }
        return false;
    }

    /** Сменить родителя элемента. */
    setParent(id, parentId) {
        this.update(id, { parentId: parentId || null });
    }

    /** Список прямых детей frame-id (или корневых элементов если parentId === null). */
    children(parentId) {
        return this.elements.filter(e => (e.parentId || null) === (parentId || null));
    }

    remove(id) {
        const i = this.elements.findIndex(e => e.id === id);
        if (i < 0) return;
        // Каскадно удаляем всех потомков
        const queue = [id];
        const toDelete = new Set();
        while (queue.length > 0) {
            const cur = queue.shift();
            toDelete.add(cur);
            for (const e of this.elements) {
                if (e.parentId === cur) queue.push(e.id);
            }
        }
        this.elements = this.elements.filter(e => !toDelete.has(e.id));
        this._notify();
    }

    rename(id, name) {
        const el = this.get(id);
        if (!el) return;
        el.name = String(name || '').slice(0, 80);
        this._notify();
    }

    /** Поднять/опустить элемент в z-order (вверх = ближе к зрителю). */
    moveZ(id, direction) {
        const i = this.elements.findIndex(e => e.id === id);
        if (i < 0) return;
        const target = direction === 'up' ? i + 1 : i - 1;
        if (target < 0 || target >= this.elements.length) return;
        const tmp = this.elements[i];
        this.elements[i] = this.elements[target];
        this.elements[target] = tmp;
        // Перенумеровываем zIndex по порядку
        this.elements.forEach((e, idx) => { e.zIndex = idx + 1; });
        this._notify();
    }

    getAll() { return this.elements.slice(); }

    serialize() {
        // Элементы, созданные скриптом в Play (game.gui.create), НЕ
        // сохраняем в проект — иначе автосейв запишет их в БД и они
        // «вернутся» после Stop. Служебное поле _scriptCreated не пишем.
        return this.elements
            .filter(e => !e._scriptCreated)
            .map(e => {
                const c = { ...e };
                delete c._scriptCreated;
                return c;
            });
    }

    loadFromArray(arr) {
        this.elements = Array.isArray(arr)
            ? arr.filter(e => e && typeof e.id === 'string' && typeof e.type === 'string')
                 .map(e => ({ ...e, parentId: e.parentId || null }))
            : [];
        // Чистим висящие parentId на несуществующих или не-frame родителей
        const ids = new Set(this.elements.map(e => e.id));
        for (const el of this.elements) {
            if (el.parentId && !ids.has(el.parentId)) { el.parentId = null; }
            else if (el.parentId) {
                const p = this.elements.find(e => e.id === el.parentId);
                if (!_isContainer(p)) el.parentId = null;
            }
        }
        this._notify();
    }

    clear() {
        if (this.elements.length === 0) return;
        this.elements = [];
        this._notify();
    }
}

function _defaultName(type, n) {
    if (type === 'frame')   return `Контейнер ${n}`;
    if (type === 'text')    return `Надпись ${n}`;
    if (type === 'button')  return `Кнопка ${n}`;
    if (type === 'image')   return `Картинка ${n}`;
    if (type === 'textbox') return `Поле ввода ${n}`;
    if (type === 'scroll')  return `Список ${n}`;
    return `Элемент ${n}`;
}

function _defaultSize(type) {
    if (type === 'frame')   return { w: 30, h: 20 };
    if (type === 'text')    return { w: 20, h: 6 };
    if (type === 'button')  return { w: 18, h: 8 };
    if (type === 'image')   return { w: 15, h: 15 };
    if (type === 'textbox') return { w: 24, h: 8 };
    if (type === 'scroll')  return { w: 30, h: 40 };
    return { w: 20, h: 10 };
}

function _defaultText(type) {
    if (type === 'text')   return 'Текст';
    if (type === 'button') return 'Кнопка';
    return '';   // textbox стартует пустым
}

function _defaultBgColor(type) {
    if (type === 'frame')   return '#1f1810';
    if (type === 'button')  return '#5a8c3e';
    if (type === 'text')    return 'transparent';
    if (type === 'image')   return 'transparent';
    if (type === 'textbox') return '#2a2218';   // поле ввода — тёмный фон
    if (type === 'scroll')  return '#1f1810';
    return '#1f1810';
}

function _defaultBgOpacity(type) {
    if (type === 'frame')   return 0.85;
    if (type === 'button')  return 0.95;
    if (type === 'textbox') return 0.95;
    if (type === 'scroll')  return 0.85;
    return 1;
}
