<script lang="ts">
import { ChevronDown, ChevronUp, Cpu, RefreshCw } from '@lucide/svelte';
/**
 * launcher_background_sync.svelte
 * Exhaustive Background Pre-Caching Engine with Loop Monitoring.
 */
import { onMount, onDestroy } from 'svelte';
import { ae_loc, ae_api } from '$lib/stores/ae_stores';
import {
    events_slct,
    events_sess
} from '$lib/stores/ae_events_stores';
import { launcher_loc } from '$lib/stores/ae_events_stores__launcher.svelte';
import { events_func } from '$lib/ae_events/ae_events_functions';
import { ae_util } from '$lib/ae_utils/ae_utils';
import { db_events } from '$lib/ae_events/db_events';
import * as native from '$lib/electron/electron_relay';
const { cleanup_tmp_files } = native;

/**
 * Parses other_json (string or object) into a plain Record.
 * Duplicated from launcher_cfg_wallpaper to keep this component self-contained.
 */
function parse_other_json(raw: unknown): Record<string, unknown> {
    if (!raw) return {};
    if (typeof raw === 'object') return { ...(raw as Record<string, unknown>) };
    if (typeof raw !== 'string') return {};
    try {
        const parsed = JSON.parse(raw);
        return parsed && typeof parsed === 'object' ? { ...parsed } : {};
    } catch {
        return {};
    }
}

/**
 * Auto-applies the configured wallpaper if the URL in device config differs from
 * what was last applied on this machine. Called at startup and after each heartbeat.
 *
 * WHY: staff updates the wallpaper URL in the device config once (from any machine);
 * all native Launcher instances pick it up within one heartbeat cycle (~60s) without
 * anyone needing to visit each podium Mac physically.
 */
async function apply_wallpaper_if_changed(device_other_json: unknown) {
    if (!$ae_loc.is_native) return;

    const other = parse_other_json(device_other_json);
    const launcher_cfg = (other.launcher as Record<string, unknown> | undefined) ?? {};
    const wallpaper = (launcher_cfg.wallpaper as { url?: string; url_external?: string } | undefined) ?? {};

    const configured_url = wallpaper.url ?? '';
    const configured_url_external = wallpaper.url_external ?? '';
    const applied_url = launcher_loc.current.wallpaper_applied_url ?? '';
    const applied_url_external = launcher_loc.current.wallpaper_applied_url_external ?? '';

    const url_changed = configured_url && configured_url !== applied_url;
    const ext_changed = configured_url_external !== applied_url_external;

    if (!url_changed && !ext_changed) return;

    if (log_lvl) console.log('Sync: Wallpaper config changed — applying.', { configured_url, applied_url });

    // If only the external URL is configured, leave the built-in display unchanged.
    const display = !configured_url && configured_url_external ? 'external' : 'all';

    const result = await native.set_wallpaper({
        url: configured_url || undefined,
        url_external: configured_url_external || undefined,
        display,
        api_key: $ae_api.api_secret_key,
        account_id: String($ae_api.account_id ?? '')
    });

    if (result?.success && (result as any).linux_test_mode) {
        // Linux dev mode: gsettings was skipped. Log and leave applied URL unset so
        // the cfg component popup can show the details when triggered manually.
        console.info('Sync: Wallpaper linux_test_mode — would have applied:', (result as any).would_run);
    } else if (result?.success) {
        launcher_loc.current.wallpaper_applied_url = configured_url || null;
        launcher_loc.current.wallpaper_applied_url_external = configured_url_external || null;
        if (log_lvl) console.log('Sync: Wallpaper applied.');
    } else {
        console.warn('Sync: Wallpaper apply failed.', (result as any)?.error);
    }
}

let { log_lvl = 1 } = $props();

let currently_syncing: string | null = $state(null);
let sync_results: Record<string, 'success' | 'error' | 'pending'> = $state({});
let sync_stats = $state({ total: 0, cached: 0, missing: 0 });
let last_heartbeat: string | null = $state(null);

function is_url_file(file_obj: any): boolean {
    const filename = (file_obj?.filename ?? '') as string;
    const extension = (file_obj?.extension ?? '').toLowerCase();
    return (
        filename.startsWith('https://') ||
        filename.startsWith('http://') ||
        extension === 'url'
    );
}

// Loop Timings (Visible in UI)
// WHY: Session was originally 15s which combined with the SWR background-refresh
// pattern caused a continuous API call every 15s even on cache hits. 60s is still
// responsive for live conference use but dramatically reduces server load.
// Defaults here must match the onMount fallbacks below — keep in sync.
let loop_info = $state({
    event: 90000,
    device: 60000,
    location: 60000,
    session: 60000,
    presentation: 120000,
    presenter: 120000,
    file_sync: 30000
});

// Timer Handles
let timer__event: any = $state(null);
let timer__device: any = $state(null);
let timer__location: any = $state(null);
let timer__session: any = $state(null);
let timer__presentation: any = $state(null);
let timer__presenter: any = $state(null);
let timer__file_sync: any = $state(null);
let timer__file_list: any = $state(null);

let is_syncing = false;
let show_monitor = $state(false);

onMount(async () => {
    // ... (hydration logic preserved)
    if ($ae_loc.is_native) {
        try {
            const info = await native.get_device_info();
            if (info) {
                $ae_loc.home_directory = info.home_directory;
                $ae_loc.tmp_directory = info.tmp_directory;

                // Also sync into native_device for redundancy
                if (!$ae_loc.native_device) $ae_loc.native_device = {};
                $ae_loc.native_device.home_directory = info.home_directory;
                $ae_loc.native_device.tmp_directory = info.tmp_directory;

                if (log_lvl)
                    console.log('Sync: Native OS metadata hydrated.', {
                        home: info.home_directory
                    });
            }
        } catch (err) {
            console.error('Sync: Metadata hydration FAILED:', err);
        }
    }

    const dev = $ae_loc.native_device || {};
    const cfg = launcher_loc.current.sync_intervals || {};

    // Load timings from persistent config, with fallbacks to device config or defaults.
    // Fallback values here must match the loop_info $state defaults above — keep in sync.
    loop_info.event = cfg.event || dev.check_event_loop_period || 90000;
    loop_info.device =
        cfg.device || dev.check_event_device_loop_period || 60000;
    loop_info.location =
        cfg.location || dev.check_event_location_loop_period || 60000;
    loop_info.session =
        cfg.session || dev.check_event_session_loop_period || 60000;
    loop_info.presentation =
        cfg.presentation || dev.check_event_presentation_loop_period || 120000;
    loop_info.presenter =
        cfg.presenter || dev.check_event_presenter_loop_period || 120000;
    loop_info.file_sync =
        cfg.file_sync || dev.check_file_sync_loop_period || 30000;

    // 1. Structural/Metadata Loops
    timer__event = setInterval(() => refresh_event_data(), loop_info.event);
    timer__device = setInterval(() => run_device_heartbeat(), loop_info.device);
    timer__location = setInterval(
        () => refresh_location_config(),
        loop_info.location
    );

    // 2. Room Content Refresh Loops (API -> Dexie)
    timer__session = setInterval(
        () => refresh_session_data(),
        loop_info.session
    );
    timer__presentation = setInterval(
        () => refresh_presentation_data(),
        loop_info.presentation
    );
    timer__presenter = setInterval(
        () => refresh_presenter_data(),
        loop_info.presenter
    );

    // 3. Native File Sync Loop (Dexie -> Disk)
    timer__file_sync = setInterval(() => run_sync_cycle(), loop_info.file_sync);

    // 4. File List Refresh Loop (API -> Dexie, prunes deleted/hidden session files)
    // WHY: The presenter/session refresh loops don't fetch file lists, so deleted or
    // hidden files can stay in Dexie on the Launcher machine indefinitely (until IDB is
    // manually cleared). This loop calls load_ae_obj_li__event_file for the selected
    // session, which triggers the stale-record pruning in _refresh_file_li_background.
    // 60s matches the session/location loops; presenter files are pruned via inc_file_li
    // in refresh_presenter_data which runs every 120s.
    timer__file_list = setInterval(() => refresh_current_session_files(), 60000);

    // Immediate first run for metadata
    refresh_event_data();
    run_device_heartbeat();
    refresh_location_config();

    // On startup: apply wallpaper if the device config has one that differs from
    // what was last applied (covers reboots and first-time device setup).
    if ($ae_loc.is_native) {
        apply_wallpaper_if_changed($ae_loc.native_device?.other_json);
    }

    // Stagger initial data fetches
    setTimeout(() => refresh_session_data(), 1000);
    setTimeout(() => refresh_presentation_data(), 3000);
    setTimeout(() => refresh_presenter_data(), 5000);
    setTimeout(() => refresh_current_session_files(), 7000);

    // Clean up stale .tmp files (interrupted downloads) on startup.
    // Age threshold is user-configurable (cfg → General → Cache Maintenance), default 24h.
    const cache_root = $ae_loc.local_file_cache_path;
    if ($ae_loc.is_native && cache_root) {
        const max_age_hours =
            launcher_loc.current.cleanup_tmp_max_age_hours ?? 24;
        cleanup_tmp_files({
            cache_root,
            max_age_minutes: max_age_hours * 60
        }).then((result) => {
            if (log_lvl) console.log('Sync: .tmp cleanup complete.', result);
        });
    }
});

onDestroy(() => {
    if (timer__event) clearInterval(timer__event);
    if (timer__device) clearInterval(timer__device);
    if (timer__location) clearInterval(timer__location);
    if (timer__session) clearInterval(timer__session);
    if (timer__presentation) clearInterval(timer__presentation);
    if (timer__presenter) clearInterval(timer__presenter);
    if (timer__file_sync) clearInterval(timer__file_sync);
    if (timer__file_list) clearInterval(timer__file_list);
});

// WHY: refresh_location_config() runs on a 60s timer. Without this effect,
// switching locations leaves the new location's file list empty until the
// next timer tick. Watching event_location_id here fires an immediate fetch
// whenever the operator selects a different room.
$effect(() => {
    const location_id = $events_slct.event_location_id;
    if (location_id) {
        refresh_location_config();
    }
});

// WHY: fires refresh_current_session_files() immediately when the operator switches
// sessions so deleted/hidden files are pruned without waiting for the 60s timer.
$effect(() => {
    const session_id = $events_slct.event_session_id;
    if (session_id) {
        refresh_current_session_files();
    }
});

// Force Sync Trigger: fetches ALL child metadata for ALL sessions in the room.
// WHY: by default, the launcher only warms presentation/presenter caches for the
// *selected* session to save bandwidth. Onsite operators use this trigger to
// force-fetch everything ahead of time so the file sync loop can pre-cache
// every slide deck for the entire day.
$effect(() => {
    if ($events_sess.launcher.trigger__force_location_sync) {
        $events_sess.launcher.trigger__force_location_sync = false;
        force_location_sync();
    }
});

/**
 * API Refresh: Event
 */
async function refresh_event_data() {
    if (launcher_loc.current.sync_paused) return;
    if (!$events_slct.event_id) return;
    try {
        await events_func.load_ae_obj_id__event({
            api_cfg: $ae_api,
            event_id: $events_slct.event_id,
            inc_location_li: true,
            enabled: 'enabled',
            hidden: 'all',
            try_cache: true,
            log_lvl: 0
        });
    } catch (err) {}
}

/**
 * API Refresh: Sessions in Room
 */
async function refresh_session_data() {
    if (launcher_loc.current.sync_paused) return;
    const location_id = $events_slct.event_location_id;
    if (!location_id) return;
    try {
        if (log_lvl > 1)
            console.log(
                `Sync: Refreshing sessions for location: ${location_id}`
            );
        await events_func.load_ae_obj_li__event_session({
            api_cfg: $ae_api,
            for_obj_type: 'event_location',
            for_obj_id: location_id,
            view: 'alt',
            hidden: 'all', // Launcher is operator-only; fetch all sessions so the "All Sessions" toggle works
            try_cache: true,
            log_lvl: 0
        });
    } catch (err) {}
}

/**
 * API Refresh: Presentations for Selected Session
 */
async function refresh_presentation_data() {
    if (launcher_loc.current.sync_paused) return;
    const session_id = $events_slct.event_session_id;
    if (!session_id) return;
    try {
        if (log_lvl > 1)
            console.log(
                `Sync: Refreshing presentations for session: ${session_id}`
            );
        await events_func.load_ae_obj_li__event_presentation({
            api_cfg: $ae_api,
            for_obj_type: 'event_session',
            for_obj_id: session_id,
            try_cache: true,
            log_lvl: 0
        });
    } catch (err) {}
}

/**
 * API Refresh: Presenters for Selected Session
 */
async function refresh_presenter_data() {
    if (launcher_loc.current.sync_paused) return;
    const session_id = $events_slct.event_session_id;
    if (!session_id) return;
    try {
        if (log_lvl > 1)
            console.log(
                `Sync: Refreshing presenters for session: ${session_id}`
            );
        await events_func.load_ae_obj_li__event_presenter({
            api_cfg: $ae_api,
            for_obj_type: 'event_session',
            for_obj_id: session_id,
            inc_file_li: true, // WHY: triggers pruning of deleted/hidden presenter files in Dexie
            try_cache: true,
            log_lvl: 0
        });
    } catch (err) {}
}

async function run_sync_cycle() {
    if (launcher_loc.current.sync_paused) return;
    const location_id = $events_slct.event_location_id;
    const cache_root = $ae_loc.local_file_cache_path;
    const prefix_len = $ae_loc.native_device?.hash_prefix_length || 2;

    if (!location_id || !cache_root || is_syncing || !$ae_loc.is_native) return;

    is_syncing = true;
    try {
        const sessions = await db_events.session
            .where('event_location_id')
            .equals(location_id)
            .toArray();
        const session_ids = sessions.map((s) => s.event_session_id);
        if (session_ids.length === 0) return;

        const presentations = await db_events.presentation
            .where('event_session_id')
            .anyOf(session_ids)
            .toArray();
        const presentation_ids = presentations.map(
            (p) => p.event_presentation_id
        );

        const presenters = await db_events.presenter
            .where('event_session_id')
            .anyOf(session_ids)
            .toArray();
        const presenter_ids = presenters.map((p) => p.event_presenter_id);

        const all_for_ids = [
            ...session_ids,
            ...presentation_ids,
            ...presenter_ids,
            $events_slct.event_id
        ];
        const files = await db_events.file
            .where('for_id')
            .anyOf(all_for_ids)
            .toArray();

        const cacheable_files = files.filter(
            (file_obj) => !is_url_file(file_obj)
        );

        // Optimized Chronological Sync Order
        // 1. Prioritize Event/Location level files (Global assets).
        // 2. Prioritize by Session start time (Urgent schedule priority).
        // 3. Prioritize by Presentation start time (Within a session).
        // 4. Prioritize by created_on (Oldest first — "Not penalizing" on-time uploads).
        cacheable_files.sort((a, b) => {
            const time_a_sess = a.event_session_start_datetime
                ? new Date(a.event_session_start_datetime).getTime()
                : 0;
            const time_b_sess = b.event_session_start_datetime
                ? new Date(b.event_session_start_datetime).getTime()
                : 0;
            if (time_a_sess !== time_b_sess) return time_a_sess - time_b_sess;

            const time_a_pres = a.event_presentation_start_datetime
                ? new Date(a.event_presentation_start_datetime).getTime()
                : 0;
            const time_b_pres = b.event_presentation_start_datetime
                ? new Date(b.event_presentation_start_datetime).getTime()
                : 0;
            if (time_a_pres !== time_b_pres) return time_a_pres - time_b_pres;

            const created_a = a.created_on
                ? new Date(a.created_on).getTime()
                : 0;
            const created_b = b.created_on
                ? new Date(b.created_on).getTime()
                : 0;
            return created_a - created_b;
        });

        sync_stats.total = cacheable_files.length;
        let cached_count = 0;
        let missing_count = 0;

        for (const file_obj of cacheable_files) {
            // Re-check pause flag each iteration — a sync cycle can run for many
            // seconds if there are missing files, so we must honour a pause request
            // mid-loop rather than waiting for the entire batch to finish.
            if (launcher_loc.current.sync_paused) break;

            if (!file_obj.hash_sha256) continue;
            if (sync_results[file_obj.event_file_id] === 'success') {
                cached_count++;
                continue;
            }

            const exists = await native.check_hash_file_cache({
                cache_root,
                hash: file_obj.hash_sha256,
                hash_prefix_length: prefix_len,
                verify_hash: true // Hardened check: Perform full SHA-256 verify if file exists
            });

            if (exists) {
                sync_results[file_obj.event_file_id] = 'success';
                cached_count++;
                continue;
            }

            missing_count++;
            currently_syncing = file_obj.filename;
            $events_sess.launcher.sync_stats.currently_syncing =
                currently_syncing;

            // Use the PROVEN endpoint path from api.ts that is known to work in Default Mode.
            const url = `${$ae_api.base_url}/v3/action/hosted_file/${file_obj.hosted_file_id}/download?return_file=true&filename=${encodeURIComponent(file_obj.filename)}&key=${$ae_api.account_id}`;
            const result = await native.download_to_cache({
                url,
                cache_root,
                hash: file_obj.hash_sha256,
                api_key: $ae_api.api_secret_key,
                account_id: $ae_api.account_id,
                hash_prefix_length: prefix_len
            });

            if (result.success)
                sync_results[file_obj.event_file_id] = 'success';
        }
        sync_stats.cached = cached_count;
        sync_stats.missing = missing_count;

        // Sync to shared store
        $events_sess.launcher.sync_stats = {
            total: sync_stats.total,
            cached: sync_stats.cached,
            missing: sync_stats.missing,
            currently_syncing: null
        };
    } catch (err) {
        console.error('Sync Engine Error:', err);
    } finally {
        currently_syncing = null;
        $events_sess.launcher.sync_stats.currently_syncing = null;
        is_syncing = false;
    }
}

/**
 * Device Heartbeat & Telemetry
 * Gathers OS info and updates the device record in the cloud.
 */
async function run_device_heartbeat() {
    const dev = $ae_loc.native_device;
    // String-Only ID Vision: Prioritize semantic string IDs, then generic, then legacy random strings
    const device_id =
        dev?.event_device_id ||
        dev?.id;

    if (!device_id) {
        // Only log warning if we are actually supposed to be in native mode
        if (log_lvl && $ae_loc.is_native)
            console.warn(
                'Sync: Heartbeat skipped, no device_id found in $ae_loc.native_device.'
            );
        $events_sess.launcher.heartbeat_info.status = 'error';
        return;
    }

    if (log_lvl > 1)
        console.log(`Sync: Running heartbeat for device: ${device_id}`);

    try {
        let info = null;
        if ($ae_loc.is_native) {
            info = await native.get_device_info();
        }

        const update_payload: any = {
            // Use standard SQL format YYYY-MM-DD HH:mm:ss for MySQL compatibility
            heartbeat: ae_util.iso_datetime_formatter(
                new Date(),
                'datetime_iso'
            )
        };

        if (info) {
            update_payload.info_hostname = info.hostname;
            // Safely handle IP list (bridge may return ip_addresses or networkInterfaces)
            const ips = info.ip_addresses || [];
            update_payload.info_ip_list = Array.isArray(ips)
                ? ips.join(', ')
                : 'Unknown';

            update_payload.meta_json = {
                platform: info.platform,
                release: info.release,
                arch: info.arch,
                cpus: info.cpus,
                total_mem: Math.round(info.total_mem / (1024 * 1024)) + 'MB',
                free_mem: Math.round(info.free_mem / (1024 * 1024)) + 'MB'
            };
        } else {
            update_payload.info_hostname = 'Web Browser';
            update_payload.notes = 'Heartbeat from non-native web session.';
        }

        const heartbeat_result = await events_func.update_ae_obj__event_device({
            api_cfg: $ae_api,
            event_device_id: String(device_id),
            data_kv: update_payload,
            log_lvl: 0
        });

        // V3 PATCH returns the full updated record. Use it to detect wallpaper
        // config changes made remotely (e.g. staff updated other_json from another device).
        if (heartbeat_result) {
            apply_wallpaper_if_changed((heartbeat_result as any).other_json);
        }

        last_heartbeat = new Date().toLocaleTimeString();
        $events_sess.launcher.heartbeat_info = {
            last_timestamp: last_heartbeat,
            status: 'success'
        };
        if (log_lvl > 1) console.log('Sync: Device heartbeat SUCCESS.');
    } catch (err) {
        console.error('Sync: Device heartbeat FAILED:', err);
        $events_sess.launcher.heartbeat_info.status = 'error';
    }
}

/**
 * Location Config Refresh
 * Ensures we have latest room settings.
 */
async function refresh_location_config() {
    if (launcher_loc.current.sync_paused) return;
    const location_id = $events_slct.event_location_id;
    if (!location_id) return;

    try {
        // WHY: inc_file_li must be true so location-level files reach Dexie.
        // Without it, lq__location_event_file_obj_li in the layout finds nothing and
        // the location file list in the Launcher menu never renders.
        await events_func.load_ae_obj_id__event_location({
            api_cfg: $ae_api,
            event_location_id: location_id,
            inc_file_li: true,
            try_cache: true,
            log_lvl: 0
        });
    } catch (err) {
        console.error('Sync: Location refresh FAILED:', err);
    }
}

/**
 * Session File List Refresh
 * Fetches the file list for the SELECTED session and prunes stale/deleted/hidden records.
 * WHY: The regular session/presenter refresh loops don't fetch file lists, so deleted or
 * hidden files persist in the Launcher's Dexie indefinitely. This runs on a 60s timer
 * and also fires immediately when the selected session changes. Presenter-level files are
 * handled separately via inc_file_li=true in refresh_presenter_data (120s timer).
 */
async function refresh_current_session_files() {
    if (launcher_loc.current.sync_paused) return;
    const session_id = $events_slct.event_session_id;
    if (!session_id) return;
    try {
        await events_func.load_ae_obj_li__event_file({
            api_cfg: $ae_api,
            for_obj_type: 'event_session',
            for_obj_id: session_id,
            try_cache: true,
            log_lvl: 0
        });
    } catch (err) {}
}

/**
 * Force Location Sync
 * Fetches ALL sessions and their children (presentations, presenters, files)
 * for the current location into Dexie.
 */
async function force_location_sync() {
    const location_id = $events_slct.event_location_id;
    if (!location_id) return;

    if (log_lvl)
        console.log(`Sync: FORCING full sync for location: ${location_id}`);

    try {
        // 1. Fetch all sessions with all children (recursive loads handled by load_ae_obj_li)
        await events_func.load_ae_obj_li__event_session({
            api_cfg: $ae_api,
            for_obj_type: 'event_location',
            for_obj_id: location_id,
            inc_presentation_li: true,
            inc_presenter_li: true,
            inc_file_li: true, // Also get session-level files
            view: 'alt',
            hidden: 'all',
            try_cache: true,
            log_lvl: 1
        });

        if (log_lvl)
            console.log(
                'Sync: Force metadata fetch complete. File sync cycle will follow.'
            );

        // 2. Trigger the file sync cycle immediately to start downloads
        run_sync_cycle();
    } catch (err) {
        console.error('Sync: Force location sync FAILED:', err);
    }
}
</script>

<!-- Monitor Overlay: only shown in Native/App mode.
     Positioned bottom-left at bottom-20 to clear the debug π button (bottom-8 left-2)
     and the sys bar (bottom-12 right-2). Panel grows upward from the status chip. -->
{#if launcher_loc.current.app_mode === 'native' || $ae_loc.is_native}
    <div
        class="pointer-events-none fixed bottom-15 left-2 z-10 flex flex-col items-start gap-2">
        {#if show_monitor}
            <div
                class="bg-surface-50/95 dark:bg-surface-900/95 text-surface-800 dark:text-surface-100 border-surface-200 dark:border-primary-700 pointer-events-auto min-w-52 rounded-lg border p-3 font-mono text-[10px] shadow-2xl backdrop-blur-sm">
                <div
                    class="border-surface-200 dark:border-primary-700 mb-2 flex justify-between border-b pb-1">
                    <span
                        class="text-primary-600 dark:text-primary-400 font-bold"
                        >NATIVE SYNC MONITOR</span>
                    <button
                        type="button"
                        onclick={() => (show_monitor = false)}
                        class="text-error-500 hover:text-error-400 ml-2"
                        >×</button>
                </div>

                <div class="mb-2 grid grid-cols-2 gap-x-4 gap-y-1">
                    <span
                        class="text-primary-700 dark:text-primary-300 opacity-60"
                        >Room Status:</span>
                    <span class="text-right"
                        >{sync_stats.cached} / {sync_stats.total} Files</span>

                    <span
                        class="text-primary-700 dark:text-primary-300 opacity-60"
                        >Prefix Len:</span>
                    <span class="text-right"
                        >{$ae_loc.native_device?.hash_prefix_length || 2} chars</span>

                    <span
                        class="text-primary-700 dark:text-primary-300 opacity-60"
                        >Heartbeat:</span>
                    <span
                        class="text-right {last_heartbeat
                            ? 'text-success-600 dark:text-success-400'
                            : 'text-error-600 dark:text-error-400'}">
                        {last_heartbeat || 'Pending...'}
                    </span>
                </div>

                <div
                    class="border-surface-200 dark:border-surface-700 flex flex-col gap-1 border-t pt-2">
                    <div class="flex items-center justify-between">
                        <span class:text-primary-500={timer__event}
                            >Event Loop:</span>
                        <span>{loop_info.event / 1000}s</span>
                    </div>
                    <div class="flex items-center justify-between">
                        <span class:text-primary-500={timer__device}
                            >Device Loop:</span>
                        <span>{loop_info.device / 1000}s</span>
                    </div>
                    <div class="flex items-center justify-between">
                        <span class:text-primary-500={timer__location}
                            >Location Loop:</span>
                        <span>{loop_info.location / 1000}s</span>
                    </div>
                    <div class="flex items-center justify-between">
                        <span class:text-primary-500={timer__session}
                            >Session Loop:</span>
                        <span>{loop_info.session / 1000}s</span>
                    </div>
                    <div class="flex items-center justify-between">
                        <span class:text-primary-500={timer__presentation}
                            >Pres Loop:</span>
                        <span>{loop_info.presentation / 1000}s</span>
                    </div>
                    <div class="flex items-center justify-between">
                        <span class:text-primary-500={timer__presenter}
                            >Speaker Loop:</span>
                        <span>{loop_info.presenter / 1000}s</span>
                    </div>
                </div>
            </div>
        {/if}

        <!-- Compact always-visible status chip.
         Syncing: animated primary border + pulse. Idle: subtle muted chip.
         Replaces the old invisible secret button so staff can always see the monitor. -->
        <button
            type="button"
            onclick={() => (show_monitor = !show_monitor)}
            class="
            pointer-events-auto flex items-center
            gap-1.5 rounded-lg px-2
            py-1.5 font-mono
            text-[10px]
            transition-all
            {currently_syncing
                ? 'bg-primary-500/15 dark:bg-primary-500/25 border-primary-500 text-primary-700 dark:text-primary-300 animate-pulse border shadow-md'
                : 'bg-surface-100/90 dark:bg-surface-800/80 border-surface-300 dark:border-surface-600 text-surface-600 dark:text-surface-300 border opacity-60 shadow-sm hover:opacity-100'}
        "
            title="Native Sync Monitor · {sync_stats.cached}/{sync_stats.total} files · click to {show_monitor
                ? 'close'
                : 'open'}">
            {#if currently_syncing}<RefreshCw
                    size="1em"
                    class="text-primary-500 animate-spin text-[9px]" />{:else}<Cpu
                    size="1em"
                    class="text-[9px] opacity-50" />{/if}
            {#if currently_syncing}
                <span class="max-w-40 truncate text-left"
                    >{currently_syncing}</span>
            {:else}
                <span>Native Sync</span>
                <span class="ml-0.5 opacity-50"
                    >{sync_stats.cached}/{sync_stats.total}</span>
            {/if}
            {#if show_monitor}<ChevronDown
                    size="1em"
                    class="ml-0.5 text-[7px] opacity-40" />{:else}<ChevronUp
                    size="1em"
                    class="ml-0.5 text-[7px] opacity-40" />{/if}
        </button>
    </div>
{/if}
