// Pantallas de composición + resultado + reproductor + compartir

// ─ Componiendo ──────────────────────────────────────────────────
function UvComposing({ p, data, lyrics, onDone }) {
  const [phase, setPhase] = React.useState(t("comp_phases")[0]);
  const [pct, setPct] = React.useState(2);
  const [error, setError] = React.useState(null);
  const [debugLog, setDebugLog] = React.useState([]);
  const [showDebug, setShowDebug] = React.useState(false);
  const usingSuno = UV_KIE.isConfigured();
  const log = (m) => { console.log("[UV]", m); setDebugLog(l => [...l.slice(-9), `${new Date().toLocaleTimeString()} · ${m}`]); };

  // Frases para el modo sintético (sin Suno configurado)
  const fakePhrases = t("comp_phases");

  React.useEffect(() => {
    let cancelled = false;

    if (!usingSuno) {
      // Modo sintético: simular fases
      let i = 0;
      const tick = () => {
        if (cancelled) return;
        setPhase(fakePhrases[i] || t("comp_phases")[4]);
        setPct(Math.min(100, (i + 1) * 20));
        i++;
        if (i >= fakePhrases.length) {
          setTimeout(() => !cancelled && onDone({ audioUrl: null }), 700);
        } else {
          setTimeout(tick, 1100);
        }
      };
      tick();
      return () => { cancelled = true; };
    }

    // Modo Kie.ai (vía backend): llamada real
    log(`Kie.ai · ${UV_KIE.cfg().model} · ${UV_KIE.baseUrl() || "mismo origen"}`);
    log(`Datos: ${data.nombre || data.apodo || "(sin nombre)"} · ${data.genero || "(sin género)"}`);
    log(`Letra: ${lyrics?.estrofas?.length || 0} estrofas`);
    (async () => {
      try {
        const result = await UV_KIE.compose(data, lyrics, (msg, p) => {
          if (cancelled) return;
          if (msg) { setPhase(msg); log(msg); }
          if (p != null) setPct(p);
        });
        log(`✓ audioUrl: ${result.audioUrl?.slice(0,60) || "vacío"}`);
        if (result.streamUrl && result.streamUrl !== result.audioUrl) log(`  streamUrl: ${result.streamUrl.slice(0,60)}`);
        if (result.lyrics?.estrofas?.length) log(`  letra real: «${result.lyrics.titulo || ""}» · ${result.lyrics.estrofas.length} estrofas`);
        // Pasamos el resultado COMPLETO: audioUrl(s), versión B, songId, QR,
        // publicUrl y —clave— la letra REAL que se envió a Kie y que canta la
        // canción. Antes solo se pasaba audioUrl/streamUrl/imageUrl, así que la
        // app seguía mostrando la plantilla preliminar en vez de la letra real.
        if (!cancelled) onDone(result);
      } catch (e) {
        log(`✗ ${e.message || e}`);
        if (cancelled) return;
        setError(e.message || String(e));
      }
    })();

    return () => { cancelled = true; };
  }, []);

  if (error) {
    return (
      <div style={{
        height: "100%", display: "flex", flexDirection: "column",
        padding: "60px 28px", background: p.bg, color: p.ink,
        textAlign: "center", justifyContent: "center", gap: 18,
      }}>
        <div style={{
          width: 64, height: 64, borderRadius: 999, margin: "0 auto",
          background: p.accentSoft, color: p.accent,
          display: "flex", alignItems: "center", justifyContent: "center",
          fontSize: 26, fontFamily: UV_FONTS.display,
        }}>!</div>
        <div style={{ fontSize: 22, fontFamily: UV_FONTS.display, fontWeight: 500 }}>
          {t("comp_err_title")}
        </div>
        <div style={{ fontSize: 13, color: p.inkSoft, fontFamily: UV_FONTS.body, maxWidth: 290, margin: "0 auto", lineHeight: 1.5 }}>
          {error}
        </div>
        <div style={{ display: "flex", gap: 10, justifyContent: "center", marginTop: 10 }}>
          <UvGhostBtn p={p} onClick={() => onDone({ audioUrl: null, skipped: true })}>
            {t("comp_use_draft")}
          </UvGhostBtn>
        </div>

        {/* Log de diagnóstico en pantalla de error */}
        {debugLog.length > 0 && (
          <details style={{ maxWidth: 320, margin: "10px auto 0", textAlign: "left" }}>
            <summary style={{ fontSize: 11, color: p.inkSoft, cursor: "pointer", fontFamily: UV_FONTS.body }}>
              Ver diagnóstico técnico
            </summary>
            <div style={{
              marginTop: 8, padding: 10, borderRadius: 8,
              background: "rgba(0,0,0,.04)", border: `.5px solid ${p.line}`,
              fontFamily: "ui-monospace, Menlo, monospace", fontSize: 10.5,
              color: p.ink, lineHeight: 1.5, maxHeight: 200, overflow: "auto",
            }}>
              {debugLog.map((m, i) => <div key={i}>{m}</div>)}
            </div>
          </details>
        )}
      </div>
    );
  }

  return (
    <div style={{
      height: "100%", display: "flex", flexDirection: "column",
      alignItems: "center", justifyContent: "center",
      padding: "60px 28px", background: p.bg, color: p.ink,
      textAlign: "center", gap: 24,
    }}>
      <div style={{ position: "relative", width: 128, height: 128 }}>
        <div className="ev-mesh ev-grain ev-grain-strong" style={{
          position: "absolute", inset: 0, borderRadius: "50%",
          boxShadow: "0 22px 55px -20px rgba(120,60,30,.55)",
          animation: "evBreath 3.6s ease-in-out infinite",
        }} />
      </div>

      <div style={{ fontSize: 26, lineHeight: 1.25, letterSpacing: "-.01em", fontFamily: UV_FONTS.display, fontWeight: 500 }}>
        {t("comp_writing")}
      </div>

      <div style={{
        fontSize: 13.5, color: p.inkSoft, minHeight: 22,
        transition: "opacity .4s", fontFamily: UV_FONTS.body,
        maxWidth: 280,
      }} key={phase}>
        {phase}
      </div>

      {/* Barra de progreso */}
      <div style={{
        width: 220, height: 4, borderRadius: 4,
        background: p.inkFaint, overflow: "hidden",
      }}>
        <div style={{
          height: "100%", width: `${pct}%`, background: p.accent,
          borderRadius: 4, transition: "width .6s ease-out",
        }} />
      </div>

      {usingSuno && (
        <div style={{
          fontSize: 10, color: p.inkSoft, letterSpacing: ".22em",
          textTransform: "uppercase", fontFamily: UV_FONTS.display, fontWeight: 600,
        }}>
          Kie.ai · {UV_KIE.cfg().model}
        </div>
      )}

      {/* Toggle de debug */}
      <button onClick={() => setShowDebug(s => !s)} style={{
        background: "none", border: "none", color: p.inkSoft, opacity: .35,
        fontSize: 10, cursor: "pointer", fontFamily: UV_FONTS.body,
        textDecoration: "underline", letterSpacing: ".02em",
      }}>
        {showDebug ? "ocultar diagnóstico" : "ver diagnóstico"}
      </button>

      {showDebug && (
        <div style={{
          maxWidth: 320, padding: 10, borderRadius: 8,
          background: "rgba(0,0,0,.04)", border: `.5px solid ${p.line}`,
          fontFamily: "ui-monospace, Menlo, monospace", fontSize: 10.5,
          color: p.ink, textAlign: "left", lineHeight: 1.5,
          maxHeight: 180, overflow: "auto",
        }}>
          {debugLog.length === 0 ? <em style={{ opacity: .5 }}>esperando…</em> :
            debugLog.map((m, i) => <div key={i}>{m}</div>)}
        </div>
      )}

      <style>{`
        @keyframes uvPulse {
          0% { transform: scale(0.5); opacity: 0.7; }
          100% { transform: scale(1.4); opacity: 0; }
        }
        @keyframes evBreath {
          0%, 100% { transform: scale(0.95); }
          50% { transform: scale(1.05); }
        }
      `}</style>
    </div>
  );
}

// ─ Reproductor ──────────────────────────────────────────────────
function UvPlayer({ p, data, audioUrls, audioUrlsB, selected, onSelect, playing, setPlaying, progress, setProgress }) {
  const audioRef = React.useRef(null);
  const hasB = Array.isArray(audioUrlsB) && audioUrlsB.length > 0;
  const urls = (selected === "B" && hasB) ? audioUrlsB : (audioUrls || []);
  const [urlIdx, setUrlIdx] = React.useState(0);
  const currentUrl = urls[urlIdx] || null;
  const [audioError, setAudioError] = React.useState(null);
  const usingReal = !!currentUrl;
  const [realDur, setRealDur] = React.useState(null);

  // Resetear al cambiar URLs o versión
  React.useEffect(() => {
    setUrlIdx(0);
    setAudioError(null);
  }, [selected, (audioUrls||[]).join("|"), (audioUrlsB||[]).join("|")]);

  // Inicializar <audio>
  React.useEffect(() => {
    if (!usingReal) return;
    const a = new Audio(currentUrl);
    a.preload = "auto";
    // NO usamos crossOrigin — no necesitamos Web Audio API y crossOrigin
    // bloquea reproducción desde file:// si el servidor no envía CORS.
    audioRef.current = a;
    const onMeta = () => setRealDur(a.duration);
    const onTime = () => {
      if (a.duration) setProgress(a.currentTime / a.duration);
    };
    const onEnd = () => { setPlaying(false); setProgress(0); };
    const onError = () => {
      console.warn("[UV] Audio falló en url[" + urlIdx + "]:", currentUrl);
      if (urlIdx + 1 < urls.length) {
        console.log("[UV] Probando url[" + (urlIdx + 1) + "]:", urls[urlIdx + 1]);
        setUrlIdx(i => i + 1);
        setAudioError(null);
      } else {
        setAudioError("No se pudo cargar el audio desde ningún servidor. Usa ↓ para descargar o ábrelo desde otra red.");
        setPlaying(false);
      }
    };
    a.addEventListener("loadedmetadata", onMeta);
    a.addEventListener("timeupdate", onTime);
    a.addEventListener("ended", onEnd);
    a.addEventListener("error", onError);
    return () => {
      a.pause();
      a.removeEventListener("loadedmetadata", onMeta);
      a.removeEventListener("timeupdate", onTime);
      a.removeEventListener("ended", onEnd);
      a.removeEventListener("error", onError);
      audioRef.current = null;
    };
  }, [currentUrl]);

  // Reproducción
  React.useEffect(() => {
    if (usingReal) {
      const a = audioRef.current;
      if (!a) return;
      if (playing) a.play().catch(() => setPlaying(false));
      else a.pause();
      return;
    }
    // Modo sintético
    if (playing) {
      if (progress > 0 && progress < 1 && UV_AUDIO.paused) {
        UV_AUDIO.resume();
      } else {
        UV_AUDIO.play(data, {
          onTick: (p) => setProgress(p),
          onEnd: () => { setPlaying(false); setProgress(0); },
        });
      }
    } else if (progress > 0 && progress < 1) {
      UV_AUDIO.pause();
    } else {
      UV_AUDIO.stop();
    }
  }, [playing, currentUrl]);

  React.useEffect(() => () => { if (!usingReal) UV_AUDIO.stop(); }, []);

  const totalSec = realDur || UV_AUDIO.parseDur(data.duracion);
  const cur = Math.floor(totalSec * progress);
  const fmt = (s) => `${Math.floor(s/60)}:${String(s%60).padStart(2,"0")}`;

  const bars = 44;
  const heights = React.useMemo(() =>
    Array.from({ length: bars }, (_, i) => 6 + Math.abs(Math.sin(i * 1.3)) * 22 + Math.abs(Math.cos(i * 0.7)) * 12)
  , []);

  const seek = (e) => {
    const r = e.currentTarget.getBoundingClientRect();
    const x = Math.max(0, Math.min(1, (e.clientX - r.left) / r.width));
    if (usingReal && audioRef.current) {
      audioRef.current.currentTime = x * totalSec;
      setProgress(x);
    } else {
      UV_AUDIO.stop();
      setProgress(0);
      if (playing) setPlaying(false);
    }
  };

  const reset = () => {
    if (usingReal && audioRef.current) {
      audioRef.current.currentTime = 0;
      audioRef.current.pause();
    } else {
      UV_AUDIO.stop();
    }
    setProgress(0);
    setPlaying(false);
  };

  const download = () => {
    if (!usingReal) return;
    const a = document.createElement("a");
    a.href = currentUrl;
    a.download = `ultimo-verso-${(data.apodo || data.nombre || "cancion").replace(/\s+/g,"-").toLowerCase()}-${selected || "A"}.mp3`;
    a.target = "_blank";
    a.click();
  };

  return (
    <div style={{
      padding: 16, borderRadius: 16,
      background: p.surface, border: `1px solid ${p.line}`,
      display: "flex", flexDirection: "column", gap: 10,
    }}>
      {hasB && (
        <div style={{
          display: "flex", padding: 3, borderRadius: 99,
          background: p.inkFaint, gap: 2, marginBottom: 4,
        }}>
          {["A", "B"].map(v => {
            const active = selected === v;
            return (
              <button key={v} onClick={() => onSelect?.(v)} style={{
                flex: 1, padding: "7px 0", borderRadius: 99,
                border: "none", cursor: "pointer",
                background: active ? p.bg : "transparent",
                color: active ? p.ink : p.inkSoft,
                fontSize: 12, fontWeight: 600,
                fontFamily: UV_FONTS.display, letterSpacing: ".08em",
                transition: "background .15s, color .15s",
                boxShadow: active ? "0 1px 3px rgba(0,0,0,.06)" : "none",
              }}>
                Versión {v}
              </button>
            );
          })}
        </div>
      )}
      <div onClick={seek} style={{ display: "flex", alignItems: "center", gap: 2.5, height: 48, cursor: "pointer" }}>
        {heights.map((h, i) => {
          const ratio = i / bars;
          const filled = ratio < progress;
          const pulse = playing && Math.abs(ratio - progress) < 0.045;
          return (
            <div key={i} style={{
              flex: 1, height: pulse ? Math.min(46, h * 1.28) : h, borderRadius: 99,
              background: filled
                ? `linear-gradient(180deg, ${p.accent} 0%, ${p.accentDeep} 100%)`
                : "rgba(27,25,34,.13)",
              boxShadow: filled ? `0 2px 7px -2px ${p.accent}` : "none",
              transition: "background .2s, height .12s, box-shadow .2s",
            }} />
          );
        })}
      </div>
      <div style={{ display: "flex", justifyContent: "space-between", fontSize: 11, color: p.inkSoft, fontVariantNumeric: "tabular-nums", fontFamily: UV_FONTS.body }}>
        <span>{fmt(cur)}</span>
        <span style={{ display: "flex", alignItems: "center", gap: 6 }}>
          {usingReal && <span style={{
            fontSize: 9, padding: "2px 6px", borderRadius: 99,
            background: p.accentSoft, color: p.accent,
            letterSpacing: ".1em", textTransform: "uppercase", fontWeight: 600,
            fontFamily: UV_FONTS.display,
          }}>Kie</span>}
          <span>{fmt(Math.floor(totalSec))}</span>
        </span>
      </div>
      <div style={{ display: "flex", alignItems: "center", gap: 16, justifyContent: "center", marginTop: 4 }}>
        <button onClick={reset} style={iconBtn(p)} title="Reiniciar">
          <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><path d="M3 12a9 9 0 1 0 3-6.7"/><path d="M3 4v4h4"/></svg>
        </button>
        <button onClick={() => setPlaying(!playing)} style={{
          width: 68, height: 68, borderRadius: "50%",
          background: `linear-gradient(155deg, ${p.accent} 0%, ${p.accentDeep} 100%)`,
          color: "#fff", border: "none", cursor: "pointer",
          display: "flex", alignItems: "center", justifyContent: "center",
          position: "relative",
          boxShadow: `0 14px 28px -8px ${p.accent}, 0 5px 12px -4px rgba(0,0,0,.28), inset 0 2px 3px rgba(255,255,255,.55), inset 0 -5px 10px rgba(0,28,36,.30)`,
          transition: "transform .12s",
        }}
        onMouseDown={e => e.currentTarget.style.transform = "scale(.94)"}
        onMouseUp={e => e.currentTarget.style.transform = "scale(1)"}
        onMouseLeave={e => e.currentTarget.style.transform = "scale(1)"}>
          {playing
            ? <svg width="22" height="22" viewBox="0 0 24 24" fill="#fff"><rect x="6.5" y="5" width="4" height="14" rx="1.5"/><rect x="13.5" y="5" width="4" height="14" rx="1.5"/></svg>
            : <svg width="24" height="24" viewBox="0 0 24 24" fill="#fff" style={{ marginLeft: 3, filter: "drop-shadow(0 1px 1px rgba(0,30,40,.3))" }}><path d="M8 5.2v13.6l11-6.8z"/></svg>}
        </button>
        <button onClick={download} style={{ ...iconBtn(p), opacity: usingReal ? 1 : .4, cursor: usingReal ? "pointer" : "not-allowed" }} title={usingReal ? `Descargar Versión ${selected || "A"} (MP3)` : "Disponible al generar"}>
          <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><path d="M12 4v11"/><path d="M7 11l5 5 5-5"/><path d="M5 20h14"/></svg>
        </button>
      </div>
      {audioError && (
        <div style={{
          marginTop: 6, padding: "8px 10px", borderRadius: 8,
          background: p.accentSoft, color: p.accent,
          fontSize: 11.5, lineHeight: 1.4, fontFamily: UV_FONTS.body,
        }}>
          {audioError}
        </div>
      )}
    </div>
  );
}
function iconBtn(p) {
  return {
    width: 42, height: 42, borderRadius: "50%",
    background: "rgba(255,255,255,.5)", border: `1px solid ${p.inkFaint}`,
    color: p.ink, cursor: "pointer",
    display: "flex", alignItems: "center", justifyContent: "center",
    boxShadow: "0 2px 6px -3px rgba(0,0,0,.2)",
  };
}

// ─ Resultado ──────────────────────────────────────────────────
function UvResult({ p, data, lyrics, setLyrics, audioUrls, audioUrlsB, publicUrl, qrUrl, songId, onShare, onRegenerate }) {
  const [playing, setPlaying] = React.useState(false);
  const [progress, setProgress] = React.useState(0);
  const [editingKey, setEditingKey] = React.useState(null);
  const [showGenres, setShowGenres] = React.useState(false);
  const [selected, setSelected] = React.useState("A");
  const [savingSel, setSavingSel] = React.useState(false);
  const hasB = Array.isArray(audioUrlsB) && audioUrlsB.length > 0;

  const selectVersion = (v) => {
    if (v === selected) return;
    setSelected(v);
    setPlaying(false);
    setProgress(0);
    // Persistir en backend (silencioso). Si falla, no rompe la UX.
    if (songId && window.UV_KIE?.select) {
      setSavingSel(true);
      window.UV_KIE.select(songId, v)
        .catch(e => console.warn("[UV] select falló:", e))
        .finally(() => setSavingSel(false));
    }
  };

  const editLine = (sIdx, lIdx, val) => {
    const next = { ...lyrics, estrofas: lyrics.estrofas.map((s, i) => i !== sIdx ? s : { ...s, texto: s.texto.map((t, j) => j === lIdx ? val : t) }) };
    setLyrics(next);
  };

  const changeGenre = (g) => {
    setShowGenres(false);
    onRegenerate(g);
  };

  return (
    <div style={{ height: "100%", display: "flex", flexDirection: "column", background: p.bg, color: p.ink, overflow: "hidden" }}>
      {/* Header */}
      <div style={{ padding: "52px 22px 14px", display: "flex", alignItems: "center", gap: 10 }}>
        <div style={{ flex: 1 }}>
          <AltimaLogo color={EV_INK} size={13} />
          <div style={{ fontSize: 10.5, letterSpacing: ".22em", textTransform: "uppercase", color: p.accent, fontWeight: 600, marginTop: 6, fontFamily: UV_FONTS.display }}>
            {t("res_eyebrow")}
          </div>
          <div style={{ fontSize: 11.5, color: p.inkSoft, marginTop: 2, fontFamily: UV_FONTS.body }}>
            {t("res_for").replace("{n}", data.apodo || data.nombre?.split(" ")[0] || "tu ser querido")}
          </div>
        </div>
        <button onClick={onShare} style={{
          height: 36, padding: "0 14px", borderRadius: 999,
          background: p.accent, color: p.accentInk, border: "none",
          fontSize: 13, fontWeight: 600, cursor: "pointer",
          fontFamily: UV_FONTS.display, letterSpacing: ".02em",
          boxShadow: `0 6px 18px -8px ${p.accent}`,
        }}>{t("res_share_btn")}</button>
      </div>

      <div style={{ flex: 1, minHeight: 0, overflow: "auto", padding: "6px 22px 20px", display: "flex", flexDirection: "column", gap: 18 }}>

        {/* Portada + título */}
        <div style={{ display: "flex", gap: 14, alignItems: "center" }}>
          <div className="ev-mesh ev-grain ev-grain-strong" style={{
            width: 84, height: 84, borderRadius: 18, flex: "none",
            boxShadow: "0 14px 32px -16px rgba(120,60,30,.5)",
          }} />
          <div style={{ flex: 1, minWidth: 0 }}>
            <div style={{
              fontSize: 23, lineHeight: 1.16, letterSpacing: "-.02em",
              fontFamily: UV_FONTS.display, fontWeight: 700,
              color: p.ink,
            }}>{lyrics.titulo}</div>
            <div style={{
              height: 2, width: 32, background: p.clay, borderRadius: 2,
              marginTop: 10,
            }} />
          </div>
        </div>

        {/* Meta — clic para cambiar género */}
        <div style={{ display: "flex", alignItems: "center", gap: 8, flexWrap: "wrap" }}>
          <button onClick={() => setShowGenres(!showGenres)} style={{
            fontSize: 12, padding: "6px 12px", borderRadius: 999,
            border: `.5px solid ${p.inkFaint}`, background: p.surface,
            color: p.ink, cursor: "pointer", fontFamily: "inherit",
            display: "flex", alignItems: "center", gap: 6,
          }}>
            {data.genero || "Copla"} · {data.tempo || "Lento"} · {data.duracion || "2:30"}
            <span style={{ fontSize: 10, color: p.inkSoft }}>⌄</span>
          </button>
        </div>

        {showGenres && (
          <div style={{
            padding: 12, borderRadius: 12,
            background: p.surface, border: `.5px solid ${p.line}`,
          }}>
            <div style={{ fontSize: 11, color: p.inkSoft, marginBottom: 8, textTransform: "uppercase", letterSpacing: ".1em" }}>
              {t("res_change_genre")}
            </div>
            <UvChips p={p} value={data.genero} small
              options={["Copla","Bolero","Balada","Flamenco","Pop","Clásica","Rock suave"]}
              onSelect={changeGenre} />
          </div>
        )}

        {/* Reproductor */}
        <UvPlayer p={p} data={data}
          audioUrls={audioUrls}
          audioUrlsB={audioUrlsB}
          selected={selected}
          onSelect={selectVersion}
          playing={playing} setPlaying={setPlaying}
          progress={progress} setProgress={setProgress} />

        {hasB && (
          <div style={{
            display: "flex", alignItems: "center", gap: 10,
            padding: "10px 14px", borderRadius: 12,
            background: p.accentSoft, color: p.accent,
            fontSize: 12, fontFamily: UV_FONTS.body, lineHeight: 1.4,
          }}>
            <div style={{
              width: 22, height: 22, borderRadius: 999,
              background: p.accent, color: p.accentInk,
              display: "flex", alignItems: "center", justifyContent: "center",
              fontSize: 12, fontWeight: 700, flexShrink: 0,
              fontFamily: UV_FONTS.display,
            }}>{selected}</div>
            <div style={{ flex: 1 }}>
              {t("res_version")} <strong>{selected}</strong> {t("res_fav")}
              {savingSel && <span style={{ opacity: .6, marginLeft: 6 }}>{t("res_saving")}</span>}
            </div>
          </div>
        )}

        {/* Letra editable */}
        <div style={{
          padding: "18px 18px 14px", borderRadius: 16,
          background: p.surface, border: `.5px solid ${p.line}`,
          display: "flex", flexDirection: "column", gap: 16,
        }}>
          {lyrics.estrofas.map((s, sIdx) => {
            // Las secciones instrumentales (Intro/Outro sin letra) no se muestran
            // en la hoja de letra: solo guían a Suno.
            const hasText = (s.texto || []).some(l => l && l.trim());
            if (!hasText) return null;
            return (
            <div key={sIdx}>
              <div style={{ fontSize: 10, letterSpacing: ".22em", textTransform: "uppercase", color: p.accent, marginBottom: 6, fontWeight: 600 }}>
                {s.tipo}
              </div>
              {s.texto.map((line, lIdx) => {
                const key = `${sIdx}-${lIdx}`;
                const editing = editingKey === key;
                return editing ? (
                  <input key={lIdx} autoFocus value={line}
                    onBlur={() => setEditingKey(null)}
                    onKeyDown={(e) => { if (e.key === "Enter") setEditingKey(null); }}
                    onChange={e => editLine(sIdx, lIdx, e.target.value)}
                    style={{
                      width: "100%", background: p.accentSoft,
                      border: "none", borderRadius: 4,
                      padding: "4px 6px", margin: "0 -6px",
                      fontFamily: UV_FONTS.body,
                      color: p.ink, fontSize: 15.5, lineHeight: 1.6,
                      outline: `1px solid ${p.accent}`,
                    }} />
                ) : (
                  <div key={lIdx}
                    onClick={() => setEditingKey(key)}
                    style={{
                      fontSize: 15.5, lineHeight: 1.6,
                      color: p.ink,
                      fontFamily: UV_FONTS.body,
                      cursor: "text", borderRadius: 4,
                      padding: "1px 6px", margin: "0 -6px",
                      transition: "background .12s",
                    }}
                    onMouseEnter={e => e.currentTarget.style.background = p.accentSoft}
                    onMouseLeave={e => e.currentTarget.style.background = "transparent"}
                  >{line}</div>
                );
              })}
            </div>
            );
          })}
          <div style={{ fontSize: 11, color: p.inkSoft, marginTop: 4, fontStyle: "italic" }}>
            {t("res_edit_hint")}
          </div>
        </div>

        {/* QR */}
        <div style={{
          padding: 14, borderRadius: 16,
          background: p.surface, border: `1px solid ${p.line}`,
          display: "flex", gap: 14, alignItems: "center",
        }}>
          <div style={{ background: "#fff", padding: 6, borderRadius: 8, border: `1px solid ${p.line}` }}>
            {qrUrl
              ? <img src={qrUrl} alt="QR" style={{ width: 72, height: 72, display: "block" }} />
              : <UvQR size={72} seed={9} fg={p.accent} />}
          </div>
          <div style={{ flex: 1 }}>
            <div style={{ fontSize: 13.5, fontWeight: 600, fontFamily: UV_FONTS.body, color: p.ink }}>{t("res_qr_title")}</div>
            <div style={{ fontSize: 12, color: p.inkSoft, marginTop: 3, lineHeight: 1.5, fontFamily: UV_FONTS.body }}>
              {publicUrl
                ? t("res_qr_pub")
                : t("res_qr_dl")}
            </div>
            {publicUrl && (
              <a href={publicUrl} target="_blank" rel="noopener" style={{
                display: "inline-block", marginTop: 6,
                fontSize: 11, color: p.accent, textDecoration: "underline",
                fontFamily: UV_FONTS.body, wordBreak: "break-all",
              }}>{publicUrl.replace(/^https?:\/\//, "")}</a>
            )}
          </div>
        </div>

        {/* Firma EternaVoz */}
        <div style={{
          display: "flex", alignItems: "center", justifyContent: "center", gap: 8,
          paddingTop: 6, opacity: .7,
        }}>
          <div style={{ fontSize: 10, color: p.inkSoft, fontFamily: UV_FONTS.body, letterSpacing: ".04em" }}>
            {t("res_created")}
          </div>
          <AltimaLogo color={EV_INK} size={11} />
        </div>
      </div>
    </div>
  );
}

// ─ Compartir ──────────────────────────────────────────────────
function UvShare({ p, data, lyrics, audioUrl, publicUrl, qrUrl, onClose }) {
  const [toast, setToast] = React.useState(null);
  const flash = (msg) => { setToast(msg); setTimeout(() => setToast(null), 2200); };
  // Tras cualquier descarga, volvemos a la pantalla final (resultado)
  const backToFinal = () => { if (onClose) setTimeout(onClose, 900); };

  const titulo = lyrics?.titulo || "EternaVoz";
  const nombre = data.apodo || data.nombre?.split(" ")[0] || "Tu ser querido";
  const filename = `eternavoz-${(data.apodo || data.nombre || "cancion").replace(/\s+/g,"-").toLowerCase()}`;

  // Texto plano de la letra (omitimos secciones instrumentales sin letra)
  const lyricsText = React.useMemo(() => {
    if (!lyrics?.estrofas) return "";
    return lyrics.estrofas
      .filter(s => (s.texto || []).some(l => l && l.trim()))
      .map(s => `[${s.tipo}]\n${s.texto.join("\n")}`).join("\n\n");
  }, [lyrics]);

  // Imprimir / PDF
  const printable = (mode) => {
    const w = window.open("", "_blank", "width=720,height=900");
    if (!w) { flash("Permite ventanas emergentes para imprimir"); return; }
    const accent = p.accent;
    const ink = p.ink;
    const html = `<!doctype html><html><head><meta charset="utf-8"><title>${titulo}</title>
<style>
  @page { size: A5; margin: 18mm 16mm; }
  body { font-family: Georgia, "Times New Roman", serif; color: ${ink}; margin: 0; padding: 32px; }
  .brand { font-family: "Hanken Grotesk", sans-serif; color: ${accent}; letter-spacing: .08em; font-size: 14px; }
  .brand::before { content: ""; display: inline-block; width: 14px; height: 2px; background: ${accent}; vertical-align: middle; margin-right: 8px; }
  h1 { font-family: Georgia, serif; font-weight: 500; font-size: 30px; line-height: 1.2; margin: 18px 0 4px; }
  .for { font-size: 13px; color: #666; letter-spacing: .1em; text-transform: uppercase; }
  .rule { width: 32px; height: 2px; background: ${accent}; margin: 14px 0 22px; }
  .stanza { margin-bottom: 18px; }
  .tag { font-family: "Hanken Grotesk", sans-serif; font-size: 10px; letter-spacing: .22em; text-transform: uppercase; color: ${accent}; font-weight: 600; margin-bottom: 4px; }
  .line { font-size: 15px; line-height: 1.65; }
  footer { margin-top: 30px; font-size: 10px; color: #888; letter-spacing: .2em; text-transform: uppercase; }
</style></head><body>
<div class="brand">ETERNAVOZ</div>
<h1>${titulo}</h1>
<div class="for">Para ${nombre}</div>
<div class="rule"></div>
${lyrics?.estrofas?.filter(s => (s.texto || []).some(l => l && l.trim())).map(s => `<div class="stanza"><div class="tag">${s.tipo}</div>${s.texto.map(l => `<div class="line">${l.replace(/</g,"&lt;")}</div>`).join("")}</div>`).join("") || ""}
<footer>EternaVoz · Su memoria, ahora eterna.</footer>
<script>setTimeout(()=>window.print(), 400);</script>
</body></html>`;
    w.document.write(html);
    w.document.close();
    backToFinal();
  };

  // Descargar archivo de letra (.txt)
  const downloadLyricsTxt = () => {
    const txt = `${titulo}\nPara ${nombre}\n\n${lyricsText}\n\n— EternaVoz · Su memoria, ahora eterna.`;
    const blob = new Blob([txt], { type: "text/plain;charset=utf-8" });
    const url = URL.createObjectURL(blob);
    const a = document.createElement("a");
    a.href = url; a.download = `${filename}.txt`; a.click();
    setTimeout(() => URL.revokeObjectURL(url), 1000);
    flash("Letra descargada");
    backToFinal();
  };

  // Descargar MP3
  const downloadMp3 = () => {
    if (!audioUrl) { flash("Sin generación configurada, no hay MP3"); return; }
    const a = document.createElement("a");
    a.href = audioUrl;
    a.download = `${filename}.mp3`;
    a.target = "_blank";
    a.rel = "noopener";
    a.click();
    flash("Abriendo MP3…");
    backToFinal();
  };

  // Email
  const sendEmail = () => {
    const subject = encodeURIComponent(`${titulo} — para ${nombre}`);
    const body = encodeURIComponent(`${titulo}\nPara ${nombre}\n\n${lyricsText}\n\n${audioUrl ? "Audio: " + audioUrl + "\n\n" : ""}— EternaVoz · Su memoria, ahora eterna.`);
    window.location.href = `mailto:?subject=${subject}&body=${body}`;
  };

  // WhatsApp
  const sendWhatsApp = () => {
    const first = lyrics?.estrofas?.[0]?.texto?.[0] || "";
    const msg = `"${titulo}" — una canción para ${nombre}.\n${first ? `«${first}»\n` : ""}${audioUrl ? "\n" + audioUrl : ""}\n\n— EternaVoz`;
    window.open(`https://wa.me/?text=${encodeURIComponent(msg)}`, "_blank", "noopener");
  };

  // Guardar en biblioteca (localStorage)
  const saveLibrary = () => {
    try {
      const key = "uv.library";
      const arr = JSON.parse(localStorage.getItem(key) || "[]");
      arr.unshift({
        ts: Date.now(),
        titulo, nombre, audioUrl,
        data, lyrics,
      });
      localStorage.setItem(key, JSON.stringify(arr.slice(0, 50)));
      flash("Guardada en tu biblioteca");
    } catch (e) {
      flash("No se pudo guardar");
    }
  };

  // Descargar QR (PNG) — descarga real vía blob; si hay CORS, abre en pestaña
  const downloadQr = async () => {
    if (!qrUrl) { flash("QR aún no disponible"); return; }
    try {
      if (qrUrl.startsWith("data:")) {
        const a = document.createElement("a");
        a.href = qrUrl; a.download = `${filename}-qr.png`; a.click();
      } else {
        const res = await fetch(qrUrl, { mode: "cors" });
        const blob = await res.blob();
        const url = URL.createObjectURL(blob);
        const a = document.createElement("a");
        a.href = url; a.download = `${filename}-qr.png`; a.click();
        setTimeout(() => URL.revokeObjectURL(url), 1500);
      }
      flash("QR descargado");
    } catch (e) {
      window.open(qrUrl, "_blank", "noopener");
      flash("QR abierto en pestaña");
    }
    backToFinal();
  };

  // Copiar enlace público al portapapeles
  const copyLink = async () => {
    if (!publicUrl) { flash("Enlace aún no disponible"); return; }
    try {
      await navigator.clipboard.writeText(publicUrl);
      flash("Enlace copiado ✓");
    } catch (e) {
      flash("No se pudo copiar");
    }
  };

  const rows = [
    { ic: "↓", label: t("row_pdf_l"), sub: t("row_pdf_s"), onClick: () => printable("pdf") },
    { ic: "♪", label: t("row_mp3_l"), sub: audioUrl ? t("row_mp3_s").replace("{d}", data.duracion || "2:30") : t("row_unavail"), onClick: downloadMp3, dim: !audioUrl },
    { ic: "▦", label: t("row_qr_l"), sub: qrUrl ? t("row_qr_s") : t("row_unavail"), onClick: downloadQr, dim: !qrUrl },
    { ic: "⌘", label: t("row_link_l"), sub: publicUrl ? publicUrl.replace(/^https?:\/\//, "") : t("row_unavail"), onClick: copyLink, dim: !publicUrl },
    { ic: "✉", label: t("row_email_l"), sub: t("row_email_s"), onClick: sendEmail },
    { ic: "◐", label: "WhatsApp", sub: t("row_wa_s"), onClick: sendWhatsApp },
    { ic: "⎙", label: t("row_print_l"), sub: t("row_print_s"), onClick: () => printable("print") },
    { ic: "⤓", label: t("row_txt_l"), sub: t("row_txt_s"), onClick: downloadLyricsTxt },
    { ic: "♡", label: t("row_lib_l"), sub: t("row_lib_s"), onClick: saveLibrary },
  ];
  return (
    <div style={{ height: "100%", background: p.bg, color: p.ink, display: "flex", flexDirection: "column" }}>
      <div style={{ padding: "52px 22px 14px", display: "flex", alignItems: "center", gap: 10 }}>
        <button onClick={onClose} style={{
          width: 36, height: 36, borderRadius: 999,
          border: `1px solid ${p.inkFaint}`, background: p.surface,
          color: p.ink, cursor: "pointer", fontSize: 16,
          fontFamily: UV_FONTS.body,
        }}>←</button>
        <div style={{ flex: 1, display: "flex", flexDirection: "column", alignItems: "center", gap: 2 }}>
          <AltimaLogo color={EV_INK} size={14} />
          <div style={{ fontSize: 9.5, letterSpacing: ".26em", textTransform: "uppercase", color: p.inkSoft, fontWeight: 600, fontFamily: UV_FONTS.display }}>
            {t("res_share_btn")}
          </div>
        </div>
        <div style={{ width: 36 }} />
      </div>
      <div style={{ flex: 1, overflow: "auto", padding: "6px 22px 30px" }}>
        <UvTitle p={p} style={{ marginBottom: 6 }}>
          <span style={{ color: p.accent }}>{data.apodo || data.nombre?.split(" ")[0] || "Tu"}</span> {t("share_ready")}
        </UvTitle>
        <UvHint p={p}>{t("share_sub")}</UvHint>
        <div style={{ marginTop: 18, display: "flex", flexDirection: "column", gap: 8 }}>
          {rows.map(r => (
            <button key={r.label} onClick={r.onClick} style={{
              display: "flex", alignItems: "center", gap: 14,
              padding: "14px 16px", borderRadius: 14,
              border: `.5px solid ${p.line}`, background: p.surface,
              color: p.ink, cursor: "pointer", textAlign: "left",
              fontFamily: "inherit",
              opacity: r.dim ? 0.55 : 1,
              transition: "transform .12s, background .12s",
            }}
            onMouseDown={e => e.currentTarget.style.transform = "scale(.98)"}
            onMouseUp={e => e.currentTarget.style.transform = "scale(1)"}
            onMouseLeave={e => e.currentTarget.style.transform = "scale(1)"}
            >
              <div style={{
                width: 38, height: 38, borderRadius: "50%",
                background: p.accentSoft, color: p.accent,
                display: "flex", alignItems: "center", justifyContent: "center",
                fontSize: 16,
              }}>{r.ic}</div>
              <div style={{ flex: 1 }}>
                <div style={{ fontSize: 14.5, fontWeight: 500 }}>{r.label}</div>
                <div style={{ fontSize: 12, color: p.inkSoft, marginTop: 2 }}>{r.sub}</div>
              </div>
              <div style={{ color: p.inkSoft, fontSize: 14 }}>›</div>
            </button>
          ))}
        </div>
      </div>

      {/* Toast */}
      {toast && (
        <div style={{
          position: "absolute", bottom: 28, left: "50%", transform: "translateX(-50%)",
          background: p.ink, color: p.bg,
          padding: "10px 18px", borderRadius: 999,
          fontSize: 13, fontFamily: UV_FONTS.body,
          boxShadow: "0 8px 24px rgba(0,0,0,.18)",
          zIndex: 100, animation: "uvToast .25s ease-out",
        }}>{toast}</div>
      )}
      <style>{`@keyframes uvToast { from { transform: translate(-50%, 16px); opacity: 0 } to { transform: translate(-50%, 0); opacity: 1 } }`}</style>
    </div>
  );
}

// ─ Muestra de 15 segundos (tras componer, antes del resultado) ───
function UvPreview({ p, data, lyrics, audioUrls, onReveal }) {
  const [playing, setPlaying] = React.useState(false);
  const [prog, setProg] = React.useState(0);
  const audioRef = React.useRef(null);
  const rafRef = React.useRef(null);
  const startRef = React.useRef(0);
  const LIMIT = 15;
  const url = (audioUrls && audioUrls[0]) || null;

  const stop = () => {
    setPlaying(false);
    if (audioRef.current) { audioRef.current.pause(); audioRef.current.currentTime = 0; audioRef.current = null; }
    else { try { UV_AUDIO.stop(); } catch (e) {} }
    cancelAnimationFrame(rafRef.current);
    setProg(0);
  };
  const tick = () => {
    const el = (Date.now() - startRef.current) / 1000;
    const r = Math.min(1, el / LIMIT);
    setProg(r);
    if (r >= 1) { stop(); return; }
    rafRef.current = requestAnimationFrame(tick);
  };
  const toggle = () => {
    if (playing) { stop(); return; }
    setPlaying(true);
    startRef.current = Date.now();
    if (url) { const a = new Audio(url); audioRef.current = a; a.play().catch(() => {}); }
    else { try { UV_AUDIO.play(data, { onTick: () => {}, onEnd: () => {} }); } catch (e) {} }
    rafRef.current = requestAnimationFrame(tick);
  };
  React.useEffect(() => () => { if (audioRef.current) audioRef.current.pause(); else { try { UV_AUDIO.stop(); } catch (e) {} } cancelAnimationFrame(rafRef.current); }, []);

  const firstLines = ((lyrics?.estrofas || []).find(s => (s.texto || []).some(l => l && l.trim()))?.texto || [])
    .filter(l => l && l.trim()).slice(0, 2);

  return (
    <div style={{ height: "100%", background: p.bg, color: p.ink, display: "flex", flexDirection: "column", overflow: "hidden" }}>
      <div style={{ flex: 1, minHeight: 0, overflowY: "auto", display: "flex", flexDirection: "column", alignItems: "center", justifyContent: "center", textAlign: "center", padding: "54px 30px 12px", gap: 16 }}>
        <div style={{ fontFamily: UV_FONTS.display, fontWeight: 700, fontSize: 10.5, letterSpacing: ".24em", textTransform: "uppercase", color: p.accent }}>{t("prev_eyebrow")}</div>
        <div style={{ fontFamily: UV_FONTS.display, fontWeight: 700, fontSize: 26, letterSpacing: "-.02em", lineHeight: 1.12 }}>{t("prev_title")}</div>

        <button onClick={toggle} className="ev-mesh ev-grain ev-grain-strong" style={{
          width: 184, height: 184, borderRadius: 34, border: "none", cursor: "pointer", flex: "none",
          position: "relative", boxShadow: "0 26px 54px -18px rgba(20,90,100,.5)", margin: "6px 0",
        }}>
          <span style={{ position: "absolute", inset: 0, display: "flex", alignItems: "center", justifyContent: "center", zIndex: 3 }}>
            <span style={{ width: 64, height: 64, borderRadius: "50%", background: "rgba(255,255,255,.94)", display: "flex", alignItems: "center", justifyContent: "center", boxShadow: "0 8px 22px -6px rgba(0,0,0,.32)" }}>
              {playing
                ? <svg width="22" height="22" viewBox="0 0 24 24" fill={p.accent}><rect x="6.5" y="5" width="4" height="14" rx="1.5"/><rect x="13.5" y="5" width="4" height="14" rx="1.5"/></svg>
                : <svg width="24" height="24" viewBox="0 0 24 24" fill={p.accent} style={{ marginLeft: 3 }}><path d="M8 5.2v13.6l11-6.8z"/></svg>}
            </span>
          </span>
        </button>

        <div style={{ width: 200, height: 4, borderRadius: 99, background: p.inkFaint, overflow: "hidden" }}>
          <div style={{ height: "100%", width: `${prog * 100}%`, background: p.accent, borderRadius: 99, transition: "width .12s linear" }} />
        </div>
        <div style={{ fontSize: 12, color: p.inkSoft, fontFamily: UV_FONTS.body, fontVariantNumeric: "tabular-nums" }}>
          0:{String(Math.max(0, Math.ceil((1 - prog) * LIMIT))).padStart(2, "0")}
        </div>

        <div style={{ marginTop: 4 }}>
          <div style={{ fontFamily: UV_FONTS.display, fontWeight: 600, fontSize: 17, color: p.ink }}>«{lyrics?.titulo || ""}»</div>
          {firstLines.map((l, i) => (
            <div key={i} style={{ fontFamily: UV_FONTS.body, fontSize: 14, color: p.inkSoft, lineHeight: 1.55, marginTop: i ? 2 : 8, fontStyle: "italic" }}>{l}</div>
          ))}
        </div>
        <div style={{ fontSize: 13, color: p.inkSoft, fontFamily: UV_FONTS.body, maxWidth: 280, lineHeight: 1.5, marginTop: 4 }}>{t("prev_sub")}</div>
      </div>

      <div style={{ flexShrink: 0, padding: "14px 28px max(26px, env(safe-area-inset-bottom))", background: p.bg }}>
        <button onClick={() => { stop(); onReveal(); }} style={{
          width: "100%", height: 54, border: "none", borderRadius: 999, background: p.ink, color: "#fff",
          fontFamily: UV_FONTS.display, fontWeight: 600, fontSize: 16,
          display: "flex", alignItems: "center", justifyContent: "center", gap: 10, cursor: "pointer",
        }}>
          {t("prev_cta")}
          <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="#fff" strokeWidth="2.4" strokeLinecap="round" strokeLinejoin="round"><path d="M5 12h14M13 6l6 6-6 6"/></svg>
        </button>
      </div>
    </div>
  );
}

Object.assign(window, { UvComposing, UvPreview, UvResult, UvShare });
