import { useEffect, useRef, useState } from "react";
import { X } from "lucide-react";
import type { VoiceState } from "../lib/voice";

// 全屏「JARVIS 模式」HUD：弧反应堆核心 + 随真实声音反应的环形频谱 + 遥测 + 解码文字 + 开机自检。
// 视觉数据全来自真实语音会话：level 由 VoiceClient 的麦克风/TTS 探针实时给出（getLevel），
// state 是会话状态机；transcript/reply 是当轮问答。点中心说话，右上角/ESC 退出。

interface Props {
  name: string;
  state: VoiceState;
  getLevel: () => number;
  transcript: string;
  reply: string;
  hint: string;
  // 按住说话（与语音坞/空格键同一套 PTT 语义）：按下开始听、松开发送。
  onPttDown: () => void;
  onPttUp: () => void;
  onClose: () => void;
}

type Conf = { accent: number[]; activity: number; ringSpin: number; label: string };
const CY = [56, 225, 255];
const AM = [255, 182, 56];
const CONF: Record<VoiceState, Conf> = {
  idle: { accent: CY, activity: 0.18, ringSpin: 0.1, label: "STANDBY" },
  connecting: { accent: CY, activity: 0.22, ringSpin: 0.5, label: "LINKING" },
  ready: { accent: CY, activity: 0.18, ringSpin: 0.1, label: "READY" },
  listening: { accent: CY, activity: 0.6, ringSpin: 0.35, label: "LISTENING" },
  thinking: { accent: AM, activity: 0.3, ringSpin: 0.8, label: "PROCESSING" },
  speaking: { accent: CY, activity: 0.85, ringSpin: 0.22, label: "SPEAKING" },
};
const GLYPH = "ABCDEFGHJKLMNPQRSTUVWXYZ0123456789#%@*<>/\\";

export function JarvisHud({ name, state, getLevel, transcript, reply, hint, onPttDown, onPttUp, onClose }: Props) {
  const canvasRef = useRef<HTMLCanvasElement>(null);
  const decRef = useRef<HTMLSpanElement>(null);
  const sigRef = useRef<HTMLElement>(null);
  const latRef = useRef<HTMLElement>(null);
  const gainRef = useRef<HTMLElement>(null);
  const [booting, setBooting] = useState(true);
  // 用 ref 让 RAF 闭包读到最新 props，无需重启动画。
  const P = useRef({ state, getLevel, transcript, reply });
  P.current = { state, getLevel, transcript, reply };

  // 开机自检：进场放一段 INITIALIZING…ONLINE，约 2.4s 后淡出（reduced-motion 直接跳过）。
  useEffect(() => {
    if (matchMedia("(prefers-reduced-motion: reduce)").matches) {
      setBooting(false);
      return;
    }
    const id = setTimeout(() => setBooting(false), 2400);
    return () => clearTimeout(id);
  }, []);

  useEffect(() => {
    const cv = canvasRef.current!;
    const ctx = cv.getContext("2d")!;
    const reduce = matchMedia("(prefers-reduced-motion: reduce)").matches;
    let W = 0, H = 0, raf = 0;
    const resize = () => {
      const r = cv.getBoundingClientRect();
      const dpr = Math.min(2, devicePixelRatio || 1);
      W = r.width; H = r.height;
      cv.width = W * dpr; cv.height = H * dpr;
      ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
    };
    resize();
    const ro = new ResizeObserver(resize);
    ro.observe(cv);

    const rgba = (c: number[], a: number) => `rgba(${c[0]},${c[1]},${c[2]},${a})`;
    const lerp = (a: number, b: number, t: number) => a + (b - a) * t;
    const accent = CY.slice();
    let level = 0.18, think = 0, t = 0, last = performance.now();
    const parts = Array.from({ length: 64 }, () => ({ a: Math.random() * 6.28, r: Math.random(), s: 0.3 + Math.random() * 0.9 }));
    // 解码文字状态：shown=已定稿字数，scr=当前字的乱码累积。
    let decShown = 0, scr = 0;

    const synth = (s: VoiceState) => (s === "thinking" ? 0.2 + 0.06 * Math.sin(t * 3) : 0.16 + 0.05 * Math.sin(t * 1.6));

    const draw = (now: number) => {
      const dt = Math.min(0.05, (now - last) / 1000); last = now; t += dt;
      const st = P.current.state;
      const c = CONF[st] ?? CONF.idle;
      const activeAudio = st === "listening" || st === "speaking";
      const tgt = activeAudio ? Math.max(P.current.getLevel(), 0) : synth(st);
      level += (tgt - level) * Math.min(1, dt * (activeAudio ? 16 : 6));
      for (let i = 0; i < 3; i++) accent[i] = lerp(accent[i], c.accent[i], Math.min(1, dt * 4));
      think += ((st === "thinking" ? 1 : 0) - think) * Math.min(1, dt * 3);

      const cx = W / 2, cy = H / 2, R = Math.min(W, H) * 0.16;
      ctx.clearRect(0, 0, W, H);
      ctx.globalCompositeOperation = "lighter";

      let g = ctx.createRadialGradient(cx, cy, R * 0.2, cx, cy, R * 3.4);
      g.addColorStop(0, rgba(accent, 0.1 + level * 0.1));
      g.addColorStop(1, rgba(accent, 0));
      ctx.fillStyle = g; ctx.fillRect(cx - R * 4, cy - R * 4, R * 8, R * 8);

      const spin = reduce ? 0 : t * c.ringSpin;
      [1.35, 1.7, 2.05, 2.5, 3.0].forEach((rr, i) => {
        const rad = R * rr, dir = i % 2 ? -1 : 1, a0 = spin * dir * (1 + i * 0.25);
        ctx.lineWidth = i === 0 ? 2.2 : 1.2;
        ctx.strokeStyle = rgba(accent, (0.5 - i * 0.06) * (0.6 + level * 0.5));
        ctx.shadowColor = rgba(accent, 0.8); ctx.shadowBlur = 12;
        const segs: [number, number][] = i === 0 ? [[0, 5.0]] : [[a0, 1.3], [a0 + 2.3, 2.0], [a0 + 5.0, 0.8]];
        segs.forEach(([s, len]) => { ctx.beginPath(); ctx.arc(cx, cy, rad, s, s + len); ctx.stroke(); });
      });
      ctx.shadowBlur = 0;

      const NB = 104, inner = R * 1.12;
      ctx.lineWidth = 2;
      for (let i = 0; i < NB; i++) {
        const ang = (i / NB) * 6.28 - Math.PI / 2 + spin * 0.15;
        const seed = Math.sin(i * 12.9898) * 43758.5453; const nz = seed - Math.floor(seed);
        const wob = 0.5 + 0.5 * Math.sin(t * 3 + i * 0.5);
        const h = R * (0.06 + (0.1 + c.activity * 0.9 * level) * (0.35 + 0.65 * wob) * (0.5 + nz));
        const x1 = cx + Math.cos(ang) * inner, y1 = cy + Math.sin(ang) * inner;
        const x2 = cx + Math.cos(ang) * (inner + h), y2 = cy + Math.sin(ang) * (inner + h);
        const bright = 0.35 + 0.65 * (h / (R * 0.9));
        ctx.strokeStyle = rgba(accent, 0.25 + bright * 0.6);
        ctx.beginPath(); ctx.moveTo(x1, y1); ctx.lineTo(x2, y2); ctx.stroke();
      }

      if (think > 0.02) {
        parts.forEach((p) => {
          if (!reduce) p.a += dt * p.s * 1.2;
          const rad = R * (1.15 + p.r * 1.7), x = cx + Math.cos(p.a) * rad, y = cy + Math.sin(p.a) * rad;
          ctx.fillStyle = rgba(AM, 0.7 * think);
          ctx.shadowColor = rgba(AM, 0.9); ctx.shadowBlur = 8;
          ctx.beginPath(); ctx.arc(x, y, 1.6, 0, 6.28); ctx.fill();
        });
        const sa = t * 2.2;
        ctx.save(); ctx.beginPath(); ctx.moveTo(cx, cy); ctx.arc(cx, cy, R * 2.9, sa, sa + 0.5); ctx.closePath();
        ctx.fillStyle = rgba(AM, 0.1 * think); ctx.fill(); ctx.restore();
        ctx.shadowBlur = 0;
      }

      const cr = R * (0.62 + level * 0.3);
      const cg = ctx.createRadialGradient(cx, cy, 0, cx, cy, cr);
      cg.addColorStop(0, rgba([255, 255, 255], 0.95));
      cg.addColorStop(0.25, rgba(accent, 0.95));
      cg.addColorStop(0.7, rgba(accent, 0.28));
      cg.addColorStop(1, rgba(accent, 0));
      ctx.fillStyle = cg; ctx.beginPath(); ctx.arc(cx, cy, cr, 0, 6.28); ctx.fill();
      ctx.globalCompositeOperation = "source-over";
      for (let k = 0; k < 3; k++) {
        ctx.lineWidth = 1; ctx.strokeStyle = rgba([255, 255, 255], 0.5 - k * 0.13);
        ctx.beginPath(); ctx.arc(cx, cy, R * (0.3 + k * 0.14), 0, 6.28); ctx.stroke();
      }
      ctx.globalCompositeOperation = "lighter";
      ctx.strokeStyle = rgba([255, 255, 255], 0.85); ctx.lineWidth = 1.5; ctx.beginPath();
      for (let k = 0; k < 3; k++) {
        const a = spin * 2 + k * 2.094, rr = R * 0.22;
        const x = cx + Math.cos(a) * rr, y = cy + Math.sin(a) * rr;
        k ? ctx.lineTo(x, y) : ctx.moveTo(x, y);
      }
      ctx.closePath(); ctx.stroke();
      ctx.globalCompositeOperation = "source-over";

      // 遥测读数
      if (sigRef.current) sigRef.current.textContent = String(Math.round(level * 100)).padStart(2, "0");
      if (latRef.current) latRef.current.textContent = String(90 + Math.round(level * 60 + Math.sin(t * 9) * 8)).padStart(3, "0");
      if (gainRef.current) gainRef.current.textContent = (0.8 + level * 2.4).toFixed(1);

      // 解码式回答：让文字逐字"定稿"，前沿几个字符跳乱码。
      const rep = P.current.reply;
      if (decShown > rep.length) decShown = 0; // 新一轮回答，重置
      if (decShown < rep.length) { scr += 0.5; if (scr >= 1) { scr = 0; decShown++; } }
      if (decRef.current) {
        let out = rep.slice(0, decShown);
        const frontier = Math.min(6, rep.length - decShown);
        for (let i = 0; i < frontier; i++) out += `<span class="jhud-scr">${GLYPH[(Math.random() * GLYPH.length) | 0]}</span>`;
        decRef.current.innerHTML = out;
      }

      raf = requestAnimationFrame(draw);
    };
    raf = requestAnimationFrame(draw);
    return () => { cancelAnimationFrame(raf); ro.disconnect(); };
  }, []);

  const c = CONF[state] ?? CONF.idle;
  const amber = state === "thinking";
  const NAME = (name || "JARVIS").toUpperCase();

  return (
    <div className="jhud" role="dialog" aria-label="JARVIS 全屏模式">
      <style>{HUD_CSS}</style>
      <canvas ref={canvasRef} className="jhud-canvas" />

      <span className="jhud-corner tl" /><span className="jhud-corner tr" />
      <span className="jhud-corner bl" /><span className="jhud-corner br" />

      <div className="jhud-status">
        <div className="jhud-brand">{NAME}</div>
        <div className="jhud-row">
          <span className={"jhud-dot" + (state === "listening" ? " live" : amber ? " amber" : "")} />
          <b>{c.label}</b>
        </div>
      </div>

      <div className="jhud-tel">
        <div>SIGNAL <b ref={sigRef}>00</b>%</div>
        <div>LATENCY <b ref={latRef}>000</b>ms</div>
        <div>GAIN <b ref={gainRef}>0.0</b></div>
        <div>SESSION <b>#7F2A</b></div>
      </div>

      <button className="jhud-close" onClick={onClose} aria-label="退出 JARVIS 模式">
        <X className="h-4 w-4" /> ESC
      </button>

      <button
        className="jhud-core"
        onPointerDown={(e) => { e.preventDefault(); onPttDown(); }}
        onPointerUp={onPttUp}
        onPointerLeave={onPttUp}
        onPointerCancel={onPttUp}
        aria-label={hint}
        title={hint}
      />
      <div className={"jhud-word" + (amber ? " amber" : state === "listening" ? " live" : "")}>{hint}</div>

      <div className="jhud-decode">
        {transcript && <p className="jhud-me"><span>我</span>{transcript}</p>}
        <p className="jhud-reply" style={{ visibility: reply ? "visible" : "hidden" }}>
          <span>{NAME}</span><span ref={decRef} />
          {(state === "thinking" || state === "speaking") && <i className="jhud-cursor" />}
        </p>
      </div>

      {booting && (
        <div className="jhud-boot" aria-hidden="true">
          <div><span className="b">›</span> INITIALIZING {NAME} CORE</div>
          <div><span className="b">›</span> AUDIO SUBSYSTEM <span className="ok">… OK</span></div>
          <div><span className="b">›</span> NEURAL UPLINK <span className="ok">… OK</span></div>
          <div><span className="b">›</span> VOICE MODEL · v4-flash <span className="ok">… OK</span></div>
          <div className="big">ONLINE</div>
        </div>
      )}
    </div>
  );
}

const HUD_CSS = `
.jhud{position:fixed;inset:0;z-index:60;overflow:hidden;color:#d6f2fc;user-select:none;
  background:radial-gradient(120% 80% at 50% 44%,#08131d 0%,#04070d 62%,#02040a 100%);
  font-family:ui-monospace,"SF Mono","JetBrains Mono",Menlo,Consolas,monospace;animation:jhudIn .5s ease}
@keyframes jhudIn{from{opacity:0}to{opacity:1}}
.jhud::before{content:"";position:absolute;inset:0;pointer-events:none;opacity:.5;
  background-image:linear-gradient(transparent 0 3px,rgba(56,225,255,.035) 3px 4px),linear-gradient(90deg,transparent 0 3px,rgba(56,225,255,.028) 3px 4px);
  background-size:100% 4px,4px 100%;mask-image:radial-gradient(100% 100% at 50% 45%,#000 55%,transparent 100%)}
.jhud::after{content:"";position:absolute;inset:0;pointer-events:none;z-index:3;
  background:linear-gradient(rgba(56,225,255,0),rgba(56,225,255,.05) 50%,rgba(56,225,255,0));height:26%;animation:jhudSweep 6.5s linear infinite;opacity:.55}
@keyframes jhudSweep{0%{transform:translateY(-30%)}100%{transform:translateY(430%)}}
.jhud-canvas{position:absolute;inset:0;width:100%;height:100%;z-index:1}
.jhud-corner{position:absolute;width:44px;height:44px;border:1.5px solid #0e5a70;z-index:4;opacity:.8}
.jhud-corner.tl{top:20px;left:20px;border-right:0;border-bottom:0}
.jhud-corner.tr{top:20px;right:20px;border-left:0;border-bottom:0}
.jhud-corner.bl{bottom:20px;left:20px;border-right:0;border-top:0}
.jhud-corner.br{bottom:20px;right:20px;border-left:0;border-top:0}
.jhud-status{position:absolute;top:30px;left:38px;z-index:5;letter-spacing:.14em;font-size:12px;line-height:1.9}
.jhud-brand{color:#38e1ff;font-size:15px;letter-spacing:.28em;text-shadow:0 0 14px rgba(56,225,255,.55)}
.jhud-row{display:flex;gap:8px;align-items:center;color:#5f8496}
.jhud-row b{color:#d6f2fc;font-weight:500}
.jhud-dot{width:7px;height:7px;border-radius:50%;background:#38e1ff;box-shadow:0 0 10px #38e1ff}
.jhud-dot.live{background:#ff4d5e;box-shadow:0 0 10px #ff4d5e;animation:jhudBlink 1s steps(2) infinite}
.jhud-dot.amber{background:#ffb638;box-shadow:0 0 10px #ffb638}
@keyframes jhudBlink{50%{opacity:.25}}
.jhud-tel{position:absolute;top:66px;right:40px;z-index:5;text-align:right;font-size:11px;line-height:2.1;letter-spacing:.14em;color:#5f8496}
.jhud-tel b{color:#38e1ff;font-weight:500;font-variant-numeric:tabular-nums}
.jhud-close{position:absolute;top:26px;right:34px;z-index:6;display:flex;align-items:center;gap:6px;
  background:rgba(8,22,32,.6);border:1px solid #0e2a38;color:#7fa6b6;border-radius:999px;padding:7px 13px;
  font-family:inherit;font-size:11px;letter-spacing:.14em;cursor:pointer;backdrop-filter:blur(6px);transition:.18s}
.jhud-close:hover{border-color:#38e1ff;color:#d6f2fc}
.jhud-core{position:absolute;left:50%;top:50%;transform:translate(-50%,-50%);width:26vmin;height:26vmin;border-radius:50%;background:transparent;border:0;cursor:pointer;z-index:5}
.jhud-word{position:absolute;left:50%;top:calc(50% + 20vmin);transform:translateX(-50%);z-index:5;font-size:13px;letter-spacing:.5em;color:#38e1ff;text-shadow:0 0 16px rgba(56,225,255,.5);text-transform:uppercase;white-space:nowrap}
.jhud-word.amber{color:#ffb638;text-shadow:0 0 16px rgba(255,182,56,.5)}
.jhud-word.live{color:#ff4d5e;text-shadow:0 0 16px rgba(255,77,94,.5)}
.jhud-decode{position:absolute;left:50%;bottom:56px;transform:translateX(-50%);z-index:5;width:min(760px,88vw);text-align:center;line-height:1.55}
.jhud-me{color:#5f8496;font-size:13px;margin:0 0 10px}
.jhud-me span,.jhud-reply>span:first-child{font-size:10px;letter-spacing:.24em;margin-right:8px;opacity:.75}
.jhud-reply{color:#eaf8ff;font-size:16px;margin:0;min-height:24px;text-shadow:0 0 10px rgba(56,225,255,.25)}
.jhud-reply>span:first-child{color:#38e1ff}
.jhud-scr{color:#38e1ff;opacity:.9}
.jhud-cursor{display:inline-block;width:8px;height:2px;background:#38e1ff;margin-left:3px;vertical-align:middle;box-shadow:0 0 8px #38e1ff;animation:jhudBlink .8s steps(2) infinite}
.jhud-boot{position:absolute;inset:0;z-index:8;background:#02040a;display:flex;flex-direction:column;justify-content:center;padding-left:12%;gap:6px;font-size:14px;letter-spacing:.06em;color:#38e1ff;animation:jhudBootOut .6s ease 1.8s forwards}
.jhud-boot>div{opacity:0;animation:jhudBootLine .3s ease forwards}
.jhud-boot>div:nth-child(1){animation-delay:.1s}
.jhud-boot>div:nth-child(2){animation-delay:.5s}
.jhud-boot>div:nth-child(3){animation-delay:.9s}
.jhud-boot>div:nth-child(4){animation-delay:1.2s}
.jhud-boot .b{color:#3d6475}.jhud-boot .ok{color:#38e1ff}
.jhud-boot .big{font-size:26px;letter-spacing:.4em;margin-top:14px;color:#eaf8ff;text-shadow:0 0 20px rgba(56,225,255,.6);animation-delay:1.5s}
@keyframes jhudBootLine{from{opacity:0;transform:translateX(-8px)}to{opacity:1;transform:none}}
@keyframes jhudBootOut{to{opacity:0}}
@media (prefers-reduced-motion: reduce){.jhud::after,.jhud-boot{display:none}.jhud-dot.live,.jhud-cursor{animation:none}}
`;
