/**
 * Main app.js — Dashboard live updates, global handlers.
 *
 * Relies on pedalWS (from websocket.js) for real-time state pushes
 * and provides REST helper functions used by all pages.
 */

/* ── XSS-safe string helper ───────────────────────────────────────── */

function escapeHtml(text) {
    if (text == null) return '';
    const div = document.createElement('div');
    div.appendChild(document.createTextNode(String(text)));
    return div.innerHTML;
}

/* ── REST helpers ───────────────────────────────────────────────── */

async function apiGet(path) {
    const res = await fetch(`/api${path}`);
    if (!res.ok) throw new Error(`API GET ${path}: ${res.status}`);
    return res.json();
}

async function apiPost(path, body = {}) {
    const res = await fetch(`/api${path}`, {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify(body),
    });
    if (!res.ok) throw new Error(`API POST ${path}: ${res.status}`);
    return res.json();
}

async function apiPut(path, body = {}) {
    const res = await fetch(`/api${path}`, {
        method: 'PUT',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify(body),
    });
    if (!res.ok) throw new Error(`API PUT ${path}: ${res.status}`);
    return res.json();
}

async function apiDelete(path) {
    const res = await fetch(`/api${path}`, { method: 'DELETE' });
    if (!res.ok) throw new Error(`API DELETE ${path}: ${res.status}`);
    return res.json();
}

async function apiPatch(path, body = {}) {
    const res = await fetch(`/api${path}`, {
        method: 'PATCH',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify(body),
    });
    if (!res.ok) throw new Error(`API PATCH ${path}: ${res.status}`);
    return res.json();
}

/* ── Dashboard updates (via WebSocket) ──────────────────────────── */

pedalWS.on('connected', (msg) => {
    if (msg.state) updateDashboardState(msg.state);
});

pedalWS.on('state', (msg) => {
    if (msg.state) updateDashboardState(msg.state);
});

pedalWS.on('preset_changed', (msg) => {
    if (msg.preset) {
        const nameEl = document.getElementById('preset-name');
        const metaEl = document.getElementById('preset-meta');
        if (nameEl) nameEl.textContent = msg.preset.name;
        if (metaEl) metaEl.textContent = `Bank ${msg.preset.bank} — Program ${msg.preset.program}`;
        renderFXChain(msg.preset.bank, msg.preset.program);
    }
    if (msg.state) updateDashboardState(msg.state);
});

pedalWS.on('bypass_changed', (msg) => {
    const btn = document.getElementById('bypass-btn');
    if (btn) {
        btn.textContent = msg.bypass ? 'BYPASSED' : 'ACTIVE';
        btn.classList.toggle('active', msg.bypass);
    }
});

pedalWS.on('tuner_changed', (msg) => {
    const btn = document.getElementById('tuner-btn');
    if (btn) {
        btn.textContent = msg.tuner_enabled ? 'ON' : 'OFF';
        btn.classList.toggle('active', msg.tuner_enabled);
    }
    const sBtn = document.getElementById('settings-tuner-btn');
    if (sBtn) {
        sBtn.textContent = msg.tuner_enabled ? 'ON' : 'OFF';
        sBtn.classList.toggle('active', msg.tuner_enabled);
    }
    if (msg.tuner_enabled) startTunerPoll(); else stopTunerPoll();
});

pedalWS.on('routing_changed', (msg) => {
    updateRoutingUI(msg.routing_mode, msg.routing_breakpoint);
});

/* ── 4CM Routing UI ────────────────────────────────────────────── */

function updateRoutingUI(mode, breakpoint) {
    // Dashboard badge
    const indicator = document.getElementById('routing-mode-indicator');
    const info = document.getElementById('routing-info');
    if (indicator) {
        indicator.setAttribute('data-mode', mode);
        if (mode === '4cm') {
            indicator.innerHTML = '<span class="badge badge-4cm">4CM</span>';
        } else {
            indicator.innerHTML = '<span class="badge badge-mono">Mono</span>';
        }
    }
    if (info) {
        info.textContent = mode === '4cm' ? `Breakpoint: ${breakpoint}` : 'Full chain';
    }

    // Settings page controls
    const toggleBtn = document.getElementById('4cm-toggle-btn');
    if (toggleBtn) {
        toggleBtn.textContent = mode === '4cm' ? 'ON' : 'OFF';
        toggleBtn.classList.toggle('active', mode === '4cm');
    }
    const bpRow = document.getElementById('breakpoint-row');
    if (bpRow) {
        bpRow.style.opacity = mode === '4cm' ? '1' : '0.4';
        bpRow.style.pointerEvents = mode === '4cm' ? 'auto' : 'none';
    }
    const bpSlider = document.getElementById('4cm-breakpoint');
    const bpText = document.getElementById('4cm-breakpoint-text');
    if (bpSlider) bpSlider.value = breakpoint;
    if (bpText) bpText.textContent = breakpoint;

    const desc = document.getElementById('4cm-routing-desc');
    if (desc) {
        if (mode === '4cm') {
            desc.innerHTML = `Input 1 (Guitar) → Pre blocks [0..${breakpoint}) → Send &nbsp;|&nbsp; Input 2 (Return) → Post blocks [${breakpoint}..] → Output`;
        } else {
            desc.textContent = 'Mono: Guitar → Full chain → Output';
        }
    }
}

async function toggle4cm() {
    const btn = document.getElementById('4cm-toggle-btn');
    const currentMode = btn && btn.classList.contains('active') ? '4cm' : 'mono';
    const newMode = currentMode === '4cm' ? 'mono' : '4cm';
    try {
        await apiPost('/routing', { routing_mode: newMode });
    } catch (e) {
        console.warn('Failed to toggle 4CM:', e);
    }
}

async function set4cmBreakpoint(val) {
    const bp = parseInt(val);
    try {
        await apiPost('/routing', { routing_breakpoint: bp });
    } catch (e) {
        console.warn('Failed to set breakpoint:', e);
    }
}

function updateDashboardState(state) {
    if (!state || !state.connected) return;

    // Preset info
    if (state.current_preset) {
        const nameEl = document.getElementById('preset-name');
        const metaEl = document.getElementById('preset-meta');
        if (nameEl) nameEl.textContent = state.current_preset.name;
        if (metaEl) metaEl.textContent = `Bank ${state.current_preset.bank} — Program ${state.current_preset.program}`;
        // Render FX chain
        renderFXChain(state.current_preset.bank, state.current_preset.program);
    }

    // Bypass
    const bypassBtn = document.getElementById('bypass-btn');
    if (bypassBtn) {
        bypassBtn.textContent = state.bypass ? 'BYPASSED' : 'ACTIVE';
        bypassBtn.classList.toggle('active', state.bypass);
    }

    // Tuner
    const tunerBtn = document.getElementById('tuner-btn');
    if (tunerBtn) {
        tunerBtn.textContent = state.tuner_enabled ? 'ON' : 'OFF';
        tunerBtn.classList.toggle('active', state.tuner_enabled);
    }
    const sTunerBtn = document.getElementById('settings-tuner-btn');
    if (sTunerBtn) {
        sTunerBtn.textContent = state.tuner_enabled ? 'ON' : 'OFF';
        sTunerBtn.classList.toggle('active', state.tuner_enabled);
    }
    // Start/stop tuner polling based on current state
    if (state.tuner_enabled) startTunerPoll(); else stopTunerPoll();

    // Volume
    const volSlider = document.getElementById('volume-slider');
    const volText = document.getElementById('volume-text');
    const sVolSlider = document.getElementById('settings-volume');
    const sVolText = document.getElementById('settings-volume-text');
    const vol = Math.round((state.master_volume || 0.8) * 100);
    if (volSlider) volSlider.value = vol;
    if (volText) volText.textContent = vol + '%';
    if (sVolSlider) sVolSlider.value = vol;
    if (sVolText) sVolText.textContent = vol + '%';

    // NAM model
    const modelNameEl = document.getElementById('model-name');
    if (modelNameEl) modelNameEl.textContent = state.nam_model || 'None loaded';

    // IR
    const irNameEl = document.getElementById('ir-name');
    if (irNameEl) irNameEl.textContent = state.ir_name || 'None loaded';

    // 4CM Routing
    if (state.routing_mode) {
        updateRoutingUI(state.routing_mode, state.routing_breakpoint);
    }
}

/* ── Tuner pitch polling ────────────────────────────────────────── */

const STRING_NAMES = {1: 'E', 2: 'A', 3: 'D', 4: 'G', 5: 'B', 6: 'e'};
let _tunerPollTimer = null;

async function pollTunerPitch() {
    try {
        const data = await apiGet('/tuner/pitch');
        const display = document.getElementById('tuner-display');
        if (!display) return;

        if (!data.enabled) {
            display.style.display = 'none';
            stopTunerPoll();
            return;
        }

        display.style.display = 'flex';
        document.getElementById('tuner-note').textContent = data.note || '--';

        // Cents bar: -50 to +50 maps to 0%–100% fill
        const cents = Math.max(-50, Math.min(50, data.cents || 0));
        const fillPct = ((cents + 50) / 100) * 100;
        const fillEl = document.getElementById('tuner-cents-fill');
        if (fillEl) fillEl.style.width = fillPct + '%';

        document.getElementById('tuner-cents-text').textContent =
            (data.cents > 0 ? '+' : '') + data.cents + '¢';

        const strEl = document.getElementById('tuner-string');
        if (strEl) {
            const str = data.string;
            if (str >= 1 && str <= 6) {
                strEl.textContent = STRING_NAMES[str] || '—';
                strEl.className = 'tuner-string-active';
            } else {
                strEl.textContent = '—';
                strEl.className = '';
            }
        }
    } catch (e) {
        // Silent — tuner endpoint may 503 if pedal disconnected
    }
}

function startTunerPoll() {
    stopTunerPoll();
    pollTunerPitch(); // immediate first poll
    _tunerPollTimer = setInterval(pollTunerPitch, 100); // 100ms for responsive tuning
}

function stopTunerPoll() {
    if (_tunerPollTimer) {
        clearInterval(_tunerPollTimer);
        _tunerPollTimer = null;
    }
}

/* ── Dashboard action handlers ─────────────────────────────────── */

async function toggleBypass() {
    const data = await apiPost('/bypass');
    // WS will update UI
}

async function toggleTuner() {
    const enabled = !document.getElementById('tuner-btn')?.classList.contains('active');
    const data = await apiPost('/tuner', { enabled });
    if (enabled) startTunerPoll(); else stopTunerPoll();
}

async function setVolume(val) {
    const vol = parseInt(val) / 100;
    await apiPut('/volume', { volume: vol });
}

async function activatePreset(direction, delta) {
    const state = await apiGet('/state');
    if (!state.connected) return;
    const bank = state.current_preset?.bank || 0;
    const program = (state.current_preset?.program || 0) + delta;
    // Clamp 0-3 within the bank
    const clamped = Math.max(0, Math.min(3, program));
    if (clamped !== program) return;  // at edge
    await apiPost(`/presets/${bank}/${clamped}/activate`);
}

/* ── FX Chain renderer ───────────────────────────────────────── */

const FX_TYPE_LABELS = {
    noise_gate: 'Noise Gate',
    compressor: 'Compressor',
    expander: 'Expander',
    de_esser: 'De-esser',
    transient_shaper: 'Transient Shaper',
    sidechain_comp: 'Sidechain Comp',
    boost: 'Boost',
    overdrive: 'Overdrive',
    distortion: 'Distortion',
    fuzz: 'Fuzz',
    bitcrusher: 'Bitcrusher',
    wavefolder: 'Wavefolder',
    rectifier: 'Rectifier',
    nam_amp: 'NAM Amp',
    ir_cab: 'IR Cab',
    chorus: 'Chorus',
    flanger: 'Flanger',
    phaser: 'Phaser',
    tremolo: 'Tremolo',
    vibrato: 'Vibrato',
    ring_mod: 'Ring Mod',
    auto_wah: 'Auto-Wah',
    envelope_filter: 'Env Filter',
    rotary_speaker: 'Rotary',
    uni_vibe: 'Uni-Vibe',
    auto_pan: 'Auto-Pan',
    stereo_widener: 'Stereo Widener',
    delay: 'Delay',
    ping_pong_delay: 'Ping-Pong',
    multi_tap_delay: 'Multi-Tap',
    reverse_delay: 'Reverse',
    tape_echo: 'Tape Echo',
    reverb: 'Reverb',
    shimmer_reverb: 'Shimmer',
    early_reflections: 'Early Refl.',
    looper: 'Looper',
    octaver: 'Octaver',
    detune: 'Detune',
    harmonizer: 'Harmonizer',
    whammy: 'Whammy',
    pitch_shifter: 'Pitch Shift',
    eq: 'EQ',
    parametric_eq: 'Param EQ',
    high_pass: 'High Pass',
    low_pass: 'Low Pass',
    band_pass: 'Band Pass',
    notch: 'Notch',
    formant: 'Formant',
    wah: 'Wah',
    volume: 'Volume',
};

const FX_CATEGORY_COLORS = {
    dynamics: { bg: '#1b5e20', text: '#a5d6a7' },
    drive: { bg: '#bf360c', text: '#ffccbc' },
    amp: { bg: '#4a148c', text: '#ce93d8' },
    modulation: { bg: '#01579b', text: '#81d4fa' },
    time: { bg: '#33691e', text: '#c5e1a5' },
    pitch: { bg: '#e65100', text: '#ffe0b2' },
    filter: { bg: '#263238', text: '#b0bec5' },
    utility: { bg: '#37474f', text: '#cfd8dc' },
};

function fxCategory(fxType) {
    const cat = {
        noise_gate: 'dynamics', compressor: 'dynamics', expander: 'dynamics',
        de_esser: 'dynamics', transient_shaper: 'dynamics', sidechain_comp: 'dynamics',
        boost: 'drive', overdrive: 'drive', distortion: 'drive', fuzz: 'drive',
        bitcrusher: 'drive', wavefolder: 'drive', rectifier: 'drive',
        nam_amp: 'amp', ir_cab: 'amp',
        chorus: 'modulation', flanger: 'modulation', phaser: 'modulation',
        tremolo: 'modulation', vibrato: 'modulation', ring_mod: 'modulation',
        auto_wah: 'modulation', envelope_filter: 'modulation', rotary_speaker: 'modulation',
        uni_vibe: 'modulation', auto_pan: 'modulation', stereo_widener: 'modulation',
        delay: 'time', ping_pong_delay: 'time', multi_tap_delay: 'time',
        reverse_delay: 'time', tape_echo: 'time', reverb: 'time',
        shimmer_reverb: 'time', early_reflections: 'time', looper: 'time',
        octaver: 'pitch', detune: 'pitch', harmonizer: 'pitch',
        whammy: 'pitch', pitch_shifter: 'pitch',
        eq: 'filter', parametric_eq: 'filter', high_pass: 'filter',
        low_pass: 'filter', band_pass: 'filter', notch: 'filter',
        formant: 'filter', wah: 'filter',
        volume: 'utility',
    }[fxType] || 'utility';
    return FX_CATEGORY_COLORS[cat] || FX_CATEGORY_COLORS.utility;
}

function fxLabel(fxType) {
    return FX_TYPE_LABELS[fxType] || fxType.replace(/_/g, ' ').replace(/\b\w/g, c => c.toUpperCase());
}

async function renderFXChain(bank, program) {
    const container = document.getElementById('fx-chain');
    const placeholder = document.getElementById('fx-chain-placeholder');
    if (!container) return;

    if (bank == null || program == null) {
        if (placeholder) placeholder.style.display = 'block';
        return;
    }

    try {
        const preset = await apiGet(`/presets/${bank}/${program}`);
        const chain = preset.chain || [];
        if (placeholder) placeholder.style.display = 'none';

        if (chain.length === 0) {
            container.innerHTML = '<div class="fx-block-chain-placeholder"><p>No FX blocks in chain.</p></div>';
            return;
        }

        let html = '';
        for (const block of chain) {
            const isOn = block.enabled && !block.bypass;
            let label = fxLabel(block.fx_type);
            // Show actual model/IR name instead of generic type label
            if (block.fx_type === 'nam_amp' && block.nam_model_name) {
                label = block.nam_model_name.replace(/_/g, ' ');
            } else if (block.fx_type === 'ir_cab' && block.ir_file_name) {
                label = block.ir_file_name.replace(/_/g, ' ');
            }
            const cat = fxCategory(block.fx_type);
            const bid = block.block_id || block.fx_type;
            html += `<div class="fx-block ${isOn ? '' : 'bypassed'}"
                          data-block-id="${bid}"
                          onclick="toggleFXBlock(this)"
                          style="cursor:pointer; border-color: ${isOn ? cat.bg : 'var(--border)'}">
                        <span class="fx-badge" style="background: ${isOn ? cat.bg : 'var(--text-muted)'}">
                            ${isOn ? 'ON' : 'OFF'}
                        </span>
                        <span class="fx-name">${label}</span>
                     </div>`;
        }
        container.innerHTML = html;
    } catch (e) {
        console.warn('Failed to load FX chain:', e);
        if (placeholder) placeholder.style.display = 'block';
    }
}

async function toggleFXBlock(el) {
    // Read current state from the DOM element
    const bid = el.dataset.blockId;
    const currentEnabled = !el.classList.contains('bypassed');
    try {
        await apiPatch('/blocks', { block_id: bid, enabled: !currentEnabled });
        // Re-fetch and re-render
        const state = await apiGet('/state');
        if (state.connected && state.current_preset) {
            await renderFXChain(state.current_preset.bank, state.current_preset.program);
        }
    } catch (e) {
        console.warn('Failed to toggle block:', e);
    }
}

/* ── Audio profile functions ──────────────────────────────────── */

async function refreshAudioProfile() {
    const select = document.getElementById('audio-profile-select');
    const status = document.getElementById('jack-status');
    if (!select) return;

    try {
        // Fetch available profiles
        const profilesData = await apiGet('/audio/profiles');
        const current = profilesData.current || 'standard';
        const sortedKeys = (profilesData.profiles || []).map(p => p.key);

        // Build dropdown
        let opts = '';
        for (const key of sortedKeys) {
            const p = profilesData.profiles.find(x => x.key === key) || {};
            const label = key.charAt(0).toUpperCase() + key.slice(1).replace(/_/g, ' ');
            const selected = key === current ? 'selected' : '';
            opts += `<option value="${key}" ${selected}>${label}</option>`;
        }
        select.innerHTML = opts;

        // Fetch current profile detail
        const detail = await apiGet('/audio/profile');
        if (status) {
            const running = detail.jack_running ? '🟢 Running' : '🔴 Stopped';
            const xruns = detail.xrun_count != null ? ` · ${detail.xrun_count} xruns` : '';
            status.textContent = `${running} · ${detail.period}/${detail.rate}${xruns}`;
        }
    } catch (e) {
        select.innerHTML = '<option value="">Offline</option>';
        if (status) status.textContent = '🔴 Offline';
    }
}

async function switchAudioProfile(profileKey) {
    if (!profileKey) return;
    const select = document.getElementById('audio-profile-select');
    const status = document.getElementById('jack-status');
    if (status) status.textContent = '⏳ Switching...';
    select.disabled = true;

    try {
        const result = await apiPost('/audio/profile', { profile: profileKey });
        if (status) {
            if (result.restarted) {
                status.textContent = `✅ Switched to ${profileKey} (JACK restarted)`;
            } else {
                status.textContent = `✅ Already on ${profileKey}`;
            }
        }
        await refreshAudioProfile();
    } catch (e) {
        if (status) status.textContent = `❌ Failed: ${e.message}`;
        await refreshAudioProfile();
    } finally {
        select.disabled = false;
    }
}

/* ── Initial page load: fetch state ─────────────────────────────── */

document.addEventListener('DOMContentLoaded', async () => {
    // If we're on the dashboard, fetch initial state
    if (document.getElementById('preset-name')) {
        try {
            const state = await apiGet('/state');
            updateDashboardState(state);
        } catch (e) {
            console.warn('Could not fetch initial state:', e);
        }
    }

    // Update WebSocket client count on settings page
    const wsCount = document.getElementById('ws-count');
    if (wsCount) {
        try {
            const state = await apiGet('/state');
            wsCount.textContent = pedalWS._handlers ? '1' : '0';
        } catch (e) {}
    }

    // If on settings page with audio profile selector, load profiles
    if (document.getElementById('audio-profile-select')) {
        refreshAudioProfile();
    }
});