/* global React, Icon */
/* AIActivity — panel flotante que escucha los CustomEvents del shim
   (sop-creator:ai-start/chunk/end/error) y muestra en vivo qué está
   haciendo la IA en modos Simple y Avanzado.

   En el modo Agente existe su propio activity log (eventos SSE con tool
   calls). Acá emulamos algo parecido pero a partir del único señal que
   tenemos en Simple/Avanzado: tokens fluyendo de /api/complete.

   Estrategia de render para que no parpadee:
   - Buffer rolling de ACUMULADO (no del delta). El stream del modelo emite
     de a 1-5 chars por chunk; si pintamos solo el delta, el preview parece
     "saltar". Si pintamos los últimos N del acumulado, queda estable.
   - rAF throttle: agrupamos chunks dentro del mismo frame en un solo render.
     Sin esto, React mete decenas de renders por segundo cuando el modelo
     escupe tokens rápido. */

const { useState: useStateAI, useEffect: useEffectAI, useRef: useRefAI } = React;

const PREVIEW_TAIL = 110;  // cuántos chars del acumulado mostrar en el stream
const AUTO_HIDE_DONE_MS = 6000;
const AUTO_HIDE_ERROR_MS = 10000;

function AIActivity() {
  const [state, setState] = useStateAI("idle");   // idle | running | done | error
  const [chars, setChars] = useStateAI(0);
  const [preview, setPreview] = useStateAI("");
  const [provider, setProvider] = useStateAI("");
  const [startedAt, setStartedAt] = useStateAI(null);
  const [elapsedMs, setElapsedMs] = useStateAI(0);
  const [error, setError] = useStateAI("");
  const [phase, setPhase] = useStateAI("Pensando…");
  const tickRef = useRefAI(null);
  const hideTimerRef = useRefAI(null);
  // Buffer de últimos chunks acumulados. Lo mantengo en ref para evitar
  // re-renderer en cada chunk; el rAF copia esto al state a 60fps máximo.
  const accRef = useRefAI("");
  const pendingFrameRef = useRefAI(null);

  // El "phase" es heurístico: en Simple/Avanzado la respuesta es UN solo
  // JSON, así que solo distinguimos fases por bytes recibidos. Feedback
  // aproximado, no telemetría real.
  useEffectAI(() => {
    if (state !== "running") return;
    if (chars === 0) setPhase("Pensando…");
    else if (chars < 200) setPhase("Generando estructura…");
    else if (chars < 1500) setPhase("Escribiendo fases…");
    else setPhase("Finalizando…");
  }, [state, chars]);

  // Tick del elapsed (200ms es más que suficiente para mostrar segundos)
  useEffectAI(() => {
    if (state !== "running" || !startedAt) {
      if (tickRef.current) { clearInterval(tickRef.current); tickRef.current = null; }
      return;
    }
    tickRef.current = setInterval(() => {
      setElapsedMs(Date.now() - startedAt);
    }, 200);
    return () => { if (tickRef.current) clearInterval(tickRef.current); };
  }, [state, startedAt]);

  useEffectAI(() => {
    const flush = () => {
      pendingFrameRef.current = null;
      const acc = accRef.current;
      setChars(acc.length);
      // El acumulado puede tener saltos de línea / JSON crudo; lo aplanamos a
      // una sola línea para el preview compacto. Mantenemos los últimos N chars.
      const flat = acc.replace(/\s+/g, " ");
      setPreview(flat.length > PREVIEW_TAIL ? "…" + flat.slice(-PREVIEW_TAIL) : flat);
    };

    const schedule = () => {
      if (pendingFrameRef.current != null) return;
      pendingFrameRef.current = requestAnimationFrame(flush);
    };

    const onStart = (e) => {
      const d = e.detail || {};
      if (hideTimerRef.current) { clearTimeout(hideTimerRef.current); hideTimerRef.current = null; }
      if (pendingFrameRef.current != null) {
        cancelAnimationFrame(pendingFrameRef.current);
        pendingFrameRef.current = null;
      }
      accRef.current = "";
      setState("running");
      setChars(0);
      setPreview("");
      setError("");
      setProvider(d.provider || "");
      setStartedAt(d.startedAt || Date.now());
      setElapsedMs(0);
      setPhase("Pensando…");
    };
    const onChunk = (e) => {
      const d = e.detail || {};
      // Preferimos el acumulado del shim (verdad canónica); si no, parcheamos
      // localmente concatenando el delta. Esto da un preview estable que no
      // parpadea con cada token.
      if (typeof d.accumulated === "string") accRef.current = d.accumulated;
      else if (typeof d.latest === "string") accRef.current += d.latest;
      schedule();
    };
    const onEnd = (e) => {
      const d = e.detail || {};
      // Forzar flush final antes de cambiar de estado para no quedarnos con un
      // preview a medio actualizar.
      if (pendingFrameRef.current != null) {
        cancelAnimationFrame(pendingFrameRef.current);
        pendingFrameRef.current = null;
      }
      const acc = accRef.current;
      setChars(typeof d.chars === "number" ? d.chars : acc.length);
      setState("done");
      setElapsedMs(d.durationMs || 0);
      hideTimerRef.current = setTimeout(() => setState("idle"), AUTO_HIDE_DONE_MS);
    };
    const onError = (e) => {
      const d = e.detail || {};
      setState("error");
      setError(d.message || "Error desconocido");
      hideTimerRef.current = setTimeout(() => setState("idle"), AUTO_HIDE_ERROR_MS);
    };
    window.addEventListener("sop-creator:ai-start", onStart);
    window.addEventListener("sop-creator:ai-chunk", onChunk);
    window.addEventListener("sop-creator:ai-end", onEnd);
    window.addEventListener("sop-creator:ai-error", onError);
    return () => {
      window.removeEventListener("sop-creator:ai-start", onStart);
      window.removeEventListener("sop-creator:ai-chunk", onChunk);
      window.removeEventListener("sop-creator:ai-end", onEnd);
      window.removeEventListener("sop-creator:ai-error", onError);
      if (tickRef.current) clearInterval(tickRef.current);
      if (hideTimerRef.current) clearTimeout(hideTimerRef.current);
      if (pendingFrameRef.current != null) cancelAnimationFrame(pendingFrameRef.current);
    };
  }, []);

  if (state === "idle") return null;

  const elapsedSec = (elapsedMs / 1000).toFixed(1);
  const providerLabel = provider === "codex" ? "Codex" : provider === "ollama" ? "Ollama" : provider || "IA";

  return (
    <div className={`ai-activity ai-activity--${state}`} role="status" aria-live="polite">
      <div className="ai-activity__head">
        <span className="ai-activity__icon">
          {state === "running" && <span className="ai-activity__spinner" />}
          {state === "done" && <Icon name="check" size={14} />}
          {state === "error" && <Icon name="x" size={14} />}
        </span>
        <span className="ai-activity__title">
          {state === "running" && phase}
          {state === "done" && "Listo"}
          {state === "error" && "Error"}
        </span>
        <span className="ai-activity__meta">
          <span className="ai-activity__provider">{providerLabel}</span>
          <span className="ai-activity__sep">·</span>
          <span>{chars.toLocaleString("es-ES")} chars</span>
          <span className="ai-activity__sep">·</span>
          <span>{elapsedSec}s</span>
        </span>
      </div>
      {state === "running" && preview && (
        <div className="ai-activity__stream" title="Últimos tokens recibidos">
          {preview}
        </div>
      )}
      {state === "error" && error && (
        <div className="ai-activity__err">{error}</div>
      )}
    </div>
  );
}

window.AIActivity = AIActivity;
