/**
 * SoundLibrary — библиотека пользовательских звуков проекта (Фаза 5.5).
 *
 * Зачем: даёт автору загрузить свои короткие звуки (mp3/wav/ogg) и
 * проигрывать их в игре через game.sound.* — в т.ч. как 3D-позиционный
 * звук от точки в мире.
 *
 * Как хранится: каждый звук — { id, name, dataUrl }, где dataUrl —
 * base64 аудио. Сериализуется в scene.sounds, едет в БД с проектом
 * (своего файлового хранилища у Рублокс-веба нет — как картинки в
 * AssetManager).
 *
 * Ограничения (звук в base64 раздувает JSON проекта):
 *   MAX_SOUNDS — не больше 8 звуков на проект;
 *   MAX_BYTES  — не больше 2 МБ на звук (mp3 ~30-60 сек; для коротких
 *                фоновых треков и эффектов хватает).
 */

export const MAX_SOUNDS = 8;
export const MAX_SOUND_BYTES = 2 * 1024 * 1024;

let _soundIdSeq = 1;

export class SoundLibrary {
    constructor() {
        // Map id → { id, name, dataUrl }
        this.sounds = new Map();
    }

    /** Все звуки массивом — для UI-панели. */
    list() {
        return [...this.sounds.values()];
    }

    get(id) {
        return this.sounds.get(id) || null;
    }

    /** dataURL по id — то, что отдаём в Audio / decodeAudioData. */
    getDataUrl(id) {
        const s = this.sounds.get(id);
        return s ? s.dataUrl : null;
    }

    count() {
        return this.sounds.size;
    }

    /**
     * Загрузить звук из File (input type=file).
     * Возвращает Promise<{ ok, id?, error? }>.
     */
    addFromFile(file) {
        return new Promise((resolve) => {
            if (this.sounds.size >= MAX_SOUNDS) {
                resolve({ ok: false, error: `Лимит ${MAX_SOUNDS} звуков на проект` });
                return;
            }
            if (!file || !/^audio\//.test(file.type)) {
                resolve({ ok: false, error: 'Это не звуковой файл' });
                return;
            }
            // Грубая проверка размера ДО чтения (base64 ~+33% сверху).
            if (file.size > MAX_SOUND_BYTES) {
                resolve({
                    ok: false,
                    error: `Звук слишком большой (макс ${Math.round(MAX_SOUND_BYTES / 1024)} КБ)`,
                });
                return;
            }
            const reader = new FileReader();
            reader.onerror = () => resolve({ ok: false, error: 'Не удалось прочитать файл' });
            reader.onload = () => {
                const dataUrl = reader.result;
                if (typeof dataUrl !== 'string') {
                    resolve({ ok: false, error: 'Битый файл' });
                    return;
                }
                if (dataUrl.length > MAX_SOUND_BYTES * 1.4) {
                    resolve({ ok: false, error: 'Звук слишком большой' });
                    return;
                }
                const name = (file.name || 'звук').replace(/\.[^.]+$/, '');
                const id = this.add(name, dataUrl);
                resolve({ ok: true, id });
            };
            reader.readAsDataURL(file);
        });
    }

    /** Добавить звук с готовым dataUrl. Возвращает id. */
    add(name, dataUrl) {
        const id = `sound_${_soundIdSeq++}`;
        this.sounds.set(id, { id, name: name || 'звук', dataUrl });
        return id;
    }

    rename(id, name) {
        const s = this.sounds.get(id);
        if (s) s.name = name || s.name;
    }

    remove(id) {
        this.sounds.delete(id);
    }

    // ============ STATE ============

    serialize() {
        return this.list().map(s => ({
            id: s.id, name: s.name, dataUrl: s.dataUrl,
        }));
    }

    load(data) {
        this.sounds.clear();
        if (!Array.isArray(data)) return;
        let maxNum = 0;
        for (const s of data) {
            if (!s || typeof s.id !== 'string' || typeof s.dataUrl !== 'string') continue;
            this.sounds.set(s.id, {
                id: s.id,
                name: typeof s.name === 'string' ? s.name : 'звук',
                dataUrl: s.dataUrl,
            });
            const m = /^sound_(\d+)$/.exec(s.id);
            if (m) maxNum = Math.max(maxNum, Number(m[1]));
        }
        if (maxNum >= _soundIdSeq) _soundIdSeq = maxNum + 1;
    }

    dispose() {
        this.sounds.clear();
    }
}
