<script lang="ts">
/**
 * menu_session_list.svelte — Aether Launcher: Session Selector
 *
 * PURPOSE:
 *   This is the primary navigation control for conference operators using
 *   the Aether Events Launcher. It lists all sessions in the selected room
 *   (event_location) and lets the operator switch the room's active session.
 *
 * ENVIRONMENT:
 *   The Launcher runs on operator laptops, dedicated podium/kiosk tablets,
 *   projector-connected desktops, and occasionally phones in breakout rooms.
 *   Users range from tech-savvy AV staff to volunteers with limited computer
 *   experience. Some users have motor impairments or shaky hands (e.g. older
 *   members common at IDAA and similar events).
 *
 * KEY DESIGN CONSTRAINTS:
 *   - Must show 0–20 sessions without scrolling (compact fixed-height rows)
 *   - Session names can be extremely long (~300 chars) — must truncate at
 *     rest but reveal fully on hover without pushing other rows around
 *   - Hover-to-switch fires after a delay timer (not instantly) to prevent
 *     accidental session changes from casual cursor movement
 *   - Strongly prefer click-to-confirm over hair-trigger hover activation
 *   - Works in light and dark mode; projector-safe high-contrast overlay
 *
 * DATA FLOW:
 *   lq__event_session_obj_li (Dexie liveQuery, passed from launcher/+layout.svelte)
 *   → rendered here as buttons
 *   → click / hover-timer sets trigger_reload__event_session_obj_id
 *   → $effect fires load + URL navigation + optional WS remote-control push
 *
 * SESSION VISIBILITY (operator toggle — show_content__hidden_sessions):
 *   Normal sessions:             always visible
 *   hide_event_launcher = true:  hidden from list by default (launcher-specific
 *                                suppression, e.g. overflow/backup sessions)
 *   hide = true:                 globally hidden across all views (draft/cancelled)
 *
 *   Both hidden states are fetched into Dexie with hidden:'all' by the background
 *   sync so the operator can reveal them via the "All Sessions" menu toggle.
 *   When revealed they appear dimmed (opacity-40) with an eye-slash indicator.
 */

interface Props {
    slct__event_session_id?: null | boolean | string;
    loading__session_id_status?: null | boolean | string;
    // export let lq__event_session_obj: any;
    lq__event_session_obj_li: any;

    trigger_reload__event_session_obj_id?: boolean | null | string;
    // trigger_reload__event_session_obj_li?: boolean;

    log_lvl?: number;
}

let {
    slct__event_session_id = $bindable(null),
    loading__session_id_status = $bindable(null),
    lq__event_session_obj_li,

    trigger_reload__event_session_obj_id = $bindable(false),
    // trigger_reload__event_session_obj_li = $bindable(false),

    log_lvl = $bindable(1)
}: Props = $props();

// *** Import Svelte specific
import { untrack } from 'svelte';
import { page } from '$app/state';
import { goto } from '$app/navigation';
// import { liveQuery } from "dexie";

// *** Import other supporting libraries

// *** 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 { db_events } from "$lib/db_events";
import {
    ae_snip,
    ae_loc,
    ae_sess,
    ae_trig,
    slct,
    slct_trigger
} from '$lib/stores/ae_stores';
import {
    events_sess,
    events_slct,
    events_trigger
} from '$lib/stores/ae_events_stores';
import { launcher_loc } from '$lib/stores/ae_events_stores__launcher.svelte';
import {
    CalendarCheck,
    CalendarDays,
    Check,
    EyeOff,
    Image,
    LoaderCircle
} from '@lucide/svelte';
// export let slct__event_session_id: any;

// *** Functions and Logic

let ae_promises: key_val = $state({
    slct__event_session_id: null,
    slct__event_presentation_li: null
});

// WHY 1200ms: Aether Launcher is used at conferences by operators of all ages and
// motor abilities — shaky hands, imprecise trackpads, and fat-finger tablet taps are
// routine. 750ms (the previous value) triggered accidental session changes when the
// cursor drifted across the list. 1200ms means the operator must deliberately hold
// focus on a row for over a second before it fires — still fast for intentional use.
// NOTE: hover-timer only triggers a data PRE-LOAD (preview). The session does not
// actually switch until the operator clicks. See onclick handler below.
let hover_timer_wait = 1400;
let hover_timer: any = $state(null);

// Navigation Shield Pattern (Refactored 2026-02-11)
// WHY: We use untrack for store updates to prevent circular reactivity loops
// with the layout's sync effect. Standardizing on page.url ensures
// the back button and browser history work correctly.
$effect(() => {
    if (trigger_reload__event_session_obj_id) {
        const start = performance.now();
        const event_session_id = String(trigger_reload__event_session_obj_id);

        if (log_lvl) {
            console.log(
                `[UI Trace] trigger_reload changed to: ${event_session_id}`
            );
        }

        untrack(() => {
            // 1. Reset trigger immediately
            trigger_reload__event_session_obj_id = false;

            // 2. Local State Updates (Sync store for instant LiveQuery reaction)
            if (slct__event_session_id !== event_session_id) {
                slct__event_session_id = event_session_id;
            }

            // 3. Remote Control Sync
            if (launcher_loc.current.controller == 'local_push') {
                $events_sess.launcher.controller_cmd = `ae_load:event_session=${event_session_id}`;
                $events_sess.launcher.controller_trigger_send = true;
            }

            // 4. URL Navigation — the single load fires from +page.ts when the URL updates.
            // Assigning the goto promise to ae_promises drives the #await spinner in the template.
            loading__session_id_status = 'loading';
            let new_url_obj = new URL(page.url);
            new_url_obj.searchParams.set('session_id', event_session_id);

            if (log_lvl) console.log(`[UI Trace] Initiating SvelteKit goto...`);

            ae_promises.slct__event_session_id = goto(new_url_obj.toString(), {
                replaceState: false,
                noScroll: true,
                keepFocus: true
            }).then(() => {
                loading__session_id_status = false;
                if (log_lvl)
                    console.log(
                        `🏁 [Trace] Navigation Roundtrip: ${(performance.now() - start).toFixed(2)}ms`
                    );
            });
        });
    }
});
</script>

<div
    class="
        flex w-full
        max-w-80 flex-col flex-wrap items-center justify-start gap-1 md:justify-center
        ">
    {#if $lq__event_session_obj_li && $lq__event_session_obj_li.length > 0}
        <div class="text-surface-600-400 text-xs">
            <strong>
                Sessions:
                <span
                    class:hidden={!$ae_loc.trusted_access ||
                        !$ae_loc.edit_mode}>
                    ({$lq__event_session_obj_li?.length}&times;)
                </span>

                <!-- This should fade out once the data is loaded. -->
                {#await ae_promises.slct__event_session_id}
                    <LoaderCircle
                        size="0.85em"
                        class="inline animate-spin text-blue-500" />
                {:then result}
                    <Check size="0.85em" class="inline text-green-500/80" />
                {/await}
            </strong>
        </div>

        <ul
            class="
        m-0 flex
        w-full max-w-full
        flex-col items-start justify-start gap-0 p-0
        ">
            {#each $lq__event_session_obj_li as event_session_obj (event_session_obj.event_session_id)}
                <li
                    class="
                session-item
                relative
                m-0 w-full
                max-w-full p-0
                "
                    class:session-active={slct__event_session_id ===
                        event_session_obj?.id}
                    class:hidden={!launcher_loc.current
                        .show_content__hidden_sessions &&
                        (event_session_obj?.hide ||
                            event_session_obj?.hide_event_launcher)}>
                    <button
                        type="button"
                        onmouseenter={() => {
                            // Start a 750 ms timer to prevent changing the session too quickly.
                            hover_timer = setTimeout(async () => {
                                // Only run if the session ID has changed
                                if (
                                    slct__event_session_id ===
                                    event_session_obj?.id
                                ) {
                                    return;
                                }
                                trigger_reload__event_session_obj_id =
                                    event_session_obj?.id;
                            }, hover_timer_wait);
                        }}
                        onmouseleave={() => {
                            clearTimeout(hover_timer);
                        }}
                        onclick={async () => {
                            trigger_reload__event_session_obj_id =
                                event_session_obj?.id;

                            $events_slct.event_file_id = null;
                            $events_slct.event_file_obj = null;
                        }}
                        class="
                            session-btn
                            btn btn-sm
                            focus-visible:ring-primary-400 m-0 flex

                            w-full
                            max-w-full flex-row
                            items-center
                            justify-start
                            rounded-md px-1.5

                            py-1
                            text-left text-sm transition-colors duration-200
                            focus-visible:ring-2 focus-visible:ring-offset-1
                            "
                        class:font-bold={slct__event_session_id === event_session_obj?.id}
                        class:preset-tonal-primary={slct__event_session_id === event_session_obj?.id}
                        class:preset-outlined-primary-200-800={slct__event_session_id === event_session_obj?.id}
                        class:preset-tonal-secondary={slct__event_session_id != event_session_obj?.id}
                        class:preset-outlined-secondary-200-800={slct__event_session_id != event_session_obj?.id}
                        class:opacity-40={event_session_obj?.hide || event_session_obj?.hide_event_launcher}
                        title={`Session: ${event_session_obj?.name}\nID: ${event_session_obj?.id} | ${ae_util.iso_datetime_formatter(event_session_obj?.start_datetime, launcher_loc.current.time_format)}`}>
                        <!-- Session row layout: [date column | session name]
                             Date column is fixed-width (shrink-0) so name column always
                             gets consistent space regardless of date string length.

                             VISIBILITY (class:hidden / class:opacity-40 above):
                             - hide_event_launcher: suppressed in Launcher only (operator toggle reveals)
                             - hide: globally hidden across all Aether views (draft/cancelled)
                             Both fetched into Dexie with hidden:'all' by launcher_background_sync.
                             When revealed, dimmed (opacity-40) with eye-slash icon. -->

                        <span
                            class="border-surface-300-700 min-w-20 shrink-0 border-r pr-1 font-mono">
                            {#if slct__event_session_id === event_session_obj?.id}
                                <CalendarCheck size="0.85em" class="inline opacity-50" />
                            {:else}
                                <CalendarDays size="0.85em" class="inline opacity-50" />
                            {/if}
                            <span
                                class="text-xs"
                                class:hidden={slct__event_session_id ===
                                    event_session_obj?.id}>
                                {ae_util.iso_datetime_formatter(
                                    event_session_obj?.start_datetime,
                                    'week_medium'
                                )}
                            </span>
                            <span class="text-xs">
                                {ae_util.iso_datetime_formatter(
                                    event_session_obj?.start_datetime,
                                    launcher_loc.current.time_format
                                )}
                            </span>
                        </span>

                        <span
                            class="
                    session-name
                    min-w-0 grow
                    text-sm
                    ">
                            {#if event_session_obj?.type_code == 'poster'}
                                <span title="Digital Poster Session"
                                    ><Image
                                        size="0.85em"
                                        class="text-primary-500 mr-1 inline" /></span>
                            {/if}
                            <!-- Distinct icon styles distinguish the two hidden states:
                                 amber  = hide (globally hidden — draft, cancelled, or admin-only)
                                 muted  = hide_event_launcher (suppressed in Launcher view only) -->
                            {#if event_session_obj?.hide}
                                <span title="Hidden session"
                                    ><EyeOff
                                        size="0.85em"
                                        class="text-warning-600 mr-1 inline" /></span>
                            {:else if event_session_obj?.hide_event_launcher}
                                <span title="Hidden from Launcher"
                                    ><EyeOff
                                        size="0.85em"
                                        class="mr-1 inline opacity-60" /></span>
                            {/if}
                            {event_session_obj?.name}
                        </span>
                    </button>
                </li>
            {/each}
        </ul>
    {:else}
        <div class="text-sm">No sessions found.</div>
    {/if}
</div>

<style>
/*
     * ═══════════════════════════════════════════════════════════════════
     *  Aether Launcher — Compact Session List Styles
     *  One Sky IT — specialized for conference operator use
     * ═══════════════════════════════════════════════════════════════════
     *
     * ARCHITECTURE OVERVIEW
     * ─────────────────────
     * Each <li class="session-item"> is a FIXED-HEIGHT placeholder (2rem).
     * Its child <button class="session-btn"> is clipped to that height at rest.
     *
     * On hover/focus-within, the button becomes position:absolute — it pops
     * OUT of the layout flow and floats as an overlay panel above sibling rows.
     * The <li> retains its original 2rem height permanently, so NO siblings
     * ever shift position. This is deliberate and critical:
     *
     *   WHY NOT transform:scale?
     *   scale() keeps the element in flow but enlarges its visual footprint.
     *   If it grows large enough to show wrapped text, neighbors shift OR the
     *   cursor drifts off the hitbox edge, firing mouseleave and cancelling the
     *   hover timer — exactly the jitter problem for shaky-hand operators.
     *
     *   WHY position:absolute instead?
     *   The overlay grows in z-space only. The operator's cursor stays inside
     *   the expanded button hitbox the entire time (the button IS the element
     *   they are hovering — its hitbox grows with it). No jitter.
     *
     * ACTIVE SESSION
     * ──────────────
     * The currently-selected session (.session-active) reverts to height:auto
     * in normal flow so its full (potentially very long) name is always visible
     * without requiring any interaction. One expanded row in a 20-row list is
     * visually negligible and gives the operator instant confirmation of where
     * the room currently is.
     *
     * OVERLAY BACKGROUNDS
     * ────────────────────
     * preset-tonal-secondary is semi-transparent by design (~20% fill). Used as
     * the overlay background it lets underlying rows bleed through, making the
     * text hard to read — especially on projectors and in dark mode.
     * The overlay therefore uses explicit opaque colours:
     *   Light: slate-100  (#f1f5f9) — matches standard card surface
     *   Dark:  slate-800  (#1e293b) + slate-100 text — high contrast, WCAG AA
     * A primary-accent left border provides a clear visual anchor so operators
     * can instantly see which row is being previewed.
     */

/* ── Inactive row: fixed compact height ── */
.session-item {
    height: 2rem;
}

/* ── Active row: always fully visible in flow ── */
.session-item.session-active {
    height: auto;
    min-height: 2rem;
}

/* Clip inactive button content to row height */
.session-btn {
    overflow: hidden;
}

/* Active button: never clip — operator can always read it */
.session-item.session-active .session-btn {
    overflow: visible;
}

/*
     * Inactive hover/focus: pop button out as an overlay panel.
     * - position:absolute keeps the <li> placeholder at 2rem (no layout shift)
     * - Solid opaque background prevents tonal transparency from showing through
     * - Left border accent gives a clear "you are here" cue
     */
.session-item:not(.session-active):hover .session-btn,
.session-item:not(.session-active):focus-within .session-btn {
    position: absolute;
    top: 0;
    left: 0;
    right: 0;
    height: auto;
    z-index: 20;
    overflow: visible;
    opacity: 1;
    background-color: #f1f5f9; /* slate-100 — solid light surface */
    border-left: 3px solid rgb(var(--color-primary-500, 99 102 241));
    border-radius: 0.375rem;
    box-shadow:
        0 4px 20px rgba(0, 0, 0, 0.15),
        0 1px 4px rgba(0, 0, 0, 0.08);
}

/* Dark mode overlay — solid dark surface, light readable text */
:global(.dark) .session-item:not(.session-active):hover .session-btn,
:global(.dark) .session-item:not(.session-active):focus-within .session-btn {
    background-color: #1e293b; /* slate-800 */
    color: #f1f5f9; /* slate-100 */
    box-shadow:
        0 4px 24px rgba(0, 0, 0, 0.6),
        0 1px 4px rgba(0, 0, 0, 0.4);
}

/* ── Session name: single-line truncated at rest ── */
.session-name {
    overflow: hidden;
    white-space: nowrap;
    text-overflow: ellipsis;
}

/* Active session: name always wraps fully */
.session-item.session-active .session-name {
    white-space: normal;
    overflow: visible;
    text-overflow: unset;
}

/* Overlay: name wraps fully (300-char titles readable) */
.session-item:not(.session-active):hover .session-name,
.session-item:not(.session-active):focus-within .session-name {
    white-space: normal;
    overflow: visible;
    text-overflow: unset;
}
</style>
