/* ════════════════════════════════════════════════════════════════════
   tab-image-to-pdf.js — assemblage d'images en un document PDF.
   JPG et PNG sont intégrés tels quels (sans perte de qualité) ; les
   autres formats décodables par le navigateur (WebP, GIF, BMP, AVIF…)
   sont convertis en PNG via un canvas. Les photos JPG dont l'EXIF
   indique une rotation sont redressées avant intégration, car le
   format PDF ignore cette métadonnée.
   ════════════════════════════════════════════════════════════════════ */
(() => {
  'use strict';
  const { byId, uid, esc, toast, readFile, downloadBlob, baseName } = Utils;

  const PT_PER_MM = 72 / 25.4;
  const PAGE_SIZES = { a4: [595.28, 841.89], letter: [612, 792] }; // points

  const state = {
    /** @type {{id:string, file:File, url:string}[]} */
    items: [],
    busy: false,
  };

  const els = {};
  let prog;

  function init() {
    ['drop', 'file', 'workspace', 'grid', 'add', 'count', 'size', 'orient',
     'margin', 'reset', 'create', 'progress']
      .forEach((k) => { els[k] = byId(`i2p-${k}`); });
    prog = Utils.progress(els.progress);

    Utils.setupDropzone(els.drop, els.file, addFiles);
    els.add.addEventListener('click', () => els.file.click());
    els.reset.addEventListener('click', resetAll);
    els.create.addEventListener('click', createPdf);
    // L'orientation n'a de sens que pour une taille de page fixe
    els.size.addEventListener('change', () => {
      els.orient.disabled = els.size.value === 'auto';
    });

    Sortable.create(els.grid, {
      animation: 150,
      ghostClass: 'drag-ghost',
      chosenClass: 'drag-chosen',
      filter: '.card-btn',
      preventOnFilter: false,
    });

    App.registerTab('img2pdf', () => state.items.length > 0);
  }

  /* ── Import ────────────────────────────────────────────────────── */
  function addFiles(files) {
    const images = files.filter(Utils.isImage);
    if (!images.length) { toast('Déposez des images (JPG, PNG, WebP…).', 'error'); return; }
    if (images.length < files.length) toast('Les fichiers qui ne sont pas des images ont été ignorés.');

    for (const file of images) {
      const item = { id: uid(), file, url: URL.createObjectURL(file) };
      state.items.push(item);
      els.grid.appendChild(cardEl(item));
    }
    updateAll();
  }

  function cardEl(item) {
    const card = document.createElement('div');
    card.className = 'page-card';
    card.dataset.id = item.id;
    card.title = item.file.name;
    card.innerHTML =
      `<div class="card-actions">` +
        `<button type="button" class="card-btn" data-act="remove" title="Retirer l'image" aria-label="Retirer ${esc(item.file.name)}"><svg class="icon"><use href="#i-x"/></svg></button>` +
      `</div>` +
      `<div class="thumb-box"><img alt="${esc(item.file.name)}"></div>` +
      `<span class="pos" aria-hidden="true"></span>` +
      `<span class="card-name">${esc(item.file.name)}</span>`;

    const img = card.querySelector('img');
    img.src = item.url;
    img.onerror = () => {
      toast(`« ${item.file.name} » : format d'image non pris en charge par ce navigateur.`, 'error');
      removeItem(item.id);
    };
    card.querySelector('.card-btn').addEventListener('click', () => removeItem(item.id));
    return card;
  }

  function removeItem(id) {
    const item = state.items.find((x) => x.id === id);
    if (item) URL.revokeObjectURL(item.url);
    state.items = state.items.filter((x) => x.id !== id);
    const card = els.grid.querySelector(`[data-id="${id}"]`);
    if (card) card.remove();
    updateAll();
  }

  function updateAll() {
    const empty = state.items.length === 0;
    els.drop.hidden = !empty;
    els.workspace.hidden = empty;
    els.count.textContent = empty ? '' :
      `${state.items.length} image${state.items.length > 1 ? 's' : ''} — glissez les vignettes pour changer l'ordre des pages`;
    els.create.disabled = state.busy || empty;
    els.add.disabled = state.busy;
    els.reset.disabled = state.busy;
  }

  function resetAll() {
    if (state.items.length && !confirm('Retirer toutes les images ?')) return;
    for (const item of state.items) URL.revokeObjectURL(item.url);
    state.items = [];
    els.grid.replaceChildren();
    updateAll();
  }

  /* ── Lecture de l'orientation EXIF d'un JPEG ───────────────────── */
  /**
   * Renvoie la valeur EXIF « Orientation » (1 à 8), ou 1 si absente.
   * Parcourt les segments JPEG jusqu'à l'APP1/Exif, puis le premier IFD.
   */
  function jpegOrientation(bytes) {
    const v = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);
    if (v.byteLength < 4 || v.getUint16(0) !== 0xffd8) return 1;
    let o = 2;
    while (o + 4 <= v.byteLength) {
      const marker = v.getUint16(o);
      if ((marker & 0xff00) !== 0xff00) return 1;   // flux corrompu
      if (marker === 0xffda) return 1;               // début des données image
      const size = v.getUint16(o + 2);
      if (marker === 0xffe1 && o + 10 <= v.byteLength && v.getUint32(o + 4) === 0x45786966) {
        const tiff = o + 10;                          // après « Exif\0\0 »
        if (tiff + 8 > v.byteLength) return 1;
        const little = v.getUint16(tiff) === 0x4949;  // « II » = petit-boutiste
        const g16 = (p) => v.getUint16(p, little);
        const g32 = (p) => v.getUint32(p, little);
        const ifd = tiff + g32(tiff + 4);
        if (ifd + 2 > v.byteLength) return 1;
        const count = g16(ifd);
        for (let i = 0; i < count; i++) {
          const entry = ifd + 2 + i * 12;
          if (entry + 12 > v.byteLength) break;
          if (g16(entry) === 0x0112) return g16(entry + 8) || 1; // tag Orientation
        }
        return 1;
      }
      o += 2 + size;
    }
    return 1;
  }

  /* ── Décodage redressé (EXIF appliqué) vers un canvas ──────────── */
  async function drawOriented(file) {
    const canvas = document.createElement('canvas');
    const ctx = canvas.getContext('2d');
    try {
      // Voie moderne : le navigateur applique lui-même l'orientation EXIF
      const bitmap = await createImageBitmap(file, { imageOrientation: 'from-image' });
      canvas.width = bitmap.width;
      canvas.height = bitmap.height;
      ctx.drawImage(bitmap, 0, 0);
      bitmap.close();
    } catch (_) {
      // Repli : décodage via <img> (les navigateurs récents orientent aussi)
      const url = URL.createObjectURL(file);
      try {
        const img = await new Promise((resolve, reject) => {
          const el = new Image();
          el.onload = () => resolve(el);
          el.onerror = () => reject(new Error('Image illisible.'));
          el.src = url;
        });
        canvas.width = img.naturalWidth;
        canvas.height = img.naturalHeight;
        ctx.drawImage(img, 0, 0);
      } finally {
        URL.revokeObjectURL(url);
      }
    }
    return canvas;
  }

  /** canvas.toBlob sous forme de promesse. */
  function canvasToBlob(canvas, mime, quality) {
    return new Promise((resolve, reject) => {
      canvas.toBlob(
        (blob) => blob ? resolve(blob) : reject(new Error('Encodage de l\u2019image impossible.')),
        mime, quality,
      );
    });
  }

  /**
   * Prépare une image pour pdf-lib : intégration directe quand c'est
   * possible (JPG/PNG), sinon passage par un canvas.
   * @returns {Promise<{embedded:any, w:number, h:number}>} dimensions en pixels.
   */
  async function prepareImage(out, file) {
    const bytes = await readFile(file);
    const isJpeg = file.type === 'image/jpeg' || /\.jpe?g$/i.test(file.name);
    const isPng = file.type === 'image/png' || /\.png$/i.test(file.name);

    if (isJpeg) {
      if (jpegOrientation(bytes) <= 1) {
        const embedded = await out.embedJpg(bytes);
        return { embedded, w: embedded.width, h: embedded.height };
      }
      // Photo tournée : on la redresse, puis on ré-encode en JPG
      const canvas = await drawOriented(file);
      const blob = await canvasToBlob(canvas, 'image/jpeg', 0.92);
      const embedded = await out.embedJpg(new Uint8Array(await blob.arrayBuffer()));
      return { embedded, w: canvas.width, h: canvas.height };
    }
    if (isPng) {
      const embedded = await out.embedPng(bytes);
      return { embedded, w: embedded.width, h: embedded.height };
    }
    // Autres formats : conversion en PNG (conserve la transparence)
    const canvas = await drawOriented(file);
    const blob = await canvasToBlob(canvas, 'image/png');
    const embedded = await out.embedPng(new Uint8Array(await blob.arrayBuffer()));
    return { embedded, w: canvas.width, h: canvas.height };
  }

  /* ── Création du PDF ───────────────────────────────────────────── */
  async function createPdf() {
    if (!state.items.length || state.busy) return;
    setBusy(true);
    prog.start('Préparation…', true);
    try {
      // L'ordre du DOM (après glisser-déposer) fait foi
      const byIdMap = new Map(state.items.map((x) => [x.id, x]));
      const ordered = [...els.grid.children]
        .map((el) => byIdMap.get(el.dataset.id))
        .filter(Boolean);

      const sizeMode = els.size.value;              // 'auto' | 'a4' | 'letter'
      const orientMode = els.orient.value;          // 'auto' | 'portrait' | 'landscape'
      const margin = Number(els.margin.value) * PT_PER_MM;

      const out = await PDFLib.PDFDocument.create();
      for (let i = 0; i < ordered.length; i++) {
        const item = ordered[i];
        prog.set(i, ordered.length, `${i + 1} / ${ordered.length} — ${item.file.name}`);
        const { embedded, w, h } = await prepareImage(out, item.file);

        // Dimensions de la page (1 px est traité comme 1 pt, soit 72 ppp)
        let pw, ph;
        if (sizeMode === 'auto') {
          pw = w + 2 * margin;
          ph = h + 2 * margin;
        } else {
          [pw, ph] = PAGE_SIZES[sizeMode];
          const landscape = orientMode === 'auto' ? w > h : orientMode === 'landscape';
          if (landscape) [pw, ph] = [ph, pw];
        }

        // Ajustement « contain » dans la zone utile, image centrée
        const availW = pw - 2 * margin;
        const availH = ph - 2 * margin;
        const s = Math.min(availW / w, availH / h);
        const dw = w * s;
        const dh = h * s;

        const page = out.addPage([pw, ph]);
        page.drawImage(embedded, {
          x: (pw - dw) / 2,
          y: (ph - dh) / 2,
          width: dw,
          height: dh,
        });
      }

      prog.start('Assemblage du fichier…', true);
      const bytes = await out.save();
      const name = ordered.length === 1
        ? `${baseName(ordered[0].file.name)}.pdf`
        : 'images.pdf';
      downloadBlob(bytes, name, 'application/pdf');
      toast(`« ${name} » téléchargé (${ordered.length} page${ordered.length > 1 ? 's' : ''}).`, 'success');
    } catch (e) {
      console.error(e);
      toast(`Échec de la création du PDF : ${e.message}`, 'error');
    } finally {
      prog.done();
      setBusy(false);
    }
  }

  function setBusy(b) {
    state.busy = b;
    updateAll();
  }

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