/* global React, window */
// Shared atomic UI components.

const { useState, useEffect, useRef, useMemo } = React;

function cx(...a) { return a.filter(Boolean).join(" "); }

// ===== i18n =====
// Lightweight pub/sub. Components that contain translatable strings call
// useLang() to subscribe; t(en, zh) reads the current value synchronously
// so plain JSX expressions can mix t() with non-React code (e.g. tooltips).
const __langListeners = new Set();
function getLang() {
  try {
    const v = localStorage.getItem("atria-proto-lang");
    if (v === "zh" || v === "en") return v;
    // First visit (no stored choice): infer from browser language.
    // Once the user toggles, their pick is persisted and overrides this.
    if (typeof navigator !== "undefined") {
      const nav = (navigator.language || (navigator.languages && navigator.languages[0]) || "").toLowerCase();
      if (nav.indexOf("zh") === 0) return "zh";
    }
    return "en";
  } catch { return "en"; }
}
function setLang(v) {
  const next = v === "zh" ? "zh" : "en";
  try { localStorage.setItem("atria-proto-lang", next); } catch {}
  if (typeof document !== "undefined") document.documentElement.lang = next === "zh" ? "zh" : "en";
  __langListeners.forEach(fn => { try { fn(next); } catch {} });
}
function useLang() {
  const [l, setL] = useState(getLang());
  useEffect(() => {
    __langListeners.add(setL);
    return () => __langListeners.delete(setL);
  }, []);
  return l;
}
function t(en, zh) {
  return getLang() === "zh" && zh != null ? zh : en;
}

// Reactive variant of t() — components that use this auto-update on switch.
function useT() {
  useLang(); // subscribe for re-render
  return t;
}

// Color palette for ad-code initials — deterministic by code so the same ad
// always lights up the same color across sessions.
const THUMB_PALETTE = [
  { bg: "#D14A1F", ink: "#FFF" }, // accent orange
  { bg: "#2C6FE0", ink: "#FFF" }, // blue
  { bg: "#2E6B3A", ink: "#FFF" }, // green
  { bg: "#B98424", ink: "#FFF" }, // amber
  { bg: "#7E4FB8", ink: "#FFF" }, // purple
  { bg: "#0E7C7B", ink: "#FFF" }, // teal
  { bg: "#A8362A", ink: "#FFF" }, // red
  { bg: "#4A3F35", ink: "#FBF8F2" }, // ink/brown
];
function thumbColor(code) {
  const s = String(code || "A");
  let h = 0;
  for (let i = 0; i < s.length; i++) h = (h * 31 + s.charCodeAt(i)) >>> 0;
  return THUMB_PALETTE[h % THUMB_PALETTE.length];
}

function Thumb({ ad, w = 60, h = 60, big = false, accent }) {
  // First-letter color chip — calm and legible at any size.
  const code = ad?.code || "AD";
  const letter = code.charAt(0).toUpperCase();
  const c = thumbColor(code);
  const isVideo = ad?.type === "video";
  const size = typeof w === "number" ? w : 60;
  const letterSize = big
    ? Math.max(48, Math.floor(size * 0.42))
    : Math.max(18, Math.floor(size * 0.5));
  return (
    <div
      className="thumb"
      style={{
        width: w, height: h, borderRadius: 6, flexShrink: 0,
        background: c.bg,
        border: "1px solid rgba(0,0,0,0.08)",
        display: "grid",
        placeItems: "center",
        position: "relative",
        overflow: "hidden",
      }}
    >
      <span
        className="serif"
        style={{
          fontFamily: "Source Serif 4, Georgia, serif",
          fontWeight: 600,
          fontSize: letterSize,
          color: c.ink,
          lineHeight: 1,
          letterSpacing: "-0.02em",
        }}
      >
        {letter}
      </span>
      {isVideo && (
        <span
          style={{
            position: "absolute",
            bottom: 4, right: 5,
            fontSize: big ? 12 : 9,
            color: c.ink,
            opacity: 0.85,
            lineHeight: 1,
          }}
        >▶</span>
      )}
    </div>
  );
}

// AdCreative — pure-HTML/CSS "rendered ad" for original-ad previews and
// variant cards. Replaces the striped placeholder. Each ad gets a brand-
// colored backdrop derived from its code, a soft product "shape" in the
// middle, headline + sub overlaid, and a CTA pill at the bottom.
//
// Variant-specific styling: each variant idx (0..3) layers on its own
// treatment — promo / urgency / benefit / onboarding.
function AdCreative({ ad, variant, w = "100%", h = 360, big = false }) {
  const code = ad?.code || "AD";
  const c = thumbColor(code);
  const isZh = getLang() === "zh";
  const headline = isZh && ad?.headlineZh ? ad.headlineZh : (ad?.headline || code);
  const sub = isZh && ad?.subZh ? ad.subZh : (ad?.sub || "");
  const ratio = typeof h === "number" && typeof w === "number" ? null : "2/3";

  // Variant-specific style overlays
  const vIdx = variant?.idx ?? -1;
  const variantLabel = variant?.name || "";
  const variantDesc = variant?.desc || "";

  // Per-variant deltaColor (treatment) used to differentiate the 4 cards
  const variantPalette = [
    { name: "promo",     overlay: "rgba(255,255,255,0.10)", ctaBg: "rgba(255,255,255,0.95)", ctaFg: c.bg, badge: null },
    { name: "urgency",   overlay: "rgba(255,255,255,0.10)", ctaBg: "#1A1410", ctaFg: "#FBF8F2", badge: { text: isZh ? "限时" : "LIMITED", color: "#D14A1F" } },
    { name: "benefit",   overlay: "rgba(255,255,255,0.18)", ctaBg: "rgba(251,248,242,0.95)", ctaFg: "#1A1410", badge: null },
    { name: "onboarding",overlay: "rgba(255,255,255,0.14)", ctaBg: c.ink,  ctaFg: c.bg, badge: null },
  ];
  const vp = vIdx >= 0 ? variantPalette[vIdx % 4] : variantPalette[0];

  // Decorative product shape: a soft rounded rectangle in lighter brand tone.
  // For variants we tilt/scale it slightly so the 4 cards feel distinct.
  const shapeStyles = [
    { transform: "rotate(-4deg) translateY(6%)",  size: 0.62 },
    { transform: "rotate(2deg)  translateY(-2%)", size: 0.68 },
    { transform: "rotate(-2deg) translateY(8%)",  size: 0.55 },
    { transform: "rotate(6deg)  translateY(4%)",  size: 0.65 },
  ];
  const sh = vIdx >= 0 ? shapeStyles[vIdx % 4] : { transform: "rotate(-3deg)", size: 0.6 };

  // CTA defaults — picked from headline tone or fall back
  const ctaDefaults = [
    { en: "Try it →",      zh: "立刻试一下 →" },
    { en: "Shop now →",    zh: "立即购买 →" },
    { en: "See how →",     zh: "看怎么做 →" },
    { en: "Get started →", zh: "开始 →" },
  ];
  const ctaDef = vIdx >= 0 ? ctaDefaults[vIdx % 4] : ctaDefaults[0];
  const ctaText = isZh ? ctaDef.zh : ctaDef.en;

  const isVideo = ad?.type === "video";

  return (
    <div
      className="ad-creative"
      style={{
        width: w, height: h,
        aspectRatio: ratio,
        position: "relative",
        borderRadius: 6,
        overflow: "hidden",
        background: `linear-gradient(155deg, ${c.bg} 0%, ${shadeColor(c.bg, -8)} 100%)`,
        color: c.ink,
        boxShadow: "0 1px 0 rgba(0,0,0,0.06), 0 14px 28px rgba(0,0,0,0.06)",
        fontFamily: "Source Serif 4, Georgia, serif",
      }}
    >
      {/* Decorative product shape */}
      <div
        aria-hidden
        style={{
          position: "absolute",
          left: "50%", top: "42%",
          width: `${Math.round(sh.size * 100)}%`,
          aspectRatio: "1 / 1",
          transform: `translate(-50%, -50%) ${sh.transform}`,
          background: `linear-gradient(160deg, ${shadeColor(c.bg, 18)} 0%, ${shadeColor(c.bg, -4)} 100%)`,
          borderRadius: "28%",
          boxShadow: `inset 0 0 0 1px ${shadeColor(c.bg, 24)}`,
        }}
      />
      {/* Inner glow / vignette */}
      <div
        aria-hidden
        style={{
          position: "absolute", inset: 0,
          background: "radial-gradient(120% 80% at 30% 20%, rgba(255,255,255,0.18), transparent 60%), radial-gradient(120% 80% at 80% 110%, rgba(0,0,0,0.18), transparent 60%)",
        }}
      />
      {/* Subtle overlay for variant treatment */}
      <div aria-hidden style={{ position: "absolute", inset: 0, background: vp.overlay }} />

      {/* Top row: ad code or variant chip + format */}
      <div style={{ position: "absolute", top: 10, left: 12, right: 12, display: "flex", justifyContent: "space-between", alignItems: "center", fontFamily: "IBM Plex Mono, monospace" }}>
        <span style={{ fontSize: 9, letterSpacing: ".1em", color: c.ink, opacity: 0.78, textTransform: "uppercase" }}>
          {variant ? `${isZh ? "变体" : "Variant"} · ${String(vIdx + 1).padStart(2, "0")}` : code}
        </span>
        <span style={{ fontSize: 9, letterSpacing: ".1em", color: c.ink, opacity: 0.78, textTransform: "uppercase" }}>
          {isVideo ? (isZh ? "▶ 视频" : "▶ VIDEO") : (isZh ? "图片" : "IMAGE")}
        </span>
      </div>

      {/* Limited-time ribbon (variant 1 only) */}
      {vp.badge && (
        <div style={{
          position: "absolute", top: 32, left: 12,
          background: vp.badge.color, color: "#FBF8F2",
          fontFamily: "IBM Plex Mono, monospace", fontSize: 9, fontWeight: 600,
          letterSpacing: ".1em", padding: "3px 8px", borderRadius: 2,
        }}>{vp.badge.text}</div>
      )}

      {/* Bottom panel: headline + sub + CTA */}
      <div
        style={{
          position: "absolute",
          left: 12, right: 12, bottom: 12,
          padding: 12,
          background: "rgba(0,0,0,0.06)",
          backdropFilter: "blur(4px)",
          WebkitBackdropFilter: "blur(4px)",
          borderRadius: 4,
          border: "1px solid rgba(255,255,255,0.18)",
          color: c.ink,
        }}
      >
        <div
          style={{
            fontFamily: "Source Serif 4, Georgia, serif",
            fontWeight: 600,
            fontSize: variant ? (big ? 18 : 14) : (big ? 22 : 14),
            lineHeight: 1.18,
            color: c.ink,
            display: "-webkit-box",
            WebkitLineClamp: variant ? 2 : 3,
            WebkitBoxOrient: "vertical",
            overflow: "hidden",
            wordBreak: "break-word",
          }}
        >
          {variant ? variantLabel : headline}
        </div>
        {(variant ? variantDesc : sub) && (
          <div
            style={{
              fontFamily: "IBM Plex Mono, monospace",
              fontSize: variant ? 9 : 10,
              marginTop: 6,
              color: c.ink, opacity: 0.78,
              letterSpacing: ".02em",
              display: "-webkit-box",
              WebkitLineClamp: 2,
              WebkitBoxOrient: "vertical",
              overflow: "hidden",
            }}
          >
            {variant ? variantDesc : sub}
          </div>
        )}
        {/* CTA pill */}
        <div
          style={{
            marginTop: 10,
            display: "inline-flex",
            alignItems: "center",
            padding: "5px 12px",
            background: vp.ctaBg,
            color: vp.ctaFg,
            borderRadius: 999,
            fontFamily: "IBM Plex Sans, sans-serif",
            fontSize: 11,
            fontWeight: 600,
            letterSpacing: ".02em",
            border: "1px solid rgba(0,0,0,0.08)",
          }}
        >
          {ctaText}
        </div>
      </div>
    </div>
  );
}

// Shade a hex/rgb color by a percentage (-100..100). Used by AdCreative.
function shadeColor(input, percent) {
  const m = input.match(/^#([0-9a-f]{6})$/i);
  if (!m) return input;
  const num = parseInt(m[1], 16);
  let r = (num >> 16) & 0xff;
  let g = (num >> 8) & 0xff;
  let b = num & 0xff;
  const amt = Math.round(2.55 * percent);
  r = Math.max(0, Math.min(255, r + amt));
  g = Math.max(0, Math.min(255, g + amt));
  b = Math.max(0, Math.min(255, b + amt));
  return "#" + ((r << 16) | (g << 8) | b).toString(16).padStart(6, "0");
}

function VariantPlaceholder({ v, w, h, accent }) {
  // a slightly more designed placeholder for Variants — emulates an ad creative
  const styles = [
    // 0 Explore Now — clean centered
    { bg: "var(--card-stripe)", title: "EXPLORE NOW →", layout: "centered" },
    // 1 Limited Time — red banner
    { bg: "var(--card-stripe)", title: "−30% TODAY ONLY", layout: "ribbon" },
    // 2 Save 10 Hours — benefit
    { bg: "var(--card-stripe)", title: "SAVE 10 HRS / WEEK", layout: "bottom" },
    // 3 Get Started — onboarding
    { bg: "var(--card-stripe)", title: "GET STARTED →", layout: "split" },
  ];
  const s = styles[v.idx % 4];
  return (
    <div className="thumb" style={{ width: w, height: h, background: s.bg, borderRadius: 4 }}>
      <div className="thumb-body" style={{ fontSize: 10, padding: 10 }}>
        <div className="thumb-meta">
          <span className="mono" style={{ fontSize: 9, color: "var(--ink-3)" }}>VARIANT · {String(v.idx + 1).padStart(2, "0")}</span>
          <span className="mono" style={{ fontSize: 9, color: "var(--ink-3)" }}>2:3</span>
        </div>
        <div style={{ display: "flex", flexDirection: "column", justifyContent: "flex-end", alignItems: "flex-start", height: "100%", gap: 4 }}>
          {s.layout === "ribbon" && (
            <div style={{ background: "var(--accent)", color: "var(--accent-ink)", padding: "2px 8px", fontFamily: "IBM Plex Mono, monospace", fontSize: 10, letterSpacing: ".08em", marginBottom: 6 }}>
              {s.title}
            </div>
          )}
          {s.layout !== "ribbon" && (
            <div style={{
              fontFamily: "Source Serif 4, Georgia, serif",
              fontWeight: 600,
              fontSize: Math.max(16, Math.floor((typeof w === "number" ? w : 280) / 12)),
              lineHeight: 1.0,
              color: "var(--ink)",
              padding: "4px 6px",
              background: "rgba(255,255,255,0.78)",
            }}>
              {v.name}
            </div>
          )}
          <div className="mono" style={{ fontSize: 9, color: "var(--ink-3)", padding: "0 6px" }}>{v.desc}</div>
        </div>
      </div>
    </div>
  );
}

function Pill({ children, variant = "default", style }) {
  return <span className={cx("pill", variant !== "default" && variant)} style={style}>{children}</span>;
}

function Grade({ value, size = 28 }) {
  // Round 4: newly-created ads have no grade yet ("pending"). Render a
  // dashed placeholder instead of crashing on null.
  if (!value) {
    return (
      <span
        className="grade-chip"
        title={typeof window !== "undefined" && window.t ? window.t("No grade yet — waiting for first delivery window", "暂无评级 —— 等第一个投放窗口的数据") : "pending"}
        style={{
          width: size, height: size, fontSize: size * 0.45,
          background: "var(--bg-deep)",
          color: "var(--ink-3)",
          border: "1.5px dashed var(--rule-strong)",
          fontWeight: 400,
        }}
      >—</span>
    );
  }
  return <span className={cx("grade-chip", value.toLowerCase())} style={{ width: size, height: size, fontSize: size * 0.7 }}>{value}</span>;
}

function SubGrade({ label, value }) {
  if (!value) {
    return (
      <span className="subgrade">
        <span className="label" style={{ fontSize: 9, letterSpacing: ".1em" }}>{label}</span>
        <span className="v" style={{ background: "var(--bg-deep)", color: "var(--ink-3)", border: "1px dashed var(--rule-strong)" }}>—</span>
      </span>
    );
  }
  return (
    <span className="subgrade">
      <span className="label" style={{ fontSize: 9, letterSpacing: ".1em" }}>{label}</span>
      <span className={cx("v", value.toLowerCase())}>{value}</span>
    </span>
  );
}

function Check({ on, onClick, label }) {
  return (
    <span
      role="checkbox" aria-checked={on}
      className={cx("check", on && "on")}
      onClick={(e) => { e.stopPropagation(); onClick(); }}
      aria-label={label}
    />
  );
}

function Icon({ name, size = 14 }) {
  const paths = {
    image: <><rect x="3" y="3" width="18" height="18" rx="2" stroke="currentColor" strokeWidth="1.5" fill="none"/><circle cx="8.5" cy="9.5" r="1.5" stroke="currentColor" strokeWidth="1.5"/><path d="M21 15l-5-5L5 21" stroke="currentColor" strokeWidth="1.5" fill="none"/></>,
    video: <><rect x="3" y="5" width="14" height="14" rx="2" stroke="currentColor" strokeWidth="1.5" fill="none"/><path d="M17 10l4-2v8l-4-2" stroke="currentColor" strokeWidth="1.5" fill="none" strokeLinejoin="round"/></>,
    close: <><path d="M5 5l14 14M19 5L5 19" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round"/></>,
    chev: <><path d="M9 6l6 6-6 6" stroke="currentColor" strokeWidth="1.5" fill="none" strokeLinecap="round" strokeLinejoin="round"/></>,
    chevDown: <><path d="M6 9l6 6 6-6" stroke="currentColor" strokeWidth="1.5" fill="none" strokeLinecap="round" strokeLinejoin="round"/></>,
    plus: <><path d="M12 5v14M5 12h14" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round"/></>,
    star: <><path d="M12 3l2.7 5.7 6.3.9-4.5 4.4 1.1 6.2-5.6-3-5.6 3 1.1-6.2L3 9.6l6.3-.9L12 3z" stroke="currentColor" strokeWidth="1.5" fill="none" strokeLinejoin="round"/></>,
    starFill: <><path d="M12 3l2.7 5.7 6.3.9-4.5 4.4 1.1 6.2-5.6-3-5.6 3 1.1-6.2L3 9.6l6.3-.9L12 3z" fill="currentColor"/></>,
    download: <><path d="M12 4v12m0 0l-4-4m4 4l4-4M5 20h14" stroke="currentColor" strokeWidth="1.5" fill="none" strokeLinecap="round" strokeLinejoin="round"/></>,
    compare: <><rect x="3" y="5" width="8" height="14" stroke="currentColor" strokeWidth="1.5" fill="none"/><rect x="13" y="5" width="8" height="14" stroke="currentColor" strokeWidth="1.5" fill="none"/></>,
    refresh: <><path d="M4 12a8 8 0 0 1 14-5.3L20 9M20 4v5h-5M20 12a8 8 0 0 1-14 5.3L4 15M4 20v-5h5" stroke="currentColor" strokeWidth="1.5" fill="none" strokeLinecap="round" strokeLinejoin="round"/></>,
    sparkle: <><path d="M12 3v6M12 15v6M3 12h6M15 12h6M5.6 5.6l4.2 4.2M14.2 14.2l4.2 4.2M5.6 18.4l4.2-4.2M14.2 9.8l4.2-4.2" stroke="currentColor" strokeWidth="1.3" strokeLinecap="round"/></>,
    inbox: <><path d="M3 13l3-8h12l3 8v6H3v-6z" stroke="currentColor" strokeWidth="1.5" fill="none" strokeLinejoin="round"/><path d="M3 13h5l1 2h6l1-2h5" stroke="currentColor" strokeWidth="1.5" fill="none"/></>,
    info: <><circle cx="12" cy="12" r="9" stroke="currentColor" strokeWidth="1.5" fill="none"/><path d="M12 11v6M12 8v.5" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round"/></>,
    help: <><circle cx="12" cy="12" r="9" stroke="currentColor" strokeWidth="1.5" fill="none"/><path d="M9.3 9.5a2.7 2.7 0 0 1 5.4.2c0 1.5-2.2 1.9-2.7 3.3M12 17v.5" stroke="currentColor" strokeWidth="1.5" fill="none" strokeLinecap="round"/></>,
    filter: <><path d="M4 5h16l-6 8v6l-4-2v-4L4 5z" stroke="currentColor" strokeWidth="1.5" fill="none" strokeLinejoin="round"/></>,
    columns: <><rect x="3" y="4" width="18" height="16" stroke="currentColor" strokeWidth="1.5" fill="none"/><path d="M9 4v16M15 4v16" stroke="currentColor" strokeWidth="1.5"/></>,
    arrow: <><path d="M4 12h16M14 6l6 6-6 6" stroke="currentColor" strokeWidth="1.5" fill="none" strokeLinecap="round" strokeLinejoin="round"/></>,
    bolt: <><path d="M13 3L4 14h7l-1 7 9-11h-7l1-7z" stroke="currentColor" strokeWidth="1.5" fill="none" strokeLinejoin="round"/></>,
    play: <><path d="M6 4v16l14-8L6 4z" fill="currentColor"/></>,
    warn: <><path d="M12 3l10 18H2L12 3z" stroke="currentColor" strokeWidth="1.5" fill="none" strokeLinejoin="round"/><path d="M12 10v5M12 18v.5" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round"/></>,
    check: <><path d="M5 12l5 5L20 7" stroke="currentColor" strokeWidth="1.7" fill="none" strokeLinecap="round" strokeLinejoin="round"/></>,
    thumbUp: <><path d="M7 10v10H4V10h3zm3 10V9.5L13 3l1 1 .5 4H20a1 1 0 0 1 1 1l-1 8a2 2 0 0 1-2 2h-8z" stroke="currentColor" strokeWidth="1.4" fill="none" strokeLinejoin="round"/></>,
    thumbDown: <><path d="M7 14V4H4v10h3zm3-10v10.5L13 21l1-1 .5-4H20a1 1 0 0 0 1-1l-1-8a2 2 0 0 0-2-2h-8z" stroke="currentColor" strokeWidth="1.4" fill="none" strokeLinejoin="round"/></>,
  };
  return (
    <svg width={size} height={size} viewBox="0 0 24 24" style={{ display: "inline-block", verticalAlign: "-2px" }}>
      {paths[name]}
    </svg>
  );
}

function ScoreChip({ score, size = "md" }) {
  let cls = "a";
  if (score < 65) cls = "d";
  else if (score < 75) cls = "c";
  else if (score < 85) cls = "b";
  const sz = size === "sm" ? { padding: "2px 6px", fontSize: 10 } : { padding: "3px 8px", fontSize: 11 };
  return (
    <span style={{
      display: "inline-flex", alignItems: "center", gap: 4,
      fontFamily: "IBM Plex Mono, monospace",
      background: "var(--card)", border: "1px solid var(--rule-strong)",
      color: cls === "a" ? "var(--grade-a)" : cls === "b" ? "var(--grade-b)" : cls === "c" ? "var(--grade-c)" : "var(--grade-d)",
      borderRadius: 3, fontWeight: 600,
      ...sz,
    }}>
      <span style={{ opacity: 0.7, fontWeight: 400 }}>AI</span>
      {score}
    </span>
  );
}

// MetricDelta — replaces ScoreChip. Shows the delta on the metric the rec
// actually targeted, with a confidence-aware hover popover.
function MetricDelta({ metricName, delta, baseline, projected, confidence = "med", basis = 0, size = "md", variant = "chip" }) {
  const [hover, setHover] = useState(false);
  const pos = delta >= 0;
  const cls = delta >= 35 ? "a" : delta >= 18 ? "b" : delta >= 0 ? "c" : "d";
  const color = cls === "a" ? "var(--grade-a)" : cls === "b" ? "var(--grade-b)" : cls === "c" ? "var(--grade-c)" : "var(--grade-d)";
  const confLevel = confidence === "high" ? 3 : confidence === "med" ? 2 : 1;

  const sz = size === "sm" ? { padding: "2px 6px", fontSize: 10 } : { padding: "3px 8px", fontSize: 11 };
  const display = `${pos ? "+" : ""}${delta}% ${metricName}`;

  return (
    <span style={{ position: "relative", display: "inline-block" }}
      onMouseEnter={() => setHover(true)} onMouseLeave={() => setHover(false)}>
      <span style={{
        display: "inline-flex", alignItems: "center", gap: 4,
        fontFamily: "IBM Plex Mono, monospace",
        background: "var(--card)", border: "1px solid var(--rule-strong)",
        color, borderRadius: 3, fontWeight: 600,
        whiteSpace: "nowrap",
        ...sz,
        cursor: "help",
      }}>
        {display}
      </span>
      {hover && (
        <span className="popover" style={{ left: 0, top: "calc(100% + 6px)", fontFamily: "IBM Plex Sans, sans-serif", color: "var(--ink)" }}>
          <div className="label" style={{ marginBottom: 4 }}>Predicted change · vs original</div>
          <div style={{ display: "flex", alignItems: "baseline", gap: 6 }}>
            <span className="mono" style={{ fontSize: 11, color: "var(--ink-3)" }}>{baseline}</span>
            <span style={{ color: "var(--ink-3)" }}>→</span>
            <span className="mono" style={{ fontSize: 16, fontWeight: 600, color }}>{projected}</span>
            <span className="mono" style={{ fontSize: 10, color: "var(--ink-3)" }}>{metricName.toLowerCase().includes("rate") || metricName.toLowerCase().includes("ctr") || metricName.toLowerCase().includes("thumb") || metricName.toLowerCase().includes("hook") || metricName.toLowerCase().includes("hold") ? "%" : ""}</span>
          </div>
          <div style={{ display: "flex", alignItems: "center", gap: 8, marginTop: 6, fontSize: 11, color: "var(--ink-3)" }}>
            <span>Confidence</span>
            <span>
              {[1,2,3].map(i => <span key={i} className={"conf-dot " + (i <= confLevel ? "on" : "")} />)}
            </span>
            <span style={{ textTransform: "capitalize" }}>{confidence}</span>
          </div>
          <div style={{ fontSize: 11, color: "var(--ink-3)", marginTop: 4 }}>
            Based on {basis} similar creatives in your cohort.
          </div>
        </span>
      )}
    </span>
  );
}

// Column header with a «?» icon that toggles an explanatory popover.
// Used for Grade / Sub-scores etc. where the meaning isn't self-evident.
function HelpHeader({ label, children, align = "left" }) {
  const [open, setOpen] = useState(false);
  const ref = useRef(null);
  useEffect(() => {
    if (!open) return;
    const close = (e) => { if (ref.current && !ref.current.contains(e.target)) setOpen(false); };
    document.addEventListener("mousedown", close);
    return () => document.removeEventListener("mousedown", close);
  }, [open]);
  return (
    <span ref={ref} style={{ position: "relative", display: "inline-flex", alignItems: "center", gap: 4 }}>
      <span>{label}</span>
      <button
        type="button"
        onClick={(e) => { e.stopPropagation(); setOpen(o => !o); }}
        aria-label="What does this mean?"
        style={{
          width: 14, height: 14,
          display: "inline-grid", placeItems: "center",
          borderRadius: "50%",
          border: "1px solid var(--rule-strong)",
          background: open ? "var(--accent)" : "var(--card)",
          color: open ? "var(--accent-ink)" : "var(--ink-3)",
          cursor: "pointer", padding: 0, lineHeight: 1,
          fontFamily: "IBM Plex Sans, sans-serif",
          fontSize: 9, fontWeight: 600,
          textTransform: "none", letterSpacing: 0,
        }}
        title="What does this mean?"
      >?</button>
      {open && (
        <span className="popover" style={{
          top: "calc(100% + 6px)",
          ...(align === "right" ? { right: 0 } : { left: 0 }),
          minWidth: 280, maxWidth: 340,
          fontFamily: "IBM Plex Sans, sans-serif",
          color: "var(--ink)",
          letterSpacing: 0,
          textTransform: "none",
          fontSize: 12, fontWeight: 400,
          padding: "12px 14px",
        }}>{children}</span>
      )}
    </span>
  );
}

// Resolve a recommendation title to the right language. Looks up by recId in
// REC_TEMPLATES, falling back to the literal recTitle field on the task.
function localizedRecTitle(task) {
  if (!task) return "";
  const recs = (typeof window !== "undefined" && window.REC_TEMPLATES) || [];
  const rec = recs.find(r => r.id === task.recId);
  const isZh = getLang() === "zh";
  if (rec) return isZh && rec.titleZh ? rec.titleZh : rec.title;
  // Fallbacks for inline tasks (e.g. cloned winners) that stash recTitleZh.
  if (isZh && task.recTitleZh) return task.recTitleZh;
  return task.recTitle || "";
}

// Format helpers
function fmtMoney(n) {
  return "$" + n.toFixed(2).replace(/\B(?=(\d{3})+(?!\d))/g, ",");
}
function fmtRoas(n) { return n.toFixed(2); }
function relTime(ts) {
  const diff = Date.now() - ts;
  const m = Math.round(diff / 60000);
  if (m < 1) return "just now";
  if (m < 60) return `${m}m ago`;
  const h = Math.round(m / 60);
  if (h < 24) return `${h}h ago`;
  return `${Math.round(h / 24)}d ago`;
}

Object.assign(window, {
  cx, Thumb, VariantPlaceholder, AdCreative, Pill, Grade, SubGrade, Check, Icon, ScoreChip, MetricDelta,
  HelpHeader,
  fmtMoney, fmtRoas, relTime, localizedRecTitle,
  getLang, setLang, useLang, useT, t,
});
