/**
 * Non-side-effect class export for `<pagination-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/pagination`
 * (which imports this file + calls `defineIfFree()`).
 *
 * @see ../../USAGE.md#registration--auto-vs-explicit
 */

/**
 * <pagination-ui page="1" total="10" siblings="1"></pagination-ui>
 *
 * Page navigation with prev/next buttons, page numbers, and ellipsis.
 * Shows: first, ..., siblings around current, ..., last.
 *
 * Events:
 *   page-change — { detail: { page } }
 */

import { UIElement } from '../../core/element.js';

export class UIPagination extends UIElement {
  static properties = {
    page:     { type: Number, default: 1, reflect: true },
    total:    { type: Number, default: 1, reflect: true },
    siblings: { type: Number, default: 1, reflect: true },
    variant:  { type: String, default: 'default', reflect: true },
    // Threads through to every nested <button-ui size=…> so pagination
    // honors the universal [size] system (sm=24 / md=30 / lg=36 px with
    // density modifier). Default `md` matches <button-ui>'s default —
    // pagination is a button-ui composite, so the canonical size is the
    // same. Authors who want a denser numbered row pass [size="sm"].
    size:     { type: String, default: 'md', reflect: true },
  };

  // Phosphor icons stamped by this primitive (prev/next carets inside
  // the nested <button-ui>). Audited by check-required-icons.mjs.
  static requiredIcons = ['caret-left', 'caret-right'];

  static template = () => null;

  #nav = null;
  #bound = false;

  connected() {
    if (!this.#nav) {
      this.#nav = document.createElement('nav');
      this.#nav.setAttribute('slot', 'nav');
      this.#nav.setAttribute('aria-label', 'Pagination');
      this.appendChild(this.#nav);
    }

    if (!this.#bound) {
      this.#bound = true;
      // `press` is the canonical button-ui event — fires only when not
      // disabled (button-ui stops native click propagation on disabled
      // state), so we get the right gating for free.
      this.#nav.addEventListener('press', this.#onPress);
    }
  }

  disconnected() {
    this.#nav?.removeEventListener('press', this.#onPress);
    this.#nav = null;
    this.#bound = false;
  }

  render() {
    if (!this.#nav) return;

    const page = Math.max(1, Math.min(this.page, this.total));
    const total = Math.max(1, this.total);
    const siblings = Math.max(0, this.siblings);

    const range = this.#buildRange(page, total, siblings);

    this.reconcile(
      this.#nav,
      range,
      (item) => item.key,
      (item, i, existing) => {
        if (existing) {
          this.#updateItem(existing, item, page);
          return existing;
        }
        return this.#createItem(item, page);
      },
    );
  }

  #buildRange(page, total, siblings) {
    const items = [];
    items.push({ key: 'prev', type: 'prev', value: page - 1 });

    // W = constant compact-mode width (visible page cells, incl ellipses).
    //   = 2 bookends + (2*siblings + 1 sibling window) + 2 ellipsis slots
    // siblings=1 → W=7; siblings=2 → W=9.
    // Holding W invariant across page positions prevents the layout from
    // jumping when the current page advances by one — fixes the wobble
    // where page=3 showed 6 cells but page=4 showed 7.
    const W = 2 * siblings + 5;

    if (total <= W) {
      // Small total — show every page, never ellipsis. No need to compact
      // when compacting wouldn't save horizontal slots.
      for (let i = 1; i <= total; i++) {
        items.push({ key: `page-${i}`, type: 'page', value: i });
      }
    } else {
      // Compact mode. Three layouts, each yielding exactly W cells so the
      // row width stays constant under any page advance.
      const nearStart = page <= siblings + 3;
      const nearEnd = page >= total - siblings - 2;

      if (nearStart) {
        // 1..(W-2), ellipsis, total. The left window expands rightward to
        // fill the slot a left ellipsis would have used.
        const leftEnd = W - 2;
        for (let i = 1; i <= leftEnd; i++) {
          items.push({ key: `page-${i}`, type: 'page', value: i });
        }
        items.push({ key: 'ellipsis-end', type: 'ellipsis' });
        items.push({ key: `page-${total}`, type: 'page', value: total });
      } else if (nearEnd) {
        // 1, ellipsis, (total-W+3)..total. Right window expands leftward.
        items.push({ key: 'page-1', type: 'page', value: 1 });
        items.push({ key: 'ellipsis-start', type: 'ellipsis' });
        const rightStart = total - W + 3;
        for (let i = rightStart; i <= total; i++) {
          items.push({ key: `page-${i}`, type: 'page', value: i });
        }
      } else {
        // Middle. 1, ellipsis, current±siblings, ellipsis, total.
        items.push({ key: 'page-1', type: 'page', value: 1 });
        items.push({ key: 'ellipsis-start', type: 'ellipsis' });
        for (let i = page - siblings; i <= page + siblings; i++) {
          items.push({ key: `page-${i}`, type: 'page', value: i });
        }
        items.push({ key: 'ellipsis-end', type: 'ellipsis' });
        items.push({ key: `page-${total}`, type: 'page', value: total });
      }
    }

    items.push({ key: 'next', type: 'next', value: page + 1 });
    return items;
  }

  // The non-active button variant \u2014 `ghost` for default mode (chrome-less
  // hover), `outline` for variant="button" mode (1\u00D71 bordered cells).
  // Active items always use `primary` so the active-state token chain
  // comes from button-ui's primary surface matrix (no re-implementing
  // a separate accent fill at the pagination tier).
  #restVariant() {
    return this.variant === 'button' ? 'outline' : 'ghost';
  }

  #createItem(item, page) {
    if (item.type === 'ellipsis') {
      const span = document.createElement('span');
      span.setAttribute('data-ellipsis', '');
      span.textContent = '\u2026';
      return span;
    }

    const btn = document.createElement('button-ui');
    btn.setAttribute('size', this.size);

    if (item.type === 'prev') {
      btn.setAttribute('data-prev', '');
      btn.setAttribute('icon', 'caret-left');
      btn.setAttribute('aria-label', 'Previous page');
    } else if (item.type === 'next') {
      btn.setAttribute('data-next', '');
      btn.setAttribute('icon', 'caret-right');
      btn.setAttribute('aria-label', 'Next page');
    } else {
      btn.setAttribute('data-page', '');
      btn.dataset.value = String(item.value);
      btn.setAttribute('text', String(item.value));
      btn.setAttribute('aria-label', `Page ${item.value}`);
    }

    this.#updateItem(btn, item, page);
    return btn;
  }

  #updateItem(el, item, page) {
    if (item.type === 'ellipsis') return;

    // Keep size in sync \u2014 the host's [size] may change between renders.
    el.setAttribute('size', this.size);

    if (item.type === 'prev') {
      el.setAttribute('variant', this.#restVariant());
      if (page <= 1) { el.setAttribute('disabled', ''); el.setAttribute('tabindex', '-1'); }
      else { el.removeAttribute('disabled'); el.setAttribute('tabindex', '0'); }
    } else if (item.type === 'next') {
      el.setAttribute('variant', this.#restVariant());
      if (page >= this.total) { el.setAttribute('disabled', ''); el.setAttribute('tabindex', '-1'); }
      else { el.removeAttribute('disabled'); el.setAttribute('tabindex', '0'); }
    } else {
      el.dataset.value = String(item.value);
      el.setAttribute('text', String(item.value));
      el.setAttribute('aria-label', `Page ${item.value}`);
      if (item.value === page) {
        // Active page reads as `variant="primary"` so the filled-accent
        // state comes from button-ui's primary surface matrix (the
        // canonical token chain) \u2014 not a pagination-tier re-impl.
        el.setAttribute('variant', 'primary');
        el.setAttribute('data-active', '');
        el.setAttribute('aria-current', 'page');
      } else {
        el.setAttribute('variant', this.#restVariant());
        el.removeAttribute('data-active');
        el.removeAttribute('aria-current');
      }
    }
  }

  #onPress = (e) => {
    // `press` fires on the <button-ui> itself, which is exactly the
    // element carrying the data-prev / data-next / data-page marker.
    const btn = e.target.closest('[data-prev], [data-next], [data-page]');
    if (!btn || btn.hasAttribute('disabled') || !this.#nav.contains(btn)) return;

    let newPage;

    if (btn.hasAttribute('data-prev')) {
      newPage = this.page - 1;
    } else if (btn.hasAttribute('data-next')) {
      newPage = this.page + 1;
    } else if (btn.hasAttribute('data-page')) {
      newPage = Number(btn.dataset.value);
    }

    if (newPage == null || newPage < 1 || newPage > this.total || newPage === this.page) return;

    this.page = newPage;
    this.dispatchEvent(new CustomEvent('page-change', {
      bubbles: true,
      detail: { page: this.page },
    }));
  };
}
