<script lang="ts">
    let log_lvl: number = $state(0);
    interface Props {
        /** @type {import('./$types').LayoutData} */
        data: any;
        children?: import('svelte').Snippet;
    }

    let { data, children }: Props = $props();

    // *** Import Svelte specific
    import { untrack } from 'svelte';
    // import { onMount, tick } from 'svelte';
    import { goto } from '$app/navigation';
    import { sineIn } from 'svelte/easing';

    // *** Import other supporting libraries
    import { liveQuery } from 'dexie';
    import { Drawer, Footer, Modal } from 'flowbite-svelte';
    import { listen, idle, onIdle, restartCountdown } from 'svelte-idle';

    import {
        // Brain,
        // House, Library,
        LoaderCircle,
        // RefreshCw,
        Satellite
    } from '@lucide/svelte';

    // *** Import Aether specific variables and functions
    import type { key_val } from '$lib/stores/ae_stores';
    import { ae_util } from '$lib/ae_utils/ae_utils';
    import { api } from '$lib/api/api';
    import { db_events } from '$lib/ae_events/db_events';
    import {
        ae_snip,
        ae_loc,
        ae_sess,
        ae_api,
        ae_trig,
        slct,
        slct_trigger,
        time
    } from '$lib/stores/ae_stores';
    import {
        events_loc,
        events_sess,
        events_slct,
        events_trigger,
        events_trig
    } from '$lib/stores/ae_events_stores';
    import { events_func } from '$lib/ae_events_functions';

    import Launcher_cfg from '../launcher_cfg.svelte';
    import Launcher_menu from '../launcher_menu.svelte';
    import Launcher_session_view from '../launcher_session_view.svelte';
    import Element_websocket_v3 from '$lib/elements/element_websocket_v3.svelte';

    // *** Set initial variables
    // NOTE: Derived from data.account_id (prop) instead of $slct.account_id (store)
    // to prevent circular dependency loops during hydration.
    let ae_acct = $derived(data[data.account_id]);

    import { online } from 'svelte/reactivity/window';

    $ae_sess.disable_sys_nav = true;
    $ae_sess.disable_sys_header = true;
    $ae_sess.disable_sys_footer = true;

    if (!$events_loc?.launcher) {
        $events_loc.launcher = {
            app_mode: 'default',
            controller: 'local',
            controller_group_code: 'launcher-00',
            ws_connect: false,
            hide_drawer__cfg: true,
            hide_drawer__debug: true
        };
    }

    // Generate a stable per-device client ID on first load and persist it.
    // events_loc is backed by svelte-persisted-store (localStorage) so this
    // survives page reloads. Without this, client_id falls back to Date.now()
    // inside element_websocket_v3 — a new ID on every reload, which breaks
    // direct-target WS messages and doesn't match V3 Vision ID expectations.
    if (!$events_loc.launcher.controller_client_id) {
        $events_loc.launcher.controller_client_id = crypto.randomUUID();
    }

    // Unified Selection Sync (Refactored 2026-02-11)
    // WHY: We track URL params directly to ensure the UI reacts instantly to navigation.
    // We use untrack for store writes to prevent circular dependency loops.
    $effect(() => {
        const url_session_id = data.url.searchParams.get('session_id');
        const path_location_id = data.params.event_location_id;
        const path_event_id = data.params.event_id;

        // WHY: URL params for display overrides — lets you share a clean link
        // (e.g. tablet PWA, digital poster kiosk) without the recipient needing
        // to configure settings manually on their device.
        // These are read on every URL change (reactive to SPA navigation), which
        // is better than the root layout's onMount which only fires once on load.
        // Values persist in localStorage so they survive page reloads within the
        // same device; the URL param always wins when present.
        //
        // Supported params:
        //   ?iframe=true/false         — hide/show global sys & debug menus
        //   ?launcher_menu=hide/show   — hide/show left session/location panel
        //   ?launcher_header=hide/show — hide/show "Æ Launcher v3" header bar
        //   ?launcher_footer=hide/show — hide/show the status footer
        //
        // Example clean poster-kiosk link:
        //   /events/{id}/launcher/{loc_id}?session_id={sess_id}
        //     &iframe=true&launcher_menu=hide&launcher_header=hide
        const param_iframe          = data.url.searchParams.get('iframe');
        const param_launcher_menu   = data.url.searchParams.get('launcher_menu');
        const param_launcher_header = data.url.searchParams.get('launcher_header');
        const param_launcher_footer = data.url.searchParams.get('launcher_footer');

        if (log_lvl > 1) {
            console.log(`[Launcher Sync] URL Change: event=${path_event_id}, loc=${path_location_id}, sess=${url_session_id}`);
        }

        untrack(() => {
            if ($events_slct.event_id !== path_event_id) {
                $events_slct.event_id = path_event_id;
            }
            if ($events_slct.event_location_id !== path_location_id) {
                $events_slct.event_location_id = path_location_id;
            }
            // CRITICAL: Ensure session_id is synced to store so LiveQueries react
            if ($events_slct.event_session_id !== url_session_id) {
                if (log_lvl) console.log(`[Launcher Sync] Updating store session_id: ${url_session_id}`);
                $events_slct.event_session_id = url_session_id;
            }

            // Apply display overrides from URL params (when present)
            if (param_iframe === 'true')  $ae_loc.iframe = true;
            else if (param_iframe === 'false') $ae_loc.iframe = false;

            if (param_launcher_menu === 'hide')  $events_loc.launcher.hide__launcher_menu = true;
            else if (param_launcher_menu === 'show') $events_loc.launcher.hide__launcher_menu = false;

            if (param_launcher_header === 'hide')  $events_loc.launcher.hide__launcher_header = true;
            else if (param_launcher_header === 'show') $events_loc.launcher.hide__launcher_header = false;

            if (param_launcher_footer === 'hide')  $events_loc.launcher.hide__launcher_footer = true;
            else if (param_launcher_footer === 'show') $events_loc.launcher.hide__launcher_footer = false;
        });
    });

    // String-Only ID Vision: Sync the device ID from the native environment
    const native_dev = $derived($ae_loc.native_device);
    $effect(() => {
        if (native_dev) {
            untrack(() => {
                $events_slct.event_device_id =
                    native_dev.event_device_id ||
                    native_dev.id ||
                    native_dev.event_device_id_random ||
                    native_dev.id_random;
            });
        }
    });

    $effect(() => {
        if (ae_acct) {
            untrack(() => {
                const new_location_obj_li = ae_acct.slct.event_location_obj_li ?? [''];
                // Compare by extracting IDs only — object identity (===) won't work for
                // plain JS objects from the store. Joining IDs is cheap and avoids a full
                // JSON.stringify of potentially large location objects on every navigation.
                const current_obj_ids = ($events_slct.event_location_obj_li ?? [])
                    .map((o: any) => o?.event_location_id ?? o)
                    .join(',');
                const new_obj_ids = new_location_obj_li
                    .map((o: any) => o?.event_location_id ?? o)
                    .join(',');
                if (current_obj_ids !== new_obj_ids) {
                    $events_slct.event_location_obj_li = new_location_obj_li;
                }

                const new_id_li__event_location = ae_acct.slct.id_li__event_location ?? [''];
                // ID list contains plain strings — join-compare is O(n) and avoids JSON.stringify.
                if (($events_slct.id_li__event_location ?? []).join(',') !== new_id_li__event_location.join(',')) {
                    $events_slct.id_li__event_location = new_id_li__event_location;
                }
            });
        }
    });

    // *** Functions and Logic

    // Event
    let lq__event_obj = liveQuery(async () => {
        const id = $events_slct?.event_id;
        if (!id) return null;
        if (log_lvl > 1) console.log(`lq__event_obj: event_id = ${id}`);
        return await db_events.event.get(id);
    });

    // Event Device
    let lq__event_device_obj = liveQuery(async () => {
        const id = $events_slct.event_device_id;
        if (!id) return null;
        return await db_events.device.get(id);
    });

    // Event File - For Event
    let lq__event_event_file_obj_li = liveQuery(async () => {
        const id = $events_slct.event_id;
        if (!id) return [];
        return await db_events.file
            .where('for_id')
            .equals(id)
            .sortBy('filename');
    });

    // Event File - For Location
    // $derived.by: must recreate the observable when event_location_id changes.
    // Plain liveQuery() only re-fires when Dexie detects a change in the initially
    // watched index range — if the id starts as null and changes to a real value,
    // Dexie never fires because a different range was added (not the null range).
    let lq__location_event_file_obj_li = $derived.by(() => {
        const id = $events_slct.event_location_id;
        return liveQuery(async () => {
            if (!id) return [];
            return await db_events.file
                .where('for_id')
                .equals(id)
                .sortBy('filename');
        });
    });

    // Event Location — same reason as above
    let lq__event_location_obj = $derived.by(() => {
        const id = $events_slct.event_location_id;
        return liveQuery(async () => {
            if (!id) return null;
            return await db_events.location.get(id);
        });
    });

    let lq__event_location_obj_li = liveQuery(async () => {
        const id = $events_slct.event_id;
        if (!id) return [];
        return await db_events.location
            .where('event_id')
            .equals(id)
            .sortBy('name');
    });

    // $derived.by: must recreate when event_location_id changes (see comment above).
    let lq__event_session_obj_li = $derived.by(() => {
        const id = $events_slct.event_location_id;
        return liveQuery(async () => {
            if (!id) return [];
            if (log_lvl > 1)
                console.log(`LQ - Event Session list location_id: ${id}`);
            // Note: .reverse() before .sortBy() is a no-op — sortBy always re-sorts.
            return await db_events.session
                .where('event_location_id')
                .equals(id)
                .sortBy('name');
        });
    });

    // Event Session (Main View Trigger - Needed for Global Header/Idle)
    // $derived.by: capture ID in outer closure so Svelte tracks it as a dependency.
    // liveQuery callback runs in Dexie's async context where Svelte tracking is off.
    let lq__event_session_obj = $derived.by(() => {
        const id = $events_slct.event_session_id;
        return liveQuery(() => db_events.session.get(id));
    });

    // Store sync effects — keep liveQuery closures pure (data-only) and sync to
    // $events_slct here in reactive effects instead. Comparing updated_on + id is
    // O(1) vs O(serialized-bytes) for JSON.stringify and avoids running inside a
    // Dexie async context where Svelte's reactivity tracking is undefined.
    $effect(() => {
        const result = $lq__event_obj;
        if (result) {
            untrack(() => {
                if (result.updated_on !== $events_slct.event_obj?.updated_on ||
                    result.id !== $events_slct.event_obj?.id) {
                    $events_slct.event_obj = { ...result };
                }
            });
        }
    });

    $effect(() => {
        const result = $lq__event_device_obj;
        if (result) {
            untrack(() => {
                if (result.updated_on !== $events_slct.event_device_obj?.updated_on ||
                    result.id !== $events_slct.event_device_obj?.id) {
                    $events_slct.event_device_obj = { ...result };
                }
            });
        }
    });

    $effect(() => {
        const results = $lq__event_session_obj_li;
        if (results) {
            untrack(() => {
                const current = $events_slct.event_session_obj_li ?? [];
                // Compare by joining IDs — O(n) string compare vs O(n*m) JSON.stringify.
                const new_ids = (results as any[]).map((r: any) => r.id ?? r.event_session_id).join(',');
                const cur_ids = current.map((r: any) => r.id ?? r.event_session_id).join(',');
                if (new_ids !== cur_ids) {
                    $events_slct.event_session_obj_li = [...(results as any[])];
                }
            });
        }
    });

    let trigger_handle_ws_conn = $state(false);
    let trigger_handle_ws_recv = $state(false);
    let trigger_handle_ws_sent = $state(false);

    /* *** BEGIN *** Handle WebSocket events */
    function handle_ws_conn(ws_conn_status: any) {
        if (log_lvl) console.log('*** handle_ws_conn() ***', ws_conn_status);
        if (ws_conn_status.status == 'connected') {
            $events_sess.launcher.ws = { status: 'connected' };
        } else {
            $events_sess.launcher.ws = { status: 'disconnected' };
        }
    }

    function handle_ws_recv(ws_recv_status: any) {
        if (log_lvl) console.log('*** handle_ws_recv() ***', ws_recv_status);
        // V3 schema uses msg_type instead of type
        if (ws_recv_status.msg_type == 'cmd' && ws_recv_status.cmd) {
            let cmd = ws_recv_status.cmd;
            if ($events_loc.launcher.controller != 'remote') return;

            if (cmd.startsWith('ae_load:')) {
                let cmd_parts = cmd.split(':');
                let obj_parts = cmd_parts[1].split('=');
                let obj_type = obj_parts[0];
                let obj_id = obj_parts[1];

                if (obj_type == 'event_session') {
                    $events_slct.event_session_id = obj_id;
                    let new_url = new URL(data.url);
                    new_url.pathname = `/events/${data.params.event_id}/launcher/${$events_slct.event_location_id}`;
                    new_url.searchParams.set(
                        'session_id',
                        $events_slct.event_session_id
                    );
                    goto(new_url.toString(), { replaceState: false });
                }
            } else if (cmd.startsWith('ae_download:')) {
                let cmd_parts = cmd.split(':');
                let obj_parts = cmd_parts[1].split('=');
                let obj_type = obj_parts[0];
                let obj_id = obj_parts[1];
                let obj_filename = cmd_parts[2];

                api.get_object({
                    api_cfg: $ae_api,
                    endpoint: `/v3/action/hosted_file/${obj_id}/download`,
                    params: {
                        filename: obj_filename,
                        key: $ae_api.account_id
                    },
                    filename: obj_filename,
                    return_blob: true,
                    auto_download: true,
                    log_lvl: 1
                });
            } else if (cmd.startsWith('ae_open:')) {
                let cmd_parts = cmd.split(':');
                let obj_parts = cmd_parts[1].split('=');
                let obj_id = obj_parts[1];

                if (obj_parts[0] == 'event_file') {
                    $events_sess.launcher.modal__open_event_file_id = null;
                    clearInterval(idle_timer_interval);
                    saver_looping = false;
                    restartCountdown();
                    $events_slct.event_file_id = obj_id;
                    $events_sess.launcher.modal__open_event_file_id = obj_id;
                    // Look up the file object from Dexie so the modal can render the image.
                    // The remote device has no event_file_obj in scope — only the ID was sent over WS.
                    // Also look up the parent presentation to use its name as the modal title
                    // (cleaner for the LCD display than a raw filename).
                    db_events.file.get(obj_id).then(async (file_obj: any) => {
                        if (file_obj) {
                            $events_sess.launcher.modal__event_file_obj = file_obj;
                            const presentation = file_obj.for_id
                                ? await db_events.presentation.get(file_obj.for_id)
                                : null;
                            $events_sess.launcher.modal__title =
                                presentation?.name ?? file_obj.filename ?? '';
                        }
                    });
                }
            } else if (cmd.startsWith('ae_close:')) {
                if (cmd.split(':')[1] == 'event_file_modal') {
                    $events_sess.launcher.modal__open_event_file_id = null;
                }
                clearInterval(idle_timer_interval);
                saver_looping = false;
                restartCountdown();
            } else if (cmd.startsWith('ae_zoom:')) {
                // WHY: Controller can push zoom state to the remote display so both
                // devices stay in sync (e.g. operator zooms to show detail to an attendee
                // while the wall screen also zooms in for the room to see).
                const zoom_target = cmd.split(':')[1];
                if (zoom_target === 'fit') modal_zoom_fit = true;
                else if (zoom_target === 'zoom') modal_zoom_fit = false;
            } else if (cmd.startsWith('ae_refresh:')) {
                if (cmd.split(':')[1] == 'now') location.reload();
            } else if (cmd.startsWith('ae_mode:')) {
                // WHY: Allows a controller to remotely push a display mode preset
                // (e.g. switch all poster kiosks into kiosk mode without touching each device).
                const mode_target = cmd.split(':')[1];
                if (mode_target === 'poster') {
                    $ae_loc.iframe = true;
                    $events_loc.launcher.hide__launcher_menu   = true;
                    $events_loc.launcher.hide__launcher_header = true;
                    $events_loc.launcher.hide__launcher_footer = true;
                } else if (mode_target === 'oral') {
                    $ae_loc.iframe = false;
                    $events_loc.launcher.hide__launcher_menu   = false;
                    $events_loc.launcher.hide__launcher_header = false;
                    $events_loc.launcher.hide__launcher_footer = false;
                }
            }
        }
    }

    function handle_ws_sent(ws_sent_status: any) {
        $events_sess.launcher.controller_cmd = null;
        $events_sess.launcher.controller_trigger_send = null;
    }

    $effect(() => {
        if (trigger_handle_ws_conn) {
            handle_ws_conn(trigger_handle_ws_conn);
            trigger_handle_ws_conn = false;
        }
    });

    $effect(() => {
        if (trigger_handle_ws_recv) {
            handle_ws_recv(trigger_handle_ws_recv);
            trigger_handle_ws_recv = false;
        }
    });

    $effect(() => {
        if (trigger_handle_ws_sent) {
            handle_ws_sent(trigger_handle_ws_sent);
            trigger_handle_ws_sent = false;
        }
    });

    if (!$events_loc.launcher.idle_timer)
        $events_loc.launcher.idle_timer = 5 * 60 * 1000;
    if (!$events_loc.launcher.idle_cycle)
        $events_loc.launcher.idle_cycle = 5 * 1000;
    if (!$events_loc.launcher.idle_loop_period)
        $events_loc.launcher.idle_loop_period = 3 * 60 * 1000;

    listen({
        timer: $events_loc.launcher.idle_timer,
        cycle: $events_loc.launcher.idle_cycle
    });

    let idle_timer_interval: any = $state();
    let saver_looping: boolean = $state(false);
    // Tracks fit vs. zoom mode for the poster modal.
    // Reset to fit whenever a new poster opens so every poster starts clean.
    let modal_zoom_fit: boolean = $state(true);

    function handle_idle_client() {
        if (
            $lq__event_session_obj &&
            $lq__event_session_obj?.type_code == 'poster'
        ) {
            if (saver_looping) return false;
            saver_looping = true;

            idle_timer_interval = setInterval(
                () => {
                    if ($events_loc.launcher.screen_saver_img_kv) {
                        const keys = Object.keys(
                            $events_loc.launcher.screen_saver_img_kv
                        );
                        const rand_index = Math.floor(
                            Math.random() * keys.length
                        );
                        let event_file_obj =
                            $events_loc.launcher.screen_saver_img_kv[
                                keys[rand_index]
                            ];

                        $events_slct.event_file_id =
                            event_file_obj.event_file_id;
                        $events_slct.event_file_obj = event_file_obj;
                        $events_sess.launcher.modal__open_event_file_id = null;
                        $events_sess.launcher.modal__title =
                            event_file_obj.filename ?? '*';
                        $events_sess.launcher.modal__open_event_file_id =
                            $events_slct.event_file_id;
                        $events_sess.launcher.modal__event_file_obj =
                            event_file_obj;
                        return true;
                    }
                    return false;
                },
                $events_loc.launcher.idle_loop_period ?? 2 * 60 * 1000
            );
        } else {
            saver_looping = false;
            return false;
        }
    }

    onIdle(() => {
        clearInterval(idle_timer_interval);
        handle_idle_client();
    });

    $effect(() => {
        if (!$idle) {
            clearInterval(idle_timer_interval);
            saver_looping = false;
        }
    });

    // Reset to fit-mode whenever a different poster is opened.
    $effect(() => {
        if ($events_sess.launcher.modal__open_event_file_id) {
            modal_zoom_fit = true;
        }
    });
</script>

<svelte:head>
    <title>
        &AElig;:
        {$lq__event_location_obj?.name ?? '-- not set --'}
        ({$lq__event_session_obj?.name ?? 'Æ loading...'}) - Launcher v3 -
        {$events_loc?.title}
    </title>
</svelte:head>

<div
    class:mt-12={!$events_loc.launcher.hide__launcher_header}
    class:mt-2={$events_loc.launcher.hide__launcher_header}
    class="
        static
        m-auto
        border-x border-gray-200 dark:border-gray-600
        mb-16 sm:mb-12
        h-full
        w-full max-w-7xl
        transition-all
        "
>
    <header
        id="Main-Header"
        class:hidden={$events_loc.launcher.hide__launcher_header}
        class="
        z-20
        absolute top-0 left-0 right-0
        w-full max-w-7xl
        h-12
        p-1 px-12 m-auto

        flex flex-row items-center justify-around sm:justify-between

        text-sm

        bg-slate-200 dark:bg-slate-800

        opacity-95 hover:opacity-100
        transition-colors duration-300
        "
    >
        <h3 class="h4 text-center italic text-surface-600-400">
            <!-- Menu toggle: needs a real tap target for tablet/touch operators -->
            <button
                type="button"
                class="px-2 py-1 rounded hover:bg-surface-500/10 transition-colors"
                onclick={() => {
                    $events_loc.launcher.hide__launcher_menu =
                        !$events_loc.launcher.hide__launcher_menu;
                }}
                title="Toggle Launcher menu"
            >
                <Satellite class="text-base mx-1 inline-block text-gray-500" />
                <abbr title="Aether - Events Module Launcher">
                    Æ Launcher
                    <span
                        class="text-xs align-super font-normal"
                        title="Version 3">v3</span
                    >
                </abbr>
            </button>
        </h3>

        {#if $lq__event_obj}
            <h2
                class="hidden md:inline-block h3 text-center text-surface-600-400"
            >
                {$lq__event_obj.cfg_json?.short_name}
            </h2>
            <h3
                class="h4 text-center italic text-surface-600-400"
                title="Location ID: {$lq__event_location_obj?.event_location_id} Name: {$lq__event_location_obj?.name}"
            >
                <button
                    type="button"
                    class="text-base"
                    onclick={() => {
                        $ae_loc.edit_mode = !$ae_loc.edit_mode;
                    }}
                    title="Toggle Edit Mode to show location options and more"
                >
                    <span class="fas fa-map-marker-alt"></span>
                    <span class="sr-only">Location:</span>
                </button>
                {$lq__event_location_obj?.name}
            </h3>
        {:else}
            <div class="flex flex-row gap-1 items-center justify-center">
                <span class="fas fa-spinner fa-spin mx-1"></span>
                <span>Loading event...</span>
            </div>
        {/if}
    </header>

    <div
        class="
        h-full min-w-full w-full max-w-full
        flex flex-col sm:flex-row flex-wrap sm:flex-nowrap gap-0
        items-center
        justify-start sm:justify-center
        py-1 px-0.5
        bg-gray-100 dark:bg-gray-900

        "
    >
        <section
            id="Main-Nav-Menu"
            class="event_launcher_menu
        h-full
        basis-1/5
        min-w-56 md:min-w-64 lg:min-w-72
        max-w-xs
        pt-0.5 pr-0.5
        flex flex-col gap-1 items-center justify-start
        overflow-y-auto

        border-r border-gray-200 dark:border-gray-700
        "
            class:hidden={$events_loc.launcher.hide__launcher_menu}
        >
            <Launcher_menu
                {lq__event_obj}
                {lq__event_event_file_obj_li}
                {lq__location_event_file_obj_li}
                slct__event_file_id={$events_slct.event_file_id}
                {lq__event_location_obj_li}
                {lq__event_location_obj}
                slct__event_location_id={$events_slct.event_location_id}
                bind:loading__session_li_status={
                    $events_sess.launcher.loading__session_li_status
                }
                {lq__event_session_obj_li}
                bind:loading__session_id_status={
                    $events_sess.launcher.loading__session_id_status
                }
                bind:slct__event_session_id={$events_slct.event_session_id}
                bind:trigger_reload__event_session_obj_id={
                    $events_sess.launcher.trigger_reload__event_session_obj_id
                }
                bind:trigger_reload__event_session_obj_li={
                    $events_sess.launcher.trigger_reload__event_session_obj_li
                }
                bind:trigger_reload__event_location_obj_li={
                    $events_sess.launcher.trigger_reload__event_location_obj_li
                }
            ></Launcher_menu>
        </section>

        <section
            id="Main-Content"
            class="event_launcher_main
        h-full
        min-w-xs
        max-w-full
        py-1 px-0.5
        basis-4/5
        flex flex-col gap-1
        items-center
        justify-center
        overflow-y-auto
        "
        >
            {#if !$events_slct.event_location_id}
                <div
                    class="flex flex-row items-center justify-center p-8 opacity-50"
                >
                    <span class="fas fa-map-marker-alt mx-2 text-2xl"></span>
                    <span>Please select a location from the menu</span>
                </div>
            {/if}

            {#if $events_slct.event_session_id}
                <Launcher_session_view
                    bind:slct__event_session_id={$events_slct.event_session_id}
                ></Launcher_session_view>
            {:else if $events_slct.event_location_id}
                <!-- Location selected but no session chosen yet — prompt operator -->
                <div
                    class="flex flex-col items-center justify-center p-8 opacity-50"
                >
                    <LoaderCircle class="animate-spin mb-2" />
                    <span>Select a session from the menu</span>
                </div>
            {/if}
        </section>
    </div>

    {@render children?.()}
</div>

<footer
    id="Main-Footer"
    class:hidden={$events_loc.launcher.hide__launcher_footer}
    class="
        z-20
        absolute bottom-0 left-0 right-0
        w-full max-w-7xl
        p-1 m-auto

        flex flex-row items-center justify-between

        text-xs

        bg-gray-200 border-t border-gray-300
        dark:bg-gray-800 dark:border-gray-600

        opacity-70 hover:opacity-100
        transition-opacity duration-500
        "
>
    <div
        class="slct_location_name transition-colors duration-300"
        title="Location ID: {$lq__event_location_obj?.event_location_id} Name: {$lq__event_location_obj?.name} | Device ID: {$lq__event_device_obj?.event_device_id} Name: {$lq__event_device_obj?.name}"
    >
        <!-- Edit mode toggle: needs tap target for tablet operators -->
        <button
            type="button"
            class="px-1.5 py-1 rounded hover:bg-surface-500/10 transition-colors"
            onclick={() => {
                $ae_loc.edit_mode = !$ae_loc.edit_mode;
            }}
            title="Toggle Edit Mode to show location options and more"
        >
            <span class="sr-only">Location:</span>
            <span class="fas fa-map-marker-alt"></span>
        </button>
        {$lq__event_location_obj?.name}
        {#if $lq__event_device_obj?.name}
            <span class="fas fa-laptop mx-1"></span>
            {$lq__event_device_obj?.name}
        {/if}
    </div>

    <span
        class:preset-tonal-warning={!$idle}
        class:preset-tonal-success={$idle}
        class="group px-2 py-0.5 rounded-md transition-colors duration-300"
        title="The user is currently {$idle ? 'idle' : 'active'}"
    >
        {#if $idle}
            <span class="fas fa-bed mx-1"></span>
            <span class="hidden group-hover:inline"> Idle </span>
        {:else}
            <span class="fas fa-mouse-pointer mx-1"></span>
            <span class="hidden group-hover:inline"> Active </span>
        {/if}
    </span>

    <span
        class="group px-2 py-0.5 rounded-md transition-colors duration-300"
        title="Online status = {online?.current}"
    >
        <span class="fas fa-wifi mx-1"></span>
        {online?.current ? '' : 'Offline!'}
    </span>

    <span
        class:hidden={!$events_loc.launcher.ws_connect}
        class:preset-tonal-warning={$events_sess.launcher.ws_connect_status !=
            'connected'}
        class:preset-tonal-success={$events_sess.launcher.ws_connect_status ==
            'connected'}
        class="group px-2 py-0.5 rounded-md transition-colors duration-300"
        title="WebSocket is {$events_sess.launcher.ws_connect_status ==
        'connected'
            ? 'connected'
            : 'disconnected'} API: {$ae_api?.base_url}"
    >
        {#if $events_sess.launcher.ws_connect_status == 'connected'}
            <span class="fas fa-sitemap mx-1 text-green-700"></span>
            <span class="hidden group-hover:inline"> WS Connected </span>
        {:else}
            <span class="fas fa-times mx-1"></span>
            <span class="hidden group-hover:inline"> WS Disconnected </span>
        {/if}
    </span>

    <!-- Font size cycler: allows onsite operators to adjust text size without needing
         access to the global sys menu, which may be unavailable in kiosk/locked setups. -->
    <button
        type="button"
        onclick={() => {
            const mode = $ae_loc.font_size_mode;
            if (!mode || mode === 'default') $ae_loc.font_size_mode = 'larger';
            else if (mode === 'larger') $ae_loc.font_size_mode = 'smaller';
            else $ae_loc.font_size_mode = 'default';
        }}
        class="group px-2 py-0.5 rounded-md font-mono font-bold hover:bg-surface-500/10 transition-colors duration-200"
        title="Font size: {$ae_loc.font_size_mode ?? 'default'} — tap to cycle (default → larger → smaller)"
    >
        {#if $ae_loc.font_size_mode === 'larger'}
            <span>A+</span>
        {:else if $ae_loc.font_size_mode === 'smaller'}
            <span class="text-[0.7em]">A−</span>
        {:else}
            <span>A</span>
        {/if}
    </button>

    <div
        class="current_datetime font-mono px-2 hover:font-bold hover:bg-white dark:hover:bg-slate-700 transition-colors"
    >
        <span class="hidden md:inline">
            <span class="fas fa-calendar-alt"></span>
            {ae_util.iso_datetime_formatter($time, 'date_full_no_year')}
        </span>
        <span class="hidden sm:inline">
            <span class="fas fa-clock"></span>
        </span>
        {#if $events_loc.launcher?.time_hours == 12}
            {ae_util.iso_datetime_formatter($time, 'time_12_long')}
        {:else}
            {ae_util.iso_datetime_formatter($time, 'time_long')}
        {/if}
    </div>
</footer>

<div class="absolute top-0 left-0 z-20 text-center">
    <button
        type="button"
        onclick={() => ($events_loc.launcher.hide_drawer__cfg = false)}
        class="btn btn-sm p-3 preset-tonal-error hover:preset-filled-error-500 transition-colors duration-300"
        class:opacity-25={!$ae_loc.trusted_access}
        class:hover:opacity-75={!$ae_loc.trusted_access}
    >
        <span class="fas fa-biohazard"></span>
        <span class="hidden">Launcher Config</span>
    </button>
</div>

<Drawer
    dismissable={false}
    class="bg-orange-50 dark:bg-slate-800 opacity-90 hover:opacity-97 transition-all duration-300 border border-gray-300 dark:border-gray-600 w-full md:w-96 lg:w-[32rem]"
    placement="left"
    {...{
        transitionType: 'fly',
        transitionParams: {
            x: -520,
            duration: 200,
            easing: sineIn
        }
    }}
    bind:hidden={$events_loc.launcher.hide_drawer__cfg}
    id="sidebar1"
>
    <Launcher_cfg></Launcher_cfg>

    <hr class="my-2 border-gray-300 dark:border-gray-600" />

    <div
        class="flex flex-row flex-wrap gap-0.5 items-center justify-center max-w-md"
    >
        <a
            href="/events/{$events_slct.event_id}"
            class="btn btn-sm preset-tonal-primary hover:preset-filled-primary-500"
        >
            <span class="fas fa-search m-1"></span>
            Session Search
        </a>
        {#if $events_slct?.event_location_id}
            <a
                href="/events/{$events_slct.event_id}/location/{$events_slct.event_location_id}"
                class="btn btn-sm preset-tonal-primary hover:preset-filled-primary-500"
            >
                <span class="fas fa-map-marker m-1"></span>
                View Selected Location
            </a>
        {/if}
        {#if $events_slct?.event_session_id}
            <a
                href="/events/{$events_slct.event_id}/session/{$events_slct.event_session_id}"
                class="btn btn-sm preset-tonal-primary hover:preset-filled-primary-500"
            >
                <span class="fas fa-chalkboard-teacher m-1"></span>
                View Selected Session
            </a>
        {/if}
    </div>
</Drawer>

<Drawer
    activateClickOutside={false}
    class="bg-red-50 dark:bg-slate-900 opacity-75 hover:opacity-95 transition-all duration-300"
    placement="bottom"
    {...{
        transitionType: 'fly',
        transitionParams: {
            y: 320,
            duration: 200,
            easing: sineIn
        }
    }}
    bind:hidden={$events_loc.launcher.hide_drawer__debug}
    id="sidebar2"
>
    <div class="flex flex-row items-center justify-between">
        <h2
            class="text-center mb-4 text-base font-semibold text-gray-500 dark:text-gray-400"
        >
            Debug
        </h2>
        <button
            type="button"
            onclick={() => ($events_loc.launcher.hide_drawer__debug = true)}
            class="mb-4 dark:text-white"
        >
            <span class="fas fa-times"></span>
            <span class="hidden">Close Debug Drawer</span>
        </button>
    </div>

    <div>
        <pre class="text-xs">
        {JSON.stringify($events_loc.launcher, null, 2)}
    </pre>
        <hr />
        <pre class="text-xs">
        {JSON.stringify($ae_api, null, 2)}
    </pre>
    </div>
</Drawer>

<Modal
    open={$events_sess.launcher?.modal__open_event_file_id}
    autoclose={false}
    placement="top-center"
    class="
        bg-gray-500/90 dark:bg-gray-800/90 text-gray-800 dark:text-gray-200
        rounded-lg border-gray-200 dark:border-gray-700
        divide-y divide-gray-200 dark:divide-gray-700 shadow-md
        relative
        flex flex-col items-center justify-center
        {$events_loc.launcher.controller == 'remote' ? 'min-h-full' : ''}
        min-w-full
        "
    bodyClass="p-0 space-y-0 overflow-auto flex flex-col gap-1 items-center justify-center pb-14"
    headerClass={`fixed top-0 right-0 left-0 p-1 md:p-2 flex flex-row items-center ${$events_loc.launcher.controller == 'remote' ? 'hidden' : ''} bg-white dark:bg-gray-800 opacity-50 ${$events_loc.launcher.hide__modal_header_title ? 'justify-center' : 'justify-between'}`}
    footerClass="text-center hidden"
>
    {#snippet header()}
        <h3
            class:hidden={$events_loc.launcher.hide__modal_header_title}
            class="text-lg font-semibold opacity-20 hover:opacity-100 transition-all"
        >
            {$events_sess.launcher?.modal__title ?? 'Digital Poster Display'}
        </h3>
        <button
            type="button"
            class="btn flex-row-reverse group transition-all justify-self-end"
            onclick={() => {
                $events_sess.launcher.modal__open_event_file_id = null;
            }}
            title="Close Modal"
        >
            <span class="fas fa-times my-1.5"></span>
            <span class="hidden group-hover:inline"> Close</span>
        </button>
    {/snippet}

    <!-- WHY: overflow-auto on wrapper + touch-action: pinch-zoom on the img enables
         native pinch-to-zoom on mobile/tablet without any JS library.
         Toggling modal_zoom_fit switches between 'fit to viewport' (default, clean display)
         and 'natural size' mode where the operator can pan/scroll and pinch-zoom freely
         for closer inspection or accessibility accommodation. -->
    <div
        class="w-full flex-1 flex items-center justify-center"
        class:overflow-auto={!modal_zoom_fit}
        class:overflow-hidden={modal_zoom_fit}
    >
        {#if $events_sess.launcher.modal__event_file_obj?.hosted_file_id}
            <!-- WHY: Use hosted_file endpoint (not event_file) — the event_file download
                 endpoint requires auth headers that a plain <img> tag cannot send (→ 403).
                 The hosted_file endpoint accepts key=account_id as a query param and is
                 the proven browser-compatible path for direct file display. -->
            <img
                src="{$ae_api.base_url}/v3/action/hosted_file/{$events_sess.launcher
                    .modal__event_file_obj.hosted_file_id}/download?return_file=true&filename={encodeURIComponent(
                    $events_sess.launcher.modal__event_file_obj.filename ?? ''
                )}&key={$ae_api.account_id}"
                alt="Poster: {$events_sess.launcher.modal__title}"
                ondblclick={() => {
                    modal_zoom_fit = !modal_zoom_fit;
                    // Sync zoom state to the remote display when acting as controller.
                    if ($events_loc.launcher.controller == 'local_push' && $events_sess.launcher.ws_connect_status == 'connected') {
                        $events_sess.launcher.controller_cmd = `ae_zoom:${modal_zoom_fit ? 'fit' : 'zoom'}`;
                        $events_sess.launcher.controller_trigger_send = true;
                    }
                }}
                title="Double-tap to toggle zoom / fit"
                class="block transition-[max-height,max-width,width] duration-200"
                class:max-h-[85dvh]={modal_zoom_fit}
                class:max-w-full={modal_zoom_fit}
                class:w-auto={modal_zoom_fit}
                class:object-contain={modal_zoom_fit}
                class:cursor-zoom-in={modal_zoom_fit}
                class:cursor-zoom-out={!modal_zoom_fit}
                style="touch-action: pinch-zoom;"
            />
        {:else}
            <div class="flex flex-row items-center justify-center p-4">
                <span class="fas fa-info-circle mx-1"></span>
                <span>No image selected</span>
            </div>
        {/if}
    </div>

    <!-- Bottom control bar — hidden on the remote display (operator-free kiosk) -->
    <!-- WHY: pb-14 on bodyClass reserves space so these buttons don't obscure poster content. -->
    <div
        class="
            absolute bottom-0 left-0 right-0
            flex flex-row items-center justify-between gap-2
            p-1.5
            bg-black/30 backdrop-blur-sm
            "
        class:hidden={$events_loc.launcher.controller == 'remote'}
    >
        <!-- Zoom / Fit toggle: accessibility accommodation — lets operators and general
             public zoom in to read details, pinch on mobile, or double-tap the image. -->
        <button
            type="button"
            onclick={() => {
                modal_zoom_fit = !modal_zoom_fit;
                // Sync zoom state to the remote display when acting as controller.
                if ($events_loc.launcher.controller == 'local_push' && $events_sess.launcher.ws_connect_status == 'connected') {
                    $events_sess.launcher.controller_cmd = `ae_zoom:${modal_zoom_fit ? 'fit' : 'zoom'}`;
                    $events_sess.launcher.controller_trigger_send = true;
                }
            }}
            class="btn btn-sm preset-tonal-surface opacity-80 hover:opacity-100 transition-opacity"
            title={modal_zoom_fit
                ? 'Pan / Zoom mode — pinch or double-tap image to zoom'
                : 'Fit image to screen'}
        >
            {#if modal_zoom_fit}
                <span class="fas fa-search-plus mr-1"></span>
                <span class="hidden sm:inline">Zoom</span>
            {:else}
                <span class="fas fa-compress mr-1"></span>
                <span class="hidden sm:inline">Fit</span>
            {/if}
        </button>

        <!-- Close Both: sends WS close to remote display AND dismisses this modal.
             Remote screensaver will resume cycling after idle timeout. -->
        <button
            type="button"
            onclick={() => {
                $events_sess.launcher.controller_cmd = `ae_close:event_file_modal`;
                $events_sess.launcher.controller_trigger_send = true;
                $events_sess.launcher.modal__title = '';
                $events_sess.launcher.modal__open_event_file_id = null;
                $events_sess.launcher.modal__event_file_obj = null;
            }}
            class="btn btn-sm preset-tonal-error opacity-80 hover:opacity-100 transition-all"
            class:hidden={$events_loc.launcher.controller != 'local_push' ||
                $events_sess.launcher.ws_connect_status != 'connected'}
            title="Close poster on this device and on the remote display (screensaver resumes)"
        >
            <span class="fas fa-tv mr-1"></span>
            <span class="fas fa-times"></span>
            <span class="hidden sm:inline ml-1">Close Both</span>
        </button>

        <!-- Back to List: dismisses this controller's view only.
             Remote display keeps showing the current poster until next action or screensaver. -->
        <button
            type="button"
            onclick={() => {
                $events_sess.launcher.modal__title = '';
                $events_sess.launcher.modal__open_event_file_id = null;
                $events_sess.launcher.modal__event_file_obj = null;
            }}
            class="btn btn-sm preset-tonal-surface border border-surface-400/50 opacity-80 hover:opacity-100 transition-all"
            class:hidden={!$ae_loc.trusted_access &&
                ($events_loc.launcher.controller != 'local_push' ||
                    $events_sess.launcher.ws_connect_status != 'connected')}
            title="Close poster on this device only — remote display keeps showing"
        >
            <span class="fas fa-list mr-1"></span>
            <span class="hidden sm:inline">Back to List</span>
        </button>
    </div>
</Modal>

{#if $events_loc.launcher.controller_group_code && $events_loc.launcher.ws_connect}
    <Element_websocket_v3
        {log_lvl}
        bind:ws_connect={$events_loc.launcher.ws_connect}
        bind:ws_connect_status={$events_sess.launcher.ws_connect_status}
        ws_server={$ae_api.fqdn}
        api_key={$ae_api.api_secret_key}
        jwt={$ae_loc.jwt}
        bind:group_id={$events_loc.launcher.controller_group_code}
        bind:client_id={$events_loc.launcher.controller_client_id}
        bind:cmd={$events_sess.launcher.controller_cmd}
        bind:trigger_send={$events_sess.launcher.controller_trigger_send}
        bind:trigger_connect={$events_sess.launcher.trigger__ws_connect}
        bind:trigger_disconnect={$events_sess.launcher.trigger__ws_disconnect}
        bind:hide__ws_element={$events_loc.launcher.hide__ws_element}
        bind:hide__ws_form={$events_loc.launcher.hide__ws_form}
        bind:hide__ws_messages={$events_loc.launcher.hide__ws_messages}
        bind:hide__ws_commands={$events_loc.launcher.hide__ws_commands}
        bind:ws_conn_status={trigger_handle_ws_conn}
        bind:ws_recv_status={trigger_handle_ws_recv}
        bind:ws_sent_status={trigger_handle_ws_sent}
    />
{/if}
