/* global React, Icons, Button, CourseStore */
/* ============================================================
   Course notebook tools for "Mi espacio".
   · NotebookEditorDialog: the same notes editor the student uses beside each lesson
     (/campus-notes, Tiptap in an iframe), opened full screen from Mi espacio.
   · Exporters: PDF (print), Word (.doc), Markdown, plain text and clipboard,
     generated from the saved Tiptap JSON. Exported HTML is built with escaping;
     only http(s)/mailto and campus links survive.
   ============================================================ */

const nbEsc = s => String(s ?? "").replace(/[&<>"']/g, c => ({ "&": "&amp;", "<": "&lt;", ">": "&gt;", '"': "&quot;", "'": "&#39;" }[c]));
const NB_COLOR = /^#[0-9a-f]{3,8}$/i;
const nbCampusUrl = hash => `${window.location.origin}${window.location.pathname}${hash}`;
// External links stay; campus links (lesson references) become absolute so they work outside the app.
function nbHref(href) {
  if (!href) return null;
  try {
    const u = new URL(href, window.location.href);
    if (u.origin === window.location.origin && u.pathname === window.location.pathname && u.hash.startsWith("#/")) return nbCampusUrl(u.hash);
    if (/^(https?:|mailto:)$/i.test(u.protocol)) return u.href;
  } catch {}
  return null;
}
const nbAlign = node => (["center", "right", "justify"].includes(node.attrs?.textAlign) ? ` style="text-align:${node.attrs.textAlign}"` : "");

/* ---------- Tiptap JSON → HTML (for PDF, Word and rich clipboard) ---------- */
function nbToHTML(node) {
  if (!node || typeof node !== "object") return "";
  const kids = () => (Array.isArray(node.content) ? node.content.map(nbToHTML).join("") : "");
  switch (node.type) {
    case "doc": return kids();
    case "paragraph": return `<p${nbAlign(node)}>${kids() || "&nbsp;"}</p>`;
    case "heading": { const l = Math.min(Math.max(Number(node.attrs?.level) || 2, 2), 5); return `<h${l}${nbAlign(node)}>${kids()}</h${l}>`; }
    case "bulletList": return `<ul>${kids()}</ul>`;
    case "orderedList": return `<ol start="${Number(node.attrs?.start) || 1}">${kids()}</ol>`;
    case "listItem": return `<li>${kids()}</li>`;
    case "taskList": return `<ul class="tasks">${kids()}</ul>`;
    case "taskItem": return `<li class="${node.attrs?.checked ? "done" : ""}">${node.attrs?.checked ? "☑" : "☐"} ${kids()}</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 `<table>${kids()}</table>`;
    case "tableRow": return `<tr>${kids()}</tr>`;
    case "tableHeader": return `<th>${kids()}</th>`;
    case "tableCell": return `<td>${kids()}</td>`;
    case "text": {
      let out = nbEsc(node.text);
      for (const m of node.marks || []) {
        if (m.type === "bold") out = `<strong>${out}</strong>`;
        else if (m.type === "italic") out = `<em>${out}</em>`;
        else if (m.type === "underline") out = `<u>${out}</u>`;
        else if (m.type === "strike") out = `<s>${out}</s>`;
        else if (m.type === "code") out = `<code>${out}</code>`;
        else if (m.type === "highlight") out = `<mark${NB_COLOR.test(m.attrs?.color || "") ? ` style="background:${m.attrs.color}"` : ""}>${out}</mark>`;
        else if (m.type === "textStyle" && NB_COLOR.test(m.attrs?.color || "")) out = `<span style="color:${m.attrs.color}">${out}</span>`;
        else if (m.type === "link") { const h = nbHref(m.attrs?.href); if (h) out = `<a href="${nbEsc(h)}">${out}</a>`; }
      }
      return out;
    }
    default: return kids();
  }
}

/* ---------- Tiptap JSON → Markdown ---------- */
const nbMdEsc = s => String(s ?? "").replace(/([\\`*_[\]])/g, "\\$1");
function nbInlineMd(nodes) {
  return (nodes || []).map(n => {
    if (n.type === "hardBreak") return "  \n";
    if (n.type !== "text") return nbInlineMd(n.content);
    const marks = (n.marks || []).map(m => m.type);
    let out = marks.includes("code") ? `\`${n.text}\`` : nbMdEsc(n.text);
    if (marks.includes("bold")) out = `**${out}**`;
    if (marks.includes("italic")) out = `*${out}*`;
    if (marks.includes("strike")) out = `~~${out}~~`;
    const link = (n.marks || []).find(m => m.type === "link");
    const h = link && nbHref(link.attrs?.href);
    if (h) out = `[${out}](${h})`;
    return out;
  }).join("");
}
function nbToMarkdown(node, indent = "") {
  if (!node || typeof node !== "object") return "";
  const blocks = list => (list || []).map(n => nbToMarkdown(n, indent)).filter(b => b !== "").join("\n\n");
  switch (node.type) {
    case "doc": return blocks(node.content);
    case "paragraph": return indent + nbInlineMd(node.content);
    case "heading": return `${"#".repeat(Math.min(Math.max(Number(node.attrs?.level) || 2, 2), 5))} ${nbInlineMd(node.content)}`;
    case "bulletList": case "orderedList": case "taskList":
      return (node.content || []).map((li, i) => {
        const bullet = node.type === "orderedList" ? `${(Number(node.attrs?.start) || 1) + i}.` : node.type === "taskList" ? `- [${li.attrs?.checked ? "x" : " "}]` : "-";
        const inner = (li.content || []).map(c => nbToMarkdown(c, `${indent}  `).replace(new RegExp(`^${indent}  `), "")).join("\n");
        return `${indent}${bullet} ${inner}`;
      }).join("\n");
    case "blockquote": return blocks(node.content).split("\n").map(l => `> ${l}`).join("\n");
    case "codeBlock": return `\`\`\`\n${(node.content || []).map(t => t.text || "").join("")}\n\`\`\``;
    case "horizontalRule": return "---";
    case "table": {
      const rows = (node.content || []).map(r => (r.content || []).map(c => (c.content || []).map(p => nbInlineMd(p.content)).join(" ").replace(/\|/g, "\\|")));
      if (!rows.length) return "";
      const width = Math.max(...rows.map(r => r.length));
      const line = r => `| ${Array.from({ length: width }, (_, i) => r[i] || "").join(" | ")} |`;
      return [line(rows[0]), `| ${Array(width).fill("---").join(" | ")} |`, ...rows.slice(1).map(line)].join("\n");
    }
    default: return blocks(node.content);
  }
}

/* ---------- Export ---------- */
const NB_PRINT_CSS = `
  @page { margin: 18mm 16mm; }
  body { font: 11.5pt/1.6 "IBM Plex Sans", Arial, sans-serif; color: #2B3A49; margin: 0; }
  section + section { page-break-before: always; }
  header { border-bottom: 1px solid #0A1E33; padding-bottom: 10px; margin-bottom: 18px; }
  .kicker { font-size: 9pt; color: #006C95; font-weight: 600; margin: 0 0 4px; }
  h1 { font-size: 20pt; line-height: 1.15; color: #0A1E33; margin: 0 0 4px; }
  .meta { font-size: 9pt; color: #5B6874; margin: 0; }
  h2, h3, h4, h5 { color: #0A1E33; line-height: 1.25; margin: 16px 0 6px; }
  p { margin: 0 0 8px; } a { color: #006C95; }
  blockquote { margin: 0 0 10px; padding-left: 12px; border-left: 3px solid #006C95; color: #5B6874; }
  pre { background: #F6F7F8; padding: 10px; white-space: pre-wrap; }
  table { border-collapse: collapse; margin: 0 0 10px; } th, td { border: 1px solid #C3CBD3; padding: 4px 8px; text-align: left; vertical-align: top; }
  th { background: #F6F7F8; } ul.tasks { list-style: none; padding-left: 0; } li.done { color: #5B6874; }
  mark { background: #F7F0E3; } * { -webkit-print-color-adjust: exact; print-color-adjust: exact; }`;

function nbSectionMeta(s) {
  const date = s.updatedAt ? new Date(s.updatedAt).toLocaleDateString("es", { day: "numeric", month: "long", year: "numeric" }) : "";
  return [s.owner, date && `Actualizado el ${date}`].filter(Boolean).join(" · ");
}
function nbHtmlDocument(sections, title) {
  return `<!doctype html><html lang="es"><head><meta charset="utf-8"><title>${nbEsc(title)}</title><style>${NB_PRINT_CSS}</style></head><body>${
    sections.map(s => `<section><header><p class="kicker">Blue Human Academy · Mis apuntes</p><h1>${nbEsc(s.title)}</h1><p class="meta">${nbEsc(nbSectionMeta(s))}</p></header>${nbToHTML(s.content)}</section>`).join("")
  }</body></html>`;
}
function nbDownload(content, type, filename) {
  const url = URL.createObjectURL(new Blob([content], { type }));
  const a = Object.assign(document.createElement("a"), { href: url, download: filename });
  document.body.appendChild(a); a.click(); a.remove();
  setTimeout(() => URL.revokeObjectURL(url), 1500);
}
// Opens the browser's print dialog ("Guardar como PDF") for the notes only.
function nbPrint(html) {
  const frame = Object.assign(document.createElement("iframe"), { title: "Impresión de apuntes" });
  frame.style.cssText = "position:fixed;right:0;bottom:0;width:0;height:0;border:0;visibility:hidden";
  document.body.appendChild(frame);
  const doc = frame.contentDocument;
  doc.open(); doc.write(html); doc.close();
  setTimeout(() => {
    frame.contentWindow.focus();
    frame.contentWindow.print();
    setTimeout(() => frame.remove(), 1000);
  }, 250);
}
const nbToast = msg => window.dispatchEvent(new CustomEvent("bh-toast", { detail: { msg, tone: "info" } }));
const nbStamp = () => new Date().toISOString().slice(0, 10);

const NB_FORMATS = [
  { id: "pdf",  label: "PDF", hint: "Imprimir o guardar como PDF" },
  { id: "doc",  label: "Word", hint: ".doc · se abre en Word o LibreOffice" },
  { id: "md",   label: "Markdown", hint: ".md · Notion, Obsidian…" },
  { id: "txt",  label: "Texto plano", hint: ".txt" },
  { id: "copy", label: "Copiar", hint: "Al portapapeles, con formato" },
];

// sections: [{ title, slug, content (Tiptap JSON), text, updatedAt, owner }]
async function exportNotebooks(sections, format, name) {
  const usable = sections.filter(s => (s.text || "").trim());
  if (!usable.length) { nbToast("No hay apuntes que exportar todavía."); return; }
  const title = usable.length === 1 ? `Apuntes · ${usable[0].title}` : "Mis apuntes · Blue Human Academy";
  const file = `${name}-${nbStamp()}`;
  if (format === "pdf") return nbPrint(nbHtmlDocument(usable, title));
  if (format === "doc") {
    const html = nbHtmlDocument(usable, title).replace("<html lang=\"es\">", '<html lang="es" xmlns:o="urn:schemas-microsoft-com:office:office" xmlns:w="urn:schemas-microsoft-com:office:word">');
    return nbDownload(`﻿${html}`, "application/msword", `${file}.doc`);
  }
  if (format === "md") {
    const md = usable.map(s => `# ${s.title}\n\n_${nbSectionMeta(s)}_\n\n${nbToMarkdown(s.content)}`).join("\n\n---\n\n");
    return nbDownload(`${md}\n`, "text/markdown;charset=utf-8", `${file}.md`);
  }
  if (format === "txt") {
    const txt = usable.map(s => `${s.title}\n${"=".repeat(Math.min(s.title.length, 72))}\n${nbSectionMeta(s)}\n\n${s.text.trim()}`).join("\n\n\n");
    return nbDownload(`${txt}\n`, "text/plain;charset=utf-8", `${file}.txt`);
  }
  if (format === "copy") {
    const html = usable.map(s => `<h1>${nbEsc(s.title)}</h1>${nbToHTML(s.content)}`).join("<hr>");
    const text = usable.map(s => `${s.title}\n\n${s.text.trim()}`).join("\n\n");
    try {
      if (window.ClipboardItem && navigator.clipboard?.write) {
        await navigator.clipboard.write([new ClipboardItem({ "text/html": new Blob([html], { type: "text/html" }), "text/plain": new Blob([text], { type: "text/plain" }) })]);
      } else await navigator.clipboard.writeText(text);
      nbToast("Apuntes copiados al portapapeles.");
    } catch { nbToast("No se han podido copiar los apuntes. Prueba a exportarlos."); }
  }
}

/* Accessible export menu (button + menu of formats). */
const ExportMenu = ({ onExport, label = "Exportar", size = "sm", variant = "outline", disabled }) => {
  const [open, setOpen] = React.useState(false);
  const [busy, setBusy] = React.useState(false);
  const ref = React.useRef(null);
  const menuId = React.useId();
  React.useEffect(() => {
    if (!open) return;
    const outside = e => { if (!ref.current?.contains(e.target)) setOpen(false); };
    const key = e => { if (e.key === "Escape") { setOpen(false); ref.current?.querySelector("button")?.focus(); } };
    document.addEventListener("mousedown", outside);
    document.addEventListener("keydown", key);
    ref.current?.querySelector('[role="menuitem"]')?.focus();
    return () => { document.removeEventListener("mousedown", outside); document.removeEventListener("keydown", key); };
  }, [open]);
  const run = async id => {
    setOpen(false); setBusy(true);
    try { await onExport(id); }
    catch (e) { nbToast(CourseStore.errorText(e)); }
    finally { setBusy(false); }
  };
  const move = e => {
    if (!["ArrowDown", "ArrowUp"].includes(e.key)) return;
    e.preventDefault();
    const items = [...ref.current.querySelectorAll('[role="menuitem"]')];
    const i = items.indexOf(document.activeElement);
    items[(i + (e.key === "ArrowDown" ? 1 : items.length - 1)) % items.length]?.focus();
  };
  return (
    <div className="nb-export" ref={ref}>
      <Button size={size} variant={variant} icon={<Icons.Download size={14} />} aria-haspopup="menu" aria-expanded={open}
              aria-controls={menuId} disabled={disabled || busy} onClick={() => setOpen(o => !o)}>
        {busy ? "Preparando…" : label}
      </Button>
      {open && (
        <div className="gs-menu nb-export-menu" role="menu" id={menuId} onKeyDown={move}>
          {NB_FORMATS.map(f => (
            <button key={f.id} role="menuitem" className="nb-export-item" onClick={() => run(f.id)}>
              <strong>{f.label}</strong><span>{f.hint}</span>
            </button>
          ))}
        </div>
      )}
    </div>
  );
};

/* Full-screen notebook editor, opened from Mi espacio. Saves through the same
   campus_save_notebook path as the panel beside each lesson. */
const NotebookEditorDialog = ({ course, lesson, lessonLabel, lessonHref, onClose, onExport }) => {
  const frame = React.useRef(null);
  const closeBtn = React.useRef(null);
  const [dirty, setDirty] = React.useState(false);
  const userId = window.CampusAuth?.user?.id;
  const titleId = React.useId();
  const bridge = () => frame.current?.contentWindow?.CampusCourseNotes;
  const flush = async () => { try { await bridge()?.flush?.(); } catch {} };
  const close = async () => { await flush(); onClose(); };
  const connect = () => {
    const b = bridge();
    if (!b) return;
    const guarded = async op => {
      if (window.CampusAuth?.user?.id !== userId) throw new Error("not_authenticated");
      const r = await op();
      if (window.CampusAuth?.user?.id !== userId) throw new Error("not_authenticated");
      return r;
    };
    b.connect({
      courseId: course.id, userId,
      // "/referencia" and the saved "last lesson" point at the lesson the student was on.
      context: () => ({ lessonId: lesson.id, lessonTitle: lessonLabel, href: nbCampusUrl(lessonHref) }),
      onDirty: setDirty,
      api: {
        load: () => guarded(() => CourseStore.getNotebook(course.id)),
        save: (content, text, revision, lessonId) => guarded(() => CourseStore.saveNotebook(course.id, content, text, revision, lessonId)),
      },
    });
    b.setTheme(document.documentElement.getAttribute("data-theme"));
    b.setMode("full");
  };
  React.useEffect(() => {
    const ready = e => { if (e.origin === window.location.origin && e.source === frame.current?.contentWindow && e.data?.type === "bh-notes-ready") connect(); };
    const key = e => { if (e.key === "Escape" && !e.defaultPrevented) close(); };
    const previous = document.activeElement;
    const overflow = document.body.style.overflow;
    document.body.style.overflow = "hidden";
    window.addEventListener("message", ready);
    document.addEventListener("keydown", key);
    closeBtn.current?.focus();
    return () => {
      window.removeEventListener("message", ready);
      document.removeEventListener("keydown", key);
      document.body.style.overflow = overflow;
      bridge()?.flush?.();
      previous?.focus?.();
    };
  }, [course.id, userId]);
  React.useEffect(() => {
    if (!dirty) return;
    const warn = e => { e.preventDefault(); e.returnValue = ""; };
    window.addEventListener("beforeunload", warn);
    return () => window.removeEventListener("beforeunload", warn);
  }, [dirty]);

  return (
    <div className="nb-dialog" role="dialog" aria-modal="true" aria-labelledby={titleId}>
      <header className="nb-dialog-bar">
        <button ref={closeBtn} className="nb-dialog-back" onClick={close}>
          <Icons.ChevronLeft size={16} /><span>Mis apuntes</span>
        </button>
        <div className="nb-dialog-title">
          <h2 id={titleId}>{course.title}</h2>
          <span><Icons.Lock size={11} /> Solo tú puedes verlos · {dirty ? "cambios sin guardar…" : "guardado automático"}</span>
        </div>
        <div className="nb-dialog-actions">
          <ExportMenu onExport={async f => { await flush(); await onExport(f); }} />
          <Button size="sm" variant="outline" iconRight={<Icons.ArrowRight size={14} />}
                  onClick={async () => { await flush(); window.location.hash = lessonHref; }}>
            <span className="nb-hide-sm">Ir a </span>{lessonLabel.split(" · ")[0]}
          </Button>
        </div>
      </header>
      <iframe ref={frame} className="nb-dialog-frame" src="/campus-notes/index.html" title={`Editor de mis apuntes de ${course.title}`} onLoad={connect} />
    </div>
  );
};

Object.assign(window, { nbToHTML, nbToMarkdown, exportNotebooks, ExportMenu, NotebookEditorDialog });
