// ============================================================
// App: Entry point — imports all modules, registers window globals,
// and orchestrates initialization (replaces main.js)
// ============================================================

// Layer 0: Core state & utilities
import {
    cacheDom, dom, registerUpdateCallbacks,
    initLocale, fetchVersion, formatTime, setupIconPreview,
    isUserAdjustingVolume, setIsUserAdjustingVolume,
    volumeUpdateTimer, setVolumeUpdateTimer,
    currentDuration, currentPosition, setVolume, seek,
    togglePlayPause, nextTrack, previousTrack, toggleMute,
    VOLUME_THROTTLE_MS, VOLUME_RELEASE_DELAY_MS,
    changeLocale, t,
    setAuthRequired,
    showAboutDialog, closeAboutDialog,
} from './core.js';

// Layer 1: Player (tabs, theme, accent, vinyl, visualizer, UI)
import {
    activeTab, switchTab, updateTabIndicator, setMiniPlayerVisible,
    initTheme, toggleTheme, initAccentColor, applyAccentColor,
    renderAccentSwatches, selectAccentColor, toggleAccentPicker, lightenColor,
    visualizerEnabled, visualizerAvailable, setVisualizerEnabled,
    checkVisualizerAvailability, toggleVisualizer, applyVisualizerMode,
    loadAudioDevices, onAudioDeviceChanged,
    setupProgressDrag, updateUI, updatePlaybackState, stopPositionInterpolation,
    togglePlayerFullscreen, initPlayerFullscreen,
} from './player.js';

// Layer 2: WebSocket
import {
    connectWebSocket, showAuthForm, authenticate, clearToken,
    manualReconnect, updateConnectionStatus,
} from './websocket.js';

// Layer 3: Features
import {
    loadScripts, loadScriptsTable, displayQuickAccess,
    showAddScriptDialog, showEditScriptDialog, closeScriptDialog, saveScript,
    deleteScriptConfirm, executeScriptDebug, executeCallbackDebug,
    closeExecutionDialog, scriptFormDirty, setScriptFormDirty,
    addParameterRow, closeScriptParamsDialog, submitScriptWithParams,
} from './scripts.js';

import {
    loadCallbacksTable,
    showAddCallbackDialog, showEditCallbackDialog, closeCallbackDialog,
    saveCallback, deleteCallbackConfirm,
    callbackFormDirty, setCallbackFormDirty,
} from './callbacks.js';

import {
    loadMediaFolders, initBrowserToolbar, thumbnailCache,
    setViewMode, refreshBrowser, playAllFolder,
    previousPage, nextPage, goToPage,
    onBrowserSearch, clearBrowserSearch, onItemsPerPageChanged,
    downloadFile, closeFolderDialog, saveFolder,
    showManageFoldersDialog,
    showAddFolderDialog, showEditFolderDialog, deleteFolderConfirm,
} from './browser.js';

import {
    loadDisplayMonitors, onDisplayBrightnessInput, onDisplayBrightnessChange,
    onDisplayContrastInput, onDisplayContrastChange,
    onDisplayInputSourceChange, onDisplayColorPresetChange, onDisplayPictureModeChange,
    toggleDisplayPower, loadHeaderLinks, loadLinksTable,
    showAddLinkDialog, showEditLinkDialog, closeLinkDialog, saveLink, deleteLinkConfirm,
    linkFormDirty, setLinkFormDirty,
} from './links.js';

import {
    toggleDynamicBackground, applyDynamicBackground, updateBackgroundColors,
} from './background.js';

import {
    updateForegroundUI, loadForegroundProcess,
} from './foreground.js';

// ============================================================
// Register late-bound callbacks for core's updateAllText()
// ============================================================

registerUpdateCallbacks({
    updatePlaybackState,
    updateConnectionStatus,
    loadScriptsTable,
    loadCallbacksTable,
    loadLinksTable,
    displayQuickAccess,
    renderAccentSwatches,
});

// ============================================================
// Register all functions on window for HTML onclick handlers
// ============================================================

Object.assign(window, {
    // Player controls
    togglePlayPause, nextTrack, previousTrack, toggleMute, seek,
    // Tabs
    switchTab,
    // Theme & accent
    toggleTheme, toggleAccentPicker, selectAccentColor, lightenColor,
    // Visualizer (vinyl spin is structural CSS — no toggle)
    toggleVisualizer,
    // Background
    toggleDynamicBackground,
    // Fullscreen
    togglePlayerFullscreen,
    // Auth
    authenticate, clearToken, manualReconnect,
    // Locale
    changeLocale,
    // Scripts
    showAddScriptDialog, showEditScriptDialog, closeScriptDialog, saveScript,
    deleteScriptConfirm, executeScriptDebug, executeCallbackDebug,
    closeExecutionDialog,
    addParameterRow, closeScriptParamsDialog, submitScriptWithParams,
    // Callbacks
    showAddCallbackDialog, showEditCallbackDialog, closeCallbackDialog,
    saveCallback, deleteCallbackConfirm,
    // Browser
    setViewMode, refreshBrowser, playAllFolder,
    previousPage, nextPage, goToPage,
    onBrowserSearch, clearBrowserSearch, onItemsPerPageChanged,
    downloadFile, closeFolderDialog, saveFolder,
    showManageFoldersDialog,
    showAddFolderDialog, showEditFolderDialog, deleteFolderConfirm,
    // Links
    showAddLinkDialog, showEditLinkDialog, closeLinkDialog,
    saveLink, deleteLinkConfirm,
    // Display
    loadDisplayMonitors, onDisplayBrightnessInput, onDisplayBrightnessChange,
    onDisplayContrastInput, onDisplayContrastChange,
    onDisplayInputSourceChange, onDisplayColorPresetChange, onDisplayPictureModeChange,
    toggleDisplayPower,
    // Audio device
    onAudioDeviceChanged,
    // About
    showAboutDialog, closeAboutDialog,
    // Foreground
    loadForegroundProcess,
});

// ============================================================
// Initialization (DOMContentLoaded)
// ============================================================

// Prevent <dialog>.showModal() from auto-focusing the first input field.
// On touch devices this pops up the on-screen keyboard, which is confusing
// when the user just opened a dialog. Force focus onto the dialog itself.
const _origShowModal = HTMLDialogElement.prototype.showModal;
HTMLDialogElement.prototype.showModal = function (...args) {
    if (!this.hasAttribute('tabindex')) {
        this.setAttribute('tabindex', '-1');
    }
    const result = _origShowModal.apply(this, args);
    const active = document.activeElement;
    if (active && active !== this && this.contains(active)) {
        active.blur();
        this.focus({ preventScroll: true });
    }
    return result;
};

// CSP-safe replacement for inline on* handlers. HTML uses data-onclick,
// data-onchange, data-oninput, data-onsubmit with simple call expressions
// like "fn()", "fn('arg')", "fn(event)". We parse those at startup and
// attach proper addEventListener calls so script-src 'self' stays strict.
const INLINE_HANDLER_EVENTS = {
    'data-onclick': 'click',
    'data-onchange': 'change',
    'data-oninput': 'input',
    'data-onsubmit': 'submit',
};

function parseInlineHandlerArg(token) {
    const t = token.trim();
    if (t === '') return { kind: 'empty' };
    if (t === 'event') return { kind: 'event' };
    if (t === 'true') return { kind: 'literal', value: true };
    if (t === 'false') return { kind: 'literal', value: false };
    if (t === 'null') return { kind: 'literal', value: null };
    if (/^-?\d+(\.\d+)?$/.test(t)) return { kind: 'literal', value: Number(t) };
    if ((t.startsWith("'") && t.endsWith("'")) || (t.startsWith('"') && t.endsWith('"'))) {
        return { kind: 'literal', value: t.slice(1, -1) };
    }
    console.warn('inline-handler: unsupported arg token', token);
    return { kind: 'literal', value: undefined };
}

function compileInlineHandler(expr) {
    const m = expr.match(/^\s*([A-Za-z_$][\w$]*)\s*\((.*)\)\s*;?\s*$/s);
    if (!m) {
        console.warn('inline-handler: unparsable expression', expr);
        return null;
    }
    const fnName = m[1];
    const argsRaw = m[2].trim();
    const argTokens = argsRaw === '' ? [] : argsRaw.split(',').map(s => s.trim());
    const parsedArgs = argTokens.map(parseInlineHandlerArg);
    return function (event) {
        const fn = window[fnName];
        if (typeof fn !== 'function') {
            console.error('inline-handler: missing global function', fnName);
            return;
        }
        const args = parsedArgs.map(a => a.kind === 'event' ? event : a.value);
        return fn.apply(this, args);
    };
}

function wireInlineHandlers(root) {
    for (const [attr, eventName] of Object.entries(INLINE_HANDLER_EVENTS)) {
        const nodes = root.querySelectorAll(`[${attr}]`);
        for (const el of nodes) {
            const expr = el.getAttribute(attr);
            const handler = compileInlineHandler(expr);
            if (handler) el.addEventListener(eventName, handler);
            el.removeAttribute(attr);
        }
    }
}

window.addEventListener('DOMContentLoaded', async () => {
    // Cache DOM references
    cacheDom();

    // Wire CSP-safe inline-handler stand-ins from index.html
    wireInlineHandlers(document);

    // Initialize theme and accent color
    initTheme();
    initAccentColor();
    initPlayerFullscreen();

    // Register service worker for PWA installability
    if ('serviceWorker' in navigator) {
        navigator.serviceWorker.register('/sw.js').catch(() => {});
    }

    // Build the editorial spectrum bars. Fewer, fatter bars read better
    // than many thin ones at this column width. JS-managed so we can
    // drive heights from real audio data when available.
    const spectrumRoot = document.getElementById('player-spectrum');
    if (spectrumRoot && !spectrumRoot.children.length) {
        const SPECTRUM_BARS = 40;
        // CSS repeat() doesn't accept a var() for its count — set the
        // grid column template from JS so it always matches the bar
        // count and stretches each bar to claim 1fr of the row.
        spectrumRoot.style.gridTemplateColumns =
            `repeat(${SPECTRUM_BARS}, minmax(0, 1fr))`;
        const frag = document.createDocumentFragment();
        for (let i = 0; i < SPECTRUM_BARS; i++) {
            const s = document.createElement('span');
            // Pseudo-random initial scaleY for the synthetic CSS-only
            // animation (used while no real audio is flowing).
            const scale = (0.25 + Math.abs(Math.sin(i * 0.7)) * 0.70).toFixed(2);
            s.style.setProperty('--bar-h-scale', scale);
            s.style.setProperty('--bar-delay', (-Math.random() * 1.1).toFixed(2) + 's');
            frag.appendChild(s);
        }
        spectrumRoot.appendChild(frag);
    }

    // Initialize audio visualizer — auto-enable when supported so the
    // spectrum shows real audio out of the box.
    checkVisualizerAvailability().then(() => {
        if (!visualizerAvailable) return;
        // First install: opt the user in by default since the spectrum
        // is the centerpiece of the player view.
        const stored = localStorage.getItem('visualizerEnabled');
        const shouldEnable = stored === null ? true : stored === 'true';
        if (shouldEnable) {
            setVisualizerEnabled(true);   // updates the let in player.js
            applyVisualizerMode();
        }
    });

    // Initialize dynamic background
    applyDynamicBackground();

    // Initialize locale (async - loads JSON file)
    await initLocale();

    // Load version from health endpoint
    fetchVersion();

    // Check if authentication is required
    let authReq = true;
    try {
        const healthResp = await fetch('/api/health');
        const healthData = await healthResp.json();
        authReq = healthData.auth_required !== false;
    } catch { /* assume auth required on error */ }
    setAuthRequired(authReq);

    const token = localStorage.getItem('media_server_token');
    if (!authReq) {
        // No auth required — connect directly without token
        connectWebSocket('');
        loadScripts();
        loadScriptsTable();
        loadCallbacksTable();
        loadLinksTable();
        loadAudioDevices();
    } else if (token) {
        connectWebSocket(token);
        loadScripts();
        loadScriptsTable();
        loadCallbacksTable();
        loadLinksTable();
        loadAudioDevices();
    } else {
        showAuthForm();
    }

    // Shared volume slider setup (avoids duplicate handler code)
    function setupVolumeSlider(sliderId) {
        const slider = document.getElementById(sliderId);
        slider.addEventListener('input', (e) => {
            setIsUserAdjustingVolume(true);
            const volume = parseInt(e.target.value);
            // Sync both sliders and displays
            dom.volumeDisplay.textContent = `${volume}%`;
            dom.miniVolumeDisplay.textContent = `${volume}%`;
            dom.volumeSlider.value = volume;
            dom.miniVolumeSlider.value = volume;

            if (volumeUpdateTimer) clearTimeout(volumeUpdateTimer);
            setVolumeUpdateTimer(setTimeout(() => {
                setVolume(volume);
                setVolumeUpdateTimer(null);
            }, VOLUME_THROTTLE_MS));
        });

        slider.addEventListener('change', (e) => {
            if (volumeUpdateTimer) {
                clearTimeout(volumeUpdateTimer);
                setVolumeUpdateTimer(null);
            }
            const volume = parseInt(e.target.value);
            setVolume(volume);
            setTimeout(() => { setIsUserAdjustingVolume(false); }, VOLUME_RELEASE_DELAY_MS);
        });
    }

    setupVolumeSlider('volume-slider');
    setupVolumeSlider('mini-volume-slider');

    // Restore saved tab (migrate old tab names)
    let savedTab = localStorage.getItem('activeTab') || 'player';
    if (['scripts', 'callbacks', 'links'].includes(savedTab)) savedTab = 'settings';
    switchTab(savedTab);
    // Snap indicator to initial position without animation
    const initialActiveBtn = document.querySelector('.tab-btn.active');
    if (initialActiveBtn) updateTabIndicator(initialActiveBtn, false);

    // Re-position tab indicator on window resize
    window.addEventListener('resize', () => {
        const activeBtn = document.querySelector('.tab-btn.active');
        if (activeBtn) updateTabIndicator(activeBtn, false);
    });

    // Mini Player: Intersection Observer to show/hide when main player scrolls out of view
    const playerContainer = document.querySelector('.player-container');

    const observer = new IntersectionObserver((entries) => {
        entries.forEach(entry => {
            if (activeTab !== 'player') return;
            setMiniPlayerVisible(!entry.isIntersecting);
        });
    }, { threshold: 0.1 });
    observer.observe(playerContainer);

    // Drag-to-seek for progress bars
    setupProgressDrag(
        document.getElementById('mini-progress-bar'),
        document.getElementById('mini-progress-fill')
    );
    setupProgressDrag(
        document.getElementById('progress-bar'),
        document.getElementById('progress-fill')
    );

    // Enter key in token input
    document.getElementById('token-input').addEventListener('keypress', (e) => {
        if (e.key === 'Enter') {
            authenticate();
        }
    });

    // Script form dirty state tracking
    const scriptForm = document.getElementById('scriptForm');
    scriptForm.addEventListener('input', () => {
        setScriptFormDirty(true);
    });
    scriptForm.addEventListener('change', () => {
        setScriptFormDirty(true);
    });

    // Callback form dirty state tracking
    const callbackForm = document.getElementById('callbackForm');
    callbackForm.addEventListener('input', () => {
        setCallbackFormDirty(true);
    });
    callbackForm.addEventListener('change', () => {
        setCallbackFormDirty(true);
    });

    // Script dialog backdrop click to close
    const scriptDialog = document.getElementById('scriptDialog');
    scriptDialog.addEventListener('click', (e) => {
        if (e.target === scriptDialog) {
            closeScriptDialog();
        }
    });

    // Callback dialog backdrop click to close
    const callbackDialog = document.getElementById('callbackDialog');
    callbackDialog.addEventListener('click', (e) => {
        if (e.target === callbackDialog) {
            closeCallbackDialog();
        }
    });

    // Delegated click handlers for script table actions (XSS-safe)
    document.getElementById('scriptsTableBody').addEventListener('click', (e) => {
        const btn = e.target.closest('[data-action]');
        if (!btn) return;
        const action = btn.dataset.action;
        const name = btn.dataset.scriptName;
        if (action === 'execute') executeScriptDebug(name);
        else if (action === 'edit') showEditScriptDialog(name);
        else if (action === 'delete') deleteScriptConfirm(name);
    });

    // Delegated click handlers for callback table actions (XSS-safe)
    document.getElementById('callbacksTableBody').addEventListener('click', (e) => {
        const btn = e.target.closest('[data-action]');
        if (!btn) return;
        const action = btn.dataset.action;
        const name = btn.dataset.callbackName;
        if (action === 'execute') executeCallbackDebug(name);
        else if (action === 'edit') showEditCallbackDialog(name);
        else if (action === 'delete') deleteCallbackConfirm(name);
    });

    // Folder dialog backdrop click to close
    const folderDialog = document.getElementById('folderDialog');
    folderDialog.addEventListener('click', (e) => {
        if (e.target === folderDialog) {
            closeFolderDialog();
        }
    });

    // Delegated click handlers for folder table actions
    document.getElementById('foldersTableBody').addEventListener('click', (e) => {
        const btn = e.target.closest('[data-action]');
        if (!btn) return;
        const action = btn.dataset.action;
        const folderId = btn.dataset.folderId;
        if (action === 'edit') showEditFolderDialog(folderId);
        else if (action === 'delete') deleteFolderConfirm(folderId);
    });

    // Link dialog backdrop click to close
    const linkDialog = document.getElementById('linkDialog');
    linkDialog.addEventListener('click', (e) => {
        if (e.target === linkDialog) {
            closeLinkDialog();
        }
    });

    // About dialog backdrop click to close
    const aboutDialog = document.getElementById('aboutDialog');
    if (aboutDialog) {
        aboutDialog.addEventListener('click', (e) => {
            if (e.target === aboutDialog) {
                closeAboutDialog();
            }
        });
    }

    // Delegated click handlers for link table actions (XSS-safe)
    document.getElementById('linksTableBody').addEventListener('click', (e) => {
        const btn = e.target.closest('[data-action]');
        if (!btn) return;
        const action = btn.dataset.action;
        const name = btn.dataset.linkName;
        if (action === 'edit') showEditLinkDialog(name);
        else if (action === 'delete') deleteLinkConfirm(name);
    });

    // Track link form dirty state
    const linkForm = document.getElementById('linkForm');
    linkForm.addEventListener('input', () => {
        setLinkFormDirty(true);
    });
    linkForm.addEventListener('change', () => {
        setLinkFormDirty(true);
    });

    // Initialize browser toolbar and load folders
    initBrowserToolbar();
    if (!authReq || token) {
        loadMediaFolders();
    }

    // Icon preview for script and link dialogs
    setupIconPreview('scriptIcon', 'scriptIconPreview');
    setupIconPreview('linkIcon', 'linkIconPreview');

    // Settings sections: restore collapse state and persist on toggle
    document.querySelectorAll('.settings-section').forEach(details => {
        const key = `settings_section_${details.querySelector('summary')?.getAttribute('data-i18n') || ''}`;
        const saved = localStorage.getItem(key);
        if (saved === 'closed') details.removeAttribute('open');
        else if (saved === 'open') details.setAttribute('open', '');
        details.addEventListener('toggle', () => {
            localStorage.setItem(key, details.open ? 'open' : 'closed');
        });
    });

    // Cleanup blob URLs on page unload
    window.addEventListener('beforeunload', () => {
        thumbnailCache.forEach(url => URL.revokeObjectURL(url));
        thumbnailCache.clear();
    });

    // Tab bar keyboard navigation (WAI-ARIA Tabs pattern)
    document.getElementById('tabBar').addEventListener('keydown', (e) => {
        const tabs = Array.from(document.querySelectorAll('.tab-btn'));
        const currentIdx = tabs.indexOf(document.activeElement);
        if (currentIdx === -1) return;

        let newIdx;
        if (e.key === 'ArrowRight') {
            newIdx = (currentIdx + 1) % tabs.length;
        } else if (e.key === 'ArrowLeft') {
            newIdx = (currentIdx - 1 + tabs.length) % tabs.length;
        } else if (e.key === 'Home') {
            newIdx = 0;
        } else if (e.key === 'End') {
            newIdx = tabs.length - 1;
        } else {
            return;
        }

        e.preventDefault();
        tabs[newIdx].focus();
        switchTab(tabs[newIdx].dataset.tab);
    });

    // Global keyboard shortcuts
    document.addEventListener('keydown', (e) => {
        // Skip when typing in inputs, textareas, selects, or when a dialog is open
        const tag = e.target.tagName;
        if (tag === 'INPUT' || tag === 'TEXTAREA' || tag === 'SELECT') return;
        if (document.querySelector('dialog[open]')) return;

        switch (e.key) {
            case ' ':
                e.preventDefault();
                togglePlayPause();
                break;
            case 'ArrowLeft':
                e.preventDefault();
                if (currentDuration > 0) seek(Math.max(0, currentPosition - 5));
                break;
            case 'ArrowRight':
                e.preventDefault();
                if (currentDuration > 0) seek(Math.min(currentDuration, currentPosition + 5));
                break;
            case 'ArrowUp':
                e.preventDefault();
                setVolume(Math.min(100, parseInt(dom.volumeSlider.value) + 5));
                break;
            case 'ArrowDown':
                e.preventDefault();
                setVolume(Math.max(0, parseInt(dom.volumeSlider.value) - 5));
                break;
            case 'm':
            case 'M':
                toggleMute();
                break;
        }
    });
});
