// macOS BlackHole / loopback onboarding (KL-52, carved out of
// settings.js in KL-127). First-run modal + Settings-tab status line +
// diagnostic toasts. Polls /api/mac-audio/health every 30s so the
// dashboard lights up when BlackHole is installed or the user re-routes
// audio without needing a refresh.
//
// First-run behavior: on macOS without BlackHole (status: no_loopback),
// open the blocking modal immediately instead of the quieter toast.
// Subsequent status issues (loopback_not_selected, no_signal) still
// use the toast — they're diagnostic, not onboarding.
//
// Modal show/hide + button states + test-result line are Alpine-driven
// via Alpine.store('macAudio'). The 30s polling, toast push/dismiss,
// snooze API calls, and Settings-tab help link stay imperative.

import {pushToast, dismissToast, registerToastAction} from './toasts.js';
import {setAudioStatus} from './audio_input.js';

const MAC_TOAST_KEY = 'mac-audio';
const MAC_ONBOARDING_SHOWN_KEY = 'conductor.mac_audio_onboarding_shown';
const FAQ_URL_ONLINE = 'https://conductor.lighting/faq.html#mac-setup';
const FAQ_URL_OFFLINE = '/docs/mac-audio-setup';

const MAC_STATUS_COPY = {
    no_loopback: {title: 'Install BlackHole to capture system audio', level: 'warn'},
    loopback_not_selected: {title: 'Loopback is installed but not selected', level: 'warn'},
    no_signal: {title: 'No audio reaching Conductør', level: 'warn'},
    warming_up: {title: 'Listening for audio…', level: 'info'},
};

// Friendly Settings → Audio Input status line driven by mac_health
// states. Takes precedence over device-name copy from initAudio because
// "✓ System audio is being captured" is more useful than the literal
// device name ("Active: BlackHole 2ch") for non-technical users.
const MAC_AUDIO_STATUS_LINE = {
    ok: {level: 'ok', text: '✓ System audio is being captured'},
    no_loopback: {level: 'warn', text: '⚠ No loopback device detected'},
    loopback_not_selected: {level: 'warn', text: '⚠ Loopback installed but not selected'},
    no_signal: {level: 'warn', text: '⚠ No audio reaching Conductør'},
    warming_up: {level: 'info', text: 'Listening for audio…'},
};

function storeDefinition() {
    return {
        shown: false,
        testResultText: '',
        testResultLevel: '',  // '' | 'ok' | 'err'
        walkthroughUrl: FAQ_URL_ONLINE,
        recheckBusy: false,
        recheckLabel: 'Recheck',
        // Mirrors the previous data-diagnostic attribute on the detail
        // <p>; not displayed but useful to inspect via devtools when
        // debugging an onboarding flow.
        diagnosticDetails: '',

        open() {
            // Resolve walkthrough target each open so we pick up live
            // changes in navigator.onLine (e.g. hotel wifi dropped
            // mid-setup). Online → public FAQ (fresh + shareable),
            // offline → in-app markdown served by this Conductør.
            this.walkthroughUrl = navigator.onLine ? FAQ_URL_ONLINE : FAQ_URL_OFFLINE;
            this.shown = true;
            this.setTestResult('', '');
        },

        close() {
            this.shown = false;
        },

        setTestResult(level, text) {
            this.testResultLevel = level || '';
            this.testResultText = text || '';
        },

        async runCalibration() {
            this.setTestResult('', 'Playing tone…');
            try {
                const resp = await fetch('/api/mac-audio/calibrate', {method: 'POST'});
                const data = await resp.json();
                if (data.success) {
                    this.setTestResult('ok', '✓ Signal received — loopback is working.');
                    pollMacAudioHealth();
                } else {
                    this.setTestResult('err', data.error || 'Loopback test failed.');
                }
            } catch (e) {
                this.setTestResult('err', `Request failed: ${e.message}`);
            }
        },

        async recheck() {
            this.recheckBusy = true;
            this.recheckLabel = 'Rechecking…';
            this.setTestResult('', 'Refreshing audio devices…');
            try {
                const resp = await fetch('/api/mac-audio/refresh-devices', {method: 'POST'});
                const data = await resp.json();
                renderMacAudioHealth(data);
                if (data.status === 'ok' || data.status === 'loopback_not_selected') {
                    this.setTestResult('ok', '✓ BlackHole detected.');
                } else if (data.status === 'no_loopback') {
                    this.setTestResult('err', 'Still no loopback device found.');
                } else {
                    this.setTestResult('', '');
                }
            } catch (e) {
                this.setTestResult('err', `Recheck failed: ${e.message}`);
            } finally {
                this.recheckBusy = false;
                this.recheckLabel = 'Recheck';
            }
        },

        async snooze(forever) {
            try {
                await fetch('/api/mac-audio/snooze', {
                    method: 'POST',
                    headers: {'Content-Type': 'application/json'},
                    body: JSON.stringify(forever ? {forever: true} : {hours: 24}),
                });
                dismissToast(MAC_TOAST_KEY);
                this.close();
            } catch (e) { /* ignore */ }
        },
    };
}

function ensureStore() {
    if (!window.Alpine) return null;
    if (!window.Alpine.store('macAudio')) {
        window.Alpine.store('macAudio', storeDefinition());
    }
    return window.Alpine.store('macAudio');
}

if (window.Alpine) {
    ensureStore();
} else {
    document.addEventListener('alpine:init', ensureStore);
}

// Module-level so recheck() can re-fetch and route through
// renderMacAudioHealth without exposing it on the store.
async function pollMacAudioHealth() {
    try {
        const resp = await fetch('/api/mac-audio/health');
        const data = await resp.json();
        renderMacAudioHealth(data);
    } catch (e) { /* transient */ }
}

function renderMacAudioHealth(data) {
    // The Settings "Not hearing system audio?" link is only meaningful
    // on macOS — unhide once we've confirmed this is a Mac session.
    const helpLink = document.getElementById('link-mac-audio-help');
    if (helpLink) helpLink.hidden = !data.is_macos;

    // Friendly status line in Settings → Audio Input. On Mac, mac_health
    // drives the copy so users see plain-language state instead of just
    // the device name.
    if (data.is_macos) {
        const line = MAC_AUDIO_STATUS_LINE[data.status];
        if (line) setAudioStatus(line.level, line.text);
    }

    const store = ensureStore();

    if (!data.is_macos || data.snoozed || data.status === 'ok' || data.status === 'not_mac') {
        dismissToast(MAC_TOAST_KEY);
        // Setup completed (or dismissed/snoozed); if the modal is open
        // from a previous tick, close it automatically so the user
        // doesn't have to dismiss a stale onboarding prompt.
        if (store?.shown && data.status === 'ok') store.close();
        return;
    }
    // First-run onboarding: BlackHole isn't installed and we haven't
    // shown the blocking modal yet. Force the conversation.
    if (data.status === 'no_loopback' && !localStorage.getItem(MAC_ONBOARDING_SHOWN_KEY)) {
        localStorage.setItem(MAC_ONBOARDING_SHOWN_KEY, '1');
        if (store) store.open();
        return;
    }
    const copy = MAC_STATUS_COPY[data.status];
    if (!copy) {
        dismissToast(MAC_TOAST_KEY);
        return;
    }
    if (store && data.details) store.diagnosticDetails = data.details;
    pushToast({
        key: MAC_TOAST_KEY,
        level: copy.level,
        title: copy.title,
        detail: data.details || '',
        actions: [
            {id: 'mac-audio-fix', label: 'Fix audio setup'},
            {id: 'mac-audio-snooze', label: 'Snooze 24h'},
        ],
    });
}

// Settings-tab re-trigger: if the user dismissed the onboarding modal
// (or snoozed the banner) and later realizes they need the guide after
// all, this link clears every dismissal signal and reopens the modal.
// Doubles as the debug escape hatch when testing onboarding flow
// locally without having to uninstall BlackHole.
async function reopenFromSettings(ev) {
    ev.preventDefault();
    localStorage.removeItem(MAC_ONBOARDING_SHOWN_KEY);
    try {
        await fetch('/api/mac-audio/unsnooze', {method: 'POST'});
    } catch (e) { /* non-fatal; modal still opens */ }
    ensureStore()?.open();
}

// Toast actions registered at module load (not inside initMacAudio) so
// they're available even if the modal element isn't in the DOM yet.
registerToastAction('mac-audio-fix', () => ensureStore()?.open());
registerToastAction('mac-audio-snooze', () => ensureStore()?.snooze(false));

export function initMacAudio() {
    const macModal = document.getElementById('mac-audio-modal');
    if (!macModal) return;

    // Help link click handler stays imperative — element lives outside
    // the modal in the Settings panel.
    document.getElementById('link-mac-audio-help')?.addEventListener('click', reopenFromSettings);

    // Escape closes the modal (keyboard shortcut, not Alpine-bound).
    document.addEventListener('keydown', (e) => {
        const store = window.Alpine?.store('macAudio');
        if (e.key === 'Escape' && store?.shown) store.close();
    });

    pollMacAudioHealth();
    setInterval(pollMacAudioHealth, 30000);
}
