// core.js — Foundation layer for Familiar Dashboard
// Extracted from dashboard.html — all other JS files depend on this.
// Must load after the inline Jinja bridge (<script> with _SERVER).

const API = '';

// Auth — reads from _SERVER (Jinja bridge) with URL param and sessionStorage fallback
let _apiKey = (_SERVER ? _SERVER.apiKey : '')
            || new URLSearchParams(window.location.search).get('api_key')
            || sessionStorage.getItem('familiar_api_key')
            || '';

// Clean the URL so the key isn't visible in the address bar
if (window.location.search.includes('api_key')) {
    const url = new URL(window.location);
    url.searchParams.delete('api_key');
    window.history.replaceState({}, '', url);
}

if (_apiKey) sessionStorage.setItem('familiar_api_key', _apiKey);

function apiFetch(url, opts = {}) {
    opts.headers = opts.headers || {};
    if (_apiKey) opts.headers['X-API-Key'] = _apiKey;
    return fetch(url, opts).then(async res => {
        if (res.status === 401) { showLoginOverlay(); throw new Error('Unauthorized'); }
        if (!res.ok) {
            // Try to extract JSON error, fall back to status text
            let msg = res.statusText || ('HTTP ' + res.status);
            try {
                const ct = res.headers.get('content-type') || '';
                if (ct.includes('application/json')) {
                    const errData = await res.json();
                    msg = errData.error || errData.message || msg;
                }
            } catch(_) {}
            throw new Error(msg);
        }
        return res;
    }).catch(err => {
        if (err.message !== 'Unauthorized') {
            console.warn('apiFetch error:', url, err.message);
        }
        throw err;
    });
}

// ── APP_CONFIG — centralized poll intervals, timeouts, thresholds ──
const APP_CONFIG = {
    poll: {
        status:            30000,   // System status refresh
        nodes:             15000,   // Mesh node list refresh
        unreadBadge:       30000,   // Unread message badge
        scheduledMessages: 10000,   // Briefings, heartbeats, reminders
        credentialCheck:   60000,   // Credential attention banner
        messages:           3000,   // Peer messaging poll
        activity:           5000,   // Activity feed (when visible)
        social:            10000,   // Social notifications
        mesh:              15000,   // Mesh status (when panel active)
        googleOAuth:        2000,   // OAuth completion check
        apps:              30000,   // Server apps refresh
    },
    delay: {
        providerHealth:     3000,   // Initial provider health check
        credentialHealth:   5000,   // Initial credential attention check
        toastDuration:      4000,   // Toast notification auto-dismiss
        buttonReset:        2000,   // Button text reset after action
        bannerDismiss:      5000,   // Success/error banner auto-hide
    },
};

// ── PollManager — centralized interval lifecycle ──────
const PollManager = {
    _intervals: {},
    start(key, fn, intervalMs) { this.stop(key); this._intervals[key] = setInterval(fn, intervalMs); },
    stop(key) { if (this._intervals[key]) { clearInterval(this._intervals[key]); delete this._intervals[key]; } },
    stopAll() { Object.keys(this._intervals).forEach(k => this.stop(k)); },
    stopExcept(...keys) { const keep = new Set(keys); Object.keys(this._intervals).forEach(k => { if (!keep.has(k)) this.stop(k); }); }
};

// ── Global State Namespaces ─────────────────────────────
// Replaces scattered `let _*` globals with organized objects.
// Each namespace owns its section's state. Old variable names
// are aliased below for incremental migration safety.

const ChatState = {
    processing: false,
    attachedFiles: [],
};

const RoundTableState = {
    seats: new Set(),
    jobClasses: [],
    mode: 'chairman',
};

const CalendarState = {
    view: 'week',
    events: [],
    accounts: [],
    callLogTrigger: '',
};

const MsgState = {
    selectedPeer: null,
    typingTimer: null,
    peers: [],
    meshState: null,
    pickerOpen: false,
};

const EmailState = {
    triageEmails: [],
    collapsedCategories: new Set(['Promotional']),
    currentDetail: null,
    activeTab: 'inbox',
    sentLoaded: false,
    density: null,  // set from localStorage below
    focusedIdx: -1,
    flatRows: [],
    kbdHelpVisible: false,
};

const SocialState = {
    accounts: {},
    currentFilter: '',
    posts: [],
};

const WorkspaceState = {
    config: null,
    activeTab: 'overview',
    emailTab: 'inbox',
    currentDonor: null,
};

// Globals referenced across multiple files — defined here in core.js
// so they exist before any file that uses them loads.
let _activityVisible = false;
let _routingHealth = {};
let _creatureSpeciesKey = '';
let SPECIES_CARD_INTROS = {};  // populated by chat.js
let SPECIES_GREETINGS = {};   // populated by chat.js
let SPECIES_TIPS = {};        // populated by chat.js

function showLoginOverlay() {
    let overlay = document.getElementById('auth-overlay');
    if (overlay) { overlay.style.display = 'flex'; return; }
    overlay = document.createElement('div');
    overlay.id = 'auth-overlay';
    overlay.className = 'login-overlay';
    overlay.innerHTML = `
        <div class="login-card">
            <h2 class="login-title">🦔 Familiar Dashboard</h2>
            <span class="login-version">v${_SERVER.version || ''}</span>
            <p class="login-hint">Paste the API key from your terminal</p>
            <input id="auth-key-input" class="login-input" type="text" placeholder="API key">
            <button id="auth-submit-btn" class="login-btn">Connect</button>
            <p id="auth-error" class="login-error">Invalid key — check your terminal output</p>
            <a id="auth-troubleshoot-link" class="login-troubleshoot-link" href="#">Troubleshooting</a>
            <div id="auth-troubleshoot" class="login-troubleshoot">
                <h4>Common Issues</h4>
                <details>
                    <summary>Dashboard shows old version</summary>
                    <p>If you updated via <code>git pull</code> or <code>pip install</code> but the version above hasn't changed, <code>~/.familiar/app/</code> may be a stale copy instead of a symlink.</p>
                    <pre>rm -rf ~/.familiar/app
ln -s /path/to/your/familiar ~/.familiar/app
sudo systemctl restart familiar</pre>
                </details>
                <details>
                    <summary>Can't find the API key</summary>
                    <p>The key is printed to your terminal when Familiar starts. You can also set it via environment variable:</p>
                    <pre>export FAMILIAR_DASHBOARD_KEY="your-key"</pre>
                    <p>If running as a systemd service, check the logs:</p>
                    <pre>journalctl -u familiar --no-pager -n 50</pre>
                </details>
                <details>
                    <summary>Services won't restart</summary>
                    <p>Reload the systemd daemon and check service status:</p>
                    <pre>sudo systemctl daemon-reload
sudo systemctl restart familiar
systemctl status familiar
journalctl -u familiar -f</pre>
                </details>
                <details>
                    <summary>Connection refused</summary>
                    <p>Port 5000 may be in use by another process, or a firewall may be blocking it.</p>
                    <pre>ss -tlnp | grep 5000
sudo lsof -i :5000</pre>
                    <p>If another process holds the port, stop it or configure Familiar to use a different port in your config.</p>
                </details>
            </div>
        </div>`;
    document.body.appendChild(overlay);
    const inp = document.getElementById('auth-key-input');
    inp.focus();
    inp.addEventListener('keypress', e => { if (e.key === 'Enter') tryAuth(); });
    document.getElementById('auth-submit-btn').addEventListener('click', tryAuth);
    document.getElementById('auth-troubleshoot-link').addEventListener('click', e => {
        e.preventDefault();
        const panel = document.getElementById('auth-troubleshoot');
        panel.style.display = panel.style.display === 'none' ? 'block' : 'none';
    });
}

async function tryAuth() {
    const inp = document.getElementById('auth-key-input');
    const key = inp.value.trim();
    if (!key) return;
    try {
        const res = await apiFetch(API + '/api/status', { headers: { 'X-API-Key': key } });
        if (res.ok) {
            _apiKey = key;
            sessionStorage.setItem('familiar_api_key', key);
            document.getElementById('auth-overlay').style.display = 'none';
            initDashboard();
        } else {
            document.getElementById('auth-error').style.display = 'block';
        }
    } catch(e) {
        document.getElementById('auth-error').style.display = 'block';
    }
}

function initDashboard() {
    // Fetch health first so provider colors work on initial load
    apiFetch('/api/activity/health').then(r => r.json()).then(h => {
        const routing = h.routing || {};
        _routingHealth._active = (routing.active_provider || '').toLowerCase();
        _routingHealth._activeKey = (routing.active_provider_key || '').toLowerCase();
        _routingHealth._activeModel = (routing.active_model || '').toLowerCase();
        for (const dep of (h.dependencies || [])) {
            if (dep.type === 'llm_provider') {
                _routingHealth[dep.name.toLowerCase()] = dep.healthy;
            }
        }
        loadStatus();
    }).catch(() => loadStatus());
    loadCreature();
}

// ── Deferred init — runs after all script files and DOM are loaded ──
window.addEventListener('load', function() {

// Verify auth on load
if (!_apiKey) { showLoginOverlay(); }
else { fetch(API + '/api/status', { headers: { 'X-API-Key': _apiKey } })
    .then(r => { if (r.status === 401) showLoginOverlay(); else initDashboard(); })
    .catch(() => {}); }

// Collapsible nav sections
document.querySelectorAll('.nav-section h4').forEach(header => {
    header.addEventListener('click', () => {
        header.parentElement.classList.toggle('collapsed');
    });
});

// Collapsible right panel cards
document.querySelectorAll('.right-panel .card h3').forEach(header => {
    header.addEventListener('click', () => {
        header.parentElement.classList.toggle('rp-collapsed');
    });
});

// Navigation
document.querySelectorAll('.nav-item').forEach(item => {
    item.addEventListener('click', () => {
        const panel = item.dataset.panel;

        // Auto-expand the section containing the clicked item
        const section = item.closest('.nav-section');
        if (section && section.classList.contains('collapsed')) {
            section.classList.remove('collapsed');
        }

        document.querySelectorAll('.nav-item').forEach(n => n.classList.remove('active'));
        item.classList.add('active');
        
        document.querySelectorAll('.panel').forEach(p => p.classList.remove('active'));
        document.getElementById(panel + '-panel').classList.add('active');

        // Populate species-voiced card intro
        const introEl = document.getElementById('intro-' + panel);
        if (introEl) {
            const cardVoices = SPECIES_CARD_INTROS[_creatureSpeciesKey];
            if (cardVoices && cardVoices[panel]) {
                introEl.innerHTML = cardVoices[panel];
                introEl.style.display = '';
            } else {
                introEl.style.display = 'none';
            }
        }

        // Stop panel-specific polling when leaving (global polls like status/nodes/unread stay)
        if (panel !== 'activity') _activityVisible = false;
        PollManager.stopExcept('status', 'nodes', 'unread-badge', 'scheduled-messages', 'cred-attention');

        // Load data for panel
        switch(panel) {
            case 'briefing': loadBriefing(); break;
            case 'calendar': loadCalendar(); break;
            case 'email': loadEmailTriage(); break;
            case 'messages': loadMessages(); break;
            case 'social': loadSocial(); break;
            case 'tasks': loadTasks(); break;
            case 'memory': loadMemory(); break;
            case 'skills': loadSkills(); break;
            case 'nodes': loadMeshStatus(); break;
            case 'connect': loadConnectBrowse(); break;
            case 'terminal': loadTerminalModel(); break;
            case 'shell': shellFocusInput(); break;
            case 'activity': _activityVisible = true; loadActivity(); _startActivityPolling(); break;
            case 'settings': loadAccounts(); loadRoutingConfig(); loadShellToggle(); loadJobClasses(); loadBudgetSettings(); loadBriefingSettings(); loadNewsTopics(); loadEmailIntel(); break;
            case 'tarot': loadTarotSpread(); break;
            case 'models': loadModels(); break;
            case 'marketplace': loadMarketplace(); break;
            case 'apps': loadApps(); break;
            case 'server': loadServer(); break;
            case 'training': loadTraining(); break;
            case 'workspace': loadWorkspace(); break;
            case 'media': loadMedia(); break;
            case 'gallery': loadGallery(); break;
            case 'roundtable': initRoundTable(); break;
        }
    });
});

}); // end window.addEventListener('load')

function escapeHtml(str) {
    const div = document.createElement('div');
    div.textContent = str;
    return div.innerHTML;
}

function linkifyUrls(html) {
    // Convert bare URLs (not already inside an <a> tag) to clickable links
    // YouTube URLs get an inline play button
    return html.replace(/(^|[^"'>])(https?:\/\/[^\s<,)]+)/g, function(match, pre, url) {
        var ytId = _extractYouTubeId(url);
        if (ytId) {
            return pre
                + '<span class="yt-chat-card" onclick="playYouTubeInChat(this,\'' + ytId + '\')"'
                + ' style="display:inline-flex;align-items:center;gap:6px;padding:4px 10px;'
                + 'background:var(--bg-input);border:1px solid var(--border);border-radius:8px;'
                + 'cursor:pointer;margin:2px 0;font-size:0.85rem;color:var(--text);'
                + 'transition:background 0.2s;" onmouseenter="this.style.background=\'var(--border)\'"'
                + ' onmouseleave="this.style.background=\'var(--bg-input)\'">'
                + '<i class="fab fa-youtube" style="color:#ff0000;font-size:1.1rem;"></i>'
                + '<span style="text-decoration:underline;color:var(--accent);">' + url + '</span>'
                + '<i class="fas fa-play" style="font-size:0.7rem;color:var(--text-dim);"></i>'
                + '</span>';
        }
        return pre + '<a href="' + url + '" target="_blank" rel="noopener">' + url + '</a>';
    });
}


function quickCommand(cmd) {
    chatInput.value = cmd.replace(/_/g, ' ');
    sendMessage();
    
    // Switch to chat
    document.querySelector('[data-panel="chat"]').click();
}


function switchPanel(name) {
    const nav = document.querySelector('.nav-item[data-panel="' + name + '"]');
    if (nav) nav.click();
}

// ============================================================
// Round Table
// ============================================================
// ── Toast notification helper ──────────────────────────────

function showToast(msg, duration) {
    duration = duration || 3000;
    let c = document.getElementById('toast-container');
    if (!c) {
        c = document.createElement('div');
        c.id = 'toast-container';
        c.style.cssText = 'position:fixed;bottom:24px;right:24px;z-index:9999;display:flex;flex-direction:column;gap:8px;pointer-events:none;';
        document.body.appendChild(c);
    }
    const t = document.createElement('div');
    t.style.cssText = 'background:var(--bg-dark,#1e2028);border:1px solid var(--border,#333);color:var(--text,#e2e8f0);padding:10px 16px;border-radius:8px;font-size:0.85rem;box-shadow:0 4px 16px #0005;opacity:0;transition:opacity 0.2s;pointer-events:none;max-width:300px;';
    t.textContent = msg;
    c.appendChild(t);
    requestAnimationFrame(() => { t.style.opacity = '1'; });
    setTimeout(() => { t.style.opacity = '0'; setTimeout(() => t.remove(), 220); }, duration);
}

function formatDate(dateStr, mode) {
    if (!dateStr) return '';
    try {
        const raw = (dateStr instanceof Date) ? dateStr : new Date(String(dateStr).replace(/\s*\(.*\)$/, '').trim());
        if (isNaN(raw.getTime())) return escapeHtml(String(dateStr).replace(/\s*\(.*\)$/, '').trim());
        const now = new Date();
        switch (mode) {
            case 'time':
                return raw.toLocaleTimeString([], {hour: '2-digit', minute: '2-digit'});
            case 'timestamp':
                return raw.toLocaleTimeString([], {hour: '2-digit', minute: '2-digit', second: '2-digit'});
            case 'date':
                if (raw.getFullYear() === now.getFullYear()) {
                    return raw.toLocaleDateString(undefined, {month: 'short', day: 'numeric'});
                }
                return raw.toLocaleDateString(undefined, {month: 'short', day: 'numeric', year: 'numeric'});
            case 'datetime':
                return raw.toLocaleDateString(undefined, {month: 'short', day: 'numeric'}) + ', ' + raw.toLocaleTimeString([], {hour: '2-digit', minute: '2-digit'});
            case 'day':
                return raw.toLocaleDateString(undefined, {weekday: 'short', month: 'short', day: 'numeric'});
            default: // 'smart' — relative for recent, absolute for older
                const diffMs = now - raw;
                const diffMin = Math.floor(diffMs / 60000);
                const diffHr = Math.floor(diffMs / 3600000);
                if (diffMin < 1) return 'Just now';
                if (diffMin < 60) return diffMin + 'm ago';
                if (diffHr < 24) return diffHr + 'h ago';
                const diffDay = Math.floor(diffMs / 86400000);
                if (diffDay < 7) {
                    return raw.toLocaleDateString(undefined, {weekday:'short'}) + ' ' + raw.toLocaleTimeString(undefined, {hour:'numeric', minute:'2-digit'});
                }
                if (raw.getFullYear() === now.getFullYear()) {
                    return raw.toLocaleDateString(undefined, {month:'short', day:'numeric'});
                }
                return raw.toLocaleDateString(undefined, {month:'short', day:'numeric', year:'numeric'});
        }
    } catch(e) { return escapeHtml(String(dateStr).replace(/\s*\(.*\)$/, '').trim()); }
}

function showModal(id) {
    document.getElementById(id).classList.add('show');
}

function hideModal(id) {
    document.getElementById(id).classList.remove('show');
}

