<script lang="ts">
import { onMount, onDestroy } from 'svelte';
import { page } from '$app/stores';
import { ae_loc, ae_api } from '$lib/stores/ae_stores';
import { idaa_loc } from '$lib/stores/ae_idaa_stores';
import MyClipboard from '$lib/app_components/e_app_clipboard.svelte';
import {
    create_ae_obj__activity_log,
    update_ae_obj__activity_log
} from '$lib/ae_core/ae_core__activity_log';

let log_lvl: number = $state(0);

interface Props {
    data: any;
}
let { data }: Props = $props();

// Component UI State
let show_jitsi_container: boolean = $state(true);
let show_jitsi_tools: boolean = $state(true);
let expand_jitsi_tools: boolean = $state(false);
let show_meeting_details: boolean = $state(false);
let show_live_stats: boolean = $state(false);
let show_profile_editor: boolean = $state(false);
let show_sound_settings: boolean = $state(false);

// User & Meeting State
let user_id: null | string = $state(null);
let display_name: null | string = $state(null);
let email: null | string = $state(null);
let is_moderator: boolean = $state(false);
let room_name: null | string = $state(null);
let domain: null | string = $state(null);

// Jitsi API & Sound Settings
let jitsi_api: any = $state(null);
const jitsi_container_id = 'jitsi_meet_external_api_container';
let disable_incoming_msg_sound: boolean = $state(true);
let disable_participant_joined_sound: boolean = $state(false);
let disable_participant_left_sound: boolean = $state(false);
let disable_reaction_sound: boolean = $state(true);
let disable_raise_hand_sound: boolean = $state(true);
let name_input: string = $state('');
let email_input: string = $state('');

// --- NEW Logging & Stats State ---
let jitsi_meeting_id: string | null = $state(null);
let primary_activity_log_id: string | null = $state(null);
let meeting_participants = $state(new Map<string, any>());
let meeting_start_time: Date | null = $state(null);
let meeting_duration: string = $state('00:00:00');
let duration_timer_id: any = $state(null);
// reporting_timer_id is now removed

// Breakout modal — lets users open the meeting outside the Novi iframe
let show_breakout_modal: boolean = $state(false);
let breakout_link_copied: boolean = $state(false);

const breakout_url = $derived.by(() => {
    const url = new URL($page.url.href);

    // Proactively inject site access key if missing or empty.
    // WHY: SvelteKit internal navigation drops bootstrap params (key, uuid). When a member
    // "breaks out" of the Novi iframe, they need these keys in the URL to satisfy the
    // site-domain gate and the IDAA Novi auth gate in a fresh tab.
    if (!url.searchParams.get('key')) {
        const access_key = $ae_loc.allow_access;
        if (typeof access_key === 'string' && access_key) {
            url.searchParams.set('key', access_key);
        } else if ($ae_loc.site_access_key) {
            url.searchParams.set('key', $ae_loc.site_access_key);
        }
    }

    // Proactively inject Novi UUID if missing or empty (ensures auth persistence).
    if (!url.searchParams.get('uuid') && $idaa_loc.novi_uuid) {
        url.searchParams.set('uuid', $idaa_loc.novi_uuid);
    }

    return url.href;
});

function copy_meeting_link() {
    navigator.clipboard.writeText(breakout_url).then(() => {
        breakout_link_copied = true;
        setTimeout(() => (breakout_link_copied = false), 2000);
    });
}

function build_jitsi_reports_href() {
    const url = new URL($page.url.href);
    url.pathname = '/idaa/jitsi_reports';
    url.searchParams.delete('room');

    // Proactively inject site access key if missing or empty
    if (!url.searchParams.get('key')) {
        const access_key = $ae_loc.allow_access;
        if (typeof access_key === 'string' && access_key) {
            url.searchParams.set('key', access_key);
        } else if ($ae_loc.site_access_key) {
            url.searchParams.set('key', $ae_loc.site_access_key);
        }
    }

    // Proactively inject Novi UUID if missing or empty
    if (!url.searchParams.get('uuid') && $idaa_loc.novi_uuid) {
        url.searchParams.set('uuid', $idaa_loc.novi_uuid);
    }

    return `${url.pathname}${url.search}${url.hash}`;
}

/**
 * Creates a new activity log entry for a discrete event (e.g., raise hand).
 */
async function create_discrete_activity_log(
    action: string,
    action_with: string,
    meta_kv: object
) {
    if (!is_moderator || !jitsi_meeting_id) return;
    if (log_lvl)
        console.log(
            `Jitsi: Creating discrete activity log for action: ${action}`
        );

    const data_kv = {
        external_client_id: jitsi_meeting_id,
        name: 'jitsi_meeting_event',
        description: `Event in room: ${room_name}`,
        url_root: $page.url.origin,
        url_full_path: $page.url.pathname,
        url_params: $page.url.searchParams.toString(),
        action: action,
        action_with: action_with,
        meta_json: meta_kv
    };

    try {
        await create_ae_obj__activity_log({
            api_cfg: $ae_api,
            account_id: $ae_loc.account_id,
            data_kv: data_kv,
            log_lvl: log_lvl
        });
    } catch (error) {
        console.error(
            `Jitsi: Error creating discrete activity log for ${action}:`,
            error
        );
    }
}

/**
 * Updates the primary activity log entry with the latest meeting state.
 */
async function update_primary_activity_log() {
    if (!is_moderator || !primary_activity_log_id) return;
    if (log_lvl)
        console.log(
            `Jitsi: Updating primary activity log: ${primary_activity_log_id}`
        );

    const participants_array = Array.from(meeting_participants.values());
    const data_kv = {
        meta_json: {
            duration: meeting_duration,
            participants: participants_array,
            participant_count: participants_array.length
        }
    };

    try {
        await update_ae_obj__activity_log({
            api_cfg: $ae_api,
            activity_log_id: primary_activity_log_id,
            data_kv: data_kv,
            log_lvl: log_lvl
        });
    } catch (error) {
        console.error('Jitsi: Error updating primary activity log:', error);
    }
}

function add_jitsi_event_listeners(api: any) {
    // --- Meeting/Participant State Changes ---
    api.on(
        'videoConferenceJoined',
        async (jitsi_data: {
            id: string;
            displayName: string;
            roomName: string;
        }) => {
            // Map Jitsi's camelCase to our internal snake_case
            const {
                id: participant_id,
                displayName: participant_name,
                roomName: jitsi_room_name
            } = jitsi_data;

            console.log('Jitsi Event: videoConferenceJoined', jitsi_data);
            meeting_start_time = new Date();
            if (jitsi_meeting_id === null)
                jitsi_meeting_id = `${jitsi_room_name}-${Date.now()}`;

            // Start duration timer
            if (duration_timer_id) clearInterval(duration_timer_id);
            duration_timer_id = setInterval(() => {
                if (meeting_start_time) {
                    const now = new Date();
                    const diff = now.getTime() - meeting_start_time.getTime();
                    const hours = Math.floor(diff / (1000 * 60 * 60))
                        .toString()
                        .padStart(2, '0');
                    const minutes = Math.floor((diff / (1000 * 60)) % 60)
                        .toString()
                        .padStart(2, '0');
                    const seconds = Math.floor((diff / 1000) % 60)
                        .toString()
                        .padStart(2, '0');
                    meeting_duration = `${hours}:${minutes}:${seconds}`;
                }
            }, 1000);

            // Populate initial participant list
            meeting_participants.set(participant_id, {
                id: participant_id,
                displayName: participant_name,
                role: 'participant'
            });
            api.getParticipantsInfo().forEach(
                (p: { participantId: string; displayName: string }) => {
                    if (!meeting_participants.has(p.participantId)) {
                        meeting_participants.set(p.participantId, {
                            id: p.participantId,
                            displayName: p.displayName,
                            role: 'participant'
                        });
                    }
                }
            );

            // --- CREATE INITIAL LOG ENTRY (if moderator) ---
            if (is_moderator && !primary_activity_log_id) {
                console.log(
                    'Jitsi: Moderator joined, creating initial activity log...'
                );
                const participants_array = Array.from(
                    meeting_participants.values()
                );
                const data_kv = {
                    external_client_id: jitsi_meeting_id,
                    name: 'jitsi_meeting_stats',
                    description: room_name,
                    url_root: $page.url.origin,
                    url_full_path: $page.url.pathname,
                    url_params: $page.url.searchParams.toString(),
                    action: 'jitsi_meeting_init',
                    action_with: 'on_init',
                    meta_json: {
                        duration: meeting_duration,
                        participants: participants_array,
                        participant_count: participants_array.length,
                        // Verified Novi UUID of the moderator who started this log
                        moderator_novi_uuid: $idaa_loc.novi_uuid ?? null
                    }
                };
                try {
                    const result = await create_ae_obj__activity_log({
                        api_cfg: $ae_api,
                        account_id: $ae_loc.account_id,
                        data_kv: data_kv,
                        log_lvl: log_lvl
                    });
                    if (result && result.activity_log_id_random) {
                        primary_activity_log_id = result.activity_log_id_random;
                        console.log(
                            `Jitsi: Initial activity log created: ${primary_activity_log_id}`
                        );
                    }
                } catch (error) {
                    console.error(
                        'Jitsi: Error creating initial activity log:',
                        error
                    );
                }
            }
        }
    );

    api.on(
        'participantJoined',
        (participant: { id: string; displayName: string }) => {
            console.log('Jitsi Event: participantJoined', participant);
            meeting_participants.set(participant.id, {
                id: participant.id,
                displayName: participant.displayName,
                role: 'participant'
            });
            update_primary_activity_log();

            // NOTE: We also want to log this as a discrete event
            create_discrete_activity_log(
                'jitsi_meeting_participant_joined',
                'participantJoined',
                {
                    attendee_id: participant.id,
                    full_name: participant.displayName
                }
            );
        }
    );

    api.on('participantLeft', (participant: { id: string }) => {
        console.log('Jitsi Event: participantLeft', participant);
        // Capture name before removing from map — it won't be available after delete
        const p_info = meeting_participants.get(participant.id);
        if (p_info) {
            meeting_participants.delete(participant.id);
            update_primary_activity_log();

            create_discrete_activity_log(
                'jitsi_meeting_participant_left',
                'participantLeft',
                {
                    attendee_id: participant.id,
                    full_name: p_info.displayName
                }
            );
        }
    });

    api.on(
        'participantRoleChanged',
        (participant: { id: string; role: string }) => {
            console.log('Jitsi Event: participantRoleChanged', participant);
            if (meeting_participants.has(participant.id)) {
                const p = meeting_participants.get(participant.id);
                p.role = participant.role;
                meeting_participants.set(participant.id, p);
                update_primary_activity_log();
            }
        }
    );

    // --- Discrete Event Logging ---
    api.on(
        'raiseHandUpdated',
        (participant: { id: string; raisesHand: boolean }) => {
            if (participant.raisesHand) {
                const p_info = meeting_participants.get(participant.id);
                if (p_info) {
                    console.log('Jitsi Event: raiseHandUpdated', p_info);
                    create_discrete_activity_log(
                        'jitsi_meeting_raise_hand',
                        'raiseHandUpdated',
                        {
                            attendee_id: p_info.id,
                            full_name: p_info.displayName
                        }
                    );
                }
            }
        }
    );

    api.on('videoConferenceLeft', () => {
        console.log('Jitsi Event: videoConferenceLeft');
        if (duration_timer_id) clearInterval(duration_timer_id);

        // Do a final update to the primary log so it captures the true end state,
        // then log the meeting end as a discrete event for the timeline.
        if (is_moderator && primary_activity_log_id) {
            update_primary_activity_log();
            create_discrete_activity_log(
                'jitsi_meeting_end',
                'videoConferenceLeft',
                {
                    final_duration: meeting_duration,
                    final_participant_count: meeting_participants.size
                }
            );
        }
    });

    api.on('readyToClose', () => {
        console.log('Jitsi Event: readyToClose');
        show_jitsi_container = false;
    });
}

async function handle_profile_update() {
    const name_changed =
        name_input && name_input.trim() !== '' && name_input !== display_name;
    const email_changed =
        email_input && email_input.trim() !== '' && email_input !== email;

    if (name_changed || email_changed) {
        console.log(`Jitsi: User updating profile.`);
        if (name_changed) {
            console.log(` - Name from "${display_name}" to "${name_input}"`);
            display_name = name_input.trim();
        }
        if (email_changed) {
            console.log(` - Email from "${email}" to "${email_input}"`);
            email = email_input.trim();
        }
        await init_jitsi();
    } else {
        console.log('Jitsi: Profile update skipped. No changes detected.');
    }
}

async function get_jitsi_jwt(
    display_name: string,
    email: string,
    is_moderator: boolean,
    room_name: string,
    user_id: string
) {
    const token_endpoint = $ae_loc.site_cfg_json?.jitsi_token_endpoint;

    const payload = {
        name: display_name,
        email: email,
        is_moderator: is_moderator,
        room: room_name,
        user: {
            id: user_id,
            name: display_name,
            email: email,
            moderator: is_moderator
        },
        features: {
            livestreaming: false,
            recording: false,
            transcription: false,
            'outbound-call': false
        },
        settings: {
            startMuted: data.params.start_muted === 'true',
            startHidden: data.params.start_hidden === 'true',
            reactionsMuted: disable_reaction_sound
        },
        config: {
            'prejoinConfig.enabled': false
        }
    };

    try {
        const response = await fetch(token_endpoint, {
            method: 'POST',
            headers: { 'Content-Type': 'application/json' },
            body: JSON.stringify(payload)
        });

        if (!response.ok)
            throw new Error('Failed to fetch JWT token from the server.');

        const result = await response.json();
        return result.token;
    } catch (err) {
        console.error('Error getting JWT:', err);
        return null;
    }
}

async function fetch_novi_data() {
    const url_params = data.params;
    // --- Start with fallback data from URL ---
    user_id = url_params.uuid; // Novi Customer GUID
    display_name = url_params.full_name ?? 'Guest'; // May be overridden
    email = (url_params.email ?? 'guest@example.com').replace(/\s+/g, '+'); // May be overridden
    is_moderator = url_params.moderator === 'true'; // URL fallback
    room_name = url_params.room ?? 'Default-Room';
    domain = url_params.domain ?? 'jitsi.dgrzone.com';

    // Override sound defaults from URL params only when explicitly provided.
    // If the param is absent (null), keep the $state defaults — the iframe template
    // does not pass these params, so unconditional assignment would wipe out the defaults.
    if (url_params.incoming_msg_sound !== null)
        disable_incoming_msg_sound = url_params.incoming_msg_sound === 'true';
    if (url_params.participant_joined_sound !== null)
        disable_participant_joined_sound = url_params.participant_joined_sound === 'true';
    if (url_params.participant_left_sound !== null)
        disable_participant_left_sound = url_params.participant_left_sound === 'true';
    if (url_params.reaction_sound !== null)
        disable_reaction_sound = url_params.reaction_sound === 'true';
    if (url_params.raise_hand_sound !== null)
        disable_raise_hand_sound = url_params.raise_hand_sound === 'true';

    if (log_lvl) {
        console.log(
            `Jitsi: Initial data: user_id: ${user_id}, display_name: ${display_name}, email: ${email}, room_name: ${room_name}, domain: ${domain}`
        );
    }

    if (!user_id) {
        const container = document.getElementById(jitsi_container_id);
        if (container) {
            container.innerHTML =
                '<h1>User ID (uuid) is missing. Cannot start meeting.</h1>';
        }
        console.error(
            'Jitsi: User ID (uuid) is missing. Cannot start meeting.'
        );
        return;
    }

    // --- Fetch dynamic data from Novi API ---
    const novi_api_root_url = $ae_loc.site_cfg_json?.novi_api_root_url;
    const novi_api_key = $ae_loc.site_cfg_json?.novi_idaa_api_key;

    if (novi_api_root_url && novi_api_key) {
        // If the IDAA layout already verified this UUID, re-use those results rather than
        // making a duplicate Novi API call. The layout runs on every IDAA route, so by the
        // time onMount fires here it will usually have completed verification already.
        if ($idaa_loc.novi_verified && $idaa_loc.novi_email) {
            display_name = $idaa_loc.novi_full_name ?? display_name;
            email = $idaa_loc.novi_email ?? email;
            console.log(
                `Jitsi: Using layout-verified Novi data for user ${user_id}`
            );
        } else {
            const member_details = await get_novi_member_details(
                user_id,
                novi_api_root_url,
                novi_api_key
            );
            if (member_details.display_name) {
                display_name = member_details.display_name;
                console.log(
                    `Jitsi: Updated display_name from Novi: ${display_name}`
                );
            }
            if (member_details.email) {
                email = member_details.email;
                console.log(`Jitsi: Updated email from Novi: ${email}`);
            }
        }

        // Trusted/admin users are always moderators. Check the UUID directly against the
        // known lists rather than $ae_loc.trusted_access — that flag is only upgraded, never
        // downgraded, so it sticks across Novi impersonation (which does a full iframe reload
        // with a different UUID but doesn't reset the inherited access level).
        const admin_li: string[] = $idaa_loc.novi_admin_li ?? [];
        const trusted_li: string[] = $idaa_loc.novi_trusted_li ?? [];
        const is_trusted_uuid = user_id
            ? admin_li.includes(user_id) || trusted_li.includes(user_id)
            : false;
        if (is_trusted_uuid) {
            is_moderator = true;
            console.log(`Jitsi: User ${user_id} is moderator via admin/trusted UUID list.`);
        } else {
            // For regular authenticated members, check the specific meeting group.
            // Prefer g_uuid from URL (per-meeting, more precise); fall back to the global
            // novi_idaa_group_guid_li list for older Novi pages not yet passing g_uuid.
            const group_uuid = url_params.g_uuid ?? null;
            const group_guid_li = group_uuid
                ? [group_uuid]
                : ($ae_loc.site_cfg_json?.novi_idaa_group_guid_li ?? []);
            if (group_uuid) {
                console.log(`Jitsi: Checking moderator via URL g_uuid: ${group_uuid}`);
            } else {
                console.log(`Jitsi: No g_uuid in URL — falling back to site config group list (${group_guid_li.length} groups).`);
            }
            const moderatorIdSet = await get_novi_group_moderators(
                group_guid_li,
                novi_api_root_url,
                novi_api_key
            );
            const normalizedUserId = String(user_id ?? '')
                .toLowerCase()
                .trim();

            if (normalizedUserId && moderatorIdSet.has(normalizedUserId)) {
                is_moderator = true;
                console.log(`Jitsi: User ${user_id} is a moderator.`);
            } else {
                is_moderator = false;
                console.log(`Jitsi: User ${user_id} is not a moderator.`);
            }
        }
    } else {
        console.warn(
            'Jitsi: Novi API not configured. Using URL fallback for user details/moderator check.'
        );
    }

    // Set initial value for the profile editor inputs
    name_input = display_name ?? '';
    email_input = email ?? '';
}

/**
 * Dynamically loads the Jitsi external API script for a given domain and waits for it.
 * Using <svelte:head> for this script is not reliable — it loads asynchronously and there
 * is no lifecycle hook to await its completion, causing a race with onMount/init_jitsi.
 * Loading it here lets us sequence it correctly: fetch_novi_data → load script → init_jitsi.
 */
function load_jitsi_script(jitsi_domain: string): Promise<void> {
    return new Promise((resolve, reject) => {
        // @ts-expect-error — JitsiMeetExternalAPI is a global injected by the Jitsi script
        if (typeof JitsiMeetExternalAPI !== 'undefined') {
            resolve(); // Already loaded (e.g. resync after first load)
            return;
        }
        const script = document.createElement('script');
        script.src = `https://${jitsi_domain}/external_api.js`;
        script.onload = () => resolve();
        script.onerror = () => reject(new Error(`Failed to load Jitsi script from ${jitsi_domain}`));
        document.head.appendChild(script);
    });
}

async function handle_novi_resync() {
    console.log('Jitsi: Manually re-syncing Novi Data...');
    await fetch_novi_data();
    await load_jitsi_script(domain ?? 'jitsi.dgrzone.com');
    await init_jitsi();
}

onMount(async () => {
    if (log_lvl) {
        console.log(
            'Jitsi: onMount - fetching user data and initializing Jitsi...'
        );
    }

    await fetch_novi_data();

    // Defense-in-depth: the parent layout should have blocked unverified users,
    // but guard here as a second layer. For non-trusted users, the UUID in the URL
    // must match the UUID that was verified by the layout. Prevents joining Jitsi
    // with a fake UUID even if the layout guard is somehow bypassed.
    if (!$ae_loc.trusted_access && (!$idaa_loc.novi_verified || $idaa_loc.novi_uuid !== user_id)) {
        const container = document.getElementById(jitsi_container_id);
        if (container)
            container.innerHTML =
                '<p style="padding:1rem;color:red;font-weight:bold;">Access denied: Novi identity not verified for this meeting.</p>';
        console.error(
            `Jitsi: Aborting — UUID not verified or mismatch. verified=${$idaa_loc.novi_verified}, stored=${$idaa_loc.novi_uuid}, url=${user_id}`
        );
        return;
    }

    if (!domain) {
        console.error('Jitsi: domain not set after fetch_novi_data — cannot load Jitsi script.');
        return;
    }

    try {
        await load_jitsi_script(domain);
    } catch (err) {
        console.error('Jitsi: Failed to load external API script:', err);
        const container = document.getElementById(jitsi_container_id);
        if (container) container.innerHTML = '<h1>Jitsi API script failed to load. Please refresh the page.</h1>';
        return;
    }

    // --- All data fetched and script ready, now initialize Jitsi ---
    await init_jitsi();
});

onDestroy(() => {
    if (duration_timer_id) clearInterval(duration_timer_id);
    // No longer using reporting_timer_id
    if (jitsi_api) {
        console.log(
            'Jitsi: Disposing of Jitsi API instance on component destroy.'
        );
        jitsi_api.dispose();
        jitsi_api = null;
    }
});

/**
 * Fetches member details from the Novi API.
 * @param user_id - The Novi Customer GUID.
 * @param api_root_url - The root URL of the Novi API.
 * @param api_key - The API key for authorization.
 * @returns An object with the user's display name and email, or null if not found.
 */
async function get_novi_member_details(
    user_id: string,
    api_root_url: string,
    api_key: string
): Promise<{ display_name: string | null; email: string | null }> {
    if (!user_id || !api_root_url || !api_key) {
        console.error('get_novi_member_details: Missing required arguments.');
        return { display_name: null, email: null };
    }

    const headers = new Headers();
    headers.append('Authorization', `Basic ${api_key}`);
    const requestOptions = { method: 'GET', headers: headers };
    const url = `${api_root_url}/customers/${user_id}`;

    if (log_lvl) {
        console.log('Jitsi: Fetching Novi member details from:', url);
    }

    try {
        const response = await fetch(url, requestOptions);
        if (!response.ok) {
            throw new Error(
                `Novi API request failed with status ${response.status}`
            );
        }
        const result = await response.json();
        if (log_lvl > 1) {
            console.log(`Jitsi: Novi's Current User Obj:`, result);
        }

        let full_name = result?.Name;
        const first_name = result?.FirstName;
        const last_initial = result?.LastName
            ? `${result.LastName.charAt(0).toUpperCase()}.`
            : '';

        if (first_name && last_initial) {
            full_name = `${first_name} ${last_initial}`;
        }

        const novi_email = result?.Email
            ? result.Email.replace(/\s+/g, '+')
            : null;

        return { display_name: full_name, email: novi_email };
    } catch (error) {
        console.error('Error fetching Novi member details:', error);
        return { display_name: null, email: null };
    }
}

/**
 * Fetches a set of moderator IDs from Novi group(s).
 * @param group_guid_li - A list of Novi group GUIDs to check for moderators.
 * @param api_root_url - The root URL of the Novi API.
 * @param api_key - The API key for authorization.
 * @returns A Set of normalized (lowercase, trimmed) moderator UniqueIDs.
 */
async function get_novi_group_moderators(
    group_guid_li: string[],
    api_root_url: string,
    api_key: string
): Promise<Set<string>> {
    if (
        !group_guid_li ||
        group_guid_li.length === 0 ||
        !api_root_url ||
        !api_key
    ) {
        console.warn(
            'get_novi_group_moderators: Missing required arguments or empty group list.'
        );
        return new Set();
    }

    const headers = new Headers();
    headers.append('Authorization', `Basic ${api_key}`);
    const requestOptions = { method: 'GET', headers: headers };
    let allModeratorsRaw: any[] = [];

    console.log('[DEBUG] Novi Moderator Group Fetch:');
    console.log('  Group GUIDs:', group_guid_li);
    console.log('  Novi API Root URL:', api_root_url);

    for (const group_guid of group_guid_li) {
        const url = `${api_root_url}/groups/${group_guid}/members?pageSize=200`;
        console.log(`[DEBUG] Fetching moderator list for group:`, group_guid);
        console.log('  Request URL:', url);

        try {
            const response = await fetch(url, requestOptions);
            if (response.ok) {
                const result = await response.json();
                let groupModList: any[] = [];

                if (Array.isArray(result)) groupModList = result;
                else if (Array.isArray(result.Results))
                    groupModList = result.Results;
                else if (Array.isArray(result.Members))
                    groupModList = result.Members;
                else {
                    console.warn(
                        `[DEBUG] Moderator list format unexpected for group ${group_guid}. Raw result:`,
                        result
                    );
                }

                allModeratorsRaw = allModeratorsRaw.concat(groupModList);
                console.log(
                    `[DEBUG] Group ${group_guid}: fetched ${groupModList.length} moderators. Total so far: ${allModeratorsRaw.length}`
                );
                if (groupModList.length > 0) {
                    console.log(`[DEBUG] First 3 moderator objects:`, groupModList.slice(0, 3));
                }
            } else {
                console.warn(
                    `[DEBUG] Failed to fetch moderators for group ${group_guid}. Status: ${response.status}`
                );
            }
        } catch (error) {
            console.error(
                `[DEBUG] Error fetching moderators for group ${group_guid}:`,
                error
            );
        }
    }

    if (allModeratorsRaw.length === 0) {
        console.warn('[DEBUG] No moderators found across all specified Novi groups.');
    }

    console.log('[DEBUG] Combined moderator list count:', allModeratorsRaw.length);
    if (allModeratorsRaw.length > 0) {
        console.log('[DEBUG] Example moderator IDs:', allModeratorsRaw.slice(0, 5).map(m => m?.UniqueID || m?.UniqueId || m?.DuesPayerUniqueID || m?.id || ''));
    }

    const modIdSet = new Set(
        allModeratorsRaw
            .map(
                (m: any) =>
                    m?.UniqueID ??
                    m?.UniqueId ??
                    m?.DuesPayerUniqueID ??
                    m?.id ??
                    ''
            )
            .filter(Boolean)
            .map((id: string) => String(id).toLowerCase().trim())
    );
    console.log('[DEBUG] Final moderator ID set:', Array.from(modIdSet));
    return modIdSet;
}

/**
 * Initializes or re-initializes the Jitsi meeting.
 * This function handles fetching user/moderator data, getting JWTs, and configuring/loading the Jitsi external API.
 * If an existing Jitsi instance is present, it will be disposed of before creating a new one.
 */
async function init_jitsi() {
    // Clear stats and timers
    if (duration_timer_id) clearInterval(duration_timer_id);
    // No longer using reporting_timer_id
    meeting_participants.clear();
    meeting_start_time = null;
    meeting_duration = '00:00:00';

    // Dispose of any existing Jitsi instance to allow for a clean restart
    if (jitsi_api) {
        console.log(
            'Jitsi: Disposing of existing Jitsi API instance before re-initialization.'
        );
        jitsi_api.dispose();
        jitsi_api = null;
    }

    // --- Check for Jitsi API script ---
    // @ts-ignore
    if (typeof JitsiMeetExternalAPI === 'undefined') {
        console.error('Jitsi: JitsiMeetExternalAPI script not loaded yet.');
        const container = document.getElementById(jitsi_container_id);
        if (container) {
            container.innerHTML =
                '<h1>Jitsi API script not loaded. Please refresh the page.</h1>';
        }
        return;
    }

    console.log('Jitsi: Initializing Jitsi meeting interface...');

    // These variables are now expected to be set in the component's state
    if (!user_id || !display_name || !email || !room_name || !domain) {
        console.error(
            'Jitsi: Missing required data to initialize meeting. Aborting.'
        );
        const container = document.getElementById(jitsi_container_id);
        if (container) {
            container.innerHTML =
                '<h1>Could not initialize meeting: missing user data.</h1>';
        }
        return;
    }

    const url_params = data.params;

    // --- Initialize Jitsi ---
    // Issue JWT to all verified Novi users. The token's moderator flag controls
    // whether the user gets host privileges in the Jitsi room.
    console.log(`Jitsi: Attempting to get JWT (is_moderator=${is_moderator})...`);
    const jwt_token = await get_jitsi_jwt(
        display_name,
        email,
        is_moderator,
        room_name,
        user_id
    );
    if (!jwt_token) {
        const container = document.getElementById(jitsi_container_id);
        if (container)
            container.innerHTML =
                '<h1>Authentication Failed. Please try again.</h1>';
        console.error('Jitsi: Authentication failed. JWT not received.');
        return;
    }
    console.log('Jitsi: Successfully received JWT.');
    try {
        // Decode JWT payload (base64url → JSON) for debug verification.
        // This does NOT verify the signature — for logging only.
        const jwt_payload = JSON.parse(atob(jwt_token.split('.')[1].replace(/-/g, '+').replace(/_/g, '/')));
        console.log('%cJitsi JWT decoded payload:', 'font-weight:bold;color:#4f8ef7', jwt_payload);
        console.log(`Jitsi JWT: user=${jwt_payload?.context?.user?.name ?? jwt_payload?.name}, moderator=${jwt_payload?.context?.user?.moderator ?? jwt_payload?.user?.moderator}, room=${jwt_payload?.room ?? jwt_payload?.context?.room}`);
    } catch (e) {
        console.warn('Jitsi: Could not decode JWT payload for debug logging.', e);
    }

    const disabled_sounds = [
        disable_incoming_msg_sound ? 'INCOMING_MSG_SOUND' : null,
        disable_participant_joined_sound ? 'PARTICIPANT_JOINED_SOUND' : null,
        disable_participant_left_sound ? 'PARTICIPANT_LEFT_SOUND' : null,
        disable_reaction_sound ? 'REACTION_SOUND' : null,
        disable_raise_hand_sound ? 'RAISE_HAND_SOUND' : null
    ].filter((sound) => sound);

    const options = {
        roomName: room_name,
        width: '100%',
        height: '100%',
        parentNode: document.getElementById(jitsi_container_id),
        userInfo: { displayName: display_name, email: email },
        configOverwrite: {
            prejoinPageEnabled: false,
            startWithAudioMuted: true,
            startWithVideoMuted: true,
            enableLobby: is_moderator,
            disableReactionsModeration: false,
            disabledSounds: disabled_sounds,
            // Explicit toolbar whitelist — omitting 'embedmeeting' entirely.
            // "Embed Meeting" exposes the Jitsi host/room URL and must never appear
            // for IDAA users (authenticated or not) — privacy requirement.
            // 'invite' is restricted to trusted_access (IDAA staff) only — regular member
            // moderators are excluded because the invite dialog can expose backend paths
            // and room URLs that should not be visible to general members.
            toolbarButtons: [
                'camera', 'chat', 'closedcaptions', 'desktop', 'download',
                'etherpad', 'feedback', 'filmstrip', 'fullscreen', 'hangup',
                'help', ...($ae_loc.trusted_access ? ['invite'] : []), 'livestreaming', 'microphone',
                'mute-everyone', 'mute-video-everyone', 'participants-pane',
                'profile', 'raisehand', 'recording', 'security',
                'select-background', 'settings', 'shareaudio', 'sharedvideo',
                'shortcuts', 'stats', 'tileview', 'toggle-camera', 'videoquality'
            ]
        },
        interfaceConfigOverwrite: {
            DISABLE_JOIN_LEAVE_NOTIFICATIONS: true,
            NOTIFICATION_SOUND_URL: ''
        },
        jwt: jwt_token
    };

    console.log(
        'Jitsi: Initializing JitsiMeetExternalAPI with options:',
        options
    );
    // @ts-ignore
    jitsi_api = new JitsiMeetExternalAPI(domain, options);
    add_jitsi_event_listeners(jitsi_api);
    console.log('Jitsi: JitsiMeetExternalAPI initialized:', jitsi_api);
}
</script>


{#if show_jitsi_container}
    <div id={jitsi_container_id} class="jitsi-container"></div>
{/if}

{#if show_jitsi_tools && $ae_loc.edit_mode}
    <div
        class:bg-gray-400={expand_jitsi_tools}
        class="jitsi-tools max-w-xl text-sm">
        {#if expand_jitsi_tools}
            <!-- NOTE: This is a <div> instead of <header> to work with IDAA's Novi styles. -->
            <div
                class="flex w-full flex-row items-center justify-between gap-1">
                <!-- NOTE: This is a <div> instead of <h1> to work with IDAA's Novi styles. -->
                <div class="w-fit text-base">
                    IDAA's Jitsi
                    {#if display_name && email}
                        <span class="rounded bg-amber-50 px-1 py-0.5 font-bold"
                            >{display_name} ({email})</span>
                    {/if}
                </div>
                <button
                    type="button"
                    class="rounded bg-red-200 px-2 py-1 text-white hover:bg-red-400"
                    onclick={() => (expand_jitsi_tools = false)}
                    title="Close Jitsi Tools">
                    <span class="fas fa-times" aria-hidden="true"></span>
                    <span class="sr-only">Close Jitsi Tools</span>
                </button>
            </div>
            <!-- Meeting Details -->
            <div class="mt-1">
                <button
                    type="button"
                    onclick={() =>
                        (show_meeting_details = !show_meeting_details)}
                    class="flex w-full items-center justify-between text-left font-bold">
                    Meeting Details
                    <span
                        class="fas {show_meeting_details
                            ? 'fa-chevron-up'
                            : 'fa-chevron-down'}"></span>
                </button>
                {#if show_meeting_details}
                    <ul class="mt-1 pl-2">
                        <!-- {#if display_name && email}
					<li class="bg-amber-50">{display_name} ({email})</li>
				{/if} -->
                        <li>
                            Room: <span class="font-mono text-sm font-bold"
                                >{room_name}</span>
                        </li>
                        <li>
                            Domain: <span class="font-mono text-sm"
                                >{domain}</span>
                        </li>
                        <li>
                            User ID: <span class="font-mono text-sm"
                                >{user_id}</span>
                        </li>
                        <li>Moderator: {is_moderator ? 'Yes' : 'No'}</li>
                    </ul>
                {/if}
            </div>

            <!-- Live Meeting Stats -->
            <div class="mt-2 border-t-2 border-dashed border-gray-400 pt-2">
                <button
                    type="button"
                    onclick={() => (show_live_stats = !show_live_stats)}
                    class="flex w-full items-center justify-between text-left font-bold">
                    Live Meeting Stats
                    <span
                        class="fas {show_live_stats
                            ? 'fa-chevron-up'
                            : 'fa-chevron-down'}"></span>
                </button>
                {#if show_live_stats}
                    <div class="mt-1 pl-2">
                        <p>
                            Duration: <span class="font-mono"
                                >{meeting_duration}</span>
                        </p>
                        <p>Total Participants: {meeting_participants.size}</p>
                        <div class="mt-1">
                            <div class="font-bold">Moderators:</div>
                            <ul class="list-disc pl-4">
                                {#each Array.from(meeting_participants.values()).filter((p) => p.role === 'moderator') as mod (mod.id)}
                                    <li>{mod.displayName}</li>
                                {:else}
                                    <li class="text-gray-500 italic">None</li>
                                {/each}
                            </ul>
                        </div>
                        <div class="mt-1">
                            <div class="font-bold">Participants:</div>
                            <ul class="list-disc pl-4">
                                {#each Array.from(meeting_participants.values()).filter((p) => p.role !== 'moderator') as person (person.id)}
                                    <li>{person.displayName}</li>
                                {:else}
                                    <li class="text-gray-500 italic">None</li>
                                {/each}
                            </ul>
                        </div>
                    </div>
                {/if}
            </div>

            <!-- Profile -->
            <div class="mt-2 border-t-2 border-dashed border-gray-400 pt-2">
                <button
                    type="button"
                    onclick={() => (show_profile_editor = !show_profile_editor)}
                    class="flex w-full items-center justify-between text-left font-bold">
                    Profile
                    <span
                        class="fas {show_profile_editor
                            ? 'fa-chevron-up'
                            : 'fa-chevron-down'}"></span>
                </button>
                {#if show_profile_editor}
                    <div class="mt-1 space-y-2 pl-2">
                        <div>
                            <label
                                for="display_name_input"
                                class="block font-semibold">Name:</label>
                            <input
                                type="text"
                                id="display_name_input"
                                bind:value={name_input}
                                class="w-full rounded border px-2 py-1"
                                placeholder="Enter new display name" />
                        </div>
                        <div>
                            <label for="email_input" class="block font-semibold"
                                >Email:</label>
                            <input
                                type="email"
                                id="email_input"
                                bind:value={email_input}
                                class="w-full rounded border px-2 py-1"
                                placeholder="Enter new email" />
                        </div>
                        <button
                            type="button"
                            onclick={handle_profile_update}
                            class="mt-1 w-full rounded bg-green-500 px-2 py-1 text-white hover:bg-green-600"
                            title="Update your profile">
                            <span class="fas fa-user-edit" aria-hidden="true"
                            ></span>
                            Update Profile
                        </button>
                    </div>
                {/if}
            </div>

            <!-- Jitsi Sound Settings -->
            <div class="mt-2 border-t-2 border-dashed border-gray-400 pt-2">
                <button
                    type="button"
                    onclick={() => (show_sound_settings = !show_sound_settings)}
                    class="flex w-full items-center justify-between text-left font-bold">
                    Sound Settings
                    <span
                        class="fas {show_sound_settings
                            ? 'fa-chevron-up'
                            : 'fa-chevron-down'}"></span>
                </button>
                {#if show_sound_settings}
                    <div class="mt-1 flex flex-col pl-2">
                        <!-- WARNING: Fully disables the Play sound on option in the Jitsi > Settings > Notifications tab. -->
                        <label class="mt-1 flex items-center space-x-2">
                            <input
                                type="checkbox"
                                bind:checked={disable_incoming_msg_sound}
                                disabled={!is_moderator}
                                onchange={init_jitsi} />
                            <span>Disable Incoming Message Sound</span>
                        </label>

                        <label class="mt-1 flex items-center space-x-2">
                            <input
                                type="checkbox"
                                bind:checked={disable_participant_joined_sound}
                                disabled={!is_moderator}
                                onchange={init_jitsi} />
                            <span>Disable Participant Joined Sound</span>
                        </label>

                        <label class="mt-1 flex items-center space-x-2">
                            <input
                                type="checkbox"
                                bind:checked={disable_participant_left_sound}
                                disabled={!is_moderator}
                                onchange={init_jitsi} />
                            <span>Disable Participant Left Sound</span>
                        </label>

                        <!-- FUTURE: Under Notifications: Talk while muted -->
                        <!-- FUTURE: Under Notifications: Participant entered lobby -->

                        <!--
				FUTURE: Under Moderator:
				"Everyone starts muted"
				"Everyone starts hidden"
				"Everyone follows me"
				"Recorder follows me"
				-->

                        <!-- WARNING: This does not seem to work. It should be unchecking Moderator options under Jitsi > Settings > Moderator tab. -->
                        <!-- NOTE: This does not seem to work as expected. It does not seem to do anything at all? -->
                        <label class="mt-1 flex items-center space-x-2">
                            <input
                                type="checkbox"
                                bind:checked={disable_reaction_sound}
                                disabled={!is_moderator}
                                onchange={init_jitsi} />
                            <!-- Full text from Jitsi Settings popup: "Mute reaction sounds for everyone" -->
                            <span>Disable Reaction Sound</span>
                        </label>

                        <!-- WARNING: What does this correspond to in the Jitsi settings? -->
                        <!-- NOTE: This does not seem to work as expected. It does not seem to do anything at all? -->
                        <label class="mt-1 flex items-center space-x-2">
                            <input
                                type="checkbox"
                                bind:checked={disable_raise_hand_sound}
                                disabled={!is_moderator}
                                onchange={init_jitsi} />
                            <span>Disable Raise Hand Sound</span>
                        </label>
                    </div>
                {/if}
            </div>

            <div
                class="
		mt-2 flex w-full flex-col flex-wrap items-center justify-center gap-1
		border-t-2 border-dashed border-gray-400 pt-2 sm:flex-row">
                <MyClipboard
                    value={encodeURI(breakout_url)}
                    btn_text="Copy Break-out Link"
                    btn_title="Copy a link to this meeting that can be opened outside of the Novi iframe."
                    btn_class="mt-2 px-2 py-1 bg-indigo-400 text-white rounded hover:bg-indigo-500"
                ></MyClipboard>

                <button
                    type="button"
                    class="mt-2 rounded bg-orange-200 px-2 py-1 text-white hover:bg-orange-400"
                    onclick={() => init_jitsi()}
                    title="Re-initialize Jitsi Meeting">
                    <span class="fas fa-redo" aria-hidden="true"></span>
                    Re-initialize Jitsi
                </button>

                <!-- https://jitsi.github.io/handbook/docs/dev-guide/dev-guide-iframe-commands/ -->
                <button
                    type="button"
                    onclick={() =>
                        jitsi_api && jitsi_api.executeCommand('endConference')}
                    class="mt-2 rounded bg-red-200 px-2 py-1 text-white hover:bg-red-400"
                    title="End meeting for all participants">
                    <span class="fas fa-phone-slash" aria-hidden="true"></span>
                    End Meeting for *All*
                </button>

                <button
                    type="button"
                    onclick={() =>
                        (show_jitsi_container = !show_jitsi_container)}
                    class="mt-2 rounded bg-gray-200 px-2 py-1 text-white hover:bg-gray-400"
                    title="{show_jitsi_container
                        ? 'Hide'
                        : 'Show'} Jitsi Meeting">
                    {#if show_jitsi_container}
                        <span class="fas fa-video-slash" aria-hidden="true"
                        ></span>
                        Hide
                    {:else}
                        <span class="fas fa-video" aria-hidden="true"></span>
                        Show
                    {/if}
                    Jitsi Meeting
                    <span class="sr-only">Container</span>
                </button>

                <button
                    type="button"
                    class="mt-2 rounded bg-blue-200 px-2 py-1 text-white hover:bg-blue-400"
                    onclick={handle_novi_resync}
                    title="Re-synchronize Novi data">
                    <span class="fas fa-sync" aria-hidden="true"></span>
                    Re-sync Novi Data
                </button>
            </div>
        {:else}
            <button
                type="button"
                class="rounded bg-red-200 px-2 py-1 text-white hover:bg-red-400"
                onclick={() => (expand_jitsi_tools = true)}
                aria-label="Open Jitsi Tools"
                title="Open Jitsi tools and settings for IDAA Jitsi meetings">
                <span class="fas fa-tools" aria-hidden="true"></span>
                <span class="sr-only">Open Jitsi Tools</span>
            </button>
        {/if}
    </div>
{/if}

<!-- Breakout button: only shown in iframe mode — pointless outside an iframe since the user is
     already in a full tab. WHY: Novi iframes squish the layout and scrolling is unreliable.
     Members need a way to escape to a proper full-tab context. -->
{#if $ae_loc.iframe || $ae_loc.edit_mode}
<div class="fixed bottom-0.5 left-8 z-20 flex gap-2 print:hidden">
    <button
        type="button"
        onclick={() => (show_breakout_modal = true)}
        class="flex items-center gap-2 rounded-lg border border-gray-300 bg-white/90 px-3 py-2 text-sm shadow-md backdrop-blur-sm hover:bg-white"
        title="Open this meeting outside the Novi iframe">
        <span class="fas fa-external-link-alt" aria-hidden="true"></span>
        Open Externally
    </button>
    {#if $ae_loc.trusted_access}
        <a
            href={build_jitsi_reports_href()}
            class="flex items-center gap-2 rounded-lg border border-gray-300 bg-white/90 px-3 py-2 text-sm shadow-md backdrop-blur-sm hover:bg-white"
            title="Open Jitsi Reports">
            <span class="fas fa-chart-bar" aria-hidden="true"></span>
            Jitsi Reports
        </a>
    {/if}
</div>
{/if}

{#if show_breakout_modal}
    <div
        class="fixed inset-0 z-50 flex items-center justify-center bg-black/50 print:hidden"
        onclick={() => (show_breakout_modal = false)}
        onkeydown={(e) => e.key === 'Escape' && (show_breakout_modal = false)}
        role="button"
        tabindex="-1"
        aria-label="Close meeting link dialog">
        <div
            class="mx-4 w-full max-w-sm rounded-xl bg-white px-6 py-2 shadow-2xl"
            role="dialog"
            aria-modal="true"
            aria-labelledby="breakout_modal_title"
            tabindex="-1"
            onclick={(e) => e.stopPropagation()}
            onkeydown={(e) => e.stopPropagation()}>
            <div class="mb-2 flex items-start justify-between">
                <div>
                    <h2 id="breakout_modal_title" class="text-base font-bold text-center">
                        Open Meeting Externally
                    </h2>
                    <p class="mt-1 text-sm text-gray-500">
                        Open this meeting outside the embedded iframe on this page.
                    </p>
                </div>
                <button
                    type="button"
                    onclick={() => (show_breakout_modal = false)}
                    class="ml-4 shrink-0 rounded p-1 text-gray-400 hover:bg-gray-100 hover:text-gray-600"
                    aria-label="Close">
                    <span class="fas fa-times" aria-hidden="true"></span>
                </button>
            </div>
            <div class="flex flex-col gap-2">
                <button
                    type="button"
                    onclick={copy_meeting_link}
                    class="flex items-center justify-center gap-2 rounded-lg border px-4 py-2 text-sm font-medium transition-colors {breakout_link_copied
                        ? 'border-green-300 bg-green-50 text-green-700'
                        : 'border-gray-300 bg-white hover:bg-gray-50'}">
                    <span
                        class="fas {breakout_link_copied ? 'fa-check' : 'fa-copy'}"
                        aria-hidden="true"></span>
                    {breakout_link_copied ? 'Copied!' : 'Copy Link'}
                </button>
                <a
                    href={breakout_url}
                    target="_blank"
                    rel="noopener noreferrer"
                    onclick={() => (show_breakout_modal = false)}
                    class="flex items-center justify-center gap-2 rounded-lg border border-indigo-300 bg-indigo-50 px-4 py-2 text-sm font-medium text-indigo-700 hover:bg-indigo-100">
                    <span class="fas fa-external-link-alt" aria-hidden="true"></span>
                    Open in New Tab
                </a>
                <!-- Fallback for browsers that block clipboard access (common in iframes).
                     User can manually select all and copy from here. -->
                <div class="mt-0.5">
                    <p class="mb-0.5 text-xs text-gray-400">Or copy the link manually:</p>
                    <textarea
                        readonly
                        rows="3"
                        value={breakout_url}
                        onclick={(e) => (e.target as HTMLTextAreaElement).select()}
                        class="w-full cursor-text resize-none rounded border border-gray-200 bg-gray-50 px-2 py-1.5 font-mono text-xs text-gray-700 focus:outline-none"
                        title="Click to select all, then copy (ctrl/cmd + C)"
                        aria-label="Meeting link — click to select all"></textarea>
                </div>
            </div>
        </div>
    </div>
{/if}

<style>
.jitsi-container {
    height: 100vh;
    width: 100vw;
    position: absolute;
    top: 0;
    left: 0;
    overflow: hidden;
    padding-bottom: 3em; /* Space for the tools button */
}

.jitsi-tools {
    z-index: 10;
    position: fixed;
    bottom: 10em;
    right: 20px;
    /* background-color: rgba(255, 255, 255, 0.8); */
    padding: 10px;
    border-radius: 8px;
    box-shadow: 0 2px 8px rgba(0, 0, 0, 0.2);
}
</style>
