<script lang="ts">
import { onMount, untrack } from 'svelte';
let log_lvl: number = 0;

// *** Import Svelte specific
import { browser } from '$app/environment';
import { page } from '$app/stores';

// *** Import Aether specific variables and functions
import { ae_util } from '$lib/ae_utils/ae_utils';
import {
    ae_snip,
    ae_loc,
    ae_sess,
    ae_api,
    ae_trig,
    slct,
    slct_trigger
} from '$lib/stores/ae_stores';
import { idaa_loc, idaa_sess, idaa_slct } from '$lib/stores/ae_idaa_stores';
import { db_posts } from '$lib/ae_posts/db_posts';
import { db_archives } from '$lib/ae_archives/db_archives';
import { db_events } from '$lib/ae_events/db_events';
import { check_and_clear_idb_table } from '$lib/stores/store_versions';

interface Props {
    /** @type {import('./$types').LayoutData} */
    data: any;
    children?: import('svelte').Snippet;
}

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

// Reactive UUID — derived from $page.url so it updates on every SvelteKit client-side
// navigation, not just full page reloads.
// WHY: The original impl read window.location.search once (const), assuming UUID changes
// always cause a full iframe reload (Novi impersonation behavior). But manual URL edits
// in the same SvelteKit session keep the layout mounted and leave url_uuid stale — the
// TTL cache then hits for the OLD valid UUID, granting access under the wrong identity.
// $page.url.searchParams is always current, so any UUID change is caught and re-verified.
const url_uuid = $derived(browser ? ($page.url.searchParams.get('uuid') ?? null) : null);

// True while the Novi API call is in flight.
// Pre-initialized to true when a UUID is present to prevent an "Access Denied" flash
// before the effect has a chance to run on first render.
// WHY direct window.location.search here (not url_uuid derived): $state initializers are
// not reactive — we want a one-time snapshot of "is there a UUID on load?". Using the
// $derived here would only capture its initial value anyway, and Svelte warns about it.
let novi_verifying: boolean = $state(
    browser ? !!new URLSearchParams(window.location.search).get('uuid') : false
);

// Concurrency guard — separate from novi_verifying (the UI spinner).
// Do NOT use novi_verifying as a concurrency guard: it is pre-initialized to true,
// which would cause the guard to fire immediately and skip verification entirely.
let verify_in_flight = false;

// Failure latch — stores the UUID that definitively failed (Novi 200+empty, 4xx, network error).
// WHY (loop prevention): writes to $idaa_loc in the catch block trigger svelte-persisted-store
// storage events → $ae_loc re-notifies subscribers → Effect 2 re-runs. Without the latch,
// the same failing UUID would be re-verified infinitely.
// WHY (UUID-keyed, not boolean): when the URL UUID changes (SvelteKit navigation), the new
// UUID must be verified fresh. A plain boolean would wrongly block verification of the new UUID.
// Storing the failed UUID means only that exact UUID is skipped; any other UUID is a clean slate.
let verify_failed_for_uuid: string | null = null;
// Tracks the type of Novi API failure for the UI — 'rate_limited' or 'api_error'.
// A transient API error is NOT the same as a real membership denial; this lets us
// show a distinct "Verification Unavailable" state instead of "Access Denied".
let verify_error_type: 'rate_limited' | 'api_error' | 'network_error' | null = $state(null);
// Shown inside the spinner — updated during rate-limit retry waits.
let verifying_status_msg: string = $state('Verifying identity...');
// Incremented by handle_verify_retry() to re-run Effect 2 without a full page reload.
let retry_count: number = $state(0);

// In-iframe reload helper.
// After internal SvelteKit navigation the UUID is stripped from the URL — a bare
// location.reload() would reload without it, so verification can't run and the user
// sees "Access Denied" again. We save the initial load URL (which contains the UUID)
// to sessionStorage on first mount and use it for all reload buttons.
// sessionStorage is per-tab and cross-origin-isolated, so each Novi iframe instance
// gets its own slot; it clears naturally when Novi closes/reopens the iframe.
const IDAA_IFRAME_RELOAD_URL_KEY = 'idaa_iframe_reload_url';

onMount(() => {
    const uuid_in_url = new URLSearchParams(window.location.search).get('uuid');
    if (uuid_in_url) {
        // Guard: iOS Safari Private Browsing and some iframe sandbox configs throw on
        // sessionStorage access. Graceful fallback: skip the save; reload_with_uuid()
        // will fall back to location.reload() (loses UUID-preservation but doesn't crash).
        try {
            if (!sessionStorage.getItem(IDAA_IFRAME_RELOAD_URL_KEY)) {
                sessionStorage.setItem(IDAA_IFRAME_RELOAD_URL_KEY, window.location.href);
            }
        } catch {
            console.warn('IDAA Layout: sessionStorage unavailable — reload buttons will use location.reload() fallback.');
        }
    }
});

function reload_with_uuid() {
    try {
        const initial_url = sessionStorage.getItem(IDAA_IFRAME_RELOAD_URL_KEY);
        if (initial_url && initial_url !== location.href) {
            location.href = initial_url;
            return;
        }
    } catch { /* sessionStorage unavailable — fall through to location.reload() */ }
    location.reload();
}

// Clear stale db_events.event IDB data on IDAA session start.
//
// WHY: Stale cached event records were the root cause of the "no meetings found" bug
// on the IDAA Recovery Meetings page — a ~1-year unresolved issue (fixed 2026-05-16).
// After a deploy that changed properties_to_save, old IDB records persisted with missing
// or wrong fields. The search fast path returned 0 results (failed account_id filter),
// the API call errored silently, and the error state showed the same message as a real
// empty result — users had no indication anything was wrong.
//
// This runs once per IDAA session. On a version match (normal case) it costs one
// localStorage read. On a mismatch it clears the table; the SWR search re-fetches.
// To force a clear after a deploy: bump IDB_CONTENT_VERSIONS.events.event in store_versions.ts.
if (browser) {
    check_and_clear_idb_table(db_events.event, 'events', 'event').catch(() => {});
}

// Show a manual reset button if the spinner is still visible after this many ms.
// Handles the case where site_cfg_json loads without novi_idaa_api_key (stale cache)
// or the Novi API call hangs — the user would otherwise be stuck with no escape.
//
// WHY 35s: worst-case auto-retry cycle is 27s (12s first timeout + 3s wait + 12s retry).
// If the auto-retries succeed or fail within that window, the spinner is already gone
// (content shown or error panel shown) before this fires. The escape hatch only appears
// for the rare case where the Novi API is slow-but-not-timing-out, or site_cfg_json
// never loads. Previously 8s — fired mid-flight and caused premature Reset & Retry clicks.
const VERIFY_TIMEOUT_MS = 35_000;

let verifying_timed_out: boolean = $state(false);

$effect(() => {
    if (novi_verifying) {
        const id = setTimeout(() => {
            verifying_timed_out = true;
        }, VERIFY_TIMEOUT_MS);
        return () => clearTimeout(id);
    } else {
        verifying_timed_out = false;
    }
});

// WHY 12 hours: members open IDAA in a hotel/conference context and should not need to
// re-verify mid-day. After our client-side Novi call is replaced by server-side (FastAPI),
// this TTL can be tuned independently. Until then, 12h covers a full workday.
const VERIFIED_TTL_MS_DEFAULT = 12 * 60 * 60 * 1000; // 12 hours

// Effect 1: Set URL origin and params
$effect(() => {
    untrack(() => {
        $ae_loc.url_origin = data.url.origin;
        $ae_loc.params = data.params;
    });

    if (log_lvl > 1) {
        console.log(`+layout.svelte data:`, data);
    }
});

// Effect 2: Novi UUID verification
// Reactive dependencies (read outside untrack):
//   - $ae_loc.site_cfg_json: API key arrives async via SWR; re-run when config loads.
//   - url_uuid ($derived from $page.url): re-run when URL UUID changes (SvelteKit navigation).
// Everything else is inside untrack() to prevent reactive loops from store writes.
$effect(() => {
    if (!browser) return;

    const site_cfg = $ae_loc.site_cfg_json || {};
    const admin_li: string[] = site_cfg.novi_admin_li ?? [];
    const trusted_li: string[] = site_cfg.novi_trusted_li ?? [];
    const ttl_ms: number = site_cfg.novi_verified_ttl_ms ?? VERIFIED_TTL_MS_DEFAULT;

    // Read url_uuid here (outside untrack) to create a reactive dependency.
    // Effect 2 re-runs whenever the UUID in the URL changes.
    const current_uuid = url_uuid;
    void retry_count; // Reactive dep — re-run Effect 2 when user clicks "Try Again".

    untrack(() => {
        if (!current_uuid) {
            // No UUID in URL. Two possible cases:
            //
            // 1. Non-Novi path (user/pass or shared passcode sign-in) — clear and deny.
            //
            // 2. Internal SvelteKit navigation within the iframe (e.g. clicking "Meeting Details"
            //    from the list page). The UUID was on the initial iframe load URL but is NOT
            //    carried forward on internal <a href> links — they only contain the path/event_id.
            //    In this case the user has a valid TTL-cached Novi session in $idaa_loc and we
            //    must NOT clear it, or every internal navigation will show "Access Denied".
            //
            // Distinguish the two by checking if there is an active verified session.
            const now = Date.now();
            const has_cached_session =
                $idaa_loc.novi_verified &&
                $idaa_loc.novi_uuid &&
                $idaa_loc.novi_verified_ts &&
                now - $idaa_loc.novi_verified_ts < ttl_ms;
            if (has_cached_session) {
                // Case 2: internal navigation — keep the verified session, nothing to do.
                // BUT: only if $ae_loc also reflects active auth. If $ae_loc was reset
                // externally (e.g., sign-out) while $idaa_loc retained novi_verified (within TTL),
                // the state is inconsistent — fall through to Case 1 and purge.
                if ($ae_loc.trusted_access || $ae_loc.authenticated_access) {
                    novi_verifying = false;
                    return;
                }
                // Inconsistent state — $idaa_loc says verified but $ae_loc has no auth.
                // Fall through to Case 1 below to purge stale IDB data.
                console.warn('IDAA Layout: has_cached_session but no auth in $ae_loc — purging (inconsistent state).');
            }
            // Case 3: no UUID, no cached Novi session, but user has trusted/manager access.
            // They are legitimately authenticated via a non-Novi path — do NOT purge.
            if ($ae_loc.trusted_access) {
                novi_verifying = false;
                return;
            }
            // Case 1: no UUID, no cached session, no elevated access — anonymous user.
            // Purge any IDAA data that may have been cached from a previous session.
            $idaa_loc.novi_verified = false;
            novi_verifying = false;
            console.log('IDAA Layout: No UUID / no session — purging IDAA IDB tables (posts, archives, events).');
            db_posts.post.clear().catch(() => {});
            db_posts.comment.clear().catch(() => {});
            db_archives.archive.clear().catch(() => {});
            db_archives.content.clear().catch(() => {});
            db_events.event.clear().catch(() => {});
            return;
        }

        if (verify_in_flight) return;

        if (verify_failed_for_uuid === current_uuid) {
            // This exact UUID already definitively failed — do not retry.
            // A different UUID is a clean slate; see verify_failed_for_uuid declaration.
            if (log_lvl) console.log(`IDAA Layout: skipping re-verification — already failed for ${current_uuid}`);
            novi_verifying = false;
            return;
        }

        // TTL cache: skip if this UUID was recently verified AND $ae_loc still has permissions.
        // Without the permission check: if $ae_loc resets (e.g. browser restart while
        // $idaa_loc TTL is still valid), verification is skipped and the user hits Access Denied
        // because $ae_loc.authenticated_access is false. Re-running verify fixes both.
        const now = Date.now();
        if (
            $idaa_loc.novi_verified &&
            $idaa_loc.novi_uuid === current_uuid &&
            $idaa_loc.novi_verified_ts &&
            now - $idaa_loc.novi_verified_ts < ttl_ms &&
            ($ae_loc.trusted_access || $ae_loc.authenticated_access)
        ) {
            if (log_lvl) console.log(`IDAA Layout: cached verification valid for ${current_uuid}`);
            novi_verifying = false;
            return;
        }

        // Load admin/trusted lists before calling verify.
        // Only override when site_cfg provides them — don't wipe hardcoded defaults with [].
        if (admin_li?.length) $idaa_loc.novi_admin_li = admin_li;
        if (trusted_li?.length) $idaa_loc.novi_trusted_li = trusted_li;

        verify_in_flight = true;
        // WHY: Only show the "Verifying identity..." spinner on initial load when the user
        // has NO existing valid session. If the TTL expired and we're re-verifying in the
        // background (user is already authenticated), run silently — do NOT set
        // novi_verifying=true. Setting it would hide children and destroy any running
        // components (e.g. Jitsi mid-call). The user can keep working; access is only
        // revoked in the catch block if the re-verification actively fails.
        const has_valid_session =
            $ae_loc.trusted_access ||
            ($ae_loc.authenticated_access &&
                $idaa_loc.novi_verified &&
                $idaa_loc.novi_uuid === current_uuid);
        if (!has_valid_session) {
            novi_verifying = true;
        }
        verify_novi_uuid(current_uuid);
    });
});

/**
 * Verifies a Novi UUID via the Aether server-side proxy endpoint.
 * The backend calls Novi server-to-server, eliminating browser IP exposure (hotel WiFi,
 * VPN blocks, Cloudflare IP reputation) that caused false "Access Denied" for some members.
 *
 * Aether endpoint: GET /v3/action/idaa/novi_member/{uuid}
 * Response codes:
 *   200 → verified — data.full_name and data.email populated
 *   404 → not a member (or Novi returned empty 200, handled server-side) → deny
 *   429 → Novi rate limited → wait 10s, retry once
 *   503 → Novi unreachable / Novi 5xx → show api_error UI
 *   Network error / AbortError → wait 3s, retry once
 */
async function verify_novi_uuid(uuid: string, is_retry: boolean = false) {
    // WHY: Check presence of novi_idaa_api_key to detect whether this site has IDAA
    // configured. The key itself is no longer used for auth (the Aether server holds it),
    // but its absence means either (a) this is not the IDAA site / dev domain, or (b)
    // site_cfg_json hasn't finished loading yet. In both cases: skip and wait for the
    // next Effect 2 trigger. This also guards the 'ghost' account_id state (domain-not-found
    // fallback gives site_cfg_json = {}) — prevents a spurious API call with a bad account_id.
    const site_cfg = $ae_loc.site_cfg_json || {};
    if (!site_cfg.novi_idaa_api_key) {
        console.warn('IDAA Layout: Novi not configured for this site (or site_cfg_json still loading) — skipping verification.');
        verify_in_flight = false;
        return;
    }

    const account_id = $ae_loc.account_id;
    const api_key = $ae_api.api_secret_key;
    const api_url = $ae_api.base_url;

    if (!account_id || account_id === 'ghost' || !api_key || !api_url) {
        // Aether config not yet available. Do not clear $idaa_loc: that would destroy a
        // valid cached session and cause a re-auth loop on the next Effect 2 run.
        console.warn('IDAA Layout: Aether API config not yet available — skipping verification (will retry when config loads).');
        verify_in_flight = false;
        return;
    }

    verifying_status_msg = 'Verifying identity...';
    verify_error_type = null;
    console.log(`IDAA Layout: Starting Novi UUID verification (server-side) for ${uuid}...`);

    try {
        // Hard timeout: abort if the Aether endpoint doesn't respond within 12 seconds.
        const abort_ctrl = new AbortController();
        const abort_timeout_id = setTimeout(() => abort_ctrl.abort(), 12_000);
        let response: Response;
        try {
            response = await fetch(`${api_url}/v3/action/idaa/novi_member/${uuid}`, {
                method: 'GET',
                headers: {
                    'x-aether-api-key': api_key,
                    'x-account-id': account_id
                },
                signal: abort_ctrl.signal
            });
        } finally {
            clearTimeout(abort_timeout_id);
        }

        if (response.status === 429) {
            if (is_retry) {
                verify_error_type = 'rate_limited';
                throw new Error(`Novi verification rate limited for UUID ${uuid} (retry also failed)`);
            }
            console.warn(`IDAA Layout: Rate limited (429) for ${uuid}. Retrying in 10s...`);
            verifying_status_msg = 'High traffic — retrying in 10 seconds...';
            await new Promise<void>((resolve) => setTimeout(resolve, 10_000));
            await verify_novi_uuid(uuid, true);
            return;
        }

        if (response.status === 503) {
            // Novi unreachable or Novi 5xx — Aether backend returns 503.
            // Mirror the network-error retry path: one automatic 3s wait before giving up.
            if (is_retry) {
                throw new Error(`Novi verification: Novi unreachable (503) — retry also failed`);
            }
            console.warn(`IDAA Layout: Novi unreachable (503) for ${uuid}. Retrying in 3s...`);
            verifying_status_msg = 'Connection issue — retrying...';
            await new Promise<void>((resolve) => setTimeout(resolve, 3_000));
            await verify_novi_uuid(uuid, true);
            return;
        }

        if (!response.ok) {
            // 404 = not a member (Novi 404, or Novi empty-200 anti-pattern handled server-side).
            throw new Error(`Novi verification returned ${response.status} for UUID ${uuid}`);
        }

        const result = await response.json();
        if (log_lvl > 1) {
            console.log(`IDAA Layout: Novi verification response for ${uuid}:`, result);
        }

        const verified_name: string | null = result?.data?.full_name ?? null;
        const verified_email: string | null = result?.data?.email ?? null;

        if (!result?.data?.verified || (!verified_name && !verified_email)) {
            throw new Error(`Novi verification returned 200 but no identity data for UUID ${uuid}`);
        }

        $idaa_loc.novi_uuid = uuid;
        $idaa_loc.novi_email = verified_email;
        $idaa_loc.novi_full_name = verified_name;
        $idaa_loc.novi_verified = true;
        $idaa_loc.novi_verified_ts = Date.now();

        console.log(`IDAA Layout: Novi UUID verified (server-side). Name: ${verified_name}, Email: ${verified_email}`);

        // Determine permission level based on verified UUID.
        // UUID confirmed real → at minimum authenticated. Check lists for higher levels.
        let target_novi_level = 'authenticated';
        if ($idaa_loc.novi_admin_li?.includes(uuid)) {
            target_novi_level = 'administrator';
        } else if ($idaa_loc.novi_trusted_li?.includes(uuid)) {
            target_novi_level = 'trusted';
        }

        // PERMISSION UPGRADE STRATEGY: only apply if higher than current level.
        // This prevents a global 'manager' from being downgraded by the IDAA layout.
        const current_level = $ae_loc.access_type || 'anonymous';
        if (ae_util.compare_access_levels(target_novi_level, current_level) === 1) {
            console.log(
                `IDAA Layout: Upgrading access from ${current_level} to ${target_novi_level} (Novi verified)`
            );
            const perms = ae_util.process_permission_checks(target_novi_level);
            $ae_loc = { ...$ae_loc, ...perms };
        }

        // Reset BB query filters to safe defaults in case they were left in a non-default state.
        $idaa_loc.bb.qry__hidden = 'not_hidden';
        $idaa_loc.bb.qry__enabled = 'enabled';
    } catch (error) {
        // Network errors (TypeError: Failed to fetch) and AbortError (our 12s timeout) get one
        // automatic retry after a short wait — they are almost always transient.
        const is_network_or_timeout =
            error instanceof TypeError ||
            (error instanceof Error && error.name === 'AbortError');
        if (is_network_or_timeout && !is_retry) {
            const reason = error instanceof Error && error.name === 'AbortError'
                ? 'timed out (no response in 12s)'
                : 'network error';
            console.warn(`IDAA Layout: Novi verification ${reason} for ${uuid}. Retrying in 3s...`);
            verifying_status_msg = 'Connection issue — retrying...';
            await new Promise<void>((resolve) => setTimeout(resolve, 3_000));
            await verify_novi_uuid(uuid, true);
            return;
        }

        // Verification failed — all-or-nothing means deny access.
        console.error(`IDAA Layout: Novi UUID verification failed for ${uuid}:`, error);
        verify_failed_for_uuid = uuid; // Latch — stop retry loop for this UUID. See declaration comment.
        // 'rate_limited' is set before throw for 429.
        // 'network_error' for persistent network/timeout failures after retry.
        // 'api_error' for all other failures (404 non-member, 503 Novi down, etc.).
        if (!verify_error_type) {
            verify_error_type = is_network_or_timeout ? 'network_error' : 'api_error';
        }
        $idaa_loc.novi_uuid = null;
        $idaa_loc.novi_email = null;
        $idaa_loc.novi_full_name = null;
        $idaa_loc.novi_verified = false;
        // Purge private IDAA data from IDB — do not leave sensitive data cached after auth failure.
        console.log('IDAA Layout: Novi auth failed — purging IDAA IDB tables (posts, archives, events).');
        db_posts.post.clear().catch(() => {});
        db_posts.comment.clear().catch(() => {});
        db_archives.archive.clear().catch(() => {});
        db_archives.content.clear().catch(() => {});
        db_events.event.clear().catch(() => {});
    } finally {
        verify_in_flight = false;
        novi_verifying = false;
    }
}
/**
 * Clears the verification failure latch and forces Effect 2 to re-run without a full page reload.
 * Called by the "Try Again" button in the verification-error UI state.
 */
function handle_verify_retry() {
    verify_error_type = null;
    verify_failed_for_uuid = null;
    verifying_timed_out = false;
    novi_verifying = true;
    retry_count++;
}
</script>

{#if !browser}
    <!-- SSR / pre-hydration placeholder -->
    <p class="text-center text-sm text-gray-500">
        <span class="fas fa-spinner fa-spin"></span>
        Loading...
    </p>
{:else if novi_verifying}
    <!-- Async Novi API call is in flight — show spinner to prevent Access Denied flash -->
    <div
        class="container m-8 flex w-full flex-col items-center justify-center gap-2 p-8">
        <p class="text-center text-sm text-gray-500">
            <span class="fas fa-spinner fa-spin"></span>
            {verifying_status_msg}
        </p>
        {#if verifying_timed_out}
            <!-- Escape hatch: shown after VERIFY_TIMEOUT_MS if still spinning.
                 Most likely cause: stale localStorage missing novi_idaa_api_key.
                 Clearing both stores and reloading forces a fresh site config fetch. -->
            <p class="mt-2 text-center text-xs text-gray-400">
                This is taking longer than expected.
            </p>
            <button
                type="button"
                class="btn btn-sm preset-tonal-primary border-primary-500 mt-1 border"
                onclick={() => {
                    localStorage.removeItem('ae_loc');
                    localStorage.removeItem('ae_idaa_loc');
                    console.log('IDAA Layout: Reset & Retry — purging IDAA IDB tables (posts, archives, events).');
                    db_posts.post.clear().catch(() => {});
                    db_posts.comment.clear().catch(() => {});
                    db_archives.archive.clear().catch(() => {});
                    db_archives.content.clear().catch(() => {});
                    db_events.event.clear().catch(() => {});
                    reload_with_uuid();
                }}>
                <span class="fas fa-redo m-1"></span>
                Reset &amp; Retry
            </button>
        {/if}
    </div>
{:else if verify_error_type}
    <!-- Verification error — distinct from real access denial.
         A transient Novi API failure (network, 5xx, rate limit) should NOT look identical
         to a real "your UUID is not a member" denial. Provides Try Again without a reload,
         plus aggressive Clear Cache and Full Reset paths for persistent issues. -->
    <div class="container m-8 flex w-full flex-col items-center justify-center gap-3 p-8 text-center">
        <h1 class="font-bold">
            <span class="text-yellow-500 dark:text-yellow-400">
                <span class="fas fa-exclamation-circle mr-1"></span>
                Identity Verification Unavailable
            </span>
        </h1>
        {#if verify_error_type === 'rate_limited'}
            <p class="text-sm">
                The membership directory is temporarily busy (rate limited). Please wait a moment and try again.
            </p>
        {:else if verify_error_type === 'network_error'}
            <p class="text-sm">
                We were unable to reach the membership directory after two attempts. This is usually caused by a network filter blocking the connection — it is not a problem with your membership.
            </p>
            <ul class="mt-1 list-disc pl-5 text-left text-sm">
                <li>Turn off your VPN if one is running</li>
                <li>Switch from hotel or conference WiFi to your phone's cellular data</li>
                <li>Try a different network (mobile data, home WiFi)</li>
                <li>If on a corporate or hospital network, try from a personal device</li>
            </ul>
        {:else}
            <p class="text-sm">
                We were unable to connect to the membership directory. This is usually a temporary issue — it is not a problem with your membership or access.
            </p>
        {/if}
        <p class="text-xs italic text-gray-500">
            Try "Try Again" first. If the problem persists, use "Clear Cache &amp; Reload".
            If nothing works, close this page and reopen IDAA from the menu on idaa.org.
        </p>
        <div class="flex flex-row flex-wrap items-center justify-center gap-2">
            <!-- Try Again: unlatches verify_failed_for_uuid and re-runs Effect 2 — no reload needed -->
            <button
                type="button"
                onclick={handle_verify_retry}
                class="btn btn-sm preset-tonal-primary border-primary-500 border">
                <span class="fas fa-redo m-1"></span>
                Try Again
            </button>
            <!-- Clear Cache & Reload: wipes IDAA-specific localStorage + IDB, then reloads -->
            <button
                type="button"
                onclick={() => {
                    localStorage.removeItem('ae_idaa_loc');
                    console.log('IDAA Layout: Clear Cache & Reload — purging IDAA IDB tables.');
                    db_posts.post.clear().catch(() => {});
                    db_posts.comment.clear().catch(() => {});
                    db_archives.archive.clear().catch(() => {});
                    db_archives.content.clear().catch(() => {});
                    db_events.event.clear().catch(() => {});
                    reload_with_uuid();
                }}
                class="btn btn-sm preset-tonal-surface preset-outlined-warning-100-900 hover:preset-filled-warning-200-800 transition-all">
                <span class="fas fa-sync-alt m-1"></span>
                Clear Cache &amp; Reload
            </button>
            <!-- Full Reset: enumerates and deletes all IDB + clears all storage + reloads -->
            <button
                type="button"
                onclick={async () => {
                    if (!confirm('FULL RESET: Delete all caches and local storage, then reload?')) return;
                    const db_list = await indexedDB.databases();
                    for (const db of db_list) {
                        if (db.name) indexedDB.deleteDatabase(db.name);
                    }
                    localStorage.clear();
                    sessionStorage.clear();
                    // sessionStorage was just cleared, so reload_with_uuid() falls back to
                    // location.reload() — that's correct since this is a full wipe.
                    location.reload();
                }}
                class="btn btn-sm preset-tonal-surface preset-outlined-error-100-900 hover:preset-filled-error-200-800 transition-all">
                <span class="fas fa-trash m-1"></span>
                Full Reset
            </button>
        </div>
    </div>
{:else if $ae_loc.trusted_access || ($ae_loc.authenticated_access && $idaa_loc.novi_uuid && $idaa_loc.novi_verified)}
    <!-- TEMPORARY (2026-04-01): One-time notice about last night's technical issues. Remove after a few days. -->
    <!-- {#if show_tech_notice && 1 == 3}
        <div class="m-2 flex items-start gap-3 rounded-lg border border-yellow-400 bg-yellow-50 p-3 text-yellow-900 dark:border-yellow-600 dark:bg-yellow-950 dark:text-yellow-100">
            <span class="fas fa-exclamation-triangle mt-0.5 shrink-0 text-yellow-500"></span>
            <p class="flex-1 text-sm">
                Opt 1:
                <strong>Notice:</strong> We experienced technical difficulties last night (March 31) and this morning (April 1). Things are back to normal. We apologize for any inconvenience.

                <br>
                <br>

                Opt 2:
                <strong>Notice:</strong> We have experienced some technical difficulties recently. Things are back to normal. We apologize for any inconvenience.
            </p>
            <button
                type="button"
                class="shrink-0 rounded px-2 py-1 text-xs font-semibold hover:bg-yellow-200 dark:hover:bg-yellow-800 btn"
                onclick={dismiss_tech_notice}>
                OK
            </button>
        </div>
    {/if} -->
    {@render children?.()}
    {#if $idaa_loc.novi_uuid}
        <p class="text-center text-xs text-gray-500">
            Novi: {$idaa_loc.novi_uuid} · {$idaa_loc.novi_full_name ?? 'name not set'} · {$idaa_loc.novi_email ?? 'email not set'}
        </p>
    {/if}
{:else}
    <!-- Access Denied — shown only when verification is not in flight (novi_verifying=false),
         no API error (verify_error_type=null), and $ae_loc has no auth. Most common causes:
         (1) No UUID in URL and no cached session — genuine denial.
         (2) Timing race on first load — UUID arrives but $ae_loc not yet populated.
         (3) $ae_loc reset while $idaa_loc TTL cache was still valid (fixed via TTL+perms check).
         In iframe context the UUID is only on the initial Novi-provided URL, not on
         subsequent SvelteKit client-side navigations — reload_with_uuid() restores it. -->
    <div
        class="container m-8 flex w-full flex-col items-center justify-center gap-3 p-8 text-center">
        <h1 class="font-bold">
            <span class="text-red-500">
                <span class="fas fa-exclamation-triangle"></span>
                Access Denied
                <span class="fas fa-exclamation-triangle"></span>
            </span>
        </h1>
        <p class="text-sm">You do not have access to this IDAA page.</p>

        {#if $ae_loc.iframe}
            <p class="text-xs italic text-gray-500">
                If you just opened this page, try reloading. If the problem persists, try
                "Clear Cache &amp; Reload", or close this page and reopen IDAA from the menu on idaa.org.
            </p>
            <div class="flex flex-row flex-wrap items-center justify-center gap-2">
                <!-- WHY: In iframe mode the Novi UUID is passed via URL param on first load.
                     If verification was a timing race the user lands here. reload_with_uuid()
                     restores the original URL (with UUID) — plain location.reload() would
                     reload the current SvelteKit path which may not have the UUID. -->
                <button
                    type="button"
                    class="btn btn-sm preset-tonal-primary border-primary-500 border"
                    onclick={reload_with_uuid}>
                    <span class="fas fa-redo m-1"></span>
                    Reload / Retry
                </button>
                <button
                    type="button"
                    onclick={() => {
                        localStorage.removeItem('ae_idaa_loc');
                        db_posts.post.clear().catch(() => {});
                        db_posts.comment.clear().catch(() => {});
                        db_archives.archive.clear().catch(() => {});
                        db_archives.content.clear().catch(() => {});
                        db_events.event.clear().catch(() => {});
                        reload_with_uuid();
                    }}
                    class="btn btn-sm preset-tonal-surface preset-outlined-warning-100-900 hover:preset-filled-warning-200-800 transition-all">
                    <span class="fas fa-sync-alt m-1"></span>
                    Clear Cache &amp; Reload
                </button>
            </div>
        {/if}

        {#if $ae_loc.edit_mode}
            <div class="mt-2 rounded border border-gray-300 bg-gray-50 p-2 text-left text-xs text-gray-500 dark:border-gray-700 dark:bg-gray-900">
                <p class="font-mono">Debug (edit mode only)</p>
                {#if $idaa_loc.novi_uuid}
                    <p>UUID: {$idaa_loc.novi_uuid}</p>
                    <p>Name: {$idaa_loc.novi_full_name ?? 'not set'}</p>
                    <p>Email: {$idaa_loc.novi_email ?? 'not set'}</p>
                {:else}
                    <p>No Novi UUID in store</p>
                {/if}
                <p>iframe: {$ae_loc.iframe}</p>
                <p>authenticated_access: {$ae_loc.authenticated_access}</p>
                <p>trusted_access: {$ae_loc.trusted_access}</p>
            </div>
        {/if}
    </div>
{/if}
