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

/**
 * <pane-ui> — Collapsible, resizable panel.
 *
 * Structure:
 *   <pane-ui>
 *     <header>Pane Title</header>
 *     <section>
 *       <header>Section Title</header>
 *       <div data-col>
 *         ...content...
 *       </div>
 *     </section>
 *     <section>...</section>
 *     <footer>...</footer>
 *   </pane-ui>
 *
 * Attributes:
 *   collapsed    — boolean, hides content and footer
 *   resizable    — boolean, enables drag-to-resize (width)
 *   min-width    — minimum width when resizing (default: 200)
 *   max-width    — maximum width when resizing (default: 600)
 *   side         — 'leading' | 'trailing' | '' (default). When set, the
 *                  pane is treated as a horizontal sibling: the default
 *                  four-sided border is suppressed, only the inner-edge
 *                  border is drawn, the resize grabber moves to that
 *                  inner edge, and the resize-drag direction flips so
 *                  trailing panes grow when dragged leftward.
 *
 * JS API:
 *   pane.collapsed = true/false
 *   pane.toggle()
 */

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

export class UIPane extends UIElement {
  // §154 (v0.5.3): Phosphor icons this primitive auto-stamps (without
  // consumer markup). Aggregated by installIconLoadersForRegistered()
  // across all defined elements. Audited by check-required-icons.mjs
  // (slot 11). Per FEEDBACK-06 §4 + FEEDBACK-07 §4.
  static requiredIcons = ['caret-right'];

  static properties = {
    collapsed:  { type: Boolean, default: false, reflect: true },
    resizable:  { type: Boolean, default: false, reflect: true },
    minWidth:   { type: Number,  default: 200, attribute: 'min-width', reflect: true },
    maxWidth:   { type: Number,  default: 9999, attribute: 'max-width', reflect: true },
    side:       { type: String,  default: '', reflect: true },
  };

  static template = () => null;

  #resizeEl = null;
  #dragging = false;
  #startX = 0;
  #startW = 0;
  #pointerId = null;
  #prevDocCursor = '';
  #bound = false;

  connected() {
    if (!this.#bound) {
      this.#bound = true;

      // Click on pane header toggles collapse
      this.addEventListener('click', this.#onClick);
      this.addEventListener('keydown', this.#onKey);
    }

    // Stamp resize handle if resizable and not present
    if (this.resizable && !this.querySelector('[slot="resize"]')) {
      this.#resizeEl = document.createElement('div');
      this.#resizeEl.setAttribute('slot', 'resize');
      this.appendChild(this.#resizeEl);
    }

    if (this.#resizeEl || this.resizable) {
      this.#resizeEl = this.#resizeEl || this.querySelector('[slot="resize"]');
      this.#resizeEl?.addEventListener('pointerdown', this.#onResizeDown);
    }
  }

  render() {
    const header = this.querySelector(':scope > header');
    if (header) {
      header.setAttribute('role', 'button');
      header.setAttribute('tabindex', '0');
      header.setAttribute('aria-expanded', String(!this.collapsed));

      // Stamp the caret icon if not present (adopt-or-stamp: a declarative
      // [slot="caret"] child is honored, else we stamp the default).
      if (!header.querySelector('[slot="caret"]')) {
        const caret = document.createElement('icon-ui');
        caret.setAttribute('slot', 'caret');
        caret.setAttribute('name', 'caret-right');
        header.append(caret);
      }
    }
  }

  toggle() {
    this.collapsed = !this.collapsed;
    this.dispatchEvent(new Event('toggle', { bubbles: true }));
  }

  #onClick = (e) => {
    const header = this.querySelector(':scope > header');
    if (header && (e.target === header || header.contains(e.target))) {
      this.toggle();
    }
  };

  #onKey = (e) => {
    if (e.target !== this.querySelector(':scope > header')) return;
    if (e.key === 'Enter' || e.key === ' ') {
      e.preventDefault();
      this.toggle();
    }
  };

  // ── Resize ──
  //
  // Pointer capture + handle-level listeners (not document-level): keeps
  // pointer events flowing to the grabber even when the cursor races past
  // it, and works under touch. Cursor is locked at the documentElement
  // level so it stays `col-resize` across the whole viewport during drag,
  // not just over the 4px handle. CSS bypasses any width transition while
  // `[data-resizing]` is set, so resizes track the pointer 1:1.

  #onResizeDown = (e) => {
    if (!e.isPrimary) return;
    e.preventDefault();
    this.#dragging = true;
    this.#startX = e.clientX;
    this.#startW = this.getBoundingClientRect().width;
    this.#pointerId = e.pointerId;
    this.setAttribute('data-resizing', '');

    try { this.#resizeEl.setPointerCapture(e.pointerId); } catch {}
    this.#resizeEl.addEventListener('pointermove', this.#onResizeMove);
    this.#resizeEl.addEventListener('pointerup', this.#onResizeUp, { once: true });
    this.#resizeEl.addEventListener('pointercancel', this.#onResizeUp, { once: true });

    this.#prevDocCursor = document.documentElement.style.cursor;
    document.documentElement.style.cursor = 'col-resize';
  };

  #onResizeMove = (e) => {
    if (!this.#dragging) return;
    // For a trailing pane the grabber sits on the LEFT edge but the pane
    // is anchored on the RIGHT side of its flex row — dragging leftward
    // should GROW the pane (grabber moves further left = more width),
    // not shrink it. Negate dx in that case.
    const sign = this.side === 'trailing' ? -1 : 1;
    const dx = (e.clientX - this.#startX) * sign;
    const w = Math.max(this.minWidth, Math.min(this.maxWidth, this.#startW + dx));
    this.style.width = `${w}px`;
  };

  #onResizeUp = () => {
    if (!this.#dragging) return;
    this.#dragging = false;
    this.removeAttribute('data-resizing');
    try { this.#resizeEl?.releasePointerCapture(this.#pointerId); } catch {}
    this.#pointerId = null;
    this.#resizeEl?.removeEventListener('pointermove', this.#onResizeMove);
    document.documentElement.style.cursor = this.#prevDocCursor;
  };

  disconnected() {
    this.removeEventListener('click', this.#onClick);
    this.removeEventListener('keydown', this.#onKey);
    this.#resizeEl?.removeEventListener('pointerdown', this.#onResizeDown);
    this.#resizeEl?.removeEventListener('pointermove', this.#onResizeMove);
    if (this.#dragging) {
      document.documentElement.style.cursor = this.#prevDocCursor;
    }
    this.#bound = false;
  }
}
