<script lang="ts">
  import { Button } from "$lib/components/ui/button";
  import { Input } from "$lib/components/ui/input";
  import * as Dialog from "$lib/components/ui/dialog";

  let {
    open = $bindable(false),
    onResult,
    title = "Scan barcode",
  }: {
    open?: boolean;
    onResult?: (value: string) => void;
    title?: string;
  } = $props();

  let scanError = $state<string | null>(null);
  let searchInput = $state("");
  let videoEl = $state<HTMLVideoElement | undefined>(undefined);
  let stream = $state<MediaStream | null>(null);
  let scanRAF = $state(0);

  function submitSearch() {
    const value = searchInput.trim();
    if (!value) return;
    stopScan();
    open = false;
    searchInput = "";
    onResult?.(value);
  }

  async function ensureBarcodeDetector(): Promise<boolean> {
    if (typeof globalThis === "undefined" || "BarcodeDetector" in globalThis) return true;
    try {
      const mod = await import("barcode-detector-api-polyfill");
      (globalThis as unknown as { BarcodeDetector: typeof BarcodeDetector }).BarcodeDetector =
        mod.BarcodeDetector;
      return true;
    } catch {
      return false;
    }
  }

  async function startScan() {
    scanError = null;
    const hasDetector = await ensureBarcodeDetector();
    if (!hasDetector) {
      scanError = "Failed to load barcode scanner. Try again or use Chrome/Edge.";
      return;
    }
    try {
      stream = await navigator.mediaDevices.getUserMedia({
        video: { facingMode: "environment", width: { ideal: 1280 }, height: { ideal: 720 } },
      });
    } catch (e) {
      scanError = e instanceof Error ? e.message : "Could not access camera.";
    }
  }

  function stopScan() {
    if (scanRAF) {
      cancelAnimationFrame(scanRAF);
      scanRAF = 0;
    }
    if (stream) {
      stream.getTracks().forEach((t) => t.stop());
      stream = null;
    }
    if (videoEl?.srcObject) {
      videoEl.srcObject = null;
    }
    scanError = null;
  }

  $effect(() => {
    if (!open) {
      stopScan();
      searchInput = "";
      return;
    }
    startScan();
    return () => stopScan();
  });

  $effect(() => {
    if (!open || !stream || !videoEl) return;
    videoEl.srcObject = stream;
    videoEl.play().catch(() => {});
    return () => {
      if (videoEl?.srcObject) videoEl.srcObject = null;
    };
  });

  const SCAN_INTERVAL_MS = 300;

  function scanLoop() {
    if (!videoEl || !("BarcodeDetector" in globalThis)) return;
    const detector = new (globalThis as unknown as { BarcodeDetector: typeof BarcodeDetector }).BarcodeDetector();
    let lastScan = 0;
    async function detect() {
      if (!open || !videoEl) return;
      if (videoEl.readyState < 2) {
        scanRAF = requestAnimationFrame(detect);
        return;
      }
      const now = Date.now();
      if (now - lastScan >= SCAN_INTERVAL_MS) {
        lastScan = now;
        try {
          const barcodes = await detector.detect(videoEl);
          if (barcodes.length > 0) {
            const value = barcodes[0].rawValue?.trim() ?? "";
            stopScan();
            open = false;
            onResult?.(value);
            return;
          }
        } catch {
          // ignore
        }
      }
      scanRAF = requestAnimationFrame(detect);
    }
    detect();
  }

  $effect(() => {
    if (!open || !stream || !videoEl) return;
    scanLoop();
    return () => {
      if (scanRAF) cancelAnimationFrame(scanRAF);
    };
  });
</script>

<Dialog.Root bind:open>
  <Dialog.Content
    class="flex max-h-[100dvh] w-[100vw] max-w-none flex-col gap-4 rounded-none border-0 p-4 sm:max-h-[90dvh] sm:w-auto sm:max-w-md sm:rounded-lg sm:border"
    onclose={() => stopScan()}
  >
    <Dialog.Header class="shrink-0">
      <Dialog.Title>{title}</Dialog.Title>
      <Dialog.Description>Scan a barcode or type a code to look up an item.</Dialog.Description>
    </Dialog.Header>
    <div class="flex min-h-0 flex-1 flex-col gap-4">
      <div class="flex shrink-0 gap-2">
        <Input
          type="text"
          placeholder="Type barcode or SKU to search…"
          bind:value={searchInput}
          onkeydown={(e) => e.key === "Enter" && submitSearch()}
          class="min-h-11 flex-1"
        />
        <Button type="button" class="min-h-11 shrink-0" onclick={submitSearch} disabled={!searchInput.trim()}>
          Search
        </Button>
      </div>
      <p class="text-muted-foreground shrink-0 text-center text-xs">or use camera below</p>
      {#if scanError}
        <p class="text-destructive text-sm">{scanError}</p>
      {:else if stream}
        <video
          bind:this={videoEl}
          class="border-input max-h-[50vh] w-full flex-1 rounded-lg border object-cover sm:max-h-none"
          muted
          playsinline
        ></video>
      {:else}
        <p class="text-muted-foreground text-sm">Starting camera…</p>
      {/if}
      <div class="flex shrink-0 gap-2">
        <Button
          type="button"
          variant="outline"
          class="min-h-12 flex-1"
          onclick={() => { stopScan(); open = false; }}
        >
          Cancel
        </Button>
      </div>
    </div>
  </Dialog.Content>
</Dialog.Root>
