/* ════════════════════════════════════════════════════════════════════
   tab-organize.js — l'organiseur de pages.
   Importe un ou plusieurs PDF, affiche chaque page en vignette, permet
   de réordonner (glisser-déposer), pivoter, supprimer, extraire une
   sélection, puis exporter le résultat. Couvre à lui seul : fusion fine,
   extraction, suppression et réorganisation de pages.
   ════════════════════════════════════════════════════════════════════ */
(() => {
  'use strict';
  const { byId, uid, esc, toast, readFile, downloadBlob, baseName, formatSize } = Utils;

  const THUMB_WIDTH = 180; // largeur CSS visée des vignettes

  const state = {
    /** @type {Map<string,{id:string,name:string,bytes:Uint8Array,password:string|null,doc:any,pageCount:number,hue:number,label:string}>} */
    sources: new Map(),
    /** Ordre d'affichage = ordre du document final. @type {{id:string,srcId:string,index:number,extraRot:number}[]} */
    pages: [],
    /** Identifiants des pages sélectionnées. @type {Set<string>} */
    selected: new Set(),
    lastClickedId: null,     // ancre du Maj+clic
    suppressClickUntil: 0,   // ignore le clic résiduel après un glisser
    busy: false,
    hueSeed: 0,
  };

  const els = {};
  let prog;

  /* ── Initialisation ────────────────────────────────────────────── */
  function init() {
    ['drop', 'file', 'workspace', 'grid', 'sources', 'count', 'select-all',
     'select-none', 'rotate-sel', 'extract', 'delete-sel', 'add', 'reset',
     'download', 'total', 'progress']
      .forEach((k) => { els[k.replace(/-(\w)/g, (_, c) => c.toUpperCase())] = byId(`org-${k}`); });
    prog = Utils.progress(els.progress);

    Utils.setupDropzone(els.drop, els.file, addFiles);
    els.add.addEventListener('click', () => els.file.click());
    els.selectAll.addEventListener('click', () => setAllSelected(true));
    els.selectNone.addEventListener('click', () => setAllSelected(false));
    els.rotateSel.addEventListener('click', rotateSelection);
    els.extract.addEventListener('click', extractSelection);
    els.deleteSel.addEventListener('click', () => removePageIds([...state.selected]));
    els.reset.addEventListener('click', resetAll);
    els.download.addEventListener('click', () => exportPdf(state.pages, suggestName()));

    els.grid.addEventListener('click', onGridClick);
    els.grid.addEventListener('keydown', onGridKey);

    Sortable.create(els.grid, {
      animation: 150,
      ghostClass: 'drag-ghost',
      chosenClass: 'drag-chosen',
      filter: '.card-btn',
      preventOnFilter: false,
      onEnd() {
        state.suppressClickUntil = Date.now() + 250;
        syncOrderFromDom();
      },
    });

    App.registerTab('organize', () => state.pages.length > 0);
  }

  /* ── Import de fichiers ────────────────────────────────────────── */
  async function addFiles(files) {
    const pdfs = files.filter(Utils.isPdf);
    if (!pdfs.length) { toast('Déposez des fichiers PDF.', 'error'); return; }
    if (pdfs.length < files.length) toast('Les fichiers qui ne sont pas des PDF ont été ignorés.');

    const newPages = [];
    for (const file of pdfs) {
      const created = await addSource(file);
      if (created) newPages.push(...created);
    }
    if (newPages.length) appendCards(newPages);
    refreshChips();
    updateAll();
  }

  /** Charge un fichier, crée sa source et ses entrées de pages. */
  async function addSource(file) {
    try {
      const bytes = await readFile(file);
      const opened = await PdfEngine.openPdfJsInteractive(bytes, file.name);
      if (!opened) { toast(`« ${file.name} » ignoré : mot de passe non fourni.`); return null; }

      const id = uid();
      const seed = state.hueSeed++;
      const src = {
        id,
        name: file.name,
        bytes,
        password: opened.password,
        doc: opened.doc,
        pageCount: opened.doc.numPages,
        hue: (seed * 67) % 360, // angle d'or : couleurs bien réparties
        label: String.fromCharCode(65 + (seed % 26)) + (seed >= 26 ? Math.floor(seed / 26) + 1 : ''),
      };
      state.sources.set(id, src);

      const created = [];
      for (let i = 0; i < src.pageCount; i++) {
        created.push({ id: uid(), srcId: id, index: i, extraRot: 0 });
      }
      state.pages.push(...created);
      return created;
    } catch (e) {
      console.error(e);
      toast(`« ${file.name} » : ${e.message || 'ouverture impossible.'}`, 'error');
      return null;
    }
  }

  /* ── Construction des cartes ───────────────────────────────────── */
  function appendCards(pages) {
    const frag = document.createDocumentFragment();
    for (const p of pages) frag.appendChild(cardEl(p));
    els.grid.appendChild(frag);
  }

  function cardEl(p) {
    const src = state.sources.get(p.srcId);
    const card = document.createElement('div');
    card.className = 'page-card';
    card.dataset.id = p.id;
    card.tabIndex = 0;
    card.setAttribute('role', 'button');
    card.setAttribute('aria-pressed', 'false');
    card.setAttribute('aria-label', `${src.name}, page ${p.index + 1}`);
    card.innerHTML =
      `<span class="src-badge" style="--hue:${src.hue}">${esc(src.label)}·${p.index + 1}</span>` +
      `<div class="card-actions">` +
        `<button type="button" class="card-btn" data-act="rotate" title="Pivoter de 90°" aria-label="Pivoter cette page de 90 degrés"><svg class="icon"><use href="#i-rotate"/></svg></button>` +
        `<button type="button" class="card-btn" data-act="remove" title="Retirer la page" aria-label="Retirer cette page"><svg class="icon"><use href="#i-x"/></svg></button>` +
      `</div>` +
      `<div class="thumb-box"><div class="spinner" role="img" aria-label="Chargement"></div></div>` +
      `<span class="pos" aria-hidden="true"></span>`;
    card.title = `${src.name} — page ${p.index + 1}`;

    const box = card.querySelector('.thumb-box');
    PdfEngine.observe(box, () => renderThumbInto(card, p));
    return card;
  }

  async function renderThumbInto(card, p) {
    const src = state.sources.get(p.srcId);
    if (!src) return;
    const box = card.querySelector('.thumb-box');
    try {
      const canvas = await PdfEngine.renderThumb(src.doc, p.index, THUMB_WIDTH, p.extraRot);
      if (!card.isConnected) return; // carte retirée entre-temps
      box.replaceChildren(canvas);
    } catch (e) {
      console.error(e);
      box.innerHTML = '<svg class="icon" style="color:var(--danger)"><use href="#i-alert"/></svg>';
    }
  }

  /* ── Puces des fichiers sources ────────────────────────────────── */
  function refreshChips() {
    const chips = [...state.sources.values()].map((src) => {
      const n = state.pages.filter((p) => p.srcId === src.id).length;
      const chip = document.createElement('span');
      chip.className = 'chip';
      chip.style.setProperty('--hue', src.hue);
      chip.title = src.name;
      chip.innerHTML =
        `<span class="tag">${esc(src.label)}</span>` +
        `<span class="chip-name">${esc(src.name)} · ${n} p.</span>` +
        `<button type="button" class="btn-icon" aria-label="Retirer ${esc(src.name)}"><svg class="icon"><use href="#i-x"/></svg></button>`;
      chip.querySelector('button').addEventListener('click', () => removeSource(src.id));
      return chip;
    });
    els.sources.replaceChildren(...chips);
  }

  /* ── Sélection ─────────────────────────────────────────────────── */
  function onGridClick(e) {
    if (Date.now() < state.suppressClickUntil) return;
    const card = e.target.closest('.page-card');
    if (!card) return;
    const page = state.pages.find((p) => p.id === card.dataset.id);
    if (!page) return;

    const btn = e.target.closest('.card-btn');
    if (btn) {
      if (btn.dataset.act === 'rotate') rotate(page, card);
      else removePageIds([page.id]);
      return;
    }
    if (e.shiftKey && state.lastClickedId) selectRange(state.lastClickedId, page.id);
    else toggleSelect(page.id, card);
    state.lastClickedId = page.id;
  }

  function onGridKey(e) {
    if (e.key !== 'Enter' && e.key !== ' ') return;
    const card = e.target.closest('.page-card');
    if (!card || e.target !== card) return;
    e.preventDefault();
    toggleSelect(card.dataset.id, card);
    state.lastClickedId = card.dataset.id;
  }

  function toggleSelect(id, card) {
    setSelected(id, !state.selected.has(id), card);
    updateSelectionUI();
  }

  function setSelected(id, on, card) {
    if (on) state.selected.add(id); else state.selected.delete(id);
    card = card || els.grid.querySelector(`[data-id="${id}"]`);
    if (card) {
      card.classList.toggle('selected', on);
      card.setAttribute('aria-pressed', String(on));
    }
  }

  function selectRange(fromId, toId) {
    const a = state.pages.findIndex((p) => p.id === fromId);
    const b = state.pages.findIndex((p) => p.id === toId);
    if (a < 0 || b < 0) return;
    const [lo, hi] = a < b ? [a, b] : [b, a];
    for (let i = lo; i <= hi; i++) setSelected(state.pages[i].id, true);
    updateSelectionUI();
  }

  function setAllSelected(on) {
    for (const p of state.pages) setSelected(p.id, on);
    updateSelectionUI();
  }

  function updateSelectionUI() {
    const n = state.selected.size;
    els.count.textContent =
      n === 0 ? 'Aucune page sélectionnée'
      : n === 1 ? '1 page sélectionnée'
      : `${n} pages sélectionnées`;
    const off = n === 0 || state.busy;
    els.rotateSel.disabled = off;
    els.extract.disabled = off;
    els.deleteSel.disabled = off;
  }

  /* ── Rotation ──────────────────────────────────────────────────── */
  function rotate(page, card) {
    page.extraRot = (page.extraRot + 90) % 360;
    renderThumbInto(card, page);
  }

  function rotateSelection() {
    for (const id of state.selected) {
      const page = state.pages.find((p) => p.id === id);
      const card = els.grid.querySelector(`[data-id="${id}"]`);
      if (page && card) rotate(page, card);
    }
  }

  /* ── Suppression ───────────────────────────────────────────────── */
  function removePageIds(ids) {
    const gone = new Set(ids);
    state.pages = state.pages.filter((p) => !gone.has(p.id));
    for (const id of gone) {
      state.selected.delete(id);
      const card = els.grid.querySelector(`[data-id="${id}"]`);
      if (card) {
        PdfEngine.unobserve(card.querySelector('.thumb-box'));
        card.remove();
      }
    }
    pruneEmptySources();
    refreshChips();
    updateAll();
  }

  function removeSource(srcId) {
    removePageIds(state.pages.filter((p) => p.srcId === srcId).map((p) => p.id));
  }

  /** Libère les sources dont toutes les pages ont été retirées. */
  function pruneEmptySources() {
    for (const [id, src] of state.sources) {
      if (!state.pages.some((p) => p.srcId === id)) {
        try { src.doc.destroy(); } catch (_) { /* déjà libéré */ }
        state.sources.delete(id);
      }
    }
  }

  /* ── Réordonnancement ──────────────────────────────────────────── */
  function syncOrderFromDom() {
    const order = new Map(
      [...els.grid.children].map((el, i) => [el.dataset.id, i]),
    );
    state.pages.sort((a, b) => order.get(a.id) - order.get(b.id));
  }

  /* ── Export ────────────────────────────────────────────────────── */
  function suggestName() {
    if (state.sources.size === 1) {
      const [src] = state.sources.values();
      return `${baseName(src.name)}-modifie.pdf`;
    }
    return 'document-organise.pdf';
  }

  function extractSelection() {
    const list = state.pages.filter((p) => state.selected.has(p.id));
    const name = state.sources.size === 1
      ? `${baseName([...state.sources.values()][0].name)}-extrait.pdf`
      : 'extraction.pdf';
    exportPdf(list, name);
  }

  /**
   * Assemble un nouveau PDF à partir d'une liste de pages (dans l'ordre
   * donné) et lance le téléchargement. Les pages sont copiées en une
   * passe par fichier source pour dédupliquer les ressources partagées.
   */
  async function exportPdf(list, filename) {
    if (!list.length || state.busy) return;
    setBusy(true);
    prog.start('Préparation…', true);
    try {
      const out = await PDFLib.PDFDocument.create();

      // Regroupe les pages par source, dans l'ordre d'apparition
      const bySrc = new Map();
      for (const p of list) {
        if (!bySrc.has(p.srcId)) bySrc.set(p.srcId, []);
        bySrc.get(p.srcId).push(p);
      }

      // Copie groupée : une seule ouverture pdf-lib et un seul copyPages
      // par fichier source
      const copied = new Map(); // id de page → PDFPage copiée
      let doneSrc = 0;
      for (const [srcId, items] of bySrc) {
        const src = state.sources.get(srcId);
        prog.set(doneSrc, bySrc.size, `Copie de ${src.name}`);
        const lib = await PdfEngine.openPdfLib(src.bytes, src.password);
        const pagesCopied = await out.copyPages(lib, items.map((p) => p.index));
        items.forEach((p, i) => copied.set(p.id, pagesCopied[i]));
        doneSrc++;
      }

      // Insertion dans l'ordre d'affichage + rotations demandées
      prog.start('Assemblage du fichier…', true);
      for (const p of list) {
        const pg = copied.get(p.id);
        if (p.extraRot) {
          const angle = ((pg.getRotation().angle + p.extraRot) % 360 + 360) % 360;
          pg.setRotation(PDFLib.degrees(angle));
        }
        out.addPage(pg);
      }

      const bytes = await out.save();
      downloadBlob(bytes, filename, 'application/pdf');
      toast(`« ${filename} » téléchargé (${list.length} page${list.length > 1 ? 's' : ''}, ${formatSize(bytes.length)}).`, 'success');
    } catch (e) {
      console.error(e);
      toast(`Échec de la génération : ${e.message}`, 'error');
    } finally {
      prog.done();
      setBusy(false);
    }
  }

  /* ── États globaux de l'onglet ─────────────────────────────────── */
  function setBusy(b) {
    state.busy = b;
    els.download.disabled = b || state.pages.length === 0;
    els.add.disabled = b;
    els.reset.disabled = b;
    updateSelectionUI();
  }

  function updateAll() {
    const empty = state.pages.length === 0;
    els.drop.hidden = !empty;
    els.workspace.hidden = empty;
    els.total.textContent = empty ? '' :
      `${state.pages.length} page${state.pages.length > 1 ? 's' : ''} · ` +
      `${state.sources.size} fichier${state.sources.size > 1 ? 's' : ''}`;
    els.download.disabled = state.busy || empty;
    updateSelectionUI();
  }

  function resetAll() {
    if (state.pages.length &&
        !confirm('Retirer tous les fichiers et toutes les pages de l\u2019organiseur ?')) return;
    for (const src of state.sources.values()) {
      try { src.doc.destroy(); } catch (_) { /* déjà libéré */ }
    }
    state.sources.clear();
    state.pages = [];
    state.selected.clear();
    state.lastClickedId = null;
    els.grid.replaceChildren();
    refreshChips();
    updateAll();
  }

  document.addEventListener('DOMContentLoaded', init);
})();
