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

/**
 * <range-input label="Width" value="63" min="0" max="100" step="1" suffix="rem"></range-input>
 *
 * A draggable numeric field — NOT a slider. The entire field is the scrubber surface.
 * Horizontal drag on the value changes it. No track, no thumb.
 *
 * Layout: [label]  [value] [suffix]
 *   - Label left-aligned, muted
 *   - Value right-aligned, bold, editable via horizontal drag
 *   - Suffix after value, muted
 *   - While dragging: accent highlight on the field
 */

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

export class UIRange extends UIFormElement {
  // Opt out of UIFormElement's per-control `label` deprecation warning.
  // range-ui's `label` is a first-class API: it renders as the in-track
  // caption next to the value (the entire field is the scrubber surface)
  // and the host gets `aria-label` wired from it for proper screen-reader
  // announcement. Same opt-out rationale as input-ui.
  static labelDeprecated = false;

  static properties = {
    ...UIFormElement.properties,
    /** value: Number — overrides UIFormElement.value (String); syncs as string on form submit. */
    value:  { type: Number,  default: 0,  reflect: true },
    min:    { type: Number,  default: 0,  reflect: true },
    max:    { type: Number,  default: 100, reflect: true },
    step:   { type: Number,  default: 1,  reflect: true },
    label:  { type: String,  default: '', reflect: true },
    suffix: { type: String,  default: '', reflect: true },
  };

  static template = () => null;

  #fieldEl = null;
  #valueEl = null;
  #dragging = false;
  #startX = 0;
  #startVal = 0;

  connected() {
    super.connected();
    this.setAttribute('role', 'spinbutton');
    this.setAttribute('tabindex', '0');

    if (!this.querySelector('[slot="field"]')) {
      const inner = `
        <span slot="label">${this.label}</span>
        <span slot="value">${this.#format(this.value)}</span>
        ${this.suffix ? `<span slot="suffix">${this.suffix}</span>` : ''}
      `;
      this.innerHTML = `
        <div slot="field">
          <div slot="layer" data-layer="base">${inner}</div>
          <div slot="layer" data-layer="fill" aria-hidden="true">${inner}</div>
        </div>
      `;
    }

    this.#fieldEl = this.querySelector('[slot="field"]');
    this.#valueEl = this.querySelector('[data-layer="base"] [slot="value"]');

    if (this.#fieldEl) this.#fieldEl.addEventListener('pointerdown', this.#onPointerDown);
    this.addEventListener('keydown', this.#onKey);
  }

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

    const formatted = this.#format(this.value);
    if (this.#valueEl) this.#valueEl.textContent = formatted;
    const fillValueEl = this.querySelector('[data-layer="fill"] [slot="value"]');
    if (fillValueEl) fillValueEl.textContent = formatted;

    // Update fill percentage via CSS var (drives clip-path on fill layer)
    const range = this.max - this.min;
    const pct = range > 0 ? ((this.value - this.min) / range) * 100 : 0;
    this.style.setProperty('--range-fill-pct', `${pct}%`);

    this.setAttribute('aria-valuemin', this.min);
    this.setAttribute('aria-valuemax', this.max);
    this.setAttribute('aria-valuenow', this.value);
    this.setAttribute('aria-valuetext', `${this.value}${this.suffix ? ' ' + this.suffix : ''}`);
    this.setAttribute('aria-label', this.label || '');

    this.syncValue(String(this.value));
  }

  #format(v) {
    // Show decimal places matching step precision
    const decimals = (String(this.step).split('.')[1] || '').length;
    return decimals > 0 ? v.toFixed(decimals) : String(v);
  }

  #snap(raw) {
    const stepped = Math.round((raw - this.min) / this.step) * this.step + this.min;
    return Math.max(this.min, Math.min(this.max, parseFloat(stepped.toFixed(10))));
  }

  #setValue(v) {
    const snapped = this.#snap(v);
    if (snapped === this.value) return;
    this.value = snapped;
    this.dispatchEvent(new CustomEvent('input', { bubbles: true, detail: { value: this.value } }));
  }

  // ── Pointer drag ──

  #valueFromX(clientX) {
    const rect = this.#fieldEl.getBoundingClientRect();
    const ratio = Math.max(0, Math.min(1, (clientX - rect.left) / rect.width));
    return this.#snap(this.min + ratio * (this.max - this.min));
  }

  #onPointerDown = (e) => {
    if (this.disabled) return;
    e.preventDefault();

    // Click: jump to position immediately
    this.#setValue(this.#valueFromX(e.clientX));

    // Then start drag from that position
    this.#dragging = true;
    this.#startX = e.clientX;
    this.#startVal = this.value;
    this.setAttribute('data-dragging', '');
    this.#fieldEl.setPointerCapture(e.pointerId);
    this.#fieldEl.addEventListener('pointermove', this.#onPointerMove);
    this.#fieldEl.addEventListener('pointerup', this.#onPointerUp);
  };

  #onPointerMove = (e) => {
    if (!this.#dragging) return;
    const dx = e.clientX - this.#startX;
    // 1:1 — dragging across the full field width traverses the full range
    const fieldWidth = this.#fieldEl.getBoundingClientRect().width;
    const range = this.max - this.min;
    const delta = (dx / fieldWidth) * range;
    this.#setValue(this.#startVal + delta);
  };

  #onPointerUp = (e) => {
    this.#dragging = false;
    this.removeAttribute('data-dragging');
    this.#fieldEl.releasePointerCapture(e.pointerId);
    this.#fieldEl.removeEventListener('pointermove', this.#onPointerMove);
    this.#fieldEl.removeEventListener('pointerup', this.#onPointerUp);
    this.dispatchEvent(new CustomEvent('change', { bubbles: true, detail: { value: this.value } }));
  };

  // ── Keyboard ──

  #onKey = (e) => {
    if (this.disabled) return;
    let v = this.value;
    switch (e.key) {
      case 'ArrowRight': case 'ArrowUp':   v += this.step; break;
      case 'ArrowLeft':  case 'ArrowDown': v -= this.step; break;
      case 'Home': v = this.min; break;
      case 'End':  v = this.max; break;
      case 'PageUp':   v += this.step * 10; break;
      case 'PageDown': v -= this.step * 10; break;
      default: return;
    }
    e.preventDefault();
    this.#setValue(v);
    this.dispatchEvent(new CustomEvent('change', { bubbles: true, detail: { value: this.value } }));
  };

  disconnected() {
    super.disconnected();
    this.#fieldEl?.removeEventListener('pointerdown', this.#onPointerDown);
    this.#fieldEl?.removeEventListener('pointermove', this.#onPointerMove);
    this.#fieldEl?.removeEventListener('pointerup', this.#onPointerUp);
    this.removeEventListener('keydown', this.#onKey);
    this.#fieldEl = null;
    this.#valueEl = null;
  }
}
