// packs_bootstrap.js — injected into the vendored Blockbench app (same origin).
// Loads the GeckoLib + Multi Actor Animator plugins and bridges model/texture
// loading from the parent page via postMessage.
(function () {
    if (window.PacksBB) return;

    function waitFor(fn, timeout) {
        return new Promise((resolve) => {
            const start = Date.now();
            const iv = setInterval(() => {
                if (fn()) { clearInterval(iv); resolve(true); }
                else if (Date.now() - start > timeout) { clearInterval(iv); resolve(false); }
            }, 100);
        });
    }

    function notify(type, payload) {
        if (window.parent && window.parent !== window) {
            window.parent.postMessage(Object.assign({ type: 'packs-' + type }, payload || {}), window.location.origin);
        }
    }

    async function loadPlugin(path, id) {
        if (Plugins.registered[id]) {
            // already registered (persisted from a previous session) — ensure active
            const existing = Plugins.registered[id];
            if (existing.installed && !existing.disabled && typeof existing.onload === 'function' && existing.source === 'file') {
                return existing;
            }
        }
        const code = await (await fetch(path, { cache: 'no-store' })).text();
        if (!code || code.length < 20) throw new Error('empty plugin: ' + path);
        const inst = new Plugin();
        await inst.loadFromFile({ path: path, content: code }, true);
        return inst;
    }

    function filenameOf(resource) {
        const s = String(resource || '').trim();
        const parts = s.split('/');
        return parts[parts.length - 1];
    }

    // Set the Multi Actor Animator per-bone texture overrides from the geo's
    // afw_bone_textures. Only the feature bones get special treatment; the
    // vanilla skin stays the model's base (just loaded, Blockbench default).
    function applyAfwTextureOverrides(geo) {
        const afw = (geo && geo['afw_bone_textures']) || {};
        const overrides = {};
        for (const bone in afw) {
            const texName = filenameOf(afw[bone]);
            if (texName) overrides[bone] = { name: bone, texture: texName };
        }
        if (!Object.keys(overrides).length || !Project) return;
        try {
            Project['multiactor_bone_texture_overrides'] = JSON.stringify(overrides);
        } catch (e) {
            console.error('packs override set failed', e);
        }
    }

    async function openModel(msg) {
        await waitFor(() => typeof loadModelFile === 'function', 15000);
        const name = msg.name || 'model';

        if (msg.modelFile) {
            // Standalone Blockbench model (bbmodel) with baked textures.
            // glTF/GLB is not sent here — those are marked "Incompatible" by the
            // parent page (import needs the desktop-only glTF Importer plugin).
            if (msg.modelFile.content) {
                await loadModelFile({ content: msg.modelFile.content, name: name, path: name });
            }
            await new Promise((r) => setTimeout(r, 400));
            notify('model-open', { name: name });
            return;
        }

        await loadModelFile({ content: JSON.stringify(msg.geo), name: name, path: name });
        // Give the format importer a beat, then set the feature-bone overrides
        // (before adding textures, so the plugin's add_texture listener applies
        // them) and load the base texture first, then the feature overlays.
        await new Promise((r) => setTimeout(r, 800));
        applyAfwTextureOverrides(msg.geo);

        const afwFiles = new Set();
        for (const k in ((msg.geo && msg.geo['afw_bone_textures']) || {})) {
            afwFiles.add(filenameOf(msg.geo['afw_bone_textures'][k]));
        }
        const baseTextures = (msg.textures || []).filter((t) => !afwFiles.has(t.name));
        const featureTextures = (msg.textures || []).filter((t) => afwFiles.has(t.name));

        for (const t of baseTextures.concat(featureTextures)) {
            try {
                const tex = new Texture({ name: t.name || 'texture', folder: 'entity' });
                if (t.dataUrl) {
                    await tex.fromDataURL(t.dataUrl);
                } else if (t.url) {
                    await tex.fromPath(t.url);
                }
                tex.add(false);
            } catch (e) {
                console.error('packs texture load failed', t && t.name, e);
            }
        }
        await new Promise((r) => setTimeout(r, 400));
        await hotloadPackPlugins(msg.plugins);
        notify('model-open', { name: name });
    }

    // Hotload plugins bundled with the pack (served via pack_asset). A failing
    // plugin is reported but never blocks the model preview.
    async function hotloadPackPlugins(plugins) {
        for (const p of (plugins || [])) {
            if (!p || !p.url || !p.member) continue;
            try {
                const code = await (await fetch(p.url, { headers: { 'Accept': 'text/javascript' } })).text();
                if (!code || code.length < 20) continue;
                await new Plugin().loadFromFile({ path: p.member, content: code }, true);
                notify('plugin-hotloaded', { name: p.member });
            } catch (e) {
                console.error('packs pack plugin hotload failed', p.member, e);
                notify('plugin-error', { error: 'Failed to load plugin ' + p.member });
            }
        }
    }

    // Open a Minecraft Skin project (Steve/Alex player model) with the skin.
    async function openSkin(msg) {
        await waitFor(() => typeof Formats !== 'undefined' && !!Formats.skin, 15000);
        const model = msg.model === 'alex' ? 'alex' : 'steve';
        const dialog = Formats.skin.setup_dialog;
        dialog.show();
        await dialog.setFormValues({
            model: model,
            texture_source: 'upload_texture',
            texture_file: { content: msg.dataUrl, name: 'skin.png', path: 'skin.png' },
            layer_template: true,
        });
        dialog.confirm();
        // Reveal every cube (incl. the outer layer) and land in Pose mode.
        await new Promise((r) => setTimeout(r, 700));
        try {
            (typeof Cube !== 'undefined' && Cube.all || []).forEach((c) => { c.visibility = true; });
            if (typeof Canvas !== 'undefined' && Canvas.updateAll) Canvas.updateAll();
            const pose = Modes && Modes.options && Modes.options.pose;
            if (pose && typeof pose.select === 'function') pose.select();
        } catch (e) {
            console.error('packs skin post-load', e);
        }
        notify('model-open', { name: msg.name || 'skin' });
    }

    window.addEventListener('message', (e) => {
        if (!e.data) return;
        if (e.data.type === 'packs-open-skin') {
            openSkin(e.data).then(
                () => {},
                (err) => {
                    console.error('packs-open-skin failed', err);
                    notify('model-error', { name: e.data.name || '', error: String(err) });
                }
            );
            return;
        }
        if (e.data.type !== 'packs-open-model') return;
        openModel(e.data).then(
            () => {},
            (err) => {
                console.error('packs-open-model failed', err);
                notify('model-error', { name: e.data.name || '', error: String(err) });
            }
        );
    });

    async function boot() {
        window.confirm = () => true;
        const ok = await waitFor(() => window.Blockbench && window.Blockbench.setup_successful && window.Plugin && typeof loadModelFile === 'function', 30000);
        if (!ok) { notify('error', { error: 'Blockbench did not initialize' }); return; }
        try {
            await loadPlugin('plugins/geckolib/geckolib.js', 'geckolib');
            await loadPlugin('plugins/MultiactorEditor/MultiactorEditor.js', 'MultiactorEditor');
        } catch (e) {
            console.error('packs plugin load failed', e);
            notify('plugin-error', { error: String(e) });
        }
        window.PacksBB = { ready: true, plugins: ['geckolib', 'MultiactorEditor'] };
        notify('bb-ready', {});
    }

    boot();
})();
