/* ════════════════════════════════════════════════════════════════════
   utils.js — utilitaires partagés par tous les onglets.
   Exposé sous window.Utils (scripts classiques : l'application doit
   fonctionner en ouvrant simplement index.html, donc sans modules ES,
   bloqués en file://).
   ════════════════════════════════════════════════════════════════════ */
window.Utils = (() => {
  'use strict';

  /** Raccourci getElementById. */
  const byId = (id) => document.getElementById(id);

  /** Échappe une chaîne pour insertion sûre dans du HTML (noms de fichiers…). */
  const esc = (s) => String(s)
    .replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;')
    .replace(/"/g, '&quot;').replace(/'/g, '&#39;');

  /** Identifiant unique croissant (clés internes des pages/fichiers). */
  let counter = 0;
  const uid = () => 'k' + (++counter);

  /** Taille lisible en français : 1234567 → « 1,2 Mo ». */
  function formatSize(bytes) {
    const units = ['o', 'Ko', 'Mo', 'Go'];
    let v = bytes, i = 0;
    while (v >= 1000 && i < units.length - 1) { v /= 1000; i++; }
    const num = v.toLocaleString('fr-FR', { maximumFractionDigits: v >= 100 || i === 0 ? 0 : 1 });
    return `${num} ${units[i]}`;
  }

  /** Numéro sur autant de chiffres que le total : padNum(3, 120) → « 003 ». */
  const padNum = (n, total) => String(n).padStart(String(total).length, '0');

  /** Nom de fichier sans son extension. */
  const baseName = (name) => name.replace(/\.[^.]+$/, '');

  const isPdf = (f) => f.type === 'application/pdf' || /\.pdf$/i.test(f.name);
  const isImage = (f) => f.type.startsWith('image/');

  /** Lit un File en Uint8Array. */
  function readFile(file) {
    return new Promise((resolve, reject) => {
      const r = new FileReader();
      r.onload = () => resolve(new Uint8Array(r.result));
      r.onerror = () => reject(new Error(`Lecture impossible : ${file.name}`));
      r.readAsArrayBuffer(file);
    });
  }

  /**
   * Déclenche le téléchargement d'un résultat.
   * @param {Uint8Array|Blob} data
   * @param {string} filename
   * @param {string} [mime]
   */
  function downloadBlob(data, filename, mime = 'application/octet-stream') {
    const blob = data instanceof Blob ? data : new Blob([data], { type: mime });
    const url = URL.createObjectURL(blob);
    const a = document.createElement('a');
    a.href = url;
    a.download = filename;
    document.body.appendChild(a);
    a.click();
    a.remove();
    setTimeout(() => URL.revokeObjectURL(url), 4000);
  }

  /* ── Notifications ─────────────────────────────────────────────── */
  const TOAST_ICONS = { info: 'i-file', success: 'i-check', error: 'i-alert' };

  /** Affiche une notification éphémère. @param {'info'|'success'|'error'} type */
  function toast(message, type = 'info') {
    const el = document.createElement('div');
    el.className = `toast ${type}`;
    el.innerHTML = `<svg class="icon"><use href="#${TOAST_ICONS[type]}"/></svg><span></span>`;
    el.lastElementChild.textContent = message;
    byId('toasts').appendChild(el);
    setTimeout(() => el.remove(), type === 'error' ? 6000 : 4000);
  }

  /* ── Zones de dépôt ────────────────────────────────────────────── */
  /**
   * Relie une zone de dépôt à un <input type="file"> : clic, clavier,
   * glisser-déposer. `onFiles` reçoit un tableau de File.
   */
  function setupDropzone(zone, input, onFiles) {
    zone.addEventListener('click', () => input.click());
    zone.addEventListener('keydown', (e) => {
      if (e.key === 'Enter' || e.key === ' ') { e.preventDefault(); input.click(); }
    });
    input.addEventListener('change', () => {
      if (input.files.length) onFiles([...input.files]);
      input.value = ''; // permet de resélectionner le même fichier
    });
    // Compteur dragenter/dragleave : évite le clignotement au survol des enfants
    let depth = 0;
    zone.addEventListener('dragenter', (e) => { e.preventDefault(); depth++; zone.classList.add('dragover'); });
    zone.addEventListener('dragover', (e) => e.preventDefault());
    zone.addEventListener('dragleave', () => { if (--depth <= 0) { depth = 0; zone.classList.remove('dragover'); } });
    zone.addEventListener('drop', (e) => {
      e.preventDefault();
      depth = 0;
      zone.classList.remove('dragover');
      const files = [...e.dataTransfer.files];
      if (files.length) onFiles(files);
    });
  }

  /* ── Barre de progression ──────────────────────────────────────── */
  /**
   * Pilote une barre de progression (.progress).
   * `start(label, indeterminate)` l'affiche, `set(fait, total, label?)`
   * la met à jour, `done()` la masque.
   */
  function progress(root) {
    const fill = root.querySelector('.progress-fill');
    const label = root.querySelector('.progress-label');
    return {
      start(text = '', indeterminate = false) {
        root.hidden = false;
        root.classList.toggle('indeterminate', indeterminate);
        fill.style.width = indeterminate ? '' : '0%';
        label.textContent = text;
      },
      set(done, total, text) {
        root.classList.remove('indeterminate');
        fill.style.width = `${Math.round((done / total) * 100)}%`;
        label.textContent = text !== undefined ? text : `${done} / ${total}`;
      },
      done() {
        root.hidden = true;
        fill.style.width = '0%';
      },
    };
  }

  /* ── Modale « mot de passe » ───────────────────────────────────── */
  let pwChain = Promise.resolve(null); // sérialise les demandes successives

  /**
   * Demande un mot de passe à l'utilisateur (fichiers PDF protégés).
   * @param {{fileName:string, error?:boolean}} opts — `error` affiche
   *   « mot de passe incorrect » lors d'une nouvelle tentative.
   * @returns {Promise<string|null>} le mot de passe, ou null si annulé.
   */
  function promptPassword({ fileName, error = false }) {
    const ask = () => new Promise((resolve) => {
      const overlay = byId('pw-modal');
      const form = byId('pw-form');
      const input = byId('pw-input');
      byId('pw-file').textContent = fileName;
      byId('pw-error').hidden = !error;
      input.value = '';
      overlay.hidden = false;
      input.focus();

      const close = (value) => {
        overlay.hidden = true;
        form.removeEventListener('submit', onSubmit);
        byId('pw-cancel').removeEventListener('click', onCancel);
        document.removeEventListener('keydown', onKey);
        resolve(value);
      };
      const onSubmit = (e) => { e.preventDefault(); close(input.value); };
      const onCancel = () => close(null);
      const onKey = (e) => { if (e.key === 'Escape') close(null); };

      form.addEventListener('submit', onSubmit);
      byId('pw-cancel').addEventListener('click', onCancel);
      document.addEventListener('keydown', onKey);
    });
    pwChain = pwChain.then(ask, ask);
    return pwChain;
  }

  /* ── Œil afficher/masquer sur un champ mot de passe ────────────── */
  function bindEyeToggle(button, input) {
    button.addEventListener('click', () => {
      const show = input.type === 'password';
      input.type = show ? 'text' : 'password';
      button.setAttribute('aria-label', show ? 'Masquer le mot de passe' : 'Afficher le mot de passe');
      button.innerHTML = `<svg class="icon"><use href="#${show ? 'i-eye-off' : 'i-eye'}"/></svg>`;
    });
  }

  return {
    byId, esc, uid, formatSize, padNum, baseName, isPdf, isImage,
    readFile, downloadBlob, toast, setupDropzone, progress,
    promptPassword, bindEyeToggle,
  };
})();
