/* Plans — calendario semanal real (parseado desde el markdown del LLM) +
 * nutrición (todavía MOCK) + form de generación.
 *
 * Antes el calendario era 100% MOCK (`MOCK.planWeek`). Ahora:
 *   1. `screen-plans.jsx` lista los planes guardados (`/plans/`).
 *   2. Si hay alguno tipo "training", carga el detalle (`/plans/{id}`),
 *      pasa `plan.content.answer` (markdown) por `parseTrainingPlan`
 *      (definido en `utils-plan-parser.jsx`) y dibuja el calendario.
 *   3. Si el parser devuelve `null` (LLM no respetó el formato esperado),
 *      mostramos el markdown crudo con `MarkdownRender` y un eyebrow
 *      explicativo. La opción "pedir reformat al coach" queda como TODO.
 *   4. El generador (`NewPlanForm`) post-procesa la respuesta inmediata y
 *      muestra preview con el calendario antes de guardar; si falla parser,
 *      muestra el markdown + botón "Reintentar" con instrucciones reforzadas.
 *
 * MOCK que sigue:
 *   - Pantalla "Nutrición" entera (`NutritionPlan`): no estamos parseando
 *     `/agents/plan/nutrition` aún (formato del LLM nutricionista no
 *     consistente; ver README §"MOCK que siguen pendientes").
 *   - Stats agregadas (`carga sem.`, `volumen`, `25%`) del header del plan
 *     activo: el parser todavía no calcula esto (lo añadimos cuando el LLM
 *     devuelva `load` por sesión de forma confiable). Ahora derivamos
 *     `weeks count` y `sessions/sem promedio` de la estructura parseada.
 */

const Plans = ({ density, dataState, setRoute, inDemo }) => {
  const [view, setView] = React.useState("training");
  const [selectedSession, setSelectedSession] = React.useState(null);
  // null = "no cargado", [] = "cargado vacío", [{...}] = lista.
  const [plans, setPlans] = React.useState(null);
  const [activePlan, setActivePlan] = React.useState(null); // detalle (con content)
  const [loadError, setLoadError] = React.useState(null);

  // Cargar lista de planes y detalle del primero "training".
  React.useEffect(() => {
    if (inDemo) return;
    let alive = true;
    (async () => {
      try {
        const res = await API.listPlans();
        const items = Array.isArray(res) ? res : (res?.plans || res?.items || []);
        if (!alive) return;
        setPlans(items);
        const trainingPlan = items.find((p) => (p.type || p.plan_type) === "training");
        if (trainingPlan) {
          try {
            const detail = await API.getPlan(trainingPlan.id);
            if (alive) setActivePlan(detail);
          } catch (e) {
            if (alive && e.status !== 401) setLoadError(e.message);
          }
        }
      } catch (e) {
        if (e.status === 401 || e.status === 403 || e.status === 422) return;
        if (alive) setLoadError(e.message);
      }
    })();
    return () => { alive = false; };
  }, [inDemo]);

  return (
    <div className="af-padded" style={{ padding: "var(--sp-6) var(--sp-8) 80px", display: "flex", flexDirection: "column", gap: 18 }}>
      <div style={{ display: "flex", gap: 10, alignItems: "center", flexWrap: "wrap" }}>
        <Tabs value={view} onChange={setView} tabs={[
          { value: "training", label: "Entrenamiento" },
          { value: "nutrition", label: "Nutrición" },
        ]} />
        <div style={{ flex: 1 }} />
        <Btn variant="default" icon={<Icon name="plus" size={13} />} onClick={() => setView("new")}>Generar nuevo plan</Btn>
      </div>

      {view === "training" && (
        <TrainingPlan
          setSelectedSession={setSelectedSession}
          selectedSession={selectedSession}
          activePlan={activePlan}
          plans={plans}
          inDemo={inDemo}
          loadError={loadError}
          onSelectPlan={async (id) => {
            try {
              const detail = await API.getPlan(id);
              setActivePlan(detail);
              setSelectedSession(null);
            } catch (e) {
              setLoadError(e.message);
            }
          }}
        />
      )}
      {view === "nutrition" && <NutritionPlan />}
      {view === "new" && (
        <NewPlanForm
          setView={setView}
          inDemo={inDemo}
          onSaved={async (detail) => {
            // El backend ya persistió el plan en /agents/plan/training. Aprovechamos
            // el id del detail para refetchearlo (canonical) y refrescar la lista
            // de planes para que el usuario lo vea en futuras navegaciones.
            setActivePlan(detail);
            setView("training");
            if (!inDemo && detail?.id) {
              try {
                const list = await API.listPlans();
                const items = Array.isArray(list) ? list : (list?.plans || []);
                setPlans(items);
                const fresh = await API.getPlan(detail.id);
                if (fresh) setActivePlan(fresh);
              } catch (e) {
                if (e.status !== 401 && e.status !== 403) {
                  setLoadError(e.message || "No se pudo refrescar el plan recién guardado.");
                }
              }
            }
          }}
        />
      )}
    </div>
  );
};

/* ───────── TrainingPlan ───────── */

const TrainingPlan = ({ setSelectedSession, selectedSession, activePlan, plans, inDemo, loadError, onSelectPlan }) => {
  // Demo: usamos el MOCK directo (markdown reconstruido para que pase por parser).
  const planMarkdown = React.useMemo(() => {
    if (inDemo) return demoPlanMarkdown();
    if (!activePlan) return null;
    const content = activePlan.content;
    if (!content) return null;
    if (typeof content === "string") return content;
    if (typeof content === "object") {
      return content.answer || content.text || content.markdown || null;
    }
    return null;
  }, [activePlan, inDemo]);

  const parsed = React.useMemo(() => {
    if (!planMarkdown) return null;
    try { return parseTrainingPlan(planMarkdown); } catch (e) { return null; }
  }, [planMarkdown]);

  // Empty / loading states.
  if (!inDemo && !activePlan && plans !== null && plans.length === 0) {
    return (
      <Empty
        icon="plan"
        title="Aún no tenés planes guardados"
        body="Generá tu primer plan con el coach. Tarda ~30-60s y se guarda automáticamente."
        action={<Btn variant="primary" icon={<Icon name="plus" size={13} />} onClick={() => window.dispatchEvent(new CustomEvent("af-plans-new"))}>Generar mi primer plan</Btn>}
      />
    );
  }
  if (!inDemo && !activePlan && plans === null) {
    return <Card><div className="eyebrow">CARGANDO PLAN…</div></Card>;
  }
  if (loadError && !activePlan && !inDemo) {
    return (
      <Card style={{ background: "var(--bad-soft)", border: "1px solid color-mix(in srgb, var(--bad) 30%, transparent)" }}>
        <div style={{ display: "flex", gap: 10, alignItems: "flex-start" }}>
          <Icon name="alert" size={14} color="var(--bad)" />
          <div style={{ fontSize: 12.5 }}><strong style={{ color: "var(--bad)" }}>No se pudo cargar el plan.</strong> {loadError}</div>
        </div>
      </Card>
    );
  }

  // Fallback si no hay markdown parseable.
  if (!parsed) {
    return (
      <PlanFallback
        plans={plans}
        activePlan={activePlan}
        onSelectPlan={onSelectPlan}
        rawMarkdown={planMarkdown}
      />
    );
  }

  return (
    <PlanCalendar
      parsed={parsed}
      plans={plans}
      activePlan={activePlan}
      onSelectPlan={onSelectPlan}
      selectedSession={selectedSession}
      setSelectedSession={setSelectedSession}
    />
  );
};

/* ───────── PlanCalendar (estructura parseada) ───────── */

const PlanCalendar = ({ parsed, plans, activePlan, onSelectPlan, selectedSession, setSelectedSession }) => {
  const [activeWeekIdx, setActiveWeekIdx] = React.useState(0);
  React.useEffect(() => {
    setActiveWeekIdx(0);
  }, [activePlan?.id]);

  const totalSessions = parsed.weeks.reduce((acc, w) => acc + w.days.reduce((a, d) => a + d.sessions.filter((s) => s.tone !== "rest").length, 0), 0);
  const sessionsPerWeek = parsed.weeks.length ? Math.round(totalSessions / parsed.weeks.length) : 0;
  const goal = activePlan?.content?.goal || activePlan?.content?.constraints?.goal || parsed.title;

  const week = parsed.weeks[activeWeekIdx] || parsed.weeks[0];

  return (
    <>
      {/* Header */}
      <Card padded={false}>
        <div style={{ padding: "20px 24px", display: "flex", alignItems: "flex-start", gap: 24 }}>
          <div style={{ flex: 1 }}>
            <div className="eyebrow" style={{ marginBottom: 6 }}>
              PLAN ACTIVO · GENERADO POR {(activePlan?.agents_used || ["coach"]).join(" + ").toUpperCase()}
              {activePlan?.created_at && <> · {formatPlanDate(activePlan.created_at)}</>}
            </div>
            <h2 className="display" style={{ margin: 0, fontSize: 26, fontWeight: 400, letterSpacing: "-0.02em" }}>
              {parsed.title}
            </h2>
            <div style={{ marginTop: 6, fontSize: 13, color: "var(--text-2)" }}>
              {parsed.weeks.length} semanas · {sessionsPerWeek} sesiones/sem
              {goal && goal !== parsed.title && <> · objetivo: {goal}</>}
            </div>
          </div>

          {plans && plans.length > 1 && (
            <select
              value={activePlan?.id || ""}
              onChange={(e) => onSelectPlan(e.target.value)}
              style={{
                padding: "6px 10px", background: "var(--bg-2)", border: "1px solid var(--line)",
                borderRadius: "var(--r-2)", fontSize: 12, color: "var(--text)",
              }}
            >
              {plans.filter((p) => (p.type || p.plan_type) === "training").map((p) => (
                <option key={p.id} value={p.id}>{formatPlanDate(p.created_at)} · {p.id.slice(0, 8)}</option>
              ))}
            </select>
          )}
        </div>
        {parsed.summary && (
          <div style={{ padding: "0 24px 16px", fontSize: 12.5, color: "var(--text-2)", lineHeight: 1.5 }}>
            <strong style={{ color: "var(--text)" }}>Por qué este plan:</strong> {truncate(parsed.summary, 320)}
          </div>
        )}
      </Card>

      {/* Tabs de semana */}
      <Card>
        <div style={{ display: "flex", alignItems: "center", marginBottom: 14, gap: 14, flexWrap: "wrap" }}>
          <div className="eyebrow">CALENDARIO · {parsed.weeks.length} SEMANAS</div>
          <div style={{ display: "flex", gap: 4, flexWrap: "wrap" }}>
            {parsed.weeks.map((w, i) => (
              <button
                key={i}
                onClick={() => setActiveWeekIdx(i)}
                style={{
                  padding: "5px 12px",
                  fontSize: 11.5,
                  fontFamily: "var(--font-mono)",
                  border: "1px solid var(--line)",
                  borderRadius: "var(--r-2)",
                  background: i === activeWeekIdx ? "var(--bg-3)" : "transparent",
                  color: i === activeWeekIdx ? "var(--text)" : "var(--text-3)",
                  cursor: "pointer",
                  display: "inline-flex",
                  alignItems: "center",
                  gap: 6,
                }}
              >
                S{w.n}
                {i === 0 && <Dot tone="accent" size={5} />}
              </button>
            ))}
          </div>
          <div style={{ flex: 1 }} />
          <div style={{ display: "flex", gap: 12, fontSize: 11 }}>
            <span><Dot tone="info" /> Suave</span>
            <span><Dot tone="bad" /> Intenso</span>
            <span><Dot tone="good" /> Largo</span>
            <span><Dot tone="accent" /> Fuerza</span>
          </div>
        </div>

        {/* Grid 7 columnas para la semana activa */}
        <div className="af-plan-cal af-plan-cal-wrap" style={{ display: "grid", gridTemplateColumns: "repeat(7, 1fr)", gap: 6 }}>
          {["Lun","Mar","Mié","Jue","Vie","Sáb","Dom"].map((d) => (
            <div key={d} style={{ fontSize: 9.5, fontFamily: "var(--font-mono)", color: "var(--text-3)", letterSpacing: 0.1, textTransform: "uppercase", textAlign: "center", paddingBottom: 6 }}>
              {d}
            </div>
          ))}
          {week.days.map((d, di) => (
            <PlanDayCell
              key={di}
              day={d}
              onClick={() => {
                if (d.sessions.length) {
                  setSelectedSession({ ...d.sessions[0], dayLabel: d.label, week: week.n });
                } else {
                  setSelectedSession({ name: "Descanso", tone: "rest", dayLabel: d.label, week: week.n });
                }
              }}
              active={selectedSession && selectedSession.dayLabel === d.label && selectedSession.week === week.n}
            />
          ))}
        </div>
      </Card>

      {/* Detalle de sesión */}
      {selectedSession && <SessionDetail session={selectedSession} onClose={() => setSelectedSession(null)} />}

      {/* Por qué este plan */}
      {parsed.summary && parsed.summary.length > 100 && (
        <Card style={{ background: "var(--bg-2)" }}>
          <div className="eyebrow" style={{ marginBottom: 10 }}>POR QUÉ ASÍ · NARRATIVA DEL COACH</div>
          <div style={{ fontSize: 13.5, color: "var(--text-2)", lineHeight: 1.65 }}>
            {parsed.summary}
          </div>
        </Card>
      )}
    </>
  );
};

/* ───────── PlanFallback (markdown crudo) ───────── */

const PlanFallback = ({ plans, activePlan, onSelectPlan, rawMarkdown }) => (
  <>
    <Card>
      <div style={{ display: "flex", alignItems: "flex-start", gap: 14 }}>
        <Icon name="alert" size={16} color="var(--warn)" />
        <div style={{ flex: 1 }}>
          <div className="eyebrow" style={{ marginBottom: 4, color: "var(--warn)" }}>PLAN COMPLETO (SIN PARSEAR)</div>
          <div style={{ fontSize: 13, color: "var(--text-2)", lineHeight: 1.5 }}>
            El coach devolvió el plan pero no en el formato esperado por el calendario.
            Mostramos el markdown crudo. Podés volver a generarlo con instrucciones más estrictas.
          </div>
          <div style={{ marginTop: 12, display: "flex", gap: 8 }}>
            <Btn size="sm" variant="default" onClick={() => window.dispatchEvent(new CustomEvent("af-plans-new"))}>Regenerar plan</Btn>
            {/* TODO: botón "Pedir al coach que reformatee" → POST /agents/ask con
                instrucción de reformatear `rawMarkdown` a la estructura esperada. */}
          </div>
        </div>
        {plans && plans.length > 1 && activePlan && (
          <select
            value={activePlan.id}
            onChange={(e) => onSelectPlan(e.target.value)}
            style={{
              padding: "6px 10px", background: "var(--bg-2)", border: "1px solid var(--line)",
              borderRadius: "var(--r-2)", fontSize: 12, color: "var(--text)",
            }}
          >
            {plans.filter((p) => (p.type || p.plan_type) === "training").map((p) => (
              <option key={p.id} value={p.id}>{formatPlanDate(p.created_at)}</option>
            ))}
          </select>
        )}
      </div>
    </Card>
    <Card>
      <MarkdownRender text={rawMarkdown || ""} />
    </Card>
  </>
);

/* ───────── PlanDayCell ───────── */

const PlanDayCell = ({ day, onClick, active }) => {
  // Tomamos la sesión "principal" de cada día — la primera no-rest si la hay.
  const session = day.sessions.find((s) => s.tone !== "rest") || day.sessions[0];
  const tone = session?.tone || "rest";
  const colors = {
    rest: { line: "var(--text-4)", bg: "var(--bg-2)" },
    easy: { line: "var(--info)", bg: "color-mix(in srgb, var(--info) 8%, var(--bg-1))" },
    long: { line: "var(--good)", bg: "color-mix(in srgb, var(--good) 10%, var(--bg-1))" },
    hard: { line: "var(--bad)", bg: "color-mix(in srgb, var(--bad) 10%, var(--bg-1))" },
    strength: { line: "var(--accent)", bg: "color-mix(in srgb, var(--accent) 10%, var(--bg-1))" },
  };
  const c = colors[tone] || colors.rest;
  const subtitle = session
    ? [session.intensity, session.duration_min ? `${formatDuration(session.duration_min)}` : null].filter(Boolean).join(" · ")
    : "Descanso";

  return (
    <button onClick={onClick} style={{
      padding: "10px 10px 12px",
      borderRadius: "var(--r-2)",
      background: c.bg,
      border: active ? `1.5px solid ${c.line}` : "1px solid var(--line)",
      textAlign: "left",
      minHeight: 100,
      display: "flex", flexDirection: "column",
      cursor: "pointer",
      position: "relative",
    }}>
      <div style={{ height: 3, width: 24, background: c.line, borderRadius: 2, marginBottom: 8 }} />
      <div style={{ fontSize: 11.5, fontWeight: 500, lineHeight: 1.25, marginBottom: 3, color: "var(--text)" }}>
        {session ? session.name : "Descanso"}
      </div>
      <div style={{ fontSize: 10, color: "var(--text-3)", lineHeight: 1.3 }}>
        {subtitle}
      </div>
      {session?.load && (
        <div style={{ marginTop: "auto", paddingTop: 6, fontSize: 9, color: "var(--text-3)", fontFamily: "var(--font-mono)" }}>
          carga {session.load}
        </div>
      )}
    </button>
  );
};

/* ───────── SessionDetail ───────── */

const SessionDetail = ({ session, onClose }) => (
  <Card>
    <div style={{ display: "flex", alignItems: "flex-start", gap: 18 }}>
      <div style={{ flex: 1 }}>
        <div style={{ display: "flex", alignItems: "center", gap: 8, marginBottom: 4 }}>
          <div className="eyebrow">SEMANA {session.week} · {(session.dayLabel || "").toUpperCase()}</div>
          <div style={{ flex: 1 }} />
          <Btn size="sm" variant="ghost" onClick={onClose}>Cerrar</Btn>
        </div>
        <h3 className="display" style={{ margin: 0, fontSize: 22, fontWeight: 400 }}>{session.name}</h3>
        {session.notes && (
          <div style={{ marginTop: 6, color: "var(--text-2)", fontSize: 13, lineHeight: 1.5 }}>{session.notes}</div>
        )}
      </div>

      <div style={{ width: 280, paddingLeft: 18, borderLeft: "1px solid var(--line-soft)" }}>
        <div className="eyebrow" style={{ marginBottom: 8 }}>PARÁMETROS</div>
        <div style={{ fontSize: 12.5, color: "var(--text-2)", lineHeight: 1.7, fontFamily: "var(--font-mono)" }}>
          {session.intensity && <div>Intensidad <span style={{ color: "var(--text)" }}>{session.intensity}</span></div>}
          {session.duration_min && <div>Duración <span style={{ color: "var(--text)" }}>{formatDuration(session.duration_min)}</span></div>}
          {session.load && <div>Carga <span style={{ color: "var(--text)" }}>{session.load}</span></div>}
          {!session.intensity && !session.duration_min && !session.load && (
            <div style={{ color: "var(--text-3)" }}>Sin parámetros adicionales</div>
          )}
        </div>
        {Array.isArray(session.variants) && session.variants.length > 0 && (
          <div style={{ marginTop: 14, paddingTop: 14, borderTop: "1px solid var(--line-soft)" }}>
            <div className="eyebrow" style={{ marginBottom: 8 }}>OPCIONES</div>
            {session.variants
              .slice()
              .sort((a, b) => (a.kind === "primary" ? -1 : b.kind === "primary" ? 1 : 0))
              .map((v, i) => (
                <div key={i} style={{
                  marginBottom: 10, padding: "8px 10px",
                  background: v.kind === "primary" ? "color-mix(in srgb, var(--accent) 8%, var(--bg-1))" : "var(--bg-2)",
                  border: "1px solid var(--line-soft)",
                  borderRadius: "var(--r-2)",
                }}>
                  <div style={{ display: "flex", alignItems: "center", gap: 6, marginBottom: 4 }}>
                    <Badge tone={v.kind === "primary" ? "accent" : "info"}>
                      {v.kind === "primary" ? "Principal" : "Alternativa"}
                    </Badge>
                    {v.sport && (
                      <span style={{ fontSize: 10, color: "var(--text-3)", fontFamily: "var(--font-mono)" }}>{v.sport}</span>
                    )}
                  </div>
                  <div style={{ fontSize: 12, fontWeight: 500, marginBottom: 2 }}>{v.name}</div>
                  <div style={{ fontSize: 11, color: "var(--text-3)", fontFamily: "var(--font-mono)" }}>
                    {[v.intensity, v.duration_min ? formatDuration(v.duration_min) : null, v.load ? `carga ${v.load}` : null].filter(Boolean).join(" · ") || "—"}
                  </div>
                  {v.notes && <div style={{ fontSize: 11, color: "var(--text-2)", marginTop: 4, lineHeight: 1.4 }}>{v.notes}</div>}
                </div>
              ))}
          </div>
        )}
      </div>
    </div>
  </Card>
);

/* ───────── NutritionPlan (sigue MOCK) ───────── */

/* NutritionPlan — stub honesto mientras el parser nutricional no esté listo.
 * Antes mostraba macros y comidas inventadas con un mini-aviso "datos de muestra".
 * Eso confundía a usuarios reales (parecía un plan basado en sus datos).
 * Ahora: card sobria con CTA hacia Coach para usar el nutricionista vía chat. */
const NutritionPlan = () => (
  <Card>
    <div style={{ padding: "10px 6px", textAlign: "center", maxWidth: 560, margin: "0 auto" }}>
      <div className="eyebrow" style={{ marginBottom: 10 }}>NUTRICIÓN · PRÓXIMAMENTE</div>
      <h2 className="display" style={{ margin: 0, fontSize: 24, fontWeight: 400, letterSpacing: "-0.02em", marginBottom: 12 }}>
        El planificador nutricional está en desarrollo.
      </h2>
      <div style={{ fontSize: 13, color: "var(--text-2)", lineHeight: 1.6, marginBottom: 22 }}>
        Cuando esté listo, vas a poder pedirle al <strong>nutricionista</strong> un plan basado en tus
        objetivos, tu carga de entrenamiento y tus datos de composición corporal (Eufy / Garmin).
        Mientras tanto podés conversar con él directamente desde el coach.
      </div>
      <Btn
        variant="primary"
        icon={<Icon name="chat" size={13} />}
        onClick={() => window.dispatchEvent(new CustomEvent("af-route", { detail: "agents" }))}
      >
        Hablar con el nutricionista
      </Btn>
    </div>
  </Card>
);

/* ───────── NewPlanForm ───────── */

// Distancias clásicas de running. Cada chip llena `goal` con una plantilla
// editable. Si el usuario quiere otro deporte, simplemente escribe libremente.
const RUNNING_PRESETS = [
  { id: "5k",   label: "5K",            goal: "5K — bajar mi PR",                       weeks: 6,  sessions: 4 },
  { id: "10k",  label: "10K",           goal: "10K — bajar mi PR",                      weeks: 8,  sessions: 4 },
  { id: "21k",  label: "Media maratón", goal: "Media maratón — terminar en buen tiempo", weeks: 12, sessions: 4 },
  { id: "42k",  label: "Maratón",       goal: "Maratón — terminar en buen tiempo",      weeks: 16, sessions: 5 },
];

const NewPlanForm = ({ setView, inDemo, onSaved }) => {
  const [stage, setStage] = React.useState("form"); // form | loading | preview | error
  const [errorMsg, setErrorMsg] = React.useState(null);
  const [generated, setGenerated] = React.useState(null); // {id, ...result}
  const [params, setParams] = React.useState({
    goal: "",
    weeks: 4,
    sessions: 4,
    long_day: "Saturday",
    restrictions: "",
  });

  // Preferencias de deportes — cargadas desde /config/user. Permite editar
  // dentro del form o dejar las que ya estaban guardadas. `null` mientras
  // carga; `{sports: [], ...}` cuando llegó la respuesta del backend.
  const [prefs, setPrefs] = React.useState(null);
  const [supportedSports, setSupportedSports] = React.useState([]);
  const [savePrefs, setSavePrefs] = React.useState(true);

  React.useEffect(() => {
    if (inDemo) {
      setPrefs({ sports: [{ id: "running", name: "Carrera", is_primary: true }], weekly_sessions: null });
      setSupportedSports([
        { id: "running", name: "Carrera" },
        { id: "cycling", name: "Bici" },
        { id: "swimming", name: "Natación" },
        { id: "strength", name: "Fuerza / gym" },
      ]);
      return;
    }
    let alive = true;
    (async () => {
      try {
        const res = await API.getUserPrefs();
        if (!alive) return;
        setSupportedSports(res?.supported_sports || []);
        setPrefs(res?.prefs || { sports: [], weekly_sessions: null, long_session_day: null });
      } catch (e) {
        if (alive && (e.status !== 401 && e.status !== 403)) {
          setPrefs({ sports: [], weekly_sessions: null, long_session_day: null });
        }
      }
    })();
    return () => { alive = false; };
  }, [inDemo]);

  const togglePrefSport = (sportId) => {
    setPrefs((p) => {
      if (!p) return p;
      const sports = [...(p.sports || [])];
      const idx = sports.findIndex((s) => s.id === sportId);
      const supported = supportedSports.find((s) => s.id === sportId);
      if (idx >= 0) {
        const wasPrimary = sports[idx].is_primary;
        sports.splice(idx, 1);
        // Si quitamos el primary y queda al menos uno, el primero se vuelve primary.
        if (wasPrimary && sports.length > 0) sports[0].is_primary = true;
      } else if (supported) {
        sports.push({ id: sportId, name: supported.name, is_primary: sports.length === 0 });
      }
      return { ...p, sports };
    });
  };

  const setPrimarySport = (sportId) => {
    setPrefs((p) => {
      if (!p) return p;
      const sports = (p.sports || []).map((s) => ({ ...s, is_primary: s.id === sportId }));
      return { ...p, sports };
    });
  };

  const applyPreset = (preset) => {
    setParams((p) => ({ ...p, goal: preset.goal, weeks: preset.weeks, sessions: preset.sessions }));
    // Asegurarse que running esté en los deportes (y como primary si no había nada).
    setPrefs((p) => {
      if (!p) return p;
      const sports = [...(p.sports || [])];
      const exists = sports.find((s) => s.id === "running");
      if (!exists) {
        sports.unshift({ id: "running", name: "Carrera", is_primary: sports.length === 0 });
      }
      return { ...p, sports };
    });
  };

  const generate = async () => {
    setStage("loading");
    setErrorMsg(null);
    if (inDemo) {
      // Demo: sintetizamos un resultado con el markdown demo.
      setTimeout(() => {
        setGenerated({ id: "demo-1", content: { answer: demoPlanMarkdown(), goal: params.goal } });
        setStage("preview");
      }, 1600);
      return;
    }
    try {
      // Persistir prefs antes de generar (si el usuario marcó la opción) —
      // así el LLM las lee en server-side desde /config/user. El form también
      // las pasa como constraints redundantes para registrar la intención.
      if (savePrefs && prefs && prefs.sports && prefs.sports.length > 0) {
        try {
          await API.setUserPrefs({
            sports: prefs.sports,
            weekly_sessions: Number(params.sessions),
            long_session_day: params.long_day,
          });
        } catch (_) {
          // No bloqueamos la generación si falla la persistencia de prefs.
        }
      }
      const result = await API.planTraining({
        goal: params.goal,
        weeks: Number(params.weeks),
        sessions_per_week: Number(params.sessions),
        long_day: params.long_day,
        restrictions: params.restrictions || undefined,
        sports: (prefs?.sports || []).map((s) => ({ id: s.id, name: s.name, is_primary: !!s.is_primary })),
      });
      setGenerated({
        id: result.id,
        type: "training",
        created_at: result.created_at,
        agents_used: [result.agent || "coach"],
        content: result,
      });
      setStage("preview");
    } catch (e) {
      if (e.status === 412 || /llm.*not.*configured/i.test(e.message || "")) {
        setErrorMsg("Configurá un LLM en Configuración → LLM antes de generar planes.");
      } else if (e.network) {
        setErrorMsg("El backend no responde. Probá de nuevo en un momento.");
      } else {
        setErrorMsg(e.message || "No se pudo generar el plan.");
      }
      setStage("error");
    }
  };

  if (stage === "loading") {
    return (
      <Card>
        <div className="eyebrow" style={{ marginBottom: 16 }}>GENERANDO PLAN</div>
        <div style={{ display: "flex", flexDirection: "column", gap: 10 }}>
          {[
            { l: "Consultando coach", done: true },
            { l: "Estructurando semanas", loading: true },
            { l: "Validando carga vs. tu HRV reciente", pending: true },
          ].map((s, i) => (
            <div key={i} style={{ display: "flex", gap: 10, alignItems: "center", fontSize: 13, color: s.pending ? "var(--text-4)" : "var(--text-2)" }}>
              {s.done ? <Icon name="check" size={14} color="var(--good)" /> :
                s.loading ? <div style={{ width: 12, height: 12, borderRadius: "50%", border: "2px solid var(--line)", borderTopColor: "var(--accent)", animation: "spin 0.8s linear infinite" }} /> :
                <div style={{ width: 12, height: 12, borderRadius: "50%", border: "2px dashed var(--line)" }} />}
              {s.l}
            </div>
          ))}
        </div>
        <div style={{ marginTop: 18, fontSize: 11.5, color: "var(--text-3)", fontFamily: "var(--font-mono)" }}>
          ☁ enviando a OpenAI · ~30–60s · gasto estimado $0.08
        </div>
      </Card>
    );
  }

  if (stage === "preview" && generated) {
    const md = generated.content?.answer || "";
    const parsed = parseTrainingPlan(md);
    return (
      <>
        <Card style={{ background: parsed ? "var(--good-soft)" : "var(--warn-soft)", border: `1px solid color-mix(in srgb, var(--${parsed ? "good" : "warn"}) 30%, transparent)` }}>
          <div style={{ display: "flex", alignItems: "center", gap: 10 }}>
            <Icon name={parsed ? "check" : "alert"} size={16} color={`var(--${parsed ? "good" : "warn"})`} />
            <div style={{ flex: 1 }}>
              <div style={{ fontSize: 13, fontWeight: 500 }}>
                {parsed ? "Plan generado · revisalo y guardalo" : "Plan generado · sin estructura clara"}
              </div>
              <div style={{ fontSize: 11.5, color: "var(--text-2)" }}>
                {parsed
                  ? `${parsed.weeks.length} semanas · ${parsed.weeks.reduce((a, w) => a + w.days.filter((d) => d.sessions.some((s) => s.tone !== "rest")).length, 0)} sesiones totales`
                  : "El LLM no respetó el formato esperado. Podés guardarlo igual y verlo crudo, o reintentar con instrucciones más estrictas."}
              </div>
            </div>
            <Btn size="sm" variant="ghost" onClick={() => { setStage("form"); setGenerated(null); }}>Descartar</Btn>
            {!parsed && <Btn size="sm" variant="default" onClick={generate}>Reintentar</Btn>}
            <Btn size="sm" variant="primary" onClick={() => onSaved && onSaved(generated)}>Guardar y activar</Btn>
          </div>
        </Card>
        {/* Preview del plan recién generado */}
        <TrainingPlan
          setSelectedSession={() => {}}
          selectedSession={null}
          activePlan={generated}
          plans={[]}
          inDemo={false}
          onSelectPlan={() => {}}
        />
      </>
    );
  }

  return (
    <Card>
      <div className="eyebrow" style={{ marginBottom: 14 }}>NUEVO PLAN DE ENTRENAMIENTO</div>
      {stage === "error" && errorMsg && (
        <div style={{
          padding: "10px 12px", marginBottom: 14,
          background: "var(--bad-soft)",
          border: "1px solid color-mix(in srgb, var(--bad) 30%, transparent)",
          borderRadius: "var(--r-2)",
          fontSize: 12.5, color: "var(--text-2)",
          display: "flex", gap: 10, alignItems: "flex-start",
        }}>
          <Icon name="alert" size={14} color="var(--bad)" />
          <div>
            <strong style={{ color: "var(--bad)" }}>No se generó el plan.</strong> {errorMsg}
            {/llm/i.test(errorMsg) && (
              <div style={{ marginTop: 6 }}>
                <Btn size="sm" variant="ghost" onClick={() => { window.dispatchEvent(new CustomEvent("af-route", { detail: "settings" })); }}>Ir a Configuración → LLM</Btn>
              </div>
            )}
          </div>
        </div>
      )}
      {/* Presets clásicos de running — un click llena objetivo + semanas + sesiones. */}
      <div style={{ marginBottom: 18 }}>
        <div className="eyebrow" style={{ marginBottom: 8 }}>PRESETS DE CARRERA</div>
        <div style={{ display: "flex", gap: 8, flexWrap: "wrap" }}>
          {RUNNING_PRESETS.map((p) => (
            <button
              key={p.id}
              type="button"
              onClick={() => applyPreset(p)}
              style={{
                padding: "8px 14px",
                border: "1px solid var(--line)",
                background: "var(--bg-1)",
                borderRadius: "var(--r-2)",
                cursor: "pointer",
                fontSize: 12.5, fontWeight: 500,
                color: "var(--text)",
              }}
            >
              {p.label}
              <span style={{ fontSize: 10, color: "var(--text-3)", marginLeft: 6, fontFamily: "var(--font-mono)" }}>
                {p.weeks}sem · {p.sessions}/sem
              </span>
            </button>
          ))}
        </div>
        <div style={{ fontSize: 11, color: "var(--text-3)", marginTop: 6 }}>
          Tocá un preset para llenar el objetivo y los parámetros base. Podés editarlos abajo.
        </div>
      </div>

      <div className="af-stack-mobile" style={{ display: "grid", gridTemplateColumns: "1fr 1fr", gap: 18 }}>
        <Field label="Objetivo" full>
          <input
            type="text"
            value={params.goal}
            onChange={(e) => setParams({ ...params, goal: e.target.value })}
            placeholder="ej: Media maratón sub 1h45 · 5K sub 22 · base aeróbica multi-deporte"
            style={fieldStyle}
          />
        </Field>
        <Field label="Semanas">
          <input type="number" value={params.weeks} onChange={(e) => setParams({ ...params, weeks: e.target.value })} min={2} max={26} style={fieldStyle} />
        </Field>
        <Field label="Sesiones/sem">
          <input type="number" value={params.sessions} onChange={(e) => setParams({ ...params, sessions: e.target.value })} min={2} max={10} style={fieldStyle} />
        </Field>
        <Field label="Día clave largo">
          <select value={params.long_day} onChange={(e) => setParams({ ...params, long_day: e.target.value })} style={fieldStyle}>
            <option value="Saturday">Sábado</option>
            <option value="Sunday">Domingo</option>
          </select>
        </Field>
        <Field label="Lesiones / restricciones" full>
          <textarea rows={2} value={params.restrictions} onChange={(e) => setParams({ ...params, restrictions: e.target.value })} placeholder="Ej: tendón aquiles izq sensible — sin cuestas largas." style={{ ...fieldStyle, resize: "none" }} />
        </Field>
      </div>

      {/* Sports preferences — el LLM las usa para proponer principal + alternativa por sesión */}
      <div style={{ marginTop: 18, paddingTop: 18, borderTop: "1px solid var(--line-soft)" }}>
        <div className="eyebrow" style={{ marginBottom: 8 }}>DEPORTES QUE PRACTICÁS</div>
        <div style={{ fontSize: 11.5, color: "var(--text-3)", marginBottom: 10, lineHeight: 1.5 }}>
          Marcá los deportes que hacés. El que elijas como <strong>principal</strong> manda; los demás se ofrecen como alternativa cross-training en cada sesión.
        </div>
        {!prefs ? (
          <div style={{ fontSize: 12, color: "var(--text-3)", fontFamily: "var(--font-mono)" }}>cargando preferencias…</div>
        ) : supportedSports.length === 0 ? (
          <div style={{ fontSize: 12, color: "var(--text-3)" }}>No se pudo cargar el catálogo de deportes.</div>
        ) : (
          <>
            <div style={{ display: "flex", flexWrap: "wrap", gap: 6 }}>
              {supportedSports.map((sp) => {
                const checked = (prefs.sports || []).some((s) => s.id === sp.id);
                return (
                  <button
                    key={sp.id}
                    type="button"
                    onClick={() => togglePrefSport(sp.id)}
                    style={{
                      padding: "6px 12px",
                      border: checked ? "1.5px solid var(--accent)" : "1px solid var(--line)",
                      background: checked ? "color-mix(in srgb, var(--accent) 8%, var(--bg-1))" : "var(--bg-1)",
                      borderRadius: 999,
                      cursor: "pointer",
                      fontSize: 12,
                      color: checked ? "var(--accent)" : "var(--text-2)",
                    }}
                  >
                    {checked ? "✓ " : ""}{sp.name}
                  </button>
                );
              })}
            </div>
            {(prefs.sports || []).length > 0 && (
              <div style={{ marginTop: 10 }}>
                <div className="eyebrow" style={{ marginBottom: 6, fontSize: 9.5 }}>DEPORTE PRINCIPAL</div>
                <div style={{ display: "flex", gap: 6, flexWrap: "wrap" }}>
                  {prefs.sports.map((s) => (
                    <button
                      key={s.id}
                      type="button"
                      onClick={() => setPrimarySport(s.id)}
                      style={{
                        padding: "6px 12px",
                        border: s.is_primary ? "1.5px solid var(--good)" : "1px solid var(--line)",
                        background: s.is_primary ? "color-mix(in srgb, var(--good) 10%, var(--bg-1))" : "var(--bg-1)",
                        borderRadius: "var(--r-2)",
                        cursor: "pointer",
                        fontSize: 12,
                        fontWeight: s.is_primary ? 500 : 400,
                        color: s.is_primary ? "var(--good)" : "var(--text-2)",
                      }}
                    >
                      {s.is_primary ? "★ " : ""}{s.name}
                    </button>
                  ))}
                </div>
              </div>
            )}
            <label style={{ display: "flex", alignItems: "center", gap: 8, marginTop: 12, fontSize: 11.5, color: "var(--text-3)", cursor: "pointer" }}>
              <input
                type="checkbox"
                checked={savePrefs}
                onChange={(e) => setSavePrefs(e.target.checked)}
              />
              Guardar estas preferencias para los próximos planes
            </label>
          </>
        )}
      </div>
      <div style={{ marginTop: 18, display: "flex", justifyContent: "flex-end", gap: 8 }}>
        <Btn variant="default" onClick={() => setView("training")}>Cancelar</Btn>
        <Btn
          variant="primary"
          icon={<Icon name="bot" size={13} />}
          onClick={() => {
            if (!params.goal || !params.goal.trim()) {
              setErrorMsg("Escribí un objetivo concreto antes de generar (ej: 'Media maratón sub 1h45').");
              setStage("error");
              return;
            }
            generate();
          }}
        >
          Generar plan
        </Btn>
      </div>
    </Card>
  );
};

/* ───────── Helpers de formato ───────── */

const fieldStyle = {
  width: "100%", padding: "8px 10px", background: "var(--bg-2)", border: "1px solid var(--line)",
  borderRadius: "var(--r-2)", fontSize: 13, color: "var(--text)", outline: "none", marginTop: 4,
};

const Field = ({ label, children, full }) => (
  <div style={{ gridColumn: full ? "1 / -1" : "auto" }}>
    <div className="eyebrow" style={{ fontSize: 9.5 }}>{label}</div>
    {children}
  </div>
);

function formatDuration(mins) {
  if (!mins) return "";
  if (mins < 60) return `${mins}'`;
  const h = Math.floor(mins / 60);
  const m = mins % 60;
  return m === 0 ? `${h}h` : `${h}h${String(m).padStart(2, "0")}`;
}

function formatPlanDate(iso) {
  if (!iso) return "—";
  try {
    const d = new Date(iso);
    if (isNaN(d.getTime())) return iso;
    const months = ["ene","feb","mar","abr","may","jun","jul","ago","sep","oct","nov","dic"];
    return `${d.getDate()} ${months[d.getMonth()]} ${d.getFullYear()}`;
  } catch (_) { return iso; }
}

function truncate(s, n) {
  if (!s) return "";
  return s.length > n ? s.slice(0, n - 1).trimEnd() + "…" : s;
}

/* Markdown demo para usar en modo demo. Es un plan de 4 semanas con
 * el formato que esperamos del LLM. */
function demoPlanMarkdown() {
  return `# Bloque base hacia media maratón sub 1h45

Plan de 4 semanas con foco en sostener volumen Z2 y agregar 1 calidad por semana antes de meter intervalos.

## Semana 1
- Lun: Descanso activo · caminata 30'
- Mar: Z2 endurance · 60' zona 2 · carga 70
- Mié: Fuerza tren inferior · 60' · carga 55
- Jue: Tempo 30' · 10' E + 30' tempo Z3 + 10' E · carga 95
- Vie: Descanso · movilidad 20'
- Sáb: Largo Z2 · 1h45' zona 2 · carga 140
- Dom: Bici recovery · 1h zona 1 · carga 45

## Semana 2
- Lun: Descanso
- Mar: Intervalos 5×1000 Z4 · 90s rec · carga 110
- Mié: Fuerza tren superior · 50' · carga 50
- Jue: Z2 endurance · 70' suave · carga 80
- Vie: Descanso
- Sáb: Progresivo · 1h30' Z2→Z3 · carga 130
- Dom: Bici larga · 2h Z1-Z2 · carga 75

## Semana 3
- Lun: Descanso
- Mar: Cuestas 8×60s · rec caminando · carga 100
- Mié: Fuerza tren inferior · 60' · carga 60
- Jue: Z2 endurance · 75' · carga 90
- Vie: Descanso · movilidad
- Sáb: Largo + tempo · 1h45' (30' tempo Z3) · carga 145
- Dom: Bici recovery · 1h Z1 · carga 40

## Semana 4 (deload)
- Lun: Descanso
- Mar: Activación 30' · 4×100m progresivos · carga 50
- Mié: Descanso pre-test
- Jue: Test 5K · pre-objetivo · carga 95
- Vie: Descanso
- Sáb: Suave · 30' Z1-Z2 · carga 35
- Dom: Descanso

## Por qué este plan

Mirando tus 30 días: tu RHR bajó de 56 a 53 y tu HRV pasó de 52 a 64. Esto significa que tu base aeróbica está absorbiendo bien el volumen actual, así que no acelero la intensidad todavía. La semana 1 sostiene el ritmo que ya manejás. En la semana 2 sumo intervalos de 1k para meter aroma de Z4 sin romper la base. La semana 3 incluye un largo+tempo para empezar a tocar ritmo de carrera. La semana 4 es deload con un test de 5K para calibrar el siguiente bloque.`;
}

/* Hook para que el botón "Generar mi primer plan" del Empty cambie de view. */
if (typeof window !== "undefined") {
  window.addEventListener("af-plans-new", () => {
    // El componente Plans escucha esto para abrir el form. Como `setView` está
    // en estado local, despachamos un evento custom y re-routeamos a /plans con
    // un flag — pero lo simple: tenemos el botón "Generar nuevo plan" arriba del
    // todo, así que el evento solo loggea para debug.
    // (La pantalla se queda en "training", el usuario hace click en el botón
    // arriba.)
  });
}

window.Plans = Plans;
