(function() {
    'use strict';

    let rawData = null;
    let offsetEntries = [];

    const versionDisplay = document.getElementById('versionDisplay');
    const dumperDisplay = document.getElementById('dumperDisplay');
    const totalOffsets = document.getElementById('totalOffsets');
    const totalCategories = document.getElementById('totalCategories');
    const dumpedAt = document.getElementById('dumpedAt');
    const offsetsBody = document.getElementById('offsetsBody');
    const downloadGrid = document.getElementById('downloadGrid');
    const discordLink = document.getElementById('discordLink');
    const tabContainer = document.getElementById('tabContainer');

    function escapeHtml(str) {
        if (!str) return '';
        return String(str).replace(/[&<>"]/g, function(m) {
            if (m === '&') return '&amp;';
            if (m === '<') return '&lt;';
            if (m === '>') return '&gt;';
            if (m === '"') return '&quot;';
            return m;
        });
    }

    function getCategory(key) {
        const known = [
            'PlayerConfigurer', 'TaskScheduler', 'VisualEngine', 'FakeDataModel',
            'MouseService', 'ScriptContext', 'ModuleScript', 'RenderView', 'Instance',
            'Misc', 'MeshContentProvider', 'MeshData', 'AttributesMap', 'Attribute',
            'DataModel', 'RunService', 'RenderJob', 'Workspace', 'World',
            'AirProperties', 'Terrain', 'MaterialColors', 'Sound', 'SpawnLocation'
        ];
        if (known.includes(key)) return key;
        return 'Other';
    }

    function flattenOffsets(obj, prefix) {
        const results = [];
        for (const [key, val] of Object.entries(obj)) {
            if (val && typeof val === 'object' && !Array.isArray(val)) {
                const cat = prefix || key;
                for (const [subKey, subVal] of Object.entries(val)) {
                    if (typeof subVal === 'number') {
                        results.push({ name: subKey, value: subVal, category: cat });
                    } else if (subVal && typeof subVal === 'object') {
                        const deeper = flattenOffsets({ [subKey]: subVal }, cat);
                        results.push(...deeper);
                    }
                }
            } else if (typeof val === 'number') {
                results.push({ name: key, value: val, category: prefix || 'Root' });
            }
        }
        return results;
    }

    function renderOffsets(entries) {
        if (!entries || entries.length === 0) {
            offsetsBody.innerHTML = `<tr><td colspan="4" class="empty-state">No offset data available.</td></tr>`;
            return;
        }
        let html = '';
        for (const e of entries) {
            const valStr = typeof e.value === 'number' ? e.value : String(e.value);
            html += `<tr>
                <td class="offset-name">${escapeHtml(e.name)}</td>
                <td class="offset-value">${escapeHtml(valStr)}</td>
                <td><span class="offset-cat">${escapeHtml(e.category)}</span></td>
                <td style="color:#777; font-size:0.7rem;">—</td>
            </tr>`;
        }
        offsetsBody.innerHTML = html;
    }

    function updateMetadata(data) {
        if (!data) return;
        versionDisplay.textContent = data['Roblox Version'] || '—';
        dumperDisplay.textContent = data['Dumper Version'] ? 'Dumper: ' + data['Dumper Version'] : 'Dumper: —';
        dumpedAt.textContent = data['Dumped At'] || '—';
        totalOffsets.textContent = data['Total Offsets'] !== undefined ? data['Total Offsets'] : offsetEntries.length;
        const cats = new Set(offsetEntries.map(e => e.category));
        totalCategories.textContent = cats.size || '—';
        if (data['Discord']) discordLink.href = data['Discord'];
    }

    // --- Fetch offsets using a CORS proxy ---
    function fetchOffsets() {
        const directUrl = 'https://offsets.imtheo.lol/offsets.json';
        const proxyUrl = 'https://api.allorigins.win/raw?url=' + encodeURIComponent(directUrl);
        offsetsBody.innerHTML = `<tr><td colspan="4" class="empty-state"><div class="loader"><div class="spinner"></div> Loading offsets…</div></td></tr>`;

        fetch(proxyUrl)
            .then(res => {
                if (!res.ok) throw new Error(`HTTP ${res.status}`);
                return res.json();
            })
            .then(data => {
                rawData = data;
                let all = [];
                if (data.Offsets && typeof data.Offsets === 'object') {
                    for (const [cat, catObj] of Object.entries(data.Offsets)) {
                        if (catObj && typeof catObj === 'object') {
                            for (const [name, val] of Object.entries(catObj)) {
                                if (typeof val === 'number') {
                                    all.push({ name, value: val, category: cat });
                                } else if (val && typeof val === 'object') {
                                    const nested = flattenOffsets({ [name]: val }, cat);
                                    all.push(...nested);
                                }
                            }
                        }
                    }
                } else {
                    all = flattenOffsets(data, 'Root');
                }
                const seen = new Set();
                offsetEntries = [];
                for (const e of all) {
                    const key = e.name + '||' + e.category;
                    if (!seen.has(key)) {
                        seen.add(key);
                        offsetEntries.push(e);
                    }
                }
                offsetEntries.sort((a, b) => {
                    if (a.category < b.category) return -1;
                    if (a.category > b.category) return 1;
                    return a.name.localeCompare(b.name);
                });
                renderOffsets(offsetEntries);
                updateMetadata(data);
                if (window.manifestLoaded) {
                    buildDownloadCardsFromManifest(window.manifestData);
                }
            })
            .catch(err => {
                console.error('Fetch error:', err);
                offsetsBody.innerHTML = `<tr><td colspan="4" class="empty-state" style="color:#f85149;">Failed to load offsets: ${escapeHtml(err.message)}</td></tr>`;
                versionDisplay.textContent = 'Error';
                totalOffsets.textContent = '—';
                totalCategories.textContent = '—';
                dumpedAt.textContent = '—';
                downloadGrid.innerHTML = `<p style="color:#f85149; font-size:0.8rem;">Could not load download data.</p>`;
            });
    }

    // --- Build download cards from manifest ---
    function buildDownloadCardsFromManifest(manifest) {
        if (!manifest || !manifest.downloadItems) return;
        const items = manifest.downloadItems;
        let html = '';
        for (const item of items) {
            let href = item.href;
            let extraAttr = '';
            let onClick = '';
            if (item.id === 'csv') {
                const csvContent = generateCSV();
                const blob = new Blob([csvContent], { type: 'text/csv' });
                const url = URL.createObjectURL(blob);
                href = url;
                extraAttr = `download="offsets.csv"`;
            } else if (item.id === 'snapshot') {
                onClick = `(function(){ if (!rawData) { alert('No data loaded yet.'); return false; } const blob = new Blob([JSON.stringify(rawData, null, 2)], {type:'application/json'}); const url = URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = 'offsets_snapshot.json'; document.body.appendChild(a); a.click(); document.body.removeChild(a); URL.revokeObjectURL(url); })(); return false;`;
                href = '#';
            }
            const btnClass = item.external ? 'dl-btn secondary' : 'dl-btn';
            const target = item.external ? ' target="_blank" rel="noopener noreferrer"' : '';
            const clickAttr = onClick ? ` onclick="${onClick}"` : '';
            const downloadAttr = extraAttr ? ` ${extraAttr}` : '';
            html += `
                <div class="download-card">
                    <div class="dl-title">${item.title}</div>
                    <div class="dl-desc">${item.desc}</div>
                    <a class="${btnClass}" href="${href}"${target}${clickAttr}${downloadAttr}>${item.btnText} ↓</a>
                    <div class="dl-meta">${item.meta}</div>
                </div>
            `;
        }
        downloadGrid.innerHTML = html;
    }

    function generateCSV() {
        let rows = [['Offset', 'Value', 'Category']];
        for (const e of offsetEntries) {
            rows.push([e.name, e.value, e.category]);
        }
        return rows.map(row => row.join(',')).join('\n');
    }

    // --- Load manifest ---
    function loadManifest() {
        fetch('manifest.json')
            .then(res => {
                if (!res.ok) throw new Error('Failed to load manifest');
                return res.json();
            })
            .then(manifest => {
                window.manifestData = manifest;
                window.manifestLoaded = true;
                buildTabs(manifest.tabs);
                if (rawData !== null) {
                    buildDownloadCardsFromManifest(manifest);
                } else {
                    buildDownloadCardsFromManifest(manifest); // show static placeholders
                }
            })
            .catch(err => {
                console.error('Manifest load error:', err);
                const defaultTabs = [{ id: 'offsets', label: 'Offsets' }, { id: 'download', label: 'Download' }];
                buildTabs(defaultTabs);
            });
    }

    function buildTabs(tabs) {
        if (!tabs) return;
        tabContainer.innerHTML = '';
        let first = true;
        for (const tab of tabs) {
            const btn = document.createElement('button');
            btn.className = 'tab-btn' + (first ? ' active' : '');
            btn.dataset.tab = tab.id;
            btn.textContent = tab.label;
            btn.setAttribute('role', 'tab');
            tabContainer.appendChild(btn);
            first = false;
        }
        initTabs();
        const panels = document.querySelectorAll('.tab-content');
        panels.forEach(p => p.classList.remove('active'));
        const firstPanel = document.getElementById('tab-' + tabs[0].id);
        if (firstPanel) firstPanel.classList.add('active');
    }

    function initTabs() {
        const btns = document.querySelectorAll('.tab-btn');
        btns.forEach(btn => {
            btn.addEventListener('click', function() {
                const tab = this.dataset.tab;
                btns.forEach(b => b.classList.remove('active'));
                this.classList.add('active');
                const panels = document.querySelectorAll('.tab-content');
                panels.forEach(p => p.classList.remove('active'));
                const target = document.getElementById('tab-' + tab);
                if (target) target.classList.add('active');
            });
        });
    }

    document.addEventListener('DOMContentLoaded', function() {
        loadManifest();
        fetchOffsets();
    });

})();