// Admin sub-components: ArcRow, ChapterEditor, AdminExportMenu, TimelineView

const { useState: useStateAP, useEffect: useEffectAP, useRef: useRefAP } = React;

function ArcRow({ arc, idx, total, activeChapterId, onSelectChapter, onAddChapter, onUpdateArc, onDeleteArc, onDeleteChapter, onReorderChapters, onMoveChapterToArc, onArcDragStart, onArcDragOver, onArcDrop, dragOverArc, draggingArc }) {
  const [open, setOpen] = useStateAP(true);
  const [editing, setEditing] = useStateAP(false);
  const [draftTitle, setDraftTitle] = useStateAP(arc.title);
  const [dragOverChId, setDragOverChId] = useStateAP(null);
  const [draggingChId, setDraggingChId] = useStateAP(null);

  const writtenCount = arc.chapters.filter(c => (c.body || []).some(p => p && p.trim())).length;
  const accent = arc.cover.accent;

  const commitTitle = () => {
    const t = draftTitle.trim().toUpperCase() || arc.title;
    onUpdateArc({ title: t });
    setEditing(false);
  };

  // Chapter drag handlers (within this arc only)
  const handleChDragStart = (e, chId) => {
    e.dataTransfer.effectAllowed = "move";
    e.dataTransfer.setData("text/plain", `ch:${arc.id}:${chId}`);
    setDraggingChId(chId);
  };
  const handleChDragOver = (e, chId) => {
    const data = e.dataTransfer.types.includes("text/plain");
    if (!data) return;
    e.preventDefault();
    e.dataTransfer.dropEffect = "move";
    setDragOverChId(chId);
  };
  const handleChDrop = (e, targetChId) => {
    e.preventDefault();
    e.stopPropagation();
    const payload = e.dataTransfer.getData("text/plain");
    setDragOverChId(null);
    setDraggingChId(null);
    if (!payload.startsWith("ch:")) return;
    const rest = payload.slice(3);
    const sep = rest.indexOf(":");
    if (sep < 0) return;
    const srcArcId = rest.slice(0, sep);
    const srcChId = rest.slice(sep + 1);
    if (String(srcArcId) === String(arc.id)) {
      // same arc: reorder
      if (String(srcChId) === String(targetChId)) return;
      onReorderChapters(arc.id, srcChId, targetChId);
    } else {
      // cross-arc: move + insert before target
      onMoveChapterToArc(srcArcId, srcChId, arc.id, targetChId);
    }
  };

  // Drop onto arc body (empty area or end) → append to this arc
  const handleArcBodyDragOver = (e) => {
    if (!e.dataTransfer.types.includes("text/plain")) return;
    if (draggingArc) return; // arc-level drag, not chapter
    e.preventDefault();
    e.dataTransfer.dropEffect = "move";
  };
  const handleArcBodyDrop = (e) => {
    // Only fires if the click landed on the body but not on a chapter (which stops propagation)
    const payload = e.dataTransfer.getData("text/plain");
    if (!payload.startsWith("ch:")) return;
    e.preventDefault();
    e.stopPropagation();
    const rest = payload.slice(3);
    const sep = rest.indexOf(":");
    if (sep < 0) return;
    const srcArcId = rest.slice(0, sep);
    const srcChId = rest.slice(sep + 1);
    setDragOverChId(null);
    setDraggingChId(null);
    if (String(srcArcId) === String(arc.id)) return;
    onMoveChapterToArc(srcArcId, srcChId, arc.id, null); // null target → append
  };

  const isDropTargetArc = dragOverArc === arc.id && draggingArc && draggingArc !== arc.id;

  return (
    <div
      className={"adm-arc" + (draggingArc === arc.id ? " is-dragging" : "") + (isDropTargetArc ? " arc-drop-target" : "")}
      onDragOver={(e) => onArcDragOver(e, arc.id)}
      onDrop={(e) => onArcDrop(e, arc.id)}
    >
      <div
        className="adm-arc-head"
        style={{ borderLeft: `3px solid ${accent}` }}
        draggable
        onDragStart={(e) => onArcDragStart(e, arc.id)}
      >
        <button className="adm-chev" onClick={() => setOpen(o => !o)} draggable={false} onDragStart={(e) => e.stopPropagation()}>{open ? "▼" : "▶"}</button>
        <div className="adm-grip" title="Arrastrar arco">⋮⋮</div>
        <div className="adm-arc-num" style={{ color: accent }}>{arc.num}</div>
        <div className="adm-arc-titlewrap">
          {editing ? (
            <input
              className="admin-input adm-arc-title-input"
              value={draftTitle}
              autoFocus
              draggable={false}
              onDragStart={(e) => e.stopPropagation()}
              onChange={(e) => setDraftTitle(e.target.value)}
              onBlur={commitTitle}
              onKeyDown={(e) => { if (e.key === "Enter") commitTitle(); if (e.key === "Escape") { setDraftTitle(arc.title); setEditing(false); } }}
            />
          ) : (
            <div className="adm-arc-title" onClick={() => { setDraftTitle(arc.title); setEditing(true); }}>
              {arc.title}
            </div>
          )}
          <div className="adm-arc-meta">{writtenCount} / {arc.chapters.length} CAPS · {writtenCount === 0 ? "OCULTO" : "VISIBLE"}</div>
        </div>
        <div className="adm-arc-actions" onMouseDown={(e) => e.stopPropagation()}>
          <button className="adm-icon-btn danger" onClick={onDeleteArc} title="Eliminar arco" draggable={false}>✕</button>
        </div>
      </div>

      {open && (
        <div
          className={"adm-arc-body" + (isDropTargetArc ? " is-drop-target-arc" : "")}
          onDragLeave={() => setDragOverChId(null)}
          onDragOver={handleArcBodyDragOver}
          onDrop={handleArcBodyDrop}
        >
          <div className="adm-arc-cover">
            <div className="adm-arc-cover-label">PORTADA DEL ARCO</div>
            <ImageUpload
              value={arc.coverImage || ""}
              onChange={(v) => onUpdateArc({ coverImage: v })}
            />
          </div>
          {arc.chapters.length === 0 && (
            <div className="adm-empty-arc">Sin capítulos.</div>
          )}
          {arc.chapters.map((c) => {
            const written = (c.body || []).some(p => p && p.trim());
            const isDragging = draggingChId === c.id;
            const isOver = dragOverChId === c.id && draggingChId && draggingChId !== c.id;
            return (
              <div
                key={c.id}
                className={"adm-ch" + (activeChapterId === c.id ? " active" : "") + (written ? " written" : "") + (isDragging ? " is-dragging" : "") + (isOver ? " ch-drop-target" : "")}
                draggable
                onDragStart={(e) => handleChDragStart(e, c.id)}
                onDragEnd={() => { setDraggingChId(null); setDragOverChId(null); }}
                onDragOver={(e) => handleChDragOver(e, c.id)}
                onDrop={(e) => handleChDrop(e, c.id)}
                onClick={() => onSelectChapter(c.id)}
              >
                <div className="adm-grip adm-grip-ch" title="Arrastrar">⋮⋮</div>
                <div className="adm-ch-num">{c.num}</div>
                <div className="adm-ch-title">{c.title}</div>
                <div className="adm-ch-status">{written ? "●" : "○"}</div>
                <div className="adm-ch-actions" onClick={(e) => e.stopPropagation()}>
                  <button className="adm-icon-btn danger" onClick={() => onDeleteChapter(c.id)} draggable={false}>✕</button>
                </div>
              </div>
            );
          })}
          <button className="adm-add-ch" onClick={onAddChapter}>+ Nuevo capítulo</button>
        </div>
      )}
    </div>
  );
}

function ImageUpload({ value, onChange, adjust, onAdjust }) {
  // adjust = { offsetX, offsetY, scale }   (todos opcionales)
  // onAdjust = (patch) => void             (si se pasa, muestra el botón AJUSTAR)
  const [busy, setBusy] = useStateAP(false);
  const [error, setError] = useStateAP("");
  const [over, setOver] = useStateAP(false);
  const [adjusting, setAdjusting] = useStateAP(false);
  const inputRef = useRefAP(null);
  const dragRef = useRefAP(null);

  const ox = adjust?.offsetX ?? 50;
  const oy = adjust?.offsetY ?? 50;
  const scale = adjust?.scale ?? 1;
  const supportsAdjust = typeof onAdjust === "function";

  const handleFiles = async (files) => {
    setError("");
    const file = files && files[0];
    if (!file) return;
    setBusy(true);
    try {
      const url = await window.NovelStore.uploadImage(file);
      onChange(url);
    } catch (e) {
      setError(e.message || "Error al subir");
    } finally {
      setBusy(false);
    }
  };

  const onDrop = (e) => {
    e.preventDefault();
    e.stopPropagation();
    setOver(false);
    if (!e.dataTransfer.files || !e.dataTransfer.files.length) return;
    handleFiles(e.dataTransfer.files);
  };

  const onPaste = (e) => {
    const items = e.clipboardData && e.clipboardData.items;
    if (!items) return;
    for (const it of items) {
      if (it.kind === "file") {
        handleFiles([it.getAsFile()]);
        return;
      }
    }
  };

  // Pan handlers (drag dentro de la imagen).
  const onPointerDown = (e) => {
    if (!adjusting || !value) return;
    e.preventDefault();
    e.currentTarget.setPointerCapture?.(e.pointerId);
    const r = e.currentTarget.getBoundingClientRect();
    dragRef.current = {
      startX: e.clientX, startY: e.clientY,
      startOX: ox, startOY: oy,
      w: r.width, h: r.height,
    };
  };
  const onPointerMove = (e) => {
    if (!dragRef.current) return;
    const d = dragRef.current;
    const dxPct = ((e.clientX - d.startX) / d.w) * 100;
    const dyPct = ((e.clientY - d.startY) / d.h) * 100;
    const nx = Math.max(0, Math.min(100, d.startOX - dxPct));
    const ny = Math.max(0, Math.min(100, d.startOY - dyPct));
    onAdjust({ offsetX: nx, offsetY: ny });
  };
  const onPointerUp = (e) => {
    dragRef.current = null;
    e.currentTarget.releasePointerCapture?.(e.pointerId);
  };
  const onWheel = (e) => {
    if (!adjusting || !value) return;
    e.preventDefault();
    const next = Math.max(1, Math.min(4, scale + (-e.deltaY * 0.002)));
    onAdjust({ scale: next });
  };

  const resetAdjust = () => onAdjust({ offsetX: 50, offsetY: 50, scale: 1 });

  if (value) {
    return (
      <div className="adm-img-preview" style={{ position: "relative" }}>
        <div
          style={{
            position: "relative",
            width: "100%",
            height: 280,
            overflow: "hidden",
            background: "#000",
            cursor: adjusting && value ? (dragRef.current ? "grabbing" : "grab") : "default",
            touchAction: adjusting ? "none" : "auto",
          }}
          onPointerDown={onPointerDown}
          onPointerMove={onPointerMove}
          onPointerUp={onPointerUp}
          onPointerCancel={onPointerUp}
          onWheel={onWheel}
        >
          <img
            src={value}
            alt=""
            draggable={false}
            style={{
              width: "100%",
              height: "100%",
              objectFit: "cover",
              objectPosition: `${ox}% ${oy}%`,
              transform: `scale(${scale})`,
              transformOrigin: `${ox}% ${oy}%`,
              transition: dragRef.current ? "none" : "transform 0.1s",
              userSelect: "none",
              pointerEvents: adjusting ? "none" : "auto",
            }}
          />
          {adjusting && (
            <div style={{
              position: "absolute", top: 8, left: "50%", transform: "translateX(-50%)",
              padding: "6px 12px", background: "rgba(0,0,0,0.75)",
              border: "1px solid rgba(255,255,255,0.15)", borderRadius: 999,
              fontFamily: "'Geist Sans', system-ui, sans-serif",
              fontSize: 9, letterSpacing: "0.2em", color: "rgba(255,255,255,0.85)",
              pointerEvents: "none", whiteSpace: "nowrap", zIndex: 5,
            }}>
              ARRASTRA · SCROLL = ZOOM
            </div>
          )}
        </div>

        {!adjusting && (
          <div className="adm-img-overlay">
            <button className="adm-img-btn" onClick={() => inputRef.current && inputRef.current.click()} disabled={busy}>
              {busy ? "SUBIENDO..." : "REEMPLAZAR"}
            </button>
            {supportsAdjust && (
              <button className="adm-img-btn" onClick={() => setAdjusting(true)}>
                ✛ AJUSTAR
              </button>
            )}
            <button className="adm-img-btn danger" onClick={() => { if (confirm("¿Eliminar imagen?")) onChange(""); }}>
              ✕ ELIMINAR
            </button>
          </div>
        )}

        {adjusting && (
          <div style={{
            display: "flex", flexDirection: "column", gap: 8, padding: 10,
            background: "rgba(0,0,0,0.4)", borderTop: "1px solid rgba(255,255,255,0.08)",
          }}>
            <div style={{ display: "grid", gridTemplateColumns: "38px 1fr 48px", alignItems: "center", gap: 8 }}>
              <label style={{ fontFamily: "'Geist Sans', system-ui, sans-serif", fontSize: 10, letterSpacing: "0.15em", color: "rgba(255,255,255,0.55)" }}>ZOOM</label>
              <input type="range" min={1} max={4} step={0.01} value={scale}
                onChange={(e) => onAdjust({ scale: Number(e.target.value) })}
                style={{ width: "100%", accentColor: "#fff" }} />
              <span style={{ fontFamily: "'Geist Sans', system-ui, sans-serif", fontSize: 10, color: "rgba(255,255,255,0.7)", textAlign: "right" }}>{scale.toFixed(2)}×</span>
            </div>
            <div style={{ display: "grid", gridTemplateColumns: "38px 1fr 48px", alignItems: "center", gap: 8 }}>
              <label style={{ fontFamily: "'Geist Sans', system-ui, sans-serif", fontSize: 10, letterSpacing: "0.15em", color: "rgba(255,255,255,0.55)" }}>X</label>
              <input type="range" min={0} max={100} step={0.5} value={ox}
                onChange={(e) => onAdjust({ offsetX: Number(e.target.value) })}
                style={{ width: "100%", accentColor: "#fff" }} />
              <span style={{ fontFamily: "'Geist Sans', system-ui, sans-serif", fontSize: 10, color: "rgba(255,255,255,0.7)", textAlign: "right" }}>{Math.round(ox)}%</span>
            </div>
            <div style={{ display: "grid", gridTemplateColumns: "38px 1fr 48px", alignItems: "center", gap: 8 }}>
              <label style={{ fontFamily: "'Geist Sans', system-ui, sans-serif", fontSize: 10, letterSpacing: "0.15em", color: "rgba(255,255,255,0.55)" }}>Y</label>
              <input type="range" min={0} max={100} step={0.5} value={oy}
                onChange={(e) => onAdjust({ offsetY: Number(e.target.value) })}
                style={{ width: "100%", accentColor: "#fff" }} />
              <span style={{ fontFamily: "'Geist Sans', system-ui, sans-serif", fontSize: 10, color: "rgba(255,255,255,0.7)", textAlign: "right" }}>{Math.round(oy)}%</span>
            </div>
            <div style={{ display: "flex", gap: 6, justifyContent: "flex-end" }}>
              <button className="adm-img-btn" onClick={resetAdjust}>RESET</button>
              <button className="adm-img-btn" onClick={() => setAdjusting(false)}>✓ LISTO</button>
            </div>
          </div>
        )}

        <input
          ref={inputRef}
          type="file"
          accept="image/*"
          style={{ display: "none" }}
          onChange={(e) => { handleFiles(e.target.files); e.target.value = ""; }}
        />
      </div>
    );
  }

  return (
    <div
      className={"adm-img-drop" + (over ? " is-over" : "") + (busy ? " is-busy" : "")}
      onDragOver={(e) => { e.preventDefault(); e.stopPropagation(); setOver(true); }}
      onDragLeave={() => setOver(false)}
      onDrop={onDrop}
      onPaste={onPaste}
      tabIndex={0}
    >
      <div className="adm-img-drop-icon">{busy ? "⏳" : "↑"}</div>
      <div className="adm-img-drop-title">
        {busy ? "PROCESANDO..." : "ARRASTRA UNA IMAGEN AQUÍ"}
      </div>
      <div className="adm-img-drop-sub">o</div>
      <button
        className="adm-img-drop-btn"
        onClick={() => inputRef.current && inputRef.current.click()}
        disabled={busy}
      >
        SELECCIONAR DESDE COMPUTADORA
      </button>
      <div className="adm-img-drop-hint">JPG · PNG · WEBP · hasta 8 MB</div>
      {error && <div className="adm-img-drop-error">{error}</div>}
      <input
        ref={inputRef}
        type="file"
        accept="image/*"
        style={{ display: "none" }}
        onChange={(e) => { handleFiles(e.target.files); e.target.value = ""; }}
      />
    </div>
  );
}

function ChapterEditor({ arc, chapter, onChange, onDelete }) {
  const bodyText = (chapter.body || []).join("\n\n");
  const writtenCount = (chapter.body || []).filter(p => p && p.trim()).length;
  const wordCount = (chapter.body || []).reduce((n, p) => n + (p || "").trim().split(/\s+/).filter(Boolean).length, 0);

  const setBody = (text) => {
    const parts = text.split(/\n{2,}/).map(s => s.replace(/\s+$/g, ""));
    onChange({ body: parts });
  };

  return (
    <div className="adm-editor">
      <div className="adm-editor-head">
        <div className="adm-editor-eyebrow" style={{ color: arc.cover.accent }}>
          {arc.title} · ARCO {arc.num}
        </div>
        <input
          className="admin-input adm-editor-title"
          value={chapter.title}
          onChange={(e) => onChange({ title: e.target.value })}
          placeholder="Título del capítulo"
        />
        <div className="adm-editor-row">
          <label className="adm-label">
            <span>NÚMERO</span>
            <input className="admin-input adm-num-input" value={chapter.num} onChange={(e) => onChange({ num: e.target.value })} />
          </label>
          <label className="adm-label adm-label-grow">
            <span>DESCRIPCIÓN BREVE (PARA LA TARJETA)</span>
            <input className="admin-input" value={chapter.desc || ""} onChange={(e) => onChange({ desc: e.target.value })} placeholder="Resumen corto que se muestra en la galería" />
          </label>
        </div>
        <label className="adm-label">
          <span>IMAGEN DEL CAPÍTULO — OPCIONAL</span>
          <ImageUpload
            value={chapter.image || ""}
            onChange={(v) => onChange({ image: v })}
            adjust={{
              offsetX: chapter.imageOffsetX,
              offsetY: chapter.imageOffsetY,
              scale: chapter.imageScale,
            }}
            onAdjust={(patch) => {
              // Translate ImageUpload's local names → chapter fields.
              const out = {};
              if (patch.offsetX != null) out.imageOffsetX = patch.offsetX;
              if (patch.offsetY != null) out.imageOffsetY = patch.offsetY;
              if (patch.scale   != null) out.imageScale   = patch.scale;
              onChange(out);
            }}
          />
        </label>
      </div>

      <div className="adm-editor-stats">
        <span>{writtenCount} PÁRRAFOS</span>
        <span>·</span>
        <span>{wordCount} PALABRAS</span>
        <span>·</span>
        <span>{writtenCount === 0 ? "OCULTO EN LECTOR" : "VISIBLE EN LECTOR"}</span>
        <button className="adm-delete-btn" onClick={onDelete}>✕ ELIMINAR CAPÍTULO</button>
      </div>

      <textarea
        className="adm-textarea"
        value={bodyText}
        onChange={(e) => setBody(e.target.value)}
        placeholder={"Escribe el contenido del capítulo aquí.\n\nSepara párrafos con una línea en blanco.\n\nUsa ___ en una línea sola para insertar un separador horizontal."}
        spellCheck
      />
    </div>
  );
}

// Export menu — JSON, single chapter PDF, single arc PDF, full novel PDF
function AdminExportMenu({ store }) {
  const [open, setOpen] = useStateAP(false);
  const ref = useRefAP(null);

  useEffectAP(() => {
    const onClick = (e) => { if (ref.current && !ref.current.contains(e.target)) setOpen(false); };
    document.addEventListener("mousedown", onClick);
    return () => document.removeEventListener("mousedown", onClick);
  }, []);

  const dl = (filename, content, mime) => {
    const blob = new Blob([content], { type: mime });
    const url = URL.createObjectURL(blob);
    const a = document.createElement("a");
    a.href = url; a.download = filename; a.click();
    setTimeout(() => URL.revokeObjectURL(url), 100);
  };

  const exportJSON = () => {
    dl(`trooper-${Date.now()}.json`, JSON.stringify(store, null, 2), "application/json");
    setOpen(false);
  };

  const importJSON = () => {
    const inp = document.createElement("input");
    inp.type = "file"; inp.accept = ".json,application/json";
    inp.onchange = async (e) => {
      const f = e.target.files[0]; if (!f) return;
      try {
        const text = await f.text();
        const data = JSON.parse(text);
        if (!data.arcs) return alert("Archivo inválido");
        if (!confirm("Esto reemplazará TODA la novela actual. ¿Continuar?")) return;
        window.NovelStore.saveStore(data);
        location.reload();
      } catch (e) { alert("Error leyendo archivo"); }
    };
    inp.click();
  };

  const printable = (titleHTML, bodyHTML) => `<!DOCTYPE html><html><head><meta charset="utf-8"><title>TROOPER</title>
<style>
  @page { margin: 2.5cm; }
  body { font-family: Georgia, serif; line-height: 1.7; color: #1a1520; font-size: 12pt; }
  h1 { font-family: 'Helvetica Neue', sans-serif; font-weight: 800; letter-spacing: -0.01em; font-size: 28pt; margin: 0 0 8pt; }
  h2 { font-family: 'Helvetica Neue', sans-serif; font-weight: 700; letter-spacing: 0.05em; text-transform: uppercase; font-size: 14pt; margin: 32pt 0 6pt; border-bottom: 1px solid #1a1520; padding-bottom: 4pt; }
  .arc-title { page-break-before: always; }
  .arc-title:first-child { page-break-before: avoid; }
  .meta { font-family: 'Helvetica Neue', sans-serif; font-size: 9pt; letter-spacing: 0.25em; text-transform: uppercase; color: #666; margin-bottom: 24pt; }
  p { margin: 0 0 10pt; text-align: justify; text-indent: 1.4em; }
  p:first-of-type { text-indent: 0; }
  hr { border: none; text-align: center; margin: 16pt 0; }
  hr::after { content: "· · ·"; letter-spacing: 0.6em; color: #888; }
  .ch { page-break-before: always; }
  .ch:first-child { page-break-before: avoid; }
  .cover { text-align: center; padding: 100pt 0; }
  .cover h1 { font-size: 48pt; }
  .cover .sub { font-family: 'Helvetica Neue', sans-serif; letter-spacing: 0.4em; font-size: 10pt; margin-top: 18pt; }
</style></head><body>${titleHTML}${bodyHTML}</body></html>`;

  const renderBody = (body) => (body || []).map(p => p === "___" ? "<hr/>" : `<p>${escapeHTML(p)}</p>`).join("\n");
  const escapeHTML = (s) => String(s).replace(/[&<>"']/g, (c) => ({ "&":"&amp;","<":"&lt;",">":"&gt;",'"':"&quot;","'":"&#39;" }[c]));

  const pdfWindow = (html) => {
    const w = window.open("", "_blank");
    if (!w) return alert("Habilita popups para exportar PDF");
    w.document.open(); w.document.write(html); w.document.close();
    setTimeout(() => { w.focus(); w.print(); }, 400);
  };

  const exportFullPDF = () => {
    const cover = `<div class="cover"><h1>TROOPER</h1><div class="sub">NOVELA · ${store.arcs.length} ARCOS</div></div>`;
    const body = store.arcs.map(a => {
      const writtenCh = a.chapters.filter(c => (c.body||[]).some(p => p && p.trim()));
      if (writtenCh.length === 0) return "";
      const chs = writtenCh.map(c => `<div class="ch"><h2>CAP ${c.num} — ${escapeHTML(c.title)}</h2>${renderBody(c.body)}</div>`).join("");
      return `<div class="arc-title"><h1>ARCO ${a.num} — ${escapeHTML(a.title)}</h1><div class="meta">${escapeHTML(a.tagline||"")}</div>${chs}</div>`;
    }).join("");
    pdfWindow(printable(cover, body));
    setOpen(false);
  };

  const exportArcPDF = (arc) => {
    const cover = `<div class="cover"><h1>${escapeHTML(arc.title)}</h1><div class="sub">ARCO ${arc.num} · TROOPER</div></div>`;
    const writtenCh = arc.chapters.filter(c => (c.body||[]).some(p => p && p.trim()));
    if (writtenCh.length === 0) return alert("Este arco no tiene capítulos escritos.");
    const body = writtenCh.map(c => `<div class="ch"><h2>CAP ${c.num} — ${escapeHTML(c.title)}</h2>${renderBody(c.body)}</div>`).join("");
    pdfWindow(printable(cover, body));
    setOpen(false);
  };

  const exportChapterPDF = (arc, ch) => {
    const cover = `<div class="cover"><h1>${escapeHTML(ch.title)}</h1><div class="sub">${escapeHTML(arc.title)} · ARCO ${arc.num} · CAP ${ch.num}</div></div>`;
    const body = `<div class="ch">${renderBody(ch.body)}</div>`;
    pdfWindow(printable(cover, body));
    setOpen(false);
  };

  return (
    <div className="adm-export" ref={ref}>
      <button className="admin-view-btn primary" onClick={() => setOpen(o => !o)}>↓ EXPORTAR</button>
      {open && (
        <div className="adm-export-menu">
          <div className="adm-export-section">DATOS</div>
          <button onClick={exportJSON}>Exportar JSON (backup)</button>
          <button onClick={importJSON}>Importar JSON</button>
          <div className="adm-export-section">PDF</div>
          <button onClick={exportFullPDF}>Obra completa</button>
          <div className="adm-export-section">PDF · POR ARCO</div>
          {store.arcs.map(a => (
            <button key={a.id} onClick={() => exportArcPDF(a)}>{a.num} — {a.title}</button>
          ))}
          <div className="adm-export-section">PDF · POR CAPÍTULO</div>
          {store.arcs.flatMap(a => a.chapters.map(c => (
            <button key={c.id} onClick={() => exportChapterPDF(a, c)} style={{ fontSize: "11px" }}>
              {a.num}·{c.num} — {c.title}
            </button>
          )))}
        </div>
      )}
    </div>
  );
}

function AdminTimelineView({ arcs, onSelectChapter }) {
  return (
    <div className="adm-timeline">
      <div className="adm-timeline-head">TIMELINE</div>
      {arcs.length === 0 && <div className="admin-empty-sub" style={{textAlign:"center",marginTop:40}}>No hay arcos todavía.</div>}
      {arcs.map(arc => {
        const accent = arc.cover.accent;
        return (
          <div key={arc.id} className="adm-tl-arc">
            <div className="adm-tl-arc-head" style={{ borderLeft: `4px solid ${accent}` }}>
              <span className="adm-tl-num" style={{ color: accent }}>{arc.num}</span>
              <span className="adm-tl-title">{arc.title}</span>
              <span className="adm-tl-meta">{arc.chapters.length} CAPS</span>
            </div>
            <div className="adm-tl-chapters">
              {arc.chapters.map(c => {
                const written = (c.body||[]).some(p => p && p.trim());
                return (
                  <button
                    key={c.id}
                    className={"adm-tl-ch" + (written ? " written" : "")}
                    onClick={() => onSelectChapter(c.id)}
                    style={{ borderColor: written ? accent : "rgba(239,233,220,0.15)" }}
                  >
                    <div className="adm-tl-ch-num" style={{ color: accent }}>{c.num}</div>
                    <div className="adm-tl-ch-title">{c.title}</div>
                    <div className="adm-tl-ch-status">{written ? "● ESCRITO" : "○ PENDIENTE"}</div>
                  </button>
                );
              })}
            </div>
          </div>
        );
      })}
    </div>
  );
}

window.ArcRow = ArcRow;
window.ChapterEditor = ChapterEditor;
window.AdminExportMenu = AdminExportMenu;
window.AdminTimelineView = AdminTimelineView;
window.ImageUpload = ImageUpload;
