// BIBLIA DEL MUNDO — vista FICHAS del panel admin.
// Herramienta interna de escritura: personajes, lugares, objetos, facciones y
// glosario. Nada de esto lo ve el lector; vive en /api/bible (admin-only).
//
// Sufijo WB en todo (ver proto_global_scope_gotcha): los .jsx comparten scope.

const { useState: useStateWB, useEffect: useEffectWB, useMemo: useMemoWB } = React;

// ---------------------------------------------------------------- utilidades

// Todos los capítulos de la novela, aplanados, con su arco — para los pickers.
function flatChaptersWB(arcs) {
  const out = [];
  for (const a of arcs || []) {
    for (const c of a.chapters || []) {
      out.push({ id: c.id, num: c.num, title: c.title, arcId: a.id, arcTitle: a.title, accent: a.cover?.accent || "#ff5aa0" });
    }
  }
  return out;
}

function initialsWB(name) {
  const n = (name || "").trim();
  if (!n) return "?";
  const parts = n.split(/\s+/).slice(0, 2);
  return parts.map(p => p[0]).join("").toUpperCase();
}

// Busca en nombre, alias, one-liner, facción y notas. Sin acentos ni mayúsculas.
function normTextWB(s) {
  // Quita acentos (rango de marcas diacriticas) para que "Nunez" encuentre a Nuñez.
  return (s || "").toString().toLowerCase().normalize("NFD").replace(/[\u0300-\u036f]/g, "");
}
function matchesQueryWB(entity, q) {
  if (!q) return true;
  const needle = normTextWB(q);
  const hay = [entity.name, entity.alias, entity.oneLiner, entity.faction, entity.role, entity.notes]
    .map(normTextWB).join(" ");
  return hay.includes(needle);
}

// --------------------------------------------------------------- sub-componentes

// Avatar cuadrado: imagen si hay, si no las iniciales sobre el color de la ficha.
function EntityAvatarWB({ entity, size = 38 }) {
  const style = { width: size, height: size, borderColor: entity.color || "#ff5aa0" };
  if (entity.image) {
    return (
      <div className="wb-avatar" style={style}>
        <img
          src={entity.image}
          alt=""
          style={{
            objectPosition: `${entity.imageOffsetX ?? 50}% ${entity.imageOffsetY ?? 50}%`,
            transform: `scale(${entity.imageScale ?? 1})`,
          }}
        />
      </div>
    );
  }
  return (
    <div className="wb-avatar wb-avatar-empty" style={{ ...style, color: entity.color || "#ff5aa0" }}>
      {initialsWB(entity.name)}
    </div>
  );
}

// Selector de capítulos donde aparece la ficha. Agrupado por arco, con nota
// opcional por capítulo ("aquí se entera de lo del hermano").
function ChapterPickerWB({ arcs, value, onChange }) {
  const [open, setOpen] = useStateWB(false);
  const selected = value || [];
  const byId = new Map(selected.map(c => [String(c.chapterId), c]));

  const toggle = (chapterId) => {
    const id = String(chapterId);
    if (byId.has(id)) onChange(selected.filter(c => String(c.chapterId) !== id));
    else onChange([...selected, { chapterId: id, note: "" }]);
  };
  const setNote = (chapterId, note) => {
    onChange(selected.map(c => String(c.chapterId) === String(chapterId) ? { ...c, note } : c));
  };

  const all = flatChaptersWB(arcs);
  const chosen = all.filter(c => byId.has(String(c.id)));

  return (
    <div className="wb-chpicker">
      <div className="wb-chpicker-head">
        <span className="wb-field-label">APARECE EN — {chosen.length} CAPÍTULO{chosen.length === 1 ? "" : "S"}</span>
        <button type="button" className="wb-mini-btn" onClick={() => setOpen(o => !o)}>
          {open ? "▲ CERRAR" : "▼ ELEGIR CAPÍTULOS"}
        </button>
      </div>

      {chosen.length > 0 && (
        <div className="wb-chpicker-chosen">
          {chosen.map(c => (
            <div key={c.id} className="wb-chpicker-row">
              <span className="wb-chpicker-tag" style={{ borderColor: c.accent, color: c.accent }}>
                {c.arcTitle} · {c.num}
              </span>
              <span className="wb-chpicker-title">{c.title}</span>
              <input
                className="admin-input wb-chpicker-note"
                value={byId.get(String(c.id))?.note || ""}
                onChange={(e) => setNote(c.id, e.target.value)}
                placeholder="qué pasa aquí (opcional)"
              />
              <button type="button" className="wb-x-btn" onClick={() => toggle(c.id)} title="Quitar">✕</button>
            </div>
          ))}
        </div>
      )}

      {open && (
        <div className="wb-chpicker-list">
          {(arcs || []).length === 0 && <div className="wb-empty-hint">Todavía no hay capítulos.</div>}
          {(arcs || []).map(arc => (
            <div key={arc.id} className="wb-chpicker-arc">
              <div className="wb-chpicker-arc-head" style={{ color: arc.cover?.accent }}>
                {arc.title}
              </div>
              <div className="wb-chpicker-chips">
                {(arc.chapters || []).map(ch => {
                  const on = byId.has(String(ch.id));
                  return (
                    <button
                      key={ch.id}
                      type="button"
                      className={"wb-ch-chip" + (on ? " on" : "")}
                      style={on ? { borderColor: arc.cover?.accent, color: arc.cover?.accent } : undefined}
                      onClick={() => toggle(ch.id)}
                      title={ch.title}
                    >
                      {ch.num} · {ch.title}
                    </button>
                  );
                })}
                {(arc.chapters || []).length === 0 && <span className="wb-empty-hint">sin capítulos</span>}
              </div>
            </div>
          ))}
        </div>
      )}
    </div>
  );
}

// Relaciones con otras fichas. Se guardan aunque todavía no exista el lienzo
// visual del mapa de relaciones — cuando lo hagamos, los datos ya están.
function LinksEditorWB({ entity, entities, onChange }) {
  const others = entities.filter(e => e.id !== entity.id);
  const links = entity.links || [];
  const nameOf = (id) => entities.find(e => String(e.id) === String(id))?.name || "(borrada)";

  const add = () => {
    if (!others.length) return;
    onChange([...links, { id: window.BibleStore.newId(), toId: others[0].id, label: "", kind: "" }]);
  };
  const patch = (id, p) => onChange(links.map(l => l.id === id ? { ...l, ...p } : l));
  const remove = (id) => onChange(links.filter(l => l.id !== id));

  return (
    <div className="wb-links">
      <div className="wb-chpicker-head">
        <span className="wb-field-label">RELACIONES — {links.length}</span>
        <button type="button" className="wb-mini-btn" onClick={add} disabled={!others.length}>
          + AGREGAR
        </button>
      </div>
      {links.length === 0 && (
        <div className="wb-empty-hint">
          {others.length ? "Sin relaciones. Ej: “hermano de”, “le debe dinero a”." : "Crea otra ficha para poder relacionarlas."}
        </div>
      )}
      {links.map(l => (
        <div key={l.id} className="wb-link-row">
          <input
            className="admin-input wb-link-label"
            value={l.label || ""}
            onChange={(e) => patch(l.id, { label: e.target.value })}
            placeholder="es… (hermano de, mentor de, traicionó a)"
          />
          <select
            className="admin-input wb-link-target"
            value={l.toId}
            onChange={(e) => patch(l.id, { toId: e.target.value })}
            title={nameOf(l.toId)}
          >
            {others.map(o => (
              <option key={o.id} value={o.id}>{o.name || "(sin nombre)"}</option>
            ))}
          </select>
          <select
            className="admin-input wb-link-kind"
            value={l.kind || ""}
            onChange={(e) => patch(l.id, { kind: e.target.value })}
          >
            <option value="">TIPO —</option>
            <option value="familia">FAMILIA</option>
            <option value="alianza">ALIANZA</option>
            <option value="conflicto">CONFLICTO</option>
            <option value="romance">ROMANCE</option>
            <option value="otro">OTRO</option>
          </select>
          <button type="button" className="wb-x-btn" onClick={() => remove(l.id)} title="Quitar">✕</button>
        </div>
      ))}
    </div>
  );
}

// ------------------------------------------------------------------- editor

function EntityEditorWB({ entity, entities, arcs, onPatch, onDelete, novelStore, setNovelStore }) {
  const B = window.BibleStore;
  const kindInfo = B.KIND_LABELS[entity.kind] || B.KIND_LABELS.personaje;
  const fields = B.FIELD_GROUPS[entity.kind] || B.FIELD_GROUPS.personaje;
  const isPerson = entity.kind === "personaje";

  // La carta pública que sale de esta ficha (si ya se generó).
  const linkedCard = (novelStore.cards || []).find(c => String(c.id) === String(entity.cardId));

  const makeCard = () => {
    const cards = novelStore.cards || [];
    if (linkedCard) {
      // Ya existe: la actualizamos con lo que diga la ficha hoy.
      const next = cards.map(c => String(c.id) === String(entity.cardId) ? {
        ...c,
        name: entity.name || c.name,
        image: entity.image || c.image,
        imageOffsetX: entity.imageOffsetX,
        imageOffsetY: entity.imageOffsetY,
        imageScale: entity.imageScale,
        description: entity.oneLiner || c.description,
      } : c);
      setNovelStore({ ...novelStore, cards: next });
      return;
    }
    const card = {
      id: window.NovelStore.newId(),
      name: entity.name || "SIN NOMBRE",
      image: entity.image || "",
      imageOffsetX: entity.imageOffsetX ?? 50,
      imageOffsetY: entity.imageOffsetY ?? 50,
      imageScale: entity.imageScale ?? 1,
      rarity: "rara",
      category: entity.kind === "personaje" ? "personaje" : entity.kind,
      flavor: "",
      description: entity.oneLiner || "",
      dropRules: [],
    };
    setNovelStore({ ...novelStore, cards: [...cards, card] });
    onPatch({ cardId: card.id });
  };

  return (
    <div className="wb-editor">
      <div className="wb-editor-head">
        <div className="wb-editor-head-top">
          <EntityAvatarWB entity={entity} size={56} />
          <div className="wb-editor-head-main">
            <input
              className="admin-input wb-name-input"
              value={entity.name}
              onChange={(e) => onPatch({ name: e.target.value })}
              placeholder={`Nombre del ${kindInfo.singular.toLowerCase()}`}
            />
            <input
              className="admin-input wb-oneliner-input"
              value={entity.oneLiner || ""}
              onChange={(e) => onPatch({ oneLiner: e.target.value })}
              placeholder="Una línea: quién es / qué es (esto se ve en el panel mientras escribes)"
            />
          </div>
          <select
            className="admin-input wb-kind-select"
            value={entity.kind}
            onChange={(e) => onPatch({ kind: e.target.value })}
            title="Tipo de ficha"
          >
            {B.KINDS.map(k => (
              <option key={k} value={k}>{B.KIND_LABELS[k].singular}</option>
            ))}
          </select>
        </div>

        <div className="wb-color-row">
          <span className="wb-field-label">COLOR</span>
          {B.PALETTE.map(c => (
            <button
              key={c}
              type="button"
              className={"wb-color-dot" + (entity.color === c ? " on" : "")}
              style={{ background: c }}
              onClick={() => onPatch({ color: c })}
              title={c}
            />
          ))}
          <span className="wb-color-hint">se usa en la línea del tiempo</span>
        </div>
      </div>

      <div className="wb-form">
        <div className="wb-row">
          <label className="adm-label wb-grow">
            <span>ALIAS / COMO LE DICEN</span>
            <input className="admin-input" value={entity.alias || ""} onChange={(e) => onPatch({ alias: e.target.value })} />
          </label>
          {isPerson && (
            <label className="adm-label">
              <span>ROL</span>
              <select className="admin-input" value={entity.role || ""} onChange={(e) => onPatch({ role: e.target.value })}>
                {B.ROLE_OPTIONS.map(o => <option key={o.v} value={o.v}>{o.l}</option>)}
              </select>
            </label>
          )}
          {isPerson && (
            <label className="adm-label">
              <span>ESTADO</span>
              <select className="admin-input" value={entity.status || ""} onChange={(e) => onPatch({ status: e.target.value })}>
                {B.STATUS_OPTIONS.map(o => <option key={o.v} value={o.v}>{o.l}</option>)}
              </select>
            </label>
          )}
        </div>

        <div className="wb-row">
          <label className="adm-label wb-grow">
            <span>{entity.kind === "faccion" ? "LÍDER / A QUIÉN RESPONDE" : "FACCIÓN / BANDO"}</span>
            <input className="admin-input" value={entity.faction || ""} onChange={(e) => onPatch({ faction: e.target.value })} />
          </label>
          {isPerson && (
            <label className="adm-label">
              <span>EDAD</span>
              <input className="admin-input" value={entity.age || ""} onChange={(e) => onPatch({ age: e.target.value })} />
            </label>
          )}
          <label className="adm-label wb-grow">
            <span>{entity.kind === "lugar" ? "DÓNDE QUEDA" : "DE DÓNDE VIENE"}</span>
            <input className="admin-input" value={entity.origin || ""} onChange={(e) => onPatch({ origin: e.target.value })} />
          </label>
        </div>

        <label className="adm-label">
          <span>IMAGEN — OPCIONAL</span>
          <window.ImageUpload
            value={entity.image || ""}
            onChange={(v) => onPatch({ image: v })}
            adjust={{ offsetX: entity.imageOffsetX, offsetY: entity.imageOffsetY, scale: entity.imageScale }}
            onAdjust={(patch) => {
              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;
              onPatch(out);
            }}
          />
        </label>

        {fields.map(f => (
          <label key={f.k} className="adm-label">
            <span>{f.l}</span>
            <textarea
              className="admin-input wb-textarea"
              rows={f.rows}
              value={entity[f.k] || ""}
              onChange={(e) => onPatch({ [f.k]: e.target.value })}
              spellCheck
            />
          </label>
        ))}

        <ChapterPickerWB
          arcs={arcs}
          value={entity.chapters}
          onChange={(chapters) => onPatch({ chapters })}
        />

        <LinksEditorWB
          entity={entity}
          entities={entities}
          onChange={(links) => onPatch({ links })}
        />

        {window.BibleStore.COMMON_TAIL_FIELDS.map(f => (
          <label key={f.k} className={"adm-label" + (f.secret ? " wb-secret" : "")}>
            <span>{f.l}</span>
            <textarea
              className="admin-input wb-textarea"
              rows={f.rows}
              value={entity[f.k] || ""}
              onChange={(e) => onPatch({ [f.k]: e.target.value })}
              spellCheck
            />
          </label>
        ))}

        <div className="wb-editor-footer">
          <button type="button" className="wb-card-btn" onClick={makeCard} disabled={!entity.name}>
            {linkedCard ? "◆ ACTUALIZAR SU CARTA" : "◆ GENERAR CARTA DE ESTA FICHA"}
          </button>
          {linkedCard && (
            <span className="wb-card-hint">
              Ligada a la carta “{linkedCard.name}”. Se edita en CARTAS.
            </span>
          )}
          <button type="button" className="adm-delete-btn" onClick={onDelete}>✕ ELIMINAR FICHA</button>
        </div>
      </div>
    </div>
  );
}

// -------------------------------------------------------------------- panel

function BiblePanelWB({ store, setStore }) {
  const B = window.BibleStore;
  const { bible, setBible, status, flush, reload, loaded } = B.useBible();
  // Sin una lectura buena no dejamos escribir (ver bibleStore: el PUT es sync total).
  const canWrite = loaded;
  const [kind, setKind] = useStateWB("personaje");
  const [query, setQuery] = useStateWB("");
  const [activeId, setActiveId] = useStateWB(null);

  const entities = bible.entities || [];
  const visible = useMemoWB(
    () => entities.filter(e => e.kind === kind && matchesQueryWB(e, query)),
    [entities, kind, query]
  );
  const active = entities.find(e => String(e.id) === String(activeId)) || null;

  // Si la ficha activa deja de existir (borrada) o cambia de tipo, soltamos la selección.
  useEffectWB(() => {
    if (activeId && !entities.some(e => String(e.id) === String(activeId))) setActiveId(null);
  }, [entities, activeId]);

  // Ctrl+S fuerza el guardado en vez de esperar al debounce.
  useEffectWB(() => {
    const handler = (e) => {
      if ((e.ctrlKey || e.metaKey) && e.key.toLowerCase() === "s") {
        e.preventDefault();
        flush();
      }
    };
    window.addEventListener("keydown", handler);
    return () => window.removeEventListener("keydown", handler);
  }, [flush]);

  const addEntity = () => {
    const e = B.blankEntity(kind, entities.length);
    setBible({ ...bible, entities: [...entities, e] });
    setActiveId(e.id);
  };

  const patchEntity = (id, patch) => {
    setBible({
      ...bible,
      entities: entities.map(e => String(e.id) === String(id) ? { ...e, ...patch } : e),
    });
  };

  const deleteEntity = (id) => {
    const e = entities.find(x => String(x.id) === String(id));
    if (!confirm(`¿Eliminar la ficha “${e?.name || "sin nombre"}”? No se puede deshacer.`)) return;
    setBible({
      ...bible,
      entities: entities.filter(x => String(x.id) !== String(id)),
      // Y la sacamos de los eventos donde estaba como participante.
      events: (bible.events || []).map(ev => ({
        ...ev,
        entityIds: (ev.entityIds || []).filter(x => String(x) !== String(id)),
      })),
    });
    if (String(activeId) === String(id)) setActiveId(null);
  };

  const countFor = (k) => entities.filter(e => e.kind === k).length;

  const statusLabel = status === "error"
    ? (loaded ? "✕ ERROR AL GUARDAR" : "✕ NO SE PUDIERON CARGAR LAS FICHAS")
    : ({ loading: "CARGANDO…", saving: "GUARDANDO…", saved: "● GUARDADO", idle: "" }[status] || "");

  return (
    // has-active: en teléfono esconde la lista para que la ficha se quede con
    // toda la pantalla (en escritorio la clase no hace nada).
    <div className={"wb-grid" + (active ? " has-active" : "")}>
      <aside className="admin-sidebar wb-sidebar">
        <div className="wb-kind-tabs">
          {B.KINDS.map(k => (
            <button
              key={k}
              type="button"
              className={"wb-kind-tab" + (kind === k ? " active" : "")}
              onClick={() => { setKind(k); setQuery(""); }}
            >
              <span className="wb-kind-icon">{B.KIND_LABELS[k].icon}</span>
              <span className="wb-kind-label">{B.KIND_LABELS[k].label}</span>
              <span className="wb-kind-count">{countFor(k)}</span>
            </button>
          ))}
        </div>

        <input
          className="admin-input wb-search"
          value={query}
          onChange={(e) => setQuery(e.target.value)}
          placeholder="Buscar en las fichas…"
        />

        <div className="wb-list">
          {status === "loading" && <div className="wb-empty-hint">Cargando fichas…</div>}
          {status !== "loading" && visible.length === 0 && (
            <div className="wb-empty-hint">
              {query ? "Nada con esa búsqueda." : `Sin ${B.KIND_LABELS[kind].label.toLowerCase()} todavía.`}
            </div>
          )}
          {visible.map(e => (
            <button
              key={e.id}
              type="button"
              className={"wb-list-row" + (String(activeId) === String(e.id) ? " active" : "")}
              onClick={() => setActiveId(e.id)}
              style={{ borderLeftColor: e.color || "#ff5aa0" }}
            >
              <EntityAvatarWB entity={e} size={34} />
              <span className="wb-list-text">
                <span className="wb-list-name">{e.name || "(sin nombre)"}</span>
                <span className="wb-list-sub">{e.oneLiner || e.alias || "—"}</span>
              </span>
              {e.status === "muerto" && <span className="wb-dead" title="Muerto">✝</span>}
            </button>
          ))}
        </div>

        <button className="admin-add-btn wb-add-btn" onClick={addEntity} disabled={!canWrite}>
          + AGREGAR {B.KIND_LABELS[kind].singular}
        </button>
      </aside>

      <main className="admin-main wb-main">
        {active && (
          <button type="button" className="admin-back-btn" onClick={() => setActiveId(null)}>
            ← TODAS LAS FICHAS
          </button>
        )}
        <div className="wb-status-bar">
          <span className={"wb-status wb-status-" + status}>{statusLabel}</span>
          {status === "error" && (
            <button type="button" className="wb-mini-btn" onClick={() => (loaded ? flush() : reload())}>
              REINTENTAR
            </button>
          )}
        </div>

        {active ? (
          <EntityEditorWB
            key={active.id}
            entity={active}
            entities={entities}
            arcs={store.arcs || []}
            onPatch={(patch) => patchEntity(active.id, patch)}
            onDelete={() => deleteEntity(active.id)}
            novelStore={store}
            setNovelStore={setStore}
          />
        ) : (
          <div className="admin-empty">
            <div className="admin-empty-icon">{(window.BibleStore.KIND_LABELS[kind] || {}).icon || "☻"}</div>
            <div className="admin-empty-title">BIBLIA DEL MUNDO</div>
            <div className="admin-empty-sub">
              Aquí viven las fichas técnicas de tu novela: personajes, lugares, objetos,<br />
              facciones y glosario. Nada de esto lo ve el lector.<br /><br />
              Selecciona una ficha o crea la primera con el botón + AGREGAR.<br />
              Mientras escribes un capítulo puedes consultarlas sin salir del texto (Ctrl+B).
            </div>
          </div>
        )}
      </main>
    </div>
  );
}

window.BiblePanel = BiblePanelWB;
window.EntityAvatar = EntityAvatarWB;
window.wbFlatChapters = flatChaptersWB;
window.wbMatchesQuery = matchesQueryWB;
