/* global React, Icons, Button, Card, Heading, Text, Badge, Progress, Stat, Tabs, AppShell, navigate, DATA, CourseStore, useCourseProgress, courseItems, courseProgress, nextItem, itemHref */
/* ============================================================
   Mi espacio — the signed-in student's home.
   Route: #/mi-espacio[/<tab>]   tabs: resumen · cursos · calificaciones · apuntes
   (#/dashboard, #/mis-cursos and #/apuntes are kept as aliases.)

   Everything here is real campus data: enrolment, progress and quiz results come
   from campus_course_state (CourseStore cache) and notebooks from
   campus_get_notebook. Only structured courses (with `units`) have a backend;
   the illustrative catalog courses only appear as recommendations.
   ============================================================ */

const PS_TABS = [
  { value: "resumen",        label: "Resumen",        icon: <Icons.Home size={14} /> },
  { value: "cursos",         label: "Mis cursos",     icon: <Icons.Book size={14} /> },
  { value: "calificaciones", label: "Calificaciones", icon: <Icons.Award size={14} /> },
  { value: "apuntes",        label: "Mis apuntes",    icon: <Icons.Notes size={14} /> },
];
const psHref = tab => (tab && tab !== "resumen" ? `#/mi-espacio/${tab}` : "#/mi-espacio");

const psDate = (iso, opts = { day: "numeric", month: "short", year: "numeric" }) =>
  iso ? new Date(iso).toLocaleDateString("es", opts) : "";
const psRelative = iso => {
  if (!iso) return "";
  const days = Math.floor((Date.now() - new Date(iso).getTime()) / 86400000);
  if (days <= 0) return "hoy";
  if (days === 1) return "ayer";
  if (days < 30) return `hace ${days} días`;
  return psDate(iso);
};
const psItemLabel = it => (it.num ? `${it.num} · ${it.title}` : it.title);
const psPct = (score, total) => (total ? Math.round((score / total) * 100) : 0);

/* ---------- Data ---------- */

// Structured courses with their load status. Reading `enrolled` triggers the fetch.
function usePersonalCourses() {
  useCourseProgress();
  const structured = DATA.COURSES.filter(c => c.units);
  structured.forEach(c => c.enrolled);
  const statuses = structured.map(c => CourseStore.status(c.id));
  return {
    mine: structured.filter(c => CourseStore.isEnrolled(c.id)),
    saved: structured.filter(c => !CourseStore.isEnrolled(c.id) && CourseStore.isSaved(c.id)),
    loading: statuses.some(s => s === "idle" || s === "loading"),
    failed: structured.filter(c => CourseStore.status(c.id) === "error"),
  };
}

// Unit tests and final exam of a course, with the student's results.
function courseAssessments(course) {
  const s = CourseStore.get(course.id);
  const list = [
    ...course.units.filter(u => u.quiz).map(u => ({ item: { ...u.quiz, kind: "quiz", unit: u }, label: `Test · Unidad ${u.n}`, detail: u.title })),
    ...(course.final ? [{ item: { ...course.final, kind: "exam", unit: null }, label: "Examen final", detail: course.final.title, final: true }] : []),
  ];
  return list.map(a => {
    const r = s?.quiz?.[a.item.id] || {};
    const attempted = r.score != null;
    const used = r.attemptsUsed || 0;
    const max = r.maxAttempts || 2;
    let status = "pending";
    if (r.passed) status = "passed";
    else if (r.inProgress) status = "running";
    else if (attempted && used >= max) status = "exhausted";
    else if (attempted) status = "failed";
    return { ...a, ...r, attempted, used, max, status, pct: attempted ? psPct(r.score, r.total) : null };
  });
}
const PS_STATUS = {
  passed:    { label: "Aprobado",       variant: "success" },
  running:   { label: "En curso",       variant: "primary" },
  failed:    { label: "No superado",    variant: "emphasis" },
  exhausted: { label: "Sin intentos",   variant: "neutral" },
  pending:   { label: "Pendiente",      variant: "outline" },
};

function courseSummary(course) {
  const { items, done, total, pct, state } = courseProgress(course);
  const grades = courseAssessments(course);
  const attempted = grades.filter(g => g.attempted);
  const final = grades.find(g => g.final);
  return {
    items, done, total, pct, state, grades,
    next: nextItem(course),
    passed: grades.filter(g => g.status === "passed").length,
    average: attempted.length ? Math.round(attempted.reduce((a, g) => a + g.pct, 0) / attempted.length) : null,
    final,
    nextTest: grades.find(g => g.status !== "passed" && g.status !== "exhausted"),
    completed: pct === 100,
  };
}

// Notebooks of the enrolled courses (private, one per course).
function useNotebooks(courses) {
  const userId = window.CampusAuth?.user?.id;
  const ids = courses.map(c => c.id).join(",");
  const [books, setBooks] = React.useState({});
  const load = React.useCallback(courseId => {
    setBooks(b => ({ ...b, [courseId]: { ...(b[courseId] || {}), status: "loading" } }));
    CourseStore.getNotebook(courseId)
      .then(data => { if (window.CampusAuth?.user?.id === userId) setBooks(b => ({ ...b, [courseId]: { status: "ready", data } })); })
      .catch(e => setBooks(b => ({ ...b, [courseId]: { status: "error", error: CourseStore.errorText(e) } })));
  }, [userId]);
  React.useEffect(() => {
    if (!userId) return;
    courses.forEach(c => load(c.id));
  }, [ids, userId]);
  return [books, load];
}
const notebookEmpty = nb => !nb?.data || !(nb.data.text_content || "").trim();
const wordCount = text => ((text || "").trim().match(/\S+/g) || []).length;

// A link inside the notes that points at this campus (e.g. the "/referencia" heading
// the editor inserts) becomes an in-app hash; anything else stays external.
function campusHash(href) {
  try {
    const u = new URL(href, window.location.href);
    if (u.origin === window.location.origin && u.pathname === window.location.pathname && u.hash.startsWith("#/")) return u.hash;
  } catch {}
  return href.startsWith("#/") ? href : null;
}
// Lessons of this course cited in the notebook, in order of appearance.
function notebookLessons(summary, content) {
  const prefix = `#/curso/${summary.course.id}/leccion/`;
  const ids = [];
  (function walk(n) {
    if (!n || typeof n !== "object") return;
    (n.marks || []).forEach(m => {
      const hash = m.type === "link" && m.attrs?.href ? campusHash(m.attrs.href) : null;
      const id = hash?.startsWith(prefix) ? hash.slice(prefix.length).split("/")[0] : null;
      if (id && !ids.includes(id)) ids.push(id);
    });
    (Array.isArray(n.content) ? n.content : []).forEach(walk);
  })(content);
  return ids.map(id => summary.items.find(it => it.id === id)).filter(Boolean);
}
// Where the student was writing: the lesson saved with the notebook, else the last visited one.
function notebookLesson(summary, nb) {
  const find = id => id && summary.items.find(it => it.id === id);
  return find(nb?.data?.last_lesson_slug) || find(summary.state?.last) || summary.next;
}

/* ---------- Page ---------- */

const PersonalSpacePage = ({ tab = "resumen", sub }) => {
  const current = PS_TABS.some(t => t.value === tab) ? tab : "resumen";
  const data = usePersonalCourses();
  const name = window.CampusAuth?.displayName() || "";
  const summaries = data.mine.map(c => ({ course: c, ...courseSummary(c) }));
  const [books, reloadBook] = useNotebooks(data.mine);
  const setTab = t => { window.location.hash = psHref(t); };
  const tabLabel = PS_TABS.find(t => t.value === current).label;

  return (
    <AppShell active={current === "calificaciones" ? psHref("resumen") : psHref(current)}
      breadcrumbs={[{ label: "Mi espacio", href: "#/mi-espacio" }, { label: tabLabel }]}>
      <div data-screen-label="02 Mi espacio" className="ps">
        <PersonalIntro name={name} summaries={summaries} loading={data.loading} />

        <Tabs value={current} onChange={setTab} tabs={PS_TABS.map(t => ({
          ...t,
          count: t.value === "cursos" && summaries.length ? summaries.length : undefined,
        }))} />

        <div className="ps-panel">
          {data.failed.length > 0 && (
            <div className="ps-alert" role="alert">
              <Icons.Flag size={16} />
              <span>No hemos podido cargar parte de tu información.</span>
              <Button size="sm" variant="outline" onClick={() => data.failed.forEach(c => CourseStore.load(c.id))}>Reintentar</Button>
            </div>
          )}
          {data.loading && !summaries.length
            ? <div className="ps-loading"><span className="rd-spinner" /> Cargando tu espacio…</div>
            : <>
              {current === "resumen" && <OverviewTab summaries={summaries} saved={data.saved} books={books} />}
              {current === "cursos" && <CoursesTab summaries={summaries} saved={data.saved} />}
              {current === "calificaciones" && <GradesTab summaries={summaries} />}
              {current === "apuntes" && <NotesTab summaries={summaries} books={books} reload={reloadBook} openId={sub} />}
            </>}
        </div>
      </div>
    </AppShell>
  );
};

const PersonalIntro = ({ name, summaries, loading }) => {
  const active = summaries.filter(s => !s.completed);
  const resume = active.find(s => s.done > 0) || active[0];
  return (
    <section className="bh-intro bh-intro--single ps-intro">
      <div className="flex items-end justify-between wrap gap-6">
        <div style={{ maxWidth: 720 }}>
          <h1 className="bh-intro-title">{name ? `Hola, ${name}` : "Hola"}</h1>
          <p className="bh-intro-lead">
            {loading && !summaries.length ? "Preparando tu espacio de formación…"
              : !summaries.length ? "Aún no estás inscrito en ningún curso. Explora el catálogo y empieza tu formación."
              : resume ? <>Continúa con <strong className="ps-strong">{resume.course.title}</strong>: llevas un {resume.pct} % del curso.</>
              : "Has completado todos tus cursos. ¡Enhorabuena!"}
          </p>
        </div>
        <div className="flex gap-2 wrap">
          <Button variant="outline" icon={<Icons.Compass size={16} />} onClick={() => navigate("#/cursos")}>Explorar catálogo</Button>
          {resume && (
            <Button icon={<Icons.PlayCircle size={16} />} onClick={() => navigate(itemHref(resume.course, resume.next))}>
              Continuar curso
            </Button>
          )}
        </div>
      </div>
    </section>
  );
};

/* ---------- Resumen ---------- */

const OverviewTab = ({ summaries, saved, books }) => {
  if (!summaries.length) return <><EmptyCourses /><Recommended summaries={summaries} saved={saved} className="ps-section" /></>;
  const active = summaries.filter(s => !s.completed);
  const resume = active.find(s => s.done > 0) || active[0];
  const tests = summaries.flatMap(s => s.grades);
  const passed = tests.filter(t => t.status === "passed").length;
  const avgPct = Math.round(summaries.reduce((a, s) => a + s.pct, 0) / summaries.length);
  const certs = summaries.filter(s => s.completed).length;
  const upcoming = summaries.filter(s => s.nextTest).map(s => ({ course: s.course, test: s.nextTest }));
  const recentNotes = summaries
    .filter(s => !notebookEmpty(books[s.course.id]))
    .sort((a, b) => new Date(books[b.course.id].data.updated_at) - new Date(books[a.course.id].data.updated_at));

  return (
    <>
      <div className="grid grid-4 mb-8">
        <Stat label="Cursos inscritos" value={summaries.length} meta={`${active.length} en progreso · ${certs} completados`} icon={<Icons.Book size={16} />} />
        <Stat label="Progreso medio" value={`${avgPct} %`} meta={`${summaries.reduce((a, s) => a + s.done, 0)} actividades completadas`} icon={<Icons.Trending size={16} />} />
        <Stat label="Evaluaciones aprobadas" value={`${passed}/${tests.length}`} meta={upcoming.length ? `${upcoming.length} pendiente${upcoming.length > 1 ? "s" : ""}` : "Todo al día"} icon={<Icons.CheckCircle size={16} />} />
        <Stat label="Certificados" value={certs} meta={certs ? "Disponibles en Certificados" : "Completa un curso para obtenerlo"} icon={<Icons.Award size={16} />} />
      </div>

      <div className="ps-cols">
        <div className="flex-col gap-6">
          {resume && <ContinueCard summary={resume} />}

          <Card>
            <div className="ps-card-head">
              <Heading size="h3">Mis cursos</Heading>
              <Button variant="link" iconRight={<Icons.ArrowRight size={14} />} onClick={() => navigate(psHref("cursos"))}>Ver todos</Button>
            </div>
            <div className="ps-rows">
              {summaries.map(s => <CourseRow key={s.course.id} summary={s} />)}
            </div>
          </Card>

          <Recommended summaries={summaries} saved={saved} />
        </div>

        <div className="flex-col gap-6">
          <Card>
            <div className="ps-card-head">
              <Heading size="h4">Próximas evaluaciones</Heading>
              <Button variant="link" onClick={() => navigate(psHref("calificaciones"))}>Calificaciones</Button>
            </div>
            {upcoming.length ? (
              <div className="flex-col gap-3">
                {upcoming.map(({ course, test }) => (
                  <a key={course.id} href={itemHref(course, test.item)} className="ps-mini">
                    <span className="ps-mini-icon"><Icons.Quiz size={15} /></span>
                    <span className="ps-mini-body">
                      <strong>{test.label}</strong>
                      <span>{course.title}</span>
                      <span>{test.attempted ? `Mejor nota ${test.pct} % · ${test.used}/${test.max} intentos` : `${test.item.minutes} min · ${test.max} intentos`}</span>
                    </span>
                    <Badge variant={PS_STATUS[test.status].variant}>{PS_STATUS[test.status].label}</Badge>
                  </a>
                ))}
              </div>
            ) : <Text size="sm" muted>No tienes evaluaciones pendientes.</Text>}
          </Card>

          <Card>
            <div className="ps-card-head">
              <Heading size="h4">Mis apuntes</Heading>
              <Button variant="link" onClick={() => navigate(psHref("apuntes"))}>Ver apuntes</Button>
            </div>
            {recentNotes.length ? (
              <div className="flex-col gap-3">
                {recentNotes.map(s => {
                  const nb = books[s.course.id].data;
                  return (
                    <a key={s.course.id} href={notebookHref(s.course)} className="ps-mini">
                      <span className="ps-mini-icon"><Icons.Notes size={15} /></span>
                      <span className="ps-mini-body">
                        <strong>{s.course.title}</strong>
                        <span className="ps-clamp">{nb.text_content}</span>
                        <span>Última edición {psRelative(nb.updated_at)}, en {psItemLabel(notebookLesson(s, books[s.course.id]))}</span>
                      </span>
                    </a>
                  );
                })}
              </div>
            ) : <Text size="sm" muted>Aquí verás los apuntes que escribas mientras estudias. Ábrelos con «Mis apuntes» en cualquier página del curso.</Text>}
          </Card>
        </div>
      </div>
    </>
  );
};

const ContinueCard = ({ summary: s }) => (
  <Card flush className="ps-continue">
    <div className="photo" style={{ backgroundImage: `url(${s.course.photo})` }}>
      {s.course.badge && <img className="course-badge course-badge--card" src={s.course.badge} alt="" width="64" height="64" />}
      <button className="ps-play" onClick={() => navigate(itemHref(s.course, s.next))} aria-label="Continuar el curso">
        <Icons.Play size={22} />
      </button>
    </div>
    <div className="ps-continue-body">
      <div className="flex items-center gap-2 wrap">
        <Badge variant="primary">Continúa</Badge>
        <Text size="xs" muted>{s.course.track} · {s.course.level}</Text>
      </div>
      <Heading size="h3">{s.course.title}</Heading>
      <Text size="sm" muted>Siguiente: {s.next.unit ? `Unidad ${s.next.unit.n} · ` : ""}{psItemLabel(s.next)}</Text>
      <div style={{ marginTop: "auto" }}>
        <div className="flex items-center justify-between mb-2">
          <Text size="xs" muted>{s.done} de {s.total} actividades</Text>
          <Text size="xs" className="ps-strong">{s.pct} %</Text>
        </div>
        <Progress value={s.pct} />
        <div className="flex items-center justify-between mt-3 wrap gap-2">
          <Button variant="link" onClick={() => navigate(`#/curso/${s.course.id}`)}>Programa del curso</Button>
          <Button variant="link" iconRight={<Icons.ArrowRight size={14} />} onClick={() => navigate(itemHref(s.course, s.next))}>
            Continuar
          </Button>
        </div>
      </div>
    </div>
  </Card>
);

// The course badge when it has one (transparent, shown whole); otherwise its photo.
const CourseThumb = ({ course }) => (course.badge
  ? <img className="ps-thumb ps-thumb--badge" src={course.badge} alt="" width="56" height="56" />
  : <span className="ps-thumb" style={{ backgroundImage: `url(${course.photo})` }} />);

const CourseRow = ({ summary: s }) => (
  <a href={`#/curso/${s.course.id}`} className="ps-row">
    <CourseThumb course={s.course} />
    <span className="ps-row-body">
      <strong>{s.course.title}</strong>
      <span className="ps-row-meta">{s.completed ? "Completado" : `Siguiente: ${psItemLabel(s.next)}`}</span>
      <span className="ps-row-progress"><Progress value={s.pct} variant={s.completed ? "success" : undefined} /><b>{s.pct} %</b></span>
    </span>
    <Icons.ChevronRight size={16} className="ps-row-chev" />
  </a>
);

const EmptyCourses = () => (
  <Card className="ps-empty" style={{ padding: "48px 24px" }}>
    <span className="ps-empty-icon"><Icons.Book size={22} /></span>
    <Heading size="h4">Tu espacio está listo</Heading>
    <Text muted>Cuando te inscribas en un curso verás aquí tu progreso, tus calificaciones y tus apuntes.</Text>
    <Button icon={<Icons.Compass size={16} />} onClick={() => navigate("#/cursos")}>Ver el catálogo</Button>
  </Card>
);

/* ---------- Mis cursos ---------- */

const CoursesTab = ({ summaries, saved }) => {
  const [filter, setFilter] = React.useState("all");
  if (!summaries.length && !saved.length) return <><EmptyCourses /><Recommended summaries={summaries} saved={saved} className="ps-section" /></>;
  const list = summaries.filter(s => filter === "all" || (filter === "done" ? s.completed : !s.completed));
  return (
    <>
      {summaries.length > 0 && (
        <div className="ps-filter" role="group" aria-label="Filtrar cursos">
          {[["all", "Todos"], ["active", "En progreso"], ["done", "Completados"]].map(([v, l]) => (
            <button key={v} data-active={filter === v} onClick={() => setFilter(v)}>{l}</button>
          ))}
        </div>
      )}
      <div className="flex-col gap-4">
        {list.map(s => <CourseCard key={s.course.id} summary={s} />)}
        {summaries.length > 0 && !list.length && <Text muted>No hay cursos en esta categoría.</Text>}
      </div>

      {saved.length > 0 && (
        <section className="ps-section">
          <Heading size="h4" className="mb-4">Guardados para más tarde</Heading>
          <div className="grid grid-3">{saved.map(c => <MiniCourseCard key={c.id} course={c} />)}</div>
        </section>
      )}
      <Recommended summaries={summaries} saved={saved} className="ps-section" />
    </>
  );
};

const CourseCard = ({ summary: s }) => {
  const units = s.course.units.map(u => {
    const ids = [...u.pages.map(p => p.id), ...(u.quiz ? [u.quiz.id] : [])];
    const done = ids.filter(id => s.state?.done?.[id]).length;
    return { u, done, total: ids.length };
  });
  return (
    <Card flush className="ps-course">
      <div className="ps-course-top">
        <span className="ps-course-photo" style={{ backgroundImage: `url(${s.course.photo})` }}>
          {s.course.badge && <img className="course-badge course-badge--card" src={s.course.badge} alt={`Insignia del curso ${s.course.title}`} width="64" height="64" />}
        </span>
        <div className="ps-course-info">
          <div className="flex items-center gap-2 wrap">
            <Badge variant={s.completed ? "success" : "primary"}>{s.completed ? "Completado" : "En progreso"}</Badge>
            <Text size="xs" muted>{s.course.track} · {s.course.level} · {s.course.duration}</Text>
          </div>
          <Heading size="h3">{s.course.title}</Heading>
          <Text size="sm" muted>
            Inscrito el {psDate(s.state?.enrolledAt)}{s.state?.completedAt ? ` · Completado el ${psDate(s.state.completedAt)}` : ""}
          </Text>
          <div className="ps-row-progress ps-row-progress--lg">
            <Progress value={s.pct} variant={s.completed ? "success" : undefined} />
            <b>{s.pct} %</b>
          </div>
          <Text size="xs" muted>{s.done} de {s.total} actividades · {s.passed} de {s.grades.length} evaluaciones aprobadas{s.average != null ? ` · nota media ${s.average} %` : ""}</Text>
        </div>
        <div className="ps-course-actions">
          {!s.completed && <Button icon={<Icons.PlayCircle size={16} />} onClick={() => navigate(itemHref(s.course, s.next))}>Continuar</Button>}
          <Button variant="outline" onClick={() => navigate(`#/curso/${s.course.id}`)}>Ver programa</Button>
          {s.completed && <Button variant="outline" icon={<Icons.Award size={16} />} onClick={() => navigate("#/certificados")}>Certificado</Button>}
        </div>
      </div>
      <ol className="ps-units">
        {units.map(({ u, done, total }) => (
          <li key={u.n} data-done={done === total}>
            <span className="ps-unit-n">{done === total ? <Icons.Check size={12} stroke={3} /> : u.n}</span>
            <span className="ps-unit-title">{u.title}</span>
            <span className="ps-unit-count">{done}/{total}</span>
          </li>
        ))}
      </ol>
    </Card>
  );
};

/* ---------- Calificaciones ---------- */

const GradesTab = ({ summaries }) => {
  if (!summaries.length) return <EmptyCourses />;
  return (
    <div className="flex-col gap-6">
      <Text muted className="ps-note">
        Se muestra la mejor nota de cada evaluación. Se aprueba con un {Math.round(CourseStore.PASS_RATIO * 100)} % de aciertos; los tests aprobados cuentan como completados.
      </Text>
      {summaries.map(s => (
        <Card key={s.course.id} flush>
          <div className="ps-grades-head">
            <div>
              <Heading size="h4">{s.course.title}</Heading>
              <Text size="sm" muted>{s.passed} de {s.grades.length} evaluaciones aprobadas</Text>
            </div>
            <div className="ps-grade-kpis">
              <div><span>Nota media</span><b>{s.average != null ? `${s.average} %` : "—"}</b></div>
              <div><span>Examen final</span><b>{s.final?.attempted ? `${s.final.score}/${s.final.total}` : "—"}</b></div>
            </div>
          </div>
          <div className="ps-grades" role="table" aria-label={`Calificaciones de ${s.course.title}`}>
            <div className="ps-grade ps-grade--head" role="row">
              <span role="columnheader">Evaluación</span>
              <span role="columnheader">Mejor nota</span>
              <span role="columnheader">Intentos</span>
              <span role="columnheader">Estado</span>
              <span role="columnheader"><span className="sr-only">Acción</span></span>
            </div>
            {s.grades.map(g => (
              <div key={g.item.id} className="ps-grade" role="row" data-final={!!g.final}>
                <span role="cell" className="ps-grade-name">
                  <strong>{g.label}</strong>
                  <span>{g.detail}{g.at ? ` · ${psDate(g.at)}` : ""}</span>
                </span>
                <span role="cell" className="ps-grade-score">
                  {g.attempted ? <><b>{g.score}/{g.total}</b><span>{g.pct} %</span></> : <span className="ps-dim">Sin nota</span>}
                </span>
                <span role="cell" className="ps-grade-tries">{g.used}/{g.max}</span>
                <span role="cell"><Badge variant={PS_STATUS[g.status].variant}>{PS_STATUS[g.status].label}</Badge></span>
                <span role="cell" className="ps-grade-action">
                  <Button size="sm" variant={g.status === "passed" || g.status === "exhausted" ? "ghost" : "outline"}
                          onClick={() => navigate(itemHref(s.course, g.item))}>
                    {g.status === "passed" || g.status === "exhausted" ? "Revisar" : g.status === "running" ? "Reanudar" : g.attempted ? "Reintentar" : "Empezar"}
                  </Button>
                </span>
              </div>
            ))}
          </div>
        </Card>
      ))}
    </div>
  );
};

/* ---------- Mis apuntes ---------- */

const notebookSection = (s, data) => ({
  title: s.course.title, slug: s.course.id, content: data?.content, text: data?.text_content || "",
  updatedAt: data?.updated_at, owner: window.CampusAuth?.displayName() || "",
});
const notebookHref = course => `${psHref("apuntes")}/${course.id}`;

// `openId`: course whose notebook is open in the editor (#/mi-espacio/apuntes/<courseId>).
const NotesTab = ({ summaries, books, reload, openId }) => {
  const [query, setQuery] = React.useState("");
  const previous = React.useRef(openId);
  React.useEffect(() => {
    if (previous.current && previous.current !== openId) reload(previous.current); // refresh after editing
    previous.current = openId;
  }, [openId]);
  if (!summaries.length) return <EmptyCourses />;
  const q = query.trim().toLowerCase();
  const editing = summaries.find(s => s.course.id === openId);
  const written = summaries.filter(s => !notebookEmpty(books[s.course.id]));
  const exportAll = format => exportNotebooks(written.map(s => notebookSection(s, books[s.course.id].data)), format, "mis-apuntes");
  const exportFresh = async (s, format) => exportNotebooks([notebookSection(s, await CourseStore.getNotebook(s.course.id))], format, `apuntes-${s.course.id}`);
  const at = editing && notebookLesson(editing, books[editing.course.id]);
  return (
    <div className="flex-col gap-6">
      <div className="ps-notes-bar">
        <Text muted className="ps-note">Los apuntes que vas escribiendo mientras estudias, con «Mis apuntes» en cada página del curso. Desde aquí puedes abrirlos, seguir escribiendo y exportarlos. Solo tú puedes verlos.</Text>
        <div className="ps-notes-tools">
          <label className="gs-input-wrap ps-search">
            <Icons.Search size={16} style={{ color: "var(--text-subtle)" }} />
            <input className="gs-input-bare" placeholder="Buscar en mis apuntes…" aria-label="Buscar en mis apuntes" value={query} onChange={e => setQuery(e.target.value)} />
          </label>
          {written.length > 1 && <ExportMenu label="Exportar todos" onExport={exportAll} />}
        </div>
      </div>
      {summaries.map(s => {
        const nb = books[s.course.id];
        if (q && !(nb?.data?.text_content || "").toLowerCase().includes(q)) return null;
        return <NotebookCard key={s.course.id} summary={s} nb={nb} query={q} reload={() => reload(s.course.id)}
                             onExport={format => exportFresh(s, format)} />;
      })}
      {q && summaries.every(s => !(books[s.course.id]?.data?.text_content || "").toLowerCase().includes(q)) && (
        <Text muted>Ningún apunte contiene «{query}».</Text>
      )}
      {editing && (
        <NotebookEditorDialog course={editing.course} lesson={at} lessonLabel={psItemLabel(at)} lessonHref={itemHref(editing.course, at)}
          onClose={() => { window.location.hash = psHref("apuntes"); }}
          onExport={format => exportFresh(editing, format)} />
      )}
    </div>
  );
};

const NotebookCard = ({ summary: s, nb, query, reload, onExport }) => {
  const [expanded, setExpanded] = React.useState(false);
  const empty = notebookEmpty(nb);
  const ready = nb?.status === "ready";
  const at = notebookLesson(s, nb);
  const cited = ready && !empty ? notebookLessons(s, nb.data.content) : [];
  return (
    <Card flush className="ps-notebook">
      <div className="ps-notebook-head">
        <CourseThumb course={s.course} />
        <div style={{ flex: 1, minWidth: 0 }}>
          <Heading size="h4">{s.course.title}</Heading>
          <Text size="xs" muted>
            {ready && !empty
              ? <>{wordCount(nb.data.text_content)} palabras · última edición {psRelative(nb.data.updated_at)}{nb.data.last_lesson_slug ? <> en <a className="ps-inline-link" href={itemHref(s.course, at)}>{psItemLabel(at)}</a></> : null}</>
              : "Apuntes del curso"}
          </Text>
        </div>
        <div className="ps-notebook-actions">
          {ready && !empty && <ExportMenu onExport={onExport} variant="ghost" />}
          <Button size="sm" icon={<Icons.Edit size={14} />} disabled={!ready}
                  onClick={() => { window.location.hash = notebookHref(s.course); }}>
            {empty ? "Empezar a escribir" : "Abrir apuntes"}
          </Button>
        </div>
      </div>
      <div className="ps-notebook-body">
        {!nb || nb.status === "loading" ? <div className="ps-loading"><span className="rd-spinner" /> Cargando apuntes…</div>
          : nb.status === "error" ? <div className="ps-alert"><span>{nb.error}</span><Button size="sm" variant="outline" onClick={reload}>Reintentar</Button></div>
          : empty ? <Text size="sm" muted>Aún no has escrito apuntes en este curso. Puedes empezar aquí o, mientras estudias, con «Mis apuntes» en la barra de cada lección.</Text>
          : (
            <>
              {cited.length > 0 && (
                <div className="ps-cited">
                  <span>Lecciones citadas</span>
                  {cited.map(it => <a key={it.id} href={itemHref(s.course, it)}>{psItemLabel(it)}</a>)}
                </div>
              )}
              <div className="ps-doc" data-expanded={expanded || !!query}>
                <NotebookDoc node={nb.data.content} />
              </div>
              {!query && (
                <div className="ps-notebook-foot">
                  <button className="ps-more" onClick={() => setExpanded(e => !e)} aria-expanded={expanded}>
                    {expanded ? "Mostrar menos" : "Leer todo aquí"}
                  </button>
                  <a className="ps-more" href={notebookHref(s.course)}>Abrir para editar</a>
                </div>
              )}
            </>
          )}
      </div>
    </Card>
  );
};

// Read-only render of the notebook (Tiptap JSON). Built with React elements, never HTML strings.
const PS_SAFE_HREF = /^(https?:|mailto:)/i;
const PS_COLOR = /^#[0-9a-f]{3,8}$/i;
const psAlign = node => (["center", "right", "justify"].includes(node.attrs?.textAlign) ? { textAlign: node.attrs.textAlign } : undefined);
function NotebookDoc({ node }) {
  if (!node || typeof node !== "object") return null;
  const kids = Array.isArray(node.content) ? node.content.map((c, i) => <NotebookDoc key={i} node={c} />) : null;
  switch (node.type) {
    case "doc": return <>{kids}</>;
    case "paragraph": return <p style={psAlign(node)}>{kids}</p>;
    case "heading": { const H = `h${Math.min(Math.max(Number(node.attrs?.level) || 2, 2), 5)}`; return <H style={psAlign(node)}>{kids}</H>; }
    case "bulletList": return <ul>{kids}</ul>;
    case "orderedList": return <ol start={node.attrs?.start || 1}>{kids}</ol>;
    case "listItem": return <li>{kids}</li>;
    case "taskList": return <ul className="ps-tasks">{kids}</ul>;
    case "taskItem": return <li data-checked={!!node.attrs?.checked}><span className="ps-task-box" aria-label={node.attrs?.checked ? "Hecho" : "Pendiente"}>{node.attrs?.checked && <Icons.Check size={10} stroke={3} />}</span><div>{kids}</div></li>;
    case "blockquote": return <blockquote>{kids}</blockquote>;
    case "codeBlock": return <pre><code>{kids}</code></pre>;
    case "horizontalRule": return <hr />;
    case "hardBreak": return <br />;
    case "table": return <div className="ps-table"><table><tbody>{kids}</tbody></table></div>;
    case "tableRow": return <tr>{kids}</tr>;
    case "tableHeader": return <th>{kids}</th>;
    case "tableCell": return <td>{kids}</td>;
    case "text": {
      let el = node.text || "";
      for (const m of node.marks || []) {
        if (m.type === "bold") el = <strong>{el}</strong>;
        else if (m.type === "italic") el = <em>{el}</em>;
        else if (m.type === "underline") el = <u>{el}</u>;
        else if (m.type === "strike") el = <s>{el}</s>;
        else if (m.type === "code") el = <code>{el}</code>;
        else if (m.type === "highlight") el = <mark style={PS_COLOR.test(m.attrs?.color || "") ? { "--h": m.attrs.color } : undefined}>{el}</mark>;
        else if (m.type === "textStyle" && PS_COLOR.test(m.attrs?.color || "")) el = <span className="ps-color" style={{ "--c": m.attrs.color }}>{el}</span>;
        else if (m.type === "link" && m.attrs?.href) {
          const inApp = campusHash(m.attrs.href);
          if (inApp) el = <a href={inApp}>{el}</a>;
          else if (PS_SAFE_HREF.test(m.attrs.href)) el = <a href={m.attrs.href} target="_blank" rel="noopener noreferrer">{el}</a>;
        }
      }
      return <>{el}</>;
    }
    default: return kids ? <>{kids}</> : null;
  }
}

/* ---------- Recomendados ---------- */

const Recommended = ({ summaries, saved = [], className }) => {
  const tracks = new Set(summaries.map(s => s.course.track));
  const skip = new Set([...summaries.map(s => s.course.id), ...saved.map(c => c.id)]);
  const list = DATA.COURSES
    .filter(c => !skip.has(c.id) && !c.enrolled)
    .map(c => ({ c, rank: (c.comingSoon ? 0 : 10) + (tracks.has(c.track) ? 4 : 0) + (c.new ? 1 : 0) }))
    .sort((a, b) => b.rank - a.rank)
    .slice(0, 3)
    .map(x => x.c);
  if (!list.length) return null;
  const open = list.some(c => !c.comingSoon);
  return (
    <Card className={className}>
      <div className="ps-card-head">
        <div>
          <Heading size="h3">{open ? "Recomendado para ti" : "Próximos cursos"}</Heading>
          <Text size="sm" muted className="mt-1">{open ? (tracks.size ? `A partir de tus cursos de ${[...tracks].join(", ")}` : "Cursos abiertos a inscripción") : "En preparación: aún no admiten inscripciones"}</Text>
        </div>
        <Button variant="link" iconRight={<Icons.ArrowRight size={14} />} onClick={() => navigate("#/cursos")}>Catálogo</Button>
      </div>
      <div className="grid grid-3">{list.map(c => <MiniCourseCard key={c.id} course={c} />)}</div>
    </Card>
  );
};

const MiniCourseCard = ({ course }) => (
  <a href={`#/curso/${course.id}`} className="course-card">
    <div className="photo photo--plain" style={{ backgroundImage: `url(${course.photo})` }}>
      {course.badge && <img className="course-badge course-badge--card" src={course.badge} alt="" width="64" height="64" />}
    </div>
    <div className="course-card-body">
      <div className="course-card-kicker">
        <span>{course.track}</span>
        {course.comingSoon ? <span className="course-card-status">Próximamente</span>
          : course.new && <span className="course-card-status">Nuevo</span>}
      </div>
      <div className="course-card-title">{course.title}</div>
      <div className="course-card-meta">
        <span>{course.level}</span>
        <span aria-hidden="true">·</span>
        <span>{course.duration}</span>
      </div>
    </div>
  </a>
);

Object.assign(window, { PersonalSpacePage, DashboardPage: PersonalSpacePage, MiniCourseCard });
