/**
 * Token-select host component — combobox lookup + flat removable list.
 *
 * Reads a hidden <select multiple> as source of truth. Shows an inline typeahead
 * popover (no drawer, no grouping). Selected items render as a flat alphabetical
 * list below with a per-row remove button.
 */
import { resolveHost, warnOnce } from '../core/app-dom.js';

// Last-resort fallback labels for JS-rendered text. Normally the PHP helper
// ships translated strings via `data-label-*` attributes on the host — these
// defaults only surface if the host is mounted without server-rendered labels.
const LABEL_KEYS = {
  remove: 'token_select.remove',
  emptyState: 'token_select.empty_state_title',
  noMatches: 'token_select.no_matches',
};

const MAX_RESULTS = 50;

const readItems = (select) => {
  if (!(select instanceof HTMLSelectElement)) {
    return [];
  }
  return Array.from(select.options)
    .map((option) => ({
      id: option.value,
      key: option.dataset.tokenKey || '',
      label: option.dataset.tokenLabel || option.textContent || option.value,
      selected: option.selected === true,
    }))
    .filter((item) => item.id !== '');
};

export function initTokenSelect(root = document, options = {}) {
  const host = resolveHost(root);
  if (!(host instanceof HTMLElement)) {
    return { destroy: () => {} };
  }

  const hostEl = host.matches?.('[data-app-component="token-select"]')
    ? host
    : host.querySelector('[data-app-component="token-select"]');
  if (!(hostEl instanceof HTMLElement)) {
    return { destroy: () => {} };
  }

  if (hostEl.dataset.tokenSelectBound === '1') {
    return { destroy: () => {} };
  }
  hostEl.dataset.tokenSelectBound = '1';

  const select = hostEl.querySelector('select[data-token-select-hidden]');
  if (!(select instanceof HTMLSelectElement)) {
    warnOnce('UI_EL_MISSING', 'token-select hidden <select> missing', { module: 'token-select' });
    return { destroy: () => {} };
  }

  const isReadOnly = hostEl.dataset.readonly === '1' || select.disabled === true;
  const input = hostEl.querySelector('[data-token-select-input]');
  const listbox = hostEl.querySelector('[data-token-select-listbox]');
  const listContainer = hostEl.querySelector('[data-token-select-selected]');
  const countEl = hostEl.querySelector('[data-token-select-count]');
  const clearBtn = hostEl.querySelector('[data-token-select-clear]');

  if (!(listContainer instanceof HTMLElement)) {
    return { destroy: () => {} };
  }

  const labels = (options && typeof options === 'object' && options.labels) || {};
  const datasetLabels = {
    remove: hostEl.dataset.labelRemove,
    emptyState: hostEl.dataset.labelEmptyState,
    noMatches: hostEl.dataset.labelNoMatches,
  };
  const resolveLabel = (name) => {
    const fromDataset = datasetLabels[name];
    if (typeof fromDataset === 'string' && fromDataset.length > 0) {
      return fromDataset;
    }
    const override = labels[name];
    if (typeof override === 'string' && override.length > 0) {
      return override;
    }
    return LABEL_KEYS[name] || name;
  };

  const cleanupFns = [];
  const bind = (target, eventName, handler, opts) => {
    target.addEventListener(eventName, handler, opts);
    cleanupFns.push(() => target.removeEventListener(eventName, handler, opts));
  };

  let activeIndex = -1;
  let currentMatches = [];

  const fireChange = () => {
    select.dispatchEvent(new Event('change', { bubbles: true }));
  };

  const setSelected = (id, next) => {
    const option = select.querySelector(`option[value="${CSS.escape(String(id))}"]`);
    if (option instanceof HTMLOptionElement && option.selected !== next) {
      option.selected = next;
    }
  };

  const sortByLabel = (a, b) => String(a.label).localeCompare(String(b.label));

  const filterItems = (query) => {
    const items = readItems(select);
    const unselected = items.filter((it) => !it.selected).sort(sortByLabel);
    const q = String(query || '').trim().toLowerCase();
    if (q === '') {
      return unselected.slice(0, MAX_RESULTS);
    }
    return unselected
      .filter((it) => (it.label + ' ' + it.key).toLowerCase().indexOf(q) !== -1)
      .slice(0, MAX_RESULTS);
  };

  const buildListboxOption = (item, index) => {
    const li = document.createElement('li');
    li.className = 'app-token-select-listbox-option';
    li.setAttribute('role', 'option');
    li.setAttribute('data-token-select-option', item.id);
    li.id = hostEl.dataset.tokenSelectId + '-option-' + index;

    const labelSpan = document.createElement('span');
    labelSpan.className = 'app-token-select-listbox-label';
    labelSpan.textContent = item.label;
    li.appendChild(labelSpan);

    return li;
  };

  const renderListbox = () => {
    if (!(listbox instanceof HTMLElement)) {
      return;
    }
    const query = input instanceof HTMLInputElement ? input.value : '';
    currentMatches = filterItems(query);
    activeIndex = currentMatches.length > 0 ? 0 : -1;

    listbox.textContent = '';
    if (currentMatches.length === 0) {
      const emptyLi = document.createElement('li');
      emptyLi.className = 'app-empty-state app-token-select-listbox-empty';
      emptyLi.setAttribute('data-size', 'small');
      emptyLi.setAttribute('data-align', 'center');
      emptyLi.setAttribute('role', 'option');
      emptyLi.setAttribute('aria-disabled', 'true');
      const msg = document.createElement('p');
      msg.className = 'app-empty-state-message';
      msg.textContent = resolveLabel('noMatches');
      emptyLi.appendChild(msg);
      listbox.appendChild(emptyLi);
      return;
    }

    currentMatches.forEach((item, index) => {
      const option = buildListboxOption(item, index);
      if (index === activeIndex) {
        option.classList.add('is-active');
        option.setAttribute('aria-selected', 'true');
        if (input instanceof HTMLInputElement) {
          input.setAttribute('aria-activedescendant', option.id);
        }
      } else {
        option.setAttribute('aria-selected', 'false');
      }
      listbox.appendChild(option);
    });
  };

  const isListboxOpen = () => listbox instanceof HTMLElement && !listbox.hasAttribute('hidden');

  const openListbox = () => {
    if (!(listbox instanceof HTMLElement) || !(input instanceof HTMLInputElement)) {
      return;
    }
    renderListbox();
    listbox.removeAttribute('hidden');
    input.setAttribute('aria-expanded', 'true');
  };

  const closeListbox = () => {
    if (!(listbox instanceof HTMLElement) || !(input instanceof HTMLInputElement)) {
      return;
    }
    listbox.setAttribute('hidden', '');
    input.setAttribute('aria-expanded', 'false');
    input.removeAttribute('aria-activedescendant');
    activeIndex = -1;
  };

  const setActive = (next) => {
    if (!(listbox instanceof HTMLElement)) {
      return;
    }
    const opts = listbox.querySelectorAll('[data-token-select-option]');
    if (opts.length === 0) {
      return;
    }
    const normalized = ((next % opts.length) + opts.length) % opts.length;
    activeIndex = normalized;
    opts.forEach((opt, index) => {
      if (index === normalized) {
        opt.classList.add('is-active');
        opt.setAttribute('aria-selected', 'true');
        opt.scrollIntoView({ block: 'nearest' });
        if (input instanceof HTMLInputElement) {
          input.setAttribute('aria-activedescendant', opt.id);
        }
      } else {
        opt.classList.remove('is-active');
        opt.setAttribute('aria-selected', 'false');
      }
    });
  };

  const addById = (id) => {
    if (typeof id !== 'string' || id === '') {
      return;
    }
    setSelected(id, true);
    if (input instanceof HTMLInputElement) {
      input.value = '';
      input.focus();
    }
    fireChange();
  };

  const addActive = () => {
    if (activeIndex < 0 || activeIndex >= currentMatches.length) {
      return;
    }
    addById(String(currentMatches[activeIndex].id));
  };

  const removeById = (id) => {
    setSelected(id, false);
    fireChange();
  };

  const clearAll = () => {
    Array.from(select.options).forEach((opt) => {
      opt.selected = false;
    });
    fireChange();
  };

  const renderSelected = () => {
    const items = readItems(select).filter((it) => it.selected).sort(sortByLabel);
    listContainer.textContent = '';

    if (items.length === 0) {
      const emptyLi = document.createElement('li');
      emptyLi.className = 'app-empty-state app-token-select-empty';
      emptyLi.setAttribute('data-size', 'compact');
      emptyLi.setAttribute('data-align', 'center');
      emptyLi.setAttribute('data-token-select-empty', '');
      const msg = document.createElement('p');
      msg.className = 'app-empty-state-message';
      msg.textContent = resolveLabel('emptyState');
      emptyLi.appendChild(msg);
      listContainer.appendChild(emptyLi);
    } else {
      items.forEach((item) => {
        const li = document.createElement('li');
        li.className = 'app-token-select-row';
        li.setAttribute('data-token-select-row', String(item.id));

        const labelSpan = document.createElement('span');
        labelSpan.className = 'app-token-select-row-label';
        labelSpan.textContent = item.label;
        li.appendChild(labelSpan);

        if (!isReadOnly) {
          const removeBtn = document.createElement('button');
          removeBtn.type = 'button';
          removeBtn.className = 'app-icon-button app-token-select-row-remove';
          removeBtn.setAttribute('data-token-select-remove', String(item.id));
          const removeLabel = resolveLabel('remove');
          removeBtn.setAttribute('aria-label', removeLabel);
          removeBtn.setAttribute('data-tooltip', removeLabel);
          const icon = document.createElement('i');
          icon.className = 'bi bi-x-lg';
          icon.setAttribute('aria-hidden', 'true');
          removeBtn.appendChild(icon);
          li.appendChild(removeBtn);
        }
        listContainer.appendChild(li);
      });
    }

    if (countEl instanceof HTMLElement) {
      countEl.textContent = String(items.length);
    }
    if (clearBtn instanceof HTMLElement) {
      if (items.length > 0) {
        clearBtn.removeAttribute('hidden');
      } else {
        clearBtn.setAttribute('hidden', '');
      }
    }
  };

  const render = () => {
    renderSelected();
    if (isListboxOpen()) {
      renderListbox();
    }
  };

  // Combobox wiring (skipped in readonly mode where no input is rendered).
  if (!isReadOnly && input instanceof HTMLInputElement && listbox instanceof HTMLElement) {
    bind(input, 'focus', () => openListbox());

    bind(input, 'input', () => {
      if (!isListboxOpen()) {
        openListbox();
      } else {
        renderListbox();
      }
    });

    bind(input, 'keydown', (event) => {
      if (event.key === 'ArrowDown') {
        event.preventDefault();
        if (!isListboxOpen()) {
          openListbox();
        } else {
          setActive(activeIndex + 1);
        }
        return;
      }
      if (event.key === 'ArrowUp') {
        event.preventDefault();
        if (!isListboxOpen()) {
          openListbox();
          setActive(currentMatches.length - 1);
        } else {
          setActive(activeIndex - 1);
        }
        return;
      }
      if (event.key === 'Enter') {
        if (isListboxOpen() && activeIndex >= 0) {
          event.preventDefault();
          addActive();
        }
        return;
      }
      if (event.key === 'Escape') {
        if (isListboxOpen()) {
          event.preventDefault();
          closeListbox();
        }
        return;
      }
      if (event.key === 'Backspace' && input.value === '') {
        const selectedItems = readItems(select).filter((it) => it.selected).sort(sortByLabel);
        if (selectedItems.length === 0) {
          return;
        }
        event.preventDefault();
        removeById(String(selectedItems[selectedItems.length - 1].id));
      }
    });

    // Keep input focused when clicking a listbox option.
    bind(listbox, 'mousedown', (event) => {
      const target = event.target;
      if (target instanceof Element && target.closest('[data-token-select-option]')) {
        event.preventDefault();
      }
    });

    bind(listbox, 'click', (event) => {
      const target = event.target;
      if (!(target instanceof Element)) {
        return;
      }
      const option = target.closest('[data-token-select-option]');
      if (option instanceof HTMLElement) {
        event.preventDefault();
        addById(option.getAttribute('data-token-select-option') || '');
      }
    });

    bind(listbox, 'mousemove', (event) => {
      const target = event.target;
      if (!(target instanceof Element)) {
        return;
      }
      const option = target.closest('[data-token-select-option]');
      if (!(option instanceof HTMLElement)) {
        return;
      }
      const opts = Array.from(listbox.querySelectorAll('[data-token-select-option]'));
      const nextIndex = opts.indexOf(option);
      if (nextIndex !== -1 && nextIndex !== activeIndex) {
        setActive(nextIndex);
      }
    });

    // Close on outside pointer-down (fires before blur → keeps focus when clicking inside).
    const onDocumentPointerDown = (event) => {
      if (!isListboxOpen()) {
        return;
      }
      const target = event.target;
      if (target instanceof Node && !hostEl.contains(target)) {
        closeListbox();
      }
    };
    bind(document, 'mousedown', onDocumentPointerDown);
    bind(document, 'touchstart', onDocumentPointerDown, { passive: true });
  }

  // Host-level clicks for remove + clear (also works when only these exist, e.g. readonly has neither).
  bind(hostEl, 'click', (event) => {
    const target = event.target;
    if (!(target instanceof Element)) {
      return;
    }
    const removeBtn = target.closest('[data-token-select-remove]');
    if (removeBtn instanceof HTMLElement) {
      event.preventDefault();
      removeById(removeBtn.getAttribute('data-token-select-remove') || '');
      return;
    }
    if (target.closest('[data-token-select-clear]')) {
      event.preventDefault();
      clearAll();
    }
  });

  bind(select, 'change', render);

  render();

  const destroy = () => {
    cleanupFns.forEach((fn) => {
      try { fn(); } catch (_error) { /* swallow */ }
    });
    cleanupFns.length = 0;
    delete hostEl.dataset.tokenSelectBound;
  };

  return { destroy };
}

export default initTokenSelect;
