// packs_preview.js — opens a model in the vendored Blockbench app inside a
// full-screen overlay. The geo JSON is loaded via the gated pack_asset
// endpoint and the texture images are resolved from afw_bone_textures (or the
// vanilla entity skin fallback) and handed to Blockbench; the bundled
// GeckoLib + Multi Actor Animator plugins handle applying them.
(function () {
    let overlay = null;
    let iframe = null;
    let busy = false;

    const BB_URL = '/static/vendor/blockbench/index.html';

    function ensureOverlay() {
        if (overlay) return overlay;
        overlay = document.createElement('div');
        overlay.className = 'bb-overlay';
        overlay.innerHTML =
            '<div class="bb-overlay-header">' +
            '<span class="bb-overlay-title" id="bb-overlay-title"></span>' +
            '<button type="button" class="bb-overlay-close" id="bb-overlay-close" aria-label="Close"><i class="fas fa-times"></i></button>' +
            '</div>' +
            '<div class="bb-overlay-status" id="bb-overlay-status"></div>';
        overlay.addEventListener('click', (e) => { if (e.target === overlay) close(); });
        overlay.querySelector('#bb-overlay-close').addEventListener('click', close);
        document.body.appendChild(overlay);
        return overlay;
    }

    function setStatus(text) {
        const el = document.querySelector('#bb-overlay-status');
        if (el) el.textContent = text || '';
    }

    function close() {
        if (!overlay) return;
        overlay.hidden = true;
        if (iframe) { iframe.remove(); iframe = null; }
    }

    function assetUrl(baseUrl, member) {
        return baseUrl.replace('ASSET', member);
    }

    function entityNameFromMember(member) {
        let base = member.slice(member.lastIndexOf('/') + 1);
        if (base.endsWith('.geo.json')) base = base.slice(0, -'.geo.json'.length);
        for (const s of ['.mf', '.fm', '.m', '.f', '.g']) {
            if (base.endsWith(s)) { base = base.slice(0, -s.length); break; }
        }
        return base;
    }

    function resourceToMember(resource) {
        resource = String(resource);
        if (resource.indexOf(':') !== -1) {
            const p = resource.split(':');
            return 'assets/' + p.shift() + '/' + p.join(':');
        }
        if (resource.startsWith('assets/')) return resource;
        return 'assets/' + resource;
    }

    function loadImage(url) {
        return new Promise((resolve, reject) => {
            const img = new Image();
            img.onload = () => resolve(img);
            img.onerror = () => reject(new Error('Could not load ' + url));
            img.src = url;
        });
    }

    function imageDataUrl(img) {
        const c = document.createElement('canvas');
        c.width = img.naturalWidth;
        c.height = img.naturalHeight;
        c.getContext('2d').drawImage(img, 0, 0);
        return c.toDataURL('image/png');
    }

    async function fetchGeo(member, baseUrl) {
        const r = await fetch(assetUrl(baseUrl, member), { headers: { 'Accept': 'application/json' } });
        if (!r.ok) throw new Error('Could not load the model file');
        return r.json();
    }

    async function fetchModelText(member, baseUrl) {
        const r = await fetch(assetUrl(baseUrl, member), { headers: { 'Accept': 'application/json' } });
        if (!r.ok) throw new Error('Could not load the model file');
        return r.text();
    }

    // Formats whose textures are baked into the file (nothing to resolve).
    // glTF/GLB are NOT here — import needs the desktop-only glTF Importer
    // plugin, so those models are marked "Incompatible" instead.
    const EMBEDDED_FORMATS = ['.bbmodel'];

    async function fetchVanillaIndex(vanillaBaseUrl) {
        try {
            const r = await fetch(assetUrl(vanillaBaseUrl, 'entity_index.json'));
            return r.ok ? await r.json() : {};
        } catch (e) {
            return {};
        }
    }

    // Resolve the textures to hand to Blockbench: the pack's <entity>_features
    // overlay(s) plus the vanilla entity skin (a random fur/colour variant when
    // the mob has several). Blockbench + the bundled plugins UV-wrap and apply
    // them, so we don't worry about per-bone assignment here.
    async function resolveTextures(geo, entity, opts, index) {
        const featureTextures = [];
        const baseTextures = [];
        const afw = geo['afw_bone_textures'] || {};

        // 1. Feature overlays — from afw_bone_textures, or the conventional
        //    assets/<ns>/textures/entity/<entity>/<entity>_features.png path.
        const members = [];
        let namespace = 'needsofnature';
        for (const k in afw) {
            const m = resourceToMember(afw[k]);
            if (members.indexOf(m) === -1) members.push(m);
            const colon = String(afw[k]).indexOf(':');
            if (colon > 0) namespace = String(afw[k]).slice(0, colon);
        }
        if (!members.length && entity) {
            members.push('assets/' + namespace + '/textures/entity/' + entity + '/' + entity + '_features.png');
        }
        for (const m of members) {
            try {
                const img = await loadImage(assetUrl(opts.baseUrl, m));
                featureTextures.push({ name: m.split('/').pop(), dataUrl: imageDataUrl(img) });
            } catch (e) {
                console.warn('packs: skip feature texture', m, e);
            }
        }

        // 2. Vanilla base skin — a random variant when several exist, else the
        //    server-matched pack skin, else none.
        let rel = null;
        const ent = index && index[entity];
        if (ent) {
            const variants = ent.variants || (typeof ent === 'string' ? [ent] : [ent.default]);
            if (variants && variants.length) rel = variants[Math.floor(Math.random() * variants.length)];
        }
        if (rel) {
            try {
                const img = await loadImage(assetUrl(opts.vanillaBaseUrl, 'entity/' + rel));
                baseTextures.push({ name: rel.split('/').pop(), dataUrl: imageDataUrl(img) });
            } catch (e) {
                console.warn('packs: skip vanilla skin', e);
            }
        } else if (opts.defaultTexture) {
            try {
                const img = await loadImage(assetUrl(opts.baseUrl, opts.defaultTexture));
                baseTextures.push({ name: opts.defaultTexture.split('/').pop(), dataUrl: imageDataUrl(img) });
            } catch (e) {
                console.warn('packs: skip default texture', e);
            }
        }
        // The vanilla/base texture must always be loaded first, then features.
        // Wide player models additionally always load L1Z0's custom skins
        // alongside the default Steve skin (tracked in static/player_skins/).
        if (entity === 'player') {
            for (const name of ['destroyed_skin.png', 'russian_goat.png']) {
                try {
                    const img = await loadImage('/static/player_skins/' + name);
                    baseTextures.push({ name: name, dataUrl: imageDataUrl(img) });
                } catch (e) {
                    console.warn('packs: skip player skin', name, e);
                }
            }
        }
        return baseTextures.concat(featureTextures);
    }

    // Create (or reset) the full-screen Blockbench overlay + fresh iframe and
    // wait for the app to be ready. Returns false on failure.
    async function prepareOverlay(name) {
        const overlayEl = ensureOverlay();
        overlayEl.hidden = false;
        document.querySelector('#bb-overlay-title').textContent = name;
        setStatus('Starting Blockbench…');

        if (iframe) iframe.remove();
        iframe = document.createElement('iframe');
        iframe.className = 'bb-frame';
        iframe.src = BB_URL;
        overlayEl.appendChild(iframe);

        const ready = await new Promise((resolve) => {
            let done = false;
            const finish = (ok) => {
                if (done) return;
                done = true;
                window.removeEventListener('message', onMsg);
                clearTimeout(timer);
                resolve(ok);
            };
            const timer = setTimeout(() => finish(false), 45000);
            const onMsg = (e) => {
                if (e.origin !== window.location.origin) return;
                if (e.data && e.data.type === 'packs-bb-ready') finish(true);
            };
            window.addEventListener('message', onMsg);
        });
        if (!ready) setStatus('Blockbench failed to start.');
        return ready;
    }

    async function open(btn) {
        const member = btn.dataset.member;
        const name = btn.dataset.name || member;
        const modelFormat = btn.dataset.modelFormat || '.geo.json';
        const slug = btn.dataset.projectSlug || '';
        const opts = { baseUrl: btn.dataset.baseUrl, vanillaBaseUrl: btn.dataset.vanillaBaseUrl, defaultTexture: btn.dataset.defaultTexture || null };
        if (!member || !opts.baseUrl || busy) return;
        busy = true;

        if (!(await prepareOverlay(name))) { busy = false; return; }

        setStatus('Loading model…');
        try {
            const msg = { type: 'packs-open-model', name: name };
            if (modelFormat === '.gltf' || modelFormat === '.glb') {
                setStatus('Incompatible — glTF/GLB preview needs the desktop glTF Importer plugin.');
                busy = false;
                return;
            }
            // Pack plugins to hotload (autoload.txt), unless the user opted out.
            const noHotload = slug && localStorage.getItem('packs:no-hotload:' + slug) === '1';
            if (!noHotload && btn.dataset.plugins) {
                try {
                    const members = JSON.parse(btn.dataset.plugins);
                    msg.plugins = members.map(m => ({ member: m, url: assetUrl(opts.baseUrl, m) }));
                } catch (e) {
                    msg.plugins = [];
                }
            }
            if (EMBEDDED_FORMATS.indexOf(modelFormat) !== -1) {
                // Textures baked into the file — hand it to Blockbench as-is.
                msg.modelFile = { format: modelFormat, content: await fetchModelText(member, opts.baseUrl) };
            } else {
                const [geo, index] = await Promise.all([
                    fetchGeo(member, opts.baseUrl),
                    fetchVanillaIndex(opts.vanillaBaseUrl),
                ]);
                const entity = entityNameFromMember(member);
                msg.geo = geo;
                msg.textures = await resolveTextures(geo, entity, opts, index);
            }
            iframe.contentWindow.postMessage(msg, window.location.origin);
            setStatus('Applying…');
        } catch (e) {
            setStatus('Could not load model: ' + e.message);
        }
        busy = false;
    }

    // Load a Minecraft skin into Blockbench's built-in "Minecraft Skin" project.
    async function openSkin(btn) {
        const url = btn.dataset.url;
        const name = btn.dataset.name || 'skin';
        if (!url || busy) return;
        busy = true;

        if (!(await prepareOverlay(name))) { busy = false; return; }

        setStatus('Loading skin…');
        try {
            const select = btn.closest('.skin-card')?.querySelector('.skin-model-select');
            const model = select ? select.value : 'steve';
            const img = await loadImage(url);
            iframe.contentWindow.postMessage({
                type: 'packs-open-skin',
                name: name,
                model: model,
                dataUrl: imageDataUrl(img),
            }, window.location.origin);
            setStatus('Applying…');
        } catch (e) {
            setStatus('Could not load skin: ' + e.message);
        }
        busy = false;
    }

    window.addEventListener('message', (e) => {
        if (e.origin !== window.location.origin) return;
        if (!e.data) return;
        if (e.data.type === 'packs-model-open') {
            setStatus('Ready — drag to orbit, scroll to zoom.');
        } else if (e.data.type === 'packs-model-error') {
            setStatus('Model error: ' + (e.data.error || ''));
        } else if (e.data.type === 'packs-plugin-error') {
            setStatus('Plugin error: ' + (e.data.error || ''));
        }
    });

    window.PacksPreview = { open: open, openSkin: openSkin, close: close };
})();
