// ====================================================================
// ENHANCE — Tilt3D (tarjetas 3D), CommAnalysis (Análisis de Comunicación
// estructurado) y WordCloud (mapa de palabras de reseñas).
// Estilo: dark-luxury Blackwell (uppercase tracking, numeración, hairlines).
// ====================================================================

// ---------- Tarjeta con inclinación 3D + glare que sigue al cursor ----------
function Tilt3D({ children, max = 8, className = "", style, onClick, onMouseEnter, onMouseLeave, onMouseMove, ...props }) {
  const ref = React.useRef(null);
  const onMove = (e) => {
    const el = ref.current;
    if (!el) return;
    const r = el.getBoundingClientRect();
    const px = (e.clientX - r.left) / r.width - 0.5;
    const py = (e.clientY - r.top) / r.height - 0.5;
    el.style.transform = `perspective(900px) rotateX(${(-py * max).toFixed(2)}deg) rotateY(${(px * max).toFixed(2)}deg) translateY(-2px)`;
    const g = el.querySelector(".t3d-glare");
    if (g) {
      g.style.opacity = "1";
      g.style.background = `radial-gradient(380px circle at ${e.clientX - r.left}px ${e.clientY - r.top}px, rgba(255,255,255,.09), transparent 60%)`;
    }
    if (onMouseMove) onMouseMove(e);
  };
  const onLeave = (e) => {
    const el = ref.current;
    if (!el) return;
    el.style.transform = "perspective(900px)";
    const g = el.querySelector(".t3d-glare");
    if (g) g.style.opacity = "0";
    if (onMouseLeave) onMouseLeave(e);
  };
  const onEnter = (e) => {
    if (onMouseEnter) onMouseEnter(e);
  };
  return (
    <div ref={ref} className={`t3d ${className}`} style={style} onMouseMove={onMove} onMouseLeave={onLeave} onMouseEnter={onEnter} onClick={onClick} {...props}>
      {children}
      <div className="t3d-glare"></div>
    </div>
  );
}

// ---------- Mini renderer de markdown inline (negrita/cursiva/código) ----------
function InlineMd({ text }) {
  if (!text) return null;
  const parts = String(text).split(/(\*\*[^*]+\*\*|\*[^*]+\*|`[^`]+`)/g).filter(Boolean);
  return (
    <React.Fragment>
      {parts.map((p, i) => {
        if (p.startsWith("**") && p.endsWith("**")) return <strong key={i} style={{ color: "var(--white)", fontWeight: 600 }}>{p.slice(2, -2)}</strong>;
        if (p.startsWith("*") && p.endsWith("*") && p.length > 2) return <em key={i}>{p.slice(1, -1)}</em>;
        if (p.startsWith("`") && p.endsWith("`")) return <code key={i} style={{ fontFamily: "var(--font-mono)", fontSize: ".92em", background: "rgba(255,255,255,.06)", padding: "1px 5px", borderRadius: 5 }}>{p.slice(1, -1)}</code>;
        return p;
      })}
    </React.Fragment>
  );
}

// ====================================================================
// PARSER — Análisis de Comunicación Corporativa (6 secciones fijas)
// ====================================================================
function _caBullets(body) {
  const out = [];
  for (const line of String(body).split("\n")) {
    const m = line.trim().match(/^(?:[-*]|\d+[.)、])\s+(.*)$/);
    if (!m) continue;
    let item = m[1].trim();
    let title = "", text = item;
    const tm = item.match(/^\*\*(.+?)\*\*\s*(.*)$/);
    if (tm) { title = tm[1].replace(/[:：]\s*$/, ""); text = tm[2].trim().replace(/^[:：,，;；.]\s*/, ""); }
    if (title || text) out.push({ title, text });
  }
  return out;
}

function _caTable(body) {
  const rows = String(body).split("\n").map(l => l.trim())
    .filter(l => l.startsWith("|"))
    .filter(l => !/^\|[\s\-:|]+\|$/.test(l))
    .map(l => l.replace(/^\||\|$/g, "").split("|").map(c => c.trim()));
  if (rows.length < 2) return null;
  return rows.slice(1).map(r => ({
    num: r[0] || "", action: r[1] || "", channel: r[2] || "", priority: r[3] || "", term: r[4] || "",
  }));
}

function _caPriorityClass(p) {
  if (/alta|高/i.test(p)) return "hi";
  if (/media|中/i.test(p)) return "md";
  return "lo";
}

function parseCommAnalysis(md) {
  if (!md) return null;
  const text = String(md).replace(/\r\n/g, "\n");
  const titleM = text.match(/^#\s+(.+)$/m);
  const re = /^##\s*(\d+)[.、]?\s*(.+)$/gm;
  const found = [];
  let m;
  while ((m = re.exec(text)) !== null) found.push({ n: m[1], heading: m[2].trim(), start: m.index, end: re.lastIndex });
  if (found.length < 3) return null;
  const secs = {};
  found.forEach((s, i) => {
    const bodyEnd = i + 1 < found.length ? found[i + 1].start : text.length;
    secs[s.n] = { heading: s.heading, body: text.slice(s.end, bodyEnd).trim() };
  });

  // §1 Diagnóstico — párrafos
  const diagnosis = secs["1"]
    ? secs["1"].body.split(/\n{2,}/).map(p => p.trim()).filter(p => p && !p.startsWith("|"))
    : [];

  // §2 Riesgos / Oportunidades — bullets bajo sub-encabezados en negrita
  let risks = [], opps = [];
  if (secs["2"]) {
    let mode = "risk";
    for (const line of secs["2"].body.split("\n")) {
      const t = line.trim();
      const sub = t.match(/^\*\*(.+?)[:：]?\*\*\s*$/);
      if (sub) {
        mode = /oportunidad|机遇|机会|opportunit/i.test(sub[1]) ? "opp" : "risk";
        continue;
      }
      const b = t.match(/^[-*]\s+(.*)$/);
      if (!b) continue;
      let item = b[1].trim(), title = "", body = item;
      const tm = item.match(/^\*\*(.+?)\*\*\s*(.*)$/);
      if (tm) { title = tm[1].replace(/[:：]\s*$/, ""); body = tm[2].trim().replace(/^[:：,，;；.]\s*/, ""); }
      (mode === "opp" ? opps : risks).push({ title, text: body });
    }
  }

  // §3 Plan de acción — tabla markdown
  const actions = secs["3"] ? _caTable(secs["3"].body) : null;

  // §4 Mensajes clave — bullets o lista numerada (citas)
  const messages = secs["4"]
    ? _caBullets(secs["4"].body).map(b => {
        let s = (b.title ? `${b.title} ${b.text}` : b.text).trim();
        s = s.replace(/^\*+\s*/, "").replace(/\s*\*+$/, "");
        s = s.replace(/^["“«]\s*/, "").replace(/\s*["”»]$/, "");
        return s;
      }).filter(Boolean)
    : [];

  // §5 Qué NO hacer — bullets
  const donts = secs["5"] ? _caBullets(secs["5"].body) : [];

  // §6 Implicaciones — párrafos + recomendación ejecutiva
  let implications = [], recommendation = "";
  if (secs["6"]) {
    const raw = secs["6"].body;
    const rm = raw.match(/\*\*\s*(Recomendaci[oó]n ejecutiva[^:*：]*|核心高管建议|Executive recommendation)\s*[:：]?\s*\*\*\s*([\s\S]*)$/i);
    if (rm) {
      recommendation = rm[2].trim();
      implications = raw.slice(0, rm.index).split(/\n{2,}/).map(p => p.trim()).filter(Boolean);
    } else {
      implications = raw.split(/\n{2,}/).map(p => p.trim()).filter(Boolean);
    }
  }

  if (!diagnosis.length && !risks.length && !opps.length && !actions) return null;

  return {
    title: titleM ? titleM[1].trim() : "",
    headings: { h1: secs["1"]?.heading, h2: secs["2"]?.heading, h3: secs["3"]?.heading, h4: secs["4"]?.heading, h5: secs["5"]?.heading, h6: secs["6"]?.heading },
    diagnosis, risks, opps, actions, messages, donts, implications, recommendation,
  };
}

// ---------- Encabezado de sección estilo Blackwell ----------
function CaSecHead({ num, title, icon, color, bg, innerRef }) {
  return (
    <div className="ca-sec-head" ref={innerRef}>
      <span className="ca-ico" style={{ background: bg, color: color }}>
        {icon === "quote"
          ? <span style={{ fontFamily: "Georgia,serif", fontSize: 22, lineHeight: 1, marginTop: 6 }}>“</span>
          : <window.Ic name={icon} style={{ width: 17, height: 17 }} />}
      </span>
      <div>
        <div className="ca-num">{String(num).padStart(2, "0")}</div>
        <h3 style={{ fontSize: 16, margin: 0 }}>{title}</h3>
      </div>
      <div className="ca-rule"></div>
    </div>
  );
}

// ====================================================================
// COMM ANALYSIS — render estructurado e interactivo
// ====================================================================
function CommAnalysis({ md, lang }) {
  const data = React.useMemo(() => {
    try { return parseCommAnalysis(md); } catch (e) { console.warn("parseCommAnalysis:", e); return null; }
  }, [md]);
  const [prioFilter, setPrioFilter] = React.useState("all");
  const [copied, setCopied] = React.useState(-1);
  const secRefs = React.useRef({});

  // Fallback: si el markdown no tiene la estructura esperada, render clásico
  if (!data) {
    const pm = window.parseMarkdown;
    return (
      <div>
        <div className="ca-label">{lang === "zh" ? "企业传播战略分析" : "Análisis de Comunicación Corporativa"}</div>
        <div className="card card-pad">{pm ? pm(md) : <pre style={{ whiteSpace: "pre-wrap" }}>{md}</pre>}</div>
      </div>
    );
  }

  const zh = lang === "zh";
  const scrollTo = (k) => {
    const el = secRefs.current[k];
    if (el && el.scrollIntoView) el.scrollIntoView({ behavior: "smooth", block: "start" });
  };
  const copyMsg = (msg, i) => {
    try {
      navigator.clipboard.writeText(msg).then(() => {
        setCopied(i);
        setTimeout(() => setCopied(-1), 1600);
      });
    } catch (e) { /* clipboard no disponible */ }
  };

  const navItems = [
    data.diagnosis.length && { k: "d1", n: 1, label: data.headings.h1 },
    (data.risks.length || data.opps.length) && { k: "d2", n: 2, label: data.headings.h2 },
    data.actions && { k: "d3", n: 3, label: data.headings.h3 },
    data.messages.length && { k: "d4", n: 4, label: data.headings.h4 },
    data.donts.length && { k: "d5", n: 5, label: data.headings.h5 },
    (data.implications.length || data.recommendation) && { k: "d6", n: 6, label: data.headings.h6 },
  ].filter(Boolean);

  const prioLabel = (p) => {
    const cls = _caPriorityClass(p);
    return cls === "hi" ? (zh ? "高" : "Alta") : cls === "md" ? (zh ? "中" : "Media") : (zh ? "低" : "Baja");
  };
  const visibleActions = (data.actions || []).filter(a => prioFilter === "all" || _caPriorityClass(a.priority) === prioFilter);

  return (
    <div className="ca-wrap">
      {/* Header + navegación por secciones */}
      <div className="card card-pad ca-hero">
        <div className="ca-label">{zh ? "企业传播战略分析" : "Análisis de Comunicación Corporativa"}</div>
        <h2 style={{ fontSize: 21, margin: "6px 0 14px", letterSpacing: "-.02em" }}>
          {data.title.replace(/^(Análisis de Comunicación Corporativa|企业传播分析)\s*[:：]\s*/i, "")}
        </h2>
        <div className="ca-nav">
          {navItems.map(it => (
            <button key={it.k} onClick={() => scrollTo(it.k)}>
              <span className="ca-nav-num">{String(it.n).padStart(2, "0")}</span>
              {it.label}
            </button>
          ))}
        </div>
      </div>

      {/* 01 · Diagnóstico */}
      {data.diagnosis.length > 0 && (
        <section>
          <CaSecHead num={1} title={data.headings.h1} icon="compass" color="var(--gold-soft)" bg="rgba(224,161,6,.12)"
            innerRef={el => (secRefs.current.d1 = el)} />
          <div className="card card-pad ca-diag">
            {data.diagnosis.map((p, i) => (
              <p key={i} style={{ fontSize: 13.5, lineHeight: 1.7, color: "var(--ice)", margin: i ? "10px 0 0" : 0 }}>
                <InlineMd text={p} />
              </p>
            ))}
          </div>
        </section>
      )}

      {/* 02 · Riesgos y Oportunidades — tarjetas 3D */}
      {(data.risks.length > 0 || data.opps.length > 0) && (
        <section>
          <CaSecHead num={2} title={data.headings.h2} icon="alerts" color="var(--blue-soft)" bg="var(--blue-bg)"
            innerRef={el => (secRefs.current.d2 = el)} />
          <div className="ca-2col">
            <div>
              <div className="ca-col-title" style={{ color: "var(--neg)" }}>
                <window.Ic name="alerts" style={{ width: 13, height: 13 }} />
                {zh ? "声誉风险" : "Riesgos reputacionales"}
                <span className="ca-count" style={{ background: "var(--neg-bg)", color: "var(--neg)" }}>{data.risks.length}</span>
              </div>
              <div className="ca-stack">
                {data.risks.map((b, i) => (
                  <Tilt3D key={i} className="ca-bullet" style={{ borderLeft: "3px solid var(--neg)" }}>
                    {b.title && <h5>{b.title}</h5>}
                    <p><InlineMd text={b.text} /></p>
                  </Tilt3D>
                ))}
              </div>
            </div>
            <div>
              <div className="ca-col-title" style={{ color: "var(--brand)" }}>
                <window.Ic name="spark" style={{ width: 13, height: 13 }} />
                {zh ? "传播机遇" : "Oportunidades comunicacionales"}
                <span className="ca-count" style={{ background: "var(--pos-bg)", color: "var(--brand)" }}>{data.opps.length}</span>
              </div>
              <div className="ca-stack">
                {data.opps.map((b, i) => (
                  <Tilt3D key={i} className="ca-bullet" style={{ borderLeft: "3px solid var(--brand)" }}>
                    {b.title && <h5>{b.title}</h5>}
                    <p><InlineMd text={b.text} /></p>
                  </Tilt3D>
                ))}
              </div>
            </div>
          </div>
        </section>
      )}

      {/* 03 · Plan de Acción — tarjetas con filtro por prioridad */}
      {data.actions && data.actions.length > 0 && (
        <section>
          <CaSecHead num={3} title={data.headings.h3} icon="list" color="var(--brand)" bg="var(--pos-bg)"
            innerRef={el => (secRefs.current.d3 = el)} />
          <div className="filters" style={{ marginBottom: 12 }}>
            <span className="filter-lab">{zh ? "优先级" : "Prioridad"}</span>
            <div className="filter-group">
              {[["all", zh ? "全部" : "Todas"], ["hi", zh ? "高" : "Alta"], ["md", zh ? "中" : "Media"], ["lo", zh ? "低" : "Baja"]].map(([v, label]) => (
                <button key={v} className={prioFilter === v ? "active" : ""} onClick={() => setPrioFilter(v)}>{label}</button>
              ))}
            </div>
            <span style={{ fontSize: 11, color: "var(--ice-faint)", fontFamily: "var(--font-mono)" }}>
              {visibleActions.length}/{data.actions.length}
            </span>
          </div>
          <div className="ca-actions">
            {visibleActions.map((a, i) => {
              const cls = _caPriorityClass(a.priority);
              const pc = cls === "hi" ? "var(--neg)" : cls === "md" ? "var(--gold-soft)" : "var(--neu)";
              const pbg = cls === "hi" ? "var(--neg-bg)" : cls === "md" ? "var(--warn-bg)" : "var(--neu-bg)";
              return (
                <Tilt3D key={a.num + "-" + i} max={5} className="ca-action card">
                  <div className="ca-action-top">
                    <span className="ca-action-num">{a.num}</span>
                    <span className="ca-chip" style={{ background: pbg, color: pc }}>{prioLabel(a.priority)}</span>
                  </div>
                  <div className="ca-action-text"><InlineMd text={a.action} /></div>
                  <div className="ca-action-meta">
                    <span className="ca-chip" style={{ background: "var(--blue-bg)", color: "var(--blue-soft)" }}>{a.channel}</span>
                    <span className="ca-term">
                      <window.Ic name="clock" style={{ width: 11, height: 11 }} />
                      {a.term}
                    </span>
                  </div>
                </Tilt3D>
              );
            })}
          </div>
        </section>
      )}

      {/* 04 · Mensajes Clave — citas con botón copiar */}
      {data.messages.length > 0 && (
        <section>
          <CaSecHead num={4} title={data.headings.h4} icon="quote" color="var(--blue-soft)" bg="var(--blue-bg)"
            innerRef={el => (secRefs.current.d4 = el)} />
          <div className="ca-msgs">
            {data.messages.map((msg, i) => (
              <Tilt3D key={i} max={4} className="ca-msg card">
                <span className="ca-msg-quote">“</span>
                <p><InlineMd text={msg} /></p>
                <button className="btn btn-ghost ca-copy" onClick={() => copyMsg(msg.replace(/\*\*/g, ""), i)}>
                  <window.Ic name={copied === i ? "check" : "copy"} style={{ width: 12, height: 12 }} />
                  {copied === i ? (zh ? "已复制" : "¡Copiado!") : (zh ? "复制" : "Copiar")}
                </button>
              </Tilt3D>
            ))}
          </div>
        </section>
      )}

      {/* 05 · Qué NO Hacer */}
      {data.donts.length > 0 && (
        <section>
          <CaSecHead num={5} title={data.headings.h5} icon="ban" color="var(--neg)" bg="var(--neg-bg)"
            innerRef={el => (secRefs.current.d5 = el)} />
          <div className="ca-stack">
            {data.donts.map((b, i) => (
              <Tilt3D key={i} max={5} className="ca-bullet ca-dont">
                <span className="ca-dont-ico"><window.Ic name="ban" style={{ width: 14, height: 14 }} /></span>
                <div>
                  {b.title && <h5>{b.title}</h5>}
                  <p><InlineMd text={b.text} /></p>
                </div>
              </Tilt3D>
            ))}
          </div>
        </section>
      )}

      {/* 06 · Implicaciones + Recomendación ejecutiva */}
      {(data.implications.length > 0 || data.recommendation) && (
        <section>
          <CaSecHead num={6} title={data.headings.h6} icon="star" color="var(--gold-soft)" bg="rgba(224,161,6,.12)"
            innerRef={el => (secRefs.current.d6 = el)} />
          <div className="card card-pad">
            {data.implications.map((p, i) => (
              <p key={i} style={{ fontSize: 13.5, lineHeight: 1.7, color: "var(--ice)", margin: i ? "10px 0 0" : 0 }}>
                <InlineMd text={p} />
              </p>
            ))}
            {data.recommendation && (
              <div className="ca-reco">
                <div className="ca-reco-label">
                  <window.Ic name="star" style={{ width: 13, height: 13 }} />
                  {zh ? "核心高管建议" : "Recomendación ejecutiva principal"}
                </div>
                <p style={{ fontSize: 14, lineHeight: 1.7, color: "var(--white)", margin: 0 }}>
                  <InlineMd text={data.recommendation} />
                </p>
              </div>
            )}
          </div>
        </section>
      )}
    </div>
  );
}

// ====================================================================
// WORD CLOUD — mapa de palabras más frecuentes en reseñas
// ====================================================================
const WC_STOP = new Set(("el la los las lo un una unos unas de del al a ante bajo con contra desde durante en entre hacia hasta mediante para por " +
  "segun según sin sobre tras que qué quien quién quienes cual cuál cuales como cómo cuando cuándo donde dónde y e ni o u pero aunque sino porque " +
  "pues si sí no ya mas más menos muy mucho mucha muchos muchas poco poca pocos pocas tan tanto tanta tantos tantas todo toda todos todas otro otra " +
  "otros otras mismo misma mismos mismas este esta estos estas ese esa esos esas aquel aquella aquellos aquellas esto eso aquello mi mis tu tus su " +
  "sus nuestro nuestra nuestros nuestras me te se nos os le les yo él ella ellos ellas usted ustedes nosotros vosotros ser es soy eres somos son era " +
  "eran fue fueron sea sean siendo sido estar estoy esta está están estaba estaban estuvo estuve haber he ha han hay había habían hube tener tengo " +
  "tiene tienen tenia tenía tenían tuve tuvo hacer hago hace hacen hacia hacía hicieron hizo ir voy va van iba fui poder puedo puede pueden podía " +
  "pudo puedes querer quiero quiere quieren decir digo dice dicen dijo dar doy da dan dio ver veo ve ven vio saber sé sabe saben supo deber debo " +
  "debe deben así solo sólo también tampoco además luego entonces ahora antes después siempre nunca jamás aquí ahí allí allá bien casi cada vez " +
  "veces día días etc uno dos tres cuatro cinco vez algo alguien nada nadie ninguna ningún ningun cualquier ahi aun aún les esto estás estás vas " +
  "the and for with that this you your they have has not are was were but app aplicacion aplicación apps").split(/\s+/));

function _wcCompute(reviews) {
  const freq = {};
  for (const m of reviews) {
    const ex = m.excerpt;
    const txt = (typeof ex === "string" ? ex : (ex && (ex.es || ex.zh))) || "";
    const tokens = txt.toLowerCase().match(/[a-záéíóúüñ]{3,}/g) || [];
    const seen = new Set();
    for (const tk of tokens) {
      if (WC_STOP.has(tk)) continue;
      if (!freq[tk]) freq[tk] = { count: 0, docs: 0, neg: 0, pos: 0 };
      freq[tk].count++;
      if (!seen.has(tk)) {
        seen.add(tk);
        freq[tk].docs++;
        if (m.sentiment === "neg") freq[tk].neg++;
        else if (m.sentiment === "pos") freq[tk].pos++;
      }
    }
  }
  return Object.entries(freq)
    .filter(([, v]) => v.docs >= 2)
    .sort((a, b) => b[1].count - a[1].count)
    .slice(0, 44)
    .map(([text, v]) => ({ text, count: v.count, docs: v.docs, neg: v.neg, pos: v.pos }));
}

// Mide texto real usando canvas para evitar estimaciones imprecisas
let _wcCtx = null;
function _wcMeasure(text, size) {
  if (!_wcCtx) {
    const c = document.createElement('canvas');
    _wcCtx = c.getContext('2d');
  }
  _wcCtx.font = `800 ${size}px 'Arial', sans-serif`;
  const m = _wcCtx.measureText(text);
  return m.width;
}

// Coloca palabras en espiral desde el centro evitando colisiones
function _wcLayout(words, W, H) {
  if (!words.length || W < 120) return [];
  const max = words[0].count;
  const placed = [];
  const cx = W / 2, cy = H / 2;
  words.forEach((w, rank) => {
    const size = Math.round(13 + 33 * Math.sqrt(w.count / max));
    const fw = _wcMeasure(w.text, size) + 14;
    const fh = size * 1.28;
    let t = 0;
    while (t < 16000) {
      const r = 2.1 * Math.sqrt(t);
      const a = t * 0.42;
      const x = cx + r * Math.cos(a) * 1.7;
      const y = cy + r * Math.sin(a);
      const rect = { l: x - fw / 2, t: y - fh / 2, r: x + fw / 2, b: y + fh / 2 };
      if (rect.l >= 2 && rect.r <= W - 2 && rect.t >= 2 && rect.b <= H - 2 &&
        !placed.some(p => !(rect.r < p.l - 2 || rect.l > p.r + 2 || rect.b < p.t - 2 || rect.t > p.b + 2))) {
        placed.push({ ...rect, x, y, size, rank, word: w });
        return;
      }
      t += 2;
    }
  });
  return placed;
}

function WordCloud({ reviews, lang, active, onWord }) {
  const zh = lang === "zh";
  const en = lang === "en";
  const targetLang = zh ? "zh-CN" : en ? "en" : null;
  const boxRef = React.useRef(null);
  const [width, setWidth] = React.useState(0);
  const H = 300;

  React.useLayoutEffect(() => {
    const measure = () => { if (boxRef.current) setWidth(boxRef.current.clientWidth); };
    measure();
    window.addEventListener("resize", measure);
    return () => window.removeEventListener("resize", measure);
  }, []);

  const words = React.useMemo(() => _wcCompute(reviews || []), [reviews]);

  const cacheKey = targetLang === "zh-CN" ? "esToZh" : "esToEn";
  const reverseCacheKey = targetLang === "zh-CN" ? "zhToEs" : "enToEs";

  const [translations, setTranslations] = React.useState(() => {
    if (!window._WC_TRANSLATIONS) window._WC_TRANSLATIONS = {};
    if (!window._WC_TRANSLATIONS.esToZh) window._WC_TRANSLATIONS.esToZh = {};
    if (!window._WC_TRANSLATIONS.zhToEs) window._WC_TRANSLATIONS.zhToEs = {};
    if (!window._WC_TRANSLATIONS.esToEn) window._WC_TRANSLATIONS.esToEn = {};
    if (!window._WC_TRANSLATIONS.enToEs) window._WC_TRANSLATIONS.enToEs = {};
    return window._WC_TRANSLATIONS[cacheKey] || {};
  });

  React.useEffect(() => {
    if (!targetLang || !words.length) return;

    if (!window._WC_TRANSLATIONS) window._WC_TRANSLATIONS = {};
    if (!window._WC_TRANSLATIONS.esToZh) window._WC_TRANSLATIONS.esToZh = {};
    if (!window._WC_TRANSLATIONS.zhToEs) window._WC_TRANSLATIONS.zhToEs = {};
    if (!window._WC_TRANSLATIONS.esToEn) window._WC_TRANSLATIONS.esToEn = {};
    if (!window._WC_TRANSLATIONS.enToEs) window._WC_TRANSLATIONS.enToEs = {};

    const missing = words.map(w => w.text).filter(txt => !window._WC_TRANSLATIONS[cacheKey][txt]);

    if (missing.length === 0) {
      setTranslations({ ...window._WC_TRANSLATIONS[cacheKey] });
      return;
    }

    const translateBatch = async (chunk) => {
      const params = chunk.map(w => `q=${encodeURIComponent(w)}`).join('&');
      const url = `https://translate.googleapis.com/translate_a/single?client=gtx&sl=es&tl=${targetLang}&dt=t&${params}`;
      try {
        const res = await fetch(url);
        if (!res.ok) return;
        const data = await res.json();
        
        if (Array.isArray(data)) {
          let joinedTranslation = "";
          if (Array.isArray(data[0])) {
            joinedTranslation = data[0].map(x => (Array.isArray(x) && x[0]) ? x[0] : '').join('');
          }
          const translatedArray = joinedTranslation.split('\n');
          
          if (translatedArray.length === chunk.length) {
            chunk.forEach((original, idx) => {
              const translated = translatedArray[idx].trim();
              if (translated) {
                window._WC_TRANSLATIONS[cacheKey][original] = translated;
                window._WC_TRANSLATIONS[reverseCacheKey][translated.toLowerCase()] = original;
              }
            });
          } else {
            // Fallback: translate individually
            for (const original of chunk) {
              const urlSingle = `https://translate.googleapis.com/translate_a/single?client=gtx&sl=es&tl=${targetLang}&dt=t&q=${encodeURIComponent(original)}`;
              const resSingle = await fetch(urlSingle);
              if (resSingle.ok) {
                const dataSingle = await resSingle.json();
                const translated = (Array.isArray(dataSingle[0]) ? dataSingle[0] : [])
                  .map(x => (Array.isArray(x) && x[0]) ? x[0] : '')
                  .join('').trim();
                if (translated) {
                  window._WC_TRANSLATIONS[cacheKey][original] = translated;
                  window._WC_TRANSLATIONS[reverseCacheKey][translated.toLowerCase()] = original;
                }
              }
            }
          }
        }
      } catch (err) {
        console.warn("Error translating word cloud batch:", err);
      }
    };

    const runTranslation = async () => {
      const chunks = [];
      const chunkSize = 15;
      for (let i = 0; i < missing.length; i += chunkSize) {
        chunks.push(missing.slice(i, i + chunkSize));
      }
      for (const chunk of chunks) {
        await translateBatch(chunk);
      }
      setTranslations({ ...window._WC_TRANSLATIONS[cacheKey] });
    };

    runTranslation();
  }, [words, targetLang, cacheKey]);

  const mappedWords = React.useMemo(() => {
    if (lang === "es") return words;
    return words.map(w => {
      const translatedText = translations[w.text] || w.text;
      return { ...w, text: translatedText, originalText: w.text };
    });
  }, [words, lang, translations]);

  const placed = React.useMemo(() => _wcLayout(mappedWords, width, H), [mappedWords, width]);

  if (!words.length) return null;

  const colorFor = (p) => {
    const negShare = p.word.docs ? p.word.neg / p.word.docs : 0;
    const posShare = p.word.docs ? p.word.pos / p.word.docs : 0;
    if (negShare > 0.55) return "var(--neg)";
    if (posShare > 0.55) return "var(--brand)";
    return p.rank < 8 ? "var(--blue-soft)" : "var(--ice-dim)";
  };

  return (
    <div className="card card-pad" style={{ marginBottom: 18 }}>
      <div className="card-head" style={{ marginBottom: 6 }}>
        <div>
          <h3>{lang === "zh" ? "高频词云" : lang === "en" ? "Most Frequent Words Cloud" : "Mapa de palabras más frecuentes"}</h3>
          <div className="sub" style={{ marginTop: 3 }}>
            {lang === "zh" ? "字号 = 出现频率 · 点击词语筛选评价" : lang === "en" ? "Size = frequency · Click a word to filter reviews" : "Tamaño = frecuencia · Haz clic en una palabra para filtrar las reseñas"}
          </div>
        </div>
        <div className="spacer"></div>
        <div className="wc-legend">
          <span><i style={{ background: "var(--neg)" }}></i>{lang === "zh" ? "负面为主" : lang === "en" ? "Mostly negative" : "Predomin. negativo"}</span>
          <span><i style={{ background: "var(--brand)" }}></i>{lang === "zh" ? "正面为主" : lang === "en" ? "Mostly positive" : "Predomin. positivo"}</span>
          <span><i style={{ background: "var(--blue-soft)" }}></i>{lang === "zh" ? "中性/混合" : lang === "en" ? "Neutral/mixed" : "Neutro/mixto"}</span>
        </div>
      </div>
      <div ref={boxRef} className="wc-box" style={{ height: H }}>
        {placed.map((p) => {
          const isActive = active && (p.word.text === active || p.word.originalText === active);
          const displayText = p.word.text;
          return (
            <span
              key={p.word.originalText || p.word.text}
              className={`wc-word ${isActive ? "active" : ""}`}
              style={{
                left: p.x, top: p.y, fontSize: p.size,
                color: colorFor(p),
                opacity: active && !isActive ? 0.32 : 1,
                fontWeight: p.rank < 5 ? 800 : 700,
              }}
              title={`${p.word.count} ${zh ? "次出现 · " : en ? "appearances · " : "apariciones · "}${p.word.docs} ${zh ? "条评价" : en ? "reviews" : "reseñas"}`}
              onClick={() => onWord && onWord(displayText)}
            >
              {displayText}
            </span>
          );
        })}
      </div>
      {active && (
        <div style={{ marginTop: 10, display: "flex", alignItems: "center", gap: 8, fontSize: 12, color: "var(--ice-dim)" }}>
          <span>{zh ? "当前筛选：" : en ? "Filtering by:" : "Filtrando por:"}</span>
          <span className="ca-chip" style={{ background: "var(--blue-bg)", color: "var(--blue-soft)", fontSize: 12 }}>{active}</span>
          <button className="btn btn-ghost" style={{ padding: "3px 10px", fontSize: 11 }} onClick={() => onWord && onWord(active)}>
            <window.Ic name="x" style={{ width: 10, height: 10 }} /> {zh ? "清除" : en ? "Remove filter" : "Quitar filtro"}
          </button>
        </div>
      )}
    </div>
  );
}

Object.assign(window, { Tilt3D, InlineMd, CommAnalysis, WordCloud, parseCommAnalysis });
