/* Coach — briefing diario + chat con agentes
 *
 * Modelo: 3 agentes (coach / nutri / médico) corren en paralelo cada mañana
 * sobre los datos de las últimas 24-72h, emiten una lectura individual y se
 * fusionan en un plan ajustado. El usuario aprueba, ajusta o rechaza. El chat
 * queda como acción secundaria (drawer) y SÍ pega contra POST /agents/ask.
 */

const PANEL_AGENTS = {
  coach:   { label: "Coach",         role: "Carga & entrenamiento",      icon: "coach",   color: "var(--accent)" },
  nutri:   { label: "Nutricionista", role: "Energía & composición",      icon: "apple",   color: "var(--good)" },
  medical: { label: "Médico",        role: "Lectura clínica",            icon: "medical", color: "var(--info)" },
};

const TODAY_READINGS = {
  coach: {
    status: "warn",
    headline: "Carga aguda alta · ratio ACWR 1.42",
    findings: [
      { tone: "BIEN",     text: "Volumen semanal en target (47 km vs 50)." },
      { tone: "ATENCIÓN", text: "ACWR 1.42 — riesgo elevado de lesión si sumás carga." },
      { tone: "MEJORA",   text: "Bajar el largo del sábado de 22 a 16 km, mover series intensas a el martes." },
    ],
    metric: { label: "ACWR", value: "1.42", target: "0.8-1.3" },
    confidence: 0.86,
    cost: 0.018,
  },
  nutri: {
    status: "good",
    headline: "Energía suficiente · proteína 1.6 g/kg",
    findings: [
      { tone: "BIEN",     text: "EA disponible 38 kcal/kg fat-free mass — sano." },
      { tone: "BIEN",     text: "Proteína distribuida en 4 tomas, target alcanzado." },
      { tone: "MEJORA",   text: "Subir carbos en día de largo: +60-90 g pre-entreno." },
    ],
    metric: { label: "EA", value: "38", target: "≥30 kcal/kg" },
    confidence: 0.79,
    cost: 0.014,
  },
  medical: {
    status: "warn",
    headline: "HRV en tendencia descendente 7d",
    findings: [
      { tone: "ATENCIÓN", text: "HRV pasó de 58 a 47 ms en 7 días (-19%)." },
      { tone: "ATENCIÓN", text: "FC reposo subió 4 ppm vs baseline." },
      { tone: "BIEN",     text: "Sin signos de infección o sobreentreno clínico todavía." },
      { tone: "MEJORA",   text: "Considerar 1-2 días de descarga + revisar sueño (5h32 promedio)." },
    ],
    metric: { label: "HRV 7d", value: "47", target: "≥55 ms" },
    confidence: 0.71,
    cost: 0.022,
  },
};

const REGEN_HISTORY = [
  { date: "Hoy 06:42",     trigger: "Auto · sync Garmin",     summary: "Bajada de HRV detectada → propuesto día de descarga", state: "pending" },
  { date: "Ayer 06:38",    trigger: "Auto",                   summary: "Sin cambios — dentro de tolerancia",                  state: "skipped" },
  { date: "Vie 18:10",     trigger: "Manual · sentís fatiga", summary: "Sustituido tempo por rodaje suave",                   state: "applied" },
  { date: "Mié 06:35",     trigger: "Auto",                   summary: "Aumento de volumen 5% para semana 3",                 state: "applied" },
  { date: "Lun 12:04",     trigger: "Manual · viaje",         summary: "Sesión movida de jueves a miércoles",                 state: "applied" },
];

const PLAN_DIFF = {
  date: "Sábado 02 may",
  original: {
    workout: { title: "Largo Z2", detail: "22 km · 6:15/km · ~2h17", load: 145, kcal: 1640 },
    nutri:   { title: "Carga normal", detail: "Carbs 5.5 g/kg · 2,650 kcal", load: 0 },
    notes:   "Última sesión larga del bloque base.",
  },
  proposed: {
    workout: { title: "Rodaje Z2 + movilidad", detail: "16 km · 6:30/km · ~1h44", load: 102, kcal: 1180 },
    nutri:   { title: "Carbs ↑ 60-90 g pre",   detail: "Carbs 5.8 g/kg · 2,560 kcal", load: 0 },
    notes:   "Bajar volumen 27% por HRV descendente. Si HRV recupera ≥55 ms, retomar largo el sábado próximo.",
  },
  changes: [
    { agent: "coach",   change: "−6 km (−27% volumen)" },
    { agent: "coach",   change: "Pace ajustado +15 s/km" },
    { agent: "nutri",   change: "+60-90 g carbs pre" },
    { agent: "medical", change: "Flag de descarga sugerido" },
  ],
};

const Agents = ({ density, dataState, showDisclaimer }) => {
  const inDemo = API.demoMode();
  const [view, setView] = React.useState("today");
  const [chatOpen, setChatOpen] = React.useState(false);

  // LLM configurado o no — sólo bloquea fuera de demo.
  const [llmConfigured, setLlmConfigured] = React.useState(true);

  React.useEffect(() => {
    if (inDemo) { setLlmConfigured(true); return; }
    API.getLLMConfig().then(() => setLlmConfigured(true)).catch((e) => {
      if (e.status === 404 || e.status === 412 || /not configured|missing/i.test(e.message)) setLlmConfigured(false);
    });
  }, [inDemo]);

  if (dataState === "empty") {
    return (
      <div style={{ padding: "var(--sp-8)" }}>
        <Empty
          icon="agents"
          title="Aún no hay evaluación"
          body="Necesitás al menos 7 días de datos sincronizados para que el coach pueda hacer una lectura útil. Conectá Garmin y dale unos días."
          action={<Btn variant="primary">Conectar Garmin</Btn>}
        />
      </div>
    );
  }

  if (!llmConfigured) {
    return (
      <div style={{ padding: "var(--sp-8)" }}>
        <Empty
          icon="cloud"
          title="LLM sin configurar"
          body="El briefing diario y el chat con agentes necesitan un proveedor LLM. Configurá uno en Configuración → LLM."
          action={<Btn variant="primary" onClick={() => { window.dispatchEvent(new CustomEvent("af-route", { detail: "settings" })); }}>Configurar LLM</Btn>}
        />
      </div>
    );
  }

  return (
    <div className="af-padded" style={{ padding: "var(--sp-6) var(--sp-8) 80px", maxWidth: 1280, margin: "0 auto" }}>
      <div style={{ display: "flex", alignItems: "center", gap: 8, marginBottom: 14 }}>
        <SegTabs
          value={view}
          onChange={setView}
          options={[
            { value: "today",   label: "Lectura de hoy" },
            { value: "history", label: "Regeneraciones" },
            { value: "chat",    label: "Chat libre" },
          ]}
        />
        <div style={{ flex: 1 }} />
      </div>

      {view === "today" && (
        inDemo
          ? <BriefingViewDemo showDisclaimer={showDisclaimer} onChat={() => setChatOpen(true)} />
          : <BriefingViewLive showDisclaimer={showDisclaimer} onChat={() => setChatOpen(true)} onLlmMissing={() => setLlmConfigured(false)} />
      )}

      {view === "history" && <RegenHistoryLive inDemo={inDemo} />}
      {view === "chat" && <ChatFull showDisclaimer={showDisclaimer} inDemo={inDemo} />}

      {chatOpen && <ChatDrawer onClose={() => setChatOpen(false)} />}
    </div>
  );
};

/* ───────── Vista LIVE (cableada al backend) ───────── */

const REFRESH_PHASES = [
  "Consultando coach…",
  "Consultando nutricionista…",
  "Consultando médico…",
  "Consolidando plan…",
];

const BriefingViewLive = ({ showDisclaimer, onChat, onLlmMissing }) => {
  /* Estados:
   *   loading: fetch inicial de /agents/briefing/latest
   *   refreshing: POST /agents/briefing/refresh corriendo (mostrar fases)
   *   briefing: el briefing actual (o null si 404)
   *   error: error duro distinto de 404/412 (red u otro 5xx)
   */
  const [loading, setLoading] = React.useState(true);
  const [refreshing, setRefreshing] = React.useState(false);
  const [phaseIdx, setPhaseIdx] = React.useState(0);
  const [briefing, setBriefing] = React.useState(null);
  const [error, setError] = React.useState(null);

  // Carga inicial
  React.useEffect(() => {
    let alive = true;
    (async () => {
      try {
        const res = await API.briefingLatest();
        if (!alive) return;
        setBriefing(res); // null si 404
        setError(null);
      } catch (e) {
        if (!alive) return;
        if (e.status === 401 || e.status === 403) return;
        setError(e.message);
      } finally {
        if (alive) setLoading(false);
      }
    })();
    return () => { alive = false; };
  }, []);

  // Animación de fases mientras refrescamos.
  React.useEffect(() => {
    if (!refreshing) { setPhaseIdx(0); return; }
    const t = setInterval(() => {
      setPhaseIdx(i => Math.min(i + 1, REFRESH_PHASES.length - 1));
    }, 4500);
    return () => clearInterval(t);
  }, [refreshing]);

  const triggerRefresh = async () => {
    setRefreshing(true);
    setPhaseIdx(0);
    setError(null);
    try {
      const res = await API.briefingRefresh();
      setBriefing(res);
    } catch (e) {
      if (e.llmMissing) {
        // Backend dice 412 — la vista padre redirige a empty state de LLM.
        onLlmMissing && onLlmMissing();
        return;
      }
      setError(e.message);
    } finally {
      setRefreshing(false);
    }
  };

  if (loading) {
    return (
      <div style={{ padding: "var(--sp-6)", textAlign: "center", color: "var(--text-3)", fontSize: 13 }}>
        Cargando briefing…
      </div>
    );
  }

  if (error && !briefing) {
    return (
      <Card style={{ background: "var(--bad-soft)", border: "1px solid color-mix(in srgb, var(--bad) 30%, transparent)" }}>
        <div className="eyebrow" style={{ color: "var(--bad)", marginBottom: 6 }}>NO PUDIMOS CARGAR EL BRIEFING</div>
        <div style={{ fontSize: 12, color: "var(--text-2)", marginBottom: 12 }}>{error}</div>
        <Btn variant="primary" onClick={triggerRefresh} disabled={refreshing}>Reintentar</Btn>
      </Card>
    );
  }

  if (refreshing) {
    return <BriefingRefreshing phaseIdx={phaseIdx} />;
  }

  if (!briefing) {
    return <BriefingEmptyState onGenerate={triggerRefresh} />;
  }

  return (
    <BriefingDisplay
      briefing={briefing}
      onRegenerate={triggerRefresh}
      onChat={onChat}
      showDisclaimer={showDisclaimer}
    />
  );
};

const BriefingEmptyState = ({ onGenerate }) => (
  <div style={{ padding: "var(--sp-6) 0" }}>
    <Empty
      icon="agents"
      title="No hay briefing aún para hoy."
      body="Generar uno toma ~15-30 segundos. Los 3 agentes (coach, nutricionista, médico) corren en paralelo sobre tus últimas 24-72h de datos y proponen un plan ajustado."
      action={<Btn variant="primary" size="lg" onClick={onGenerate} icon={<Icon name="refresh" size={13} />}>Generar briefing del día</Btn>}
    />
  </div>
);

const BriefingRefreshing = ({ phaseIdx }) => (
  <div style={{
    background: "linear-gradient(135deg, var(--bg-1) 0%, var(--bg-2) 100%)",
    border: "1px solid var(--line)",
    borderRadius: "var(--r-3)",
    padding: "var(--sp-7)",
    minHeight: 320,
    display: "flex", flexDirection: "column", justifyContent: "center", alignItems: "center", gap: 16,
  }}>
    <div style={{
      width: 44, height: 44, borderRadius: "50%",
      background: "var(--accent-soft)", color: "var(--accent)",
      display: "grid", placeItems: "center",
    }}>
      <Icon name="spinner" size={20} color="var(--accent)" />
    </div>
    <div style={{
      fontSize: 16, fontWeight: 500, color: "var(--text)",
      fontFamily: "var(--font-display)", letterSpacing: "-0.01em",
    }}>
      Generando briefing del día…
    </div>
    <div style={{ display: "flex", flexDirection: "column", gap: 6, minWidth: 280 }}>
      {REFRESH_PHASES.map((p, i) => {
        const done = i < phaseIdx;
        const active = i === phaseIdx;
        return (
          <div key={i} style={{
            display: "flex", alignItems: "center", gap: 8,
            fontSize: 12, fontFamily: "var(--font-mono)",
            color: done ? "var(--good)" : active ? "var(--text)" : "var(--text-4)",
            opacity: done ? 0.7 : 1,
          }}>
            <span style={{ width: 10, display: "inline-block", textAlign: "center" }}>
              {done ? "✓" : active ? "●" : "·"}
            </span>
            <span>{p}</span>
          </div>
        );
      })}
    </div>
    <div style={{ fontSize: 11, color: "var(--text-3)", fontFamily: "var(--font-mono)" }}>
      ~15-30s · podés cerrar y volver, no se cancela
    </div>
  </div>
);

const BriefingDisplay = ({ briefing, onRegenerate, onChat, showDisclaimer }) => {
  const readings = Array.isArray(briefing.readings) ? briefing.readings : [];
  const generatedLabel = formatBriefingDate(briefing.generated_at);
  const hasMedical = readings.some(r => r.agent === "medical");

  return (
    <>
      <div style={{
        background: "linear-gradient(135deg, var(--bg-1) 0%, var(--bg-2) 100%)",
        border: "1px solid var(--line)",
        borderRadius: "var(--r-3)",
        padding: "var(--sp-6) var(--sp-7)",
        display: "grid", gridTemplateColumns: "1fr auto", gap: 24, alignItems: "center",
        marginBottom: 16,
      }}>
        <div>
          <div className="eyebrow" style={{ marginBottom: 8, color: "var(--accent)" }}>
            BRIEFING DEL DÍA{generatedLabel ? ` · ${generatedLabel.toUpperCase()}` : ""}
          </div>
          <h2 style={{
            fontSize: 22, fontWeight: 400, margin: "0 0 6px",
            fontFamily: "var(--font-display)", letterSpacing: "-0.01em",
            color: "var(--text)", lineHeight: 1.2,
          }}>
            Lectura conjunta de los 3 agentes.
          </h2>
          <div style={{ fontSize: 12.5, color: "var(--text-3)", fontFamily: "var(--font-mono)" }}>
            {readings.length} lectura{readings.length === 1 ? "" : "s"}
            {briefing.id ? ` · id ${String(briefing.id).slice(0, 8)}` : ""}
          </div>
        </div>
        <div style={{ display: "flex", flexDirection: "column", gap: 8, minWidth: 200 }}>
          <Btn variant="primary" size="md" onClick={onRegenerate} icon={<Icon name="refresh" size={13} />}>
            Regenerar
          </Btn>
          <Btn variant="ghost" size="sm" onClick={onChat} icon={<Icon name="chat" size={12} />}>
            Pedir ajustes (chat)
          </Btn>
        </div>
      </div>

      {readings.length > 0 && (
        <div className="af-grid-3" style={{
          display: "grid",
          gridTemplateColumns: `repeat(${Math.min(readings.length, 3)}, 1fr)`,
          gap: 12, marginBottom: 16,
        }}>
          {readings.map((r, i) => (
            <BriefingReadingCard key={i} reading={r} showDisclaimer={showDisclaimer} />
          ))}
        </div>
      )}

      {briefing.plan_proposed && (
        <Card>
          <div style={{ display: "flex", alignItems: "center", gap: 10, marginBottom: 14 }}>
            <Icon name="merge" size={14} color="var(--accent)" />
            <div className="eyebrow">PLAN PROPUESTO</div>
            <div style={{ flex: 1 }} />
            <span style={{
              fontSize: 9.5, padding: "3px 7px", borderRadius: 3,
              background: "var(--accent-soft)", color: "var(--accent)",
              fontFamily: "var(--font-mono)", fontWeight: 600, letterSpacing: "0.06em",
            }}>SÍNTESIS CONJUNTA</span>
          </div>
          <MarkdownRender text={briefing.plan_proposed} />
        </Card>
      )}

      {showDisclaimer && hasMedical && (
        <div style={{
          marginTop: 16, padding: "10px 14px",
          background: "var(--warn-soft)",
          border: "1px solid color-mix(in srgb, var(--warn) 35%, transparent)",
          borderRadius: "var(--r-2)",
          display: "flex", gap: 10, fontSize: 11.5, color: "var(--text-2)", alignItems: "flex-start",
        }}>
          <Icon name="alert" size={14} color="var(--warn)" />
          <div>
            <strong style={{ color: "var(--warn)" }}>Esto no es consejo médico.</strong> El módulo médico interpreta tendencias de tus datos. No reemplaza consulta clínica ni diagnóstico. Ante síntomas, consultá un profesional.
          </div>
        </div>
      )}
    </>
  );
};

const BriefingReadingCard = ({ reading, showDisclaimer }) => {
  const agentKey = reading.agent || "coach";
  const agent = PANEL_AGENTS[agentKey] || { label: agentKey, role: "", icon: "bot", color: "var(--accent)" };
  const isMedical = agentKey === "medical";
  const sources = Array.isArray(reading.sources) ? reading.sources : [];

  return (
    <div style={{
      background: "var(--bg-1)",
      border: "1px solid var(--line)",
      borderRadius: "var(--r-3)",
      padding: "var(--sp-4) var(--sp-5)",
      position: "relative",
    }}>
      <div style={{
        position: "absolute", top: 0, left: 0, right: 0, height: 3,
        background: agent.color,
        borderRadius: "var(--r-3) var(--r-3) 0 0",
      }} />

      <div style={{ display: "flex", alignItems: "flex-start", gap: 10, marginBottom: 10 }}>
        <div style={{
          width: 32, height: 32, borderRadius: 8,
          background: `color-mix(in srgb, ${agent.color} 14%, transparent)`,
          color: agent.color, display: "grid", placeItems: "center", flexShrink: 0,
        }}>
          <Icon name={agent.icon} size={15} color={agent.color} />
        </div>
        <div style={{ flex: 1, minWidth: 0 }}>
          <div style={{ fontSize: 13.5, fontWeight: 500, color: "var(--text)" }}>{agent.label}</div>
          <div style={{ fontSize: 10.5, color: "var(--text-3)" }}>{agent.role}</div>
        </div>
      </div>

      {isMedical && showDisclaimer && (
        <div style={{
          padding: "8px 10px", marginBottom: 10,
          background: "var(--warn-soft)",
          border: "1px solid color-mix(in srgb, var(--warn) 30%, transparent)",
          borderRadius: "var(--r-2)", fontSize: 10.5, color: "var(--text-2)",
          display: "flex", gap: 6, alignItems: "flex-start",
        }}>
          <Icon name="alert" size={11} color="var(--warn)" />
          <span><strong style={{ color: "var(--warn)" }}>No es consejo médico.</strong> Tendencias, no diagnóstico.</span>
        </div>
      )}

      {reading.title && (
        <div style={{ fontSize: 13, color: "var(--text)", lineHeight: 1.4, marginBottom: 8, fontWeight: 500 }}>
          {reading.title}
        </div>
      )}

      {reading.body && (
        <div style={{ fontSize: 12.5, color: "var(--text-2)", lineHeight: 1.55, whiteSpace: "pre-wrap" }}>
          {reading.body}
        </div>
      )}

      {sources.length > 0 && (
        <div style={{ marginTop: 12, paddingTop: 10, borderTop: "1px dashed var(--line-soft)", display: "flex", flexWrap: "wrap", gap: 6 }}>
          {sources.map((s, i) => (
            <span key={i} style={{
              fontSize: 9.5, padding: "2px 7px", borderRadius: 3,
              background: "var(--bg-2)", border: "1px solid var(--line-soft)",
              color: "var(--text-3)", fontFamily: "var(--font-mono)",
            }}>{s}</span>
          ))}
        </div>
      )}
    </div>
  );
};

/* Mini-parser de markdown (titulos h1-h3, listas con `-` y `*`, negritas con
 * `**...**`, italics con `_..._`). Suficiente para lo que escupen los LLMs en
 * el plan_proposed; si más adelante necesitamos más cosas, swap a `marked`
 * desde CDN. */
const MarkdownRender = ({ text }) => {
  const blocks = parseMarkdownBlocks(text || "");
  return (
    <div style={{ fontSize: 13, color: "var(--text)", lineHeight: 1.6 }}>
      {blocks.map((b, i) => {
        if (b.type === "h1") return <h3 key={i} style={{ margin: "10px 0 6px", fontSize: 16, fontWeight: 500, fontFamily: "var(--font-display)", letterSpacing: "-0.01em" }}>{renderInline(b.text)}</h3>;
        if (b.type === "h2") return <h4 key={i} style={{ margin: "8px 0 4px", fontSize: 14, fontWeight: 500, color: "var(--text)" }}>{renderInline(b.text)}</h4>;
        if (b.type === "h3") return <h5 key={i} style={{ margin: "6px 0 3px", fontSize: 12.5, fontWeight: 600, color: "var(--text-2)", fontFamily: "var(--font-mono)", textTransform: "uppercase", letterSpacing: "0.06em" }}>{renderInline(b.text)}</h5>;
        if (b.type === "ul") return (
          <ul key={i} style={{ margin: "4px 0 8px 0", paddingLeft: 20 }}>
            {b.items.map((li, j) => <li key={j} style={{ marginBottom: 3, color: "var(--text-2)" }}>{renderInline(li)}</li>)}
          </ul>
        );
        if (b.type === "ol") return (
          <ol key={i} style={{ margin: "4px 0 8px 0", paddingLeft: 20 }}>
            {b.items.map((li, j) => <li key={j} style={{ marginBottom: 3, color: "var(--text-2)" }}>{renderInline(li)}</li>)}
          </ol>
        );
        if (b.type === "p") return <p key={i} style={{ margin: "0 0 8px", color: "var(--text-2)" }}>{renderInline(b.text)}</p>;
        return null;
      })}
    </div>
  );
};

/* Quita el code-fence con el que algunos modelos envuelven toda la respuesta
 * ("```markdown\n...\n```"). Si la primera línea no-vacía abre un fence, lo
 * removemos junto con el fence final. Se aplica también dentro del parser de
 * planes (utils-plan-parser.jsx) para que el calendario detecte las semanas. */
function stripWrappingCodeFence(md) {
  if (!md) return md;
  const text = String(md).replace(/\r\n/g, "\n");
  const m = text.match(/^\s*```[a-zA-Z]*\s*\n([\s\S]*?)\n```\s*$/);
  if (m) return m[1];
  // También formato sin newline final (algunos modelos cierran sin salto):
  const m2 = text.match(/^\s*```[a-zA-Z]*\s*\n([\s\S]*?)```\s*$/);
  if (m2) return m2[1];
  return text;
}

function parseMarkdownBlocks(md) {
  const lines = stripWrappingCodeFence(md).split("\n");
  const blocks = [];
  let para = [];
  let list = null;          // {type: "ul"|"ol", items: []}
  const flushPara = () => { if (para.length) { blocks.push({ type: "p", text: para.join(" ") }); para = []; } };
  const flushList = () => { if (list) { blocks.push(list); list = null; } };
  for (const raw of lines) {
    const line = raw.trim();
    if (!line) { flushPara(); flushList(); continue; }
    if (line.startsWith("#### ")) { flushPara(); flushList(); blocks.push({ type: "h3", text: line.slice(5) }); continue; }
    if (line.startsWith("### "))  { flushPara(); flushList(); blocks.push({ type: "h3", text: line.slice(4) }); continue; }
    if (line.startsWith("## "))   { flushPara(); flushList(); blocks.push({ type: "h2", text: line.slice(3) }); continue; }
    if (line.startsWith("# "))    { flushPara(); flushList(); blocks.push({ type: "h1", text: line.slice(2) }); continue; }
    const liUl = line.match(/^[-*]\s+(.+)$/);
    if (liUl) {
      flushPara();
      if (!list || list.type !== "ul") { flushList(); list = { type: "ul", items: [] }; }
      list.items.push(liUl[1]);
      continue;
    }
    const liOl = line.match(/^\d+\.\s+(.+)$/);
    if (liOl) {
      flushPara();
      if (!list || list.type !== "ol") { flushList(); list = { type: "ol", items: [] }; }
      list.items.push(liOl[1]);
      continue;
    }
    flushList();
    para.push(line);
  }
  flushPara(); flushList();
  return blocks;
}

// Expose so utils-plan-parser.jsx can reuse the same fence-stripping logic.
window.stripWrappingCodeFence = stripWrappingCodeFence;

function renderInline(text) {
  // Escape HTML; soporte simple para **bold** y _italic_.
  // Devolvemos un array de fragments React seguros.
  const parts = [];
  const re = /(\*\*([^*]+)\*\*|_([^_]+)_)/g;
  let last = 0; let m; let key = 0;
  while ((m = re.exec(text)) !== null) {
    if (m.index > last) parts.push(text.slice(last, m.index));
    if (m[2] != null) parts.push(<strong key={`b${key++}`}>{m[2]}</strong>);
    else if (m[3] != null) parts.push(<em key={`i${key++}`}>{m[3]}</em>);
    last = m.index + m[0].length;
  }
  if (last < text.length) parts.push(text.slice(last));
  return parts;
}

function formatBriefingDate(iso) {
  if (!iso) return "";
  try {
    const d = new Date(iso);
    if (isNaN(d.getTime())) return "";
    const months = ["ene", "feb", "mar", "abr", "may", "jun", "jul", "ago", "sep", "oct", "nov", "dic"];
    const days = ["dom", "lun", "mar", "mié", "jue", "vie", "sáb"];
    return `${days[d.getDay()]} ${String(d.getDate()).padStart(2, "0")} ${months[d.getMonth()]} · ${String(d.getHours()).padStart(2, "0")}:${String(d.getMinutes()).padStart(2, "0")}`;
  } catch (_) { return ""; }
}

/* ───────── Vista DEMO (mock con regenerar simulado) ───────── */

const BriefingViewDemo = ({ showDisclaimer, onChat }) => {
  const [regenerating, setRegenerating] = React.useState(false);
  const [selectedReading, setSelectedReading] = React.useState(null);
  return (
    <>
      <BriefingHero
        regenerating={regenerating}
        onRegenerate={() => {
          setRegenerating(true);
          setTimeout(() => setRegenerating(false), 2400);
        }}
        onChat={onChat}
      />
      <div style={{ height: 16 }} />
      <div className="af-grid-3" style={{ display: "grid", gridTemplateColumns: "repeat(3, 1fr)", gap: 12, marginBottom: 16 }}>
        {Object.entries(TODAY_READINGS).map(([k, r]) => (
          <ReadingCard
            key={k}
            agent={PANEL_AGENTS[k]}
            reading={r}
            regenerating={regenerating}
            onClick={() => setSelectedReading(k)}
            expanded={selectedReading === k}
          />
        ))}
      </div>
      <PlanDiff diff={PLAN_DIFF} regenerating={regenerating} />
      <ActionsBar onChat={onChat} />
      {showDisclaimer && (
        <div style={{
          marginTop: 16, padding: "10px 14px",
          background: "var(--warn-soft)",
          border: "1px solid color-mix(in srgb, var(--warn) 35%, transparent)",
          borderRadius: "var(--r-2)",
          display: "flex", gap: 10, fontSize: 11.5, color: "var(--text-2)", alignItems: "flex-start",
        }}>
          <Icon name="alert" size={14} color="var(--warn)" />
          <div>
            <strong style={{ color: "var(--warn)" }}>Esto no es consejo médico.</strong> El módulo médico interpreta tendencias de tus datos. No reemplaza consulta clínica ni diagnóstico. Ante síntomas, consultá un profesional.
          </div>
        </div>
      )}
    </>
  );
};

const BriefingHero = ({ regenerating, onRegenerate, onChat }) => (
  <div style={{
    background: "linear-gradient(135deg, var(--bg-1) 0%, var(--bg-2) 100%)",
    border: "1px solid var(--line)",
    borderRadius: "var(--r-3)",
    padding: "var(--sp-6) var(--sp-7)",
    display: "grid", gridTemplateColumns: "1fr auto", gap: 24, alignItems: "center",
  }}>
    <div>
      <div className="eyebrow" style={{ marginBottom: 8, color: "var(--accent)" }}>
        BRIEFING DEL DÍA · SÁB 02 MAY
      </div>
      <h2 style={{
        fontSize: 26, fontWeight: 400, margin: "0 0 10px",
        fontFamily: "var(--font-display)", letterSpacing: "-0.01em",
        color: "var(--text)", lineHeight: 1.15,
      }}>
        Tu cuerpo pide una descarga.
        <span style={{ color: "var(--text-3)" }}> El plan se puede ajustar.</span>
      </h2>
      <div style={{ fontSize: 13.5, color: "var(--text-2)", lineHeight: 1.5, maxWidth: 640 }}>
        Los 3 agentes coinciden en que estás en una semana de carga alta con HRV
        descendente. La <strong>recomendación conjunta</strong> es bajar el largo del
        sábado de 22 a 16 km y subir carbos pre-entreno. <em style={{ color: "var(--text-3)" }}>Vos decidís.</em>
      </div>

      <div style={{ display: "flex", gap: 16, marginTop: 14, fontSize: 11, color: "var(--warn)", fontFamily: "var(--font-mono)" }}>
        <span>● coach: ATENCIÓN</span>
        <span style={{ color: "var(--good)" }}>● nutri: BIEN</span>
        <span style={{ color: "var(--warn)" }}>● médico: ATENCIÓN</span>
      </div>
    </div>

    <div style={{ display: "flex", flexDirection: "column", gap: 8, alignItems: "stretch", minWidth: 200 }}>
      <Btn variant="primary" size="lg" onClick={onRegenerate} disabled={regenerating}
        icon={<Icon name={regenerating ? "spinner" : "refresh"} size={14} />}>
        {regenerating ? "Regenerando…" : "Regenerar lectura"}
      </Btn>
      <Btn variant="ghost" size="sm" onClick={onChat} icon={<Icon name="chat" size={12} />}>
        Pedir ajustes (chat)
      </Btn>
      <div style={{ fontSize: 10.5, color: "var(--text-3)", textAlign: "center", marginTop: 4, fontFamily: "var(--font-mono)" }}>
        próx. auto-corrida: mañana 06:30
      </div>
    </div>
  </div>
);

const STATUS_TONE = {
  good: { color: "var(--good)", label: "BIEN" },
  warn: { color: "var(--warn)", label: "ATENCIÓN" },
  bad:  { color: "var(--bad)",  label: "ALERTA" },
};

const ReadingCard = ({ agent, reading, regenerating, onClick, expanded }) => {
  const t = STATUS_TONE[reading.status];
  return (
    <div
      onClick={onClick}
      style={{
        background: "var(--bg-1)",
        border: `1px solid ${expanded ? agent.color : "var(--line)"}`,
        borderRadius: "var(--r-3)",
        padding: "var(--sp-4) var(--sp-5)",
        cursor: "pointer",
        position: "relative",
        opacity: regenerating ? 0.5 : 1,
        transition: "all 0.2s",
      }}>
      <div style={{
        position: "absolute", top: 0, left: 0, right: 0, height: 3,
        background: t.color,
        borderRadius: "var(--r-3) var(--r-3) 0 0",
      }} />

      <div style={{ display: "flex", alignItems: "flex-start", gap: 10, marginBottom: 10 }}>
        <div style={{
          width: 32, height: 32, borderRadius: 8,
          background: `color-mix(in srgb, ${agent.color} 14%, transparent)`,
          color: agent.color, display: "grid", placeItems: "center", flexShrink: 0,
        }}>
          <Icon name={agent.icon} size={15} color={agent.color} />
        </div>
        <div style={{ flex: 1, minWidth: 0 }}>
          <div style={{ fontSize: 13.5, fontWeight: 500, color: "var(--text)" }}>{agent.label}</div>
          <div style={{ fontSize: 10.5, color: "var(--text-3)" }}>{agent.role}</div>
        </div>
        <div style={{
          fontSize: 9.5, fontWeight: 600, letterSpacing: "0.06em",
          padding: "3px 7px", borderRadius: 3,
          background: `color-mix(in srgb, ${t.color} 14%, transparent)`,
          color: t.color, fontFamily: "var(--font-mono)",
        }}>
          {t.label}
        </div>
      </div>

      <div style={{ fontSize: 13, color: "var(--text)", lineHeight: 1.45, marginBottom: 10, fontWeight: 500 }}>
        {reading.headline}
      </div>

      <div style={{
        display: "flex", alignItems: "baseline", gap: 8,
        padding: "6px 10px",
        background: "var(--bg-2)", border: "1px dashed var(--line)",
        borderRadius: 4, marginBottom: 12,
      }}>
        <span style={{ fontSize: 10, color: "var(--text-3)", fontFamily: "var(--font-mono)" }}>{reading.metric.label}</span>
        <span className="num" style={{ fontSize: 16, fontWeight: 500, color: t.color }}>{reading.metric.value}</span>
        <span style={{ fontSize: 10, color: "var(--text-4)", fontFamily: "var(--font-mono)", marginLeft: "auto" }}>target {reading.metric.target}</span>
      </div>

      <div style={{ display: "flex", flexDirection: "column", gap: 6 }}>
        {reading.findings.map((f, i) => (
          <FindingRow key={i} finding={f} />
        ))}
      </div>

      <div style={{
        marginTop: 12, paddingTop: 10, borderTop: "1px dashed var(--line-soft)",
        display: "flex", justifyContent: "space-between", alignItems: "center",
        fontSize: 10, color: "var(--text-3)", fontFamily: "var(--font-mono)",
      }}>
        <span>conf {(reading.confidence * 100).toFixed(0)}%</span>
        <span>${reading.cost.toFixed(3)}</span>
      </div>
    </div>
  );
};

const TONE_STYLE = {
  "BIEN":     { color: "var(--good)" },
  "ATENCIÓN": { color: "var(--warn)" },
  "MEJORA":   { color: "var(--accent)" },
  "ALERTA":   { color: "var(--bad)"  },
};

const FindingRow = ({ finding }) => {
  const s = TONE_STYLE[finding.tone] || TONE_STYLE.BIEN;
  return (
    <div style={{ display: "flex", gap: 8, alignItems: "flex-start", fontSize: 12, lineHeight: 1.45 }}>
      <span style={{
        fontFamily: "var(--font-mono)", fontSize: 9.5, fontWeight: 700,
        color: s.color, letterSpacing: "0.06em", flexShrink: 0,
        width: 60, paddingTop: 2,
      }}>
        {finding.tone}
      </span>
      <span style={{ color: "var(--text-2)", flex: 1 }}>{finding.text}</span>
    </div>
  );
};

const PlanDiff = ({ diff, regenerating }) => (
  <div style={{
    background: "var(--bg-1)",
    border: "1px solid var(--line)",
    borderRadius: "var(--r-3)",
    overflow: "hidden",
    opacity: regenerating ? 0.4 : 1,
    transition: "opacity 0.2s",
  }}>
    <div style={{
      padding: "12px 18px",
      display: "flex", alignItems: "center", gap: 10,
      borderBottom: "1px solid var(--line-soft)",
      background: "var(--bg-2)",
    }}>
      <Icon name="merge" size={14} color="var(--accent)" />
      <div style={{ fontSize: 13, fontWeight: 500 }}>Plan ajustado · {diff.date}</div>
      <span style={{
        marginLeft: 8, padding: "2px 7px", borderRadius: 3,
        background: "var(--accent-soft)", color: "var(--accent)",
        fontSize: 9.5, fontWeight: 600, fontFamily: "var(--font-mono)", letterSpacing: "0.06em",
      }}>
        SÍNTESIS CONJUNTA
      </span>
      <div style={{ flex: 1 }} />
      <span style={{ fontSize: 11, color: "var(--text-3)", fontFamily: "var(--font-mono)" }}>
        {diff.changes.length} cambios
      </span>
    </div>

    <div style={{ display: "grid", gridTemplateColumns: "1fr auto 1fr" }}>
      <PlanColumn label="Plan original" tone="muted" plan={diff.original} />
      <div style={{
        display: "flex", alignItems: "center", justifyContent: "center",
        padding: "0 12px",
        borderLeft: "1px dashed var(--line-soft)",
        borderRight: "1px dashed var(--line-soft)",
      }}>
        <div style={{
          width: 32, height: 32, borderRadius: "50%",
          background: "var(--accent-soft)", color: "var(--accent)",
          display: "grid", placeItems: "center", fontSize: 14,
        }}>→</div>
      </div>
      <PlanColumn label="Propuesto" tone="active" plan={diff.proposed} />
    </div>

    <div style={{
      padding: "12px 18px",
      borderTop: "1px solid var(--line-soft)",
      background: "var(--bg-2)",
      display: "flex", flexWrap: "wrap", gap: 8,
    }}>
      {diff.changes.map((c, i) => (
        <span key={i} style={{
          fontSize: 11, padding: "4px 9px",
          background: `color-mix(in srgb, ${PANEL_AGENTS[c.agent].color} 12%, transparent)`,
          color: PANEL_AGENTS[c.agent].color,
          border: `1px solid color-mix(in srgb, ${PANEL_AGENTS[c.agent].color} 30%, transparent)`,
          borderRadius: 3, fontFamily: "var(--font-mono)",
        }}>
          {PANEL_AGENTS[c.agent].label.toLowerCase()} · {c.change}
        </span>
      ))}
    </div>
  </div>
);

const PlanColumn = ({ label, plan, tone }) => {
  const isActive = tone === "active";
  return (
    <div style={{
      padding: "var(--sp-5) var(--sp-6)",
      opacity: isActive ? 1 : 0.7,
    }}>
      <div className="eyebrow" style={{
        marginBottom: 12,
        color: isActive ? "var(--accent)" : "var(--text-3)",
      }}>{label}</div>

      <div style={{ marginBottom: 14 }}>
        <div style={{ fontSize: 10, color: "var(--text-3)", marginBottom: 4, fontFamily: "var(--font-mono)" }}>ENTRENO</div>
        <div style={{
          fontSize: 16, fontWeight: 500, color: "var(--text)",
          textDecoration: isActive ? "none" : "line-through",
          textDecorationColor: "var(--text-4)",
        }}>
          {plan.workout.title}
        </div>
        <div style={{ fontSize: 12, color: "var(--text-2)", marginTop: 2, fontFamily: "var(--font-mono)" }}>
          {plan.workout.detail}
        </div>
        <div style={{ display: "flex", gap: 14, marginTop: 8 }}>
          <DiffStat label="Carga" value={plan.workout.load} />
          <DiffStat label="Kcal" value={plan.workout.kcal} />
        </div>
      </div>

      <div style={{ marginBottom: 14, paddingTop: 12, borderTop: "1px dashed var(--line-soft)" }}>
        <div style={{ fontSize: 10, color: "var(--text-3)", marginBottom: 4, fontFamily: "var(--font-mono)" }}>NUTRICIÓN</div>
        <div style={{ fontSize: 14, fontWeight: 500, color: "var(--text)" }}>{plan.nutri.title}</div>
        <div style={{ fontSize: 12, color: "var(--text-2)", marginTop: 2, fontFamily: "var(--font-mono)" }}>
          {plan.nutri.detail}
        </div>
      </div>

      <div style={{
        fontSize: 11.5, color: "var(--text-3)", lineHeight: 1.45,
        paddingTop: 10, borderTop: "1px dashed var(--line-soft)",
        fontStyle: "italic",
      }}>
        {plan.notes}
      </div>
    </div>
  );
};

const DiffStat = ({ label, value }) => (
  <div>
    <div style={{ fontSize: 9.5, color: "var(--text-3)", fontFamily: "var(--font-mono)" }}>{label}</div>
    <div className="num" style={{ fontSize: 14, fontWeight: 500 }}>{value}</div>
  </div>
);

const ActionsBar = ({ onChat }) => (
  <div style={{
    marginTop: 16,
    padding: "14px 18px",
    background: "var(--bg-1)",
    border: "1px solid var(--line)",
    borderRadius: "var(--r-3)",
    display: "flex", alignItems: "center", gap: 12, flexWrap: "wrap",
  }}>
    <div style={{ flex: 1, minWidth: 240 }}>
      <div style={{ fontSize: 13, fontWeight: 500, color: "var(--text)" }}>¿Aplicás los cambios al plan?</div>
      <div style={{ fontSize: 11.5, color: "var(--text-3)", marginTop: 2 }}>
        Si aceptás, el plan de la semana se reescribe. Podés revertir desde el historial.
      </div>
    </div>
    <Btn variant="ghost" size="sm" icon={<Icon name="x" size={12} />}>Rechazar</Btn>
    <Btn variant="ghost" size="sm" onClick={onChat} icon={<Icon name="chat" size={12} />}>Ajustar</Btn>
    <Btn variant="primary" icon={<Icon name="check" size={13} />}>Aplicar al plan</Btn>
  </div>
);

const HISTORY_STATE = {
  applied: { color: "var(--good)",  label: "APLICADO" },
  pending: { color: "var(--accent)", label: "PENDIENTE" },
  skipped: { color: "var(--text-3)", label: "SIN CAMBIOS" },
  rejected:{ color: "var(--bad)",   label: "RECHAZADO" },
};

/* RegenHistoryLive — wrapper que carga /agents/briefings de verdad y cae al
 * mock estático sólo en modo demo. Antes la pestaña Historial siempre
 * renderizaba REGEN_HISTORY hardcoded incluso para usuarios reales. */
const RegenHistoryLive = ({ inDemo }) => {
  const [items, setItems] = React.useState(inDemo ? REGEN_HISTORY : null);
  const [err, setErr] = React.useState(null);

  React.useEffect(() => {
    if (inDemo) return;
    let alive = true;
    (async () => {
      try {
        // briefingsHistory está expuesto en api.js (GET /agents/briefings)
        const res = await API.briefingsHistory({ limit: 30 });
        const list = Array.isArray(res?.briefings) ? res.briefings : [];
        if (!alive) return;
        // Mapear al shape esperado por RegenHistory: {date, summary, trigger, state}.
        setItems(list.map((b) => ({
          date: (b.generated_at || "").slice(0, 16).replace("T", " "),
          summary: `Briefing ${String(b.id || "").slice(0, 8)}`,
          trigger: "manual",
          state: "applied",
        })));
      } catch (e) {
        if (!alive) return;
        if (e.status === 401 || e.status === 403) return;
        setErr(e.message || "No se pudo cargar el historial.");
        setItems([]);
      }
    })();
    return () => { alive = false; };
  }, [inDemo]);

  if (items === null) {
    return (
      <div style={{ padding: "30px 0", textAlign: "center", color: "var(--text-3)", fontFamily: "var(--font-mono)", fontSize: 12 }}>
        cargando historial…
      </div>
    );
  }
  if (err) {
    return (
      <Card>
        <div style={{ fontSize: 12, color: "var(--warn)" }}>{err}</div>
      </Card>
    );
  }
  if (items.length === 0) {
    return (
      <Card>
        <div style={{ padding: "20px 10px", textAlign: "center", fontSize: 12, color: "var(--text-3)", fontFamily: "var(--font-mono)" }}>
          Sin briefings generados todavía.<br />
          <span style={{ color: "var(--text-4)", fontSize: 10, marginTop: 6, display: "inline-block" }}>
            Generá uno desde "Lectura de hoy".
          </span>
        </div>
      </Card>
    );
  }
  return <RegenHistory items={items} />;
};

const RegenHistory = ({ items }) => (
  <div style={{
    background: "var(--bg-1)", border: "1px solid var(--line)",
    borderRadius: "var(--r-3)", overflow: "hidden",
  }}>
    <div style={{
      padding: "12px 18px", borderBottom: "1px solid var(--line-soft)",
      display: "flex", alignItems: "center", gap: 10,
    }}>
      <Icon name="clock" size={13} color="var(--text-3)" />
      <div style={{ fontSize: 13, fontWeight: 500 }}>Historial de regeneraciones</div>
      <div style={{ flex: 1 }} />
      <span style={{ fontSize: 11, color: "var(--text-3)", fontFamily: "var(--font-mono)" }}>últimas 7 corridas</span>
    </div>

    <div>
      {items.map((it, i) => {
        const s = HISTORY_STATE[it.state];
        return (
          <div key={i} style={{
            display: "grid", gridTemplateColumns: "120px 1fr 100px auto",
            gap: 16, alignItems: "center",
            padding: "14px 18px",
            borderBottom: i === items.length - 1 ? "none" : "1px solid var(--line-soft)",
            fontSize: 12.5,
          }}>
            <div style={{ fontSize: 11, color: "var(--text-3)", fontFamily: "var(--font-mono)" }}>{it.date}</div>
            <div>
              <div style={{ color: "var(--text)" }}>{it.summary}</div>
              <div style={{ fontSize: 10.5, color: "var(--text-3)", marginTop: 2, fontFamily: "var(--font-mono)" }}>{it.trigger}</div>
            </div>
            <span style={{
              fontSize: 10, fontWeight: 600, letterSpacing: "0.06em",
              padding: "3px 8px", borderRadius: 3,
              background: `color-mix(in srgb, ${s.color} 12%, transparent)`,
              color: s.color, fontFamily: "var(--font-mono)",
              justifySelf: "start",
            }}>{s.label}</span>
            <Btn size="sm" variant="ghost">Ver</Btn>
          </div>
        );
      })}
    </div>
  </div>
);

/* ───────── Chat completo (cableado a /agents/ask) ───────── */

// Sugerencias evergreen — preguntas que funcionan independientemente del estado
// del usuario. Antes había sugerencias específicas (ej. "antes del largo") que
// solo aplicaban a runners y se veían fuera de contexto a otros perfiles.
const SUGGESTIONS = [
  "¿Cómo estuvo mi semana?",
  "¿Qué dice mi sueño últimamente?",
  "Plan para mañana",
];

const ChatFull = ({ showDisclaimer, inDemo }) => {
  const [messages, setMessages] = React.useState(inDemo ? MOCK.chat : []);
  const [input, setInput] = React.useState("");
  const [sending, setSending] = React.useState(false);
  const [error, setError] = React.useState(null);
  const [agent, setAgent] = React.useState("coach");
  const scrollRef = React.useRef(null);

  React.useEffect(() => {
    if (scrollRef.current) scrollRef.current.scrollTop = scrollRef.current.scrollHeight;
  }, [messages, sending]);

  const send = async (text) => {
    if (!text.trim() || sending) return;
    const userMsg = { role: "user", text, time: nowHM() };
    setMessages((prev) => [...prev, userMsg]);
    setInput("");
    setSending(true);
    setError(null);
    try {
      const res = await API.askAgent({ question: text, agent });
      const assistantMsg = {
        role: "assistant",
        agent: res.agent || agent,
        time: nowHM(),
        text: res.answer || res.text || res.message || "(sin respuesta)",
        sources: res.sources || res.context || [],
        cost: res.cost || (res.tokens && res.usd ? { tokens: res.tokens, usd: res.usd } : null),
      };
      setMessages((prev) => [...prev, assistantMsg]);
    } catch (e) {
      setError(e.message);
      setMessages((prev) => [...prev, {
        role: "assistant",
        agent,
        time: nowHM(),
        text: `No pude responder: ${e.message}. Verificá tu LLM en Configuración.`,
        error: true,
      }]);
    } finally {
      setSending(false);
    }
  };

  return (
    <div style={{ display: "grid", gridTemplateColumns: "1fr 280px", gap: 14, height: "calc(100vh - 320px)", minHeight: 480 }} className="af-agents-grid">
      <Card padded={false} style={{ display: "flex", flexDirection: "column", overflow: "hidden" }}>
        <div style={{ padding: "12px 18px", borderBottom: "1px solid var(--line-soft)", display: "flex", alignItems: "center", gap: 10 }}>
          <Icon name="bot" size={14} color={PANEL_AGENTS[agent].color} />
          <div style={{ fontSize: 13, fontWeight: 500 }}>Chat con {PANEL_AGENTS[agent].label}</div>
          <div style={{ flex: 1 }} />
          <select value={agent} onChange={(e) => setAgent(e.target.value)} style={{
            padding: "4px 8px", fontSize: 11, fontFamily: "var(--font-mono)",
            background: "var(--bg-2)", border: "1px solid var(--line)", borderRadius: 4, color: "var(--text)",
          }}>
            {Object.entries(PANEL_AGENTS).map(([k, a]) => <option key={k} value={k}>{a.label}</option>)}
          </select>
        </div>

        <div ref={scrollRef} style={{ flex: 1, overflowY: "auto", padding: 18, display: "flex", flexDirection: "column", gap: 14 }}>
          {messages.map((m, i) => <ChatBubble key={i} msg={m} showDisclaimer={showDisclaimer} />)}
          {sending && (
            <div style={{ alignSelf: "flex-start", maxWidth: "85%" }}>
              <div style={{ fontSize: 10, color: "var(--text-3)", marginBottom: 4, fontFamily: "var(--font-mono)" }}>
                {PANEL_AGENTS[agent].label} · pensando…
              </div>
              <div style={{ padding: "12px 14px", background: "var(--bg-2)", border: "1px solid var(--line)", borderRadius: "var(--r-2)", display: "inline-flex", gap: 4 }}>
                <span style={{ animation: "pulse-soft 1.4s ease-in-out infinite" }}>●</span>
                <span style={{ animation: "pulse-soft 1.4s ease-in-out 0.2s infinite" }}>●</span>
                <span style={{ animation: "pulse-soft 1.4s ease-in-out 0.4s infinite" }}>●</span>
              </div>
            </div>
          )}
        </div>

        <div style={{ padding: 14, borderTop: "1px solid var(--line-soft)" }}>
          <div style={{ display: "flex", gap: 6, marginBottom: 10, flexWrap: "wrap" }}>
            {SUGGESTIONS.map((s) => (
              <button key={s} onClick={() => send(s)} disabled={sending} style={{
                padding: "4px 9px", fontSize: 11, fontFamily: "var(--font-mono)",
                background: "var(--bg-2)", border: "1px solid var(--line)",
                borderRadius: "var(--r-2)", color: "var(--text-2)", cursor: sending ? "not-allowed" : "pointer",
              }}>{s}</button>
            ))}
          </div>
          <div style={{ display: "flex", gap: 8 }}>
            <input value={input} onChange={(e) => setInput(e.target.value)}
              onKeyDown={(e) => { if (e.key === "Enter" && !e.shiftKey) { e.preventDefault(); send(input); } }}
              placeholder="Pregúntale al coach…"
              disabled={sending}
              style={{
                flex: 1, padding: "10px 14px",
                background: "var(--bg-2)", border: "1px solid var(--line)",
                borderRadius: "var(--r-2)", fontSize: 13, color: "var(--text)", outline: "none",
              }} />
            <Btn variant="primary" icon={<Icon name="arrow" size={12} />} onClick={() => send(input)} disabled={sending}>
              Enviar
            </Btn>
          </div>
        </div>
      </Card>

      <div className="af-agents-side" style={{ display: "flex", flexDirection: "column", gap: 14 }}>
        <ChatContextCard inDemo={inDemo} />
        <Card>
          <div className="eyebrow" style={{ marginBottom: 10 }}>COSTE DE LA SESIÓN</div>
          <div style={{ display: "flex", justifyContent: "space-between", fontSize: 12, marginBottom: 6 }}>
            <span style={{ color: "var(--text-3)" }}>Tokens</span>
            <span className="num">{messages.filter(m => m.cost).reduce((s, m) => s + (m.cost?.tokens || 0), 0).toLocaleString()}</span>
          </div>
          <div style={{ display: "flex", justifyContent: "space-between", fontSize: 12 }}>
            <span style={{ color: "var(--text-3)" }}>USD</span>
            <span className="num">${messages.filter(m => m.cost).reduce((s, m) => s + (m.cost?.usd || 0), 0).toFixed(4)}</span>
          </div>
        </Card>
        {error && (
          <Card style={{ background: "var(--bad-soft)", border: "1px solid color-mix(in srgb, var(--bad) 30%, transparent)" }}>
            <div className="eyebrow" style={{ color: "var(--bad)", marginBottom: 6 }}>ERROR</div>
            <div style={{ fontSize: 11.5, color: "var(--text-2)" }}>{error}</div>
          </Card>
        )}
      </div>
    </div>
  );
};

/* ChatContextCard — pequeño panel "qué datos ve el agente" basado en lo que
 * realmente está sincronizado. Antes era hardcoded ("87 actividades · plan
 * activo · semana 1/4") sin importar el estado real. Ahora consulta:
 *   - /data/activities → count y rango real
 *   - /data/daily → count de filas con sleep/HRV
 *   - /plans/ + /plans/{id} → goal + nº de semanas si hay un plan activo
 * Cada línea se omite si no hay datos para evitar mostrar "0" como dato.
 */
const ChatContextCard = ({ inDemo }) => {
  const [ctx, setCtx] = React.useState(null);

  React.useEffect(() => {
    if (inDemo) {
      setCtx({
        activitiesCount: 12,
        dailyCount: 30,
        planGoal: "Demo: 5K sub 25",
        planWeeks: 8,
      });
      return;
    }
    let alive = true;
    (async () => {
      const out = {};
      // Estos fetches son tolerantes — si uno falla, simplemente no mostramos
      // esa línea. Importante: nunca mostrar un número inventado al usuario.
      try {
        const list = await API.activitiesList({ limit: 200 });
        const items = Array.isArray(list) ? list : (list?.rows || list?.items || []);
        out.activitiesCount = items.length;
      } catch (_) {}
      try {
        const today = new Date();
        const from = new Date(today); from.setDate(from.getDate() - 90);
        const fmt = (d) => d.toISOString().slice(0, 10);
        const daily = await API.dailyData({ from: fmt(from), to: fmt(today) });
        const rows = Array.isArray(daily?.rows) ? daily.rows : [];
        out.dailyCount = rows.length;
      } catch (_) {}
      try {
        const plansRes = await API.listPlans();
        const plans = Array.isArray(plansRes) ? plansRes : (plansRes?.plans || []);
        const training = plans.find((p) => (p.type || p.plan_type) === "training");
        if (training) {
          try {
            const detail = await API.getPlan(training.id);
            out.planGoal = detail?.content?.goal || training.goal || "plan activo";
            out.planWeeks = detail?.content?.weeks || training.weeks || null;
          } catch (_) {
            out.planGoal = training.goal || "plan activo";
            out.planWeeks = training.weeks || null;
          }
        }
      } catch (_) {}
      if (alive) setCtx(out);
    })();
    return () => { alive = false; };
  }, [inDemo]);

  return (
    <Card>
      <div className="eyebrow" style={{ marginBottom: 10 }}>CONTEXTO ACTUAL</div>
      <div style={{ fontSize: 12, color: "var(--text-2)", lineHeight: 1.6 }}>
        Datos disponibles para el agente:<br />
        <span style={{ fontFamily: "var(--font-mono)", color: "var(--text-3)", fontSize: 11 }}>
          {ctx === null ? "cargando…" : (
            <>
              {ctx.dailyCount > 0 && <>· daily {ctx.dailyCount} días<br /></>}
              {ctx.activitiesCount > 0 && <>· actividades {ctx.activitiesCount}<br /></>}
              {ctx.planGoal && <>· plan: {ctx.planGoal}{ctx.planWeeks ? ` · ${ctx.planWeeks}sem` : ""}<br /></>}
              {!ctx.dailyCount && !ctx.activitiesCount && !ctx.planGoal && (
                <>· sin datos sincronizados aún</>
              )}
            </>
          )}
        </span>
      </div>
    </Card>
  );
};

const ChatBubble = ({ msg, showDisclaimer }) => {
  const isUser = msg.role === "user";
  const isMedical = msg.agent === "medical";
  return (
    <div style={{ alignSelf: isUser ? "flex-end" : "flex-start", maxWidth: "85%" }}>
      <div style={{ fontSize: 10, color: "var(--text-3)", marginBottom: 4, fontFamily: "var(--font-mono)", display: "flex", gap: 6 }}>
        {isUser ? "Tú" : (msg.agent ? PANEL_AGENTS[msg.agent]?.label || msg.agent : "Coach")}
        {msg.time && <span>· {msg.time}</span>}
      </div>
      {isMedical && showDisclaimer && (
        <div style={{
          padding: "8px 12px", marginBottom: 6,
          background: "var(--warn-soft)",
          border: "1px solid color-mix(in srgb, var(--warn) 30%, transparent)",
          borderRadius: "var(--r-2)", fontSize: 11, color: "var(--text-2)",
          display: "flex", gap: 8, alignItems: "flex-start",
        }}>
          <Icon name="alert" size={12} color="var(--warn)" />
          <span><strong style={{ color: "var(--warn)" }}>No es consejo médico.</strong> Tendencias de tus datos. Consultá un profesional ante síntomas.</span>
        </div>
      )}
      <div style={{
        padding: "12px 14px", borderRadius: "var(--r-2)",
        background: isUser ? "var(--accent)" : "var(--bg-2)",
        color: isUser ? "var(--accent-fg)" : "var(--text)",
        fontSize: 13, lineHeight: 1.55,
        border: isUser ? "none" : (msg.error ? "1px solid var(--bad)" : "1px solid var(--line)"),
      }}>
        {/* Mensajes del usuario van como texto plano (no le rompemos su input).
         * Los del agente vienen en Markdown — ###, **bold**, listas — y los
         * pasamos por MarkdownRender para que se vean como prosa, no como
         * el dump de markdown que reportó el usuario. */}
        {isUser
          ? msg.text
          : <MarkdownRender text={msg.text || ""} />}
        {msg.bullets && (
          <ul style={{ margin: "8px 0 0 0", paddingLeft: 18 }}>
            {msg.bullets.map((b, i) => <li key={i} style={{ marginBottom: 4 }}>{b}</li>)}
          </ul>
        )}
        {msg.followup && (
          <div style={{ marginTop: 8, color: isUser ? "var(--accent-fg)" : "var(--text-2)", fontSize: 12.5 }}>{msg.followup}</div>
        )}
        {msg.insights && (
          <div style={{ marginTop: 10, display: "flex", flexDirection: "column", gap: 6 }}>
            {msg.insights.map((ins, i) => (
              <InsightTile key={i} kind={ins.kind} title={ins.title} body={ins.body} />
            ))}
          </div>
        )}
      </div>
      {!isUser && (msg.sources?.length || msg.cost) && (
        <div style={{ marginTop: 5, display: "flex", gap: 10, alignItems: "center", flexWrap: "wrap" }}>
          {msg.sources?.length > 0 && <Source sources={msg.sources} />}
          {msg.cost && (
            <span style={{ fontSize: 10, color: "var(--text-3)", fontFamily: "var(--font-mono)" }}>
              {msg.cost.tokens?.toLocaleString()} tok · ${(msg.cost.usd ?? 0).toFixed(4)}
            </span>
          )}
        </div>
      )}
    </div>
  );
};

const ChatDrawer = ({ onClose }) => {
  const [text, setText] = React.useState("");
  return (
    <div style={{
      position: "fixed", inset: 0, background: "rgba(0,0,0,0.4)",
      zIndex: 100, display: "flex", justifyContent: "flex-end",
    }} onClick={onClose}>
      <div onClick={(e) => e.stopPropagation()} style={{
        width: 420, maxWidth: "100%", height: "100%",
        background: "var(--bg)", borderLeft: "1px solid var(--line)",
        display: "flex", flexDirection: "column",
      }}>
        <div style={{
          padding: "16px 18px", borderBottom: "1px solid var(--line-soft)",
          display: "flex", alignItems: "center", gap: 10,
        }}>
          <Icon name="chat" size={14} color="var(--accent)" />
          <div style={{ fontSize: 13, fontWeight: 500 }}>Pedir ajustes</div>
          <div style={{ flex: 1 }} />
          <Btn size="sm" variant="ghost" onClick={onClose} icon={<Icon name="x" size={12} />}>Cerrar</Btn>
        </div>

        <div style={{ padding: 18, fontSize: 12, color: "var(--text-2)", borderBottom: "1px solid var(--line-soft)", lineHeight: 1.5 }}>
          Pedile al equipo de coach que <strong>ajuste matices</strong> de la propuesta de hoy.
        </div>

        <div style={{ flex: 1, overflowY: "auto", padding: 18, display: "flex", flexDirection: "column", gap: 12 }}>
          {[
            { who: "Tú",    text: "¿Y si en vez de 16 km hago 12 km + 6×400m suaves?" },
            { who: "Coach", text: "Funciona — equivale a ~92 unidades de carga. Manteniendo Z2 en los 400. Aprobado por el módulo médico." },
          ].map((m, i) => (
            <div key={i} style={{ alignSelf: m.who === "Tú" ? "flex-end" : "flex-start", maxWidth: "85%" }}>
              <div style={{ fontSize: 10, color: "var(--text-3)", marginBottom: 3, fontFamily: "var(--font-mono)" }}>{m.who}</div>
              <div style={{
                padding: "10px 13px", borderRadius: "var(--r-2)",
                background: m.who === "Tú" ? "var(--accent)" : "var(--bg-2)",
                color: m.who === "Tú" ? "var(--accent-fg)" : "var(--text)",
                fontSize: 13, lineHeight: 1.45,
                border: m.who === "Tú" ? "none" : "1px solid var(--line)",
              }}>{m.text}</div>
            </div>
          ))}
        </div>

        <div style={{ padding: 14, borderTop: "1px solid var(--line-soft)", display: "flex", gap: 8 }}>
          <input value={text} onChange={(e) => setText(e.target.value)}
            placeholder="Escribí un ajuste…"
            style={{
              flex: 1, padding: "9px 12px",
              background: "var(--bg-2)", border: "1px solid var(--line)",
              borderRadius: "var(--r-2)", fontSize: 12.5, color: "var(--text)", outline: "none",
            }} />
          <Btn variant="primary" size="sm" icon={<Icon name="arrow" size={12} />}>Enviar</Btn>
        </div>
      </div>
    </div>
  );
};

function nowHM() {
  const d = new Date();
  return `${String(d.getHours()).padStart(2, "0")}:${String(d.getMinutes()).padStart(2, "0")}`;
}

window.Agents = Agents;
