<script lang="ts">
/**
 * src/routes/events/[event_id]/(leads)/leads/exhibit/[exhibit_id]/ae_comp__lead_qr_scanner_multi.svelte
 * Multi-badge batch QR scanner.
 *
 * Uses the native BarcodeDetector API (Chrome/Edge/Safari 17+) to detect multiple
 * QR codes in a single camera frame. Staff lay 1–4 badges flat in view, tap
 * "Capture Batch", and a grid of confirm cards appears — one per detected badge.
 * Cards can be individually added, skipped, or dismissed; "Add All" handles the batch.
 *
 * Hard cap: 8 badges per batch (browser/camera support varies; 4 is the practical ask).
 *
 * Firefox: BarcodeDetector not yet supported — shows an informative fallback.
 */
import { onDestroy } from 'svelte';
import { page } from '$app/state';
import { liveQuery } from 'dexie';
import { db_events } from '$lib/ae_events/db_events';
import { ae_api, ae_loc } from '$lib/stores/ae_stores';
import { leads_loc } from '$lib/stores/ae_events_stores__leads.svelte';
import { events_func } from '$lib/ae_events/ae_events_functions';
import { ae_util } from '$lib/ae_utils/ae_utils';
import type { ae_EventBadge } from '$lib/types/ae_types';
import { SvelteMap } from 'svelte/reactivity';
import {
    CircleCheck,
    Eye,
    Layers,
    LoaderCircle,
    RefreshCw,
    RotateCcw,
    ScanLine,
    ShieldOff,
    UserPlus,
    X
} from '@lucide/svelte';

interface Props {
    exhibit_id: string;
    on_lead_added?: (badge: ae_EventBadge) => void;
}

let { exhibit_id, on_lead_added }: Props = $props();

// BarcodeDetector is in Chrome/Edge/Safari 17+; not yet in Firefox.
// Check at runtime — TypeScript lib.dom.d.ts may not have it yet.
const is_supported =
    typeof window !== 'undefined' && 'BarcodeDetector' in window;

// --- Types ---
type BatchStatus =
    | 'loading'
    | 'ready'
    | 'blocked'
    | 'already_added'
    | 'reenable'
    | 'adding'
    | 'added'
    | 'error';

interface BatchItem {
    id: string; // badge id_random from QR
    badge: ae_EventBadge | null;
    status: BatchStatus;
    existing_tracking_id: string; // set when status === 'already_added'
    dismissing: boolean; // true while CSS fade-out plays
}

// --- Existing leads (duplicate detection) ---
// Value includes tracking_id AND enabled status so we can offer re-activation for removed leads.
let existing_leads_map = $derived(
    liveQuery(async () => {
        const leads = await db_events.exhibit_tracking
            .where('event_exhibit_id')
            .equals(exhibit_id)
            .toArray();
        const map = new SvelteMap<
            string,
            { tracking_id: string; enabled: boolean }
        >();
        leads.forEach((l) => {
            const b_id = l.event_badge_id?.toString();
            if (b_id)
                map.set(b_id, {
                    tracking_id: l.event_exhibit_tracking_id?.toString() || '',
                    enabled: !!l.enable
                });
        });
        return map;
    })
);

// --- Camera ---
let video_el = $state<HTMLVideoElement | undefined>(undefined);
let stream: MediaStream | null = null;
let detector: any = null;
let camera_status = $state<
    'idle' | 'starting' | 'live' | 'capturing' | 'error'
>('idle');
let camera_error = $state('');

// Start camera when the video element mounts
$effect(() => {
    if (!video_el || !is_supported) return;
    start_camera();
    return () => stop_camera();
});

onDestroy(stop_camera);

async function start_camera() {
    if (camera_status !== 'idle') return;
    camera_status = 'starting';
    try {
        stream = await navigator.mediaDevices.getUserMedia({
            video: {
                facingMode: 'environment',
                width: { ideal: 1920 },
                height: { ideal: 1080 }
            }
        });
        if (!video_el) {
            stop_camera();
            return;
        }
        video_el.srcObject = stream;
        await video_el.play();
        // BarcodeDetector API — not yet typed in lib.dom.d.ts for all targets
        detector = new (window as any).BarcodeDetector({
            formats: ['qr_code']
        });
        camera_status = 'live';
    } catch (e: any) {
        camera_status = 'error';
        camera_error =
            e?.name === 'NotAllowedError'
                ? 'Camera access denied. Allow camera access and try again.'
                : 'Could not start camera. Please try again.';
    }
}

function stop_camera() {
    stream?.getTracks().forEach((t) => t.stop());
    stream = null;
    detector = null;
    if (camera_status !== 'error') camera_status = 'idle';
}

async function retry_camera() {
    camera_status = 'idle';
    await start_camera();
}

// --- Batch ---
let batch = $state<BatchItem[]>([]);
let ready_count = $derived(
    batch.filter((i) => i.status === 'ready' && !i.dismissing).length
);

async function capture_batch() {
    if (!detector || !video_el || camera_status !== 'live') return;
    camera_status = 'capturing';

    try {
        const barcodes: Array<{ rawValue: string }> =
            await detector.detect(video_el);
        const existing_ids = new Set(batch.map((i) => i.id));

        const new_objs = barcodes
            .map((b) => ae_util.process_data_string(b.rawValue))
            .filter(
                (obj): obj is { type: string; id: string } =>
                    !!(obj && obj.type === 'event_badge' && obj.id)
            )
            .filter((obj) => !existing_ids.has(obj.id))
            .slice(0, Math.max(0, 8 - batch.length)); // hard cap at 8 total

        for (const obj of new_objs) {
            const item: BatchItem = {
                id: obj.id,
                badge: null,
                status: 'loading',
                existing_tracking_id: '',
                dismissing: false
            };
            batch.push(item);
            load_badge(item); // fire-and-forget; updates item in place
        }
    } catch (e) {
        console.error('[Multi] BarcodeDetector.detect failed:', e);
    }

    camera_status = 'live';
}

async function load_badge(item: BatchItem) {
    try {
        const badge = await events_func.load_ae_obj_id__event_badge({
            api_cfg: $ae_api,
            event_badge_id: item.id,
            log_lvl: 1
        });
        item.badge = badge;

        if ($existing_leads_map?.has(item.id)) {
            const existing = $existing_leads_map.get(item.id)!;
            item.existing_tracking_id = existing.tracking_id;
            // Distinguish: active lead vs previously-removed lead
            item.status = existing.enabled ? 'already_added' : 'reenable';
        } else if (badge?.allow_tracking !== true) {
            // Attendee has opted out — show card so staff can inform them
            item.status = 'blocked';
        } else {
            item.status = 'ready';
        }
    } catch {
        item.status = 'error';
    }
}

async function add_lead(item: BatchItem) {
    if (item.status !== 'ready' || !item.badge?.event_badge_id) return;
    item.status = 'adding';

    const kv = leads_loc.current.auth_exhibit_kv?.[exhibit_id];
    const user_email =
        kv?.type === 'licensed' && kv.key
            ? kv.key
            : kv?.type === 'shared'
              ? 'shared_passcode'
              : $ae_loc.access_type || 'anonymous';
    try {
        const result = await events_func.create_ae_obj__exhibit_tracking({
            api_cfg: $ae_api,
            exhibit_id,
            event_badge_id: item.badge.event_badge_id,
            external_person_id: user_email,
            group: user_email
        });
        if (result) {
            item.status = 'added';
            if (on_lead_added) on_lead_added(item.badge);
            // Brief success display, then fade out
            setTimeout(() => dismiss_item(item), 1000);
        } else {
            // API returned false/null — network error, API down, or auth failure.
            item.status = 'error';
        }
    } catch {
        item.status = 'error';
    }
}

async function reenable_lead(item: BatchItem) {
    if (!item.existing_tracking_id) return;
    item.status = 'adding';

    try {
        const result = await events_func.update_ae_obj__exhibit_tracking({
            api_cfg: $ae_api,
            exhibit_id,
            exhibit_tracking_id: item.existing_tracking_id,
            data: { enable: true }
        });
        if (result) {
            item.status = 'added';
            if (on_lead_added && item.badge) on_lead_added(item.badge);
            setTimeout(() => dismiss_item(item), 1000);
        } else {
            item.status = 'error';
        }
    } catch {
        item.status = 'error';
    }
}

async function add_all() {
    const to_add = batch.filter((i) => i.status === 'ready' && !i.dismissing);
    await Promise.all(to_add.map(add_lead));
}

function dismiss_item(item: BatchItem) {
    item.dismissing = true;
    // Remove from array after CSS transition completes (300ms)
    setTimeout(() => {
        const idx = batch.findIndex((i) => i.id === item.id);
        if (idx >= 0) batch.splice(idx, 1);
    }, 350);
}
</script>

<div class="multi-scanner flex w-full flex-col items-center gap-4">
    {#if !is_supported}
        <!-- Firefox / older browser fallback -->
        <div
            class="card preset-tonal-warning border-warning-500 w-full max-w-md space-y-3 border-2 p-6 text-center">
            <Layers size="2.5em" class="text-warning-500 mx-auto" />
            <h3 class="h4 font-bold">Multi-Scan Not Available</h3>
            <p class="text-sm opacity-70">
                Multi-scan uses the browser's BarcodeDetector API, which is
                supported in Chrome, Edge, and Safari 17+. Firefox support is
                coming soon.
            </p>
            <p class="text-sm opacity-70">
                Use <strong>Rapid</strong> or <strong>Auto</strong> mode in the meantime.
            </p>
        </div>
    {:else}
        <!-- Camera viewfinder — landscape 16:9 gives more horizontal coverage for multiple badges -->
        <div
            class="border-surface-500/20 relative aspect-video w-full max-w-md overflow-hidden rounded-xl border-4 bg-black shadow-xl">
            <!-- svelte-ignore a11y_media_has_caption -->
            <video
                bind:this={video_el}
                class="h-full w-full object-cover"
                playsinline
                muted></video>

            <!-- Hint overlay: shown while camera is live, styled like a check-deposit scanner guide -->
            {#if camera_status === 'live'}
                <div
                    class="pointer-events-none absolute inset-0 flex flex-col items-center justify-end px-4 pb-3">
                    <span
                        class="rounded-full bg-black/50 px-3 py-1.5 text-center text-xs font-semibold tracking-wide text-white">
                        Align up to 4 badges flat in frame
                    </span>
                </div>
                <!-- Corner guides — visual aid for badge alignment -->
                <div class="pointer-events-none absolute inset-4">
                    <div
                        class="border-primary-400/70 absolute top-0 left-0 h-6 w-6 rounded-tl border-t-2 border-l-2">
                    </div>
                    <div
                        class="border-primary-400/70 absolute top-0 right-0 h-6 w-6 rounded-tr border-t-2 border-r-2">
                    </div>
                    <div
                        class="border-primary-400/70 absolute bottom-0 left-0 h-6 w-6 rounded-bl border-b-2 border-l-2">
                    </div>
                    <div
                        class="border-primary-400/70 absolute right-0 bottom-0 h-6 w-6 rounded-br border-r-2 border-b-2">
                    </div>
                </div>
            {/if}

            <!-- Starting overlay -->
            {#if camera_status === 'starting'}
                <div
                    class="absolute inset-0 flex items-center justify-center bg-black/40">
                    <span
                        class="animate-pulse rounded-full bg-black/60 px-4 py-2 text-sm font-semibold text-white shadow-lg">
                        Starting camera...
                    </span>
                </div>
            {/if}

            <!-- Error overlay -->
            {#if camera_status === 'error'}
                <div
                    class="absolute inset-0 flex flex-col items-center justify-center gap-4 rounded-xl bg-black/80 p-6 text-center">
                    <p
                        class="text-sm leading-snug font-semibold text-white drop-shadow">
                        {camera_error}
                    </p>
                    <button
                        type="button"
                        class="text-surface-950 hover:bg-surface-100 flex cursor-pointer items-center gap-2 rounded-xl bg-white px-8 py-3 text-base font-bold shadow-lg transition-colors"
                        onclick={retry_camera}>
                        <RefreshCw size="1.2em" />
                        Try Again
                    </button>
                </div>
            {/if}
        </div>

        <!-- Capture button -->
        {#if camera_status === 'live' || camera_status === 'capturing'}
            <button
                type="button"
                class="bg-primary-500 flex w-full max-w-md cursor-pointer items-center justify-center gap-2 rounded-xl py-4 text-base font-bold text-white shadow-md transition-all hover:brightness-110 active:brightness-90 disabled:opacity-50"
                disabled={camera_status === 'capturing'}
                onclick={capture_batch}>
                {#if camera_status === 'capturing'}
                    <LoaderCircle class="animate-spin" size="1.3em" />
                    Scanning...
                {:else}
                    <ScanLine size="1.3em" />
                    Capture Batch
                {/if}
            </button>
        {/if}

        <!-- Badge grid -->
        {#if batch.length > 0}
            <div class="grid w-full grid-cols-1 gap-3 sm:grid-cols-2">
                {#each batch as item (item.id)}
                    <div
                        class="batch-card card bg-surface-50-900 border-surface-500/20 min-h-28 space-y-3 border p-4 shadow"
                        class:dismissing={item.dismissing}>
                        {#if item.status === 'loading'}
                            <!-- Skeleton — fixed height prevents layout bounce as badges load -->
                            <div class="space-y-2">
                                <div
                                    class="bg-surface-200-800 h-5 w-3/4 animate-pulse rounded">
                                </div>
                                <div
                                    class="bg-surface-200-800 h-4 w-1/2 animate-pulse rounded">
                                </div>
                            </div>
                            <div
                                class="bg-surface-200-800 h-9 animate-pulse rounded-lg">
                            </div>
                        {:else if item.status === 'blocked'}
                            <!-- Tracking opt-out — show card so staff can inform the attendee -->
                            <div class="flex items-start gap-2">
                                <ShieldOff
                                    size="1.2em"
                                    class="text-warning-500 mt-0.5 shrink-0" />
                                <div>
                                    <p class="text-sm leading-tight font-bold">
                                        {item.badge?.full_name || 'Attendee'}
                                    </p>
                                    <p class="mt-0.5 text-xs opacity-60">
                                        Opted out of lead scanning
                                    </p>
                                </div>
                            </div>
                            <button
                                type="button"
                                class="border-warning-500/40 text-warning-600 dark:text-warning-400 hover:bg-warning-500/10 flex w-full cursor-pointer items-center justify-center gap-1.5 rounded-lg border py-2 text-sm font-bold transition-colors"
                                onclick={() => dismiss_item(item)}>
                                <X size="1em" />
                                OK, Dismiss
                            </button>
                        {:else if item.status === 'already_added'}
                            <div class="flex items-start gap-2">
                                <CircleCheck
                                    size="1.2em"
                                    class="text-secondary-500 mt-0.5 shrink-0" />
                                <div>
                                    <p class="text-sm leading-tight font-bold">
                                        {item.badge?.full_name || 'Attendee'}
                                    </p>
                                    <p class="mt-0.5 text-xs opacity-60">
                                        Already captured
                                    </p>
                                </div>
                            </div>
                            <div class="flex gap-2">
                                <a
                                    href={`/events/${page.params.event_id}/leads/exhibit/${exhibit_id}/lead/${item.existing_tracking_id}`}
                                    class="border-surface-500/40 hover:bg-surface-200-800 flex flex-1 items-center justify-center gap-1 rounded-lg border py-2 text-xs font-bold transition-colors">
                                    <Eye size="0.9em" />
                                    View
                                </a>
                                <button
                                    type="button"
                                    class="border-surface-500/40 hover:bg-surface-200-800 flex flex-1 cursor-pointer items-center justify-center gap-1 rounded-lg border py-2 text-xs font-bold transition-colors"
                                    onclick={() => dismiss_item(item)}>
                                    <X size="0.9em" />
                                    OK
                                </button>
                            </div>
                        {:else if item.status === 'reenable'}
                            <div class="flex items-start gap-2">
                                <RotateCcw
                                    size="1.2em"
                                    class="text-warning-500 mt-0.5 shrink-0" />
                                <div>
                                    <p class="text-sm leading-tight font-bold">
                                        {item.badge?.full_name || 'Attendee'}
                                    </p>
                                    <p class="mt-0.5 text-xs opacity-60">
                                        Previously removed
                                    </p>
                                </div>
                            </div>
                            <div class="flex gap-2">
                                <button
                                    type="button"
                                    class="bg-warning-500 flex flex-1 cursor-pointer items-center justify-center gap-1.5 rounded-lg py-2 text-xs font-bold text-white transition-all hover:brightness-110"
                                    onclick={() => reenable_lead(item)}>
                                    <RotateCcw size="0.9em" />
                                    Re-activate
                                </button>
                                <button
                                    type="button"
                                    class="border-surface-500/40 hover:bg-surface-200-800 flex flex-none cursor-pointer items-center justify-center rounded-lg border px-3 py-2 opacity-60 transition-colors"
                                    title="Skip"
                                    onclick={() => dismiss_item(item)}>
                                    <X size="1em" />
                                </button>
                            </div>
                        {:else if item.status === 'ready'}
                            <div>
                                <p class="text-sm leading-tight font-bold">
                                    {item.badge?.full_name || 'Badge Found'}
                                </p>
                                <p class="text-xs opacity-60">
                                    {item.badge?.affiliations || ''}
                                </p>
                            </div>
                            <div class="flex gap-2">
                                <button
                                    type="button"
                                    class="bg-primary-500 flex flex-1 cursor-pointer items-center justify-center gap-1.5 rounded-lg py-2.5 text-sm font-bold text-white transition-all hover:brightness-110"
                                    onclick={() => add_lead(item)}>
                                    <UserPlus size="1em" />
                                    Add
                                </button>
                                <button
                                    type="button"
                                    class="border-surface-500/40 hover:bg-surface-200-800 flex flex-none cursor-pointer items-center justify-center rounded-lg border px-3 py-2.5 opacity-60 transition-colors"
                                    title="Skip this badge"
                                    onclick={() => dismiss_item(item)}>
                                    <X size="1em" />
                                </button>
                            </div>
                        {:else if item.status === 'adding'}
                            <div
                                class="flex items-center gap-2 py-1 opacity-70">
                                <LoaderCircle
                                    size="1.2em"
                                    class="text-primary-500 shrink-0 animate-spin" />
                                <div>
                                    <p class="text-sm leading-tight font-bold">
                                        {item.badge?.full_name || '...'}
                                    </p>
                                    <p class="text-xs opacity-60">Adding...</p>
                                </div>
                            </div>
                        {:else if item.status === 'added'}
                            <div class="flex items-center gap-2 py-1">
                                <CircleCheck
                                    size="1.2em"
                                    class="text-success-500 shrink-0" />
                                <div>
                                    <p
                                        class="text-success-600 dark:text-success-400 text-sm leading-tight font-bold">
                                        {item.badge?.full_name || 'Lead'}
                                    </p>
                                    <p class="text-xs opacity-60">
                                        Lead added!
                                    </p>
                                </div>
                            </div>
                        {:else if item.status === 'error'}
                            <div>
                                <p
                                    class="text-error-600 dark:text-error-400 text-sm font-bold">
                                    Failed to add
                                </p>
                                <p class="text-xs opacity-60">
                                    {item.badge?.full_name || 'Unknown'}
                                </p>
                            </div>
                            <button
                                type="button"
                                class="border-error-500/40 text-error-600 dark:text-error-400 hover:bg-error-500/10 flex w-full cursor-pointer items-center justify-center gap-1.5 rounded-lg border py-2 text-xs font-bold transition-colors"
                                onclick={() => dismiss_item(item)}>
                                <X size="1em" />
                                Dismiss
                            </button>
                        {/if}
                    </div>
                {/each}
            </div>

            <!-- Add All — only shown when there are ready cards -->
            {#if ready_count > 0}
                <button
                    type="button"
                    class="bg-primary-500 flex w-full cursor-pointer items-center justify-center gap-2 rounded-xl py-4 text-base font-bold text-white shadow-md transition-all hover:brightness-110 active:brightness-90"
                    onclick={add_all}>
                    <UserPlus size="1.3em" />
                    Add All ({ready_count})
                </button>
            {/if}
        {/if}
    {/if}
</div>

<style>
/* Smooth fade-and-shrink when a card is dismissed (Add / Skip / OK).
       Duration matches the 350ms setTimeout in dismiss_item(). */
.batch-card {
    transition:
        opacity 0.3s ease,
        transform 0.3s ease;
}
.batch-card.dismissing {
    opacity: 0;
    transform: scale(0.9);
    pointer-events: none;
}
</style>
