<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_loc, events_slct, events_sess } from '$lib/stores/ae_events_stores';
    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;

    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);

    // 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 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 = $events_loc.launcher.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);

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

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

        // 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 = $events_loc.launcher.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);
    });

    /**
     * API Refresh: Event
     */
    async function refresh_event_data() {
        if ($events_loc.launcher.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 ($events_loc.launcher.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 ($events_loc.launcher.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 ($events_loc.launcher.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,
                try_cache: true,
                log_lvl: 0
            });
        } catch (err) {}
    }

    async function run_sync_cycle() {
        if ($events_loc.launcher.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();

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

            for (const file_obj of 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 ($events_loc.launcher.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 ||
            dev?.event_device_id_random ||
            dev?.id_random;

        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.';
            }

            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
            });

            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 ($events_loc.launcher.sync_paused) return;
        const location_id = $events_slct.event_location_id;
        if (!location_id) return;

        try {
            await events_func.load_ae_obj_id__event_location({
                api_cfg: $ae_api,
                event_location_id: location_id,
                try_cache: true,
                log_lvl: 0
            });
        } catch (err) {
            console.error('Sync: Location refresh 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 $events_loc.launcher.app_mode === 'native' || $ae_loc.is_native}
<div
    class="fixed bottom-20 left-4 z-[9999] flex flex-col items-start gap-2 pointer-events-none"
>
    {#if show_monitor}
        <div
            class="bg-surface-50/95 dark:bg-surface-900/95 text-surface-800 dark:text-surface-100 p-3 rounded-lg border border-surface-200 dark:border-primary-700 shadow-2xl text-[10px] font-mono min-w-52 pointer-events-auto backdrop-blur-sm"
        >
            <div
                class="flex justify-between border-b border-surface-200 dark:border-primary-700 pb-1 mb-2"
            >
                <span class="font-bold text-primary-600 dark:text-primary-400"
                    >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="grid grid-cols-2 gap-x-4 gap-y-1 mb-2">
                <span class="opacity-60 text-primary-700 dark:text-primary-300">Room Status:</span>
                <span class="text-right"
                    >{sync_stats.cached} / {sync_stats.total} Files</span
                >

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

                <span class="opacity-60 text-primary-700 dark:text-primary-300">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-t border-surface-200 dark:border-surface-700 pt-2 flex flex-col gap-1">
                <div class="flex justify-between items-center">
                    <span class:text-primary-500={timer__event}
                        >Event Loop:</span
                    >
                    <span>{loop_info.event / 1000}s</span>
                </div>
                <div class="flex justify-between items-center">
                    <span class:text-primary-500={timer__device}
                        >Device Loop:</span
                    >
                    <span>{loop_info.device / 1000}s</span>
                </div>
                <div class="flex justify-between items-center">
                    <span class:text-primary-500={timer__location}
                        >Location Loop:</span
                    >
                    <span>{loop_info.location / 1000}s</span>
                </div>
                <div class="flex justify-between items-center">
                    <span class:text-primary-500={timer__session}
                        >Session Loop:</span
                    >
                    <span>{loop_info.session / 1000}s</span>
                </div>
                <div class="flex justify-between items-center">
                    <span class:text-primary-500={timer__presentation}
                        >Pres Loop:</span
                    >
                    <span>{loop_info.presentation / 1000}s</span>
                </div>
                <div class="flex justify-between items-center">
                    <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="
            flex items-center gap-1.5
            px-2 py-1.5 rounded-lg
            text-[10px] font-mono
            pointer-events-auto
            transition-all
            {currently_syncing
                ? 'bg-primary-500/15 dark:bg-primary-500/25 border border-primary-500 text-primary-700 dark:text-primary-300 shadow-md animate-pulse'
                : 'bg-surface-100/90 dark:bg-surface-800/80 border border-surface-300 dark:border-surface-600 text-surface-600 dark:text-surface-300 opacity-60 hover:opacity-100 shadow-sm'}
        "
        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-[9px] animate-spin text-primary-500" />{:else}<Cpu size="1em" class="text-[9px] opacity-50" />{/if}
        {#if currently_syncing}
            <span class="truncate max-w-40 text-left">{currently_syncing}</span>
        {:else}
            <span>Native Sync</span>
            <span class="opacity-50 ml-0.5">{sync_stats.cached}/{sync_stats.total}</span>
        {/if}
        {#if show_monitor}<ChevronDown size="1em" class="text-[7px] opacity-40 ml-0.5" />{:else}<ChevronUp size="1em" class="text-[7px] opacity-40 ml-0.5" />{/if}
    </button>
</div>
{/if}
