/**
 * Non-side-effect class export for `<upload-ui>`.
 *
 * Importing this file gives you the class(es) without auto-registering the tag.
 * Useful for test isolation, subclassing with tag-name override, or selective
 * composition.
 *
 * The auto-register path stays at `@adia-ai/web-components/components/upload`
 * (which imports this file + calls `defineIfFree()`).
 *
 * @see ../../USAGE.md#registration--auto-vs-explicit
 */

/**
 * <upload-ui> — File upload with drag-and-drop.
 * Uses showOpenFilePicker (modern) with <input type="file"> fallback.
 * Form-associated via UIFormElement + ElementInternals.
 */

import { UIFormElement } from '../../core/form.js';

export class UIUpload extends UIFormElement {
  static labelDeprecated = false;  // §170 (v0.5.4): label is first-class per upload.yaml
  static properties = {
    ...UIFormElement.properties,
    label:    { type: String,  default: '', reflect: true },
    accept:   { type: String,  default: '', reflect: true },
    multiple: { type: Boolean, default: false, reflect: true },
  };

  static template = () => null;

  #files = [];
  #dropzoneEl = null;

  connected() {
    super.connected();

    if (!this.querySelector('[data-dropzone]')) {
      this.innerHTML = `
        <div data-dropzone tabindex="0" role="button">Drop files here or click to browse</div>
        <span hint="${this.hint || ''}"></span>
        <div data-filelist></div>
      `;
    }

    this.#dropzoneEl = this.querySelector('[data-dropzone]');
    if (this.#dropzoneEl) {
      this.#dropzoneEl.addEventListener('click', this.#onClickZone);
      this.#dropzoneEl.addEventListener('keydown', this.#onKeyZone);
      this.#dropzoneEl.addEventListener('dragover', this.#onDragOver);
      this.#dropzoneEl.addEventListener('dragleave', this.#onDragLeave);
      this.#dropzoneEl.addEventListener('drop', this.#onDrop);
    }
  }

  render() {
    const filelist = this.querySelector('[data-filelist]');
    if (!this.#dropzoneEl || !filelist) return;

    // Update hint attribute
    const hintEl = this.querySelector('span[hint]');
    if (hintEl && this.hint) hintEl.setAttribute('hint', this.hint);

    // Render file list
    filelist.innerHTML = '';
    for (const f of this.#files) {
      const span = document.createElement('span');
      span.textContent = f.name;
      filelist.appendChild(span);
    }
  }

  get files() {
    return this.#files;
  }

  // ── File picking ──

  #onClickZone = () => {
    if (this.disabled) return;
    this.#pickFiles();
  };

  #onKeyZone = (e) => {
    if (this.disabled) return;
    if (e.key === 'Enter' || e.key === ' ') {
      e.preventDefault();
      this.#pickFiles();
    }
  };

  async #pickFiles() {
    try {
      if (window.showOpenFilePicker) {
        const types = this.#parseAcceptTypes();
        const opts = { multiple: this.multiple };
        if (types.length) opts.types = types;
        const handles = await window.showOpenFilePicker(opts);
        if (!this.isConnected) return;
        const files = await Promise.all(handles.map(h => h.getFile()));
        if (!this.isConnected) return;
        this.#setFiles(files);
      } else {
        this.#pickFallback();
      }
    } catch (err) {
      // User cancelled — AbortError is expected
      if (err.name !== 'AbortError') throw err;
    }
  }

  #pickFallback() {
    const input = document.createElement('input');
    input.type = 'file';
    if (this.accept) input.accept = this.accept;
    if (this.multiple) input.multiple = true;
    input.style.display = 'none';
    document.body.appendChild(input);
    input.addEventListener('change', () => {
      if (input.files?.length) {
        this.#setFiles([...input.files]);
      }
      input.remove();
    }, { once: true });
    // Handle cancel (input removed on blur after a delay)
    input.addEventListener('cancel', () => input.remove(), { once: true });
    input.click();
  }

  /**
   * Parse the accept attribute into showOpenFilePicker types array.
   * e.g. "image/*,.pdf" → [{ accept: { "image/*": [], "application/pdf": [".pdf"] } }]
   */
  #parseAcceptTypes() {
    if (!this.accept) return [];
    const accept = {};
    for (const part of this.accept.split(',')) {
      const token = part.trim();
      if (!token) continue;
      if (token.startsWith('.')) {
        // Extension — map to wildcard
        accept['application/octet-stream'] = accept['application/octet-stream'] || [];
        accept['application/octet-stream'].push(token);
      } else {
        accept[token] = accept[token] || [];
      }
    }
    return Object.keys(accept).length ? [{ accept }] : [];
  }

  // ── Drag-and-drop ──

  #onDragOver = (e) => {
    e.preventDefault();
    this.querySelector('[data-dropzone]')?.setAttribute('data-dragover', '');
  };

  #onDragLeave = () => {
    this.querySelector('[data-dropzone]')?.removeAttribute('data-dragover');
  };

  #onDrop = (e) => {
    e.preventDefault();
    this.querySelector('[data-dropzone]')?.removeAttribute('data-dragover');
    if (this.disabled) return;
    const dt = e.dataTransfer;
    if (dt?.files?.length) {
      this.#setFiles([...dt.files]);
    }
  };

  // ── Internal state ──

  #setFiles(fileArray) {
    this.#files = fileArray;

    // Set form value as FormData
    const fd = new FormData();
    for (const f of this.#files) {
      fd.append(this.name || 'file', f);
    }
    this.internals.setFormValue(fd);

    this.dispatchEvent(new CustomEvent('change', { bubbles: true, detail: { value: this.value, files: this.files } }));
    this.render();
  }

  disconnected() {
    super.disconnected();
    if (this.#dropzoneEl) {
      this.#dropzoneEl.removeEventListener('click', this.#onClickZone);
      this.#dropzoneEl.removeEventListener('keydown', this.#onKeyZone);
      this.#dropzoneEl.removeEventListener('dragover', this.#onDragOver);
      this.#dropzoneEl.removeEventListener('dragleave', this.#onDragLeave);
      this.#dropzoneEl.removeEventListener('drop', this.#onDrop);
    }
    this.#dropzoneEl = null;
  }
}
