// ============================================================
// WebSocket: Connection, reconnection, authentication
// ============================================================

import {
    dom, t, setWs, getWs,
    WS_BACKOFF_BASE_MS, WS_BACKOFF_MAX_MS,
    WS_MAX_RECONNECT_ATTEMPTS, WS_PING_INTERVAL_MS,
    authRequired, showUpdateBanner,
} from './core.js';
import { updateUI, setFrequencyData, stopPositionInterpolation, loadAudioDevices } from './player.js';
import { loadScripts, loadScriptsTable, displayQuickAccess } from './scripts.js';
import { loadCallbacksTable } from './callbacks.js';
import { loadHeaderLinks, loadLinksTable } from './links.js';
import { updateForegroundUI } from './foreground.js';

let reconnectTimeout = null;
let wsReconnectAttempts = 0;
// Track the ping interval against the socket that owns it so we never leak
// a timer if connectWebSocket() is called while a previous socket is still
// alive. The pair is wiped on close to avoid double-clear races.
let activeSocket = null;
let activePingInterval = null;

function clearReconnect() {
    if (reconnectTimeout) {
        clearTimeout(reconnectTimeout);
        reconnectTimeout = null;
    }
}

function clearPing() {
    if (activePingInterval) {
        clearInterval(activePingInterval);
        activePingInterval = null;
    }
}

export function showAuthForm(errorMessage = '') {
    const overlay = document.getElementById('auth-overlay');
    overlay.classList.remove('hidden');

    const errorEl = document.getElementById('auth-error');
    if (errorMessage) {
        errorEl.textContent = errorMessage;
        errorEl.classList.add('visible');
    } else {
        errorEl.classList.remove('visible');
    }
}

function hideAuthForm() {
    document.getElementById('auth-overlay').classList.add('hidden');
}

export function authenticate() {
    const token = document.getElementById('token-input').value.trim();
    if (!token) {
        showAuthForm(t('auth.required'));
        return;
    }

    localStorage.setItem('media_server_token', token);
    connectWebSocket(token);
}

export function clearToken() {
    localStorage.removeItem('media_server_token');
    const current = getWs();
    if (current) {
        try { current.close(1000, 'token cleared'); } catch { /* ignore */ }
    }
    showAuthForm(t('auth.cleared'));
}

export function connectWebSocket(token) {
    // Always cancel a pending reconnect first — otherwise a user-triggered
    // reconnect can race a scheduled one and create two live sockets.
    clearReconnect();
    clearPing();

    // Close any previous socket cleanly before opening a new one.
    const previous = activeSocket;
    activeSocket = null;
    if (previous && previous.readyState <= WebSocket.OPEN) {
        try { previous.close(1000, 'reconnecting'); } catch { /* ignore */ }
    }

    const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:';
    const wsBase = `${protocol}//${window.location.host}/api/media/ws`;

    // Prefer Sec-WebSocket-Protocol-based auth so the token never appears in
    // the URL (which would otherwise land in browser history, server access
    // logs, and Referer headers). Keep the ?token=... fallback for clients
    // that pre-date this change and don't speak the subprotocol.
    let newWs;
    if (token) {
        try {
            newWs = new WebSocket(wsBase, [`media-server.token.${token}`]);
        } catch (e) {
            console.warn('Subprotocol WS handshake failed, falling back to ?token=', e);
            newWs = new WebSocket(`${wsBase}?token=${encodeURIComponent(token)}`);
        }
    } else {
        newWs = new WebSocket(wsBase);
    }
    activeSocket = newWs;
    setWs(newWs);

    newWs.onopen = () => {
        console.log('WebSocket connected');
        wsReconnectAttempts = 0;
        updateConnectionStatus(true);
        hideConnectionBanner();
        hideAuthForm();
        loadScripts();
        loadScriptsTable();
        loadCallbacksTable();
        loadLinksTable();
        loadHeaderLinks();
        loadAudioDevices();
    };

    newWs.onmessage = (event) => {
        let msg;
        try {
            msg = JSON.parse(event.data);
        } catch (err) {
            console.warn('Ignoring malformed WebSocket frame:', err);
            return;
        }

        if (msg.type === 'status' || msg.type === 'status_update') {
            updateUI(msg.data);
        } else if (msg.type === 'foreground' || msg.type === 'foreground_update') {
            updateForegroundUI(msg.data);
        } else if (msg.type === 'scripts_changed') {
            console.log('Scripts changed, reloading...');
            loadScripts();
            loadScriptsTable();
        } else if (msg.type === 'links_changed') {
            console.log('Links changed, reloading...');
            loadHeaderLinks();
            loadLinksTable();
            displayQuickAccess();
        } else if (msg.type === 'update_available') {
            showUpdateBanner(msg.data);
        } else if (msg.type === 'audio_data') {
            setFrequencyData(msg.data);
        } else if (msg.type === 'error') {
            console.error('WebSocket error:', msg.message);
        }
    };

    newWs.onerror = (error) => {
        console.error('WebSocket error:', error);
        updateConnectionStatus(false);
    };

    newWs.onclose = (event) => {
        console.log('WebSocket closed:', event.code);
        updateConnectionStatus(false);
        stopPositionInterpolation();

        // Drop this socket's ping interval. Guard so we don't kill a newer
        // socket's interval if reconnect already started.
        if (activeSocket === newWs) {
            clearPing();
            activeSocket = null;
        }

        if (event.code === 4001) {
            localStorage.removeItem('media_server_token');
            showAuthForm(t('auth.invalid'));
        } else if (event.code !== 1000) {
            wsReconnectAttempts++;

            if (wsReconnectAttempts <= WS_MAX_RECONNECT_ATTEMPTS) {
                const delay = Math.min(
                    WS_BACKOFF_BASE_MS * Math.pow(1.5, wsReconnectAttempts - 1),
                    WS_BACKOFF_MAX_MS
                );
                console.log(`Reconnecting in ${Math.round(delay / 1000)}s (attempt ${wsReconnectAttempts}/${WS_MAX_RECONNECT_ATTEMPTS})...`);

                if (wsReconnectAttempts >= 3) {
                    showConnectionBanner(t('connection.reconnecting').replace('{attempt}', wsReconnectAttempts), false);
                }

                clearReconnect();
                reconnectTimeout = setTimeout(() => {
                    reconnectTimeout = null;
                    const savedToken = localStorage.getItem('media_server_token');
                    if (savedToken || !authRequired) {
                        connectWebSocket(savedToken || '');
                    }
                }, delay);
            } else {
                showConnectionBanner(t('connection.lost'), true);
            }
        }
    };

    clearPing();
    activePingInterval = setInterval(() => {
        if (newWs.readyState === WebSocket.OPEN) {
            try { newWs.send(JSON.stringify({ type: 'ping' })); } catch { /* ignore */ }
        }
    }, WS_PING_INTERVAL_MS);
}

export function updateConnectionStatus(connected) {
    if (connected) {
        dom.statusDot.classList.add('connected');
    } else {
        dom.statusDot.classList.remove('connected');
    }
}

function showConnectionBanner(message, showButton) {
    const banner = document.getElementById('connectionBanner');
    const text = document.getElementById('connectionBannerText');
    const btn = document.getElementById('connectionBannerBtn');
    text.textContent = message;
    btn.style.display = showButton ? '' : 'none';
    banner.classList.remove('hidden');
}

function hideConnectionBanner() {
    const banner = document.getElementById('connectionBanner');
    banner.classList.add('hidden');
}

export function manualReconnect() {
    const savedToken = localStorage.getItem('media_server_token');
    if (savedToken || !authRequired) {
        wsReconnectAttempts = 0;
        hideConnectionBanner();
        connectWebSocket(savedToken || '');
    }
}

// When the browser regains connectivity or the tab becomes visible again,
// drop the backoff and reconnect immediately rather than waiting out the
// current timer.
function reconnectIfNeeded() {
    const current = activeSocket;
    if (current && (current.readyState === WebSocket.OPEN || current.readyState === WebSocket.CONNECTING)) {
        return;
    }
    const savedToken = localStorage.getItem('media_server_token');
    if (savedToken || !authRequired) {
        wsReconnectAttempts = 0;
        connectWebSocket(savedToken || '');
    }
}

window.addEventListener('online', reconnectIfNeeded);
document.addEventListener('visibilitychange', () => {
    if (!document.hidden) reconnectIfNeeded();
});
