/**
 * File upload — card-based dropzone with drag-and-drop, current file preview, and pending file preview.
 *
 * Markup contract:
 *   <div data-app-component="file-upload" class="app-file-upload"
 *        data-current-src="…" data-current-label="…">       <!-- optional: existing server file -->
 *     <div class="app-file-upload-current">                  <!-- shown when has existing file -->
 *       <img class="app-file-upload-current-image" src="" alt="">
 *       <div class="app-file-upload-current-meta">
 *         <span class="app-file-upload-current-label"></span>
 *         <span class="app-file-upload-current-actions">
 *           <button type="button" class="app-file-upload-replace-button">Replace</button>
 *           <button type="submit" class="app-file-upload-delete-button"
 *                   formaction="…" formmethod="post"
 *                   data-confirm-message="…">Delete</button>
 *         </span>
 *       </div>
 *     </div>
 *     <label class="app-file-upload-dropzone">               <!-- shown when no file -->
 *       <input type="file" name="…" accept="…">
 *       <span class="app-file-upload-dropzone-icon">…</span>
 *       <span class="app-file-upload-dropzone-label">…</span>
 *       <span class="app-file-upload-dropzone-hint">…</span>
 *     </label>
 *     <div class="app-file-upload-preview">                  <!-- shown when new file selected -->
 *       <img class="app-file-upload-thumbnail" hidden>
 *       <span class="app-file-upload-file-icon" hidden>…</span>
 *       <span class="app-file-upload-meta">
 *         <span class="app-file-upload-filename"></span>
 *         <span class="app-file-upload-filesize"></span>
 *       </span>
 *       <button type="button" class="app-file-upload-clear">…</button>
 *     </div>
 *   </div>
 */

const MAX_PREVIEW_SIZE = 10 * 1024 * 1024; // 10 MB

const isImageFile = (file) => file.type.startsWith('image/');

const formatFileSize = (bytes) => {
  if (bytes < 1024) {
    return `${bytes} B`;
  }
  if (bytes < 1024 * 1024) {
    return `${(bytes / 1024).toFixed(1)} KB`;
  }
  return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
};

export function initFileUpload(root) {
  if (!(root instanceof HTMLElement)) {
    return { destroy: () => {} };
  }

  if (root.dataset.fileUploadBound === '1') {
    return { destroy: () => {} };
  }
  root.dataset.fileUploadBound = '1';

  const input = root.querySelector('input[type="file"]');
  if (!input) {
    return { destroy: () => {} };
  }

  const thumbnail = root.querySelector('.app-file-upload-thumbnail');
  const fileIcon = root.querySelector('.app-file-upload-file-icon');
  const filenameEl = root.querySelector('.app-file-upload-filename');
  const filesizeEl = root.querySelector('.app-file-upload-filesize');
  const clearButton = root.querySelector('.app-file-upload-clear');
  const replaceButton = root.querySelector('.app-file-upload-replace-button');
  const currentImage = root.querySelector('.app-file-upload-current-image');

  const cleanupFns = [];
  let dragCounter = 0;

  // Server rendered the initial state (img src + label + has-current class).
  // `data-current-src` is kept so clearFile() can restore the server file
  // after a user previewed a different selection.
  const currentSrc = (root.dataset.currentSrc || '').trim();

  // ── Show pending file (new selection) ──
  const showFile = (file) => {
    root.classList.add('has-file');
    root.classList.remove('has-current');

    if (filenameEl) {
      filenameEl.textContent = file.name;
    }
    if (filesizeEl) {
      filesizeEl.textContent = formatFileSize(file.size);
    }

    if (thumbnail) {
      thumbnail.hidden = true;
    }
    if (fileIcon) {
      fileIcon.hidden = true;
    }

    if (isImageFile(file) && file.size <= MAX_PREVIEW_SIZE && thumbnail) {
      const reader = new FileReader();
      reader.onload = (e) => {
        thumbnail.src = /** @type {string} */ (e.target?.result ?? '');
        thumbnail.hidden = false;
        if (fileIcon) {
          fileIcon.hidden = true;
        }
      };
      reader.onerror = () => {
        if (fileIcon) {
          fileIcon.hidden = false;
        }
      };
      reader.readAsDataURL(file);
    } else if (fileIcon) {
      fileIcon.hidden = false;
    }
  };

  // ── Clear pending file → go back to current (if exists) or dropzone ──
  const clearFile = () => {
    root.classList.remove('has-file');
    input.value = '';
    if (thumbnail) {
      thumbnail.src = '';
      thumbnail.hidden = true;
    }
    if (fileIcon) {
      fileIcon.hidden = true;
    }
    if (filenameEl) {
      filenameEl.textContent = '';
    }
    if (filesizeEl) {
      filesizeEl.textContent = '';
    }
    // Restore current state if a server file exists
    if (currentSrc) {
      root.classList.add('has-current');
    }
  };

  // ── Input change ──
  const onInputChange = () => {
    const file = input.files?.[0];
    if (file) {
      showFile(file);
    } else {
      clearFile();
    }
  };
  input.addEventListener('change', onInputChange);
  cleanupFns.push(() => input.removeEventListener('change', onInputChange));

  // ── Clear button ──
  if (clearButton) {
    const onClear = (e) => {
      e.preventDefault();
      e.stopPropagation();
      clearFile();
    };
    clearButton.addEventListener('click', onClear);
    cleanupFns.push(() => clearButton.removeEventListener('click', onClear));
  }

  // ── Replace button + click on current preview → open file dialog ──
  const openPicker = (e) => {
    e.preventDefault();
    e.stopPropagation();
    input.click();
  };
  if (replaceButton) {
    replaceButton.addEventListener('click', openPicker);
    cleanupFns.push(() => replaceButton.removeEventListener('click', openPicker));
  }
  if (currentImage) {
    currentImage.addEventListener('click', openPicker);
    cleanupFns.push(() => currentImage.removeEventListener('click', openPicker));
  }

  // ── Drag and drop ──
  const onDragEnter = (e) => {
    e.preventDefault();
    dragCounter++;
    root.classList.add('is-dragover');
  };

  const onDragOver = (e) => {
    e.preventDefault();
    e.dataTransfer.dropEffect = 'copy';
  };

  const onDragLeave = (e) => {
    e.preventDefault();
    dragCounter--;
    if (dragCounter <= 0) {
      dragCounter = 0;
      root.classList.remove('is-dragover');
    }
  };

  const onDrop = (e) => {
    e.preventDefault();
    dragCounter = 0;
    root.classList.remove('is-dragover');

    const file = e.dataTransfer?.files?.[0];
    if (!file) {
      return;
    }

    // Validate against accept attribute
    const accept = input.accept;
    if (accept && !fileMatchesAccept(file, accept)) {
      root.classList.add('is-rejected');
      setTimeout(() => root.classList.remove('is-rejected'), 600);
      return;
    }

    // Transfer file to the native input via DataTransfer
    const dt = new DataTransfer();
    dt.items.add(file);
    input.files = dt.files;
    showFile(file);
  };

  root.addEventListener('dragenter', onDragEnter);
  root.addEventListener('dragover', onDragOver);
  root.addEventListener('dragleave', onDragLeave);
  root.addEventListener('drop', onDrop);
  cleanupFns.push(() => {
    root.removeEventListener('dragenter', onDragEnter);
    root.removeEventListener('dragover', onDragOver);
    root.removeEventListener('dragleave', onDragLeave);
    root.removeEventListener('drop', onDrop);
  });

  const destroy = () => {
    cleanupFns.forEach((fn) => fn());
    cleanupFns.length = 0;
    delete root.dataset.fileUploadBound;
  };

  return { destroy };
}

/**
 * Check if a File matches an accept attribute value.
 * Supports MIME types (image/*), extensions (.csv), and exact types (image/png).
 */
function fileMatchesAccept(file, accept) {
  const tokens = accept.split(',').map((t) => t.trim().toLowerCase());
  const fileName = file.name.toLowerCase();
  const fileType = file.type.toLowerCase();

  return tokens.some((token) => {
    if (token.startsWith('.')) {
      return fileName.endsWith(token);
    }
    if (token.endsWith('/*')) {
      return fileType.startsWith(token.slice(0, -1));
    }
    return fileType === token;
  });
}
