/* BioReveal — Bioscan (face + skin only — audio removed 2026-05-12).
 *
 * 25 seconds. Camera on.
 *  - Per-frame face RGB → rPPG vitals (HR, breathing, autonomic activation)
 *  - Final face frame → /api/skin (hydration, redness, texture, spots)
 *
 * The user's verbal goal is now captured as text on the Goals step (a
 * textarea). Audio capture was noisy and not pulling its weight — kill
 * it pre-Shen so the bioscan is just "hold still for 25 seconds."
 *
 * Captured numbers are NOT shown to user (they're estimates, can be wrong).
 * Just "✓ Captured" — the AI synthesizer turns the raw signals into useful
 * analysis on the Reveal page.
 */

const StepBioscan = ({ value, onChange }) => {
  const videoRef = React.useRef(null);
  const canvasRef = React.useRef(null);
  const streamRef = React.useRef(null);
  const landmarkerRef = React.useRef(null);
  const captureBufRef = React.useRef({ r: [], g: [], b: [] });
  const startTsRef = React.useRef(0);
  const rafRef = React.useRef(0);

  const [status, setStatus] = React.useState("idle"); // idle | loading | requesting | capturing | analyzing | done | error | denied
  const [progress, setProgress] = React.useState(0);
  const [usingMP, setUsingMP] = React.useState(false);
  const [errMsg, setErrMsg] = React.useState("");
  const [captured, setCaptured] = React.useState(value?.captured || false);
  const [hadVitals, setHadVitals] = React.useState(false);
  const [hadSkin, setHadSkin] = React.useState(false);
  const CAPTURE_SECONDS = 25;
  const FS = 30;

  const cleanup = () => {
    cancelAnimationFrame(rafRef.current);
    if (streamRef.current) {
      streamRef.current.getTracks().forEach(t => t.stop());
      streamRef.current = null;
    }
  };
  React.useEffect(() => () => cleanup(), []);

  const tryLoadFaceLM = async () => {
    if (landmarkerRef.current) return landmarkerRef.current;
    try {
      const mod = await import("https://cdn.jsdelivr.net/npm/@mediapipe/tasks-vision@0.10.18/+esm");
      const { FilesetResolver, FaceLandmarker } = mod;
      const fileset = await FilesetResolver.forVisionTasks("https://cdn.jsdelivr.net/npm/@mediapipe/tasks-vision@0.10.18/wasm");
      const lm = await FaceLandmarker.createFromOptions(fileset, {
        baseOptions: { modelAssetPath: "https://storage.googleapis.com/mediapipe-models/face_landmarker/face_landmarker/float16/1/face_landmarker.task" },
        runningMode: "VIDEO", numFaces: 1,
      });
      landmarkerRef.current = lm;
      return lm;
    } catch(_) { return null; }
  };

  const start = async () => {
    setErrMsg("");
    setCaptured(false);
    setStatus("loading");
    const lm = await tryLoadFaceLM();
    setUsingMP(!!lm);

    setStatus("requesting");
    let stream;
    try {
      stream = await navigator.mediaDevices.getUserMedia({
        video: { facingMode: "user", width: { ideal: 720 }, height: { ideal: 720 }, frameRate: { ideal: 30 } },
        audio: false,
      });
    } catch (err) {
      setErrMsg(err?.name === "NotAllowedError"
        ? "Camera permission needed. Click the lock icon in the address bar to allow camera, then retry."
        : err.message);
      setStatus(err?.name === "NotAllowedError" ? "denied" : "error");
      return;
    }
    streamRef.current = stream;
    const v = videoRef.current;
    v.srcObject = stream;
    try { await v.play(); } catch(_){}

    setStatus("capturing");
    startTsRef.current = performance.now();
    captureBufRef.current = { r: [], g: [], b: [] };
    const canvas = canvasRef.current;
    const cctx = canvas.getContext("2d", { willReadFrequently: true });
    canvas.width = v.videoWidth || 720;
    canvas.height = v.videoHeight || 720;

    const tick = async (ts) => {
      if (!streamRef.current) return;
      const elapsed = (ts - startTsRef.current) / 1000;
      if (elapsed >= CAPTURE_SECONDS) { await analyzeAll(canvas); return; }
      try {
        cctx.drawImage(v, 0, 0, canvas.width, canvas.height);
        const W = canvas.width, H = canvas.height;
        let rsum = 0, gsum = 0, bsum = 0, count = 0;
        if (lm) {
          let result;
          try { result = lm.detectForVideo(v, ts); } catch(_){}
          const lms = result?.faceLandmarks?.[0];
          if (lms && lms.length > 200) {
            const idxs = [50, 280, 10, 117, 346];
            const half = 14;
            for (const i of idxs) {
              const p = lms[i];
              const cx = Math.round(p.x * W), cy = Math.round(p.y * H);
              const x0 = Math.max(0, cx - half), y0 = Math.max(0, cy - half);
              const w = Math.min(W - x0, half * 2), h = Math.min(H - y0, half * 2);
              const data = cctx.getImageData(x0, y0, w, h).data;
              for (let j = 0; j < data.length; j += 4) {
                rsum += data[j]; gsum += data[j+1]; bsum += data[j+2]; count++;
              }
            }
          }
        }
        if (count === 0) {
          const w = Math.round(W * 0.4), h = Math.round(H * 0.4);
          const x0 = Math.max(0, Math.round(W/2 - w/2)), y0 = Math.max(0, Math.round(H * 0.45 - h/2));
          const data = cctx.getImageData(x0, y0, w, h).data;
          for (let j = 0; j < data.length; j += 64) {
            rsum += data[j]; gsum += data[j+1]; bsum += data[j+2]; count++;
          }
        }
        if (count > 0) {
          captureBufRef.current.r.push(rsum / count);
          captureBufRef.current.g.push(gsum / count);
          captureBufRef.current.b.push(bsum / count);
        }
        setProgress(Math.min(100, Math.round((elapsed / CAPTURE_SECONDS) * 100)));
      } catch(e){ console.warn(e); }
      rafRef.current = requestAnimationFrame(tick);
    };
    rafRef.current = requestAnimationFrame(tick);
  };

  const analyzeAll = async (canvas) => {
    setStatus("analyzing");
    cleanup();
    const buf = captureBufRef.current;

    let skinResult = null;
    try {
      const blob = await new Promise(res => canvas.toBlob(res, "image/jpeg", 0.85));
      if (blob) {
        const r = await fetch("/api/skin", { method: "POST", headers: { "Content-Type": "image/jpeg" }, body: blob });
        const d = await r.json();
        if (d.ok) skinResult = d;
      }
    } catch(e){ console.warn("skin failed", e); }

    let vitalsResult = null;
    if (buf.r.length >= 60) {
      try {
        const r = await fetch("/api/vitals", {
          method: "POST", headers: { "Content-Type": "application/json" },
          body: JSON.stringify({ r: buf.r, g: buf.g, b: buf.b, fs: FS }),
        });
        const d = await r.json();
        if (d.ok) vitalsResult = d;
      } catch(e){ console.warn("vitals failed", e); }
    }

    if (!vitalsResult && !skinResult) {
      setStatus("error");
      setErrMsg("Couldn't extract any signals — try again with better light, facing the camera.");
      return;
    }

    setHadVitals(!!vitalsResult);
    setHadSkin(!!skinResult);
    setCaptured(true);
    onChange({ captured: true, vitals: vitalsResult, skin: skinResult });
    setStatus("done");
  };

  const skip = () => { cleanup(); onChange({ skipped: true }); setStatus("done"); setCaptured(false); };
  const retry = () => { setStatus("idle"); setProgress(0); setErrMsg(""); setCaptured(false); };

  return (
    <>
      <div className="br-eyebrow">03 / 04 · The bioscan · Optional</div>
      <h2 className="br-question">25 seconds. Look at the camera.</h2>
      <p className="br-sub">Hold still in soft light. We read your face — heart rate, breathing, skin signals. No audio. No raw numbers shown.</p>

      <div className="br-selfie-frame" style={{ position: "relative", overflow: "hidden", background: "#0a0a0a" }}>
        <video ref={videoRef} muted playsInline autoPlay
          style={{ width: "100%", height: "100%", objectFit: "cover", transform: "scaleX(-1)", display: status === "capturing" || status === "analyzing" ? "block" : "none" }}/>
        <canvas ref={canvasRef} style={{ display: "none" }}/>
        <div className="br-corner tl"/><div className="br-corner tr"/><div className="br-corner bl"/><div className="br-corner br"/>

        {status === "idle" && (
          <div style={{ position: "absolute", inset: 0, display: "grid", placeItems: "center", color: "#888", fontFamily: "var(--font-mono)", fontSize: 12, letterSpacing: "0.18em", textTransform: "uppercase" }}>
            Tap "Begin BioScan"
          </div>
        )}
        {status === "loading" && (
          <div style={{ position: "absolute", inset: 0, display: "grid", placeItems: "center", color: "#bbb", fontFamily: "var(--font-mono)", fontSize: 12, letterSpacing: "0.18em", textTransform: "uppercase", textAlign: "center", padding: 16 }}>
            Loading face model<br/><span style={{ fontSize: 10, opacity: 0.6 }}>~3MB · first time only</span>
          </div>
        )}
        {status === "requesting" && (
          <div style={{ position: "absolute", inset: 0, display: "grid", placeItems: "center", color: "#bbb", fontFamily: "var(--font-mono)", fontSize: 12, letterSpacing: "0.18em", textTransform: "uppercase", textAlign: "center", padding: 16 }}>
            Allow camera
          </div>
        )}
        {status === "capturing" && (
          <>
            <div className="br-scan-line"/>
            <div className="br-selfie-stat" style={{ top: 16, left: 44 }}>● BIOSCAN · {progress}%</div>
            <div className="br-selfie-stat" style={{ top: 16, right: 44 }}>{usingMP ? "FACE-LOCKED" : "CENTER-ROI"}</div>
            <div className="br-selfie-stat" style={{ bottom: 16, left: 44 }}>HOLD STILL</div>
          </>
        )}
        {status === "analyzing" && (
          <div style={{ position: "absolute", inset: 0, display: "grid", placeItems: "center", color: "#fff", fontFamily: "var(--font-mono)", fontSize: 12, letterSpacing: "0.18em", textTransform: "uppercase" }}>
            Processing your face data…
          </div>
        )}
        {status === "done" && captured && (
          <div style={{ position: "absolute", inset: 0, background: "linear-gradient(180deg, rgba(0,0,0,0.9), #000)", display: "grid", placeItems: "center", padding: 24, textAlign: "center", color: "#fff" }}>
            <div style={{ maxWidth: 360 }}>
              <div style={{ fontSize: 56, marginBottom: 8 }}>✓</div>
              <div style={{ fontFamily: "var(--font-mono)", fontSize: 11, letterSpacing: "0.22em", opacity: 0.55, marginBottom: 18 }}>BIOSCAN COMPLETE</div>
              <div style={{ display: "flex", flexDirection: "column", gap: 8, alignItems: "center" }}>
                <Pill on={hadVitals} label="We read your heart rate"/>
                <Pill on={hadSkin}   label="We read your skin"/>
              </div>
              <div style={{ marginTop: 18, fontSize: 12, opacity: 0.55, lineHeight: 1.5 }}>
                The AI puts it all together on the next page.
              </div>
            </div>
          </div>
        )}
        {(status === "error" || status === "denied") && (
          <div style={{ position: "absolute", inset: 0, display: "grid", placeItems: "center", background: "#111", color: "#fff", padding: 24, textAlign: "center" }}>
            <div>
              <div style={{ fontFamily: "var(--font-mono)", fontSize: 11, letterSpacing: "0.2em", opacity: 0.7, marginBottom: 12 }}>BIOSCAN FAILED</div>
              <div style={{ fontSize: 13, opacity: 0.85, maxWidth: 380, lineHeight: 1.5 }}>{errMsg || "Unknown error"}</div>
            </div>
          </div>
        )}
      </div>

      <div style={{ display: "flex", gap: 10, marginTop: 16 }}>
        {status === "idle" && (
          <>
            <button className="br-option" style={{ flex: 1, justifyContent: "center" }} onClick={start}>● Begin BioScan</button>
            <button className="br-option" style={{ flex: 1, justifyContent: "center" }} onClick={skip}>Skip</button>
          </>
        )}
        {(status === "loading" || status === "requesting" || status === "capturing" || status === "analyzing") && (
          <button className="br-option" style={{ flex: 1, justifyContent: "center", opacity: 0.6 }} disabled>Working…</button>
        )}
        {status === "done" && (
          <>
            <button className="br-option selected" style={{ flex: 1, justifyContent: "center" }}>{captured ? "✓ BioScan saved" : "✓ Skipped"}</button>
            {captured && <button className="br-option" style={{ flex: 1, justifyContent: "center" }} onClick={retry}>Re-scan</button>}
          </>
        )}
        {(status === "error" || status === "denied") && (
          <>
            <button className="br-option" style={{ flex: 1, justifyContent: "center" }} onClick={retry}>Try again</button>
            <button className="br-option" style={{ flex: 1, justifyContent: "center" }} onClick={skip}>Skip</button>
          </>
        )}
      </div>

      <div style={{ marginTop: 14, fontSize: 11, color: "var(--muted)", lineHeight: 1.5, fontFamily: "var(--font-mono)", letterSpacing: "0.05em" }}>
        ⓘ <strong>WHAT THE AI READS</strong> · Your heart rate (from skin color) + your skin. Raw numbers stay private. You get the read.
      </div>
    </>
  );
};

const Pill = ({ on, label }) => (
  <div style={{ display: "flex", alignItems: "center", gap: 10, fontSize: 13, fontFamily: "var(--font-mono)", letterSpacing: "0.04em", opacity: on ? 1 : 0.4 }}>
    <span style={{ width: 8, height: 8, borderRadius: "50%", background: on ? "#10b981" : "#444", flexShrink: 0 }}/>
    <span>{label}</span>
  </div>
);

window.BR_STEPS = window.BR_STEPS || {};
window.BR_STEPS.StepBioscan = StepBioscan;
