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

/**
 * <tag-ui text="Label" variant="info" removable></tag-ui>
 *
 * Inline tag / chip. Pill-shaped, text rendered via CSS attr().
 * Optional dismiss button dispatches a 'remove' CustomEvent.
 *
 * Variants: default, info, success, warning, danger
 * Sizes: sm, md (default)
 *
 * Events:
 *   remove — { detail: { text, value } } fired when dismiss is clicked
 */

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

export class UITag 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 = ['x'];

  static properties = {
    text:      { type: String,  default: '', reflect: true },
    // NOTE: `textContent` is intentionally NOT declared as a reactive
    // prop — `installProps()` would override the native DOM setter, so
    // `el.textContent = '…'` would silently no-op. `_api-table.js`,
    // `catalog.examples.js`, and the drop-target demo all rely on
    // `tag.textContent = …` working natively; the prop declaration
    // broke that path. v0.5.x §327.
    variant:   { type: String,  default: 'default', reflect: true },
    // `tone` is read-only via attribute selectors (CSS), not by the
    // class — declared in yaml for documentation + a2ui hint but NOT
    // in `static properties` so the runtime doesn't reflect an
    // ever-present `tone="solid"` attribute onto every <tag-ui>
    // (which would defeat the variant-default cascade and force CSS
    // selectors to compete with reflected defaults). Author sets
    // `[tone]` directly when opting in.
    size:      { type: String,  default: 'md', reflect: true },
    removable: { type: Boolean, default: false, reflect: true },
    disabled:  { type: Boolean, default: false, reflect: true },
  };

  static parts = {
    dismiss: '<button slot="dismiss" type="button" aria-label="Remove"><icon-ui name="x" weight="bold"></icon-ui></button>',
  };

  static template = () => null;

  #onClick = (e) => {
    if (this.disabled) return;
    if (e.target.closest('[slot="dismiss"]')) {
      this.dispatchEvent(new CustomEvent('remove', {
        bubbles: true,
        detail: { text: this.text, value: this.text },
      }));
      this.remove();
    }
  };

  #onKeydown = (e) => {
    if (this.disabled) return;
    if (e.target.closest('[slot="dismiss"]') && (e.key === 'Enter' || e.key === ' ')) {
      e.preventDefault();
      this.dispatchEvent(new CustomEvent('remove', {
        bubbles: true,
        detail: { text: this.text, value: this.text },
      }));
      this.remove();
    }
  };

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

    this.addEventListener('click', this.#onClick);
    this.addEventListener('keydown', this.#onKeydown);
  }

  disconnected() {
    this.removeEventListener('click', this.#onClick);
    this.removeEventListener('keydown', this.#onKeydown);
  }

  render() {
    if (this.text) this.setAttribute('aria-label', this.text);

    if (this.disabled) {
      this.setAttribute('aria-disabled', 'true');
      this.setAttribute('tabindex', '-1');
    } else {
      this.removeAttribute('aria-disabled');
      this.setAttribute('tabindex', '0');
    }

    if (this.removable && !this.disabled) {
      const btn = this.ensure('dismiss');
      if (btn && btn.parentElement !== this) this.appendChild(btn);
    } else {
      this.drop('dismiss');
    }
  }
}
