// Leave Sandy a 30s voice note — record (mic or synthesized demo), playback with funny voice-over FX,
// Voltron · Yellow Lion profanity gate. Blocked notes are never saved.
const VN_MAX = 30, VN_BARS = 32;
const VOICE_FX = [
  { id: "normal", label: "My voice", emoji: "🎙️", rate: 1.0 },
  { id: "chipmunk", label: "Chipmunk", emoji: "🐿️", rate: 1.55 },
  { id: "boss", label: "Deep Boss", emoji: "🧔🏽", rate: 0.72 },
  { id: "robot", label: "Robo", emoji: "🤖", rate: 1.0, robot: true },
];
const VN_BLOCK = ["damn","hell","crap","asshole","ass","bastard","bitch","shit","piss","dick","prick","douche","idiot","stupid","moron","sucks","suck","dumb","jerk","freaking","frickin","wtf","stfu","bloody","screw you","shut up","hate you"];

function vnScan(text) {
  if (!text) return null;
  const norm = " " + text.toLowerCase().replace(/[^a-z\s]/g, " ").replace(/\s+/g, " ") + " ";
  for (const w of VN_BLOCK) { if (norm.indexOf(w) >= 0) return w; }
  return null;
}
function vnRandomBars() { return Array.from({ length: VN_BARS }, () => 6 + Math.random() * Math.random() * 92); }
function vnBufferBars(buffer) {
  const d = buffer.getChannelData(0), step = Math.floor(d.length / VN_BARS) || 1, out = [];
  for (let i = 0; i < VN_BARS; i++) { let pk = 0; for (let j = 0; j < step; j++) { const v = Math.abs(d[i * step + j] || 0); if (v > pk) pk = v; } out.push(Math.max(9, Math.min(100, pk * 135))); }
  return out;
}
function vnFmt(s) { s = Math.max(0, Math.floor(s)); return "0:" + String(s).padStart(2, "0"); }
function vnIcoPlay() { return (<svg width="17" height="17" viewBox="0 0 24 24" fill="currentColor"><path d="M8 5v14l11-7z"/></svg>); }
function vnIcoPause() { return (<svg width="17" height="17" viewBox="0 0 24 24" fill="currentColor"><rect x="6" y="5" width="4" height="14" rx="1"/><rect x="14" y="5" width="4" height="14" rx="1"/></svg>); }

function VoiceNoteModal({ open, onClose, onSent, sandy }) {
  const first = ((sandy && sandy.name) || "Sandy").split(" ")[0];
  const [phase, setPhase] = useState("idle");
  const [elapsed, setElapsed] = useState(0);
  const [bars, setBars] = useState(() => Array(VN_BARS).fill(8));
  const [fx, setFx] = useState("normal");
  const [playing, setPlaying] = useState(false);
  const [caption, setCaption] = useState("");
  const [blockWord, setBlockWord] = useState(null);
  const [demo, setDemo] = useState(false);
  const [recDur, setRecDur] = useState(0);
  const ctxRef = useRef(null), mrRef = useRef(null), streamRef = useRef(null), chunksRef = useRef([]);
  const analyserRef = useRef(null), bufferRef = useRef(null), srcRef = useRef(null), rafRef = useRef(0), startRef = useRef(0), elapsedRef = useRef(0);

  const getCtx = () => {
    if (!ctxRef.current) { const AC = window.AudioContext || window.webkitAudioContext; ctxRef.current = new AC(); }
    if (ctxRef.current.state === "suspended") ctxRef.current.resume();
    return ctxRef.current;
  };
  const cleanup = useCallback(() => {
    cancelAnimationFrame(rafRef.current);
    try { if (srcRef.current) { srcRef.current.onended = null; srcRef.current.stop(); if (srcRef.current._mod) srcRef.current._mod.stop(); } } catch (e) {}
    srcRef.current = null;
    try { if (mrRef.current && mrRef.current.state !== "inactive") mrRef.current.stop(); } catch (e) {}
    mrRef.current = null;
    if (streamRef.current) { streamRef.current.getTracks().forEach((t) => t.stop()); streamRef.current = null; }
    analyserRef.current = null;
  }, []);
  const reset = useCallback(() => {
    cleanup(); bufferRef.current = null;
    setPhase("idle"); setElapsed(0); elapsedRef.current = 0; setBars(Array(VN_BARS).fill(8));
    setFx("normal"); setPlaying(false); setCaption(""); setBlockWord(null); setRecDur(0);
  }, [cleanup]);

  useEffect(() => { if (!open) reset(); }, [open, reset]);
  useEffect(() => () => cleanup(), [cleanup]);
  useEffect(() => { document.body.style.overflow = open ? "hidden" : ""; return () => { document.body.style.overflow = ""; }; }, [open]);
  useEffect(() => { const h = (e) => { if (e.key === "Escape" && open) onClose(); }; window.addEventListener("keydown", h); return () => window.removeEventListener("keydown", h); }, [open, onClose]);

  function tick() {
    const t = (performance.now() - startRef.current) / 1000; elapsedRef.current = t; setElapsed(Math.min(t, VN_MAX));
    if (analyserRef.current) {
      const a = analyserRef.current, buf = new Uint8Array(a.frequencyBinCount); a.getByteFrequencyData(buf);
      const step = Math.floor(buf.length / VN_BARS) || 1, out = [];
      for (let i = 0; i < VN_BARS; i++) { let s = 0; for (let j = 0; j < step; j++) s += buf[i * step + j] || 0; out.push(Math.max(6, (s / step) / 255 * 118)); }
      setBars(out);
    } else setBars(vnRandomBars());
    if (t >= VN_MAX) { stopRec(); return; }
    rafRef.current = requestAnimationFrame(tick);
  }
  function synth(ctx) {
    const dur = Math.max(1.5, Math.min(elapsedRef.current || 3, 6)), sr = ctx.sampleRate, len = Math.floor(dur * sr);
    const buf = ctx.createBuffer(1, len, sr), data = buf.getChannelData(0);
    for (let i = 0; i < len; i++) {
      const t = i / sr, f = 150 + 42 * Math.sin(2 * Math.PI * 3 * t) + 22 * Math.sin(2 * Math.PI * 0.7 * t);
      const env = Math.min(1, t * 4) * Math.min(1, (dur - t) * 4) * (0.45 + 0.55 * Math.abs(Math.sin(2 * Math.PI * 2.3 * t)));
      data[i] = Math.sin(2 * Math.PI * f * t) * env * 0.32;
    }
    return buf;
  }
  async function onStopped() {
    const ctx = getCtx(); let buffer = null;
    if (chunksRef.current.length) {
      try {
        const blob = new Blob(chunksRef.current, { type: (mrRef.current && mrRef.current.mimeType) || "audio/webm" });
        const arr = await blob.arrayBuffer(); buffer = await ctx.decodeAudioData(arr.slice(0));
      } catch (e) { buffer = synth(ctx); }
    } else buffer = synth(ctx);
    bufferRef.current = buffer; setRecDur(buffer.duration || elapsedRef.current); setBars(vnBufferBars(buffer)); setPhase("recorded");
  }
  async function startRec() {
    setBlockWord(null); chunksRef.current = []; setPhase("recording"); setElapsed(0); elapsedRef.current = 0;
    const ctx = getCtx();
    try {
      if (!navigator.mediaDevices || !navigator.mediaDevices.getUserMedia || typeof MediaRecorder === "undefined") throw new Error("no-mic");
      const stream = await navigator.mediaDevices.getUserMedia({ audio: true }); streamRef.current = stream;
      const mr = new MediaRecorder(stream); mrRef.current = mr;
      mr.ondataavailable = (e) => { if (e.data && e.data.size) chunksRef.current.push(e.data); };
      mr.onstop = onStopped; mr.start();
      const node = ctx.createMediaStreamSource(stream); const an = ctx.createAnalyser(); an.fftSize = 256; node.connect(an); analyserRef.current = an; setDemo(false);
    } catch (e) { setDemo(true); analyserRef.current = null; }
    startRef.current = performance.now(); rafRef.current = requestAnimationFrame(tick);
  }
  function stopRec() {
    cancelAnimationFrame(rafRef.current);
    const mr = mrRef.current;
    if (mr && mr.state !== "inactive") mr.stop(); else onStopped();
    if (streamRef.current) { streamRef.current.getTracks().forEach((t) => t.stop()); streamRef.current = null; }
    analyserRef.current = null;
  }
  function stopPlay() { try { if (srcRef.current) { srcRef.current.onended = null; srcRef.current.stop(); if (srcRef.current._mod) srcRef.current._mod.stop(); } } catch (e) {} srcRef.current = null; setPlaying(false); }
  function play(optId) {
    const ctx = getCtx(), buffer = bufferRef.current; if (!buffer) return;
    stopPlay();
    const f = VOICE_FX.find((x) => x.id === (optId || fx)) || VOICE_FX[0];
    const src = ctx.createBufferSource(); src.buffer = buffer; src.playbackRate.value = f.rate;
    const out = ctx.createGain(); out.gain.value = 0.95;
    if (f.robot) {
      const ring = ctx.createGain(); ring.gain.value = 0.0;
      const mod = ctx.createOscillator(); mod.type = "square"; mod.frequency.value = 52;
      const depth = ctx.createGain(); depth.gain.value = 1.0; mod.connect(depth); depth.connect(ring.gain);
      src.connect(ring); ring.connect(out);
      const dl = ctx.createDelay(); dl.delayTime.value = 0.03; const fb = ctx.createGain(); fb.gain.value = 0.3;
      ring.connect(dl); dl.connect(fb); fb.connect(dl); dl.connect(out);
      mod.start(); src._mod = mod;
    } else src.connect(out);
    out.connect(ctx.destination);
    src.onended = () => { srcRef.current = null; setPlaying(false); };
    srcRef.current = src; src.start(); setPlaying(true);
  }
  function doSend() {
    stopPlay();
    const w = vnScan(caption);
    setPhase("scanning");
    window.setTimeout(() => {
      if (w) { setBlockWord(w); setPhase("blocked"); }
      else { setPhase("sent"); if (onSent) onSent(); }
    }, 1600);
  }

  if (!open) return null;
  const liveFlag = phase === "recorded" ? vnScan(caption) : null;
  return (
    <div className="vn-scrim" onMouseDown={(e) => { if (e.target === e.currentTarget) onClose(); }}>
      <div className="vn-card" data-screen-label="Voice note">
        <div className="vn-head">
          <div className="vn-head-l">
            <span className="vn-ava">SC</span>
            <div><div className="vn-head-title">Leave {first} a voice note</div><div className="vn-head-sub">Up to 30 seconds · she’ll hear it in her inbox</div></div>
          </div>
          <button className="vn-x" onClick={onClose} aria-label="Close">✕</button>
        </div>
        <div className="vn-guard">
          <span className="vn-guard-ico">🛡</span>
          <span>Protected by <b>Voltron · Yellow Lion</b>. Profanity &amp; bad language are auto-blocked — flagged notes are never saved.</span>
        </div>
        <div className="vn-body">
          {phase === "idle" && (
            <div className="vn-stage">
              <button className="vn-rec-btn" onClick={startRec} aria-label="Start recording"><span className="vn-rec-dot"></span></button>
              <div className="vn-stage-tx">Tap to start recording</div>
              <div className="vn-stage-sub">You’ll have 30 seconds. Add a funny voice-over before you send.</div>
            </div>
          )}
          {phase === "recording" && (
            <div className="vn-stage">
              <div className="vn-wave vn-wave-live">{bars.map((h, i) => <span key={i} style={{ height: h + "%" }}></span>)}</div>
              <div className="vn-timer"><span className="vn-rec-live"></span> {vnFmt(elapsed)} <span className="vn-timer-max">/ 0:30</span></div>
              <div className="vn-progress"><span style={{ width: (elapsed / VN_MAX * 100) + "%" }}></span></div>
              <button className="vn-stop" onClick={stopRec}><span className="vn-stop-sq"></span> Stop</button>
              {demo && <div className="vn-demo">Mic unavailable in this preview — recording a demo take so you can try the voices.</div>}
            </div>
          )}
          {phase === "recorded" && (
            <>
              <div className="vn-player">
                <button className="vn-play" onClick={() => (playing ? stopPlay() : play())} aria-label={playing ? "Pause" : "Play"}>{playing ? vnIcoPause() : vnIcoPlay()}</button>
                <div className="vn-wave vn-wave-static">{bars.map((h, i) => <span key={i} style={{ height: h + "%" }} className={playing ? "on" : ""}></span>)}</div>
                <span className="vn-dur">{vnFmt(recDur)}</span>
              </div>
              <span className="vn-fx-lab">Add a funny voice-over</span>
              <div className="vn-fx-row">
                {VOICE_FX.map((f) => (
                  <button key={f.id} className={"vn-fx" + (fx === f.id ? " on" : "")} onClick={() => { setFx(f.id); play(f.id); }}>
                    <span className="vn-fx-emo">{f.emoji}</span>{f.label}
                  </button>
                ))}
              </div>
              <span className="vn-cap-lab">What are you saying?<span>Voltron scans this</span></span>
              <textarea className="vn-cap" value={caption} onChange={(e) => setCaption(e.target.value)} placeholder={"e.g. Hi " + first + " — loved your Forbes piece on agentic trust. Would love a cohort seat…"} maxLength={220}></textarea>
              <div className={"vn-scan-live" + (liveFlag ? " bad" : "")}>{liveFlag ? "⚠ Voltron will block: “" + liveFlag + "”" : "✓ Looks clean to Voltron"}</div>
              <div className="vn-actions">
                <button className="vn-btn ghost" onClick={reset}>Re-record</button>
                <button className="vn-btn primary" onClick={doSend}>Send to {first} →</button>
              </div>
            </>
          )}
          {phase === "scanning" && (
            <div className="vn-stage">
              <div className="vn-scan-orb"><span className="vn-scan-ring"></span>🛡</div>
              <div className="vn-stage-tx">Voltron is reviewing your note…</div>
              <div className="vn-stage-sub">Yellow Lion is checking for profanity &amp; unsafe language.</div>
            </div>
          )}
          {phase === "blocked" && (
            <div className="vn-result vn-blocked">
              <div className="vn-res-ico">🛡</div>
              <div className="vn-res-title">Voltron blocked this note</div>
              <div className="vn-res-sub">Yellow Lion flagged <b>“{blockWord}”</b> as bad language. To keep {first}’s inbox safe, the note was <b>not saved</b>.</div>
              <div className="vn-actions center">
                <button className="vn-btn ghost" onClick={reset}>Start over</button>
                <button className="vn-btn primary" onClick={() => setPhase("recorded")}>Edit &amp; retry</button>
              </div>
            </div>
          )}
          {phase === "sent" && (
            <div className="vn-result vn-sent">
              <div className="vn-res-ico ok">✓</div>
              <div className="vn-res-title">Sent to {first}!</div>
              <div className="vn-res-sub">Voltron cleared your note and delivered it to {first}’s ActionBoard inbox. She’ll get back to you.</div>
              <div className="vn-actions center">
                <button className="vn-btn ghost" onClick={reset}>Record another</button>
                <button className="vn-btn primary" onClick={onClose}>Done</button>
              </div>
            </div>
          )}
        </div>
      </div>
    </div>
  );
}

Object.assign(window, { VoiceNoteModal, VOICE_FX, vnScan });
