// CameraCapture — inline webcam panel for the Floating AI.
// Two modes:
//   1. "scan"     — live barcode/QR decoder via @zxing/browser
//   2. "identify" — single-frame capture → POST base64 → GPT-4o Vision identifier
//
// No new module / route. Used as a child of AiFloatingChat.
//
// v2 fixes (2026-06-13):
//   - DO NOT pass {} as hints — zxing expects undefined or a real Map<DecodeHintType,any>.
//     Passing a plain object triggered "e.get is not a function" → black preview.
//   - Use IScannerControls.stop() (returned by decodeFromVideoDevice) for clean teardown.
//   - Permission deny → friendly retry button.
//   - Defensive null checks everywhere — never crash on early unmount / rebound.
//   - Debug logs (gated to dev) for init / permission / detection / close.
//   - Single-instance guard prevents duplicate readers if React StrictMode double-mounts.
import React, { useEffect, useRef, useState, useCallback } from "react";
import { Camera, X, RefreshCw, ScanBarcode, Sparkles, CheckCircle2, AlertTriangle, Plus, ShoppingCart, Truck } from "lucide-react";
import { BrowserMultiFormatReader } from "@zxing/browser";
import { api } from "@/lib/api";
import { toast } from "sonner";

const DEBUG = process.env.NODE_ENV !== "production";
const dbg = (...args) => { if (DEBUG) console.debug("[CameraCapture]", ...args); };

export default function CameraCapture({ onResult, onClose, defaultMode = "scan", companyId = null }) {
    const [mode, setMode] = useState(defaultMode);   // "scan" | "identify"
    const [error, setError] = useState("");
    const [permissionDenied, setPermissionDenied] = useState(false);
    const [busy, setBusy] = useState(false);
    const [lastCode, setLastCode] = useState("");
    const [aiResult, setAiResult] = useState(null);
    const [bootAttempt, setBootAttempt] = useState(0);     // bump to retry
    const videoRef = useRef(null);
    const readerRef = useRef(null);
    const controlsRef = useRef(null);     // IScannerControls returned by zxing
    const streamRef = useRef(null);
    const mountedRef = useRef(true);
    const handledCodeRef = useRef(null);  // prevent double-handling
    const lastSnapRef = useRef("");       // most recent captured dataUrl, for Draft/Sale/Purchase actions

    // Centralised teardown — safe to call multiple times
    const teardown = useCallback(() => {
        dbg("teardown");
        try {
            // 1) Stop zxing decoder via IScannerControls
            if (controlsRef.current && typeof controlsRef.current.stop === "function") {
                controlsRef.current.stop();
            }
        } catch (e) { dbg("controls.stop error", e?.message); }
        controlsRef.current = null;
        try {
            // 2) Defensive — older zxing builds expose .reset() on the reader
            if (readerRef.current && typeof readerRef.current.reset === "function") {
                readerRef.current.reset();
            }
        } catch (e) { dbg("reader.reset error", e?.message); }
        readerRef.current = null;
        try {
            // 3) Stop every underlying MediaStream track
            const stream = streamRef.current;
            if (stream && typeof stream.getTracks === "function") {
                stream.getTracks().forEach((t) => { try { t.stop(); } catch { /* noop */ } });
            }
        } catch (e) { dbg("stream stop error", e?.message); }
        streamRef.current = null;
        try {
            if (videoRef.current) videoRef.current.srcObject = null;
        } catch { /* noop */ }
    }, []);

    // Resolve a scanned code → existing item, else surface for "Create Draft Item"
    const handleCodeMatch = useCallback(async (code) => {
        if (!code) return;
        if (handledCodeRef.current === code) return;   // dedupe consecutive frames
        handledCodeRef.current = code;
        dbg("detected code:", code);
        try {
            const params = companyId ? { q: code, company_id: companyId } : { q: code };
            const { data } = await api.get("/items", { params });
            const rows = Array.isArray(data) ? data : (data?.items || []);
            const hit = rows.find((it) =>
                (it.code && it.code === code) ||
                (it.barcode && it.barcode === code) ||
                (it.sku && it.sku === code)
            ) || rows[0];
            if (hit) {
                onResult?.({ kind: "item_match", code, item: hit });
                toast.success(`Found: ${hit.name}`);
                onClose?.();
                return;
            }
            onResult?.({ kind: "barcode_unmatched", code });
            toast.info(`Code ${code} not in catalog — open "AI Identify" to create a draft item.`);
        } catch (e) {
            dbg("item lookup error", e?.message);
            onResult?.({ kind: "barcode", code });
        }
    }, [companyId, onClose, onResult]);

    // ---- Boot effect — single source of truth for camera lifecycle ----
    useEffect(() => {
        mountedRef.current = true;
        let cancelled = false;
        setError("");
        setPermissionDenied(false);
        handledCodeRef.current = null;

        const boot = async () => {
            // Guard: no MediaDevices (e.g. http:// in production)
            if (!navigator.mediaDevices?.getUserMedia) {
                setError("Camera not available on this browser. Use HTTPS and a modern browser.");
                return;
            }
            // Guard: page not focused (Safari quirk) — still try, but log
            dbg("boot attempt", bootAttempt, "mode:", mode);

            // 1) Acquire camera stream
            let stream;
            try {
                stream = await navigator.mediaDevices.getUserMedia({
                    video: { facingMode: { ideal: "environment" }, width: { ideal: 1280 }, height: { ideal: 720 } },
                    audio: false,
                });
            } catch (e) {
                dbg("getUserMedia error", e?.name, e?.message);
                if (e?.name === "NotAllowedError" || e?.name === "PermissionDeniedError") {
                    setPermissionDenied(true);
                    setError("Camera permission denied. Click 'Retry' after allowing access in your browser settings.");
                } else if (e?.name === "NotFoundError" || e?.name === "DevicesNotFoundError") {
                    setError("No camera detected on this device.");
                } else if (e?.name === "NotReadableError") {
                    setError("Camera is in use by another app — close other tabs/apps and retry.");
                } else {
                    setError(e?.message || "Could not start camera. Allow permission and retry.");
                }
                return;
            }
            if (cancelled || !mountedRef.current) {
                stream.getTracks().forEach((t) => t.stop());
                return;
            }
            streamRef.current = stream;

            // 2) Attach to <video>
            const video = videoRef.current;
            if (!video) {
                stream.getTracks().forEach((t) => t.stop());
                return;
            }
            try {
                video.srcObject = stream;
                await video.play().catch(() => { /* autoplay sometimes throws — ok */ });
            } catch (e) { dbg("video.play error", e?.message); }

            // 3) In SCAN mode, wire up zxing reader. CRITICAL: pass undefined
            //    for hints (NOT {}) — zxing internals call .get() on the Map,
            //    and a plain object would crash with "e.get is not a function".
            if (mode === "scan") {
                let reader;
                try {
                    reader = new BrowserMultiFormatReader(undefined, {
                        delayBetweenScanAttempts: 250,
                        delayBetweenScanSuccess: 800,
                    });
                } catch (e) {
                    dbg("zxing ctor error", e?.message);
                    setError("Barcode reader failed to initialise. Try refreshing the page.");
                    return;
                }
                readerRef.current = reader;

                // Newer API: decodeFromVideoElement returns IScannerControls
                try {
                    const controls = await reader.decodeFromVideoElement(video, (result, err) => {
                        if (cancelled || !mountedRef.current) return;
                        if (result) {
                            const code = result.getText?.();
                            if (code) {
                                setLastCode(code);
                                handleCodeMatch(code);
                            }
                        }
                        // err is expected to fire repeatedly with NotFoundException —
                        // that's normal "no barcode in frame this tick" feedback.
                    });
                    if (cancelled) {
                        try { controls?.stop?.(); } catch { /* noop */ }
                        return;
                    }
                    controlsRef.current = controls || null;
                    dbg("zxing reader started", !!controls);
                } catch (e) {
                    dbg("decodeFromVideoElement error", e?.message);
                    // Don't show the raw zxing error — keep preview alive, identify mode still works.
                    setError("Barcode reader unavailable. Try AI Identify mode.");
                }
            }
        };

        boot();
        return () => {
            cancelled = true;
            mountedRef.current = false;
            teardown();
        };
    }, [mode, bootAttempt, handleCodeMatch, teardown]);

    // Identify-mode: snap a frame and ask backend Vision AI
    const snapAndIdentify = async () => {
        if (!videoRef.current) return;
        const video = videoRef.current;
        if (!video.videoWidth || !video.videoHeight) {
            toast.error("Camera not ready yet — wait a sec.");
            return;
        }
        const canvas = document.createElement("canvas");
        canvas.width = Math.min(video.videoWidth, 1024);
        canvas.height = Math.round(canvas.width * (video.videoHeight / video.videoWidth));
        try {
            canvas.getContext("2d").drawImage(video, 0, 0, canvas.width, canvas.height);
        } catch (e) {
            toast.error("Could not capture frame: " + (e?.message || ""));
            return;
        }
        const dataUrl = canvas.toDataURL("image/jpeg", 0.78);
        lastSnapRef.current = dataUrl;
        setBusy(true);
        setAiResult(null);
        try {
            const { data } = await api.post("/ai/vision-identify", { image_data_url: dataUrl });
            setAiResult(data);
            onResult?.({ kind: "ai_identify", payload: data, image: dataUrl });
        } catch (e) {
            toast.error(e?.response?.data?.detail || "AI identification failed");
        } finally { setBusy(false); }
    };

    // Allow the user to retry after a permission deny
    const retry = () => {
        teardown();
        setError("");
        setPermissionDenied(false);
        setBootAttempt((n) => n + 1);
    };

    // Close & teardown
    const closeAll = () => { teardown(); onClose?.(); };

    // Draft-item + accounting CTAs — surfaced when an identify result is available
    const fireAction = (kind) => {
        if (!aiResult) return;
        onResult?.({ kind, payload: aiResult, image: lastSnapRef.current || "", intent: kind });
        closeAll();
    };

    return (
        <div className="fixed inset-0 z-[60] bg-black/80 flex items-center justify-center p-4" data-testid="camera-capture-modal">
            <div className="relative w-full max-w-md bg-card rounded-2xl overflow-hidden shadow-2xl">
                <div className="flex items-center justify-between px-4 py-2.5 bg-gradient-to-r from-blue-800 to-blue-950 text-white">
                    <div className="flex items-center gap-2">
                        <Camera className="h-4 w-4" />
                        <div className="text-sm font-semibold">{mode === "scan" ? "Scan Barcode / QR" : "AI Object Identifier"}</div>
                    </div>
                    <button onClick={closeAll} aria-label="Close" data-testid="camera-close" className="p-1.5 rounded hover:bg-white/15">
                        <X className="h-4 w-4" />
                    </button>
                </div>

                {/* Mode switcher */}
                <div className="flex border-b">
                    <button
                        type="button"
                        onClick={() => { setError(""); setMode("scan"); }}
                        className={`flex-1 px-3 py-2 text-xs font-medium flex items-center justify-center gap-1.5 ${mode === "scan" ? "bg-blue-600 text-white" : "hover:bg-muted"}`}
                        data-testid="camera-mode-scan"
                    >
                        <ScanBarcode className="h-3.5 w-3.5" /> Barcode / QR
                    </button>
                    <button
                        type="button"
                        onClick={() => { setError(""); setMode("identify"); }}
                        className={`flex-1 px-3 py-2 text-xs font-medium flex items-center justify-center gap-1.5 ${mode === "identify" ? "bg-blue-600 text-white" : "hover:bg-muted"}`}
                        data-testid="camera-mode-identify"
                    >
                        <Sparkles className="h-3.5 w-3.5" /> AI Identify
                    </button>
                </div>

                <div className="relative aspect-[4/3] bg-black">
                    {error ? (
                        <div className="absolute inset-0 flex flex-col items-center justify-center text-white text-sm p-4 text-center gap-3">
                            <AlertTriangle className="h-8 w-8 text-amber-400" />
                            <div className="max-w-xs">{error}</div>
                            <button
                                type="button"
                                onClick={retry}
                                data-testid="camera-retry"
                                className="px-4 py-1.5 rounded-md bg-amber-500 text-black text-xs font-semibold flex items-center gap-1.5 hover:bg-amber-400"
                            >
                                <RefreshCw className="h-3.5 w-3.5" /> Retry
                            </button>
                            {permissionDenied && (
                                <div className="text-[10px] text-white/70 max-w-[260px]">
                                    On mobile, open browser settings → Site permissions → Camera → Allow for this site.
                                </div>
                            )}
                        </div>
                    ) : (
                        <>
                            <video ref={videoRef} muted playsInline autoPlay className="w-full h-full object-cover" data-testid="camera-video" />
                            {mode === "scan" && (
                                <div className="absolute inset-0 pointer-events-none flex items-center justify-center">
                                    <div className="w-2/3 aspect-square border-4 border-amber-400/80 rounded-lg shadow-[0_0_0_2000px_rgba(0,0,0,0.4)]" />
                                </div>
                            )}
                            {mode === "scan" && lastCode && (
                                <div className="absolute bottom-2 left-2 right-2 bg-emerald-500/95 text-white text-xs px-3 py-2 rounded-md flex items-center gap-2">
                                    <CheckCircle2 className="h-3.5 w-3.5" /> Detected: <span className="font-mono">{lastCode}</span>
                                </div>
                            )}
                        </>
                    )}
                </div>

                {/* Identify mode actions */}
                {mode === "identify" && (
                    <div className="p-3 space-y-2">
                        <button
                            type="button"
                            onClick={snapAndIdentify}
                            disabled={busy || !!error}
                            data-testid="camera-snap-identify"
                            className="w-full h-10 rounded-lg bg-blue-700 hover:bg-blue-800 text-white text-sm font-semibold flex items-center justify-center gap-2 disabled:opacity-50"
                        >
                            {busy ? <RefreshCw className="h-4 w-4 animate-spin" /> : <Sparkles className="h-4 w-4" />}
                            {busy ? "Identifying with AI…" : "Identify what you see"}
                        </button>
                        {aiResult && (
                            <div className="rounded-lg border border-emerald-500/40 bg-emerald-500/5 p-2.5 text-xs space-y-1" data-testid="camera-ai-result">
                                <div className="font-semibold text-sm">{aiResult.name || "Identified item"}</div>
                                {aiResult.category && <div><b>Category:</b> {aiResult.category}</div>}
                                {aiResult.purpose && <div><b>Usage:</b> {aiResult.purpose}</div>}
                                {aiResult.unit && <div><b>Unit:</b> {aiResult.unit}</div>}
                                {Array.isArray(aiResult.related) && aiResult.related.length > 0 && (
                                    <div><b>Related:</b> {aiResult.related.join(", ")}</div>
                                )}
                                {aiResult.matched_item ? (
                                    <div className="text-emerald-700 dark:text-emerald-400 font-medium">
                                        ✓ Existing item: {aiResult.matched_item.name} (stock {aiResult.matched_item.current_stock ?? "?"} {aiResult.matched_item.base_unit || ""})
                                    </div>
                                ) : (
                                    <div className="text-amber-700 dark:text-amber-400 text-[11px]">
                                        ⚠ Not in catalog yet — create a draft item below.
                                    </div>
                                )}
                                {/* Action row — wire scan → accounting */}
                                <div className="grid grid-cols-3 gap-1.5 pt-2">
                                    <button
                                        type="button"
                                        onClick={() => fireAction("create_draft")}
                                        data-testid="camera-action-draft"
                                        className="h-8 rounded bg-amber-500 hover:bg-amber-600 text-black text-[11px] font-semibold flex items-center justify-center gap-1"
                                    >
                                        <Plus className="h-3 w-3" /> Draft Item
                                    </button>
                                    <button
                                        type="button"
                                        onClick={() => fireAction("add_to_sale")}
                                        data-testid="camera-action-sale"
                                        className="h-8 rounded bg-emerald-600 hover:bg-emerald-700 text-white text-[11px] font-semibold flex items-center justify-center gap-1"
                                    >
                                        <ShoppingCart className="h-3 w-3" /> + Sale
                                    </button>
                                    <button
                                        type="button"
                                        onClick={() => fireAction("add_to_purchase")}
                                        data-testid="camera-action-purchase"
                                        className="h-8 rounded bg-blue-700 hover:bg-blue-800 text-white text-[11px] font-semibold flex items-center justify-center gap-1"
                                    >
                                        <Truck className="h-3 w-3" /> + Purchase
                                    </button>
                                </div>
                            </div>
                        )}
                    </div>
                )}

                {mode === "scan" && !error && (
                    <div className="p-3 text-[11px] text-muted-foreground text-center">
                        Point camera at barcode / QR. Auto-detects EAN, UPC, Code-128, QR.
                    </div>
                )}
            </div>
        </div>
    );
}
