// Shared helpers used across every tab module.

// KL-89: append surface + distinct_id query params to the pricing URL so
// the website pricing page can carry them forward into the LemonSqueezy
// checkout custom_data — that's how the relay's checkout.completed event
// (KL-73 backend) ties back to the in-app cta_shown / cta_clicked events
// that fired before the user left the app. distinct_id pulls from the
// PostHog anon_id that server.py injects into the page template.
export function pricingUrlWithContext(surface) {
    const base = 'https://conductor.lighting/pricing.html';
    const params = new URLSearchParams();
    params.set('surface', surface || 'unknown');
    const distinctId = (typeof window !== 'undefined' && window.CONDUCTOR_ANON_ID) || '';
    if (distinctId) params.set('distinct_id', distinctId);
    return `${base}?${params}`;
}

// KL-89: rewrite static upgrade-link <a> tags with surface + distinct_id
// query params on init. Server-rendered links (sidebar tier badge,
// settings-footer upgrade, license-error "see plans" link) carry a
// data-upgrade-surface attribute identifying which surface they
// represent; this helper finds them all and patches the href so the
// pricing page receives the same context the JS-driven CTAs do.
export function initUpgradeLinks() {
    const links = document.querySelectorAll('a[data-upgrade-surface]');
    links.forEach((link) => {
        const surface = link.dataset.upgradeSurface || 'unknown';
        link.href = pricingUrlWithContext(surface);
    });
}

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

export function escapeAttr(s) {
    return String(s == null ? '' : s)
        .replace(/&/g, '&amp;').replace(/"/g, '&quot;').replace(/'/g, '&#39;')
        .replace(/</g, '&lt;').replace(/>/g, '&gt;');
}

// Fires a whitelisted analytics event. Server drops unknown names and
// gates the actual PostHog send on the user's opt-in, so this is safe
// to call unconditionally from CTA handlers.
export async function trackEvent(event, properties) {
    try {
        await fetch('/api/telemetry/event', {
            method: 'POST',
            headers: {'Content-Type': 'application/json'},
            body: JSON.stringify({event, properties: properties || {}}),
        });
    } catch (e) {
        // Analytics must never break the UI.
    }
}

// Crash telemetry: forward uncaught JS errors and unhandled promise
// rejections to /api/telemetry/exception, which relays to PostHog
// Error Tracking. Server gates on the existing telemetry_opt_in pref,
// so this is safe to wire unconditionally — opted-out users get a
// no-op server-side. Bails out via _reporting flag if reporting itself
// throws, to avoid feedback loops.
let _reporting = false;
async function reportException(payload) {
    if (_reporting) return;
    _reporting = true;
    try {
        await fetch('/api/telemetry/exception', {
            method: 'POST',
            headers: {'Content-Type': 'application/json'},
            body: JSON.stringify(payload),
        });
    } catch (_) {
        // Crash telemetry must never break the UI either.
    } finally {
        _reporting = false;
    }
}

function activeTabName() {
    const btn = document.querySelector('.tab-btn.active[data-tab]');
    return btn ? btn.dataset.tab : null;
}

// Parse a JS Error.stack string into structured frames for PostHog's
// $exception schema. PostHog expects {type: "raw", frames: [...]} with
// per-frame filename/function/lineno/colno; sending the raw string in
// any other shape causes the event to be dropped server-side. Handles
// the two main browser formats (V8 / SpiderMonkey-Safari).
function parseStack(stack) {
    if (!stack || typeof stack !== 'string') return null;
    const frames = [];
    const v8 = /^\s*at\s+(?:(.+?)\s+\()?(.+?):(\d+):(\d+)\)?\s*$/;
    const moz = /^(.*?)@(.+?):(\d+):(\d+)\s*$/;
    for (const line of stack.split('\n')) {
        let m = line.match(v8);
        if (!m) m = line.match(moz);
        if (!m) continue;
        frames.push({
            function: m[1] || '<anonymous>',
            filename: m[2],
            lineno: parseInt(m[3], 10),
            colno: parseInt(m[4], 10),
            in_app: true,
        });
    }
    if (!frames.length) return null;
    // PostHog expects oldest frame first (call site at the bottom). JS
    // stacks list newest first, so reverse before sending.
    frames.reverse();
    return frames;
}

export function initCrashTelemetry() {
    window.addEventListener('error', (event) => {
        const err = event.error;
        const name = (err && err.name) || 'Error';
        const message = (err && err.message) || event.message || String(err || '');
        const stack = err && err.stack;
        reportException({
            name, message,
            frames: parseStack(stack),
            source: event.filename,
            line: event.lineno,
            column: event.colno,
            active_tab: activeTabName(),
        });
    });

    window.addEventListener('unhandledrejection', (event) => {
        const reason = event.reason;
        let name = 'UnhandledRejection';
        let message = String(reason);
        let stack = null;
        if (reason && typeof reason === 'object') {
            name = reason.name || name;
            message = reason.message || message;
            stack = reason.stack || null;
        }
        reportException({
            name, message,
            frames: parseStack(stack),
            active_tab: activeTabName(),
        });
    });
}
