/* ═══════════════════════════════════════════════════════════════
 *  JAESWIFT.XYZ — Admin Panel Controller
 *  Complete admin application object
 *  ═══════════════════════════════════════════════════════════════ */

const AdminApp = {
    token: localStorage.getItem('admin_token'),
    API: '/api',
    currentSection: 'section-dashboard',
    homepageData: null,
    servicesData: [],
    navData: [],
    linksData: [],
    globeData: null,
    chatAIData: null,
    themeDefaults: {
        accent: '#d0d0d0',
        bg: '#111111',
        text: '#e0e0e0',
        scanlines: true,
        particles: true,
        glitch: false,
        grid: true,
        fontSize: 16
    },

    /* ───────────────────────── AUTH ───────────────────────── */

    init() {
        // Login form - support both <form> submit and button click
        const loginForm = document.getElementById('loginForm');
        if (loginForm) {
            loginForm.addEventListener('submit', (e) => this.login(e));
        }
        const loginBtn = document.getElementById('loginBtn');
        if (loginBtn && !loginForm) {
            loginBtn.addEventListener('click', (e) => this.login(e));
        }
        // Also handle Enter key in password field
        const loginPass = document.getElementById('loginPassword');
        if (loginPass) {
            loginPass.addEventListener('keydown', (e) => {
                if (e.key === 'Enter') this.login(e);
            });
        }

        // Check auth state
        if (this.checkAuth()) {
            this.showApp();
            this.loadDashboard();
        } else {
            this.showLogin();
        }

        // Mobile hamburger
        const mobileBtn = document.getElementById('mobileMenuBtn');
        if (mobileBtn) {
            mobileBtn.addEventListener('click', () => this.toggleSidebar());
        }

        // Sidebar overlay close
        const overlay = document.getElementById('sidebarOverlay');
        if (overlay) {
            overlay.addEventListener('click', () => this.toggleSidebar());
        }

        // Editor: auto-slug on title input
        const editTitle = document.getElementById('editorTitle');
        if (editTitle) {
            editTitle.addEventListener('input', () => this.autoSlug());
        }

        // Editor: live preview on content input
        const editContent = document.getElementById('editorContent');
        if (editContent) {
            editContent.addEventListener('input', () => this.updatePreview());
        }

        // Editor toolbar buttons
        document.querySelectorAll('[data-action]').forEach(btn => {
            btn.addEventListener('click', () => {
                this.insertMarkdown(btn.getAttribute('data-action'));
            });
        });

        // Operator HUD sliders
        ['hudEnergy', 'hudMotivation', 'hudFocus', 'hudDifficulty'].forEach(id => {
            const slider = document.getElementById(id);
            if (slider) {
                const valId = 'val' + id.replace('hud', '');
                const valEl = document.getElementById(valId);
                if (valEl) valEl.textContent = slider.value;
                slider.addEventListener('input', () => {
                    if (valEl) valEl.textContent = slider.value;
                });
            }
        });

        // Theme colour sync
        ['AccentColor', 'BgColor', 'TextColor'].forEach(name => {
            const picker = document.getElementById('theme' + name);
            const hex = document.getElementById('theme' + name + 'Hex');
            if (picker && hex) {
                picker.addEventListener('input', () => {
                    hex.value = picker.value;
                });
                hex.addEventListener('input', () => {
                    if (/^#[0-9a-fA-F]{6}$/.test(hex.value)) {
                        picker.value = hex.value;
                    }
                });
            }
        });

        // Theme font size slider
        const fontSlider = document.getElementById('themeFontSize');
        const valFont = document.getElementById('themeFontSizeVal');
        if (fontSlider && valFont) {
            valFont.textContent = fontSlider.value;
            fontSlider.addEventListener('input', () => {
                valFont.textContent = fontSlider.value;
            });
        }

        // Globe slider sync
        ['globeRotationSpeed', 'globeHexOpacity', 'globeAtmosphereAlt'].forEach(id => {
            const slider = document.getElementById(id);
            const valEl = document.getElementById(id + 'Val');
            if (slider && valEl) {
                slider.addEventListener('input', () => {
                    valEl.textContent = slider.value;
                });
            }
        });

        // Globe atmosphere colour sync
        const globeAtmoPicker = document.getElementById('globeAtmosphereColor');
        const globeAtmoHex = document.getElementById('globeAtmosphereColorHex');
        if (globeAtmoPicker && globeAtmoHex) {
            globeAtmoPicker.addEventListener('input', () => {
                globeAtmoHex.value = globeAtmoPicker.value;
            });
            globeAtmoHex.addEventListener('input', () => {
                if (/^#[0-9a-fA-F]{6}$/.test(globeAtmoHex.value)) {
                    globeAtmoPicker.value = globeAtmoHex.value;
                }
            });
        }
    },

    async login(e) {
        if (e && e.preventDefault) e.preventDefault();
        const user = document.getElementById('loginUsername').value.trim();
        const pass = document.getElementById('loginPassword').value;
        const errEl = document.getElementById('loginError');
        if (errEl) errEl.textContent = '';

        try {
            const res = await fetch(this.API + '/auth/login', {
                method: 'POST',
                headers: { 'Content-Type': 'application/json' },
                body: JSON.stringify({ username: user, password: pass })
            });
            const data = await res.json();
            if (res.ok && data.token) {
                this.token = data.token;
                localStorage.setItem('admin_token', this.token);
                this.showApp();
                this.loadDashboard();
            } else {
                errEl.textContent = data.error || 'Authentication failed';
            }
        } catch (err) {
            errEl.textContent = 'Connection error: ' + err.message;
        }
    },

    logout() {
        this.token = null;
        localStorage.removeItem('admin_token');
        this.showLogin();
    },

    checkAuth() {
        return !!this.token;
    },

    authHeaders() {
        return {
            'Authorization': 'Bearer ' + this.token,
            'Content-Type': 'application/json'
        };
    },

    showLogin() {
        const login = document.getElementById('loginScreen');
        const app = document.getElementById('adminApp');
        if (login) login.style.display = 'flex';
        if (app) app.style.display = 'none';
    },

    showApp() {
        const login = document.getElementById('loginScreen');
        const app = document.getElementById('adminApp');
        if (login) login.style.display = 'none';
        if (app) app.style.display = 'flex';
    },

    /* ─────────────────────── NAVIGATION ─────────────────────── */

    showSection(name) {
        // Normalize: ensure name has section- prefix
        if (!name.startsWith('section-')) name = 'section-' + name;

        // Hide all sections
        document.querySelectorAll('[id^="section-"]').forEach(s => {
            s.style.display = 'none';
        });

        // Show target
        const target = document.getElementById(name);
        if (target) target.style.display = 'block';
        this.currentSection = name;

        // Update sidebar active
        document.querySelectorAll('.sidebar a, .sidebar .nav-link').forEach(a => {
            a.classList.remove('active');
        });
        const shortName = name.replace('section-', '');
        const activeLink = document.querySelector(`.sidebar-link[data-section="${shortName}"]`);
        if (activeLink) activeLink.classList.add('active');

        // Update topbar title
        const titleMap = {
            'section-dashboard': 'Dashboard',
            'section-posts': 'Posts',
            'section-editor': 'Editor',
            'section-changelog': 'Changelog',
            'section-tracks': 'Tracks',
            'section-sitrep': 'SITREP',
            'section-datasync': 'Data Sync',
            'section-cronjobs': 'Cron Jobs',
            'section-settings': 'Settings',
            'section-homepage': 'Homepage',
            'section-services': 'Services',
            'section-navigation': 'Navigation',
            'section-links': 'Links',
            'section-apikeys': 'API Keys',
            'section-theme': 'Theme',
            'section-seo': 'SEO',
            'section-contact': 'Contact',
            'section-backups': 'Backups',
            'section-globe': 'Globe',
            'section-chatai': 'Chat AI'
        };
        const topTitle = document.getElementById('topbarTitle') || document.querySelector('.topbar-title');
        if (topTitle) topTitle.textContent = titleMap[name] || name.replace('section-', '').toUpperCase();

        // Load data for section
        switch (name) {
            case 'section-dashboard': this.loadDashboard(); break;
            case 'section-posts': this.loadPosts(); break;
            case 'section-editor': this.loadEditorPostList(); break;
            case 'section-tracks': this.loadTracks(); break;
            case 'section-settings': this.loadSettings(); break;
            case 'section-homepage': this.loadHomepage(); break;
            case 'section-services': this.loadServices(); break;
            case 'section-navigation': this.loadNavigation(); break;
            case 'section-links': this.loadLinks(); break;
            case 'section-apikeys': this.loadApiKeys(); break;
            case 'section-theme': this.loadTheme(); break;
            case 'section-seo': this.loadSeo(); break;
            case 'section-contact': this.loadContactSettings(); break;
            case 'section-globe': this.loadGlobe(); break;
            case 'section-chatai': this.loadChatAI(); break;
            case 'section-changelog': this.loadChangelog(); break;
            case 'section-sitrep': this.loadSitrep(); break;
            case 'section-datasync': this.loadDataSync(); break;
            case 'section-cronjobs': this.loadCronJobs(); break;
        }

        // Close mobile sidebar
        const sidebar = document.querySelector('.sidebar');
        if (sidebar) sidebar.classList.remove('open');
        const sOverlay = document.getElementById('sidebarOverlay');
        if (sOverlay) sOverlay.classList.remove('active');
    },

    toggleSidebar() {
        const sidebar = document.querySelector('.sidebar');
        const overlay = document.getElementById('sidebarOverlay');
        if (sidebar) sidebar.classList.toggle('open');
        if (overlay) overlay.classList.toggle('active');
    },

    /* ─────────────────── NOTIFICATIONS ─────────────────── */

    notify(msg, type = 'success') {
        const container = document.getElementById('notification') || document.body;
        const div = document.createElement('div');
        div.className = 'notification notification-' + type;
        div.innerHTML = `<span class="notification-icon">${type === 'success' ? '✓' : type === 'error' ? '✗' : 'ℹ'}</span><span>${msg}</span>`;

        // Styles
        Object.assign(div.style, {
            position: 'fixed',
            top: '20px',
            right: '20px',
            padding: '14px 24px',
            borderRadius: '4px',
            color: '#fff',
            fontFamily: 'JetBrains Mono, monospace',
            fontSize: '13px',
            zIndex: '10000',
            display: 'flex',
            alignItems: 'center',
            gap: '10px',
            animation: 'slideIn 0.3s ease',
            border: '1px solid',
            backdropFilter: 'blur(10px)',
            boxShadow: '0 4px 20px rgba(0,0,0,0.5)'
        });

        if (type === 'success') {
            div.style.background = 'rgba(0,255,200,0.15)';
            div.style.borderColor = '#d0d0d0';
        } else if (type === 'error') {
            div.style.background = 'rgba(255,50,50,0.15)';
            div.style.borderColor = '#ff3232';
        } else {
            div.style.background = 'rgba(0,150,255,0.15)';
            div.style.borderColor = '#0096ff';
        }

        container.appendChild(div);
        setTimeout(() => {
            div.style.opacity = '0';
            div.style.transition = 'opacity 0.3s ease';
            setTimeout(() => div.remove(), 300);
        }, 3000);
    },

    /* ─────────────────── DASHBOARD ─────────────────── */

    async loadDashboard() {
        const results = await Promise.allSettled([
            fetch(this.API + '/stats', { headers: this.authHeaders() }),
            fetch(this.API + '/posts', { headers: this.authHeaders() }),
            fetch(this.API + '/tracks', { headers: this.authHeaders() }),
            fetch(this.API + '/services', { headers: this.authHeaders() }),
            fetch(this.API + '/threats', { headers: this.authHeaders() })
        ]);

        // Stats
        try {
            if (results[0].status === 'fulfilled' && results[0].value.ok) {
                const stats = await results[0].value.json();
                const cpu = document.getElementById('statCPU');
                const mem = document.getElementById('statMEM');
                const disk = document.getElementById('statDISK');
                if (cpu) cpu.textContent = (stats.cpu_percent || stats.cpu || 0) + '%';
                if (mem) mem.textContent = (stats.memory_percent || stats.memory || 0) + '%';
                if (disk) disk.textContent = (stats.disk_percent || stats.disk || 0) + '%';
            }
        } catch (e) { console.warn('Dashboard stats error:', e); }

        // Posts count + word count
        try {
            if (results[1].status === 'fulfilled' && results[1].value.ok) {
                const posts = await results[1].value.json();
                const arr = Array.isArray(posts) ? posts : (posts.posts || []);
                const dashPosts = document.getElementById('statPosts');
                const dashWords = document.getElementById('statWords');
                if (dashPosts) dashPosts.textContent = arr.length;
                if (dashWords) {
                    const totalWords = arr.reduce((sum, p) => sum + (p.word_count || 0), 0);
                    dashWords.textContent = totalWords.toLocaleString();
                }
            }
        } catch (e) { console.warn('Dashboard posts error:', e); }

        // Tracks count
        try {
            if (results[2].status === 'fulfilled' && results[2].value.ok) {
                const tracks = await results[2].value.json();
                const arr = Array.isArray(tracks) ? tracks : (tracks.tracks || []);
                const dashTracks = document.getElementById('statTracks');
                if (dashTracks) dashTracks.textContent = arr.length;
            }
        } catch (e) { console.warn('Dashboard tracks error:', e); }

        // Services grid
        try {
            if (results[3].status === 'fulfilled' && results[3].value.ok) {
                const services = await results[3].value.json();
                const arr = Array.isArray(services) ? services : (services.services || []);
                const grid = document.getElementById('servicesGrid');
                if (grid) {
                    grid.innerHTML = arr.map(svc => {
                        const isUp = svc.status === 'up' || svc.status === 'online' || svc.status === true;
                        const dotColor = isUp ? '#d0d0d0' : '#ff3232';
                        const statusText = isUp ? 'ONLINE' : 'OFFLINE';
                        return `<div class="service-card">
                            <span class="service-dot" style="display:inline-block;width:8px;height:8px;border-radius:50%;background:${dotColor};box-shadow:0 0 8px ${dotColor};margin-right:8px;"></span>
                            <span class="service-name">${this.escapeHtml(svc.name || svc.service || 'Unknown')}</span>
                            <span class="service-status" style="color:${dotColor};margin-left:auto;font-size:11px;">${statusText}</span>
                        </div>`;
                    }).join('');
                }
            } else {
                const grid = document.getElementById('servicesGrid');
                if (grid) grid.innerHTML = '<div style="color:#ff6600;padding:12px;">Services check timed out</div>';
            }
        } catch (e) { console.warn('Dashboard services error:', e); }

        // Threats
        try {
            if (results[4].status === 'fulfilled' && results[4].value.ok) {
                const threats = await results[4].value.json();
                const arr = Array.isArray(threats) ? threats : (threats.threats || threats.items || []);
                const container = document.getElementById('dashThreats');
                if (container) {
                    if (arr.length === 0) {
                        container.innerHTML = '<div class="threat-empty">No active threats detected \u2713</div>';
                    } else {
                        container.innerHTML = arr.map(t => {
                            const severity = (t.severity || t.level || 'low').toLowerCase();
                            const sevColor = severity === 'critical' ? '#ff0040' : severity === 'high' ? '#ff6600' : severity === 'medium' ? '#c9a227' : '#d0d0d0';
                            return `<div class="threat-item" style="border-left:3px solid ${sevColor};padding:8px 12px;margin-bottom:6px;background:rgba(255,255,255,0.03);">
                                <div style="display:flex;justify-content:space-between;align-items:center;">
                                    <strong style="color:${sevColor};">${this.escapeHtml(t.cve || t.id || t.name || 'Unknown')}</strong>
                                    <span style="color:${sevColor};font-size:11px;text-transform:uppercase;">${severity}</span>
                                </div>
                                <div style="color:#888;font-size:12px;margin-top:4px;">${this.escapeHtml(t.description || t.summary || '')}</div>
                            </div>`;
                        }).join('');
                    }
                }
            }
        } catch (e) { console.warn('Dashboard threats error:', e); }
    },

    /* ─────────────────── POSTS ─────────────────── */

    async loadPosts() {
        try {
            const res = await fetch(this.API + '/posts', { headers: this.authHeaders() });
            if (!res.ok) throw new Error('Failed to load posts');
            const data = await res.json();
            const posts = Array.isArray(data) ? data : (data.posts || []);

            const tbody = document.getElementById('postsTableBody');
            if (!tbody) return;

            if (posts.length === 0) {
                tbody.innerHTML = '<tr><td colspan="5" style="text-align:center;color:#555;">No posts yet</td></tr>';
                return;
            }

            // Sort by date descending
            posts.sort((a, b) => new Date(b.date || 0) - new Date(a.date || 0));

            tbody.innerHTML = posts.map(p => {
                const date = p.date ? new Date(p.date).toLocaleDateString('en-GB') : '—';
                const words = p.word_count || 0;
                return `<tr>
                    <td>${this.escapeHtml(p.title || 'Untitled')}</td>
                    <td><code>${this.escapeHtml(p.slug || '')}</code></td>
                    <td>${date}</td>
                    <td>${words.toLocaleString()}</td>
                    <td class="post-actions">
                        <button class="btn-sm btn-edit" onclick="AdminApp.editPost('${this.escapeHtml(p.slug)}')" title="Edit">✎</button>
                        <button class="btn-sm btn-delete" onclick="AdminApp.deletePost('${this.escapeHtml(p.slug)}')" title="Delete">✗</button>
                    </td>
                </tr>`;
            }).join('');
        } catch (err) {
            console.error('Posts load error:', err);
            this.notify('Failed to load posts', 'error');
        }
    },

    newPost() {
        this.clearEditor();
        const heading = document.getElementById('editorTitle');
        if (heading) heading.textContent = 'NEW POST';
        this.showSection('section-editor');
    },

    async editPost(slug) {
        try {
            const res = await fetch(this.API + '/posts/' + encodeURIComponent(slug), { headers: this.authHeaders() });
            if (!res.ok) throw new Error('Failed to load post');
            const post = await res.json();

            const heading = document.getElementById('editorTitle');
            if (heading) heading.textContent = 'EDIT POST';

            document.getElementById('editorPostId').value = post.slug || slug;
            document.getElementById('editorTitle').value = post.title || '';
            document.getElementById('editorSlug').value = post.slug || '';

            // Parse date and time
            if (post.date) {
                const dt = new Date(post.date);
                const dateStr = dt.toISOString().split('T')[0];
                const timeStr = dt.toTimeString().slice(0, 5);
                document.getElementById('editorDate').value = dateStr;
                document.getElementById('editorTime').value = timeStr;
            } else {
                document.getElementById('editorDate').value = '';
                document.getElementById('editorTime').value = '';
            }

            document.getElementById('editorTags').value = Array.isArray(post.tags) ? post.tags.join(', ') : (post.tags || '');
            document.getElementById('editorExcerpt').value = post.excerpt || '';
            document.getElementById('editorContent').value = post.content || '';

            // Operator HUD fields
            const moodEl = document.getElementById('hudMood');
            if (moodEl) moodEl.value = post.mood || 'focused';

            const hudFields = [
                { id: 'hudEnergy', key: 'energy', valId: 'valEnergy' },
                { id: 'hudMotivation', key: 'motivation', valId: 'valMotivation' },
                { id: 'hudFocus', key: 'focus', valId: 'valFocus' },
                { id: 'hudDifficulty', key: 'difficulty', valId: 'valDifficulty' }
            ];
            hudFields.forEach(f => {
                const el = document.getElementById(f.id);
                const valEl = document.getElementById(f.valId);
                if (el) {
                    el.value = post[f.key] || 3;
                    if (valEl) valEl.textContent = el.value;
                }
            });

            const coffeeEl = document.getElementById('hudCoffee');
            if (coffeeEl) coffeeEl.value = post.coffee || 0;

            const bpmEl = document.getElementById('hudBPM');
            if (bpmEl) bpmEl.value = post.bpm || 0;

            const threatEl = document.getElementById('hudThreat');
            if (threatEl) threatEl.value = post.threat_level || 'low';

            this.updatePreview();
            this.showSection('section-editor');
        } catch (err) {
            console.error('Edit post error:', err);
            this.notify('Failed to load post for editing', 'error');
        }
    },

    async deletePost(slug) {
        if (!confirm('Delete post "' + slug + '"? This cannot be undone.')) return;
        try {
            const res = await fetch(this.API + '/posts/' + encodeURIComponent(slug), {
                method: 'DELETE',
                headers: this.authHeaders()
            });
            if (!res.ok) throw new Error('Delete failed');
            this.notify('Post deleted');
            this.loadPosts();
        } catch (err) {
            console.error('Delete post error:', err);
            this.notify('Failed to delete post', 'error');
        }
    },

    async savePost() {
        const postId = document.getElementById('editorPostId').value;
        const title = document.getElementById('editorTitle').value.trim();
        const slug = document.getElementById('editorSlug').value.trim();
        const date = document.getElementById('editorDate').value;
        const time = document.getElementById('editorTime').value || '00:00';
        const tags = document.getElementById('editorTags').value;
        const excerpt = document.getElementById('editorExcerpt').value.trim();
        const content = document.getElementById('editorContent').value;

        if (!title) { this.notify('Title is required', 'error'); return; }
        if (!slug) { this.notify('Slug is required', 'error'); return; }
        if (!content) { this.notify('Content is required', 'error'); return; }

        // Build datetime
        const datetime = date ? date + 'T' + time + ':00' : new Date().toISOString();

        // Word count
        const wordCount = content.trim().split(/\s+/).filter(w => w.length > 0).length;

        // Parse tags
        const tagList = tags.split(',').map(t => t.trim()).filter(t => t.length > 0);

        // Operator HUD
        const mood = document.getElementById('hudMood')?.value || 'focused';
        const energy = parseInt(document.getElementById('hudEnergy')?.value || 3);
        const motivation = parseInt(document.getElementById('hudMotivation')?.value || 3);
        const focus = parseInt(document.getElementById('hudFocus')?.value || 3);
        const difficulty = parseInt(document.getElementById('hudDifficulty')?.value || 3);
        const coffee = parseInt(document.getElementById('hudCoffee')?.value || 0);
        const bpm = parseInt(document.getElementById('hudBPM')?.value || 0);
        const threatLevel = document.getElementById('hudThreat')?.value || 'low';

        const payload = {
            title, slug, date: datetime, tags: tagList, excerpt, content,
            word_count: wordCount,
            mood, energy, motivation, focus, difficulty, coffee, bpm, threat_level: threatLevel
        };

        const isEdit = !!postId;
        const url = isEdit ? this.API + '/posts/' + encodeURIComponent(postId) : this.API + '/posts';
        const method = isEdit ? 'PUT' : 'POST';

        try {
            const res = await fetch(url, {
                method,
                headers: this.authHeaders(),
                body: JSON.stringify(payload)
            });
            if (!res.ok) {
                const errData = await res.json().catch(() => ({}));
                throw new Error(errData.error || 'Save failed');
            }
            this.notify(isEdit ? 'Post updated' : 'Post created');
            this.showSection('section-posts');
        } catch (err) {
            console.error('Save post error:', err);
            this.notify('Failed to save: ' + err.message, 'error');
        }
    },

    cancelEdit() {
        this.clearEditor();
        this.showSection('section-posts');
    },

    clearEditor() {
        document.getElementById('editorPostId').value = '';
        document.getElementById('editorTitle').value = '';
        document.getElementById('editorSlug').value = '';
        document.getElementById('editorDate').value = new Date().toISOString().split('T')[0];
        document.getElementById('editorTime').value = new Date().toTimeString().slice(0, 5);
        document.getElementById('editorTags').value = '';
        document.getElementById('editorExcerpt').value = '';
        document.getElementById('editorContent').value = '';

        const preview = document.getElementById('editorPreview');
        if (preview) preview.innerHTML = '';

        // Reset post selector
        const sel = document.getElementById('editorPostSelect');
        if (sel) sel.value = '';
        const delBtn = document.getElementById('editorDeleteBtn');
        if (delBtn) delBtn.style.display = 'none';

        // Reset HUD
        const moodEl = document.getElementById('hudMood');
        if (moodEl) moodEl.value = 'focused';

        ['hudEnergy', 'hudMotivation', 'hudFocus', 'hudDifficulty'].forEach(id => {
            const el = document.getElementById(id);
            if (el) {
                el.value = 3;
                const valId = 'val' + id.replace('hud', '');
                const valEl = document.getElementById(valId);
                if (valEl) valEl.textContent = '3';
            }
        });

        const coffeeEl = document.getElementById('hudCoffee');
        if (coffeeEl) coffeeEl.value = 0;
        const bpmEl = document.getElementById('hudBPM');
        if (bpmEl) bpmEl.value = 80;
        const threatEl = document.getElementById('hudThreat');
        if (threatEl) threatEl.value = 'low';
    },

    /* ─────────────────── EDITOR TOOLBAR ─────────────────── */

    insertMarkdown(action) {
        const textarea = document.getElementById('editorContent');
        if (!textarea) return;

        const start = textarea.selectionStart;
        const end = textarea.selectionEnd;
        const selected = textarea.value.substring(start, end);
        const before = textarea.value.substring(0, start);
        const after = textarea.value.substring(end);
        let insert = '';

        switch (action) {
            case 'bold':
                insert = `**${selected || 'bold text'}**`;
                break;
            case 'italic':
                insert = `*${selected || 'italic text'}*`;
                break;
            case 'strikethrough':
                insert = `~~${selected || 'strikethrough'}~~`;
                break;
            case 'code':
                insert = '`' + (selected || 'code') + '`';
                break;
            case 'code-block':
                insert = '\n```\n' + (selected || 'code block') + '\n```\n';
                break;
            case 'h1':
                insert = '\n# ' + (selected || 'Heading 1') + '\n';
                break;
            case 'h2':
                insert = '\n## ' + (selected || 'Heading 2') + '\n';
                break;
            case 'h3':
                insert = '\n### ' + (selected || 'Heading 3') + '\n';
                break;
            case 'link':
                insert = selected ? `[${selected}](url)` : '[link text](url)';
                break;
            case 'image':
                insert = selected ? `![${selected}](url)` : '![alt text](image-url)';
                break;
            case 'quote':
                insert = '\n> ' + (selected || 'blockquote') + '\n';
                break;
            case 'list-ul':
                if (selected) {
                    insert = '\n' + selected.split('\n').map(l => '- ' + l).join('\n') + '\n';
                } else {
                    insert = '\n- Item 1\n- Item 2\n- Item 3\n';
                }
                break;
            case 'list-ol':
                if (selected) {
                    insert = '\n' + selected.split('\n').map((l, i) => (i + 1) + '. ' + l).join('\n') + '\n';
                } else {
                    insert = '\n1. Item 1\n2. Item 2\n3. Item 3\n';
                }
                break;
            case 'hr':
                insert = '\n---\n';
                break;
            default:
                insert = selected;
        }

        textarea.value = before + insert + after;
        textarea.focus();
        const newCursor = start + insert.length;
        textarea.setSelectionRange(newCursor, newCursor);
        this.updatePreview();
    },

    updatePreview() {
        const content = document.getElementById('editorContent')?.value || '';
        const preview = document.getElementById('editorPreview');
        if (!preview) return;

        let html = this.escapeHtml(content);

        // Code blocks (must process before inline code)
        html = html.replace(/```([\s\S]*?)```/g, '<pre><code>$1</code></pre>');

        // Inline code
        html = html.replace(/`([^`]+)`/g, '<code>$1</code>');

        // Headers
        html = html.replace(/^### (.+)$/gm, '<h3>$1</h3>');
        html = html.replace(/^## (.+)$/gm, '<h2>$1</h2>');
        html = html.replace(/^# (.+)$/gm, '<h1>$1</h1>');

        // Bold and italic
        html = html.replace(/\*\*\*(.+?)\*\*\*/g, '<strong><em>$1</em></strong>');
        html = html.replace(/\*\*(.+?)\*\*/g, '<strong>$1</strong>');
        html = html.replace(/\*(.+?)\*/g, '<em>$1</em>');

        // Strikethrough
        html = html.replace(/~~(.+?)~~/g, '<del>$1</del>');

        // Blockquotes
        html = html.replace(/^&gt; (.+)$/gm, '<blockquote>$1</blockquote>');

        // Horizontal rule
        html = html.replace(/^---$/gm, '<hr>');

        // Images
        html = html.replace(/!\[([^\]]*)\]\(([^)]+)\)/g, '<img src="$2" alt="$1" style="max-width:100%;">');

        // Links
        html = html.replace(/\[([^\]]+)\]\(([^)]+)\)/g, '<a href="$2" target="_blank" style="color:#d0d0d0;">$1</a>');

        // Unordered lists
        html = html.replace(/^- (.+)$/gm, '<li>$1</li>');
        html = html.replace(/(<li>.*<\/li>\n?)+/g, '<ul>$&</ul>');

        // Ordered lists
        html = html.replace(/^\d+\. (.+)$/gm, '<li>$1</li>');

        // Paragraphs: double newline
        html = html.replace(/\n\n/g, '</p><p>');
        html = '<p>' + html + '</p>';

        // Single newlines to br (within paragraphs)
        html = html.replace(/\n/g, '<br>');

        // Clean empty paragraphs
        html = html.replace(/<p><\/p>/g, '');

        preview.innerHTML = html;
    },

    autoSlug() {
        const title = document.getElementById('editorTitle')?.value || '';
        const slug = title
            .toLowerCase()
            .trim()
            .replace(/[^a-z0-9\s-]/g, '')
            .replace(/\s+/g, '-')
            .replace(/-+/g, '-')
            .replace(/^-|-$/g, '');
        const slugEl = document.getElementById('editorSlug');
        if (slugEl) slugEl.value = slug;
    },

    /* ─────────────────── TRACKS ─────────────────── */

    async loadTracks() {
        try {
            const res = await fetch(this.API + '/tracks', { headers: this.authHeaders() });
            if (!res.ok) throw new Error('Failed to load tracks');
            const data = await res.json();
            const tracks = Array.isArray(data) ? data : (data.tracks || []);

            const container = document.getElementById('tracksList');
            if (!container) return;

            if (tracks.length === 0) {
                container.innerHTML = '<div class="empty-state">No tracks in library</div>';
                return;
            }

            container.innerHTML = tracks.map((t, i) => {
                const coverHtml = t.cover
                    ? `<img src="${this.escapeHtml(t.cover)}" alt="cover" style="width:40px;height:40px;border-radius:4px;object-fit:cover;margin-right:12px;">`
                    : '<div style="width:40px;height:40px;border-radius:4px;background:#1a1f2e;margin-right:12px;display:flex;align-items:center;justify-content:center;color:#555;">♪</div>';
                return `<div class="track-item" style="display:flex;align-items:center;padding:10px;border-bottom:1px solid rgba(255,255,255,0.05);">
                    ${coverHtml}
                    <div style="flex:1;">
                        <div style="font-weight:600;color:#e0e0e0;">${this.escapeHtml(t.artist || 'Unknown')} — ${this.escapeHtml(t.title || 'Untitled')}</div>
                        <div style="font-size:12px;color:#555;">${this.escapeHtml(t.album || '')} ${t.genre ? '· ' + this.escapeHtml(t.genre) : ''}</div>
                    </div>
                    ${t.url ? `<a href="${this.escapeHtml(t.url)}" target="_blank" style="color:#d0d0d0;margin-right:12px;font-size:12px;">▶</a>` : ''}
                    <button class="btn-sm btn-delete" onclick="AdminApp.deleteTrack(${i})" title="Delete">✗</button>
                </div>`;
            }).join('');
        } catch (err) {
            console.error('Tracks load error:', err);
            this.notify('Failed to load tracks', 'error');
        }
    },

    async addTrack() {
        const title = document.getElementById('trackTitle')?.value.trim();
        const artist = document.getElementById('trackArtist')?.value.trim();
        const album = document.getElementById('trackAlbum')?.value.trim();
        const genre = document.getElementById('trackGenre')?.value.trim();
        const url = document.getElementById('trackURL')?.value.trim();
        const cover = document.getElementById('trackCover')?.value.trim();

        if (!title || !artist) {
            this.notify('Title and artist are required', 'error');
            return;
        }

        const payload = { title, artist, album, genre, url, cover };

        try {
            const res = await fetch(this.API + '/tracks', {
                method: 'POST',
                headers: this.authHeaders(),
                body: JSON.stringify(payload)
            });
            if (!res.ok) throw new Error('Failed to add track');
            this.notify('Track added');

            // Clear inputs
            ['trackTitle', 'trackArtist', 'trackAlbum', 'trackGenre', 'trackURL', 'trackCover'].forEach(id => {
                const el = document.getElementById(id);
                if (el) el.value = '';
            });

            this.loadTracks();
        } catch (err) {
            console.error('Add track error:', err);
            this.notify('Failed to add track', 'error');
        }
    },

    async deleteTrack(index) {
        if (!confirm('Delete this track?')) return;
        try {
            const res = await fetch(this.API + '/tracks/' + index, {
                method: 'DELETE',
                headers: this.authHeaders()
            });
            if (!res.ok) throw new Error('Delete failed');
            this.notify('Track deleted');
            this.loadTracks();
        } catch (err) {
            console.error('Delete track error:', err);
            this.notify('Failed to delete track', 'error');
        }
    },

    /* ─────────────────── SETTINGS (WIDGET TOGGLES) ─────────────────── */

    async loadSettings() {
        try {
            const res = await fetch(this.API + '/settings', { headers: this.authHeaders() });
            if (!res.ok) throw new Error('Failed to load settings');
            const settings = await res.json();

            // Set toggle states
            document.querySelectorAll('[data-key]').forEach(toggle => {
                const key = toggle.getAttribute('data-key');
                if (settings.hasOwnProperty(key)) {
                    toggle.checked = !!settings[key];
                }
            });
        } catch (err) {
            console.error('Settings load error:', err);
            this.notify('Failed to load settings', 'error');
        }
    },

    async saveSettings() {
        const settings = {};
        document.querySelectorAll('[data-key]').forEach(toggle => {
            settings[toggle.getAttribute('data-key')] = toggle.checked;
        });

        try {
            const res = await fetch(this.API + '/settings', {
                method: 'POST',
                headers: this.authHeaders(),
                body: JSON.stringify(settings)
            });
            if (!res.ok) throw new Error('Save failed');
            this.notify('Settings saved');
        } catch (err) {
            console.error('Save settings error:', err);
            this.notify('Failed to save settings', 'error');
        }
    },

    /* ─────────────────── HOMEPAGE ─────────────────── */

    async loadHomepage() {
        try {
            const res = await fetch(this.API + '/homepage', { headers: this.authHeaders() });
            if (!res.ok) throw new Error('Failed to load homepage config');
            const data = await res.json();
            this.homepageData = data;

            document.getElementById('heroTitle').value = data.hero_title || '';
            document.getElementById('heroSubtitle').value = data.hero_subtitle || '';
            document.getElementById('heroTagline').value = data.hero_tagline || '';
            document.getElementById('aboutText').value = data.about_text || '';

            this.renderHomepageSections(data.sections || []);
        } catch (err) {
            console.error('Homepage load error:', err);
            this.notify('Failed to load homepage config', 'error');
        }
    },

    renderHomepageSections(sections) {
        const container = document.getElementById('homepageSections');
        if (!container) return;

        const defaultSections = [
            { key: 'about', label: 'About', visible: true },
            { key: 'blog', label: 'Blog', visible: true },
            { key: 'dev-cards', label: 'Dev Cards', visible: true },
            { key: 'links', label: 'Links', visible: true },
            { key: 'contact', label: 'Contact', visible: true },
            { key: 'terminal', label: 'Terminal', visible: true }
        ];

        const sectionList = sections.length > 0 ? sections : defaultSections;
        if (this.homepageData) this.homepageData.sections = sectionList;

        container.innerHTML = sectionList.map((s, i) => {
            const key = s.key || s.name || s;
            const label = s.label || s.name || key;
            const visible = s.visible !== false;
            return `<div class="section-item" data-section="${this.escapeHtml(key)}" style="display:flex;align-items:center;padding:10px;border:1px solid rgba(255,255,255,0.08);margin-bottom:6px;border-radius:4px;background:rgba(255,255,255,0.02);">
                <span style="flex:1;color:#e0e0e0;font-weight:500;">${this.escapeHtml(label)}</span>
                <label class="toggle-switch" style="margin-right:12px;">
                    <input type="checkbox" ${visible ? 'checked' : ''} onchange="AdminApp.toggleHomepageSection('${this.escapeHtml(key)}', this.checked)">
                    <span class="toggle-slider"></span>
                </label>
                <button class="btn-sm" onclick="AdminApp.moveSection('${this.escapeHtml(key)}', 'up')" ${i === 0 ? 'disabled' : ''} title="Move up" style="margin-right:4px;">▲</button>
                <button class="btn-sm" onclick="AdminApp.moveSection('${this.escapeHtml(key)}', 'down')" ${i === sectionList.length - 1 ? 'disabled' : ''} title="Move down">▼</button>
            </div>`;
        }).join('');
    },

    toggleHomepageSection(key, visible) {
        if (!this.homepageData || !this.homepageData.sections) return;
        const section = this.homepageData.sections.find(s => (s.key || s.name || s) === key);
        if (section) section.visible = visible;
    },

    moveSection(sectionKey, direction) {
        if (!this.homepageData || !this.homepageData.sections) return;
        const sections = this.homepageData.sections;
        const idx = sections.findIndex(s => (s.key || s.name || s) === sectionKey);
        if (idx === -1) return;

        const targetIdx = direction === 'up' ? idx - 1 : idx + 1;
        if (targetIdx < 0 || targetIdx >= sections.length) return;

        // Swap
        [sections[idx], sections[targetIdx]] = [sections[targetIdx], sections[idx]];
        this.renderHomepageSections(sections);
    },

    async saveHomepage() {
        const payload = {
            hero_title: document.getElementById('heroTitle')?.value.trim() || '',
            hero_subtitle: document.getElementById('heroSubtitle')?.value.trim() || '',
            hero_tagline: document.getElementById('heroTagline')?.value.trim() || '',
            about_text: document.getElementById('aboutText')?.value.trim() || '',
            sections: this.homepageData?.sections || []
        };

        try {
            const res = await fetch(this.API + '/homepage', {
                method: 'POST',
                headers: this.authHeaders(),
                body: JSON.stringify(payload)
            });
            if (!res.ok) throw new Error('Save failed');
            this.notify('Homepage config saved');
        } catch (err) {
            console.error('Save homepage error:', err);
            this.notify('Failed to save homepage config', 'error');
        }
    },

    /* ─────────────────── SERVICES ─────────────────── */

    async loadServices() {
        try {
            const res = await fetch(this.API + '/services/managed', { headers: this.authHeaders() });
            if (!res.ok) throw new Error('Failed to load services');
            const data = await res.json();
            this.servicesData = Array.isArray(data) ? data : (data.services || []);

            const container = document.getElementById('servicesList');
            if (!container) return;

            if (this.servicesData.length === 0) {
                container.innerHTML = '<div class="empty-state">No managed services</div>';
                return;
            }

            container.innerHTML = this.servicesData.map((s, i) => {
                return `<div class="service-list-item" style="display:flex;align-items:center;padding:10px;border-bottom:1px solid rgba(255,255,255,0.05);">
                    <div style="flex:1;">
                        <div style="font-weight:600;color:#e0e0e0;">${this.escapeHtml(s.name || 'Unnamed')}</div>
                        <div style="font-size:12px;color:#555;">${this.escapeHtml(s.url || '')}</div>
                    </div>
                    <button class="btn-sm btn-delete" onclick="AdminApp.deleteService(${i})" title="Delete">✗</button>
                </div>`;
            }).join('');
        } catch (err) {
            console.error('Services load error:', err);
            this.notify('Failed to load services', 'error');
        }
    },

    async addService() {
        const name = document.getElementById('serviceName')?.value.trim();
        const url = document.getElementById('serviceURL')?.value.trim();

        if (!name) {
            this.notify('Service name is required', 'error');
            return;
        }

        this.servicesData.push({ name, url });

        try {
            const res = await fetch(this.API + '/services/managed', {
                method: 'POST',
                headers: this.authHeaders(),
                body: JSON.stringify({ name, url })
            });
            if (!res.ok) throw new Error('Save failed');
            this.notify('Service added');
            document.getElementById('serviceName').value = '';
            document.getElementById('serviceURL').value = '';
            this.loadServices();
        } catch (err) {
            console.error('Add service error:', err);
            this.servicesData.pop();
            this.notify('Failed to add service', 'error');
        }
    },

    async deleteService(index) {
        if (!confirm('Delete this service?')) return;
        this.servicesData.splice(index, 1);

        try {
            const res = await fetch(this.API + '/services/managed', {
                method: 'POST',
                headers: this.authHeaders(),
                body: JSON.stringify({ name, url })
            });
            if (!res.ok) throw new Error('Delete failed');
            this.notify('Service deleted');
            this.loadServices();
        } catch (err) {
            console.error('Delete service error:', err);
            this.notify('Failed to delete service', 'error');
        }
    },

    /* ─────────────────── NAVIGATION EDITOR ─────────────────── */

    async loadNavigation() {
        try {
            const res = await fetch(this.API + '/navigation', { headers: this.authHeaders() });
            if (!res.ok) throw new Error('Failed to load navigation');
            const data = await res.json();
            this.navData = Array.isArray(data) ? data : (data.items || []);

            const container = document.getElementById('navList');
            if (!container) return;

            if (this.navData.length === 0) {
                container.innerHTML = '<div class="empty-state">No navigation items</div>';
                return;
            }

            // Sort by order
            this.navData.sort((a, b) => (a.order || 0) - (b.order || 0));

            container.innerHTML = this.navData.map((item, i) => {
                return `<div class="nav-list-item" style="display:flex;align-items:center;padding:10px;border-bottom:1px solid rgba(255,255,255,0.05);">
                    <span style="color:#d0d0d0;font-size:12px;margin-right:12px;min-width:30px;">#${item.order || i}</span>
                    <div style="flex:1;">
                        <div style="font-weight:600;color:#e0e0e0;">${this.escapeHtml(item.label || 'Unnamed')}</div>
                        <div style="font-size:12px;color:#555;">${this.escapeHtml(item.url || '')}</div>
                    </div>
                    <button class="btn-sm btn-delete" onclick="AdminApp.deleteNavItem(${i})" title="Delete">✗</button>
                </div>`;
            }).join('');
        } catch (err) {
            console.error('Navigation load error:', err);
            this.notify('Failed to load navigation', 'error');
        }
    },

    async addNavItem() {
        const label = document.getElementById('navLabel')?.value.trim();
        const url = document.getElementById('navURL')?.value.trim();
        const order = parseInt(document.getElementById('navOrder')?.value || 0);

        if (!label || !url) {
            this.notify('Label and URL are required', 'error');
            return;
        }

        this.navData.push({ label, url, order });

        try {
            const res = await fetch(this.API + '/navigation', {
                method: 'POST',
                headers: this.authHeaders(),
                body: JSON.stringify({ label, url, order })
            });
            if (!res.ok) throw new Error('Save failed');
            this.notify('Nav item added');
            document.getElementById('navLabel').value = '';
            document.getElementById('navURL').value = '';
            document.getElementById('navOrder').value = '';
            this.loadNavigation();
        } catch (err) {
            console.error('Add nav item error:', err);
            this.navData.pop();
            this.notify('Failed to add nav item', 'error');
        }
    },

    async deleteNavItem(index) {
        if (!confirm('Delete this navigation item?')) return;

        try {
            const res = await fetch(this.API + '/navigation/' + index, {
                method: 'DELETE',
                headers: this.authHeaders()
            });
            if (!res.ok) throw new Error('Delete failed');
            this.notify('Nav item deleted');
            this.loadNavigation();
        } catch (err) {
            console.error('Delete nav item error:', err);
            this.notify('Failed to delete nav item', 'error');
        }
    },

    /* ─────────────────── LINKS ─────────────────── */

    async loadLinks() {
        try {
            const res = await fetch(this.API + '/links', { headers: this.authHeaders() });
            if (!res.ok) throw new Error('Failed to load links');
            const data = await res.json();
            this.linksData = Array.isArray(data) ? data : (data.links || []);

            const container = document.getElementById('linksList');
            if (!container) return;

            if (this.linksData.length === 0) {
                container.innerHTML = '<div class="empty-state">No links configured</div>';
                return;
            }

            container.innerHTML = this.linksData.map((link, i) => {
                return `<div class="link-list-item" style="display:flex;align-items:center;padding:10px;border-bottom:1px solid rgba(255,255,255,0.05);">
                    <span style="font-size:18px;margin-right:12px;">${this.escapeHtml(link.icon || '🔗')}</span>
                    <div style="flex:1;">
                        <div style="font-weight:600;color:#e0e0e0;">${this.escapeHtml(link.name || 'Unnamed')}</div>
                        <div style="font-size:12px;color:#555;">${this.escapeHtml(link.url || '')} ${link.category ? '<span style="color:#d0d0d0;">· ' + this.escapeHtml(link.category) + '</span>' : ''}</div>
                    </div>
                    <button class="btn-sm btn-delete" onclick="AdminApp.deleteLink(${i})" title="Delete">✗</button>
                </div>`;
            }).join('');
        } catch (err) {
            console.error('Links load error:', err);
            this.notify('Failed to load links', 'error');
        }
    },

    async addLink() {
        const name = document.getElementById('linkName')?.value.trim();
        const url = document.getElementById('linkURL')?.value.trim();
        const icon = document.getElementById('linkIcon')?.value.trim();
        const category = document.getElementById('linkCategory')?.value.trim();

        if (!name || !url) {
            this.notify('Name and URL are required', 'error');
            return;
        }

        this.linksData.push({ name, url, icon: icon || '🔗', category: category || 'general' });

        try {
            const res = await fetch(this.API + '/links', {
                method: 'POST',
                headers: this.authHeaders(),
                body: JSON.stringify({ name, url, icon: icon || '🔗', category: category || 'general' })
            });
            if (!res.ok) throw new Error('Save failed');
            this.notify('Link added');
            ['linkName', 'linkURL', 'linkIcon', 'linkCategory'].forEach(id => {
                const el = document.getElementById(id);
                if (el) el.value = '';
            });
            this.loadLinks();
        } catch (err) {
            console.error('Add link error:', err);
            this.linksData.pop();
            this.notify('Failed to add link', 'error');
        }
    },

    async deleteLink(index) {
        if (!confirm('Delete this link?')) return;
        this.linksData.splice(index, 1);

        try {
            const res = await fetch(this.API + '/links', {
                method: 'POST',
                headers: this.authHeaders(),
                body: JSON.stringify({ name, url, icon: icon || '🔗', category: category || 'general' })
            });
            if (!res.ok) throw new Error('Delete failed');
            this.notify('Link deleted');
            this.loadLinks();
        } catch (err) {
            console.error('Delete link error:', err);
            this.notify('Failed to delete link', 'error');
        }
    },

    /* ─────────────────── API KEYS ─────────────────── */

    async loadApiKeys() {
        try {
            const res = await fetch(this.API + '/apikeys', { headers: this.authHeaders() });
            if (!res.ok) throw new Error('Failed to load API keys');
            const data = await res.json();

            // Weather
            this.setVal('apiWeatherKey', data.weather_api_key);

            // Spotify
            this.setVal('apiSpotifyClientId', data.spotify_client_id);
            this.setVal('apiSpotifyClientSecret', data.spotify_client_secret);
            this.setVal('apiSpotifyRefreshToken', data.spotify_refresh_token);

            // SMTP
            this.setVal('apiSmtpHost', data.smtp_host);
            this.setVal('apiSmtpPort', data.smtp_port);
            this.setVal('apiSmtpUser', data.smtp_user);
            this.setVal('apiSmtpPass', data.smtp_pass);

            // Discord / GitHub
            this.setVal('apiDiscordWebhook', data.discord_webhook);
            this.setVal('apiGithubToken', data.github_token);

            // Custom APIs
            for (let i = 1; i <= 3; i++) {
                this.setVal(`apiCustom${i}Name`, data[`custom_api_${i}_name`]);
                this.setVal(`apiCustom${i}Key`, data[`custom_api_${i}_key`]);
                this.setVal(`apiCustom${i}URL`, data[`custom_api_${i}_url`]);
            }
        } catch (err) {
            console.error('API keys load error:', err);
            this.notify('Failed to load API keys', 'error');
        }
    },

    async saveApiKey(group) {
        const data = {};

        switch (group) {
            case 'weather':
                data.api_key = this.getVal('apiWeatherKey');
                break;
            case 'spotify':
                data.client_id = this.getVal('apiSpotifyClientId');
                data.client_secret = this.getVal('apiSpotifyClientSecret');
                data.refresh_token = this.getVal('apiSpotifyRefreshToken');
                break;
            case 'smtp':
                data.host = this.getVal('apiSmtpHost');
                data.port = this.getVal('apiSmtpPort');
                data.user = this.getVal('apiSmtpUser');
                data.pass = this.getVal('apiSmtpPass');
                break;
            case 'discord':
                data.webhook = this.getVal('apiDiscordWebhook');
                break;
            case 'github':
                data.token = this.getVal('apiGithubToken');
                break;
            case 'custom1':
                data.name = this.getVal('apiCustom1Name');
                data.key = this.getVal('apiCustom1Key');
                data.url = this.getVal('apiCustom1URL');
                break;
            case 'custom2':
                data.name = this.getVal('apiCustom2Name');
                data.key = this.getVal('apiCustom2Key');
                data.url = this.getVal('apiCustom2URL');
                break;
            case 'custom3':
                data.name = this.getVal('apiCustom3Name');
                data.key = this.getVal('apiCustom3Key');
                data.url = this.getVal('apiCustom3URL');
                break;
            default:
                this.notify('Unknown API key group: ' + group, 'error');
                return;
        }

        try {
            const res = await fetch(this.API + '/apikeys', {
                method: 'POST',
                headers: this.authHeaders(),
                body: JSON.stringify({ group, data })
            });
            if (!res.ok) throw new Error('Save failed');
            this.notify('API keys saved (' + group + ')');
        } catch (err) {
            console.error('Save API key error:', err);
            this.notify('Failed to save API keys', 'error');
        }
    },

    /* ─────────────────── THEME ─────────────────── */

    async loadTheme() {
        try {
            const res = await fetch(this.API + '/theme', { headers: this.authHeaders() });
            if (!res.ok) throw new Error('Failed to load theme');
            const data = await res.json();

            // Colours
            this.setVal('themeAccentColor', data.accent || this.themeDefaults.accent);
            this.setVal('themeAccentColorHex', data.accent || this.themeDefaults.accent);
            this.setVal('themeBgColor', data.bg || this.themeDefaults.bg);
            this.setVal('themeBgColorHex', data.bg || this.themeDefaults.bg);
            this.setVal('themeTextColor', data.text || this.themeDefaults.text);
            this.setVal('themeTextColorHex', data.text || this.themeDefaults.text);

            // Effect toggles
            this.setChecked('themeScanlines', data.scanlines !== undefined ? data.scanlines : this.themeDefaults.scanlines);
            this.setChecked('themeParticles', data.particles !== undefined ? data.particles : this.themeDefaults.particles);
            this.setChecked('themeGlitch', data.glitch !== undefined ? data.glitch : this.themeDefaults.glitch);
            this.setChecked('themeGridBg', data.grid !== undefined ? data.grid : this.themeDefaults.grid);

            // Font size
            const fontSize = data.font_size || this.themeDefaults.fontSize;
            this.setVal('themeFontSize', fontSize);
            const valFont = document.getElementById('themeFontSizeVal');
            if (valFont) valFont.textContent = fontSize;
        } catch (err) {
            console.error('Theme load error:', err);
            this.notify('Failed to load theme', 'error');
        }
    },

    async saveTheme() {
        const payload = {
            accent: this.getVal('themeAccentColorHex') || this.getVal('themeAccentColor'),
            bg: this.getVal('themeBgColorHex') || this.getVal('themeBgColor'),
            text: this.getVal('themeTextColorHex') || this.getVal('themeTextColor'),
            scanlines: this.getChecked('themeScanlines'),
            particles: this.getChecked('themeParticles'),
            glitch: this.getChecked('themeGlitch'),
            grid: this.getChecked('themeGridBg'),
            font_size: parseInt(this.getVal('themeFontSize') || 16)
        };

        try {
            const res = await fetch(this.API + '/theme', {
                method: 'POST',
                headers: this.authHeaders(),
                body: JSON.stringify(payload)
            });
            if (!res.ok) throw new Error('Save failed');
            this.notify('Theme saved');
        } catch (err) {
            console.error('Save theme error:', err);
            this.notify('Failed to save theme', 'error');
        }
    },

    resetTheme() {
        if (!confirm('Reset theme to defaults?')) return;

        this.setVal('themeAccentColor', this.themeDefaults.accent);
        this.setVal('themeAccentColorHex', this.themeDefaults.accent);
        this.setVal('themeBgColor', this.themeDefaults.bg);
        this.setVal('themeBgColorHex', this.themeDefaults.bg);
        this.setVal('themeTextColor', this.themeDefaults.text);
        this.setVal('themeTextColorHex', this.themeDefaults.text);
        this.setChecked('themeScanlines', this.themeDefaults.scanlines);
        this.setChecked('themeParticles', this.themeDefaults.particles);
        this.setChecked('themeGlitch', this.themeDefaults.glitch);
        this.setChecked('themeGridBg', this.themeDefaults.grid);
        this.setVal('themeFontSize', this.themeDefaults.fontSize);
        const valFont = document.getElementById('themeFontSizeVal');
        if (valFont) valFont.textContent = this.themeDefaults.fontSize;

        this.notify('Theme reset to defaults — save to apply', 'info');
    },

    syncColorInput(pickerId, hexId) {
        const picker = document.getElementById(pickerId);
        const hex = document.getElementById(hexId);
        if (picker && hex) hex.value = picker.value;
    },

    /* ─────────────────── SEO ─────────────────── */

    async loadSeo() {
        try {
            const res = await fetch(this.API + '/seo', { headers: this.authHeaders() });
            if (!res.ok) throw new Error('Failed to load SEO settings');
            const data = await res.json();

            this.setVal('seoTitle', data.title);
            this.setVal('seoDescription', data.description);
            this.setVal('seoKeywords', data.keywords);
            this.setVal('seoOgImage', data.og_image);
            this.setVal('seoFavicon', data.favicon);
        } catch (err) {
            console.error('SEO load error:', err);
            this.notify('Failed to load SEO settings', 'error');
        }
    },

    async saveSeo() {
        const payload = {
            title: this.getVal('seoTitle'),
            description: this.getVal('seoDescription'),
            keywords: this.getVal('seoKeywords'),
            og_image: this.getVal('seoOgImage'),
            favicon: this.getVal('seoFavicon')
        };

        try {
            const res = await fetch(this.API + '/seo', {
                method: 'POST',
                headers: this.authHeaders(),
                body: JSON.stringify(payload)
            });
            if (!res.ok) throw new Error('Save failed');
            this.notify('SEO settings saved');
        } catch (err) {
            console.error('Save SEO error:', err);
            this.notify('Failed to save SEO settings', 'error');
        }
    },

    /* ─────────────────── CONTACT ─────────────────── */

    async loadContactSettings() {
        try {
            const res = await fetch(this.API + '/contact-settings', { headers: this.authHeaders() });
            if (!res.ok) throw new Error('Failed to load contact settings');
            const data = await res.json();

            this.setVal('contactEmail', data.email);
            this.setVal('contactAutoReply', data.auto_reply);
            this.setChecked('contactFormEnabled', data.form_enabled !== false);
        } catch (err) {
            console.error('Contact settings load error:', err);
            this.notify('Failed to load contact settings', 'error');
        }
    },

    async saveContactSettings() {
        const payload = {
            email: this.getVal('contactEmail'),
            auto_reply: this.getVal('contactAutoReply'),
            form_enabled: this.getChecked('contactFormEnabled')
        };

        try {
            const res = await fetch(this.API + '/contact-settings', {
                method: 'POST',
                headers: this.authHeaders(),
                body: JSON.stringify(payload)
            });
            if (!res.ok) throw new Error('Save failed');
            this.notify('Contact settings saved');
        } catch (err) {
            console.error('Save contact settings error:', err);
            this.notify('Failed to save contact settings', 'error');
        }
    },

    /* ─────────────────── BACKUPS ─────────────────── */

    async downloadBackup(type) {
        try {
            const url = this.API + '/backups/' + type;
            const res = await fetch(url, { headers: this.authHeaders() });
            if (!res.ok) throw new Error('Download failed');

            const blob = await res.blob();
            const filename = type === 'all' ? 'jaeswift-backup-all.zip' : type + '.json';

            const a = document.createElement('a');
            a.href = URL.createObjectURL(blob);
            a.download = filename;
            document.body.appendChild(a);
            a.click();
            document.body.removeChild(a);
            URL.revokeObjectURL(a.href);

            this.notify('Backup downloaded: ' + filename);
        } catch (err) {
            console.error('Backup download error:', err);
            this.notify('Failed to download backup', 'error');
        }
    },

    /* ─────────────────── GLOBE ─────────────────── */

    async loadGlobe() {
        try {
            const res = await fetch(this.API + '/globe', { headers: this.authHeaders() });
            if (!res.ok) throw new Error('Failed to load globe config');
            const data = await res.json();
            this.globeData = data;

            // Server location
            this.setVal('globeServerLat', data.server_lat);
            this.setVal('globeServerLng', data.server_lng);
            this.setVal('globeServerLabel', data.server_label);

            // Globe settings
            const rotSpeed = data.rotation_speed || 0.3;
            this.setVal('globeRotationSpeed', rotSpeed);
            const rotVal = document.getElementById('globeRotationSpeedVal');
            if (rotVal) rotVal.textContent = rotSpeed;

            const hexOp = data.hex_polygon_opacity || 0.55;
            this.setVal('globeHexOpacity', hexOp);
            const hexVal = document.getElementById('globeHexOpacityVal');
            if (hexVal) hexVal.textContent = hexOp;

            // Atmosphere
            const atmoColor = data.atmosphere_color || '#00cc33';
            this.setVal('globeAtmosphereColor', atmoColor);
            this.setVal('globeAtmosphereColorHex', atmoColor);

            const atmoAlt = data.atmosphere_altitude || 0.2;
            this.setVal('globeAtmosphereAlt', atmoAlt);
            const atmoVal = document.getElementById('globeAtmosphereAltVal');
            if (atmoVal) atmoVal.textContent = atmoAlt;

            // Arc cities table
            this.renderArcCities(data.arc_cities || []);
        } catch (err) {
            console.error('Globe load error:', err);
            this.notify('Failed to load globe config', 'error');
        }
    },

    renderArcCities(cities) {
        const tbody = document.getElementById('arcCitiesBody');
        if (!tbody) return;
        if (!cities || cities.length === 0) {
            tbody.innerHTML = '<tr><td colspan="4" style="text-align:center;color:#555;">No arc cities configured</td></tr>';
            return;
        }
        tbody.innerHTML = cities.map((c, i) => {
            return `<tr>
                <td>${this.escapeHtml(c.name || '')}</td>
                <td>${c.lat || 0}</td>
                <td>${c.lng || 0}</td>
                <td><button class="btn-sm btn-delete" onclick="AdminApp.removeArcCity(${i})" title="Remove">✗</button></td>
            </tr>`;
        }).join('');
    },

    addArcCity() {
        const name = this.getVal('arcCityName');
        const lat = parseFloat(this.getVal('arcCityLat'));
        const lng = parseFloat(this.getVal('arcCityLng'));

        if (!name || isNaN(lat) || isNaN(lng)) {
            this.notify('City name, latitude and longitude are required', 'error');
            return;
        }

        if (!this.globeData) this.globeData = {};
        if (!this.globeData.arc_cities) this.globeData.arc_cities = [];

        this.globeData.arc_cities.push({ name, lat, lng });
        this.renderArcCities(this.globeData.arc_cities);

        // Clear inputs
        this.setVal('arcCityName', '');
        this.setVal('arcCityLat', '');
        this.setVal('arcCityLng', '');
        this.notify('City added — save to persist', 'info');
    },

    removeArcCity(index) {
        if (!this.globeData || !this.globeData.arc_cities) return;
        if (!confirm('Remove this city?')) return;
        this.globeData.arc_cities.splice(index, 1);
        this.renderArcCities(this.globeData.arc_cities);
        this.notify('City removed — save to persist', 'info');
    },

    async saveGlobe() {
        const payload = {
            server_lat: parseFloat(this.getVal('globeServerLat')) || 53.4808,
            server_lng: parseFloat(this.getVal('globeServerLng')) || -2.2426,
            server_label: this.getVal('globeServerLabel') || 'MANCHESTER',
            rotation_speed: parseFloat(this.getVal('globeRotationSpeed')) || 0.3,
            hex_polygon_opacity: parseFloat(this.getVal('globeHexOpacity')) || 0.55,
            hex_polygon_color: (this.globeData && this.globeData.hex_polygon_color) || 'rgba(0, 220, 50, 0.55)',
            atmosphere_color: this.getVal('globeAtmosphereColorHex') || this.getVal('globeAtmosphereColor') || '#00cc33',
            atmosphere_altitude: parseFloat(this.getVal('globeAtmosphereAlt')) || 0.2,
            arc_cities: (this.globeData && this.globeData.arc_cities) || []
        };

        try {
            const res = await fetch(this.API + '/globe', {
                method: 'POST',
                headers: this.authHeaders(),
                body: JSON.stringify(payload)
            });
            if (!res.ok) throw new Error('Save failed');
            this.notify('Globe config saved');
        } catch (err) {
            console.error('Save globe error:', err);
            this.notify('Failed to save globe config', 'error');
        }
    },

    /* ─────────────────── CHAT AI ─────────────────── */

    async loadChatAI() {
        try {
            const res = await fetch(this.API + '/chat-config', { headers: this.authHeaders() });
            if (!res.ok) throw new Error('Failed to load chat config');
            const data = await res.json();
            this.chatAIData = data;

            this.setVal('chatDisplayName', data.display_name);
            this.setVal('chatModel', data.model);
            this.setVal('chatHeaderTag', data.header_tag);
            this.setVal('chatMaxHistory', data.max_history);
            this.setChecked('chatAutoGreeting', data.auto_greeting !== false);
            this.setVal('chatSystemPrompt', data.system_prompt_summary);
        } catch (err) {
            console.error('Chat AI load error:', err);
            this.notify('Failed to load chat config', 'error');
        }
    },

    async saveChatAI() {
        const payload = {
            display_name: this.getVal('chatDisplayName') || 'JAE-AI',
            model: this.getVal('chatModel') || 'gemma-4-uncensored',
            header_tag: this.getVal('chatHeaderTag') || 'GEMMA-4-UNCENSORED',
            max_history: parseInt(this.getVal('chatMaxHistory')) || 20,
            auto_greeting: this.getChecked('chatAutoGreeting'),
            system_prompt_summary: this.getVal('chatSystemPrompt') || ''
        };

        try {
            const res = await fetch(this.API + '/chat-config', {
                method: 'POST',
                headers: this.authHeaders(),
                body: JSON.stringify(payload)
            });
            if (!res.ok) throw new Error('Save failed');
            this.notify('Chat AI config saved');
        } catch (err) {
            console.error('Save chat AI error:', err);
            this.notify('Failed to save chat config', 'error');
        }
    },

    /* ─────────────────── UTILITIES ─────────────────── */

    escapeHtml(str) {
        if (!str) return '';
        const div = document.createElement('div');
        div.appendChild(document.createTextNode(str));
        return div.innerHTML;
    },

    setVal(id, value) {
        const el = document.getElementById(id);
        if (el) el.value = value || '';
    },

    getVal(id) {
        const el = document.getElementById(id);
        return el ? el.value.trim() : '';
    },

    setChecked(id, checked) {
        const el = document.getElementById(id);
        if (el) el.checked = !!checked;
    },

    getChecked(id) {
        const el = document.getElementById(id);
        return el ? el.checked : false;
    }

    /* ─────────────────── EDITOR POST LIST ─────────────────── */

    async loadEditorPostList() {
        try {
            const res = await fetch(this.API + '/posts', { headers: this.authHeaders() });
            if (!res.ok) throw new Error('Failed to load posts');
            const data = await res.json();
            const posts = Array.isArray(data) ? data : (data.posts || []);
            posts.sort((a, b) => new Date(b.date || 0) - new Date(a.date || 0));

            const sel = document.getElementById('editorPostSelect');
            if (!sel) return;
            const currentVal = sel.value;
            sel.innerHTML = '<option value="">— NEW POST —</option>';
            posts.forEach(p => {
                const opt = document.createElement('option');
                opt.value = p.slug;
                opt.textContent = `${p.title || 'Untitled'} (${p.slug})`;
                sel.appendChild(opt);
            });
            if (currentVal) sel.value = currentVal;
        } catch (err) {
            console.error('Editor post list error:', err);
        }
    },

    async loadPostForEdit(slug) {
        if (!slug) {
            this.clearEditor();
            return;
        }
        try {
            const res = await fetch(this.API + '/posts/' + encodeURIComponent(slug), { headers: this.authHeaders() });
            if (!res.ok) throw new Error('Failed to load post');
            const post = await res.json();

            document.getElementById('editorPostId').value = post.slug || slug;
            document.getElementById('editorTitle').value = post.title || '';
            document.getElementById('editorSlug').value = post.slug || '';

            if (post.date) {
                const dt = new Date(post.date);
                document.getElementById('editorDate').value = dt.toISOString().split('T')[0];
                document.getElementById('editorTime').value = dt.toTimeString().slice(0, 5);
            }

            document.getElementById('editorTags').value = Array.isArray(post.tags) ? post.tags.join(', ') : (post.tags || '');
            document.getElementById('editorExcerpt').value = post.excerpt || '';
            document.getElementById('editorContent').value = post.content || '';

            // Operator HUD
            const moodEl = document.getElementById('hudMood');
            if (moodEl) moodEl.value = post.mood || 'focused';

            const hudMap = { hudEnergy: 'energy', hudMotivation: 'motivation', hudFocus: 'focus', hudDifficulty: 'difficulty' };
            Object.entries(hudMap).forEach(([id, key]) => {
                const el = document.getElementById(id);
                if (el) {
                    el.value = post[key] || 3;
                    const valEl = document.getElementById(id + 'Val');
                    if (valEl) valEl.textContent = el.value;
                }
            });

            const coffeeEl = document.getElementById('hudCoffee');
            if (coffeeEl) { coffeeEl.value = post.coffee || 0; const v = document.getElementById('hudCoffeeVal'); if (v) v.textContent = coffeeEl.value; }
            const bpmEl = document.getElementById('hudBPM');
            if (bpmEl) { bpmEl.value = post.bpm || post.heart_rate || 72; const v = document.getElementById('hudBPMVal'); if (v) v.textContent = bpmEl.value; }
            const threatEl = document.getElementById('hudThreat');
            if (threatEl) threatEl.value = post.threat_level || 'low';

            // Show delete button
            const delBtn = document.getElementById('editorDeleteBtn');
            if (delBtn) delBtn.style.display = 'inline-block';

            this.updatePreview();
            this.notify('Post loaded: ' + (post.title || slug));
        } catch (err) {
            console.error('Load post for edit error:', err);
            this.notify('Failed to load post', 'error');
        }
    },

    /* ─────────────────── CHANGELOG ─────────────────── */

    async loadChangelog() {
        try {
            const res = await fetch(this.API + '/changelog', { headers: this.authHeaders() });
            if (!res.ok) throw new Error('Failed to load changelog');
            const entries = await res.json();
            const list = document.getElementById('changelogList');
            if (!list) return;

            if (!Array.isArray(entries) || entries.length === 0) {
                list.innerHTML = '<div style="color:#555;text-align:center;padding:2rem;">No changelog entries yet</div>';
                return;
            }

            const typeColors = { feature: '#00ffc8', fix: '#ffcc00', security: '#ff4444', breaking: '#ff0066', update: '#888' };

            list.innerHTML = entries.map(e => {
                const changes = Array.isArray(e.changes) ? e.changes : [];
                const typeColor = typeColors[e.type] || '#888';
                return `<div class="admin-card" style="margin-bottom:1rem;border-left:3px solid ${typeColor};">
                    <div style="display:flex;justify-content:space-between;align-items:center;">
                        <div>
                            <strong style="color:${typeColor};">[${(e.type || 'update').toUpperCase()}]</strong>
                            <strong>${this.escapeHtml(e.version || '')}</strong> — ${this.escapeHtml(e.title || '')}
                            <span style="color:#555;margin-left:1rem;">${e.date || ''}</span>
                        </div>
                        <div>
                            <button class="btn-sm btn-edit" onclick="AdminApp.editChangelog(${e.id})" title="Edit">✎</button>
                            <button class="btn-sm btn-delete" onclick="AdminApp.deleteChangelog(${e.id})" title="Delete">✗</button>
                        </div>
                    </div>
                    ${e.description ? `<p style="color:#aaa;margin:0.5rem 0 0;">${this.escapeHtml(e.description)}</p>` : ''}
                    ${changes.length ? `<ul style="margin:0.5rem 0 0;padding-left:1.2rem;color:#888;">${changes.map(c => `<li>${this.escapeHtml(c)}</li>`).join('')}</ul>` : ''}
                </div>`;
            }).join('');
        } catch (err) {
            console.error('Changelog load error:', err);
            this.notify('Failed to load changelog', 'error');
        }
    },

    async saveChangelog() {
        const editId = document.getElementById('clEditId').value;
        const version = this.getVal('clVersion');
        const date = this.getVal('clDate') || new Date().toISOString().split('T')[0];
        const type = this.getVal('clType') || 'update';
        const title = this.getVal('clTitle');
        const description = this.getVal('clDescription');
        const changesRaw = this.getVal('clChanges');
        const changes = changesRaw.split('\n').map(c => c.trim()).filter(c => c);

        if (!title) { this.notify('Title is required', 'error'); return; }

        const payload = { version, date, type, title, description, changes };

        try {
            let url, method;
            if (editId) {
                url = this.API + '/changelog/' + editId;
                method = 'PUT';
            } else {
                url = this.API + '/changelog';
                method = 'POST';
            }
            const res = await fetch(url, { method, headers: this.authHeaders(), body: JSON.stringify(payload) });
            if (!res.ok) throw new Error('Save failed');
            this.notify(editId ? 'Entry updated' : 'Entry added');
            this.clearChangelog();
            this.loadChangelog();
        } catch (err) {
            console.error('Changelog save error:', err);
            this.notify('Failed to save changelog entry', 'error');
        }
    },

    clearChangelog() {
        document.getElementById('clEditId').value = '';
        document.getElementById('clVersion').value = '';
        document.getElementById('clDate').value = '';
        document.getElementById('clType').value = 'update';
        document.getElementById('clTitle').value = '';
        document.getElementById('clDescription').value = '';
        document.getElementById('clChanges').value = '';
    },

    async editChangelog(id) {
        try {
            const res = await fetch(this.API + '/changelog', { headers: this.authHeaders() });
            if (!res.ok) throw new Error('Failed to load');
            const entries = await res.json();
            const entry = entries.find(e => e.id === id);
            if (!entry) { this.notify('Entry not found', 'error'); return; }

            document.getElementById('clEditId').value = entry.id;
            document.getElementById('clVersion').value = entry.version || '';
            document.getElementById('clDate').value = entry.date || '';
            document.getElementById('clType').value = entry.type || 'update';
            document.getElementById('clTitle').value = entry.title || '';
            document.getElementById('clDescription').value = entry.description || '';
            document.getElementById('clChanges').value = (entry.changes || []).join('\n');

            window.scrollTo({ top: 0, behavior: 'smooth' });
        } catch (err) {
            this.notify('Failed to load entry for editing', 'error');
        }
    },

    async deleteChangelog(id) {
        if (!confirm('Delete this changelog entry?')) return;
        try {
            const res = await fetch(this.API + '/changelog/' + id, { method: 'DELETE', headers: this.authHeaders() });
            if (!res.ok) throw new Error('Delete failed');
            this.notify('Entry deleted');
            this.loadChangelog();
        } catch (err) {
            this.notify('Failed to delete entry', 'error');
        }
    },

    /* ─────────────────── SITREP ─────────────────── */

    async loadSitrep() {
        try {
            // Load list
            const listRes = await fetch(this.API + '/sitrep/list', { headers: this.authHeaders() });
            if (listRes.ok) {
                const listData = await listRes.json();
                const dates = listData.dates || [];

                document.getElementById('sitrepTotal').textContent = listData.total || 0;
                document.getElementById('sitrepLatestDate').textContent = dates.length ? dates[0].date : 'NONE';
            }

            // Load today's sitrep
            const todayRes = await fetch(this.API + '/sitrep', { headers: this.authHeaders() });
            if (todayRes.ok) {
                const sitrep = await todayRes.json();
                if (sitrep && !sitrep.error) {
                    document.getElementById('sitrepStatus').textContent = sitrep.is_today ? 'TODAY ✓' : 'STALE';
                    document.getElementById('sitrepStatus').style.color = sitrep.is_today ? '#00ffc8' : '#ffcc00';

                    const summary = document.getElementById('sitrepTodaySummary');
                    if (summary) {
                        summary.style.display = 'block';
                        document.getElementById('sitrepHeadline').textContent = sitrep.headline || '';
                        document.getElementById('sitrepSummaryText').innerHTML = (sitrep.sections || []).map(s =>
                            `<div style="margin-bottom:1rem;"><strong style="color:#00ffc8;">${this.escapeHtml(s.title || '')}</strong><p style="color:#aaa;">${this.escapeHtml(s.content || '')}</p></div>`
                        ).join('');
                    }
                } else {
                    document.getElementById('sitrepStatus').textContent = 'NONE';
                    document.getElementById('sitrepStatus').style.color = '#ff4444';
                }
            } else {
                document.getElementById('sitrepStatus').textContent = 'NONE';
                document.getElementById('sitrepStatus').style.color = '#ff4444';
            }

            // Archive list
            const archRes = await fetch(this.API + '/sitrep/list', { headers: this.authHeaders() });
            if (archRes.ok) {
                const archData = await archRes.json();
                const archEl = document.getElementById('sitrepArchive');
                if (archEl) {
                    const dates = archData.dates || [];
                    if (dates.length === 0) {
                        archEl.innerHTML = '<div style="color:#555;">No reports in archive</div>';
                    } else {
                        archEl.innerHTML = dates.map(d => `<div class="admin-card" style="margin-bottom:0.5rem;display:flex;justify-content:space-between;align-items:center;">
                            <div>
                                <strong>${d.date}</strong>
                                <span style="color:#aaa;margin-left:1rem;">${this.escapeHtml(d.headline || '')}</span>
                            </div>
                            <div style="color:#555;">
                                ${d.sources_used || 0} sources · ${d.model || 'unknown'}
                            </div>
                        </div>`).join('');
                    }
                }
            }
        } catch (err) {
            console.error('SITREP load error:', err);
            this.notify('Failed to load SITREP data', 'error');
        }
    },

    async generateSitrep() {
        const btn = document.getElementById('sitrepGenerateBtn');
        if (btn) { btn.disabled = true; btn.textContent = '⏳ GENERATING...'; }

        try {
            const res = await fetch(this.API + '/sitrep/generate', { method: 'POST', headers: this.authHeaders() });
            const data = await res.json();
            if (res.ok && data.status === 'ok') {
                this.notify('SITREP generated for ' + (data.date || 'today'));
                this.loadSitrep();
            } else {
                this.notify('Generation failed: ' + (data.message || 'Unknown error'), 'error');
            }
        } catch (err) {
            console.error('SITREP generate error:', err);
            this.notify('Failed to generate SITREP', 'error');
        } finally {
            if (btn) { btn.disabled = false; btn.textContent = '⚡ GENERATE NOW'; }
        }
    },

    /* ─────────────────── DATA SYNC ─────────────────── */

    async loadDataSync() {
        try {
            const res = await fetch(this.API + '/sync/status', { headers: this.authHeaders() });
            if (!res.ok) throw new Error('Failed to load sync status');
            const data = await res.json();

            // Contraband stats
            const cb = data.contraband || {};
            document.getElementById('syncCbEntries').textContent = (cb.total_entries || 0).toLocaleString();
            document.getElementById('syncCbCategories').textContent = cb.total_categories || 0;
            document.getElementById('syncCbStarred').textContent = (cb.total_starred || 0).toLocaleString();
            document.getElementById('syncCbLastSync').textContent = cb.last_sync ? new Date(cb.last_sync).toLocaleString('en-GB') : 'NEVER';

            // Awesomelist stats
            const al = data.awesomelist || {};
            document.getElementById('syncAlSectors').textContent = al.total_sectors || 0;
            document.getElementById('syncAlLists').textContent = al.total_lists || 0;
            document.getElementById('syncAlEntries').textContent = (al.total_entries || 0).toLocaleString();
            document.getElementById('syncAlLastSync').textContent = al.last_sync ? new Date(al.last_sync).toLocaleString('en-GB') : 'NEVER';
        } catch (err) {
            console.error('Sync status error:', err);
            this.notify('Failed to load sync status', 'error');
        }
    },

    async runSync(type) {
        const btnId = type === 'contraband' ? 'syncCbBtn' : 'syncAlBtn';
        const btn = document.getElementById(btnId);
        if (btn) { btn.disabled = true; btn.textContent = '⏳ SYNCING...'; }

        try {
            const res = await fetch(this.API + '/sync/' + type, { method: 'POST', headers: this.authHeaders() });
            const data = await res.json();

            // Show log
            const logEl = document.getElementById('syncLog');
            const logContent = document.getElementById('syncLogContent');
            if (logEl && logContent) {
                logEl.style.display = 'block';
                logContent.textContent = data.log || data.stderr || data.message || 'No output';
            }

            if (res.ok && data.status === 'ok') {
                this.notify(type + ' sync completed');
                this.loadDataSync();
            } else {
                this.notify('Sync failed: ' + (data.message || data.stderr || 'Unknown error'), 'error');
            }
        } catch (err) {
            console.error('Sync error:', err);
            this.notify('Sync failed: ' + err.message, 'error');
        } finally {
            if (btn) { btn.disabled = false; btn.textContent = '⚡ SYNC NOW'; }
        }
    },

    /* ─────────────────── CRON JOBS ─────────────────── */

    async loadCronJobs() {
        try {
            const res = await fetch(this.API + '/crons', { headers: this.authHeaders() });
            if (!res.ok) throw new Error('Failed to load cron jobs');
            const jobs = await res.json();
            const list = document.getElementById('cronJobsList');
            if (!list) return;

            if (!Array.isArray(jobs) || jobs.length === 0) {
                list.innerHTML = '<div style="color:#555;text-align:center;padding:2rem;">No cron jobs configured</div>';
                return;
            }

            list.innerHTML = jobs.map(j => {
                const statusColor = j.enabled ? '#00ffc8' : (j.found ? '#ffcc00' : '#ff4444');
                const statusText = j.enabled ? 'ACTIVE' : (j.found ? 'DISABLED' : 'NOT FOUND');
                return `<div class="admin-card" style="margin-bottom:1rem;border-left:3px solid ${statusColor};display:flex;justify-content:space-between;align-items:center;">
                    <div>
                        <strong>${this.escapeHtml(j.name)}</strong>
                        <span style="color:${statusColor};margin-left:1rem;">[${statusText}]</span>
                        <div style="color:#555;margin-top:0.3rem;font-family:'JetBrains Mono',monospace;font-size:0.85rem;">
                            Schedule: <code>${this.escapeHtml(j.schedule)}</code>
                        </div>
                    </div>
                    <div>
                        ${j.found ? `<button class="action-btn ${j.enabled ? '' : 'accent'}" onclick="AdminApp.toggleCron('${j.id}')" style="min-width:100px;">
                            ${j.enabled ? '⏸ DISABLE' : '▶ ENABLE'}
                        </button>` : '<span style="color:#ff4444;">Not in crontab</span>'}
                    </div>
                </div>`;
            }).join('');
        } catch (err) {
            console.error('Cron jobs load error:', err);
            this.notify('Failed to load cron jobs', 'error');
        }
    },

    async toggleCron(jobId) {
        try {
            const res = await fetch(this.API + '/crons/' + jobId + '/toggle', { method: 'POST', headers: this.authHeaders() });
            if (!res.ok) {
                const data = await res.json();
                throw new Error(data.error || 'Toggle failed');
            }
            this.notify('Cron job toggled');
            this.loadCronJobs();
        } catch (err) {
            console.error('Cron toggle error:', err);
            this.notify('Failed to toggle cron: ' + err.message, 'error');
        }
    },

    /* ─────────────────── BACKUP IMPORT ─────────────────── */

    async importBackup() {
        const fileInput = document.getElementById('backupImportFile');
        if (!fileInput || !fileInput.files.length) {
            this.notify('Please select a ZIP file first', 'error');
            return;
        }

        if (!confirm('This will overwrite existing data files. Are you sure?')) return;

        const formData = new FormData();
        formData.append('file', fileInput.files[0]);

        try {
            const res = await fetch(this.API + '/backups/import', {
                method: 'POST',
                headers: { 'Authorization': 'Bearer ' + localStorage.getItem('jaeswift_token') },
                body: formData
            });
            const data = await res.json();
            if (res.ok && data.ok) {
                this.notify(`Backup restored: ${data.count} files (${data.restored.join(', ')})`);
                fileInput.value = '';
            } else {
                this.notify('Import failed: ' + (data.error || 'Unknown error'), 'error');
            }
        } catch (err) {
            console.error('Import error:', err);
            this.notify('Failed to import backup', 'error');
        }
    },

};

/* ─────────────────── BOOTSTRAP ─────────────────── */
document.addEventListener('DOMContentLoaded', () => AdminApp.init());
