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

/**
 * <chart-legend-ui shape="dot" position="bottom" items='[{"key":"revenue","label":"Revenue"},{"key":"users","label":"Users"}]'></chart-legend-ui>
 *
 * Standalone legend primitive for the AdiaUI chart family. Renders a row
 * of badge-ui chips (swatch + label) that are keyboard-focusable and
 * click-toggleable — clicking emits a canonical `toggle` event that
 * consumers (or chart-ui via [for]) wire to series visibility.
 *
 * Attributes:
 *   for        — id-ref of a chart-ui / heatmap-ui to mirror. When set, the
 *                legend reads series data from that element's `.legendData`
 *                property and subscribes to its `legend-update` event.
 *                (Auto-mirror ships behind [for] — explicit items still
 *                override when both are provided.)
 *   items      — JSON array of {key, label, slot?, pct?} legend items.
 *                Takes precedence over [for] if both are provided.
 *   shape      — dot | square | line | dashed. Default: dot. The swatch is
 *                a `[data-swatch]` span inside each badge-ui row, styled by
 *                the legend's [shape] attr. Custom span (rather than badge-
 *                ui's [icon] slot) is required because line/dashed shapes
 *                aren't representable as icon glyphs.
 *   position   — top | bottom | left | right. Layout hint; actual placement
 *                is where the element is placed in the DOM. Drives
 *                flex-direction.
 *   static     — when set, rows render as non-interactive badges (no
 *                role/tabindex/handlers). Default is interactive
 *                `role="button"` badges that toggle on click + Enter/Space.
 *   on-toggle  — hide | opacity. Default hide. Escape hatch per OD-CHART-09.
 *                Reported via the `toggle` event detail; chart-ui reads it
 *                when wired via [for].
 *
 * Events:
 *   toggle — detail: {key, active, mode}. Fires on row click when
 *            not [static]. `active` is the new state (true=visible).
 */

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

export class UIChartLegend extends UIElement {
  static properties = {
    for:      { type: String,  default: '',       reflect: true },
    // Items is a JSON-encoded array; reflecting to the attribute would
    // serialize a potentially long array on every change. The property is
    // the source of truth; the attribute is intentionally write-only.
    items:    { type: String,  default: '',       reflect: false },
    shape:    { type: String,  default: 'dot',    reflect: true },
    position: { type: String,  default: 'bottom', reflect: true },
    static:   { type: Boolean, default: false,    reflect: true },
    onToggle: { type: String,  default: 'hide',   reflect: true, attribute: 'on-toggle' },
  };

  static template = () => null;

  #target = null;
  #targetListener = null;
  #hiddenKeys = new Set();

  connected() {
    this.setAttribute('role', 'list');
    this.#resolveTarget();
    this.#paint();
  }

  disconnected() {
    this.#detachTarget();
  }

  render() {
    this.#resolveTarget();
    this.#paint();
  }

  /* ── Target resolution ──────────────────────────────────────────── */

  #resolveTarget() {
    this.#detachTarget();
    if (!this.for) return;
    const el = this.getRootNode()?.getElementById?.(this.for)
      || document.getElementById(this.for);
    if (!el) {
      // Warn once per (element × id) pair
      if (!UIChartLegend._warned) UIChartLegend._warned = new WeakSet();
      const marker = { el: this, id: this.for };
      if (!UIChartLegend._warned.has(this)) {
        console.warn(`[chart-legend-ui] [for="${this.for}"] did not resolve to an element.`);
        UIChartLegend._warned.add(this);
      }
      return;
    }
    this.#target = el;
    this.#targetListener = () => this.#paint();
    el.addEventListener('legend-update', this.#targetListener);
  }

  #detachTarget() {
    if (this.#target && this.#targetListener) {
      this.#target.removeEventListener('legend-update', this.#targetListener);
    }
    this.#target = null;
    this.#targetListener = null;
  }

  /* ── Item source ────────────────────────────────────────────────── */

  #resolveItems() {
    /* Explicit items prop wins. */
    if (this.items) {
      try {
        const parsed = JSON.parse(this.items);
        if (Array.isArray(parsed)) return parsed;
      } catch (_) { /* fall through */ }
    }
    /* Mirror from [for] target when present. */
    if (this.#target && Array.isArray(this.#target.legendData)) {
      return this.#target.legendData;
    }
    return [];
  }

  /* ── Render ─────────────────────────────────────────────────────── */

  #paint() {
    const items = this.#resolveItems();
    this.innerHTML = '';
    if (!items.length) return;

    for (const item of items) {
      const key = item.key ?? item.label ?? '';
      const label = item.label ?? key;
      const slot = item.slot != null ? item.slot : 0;
      const active = !this.#hiddenKeys.has(key);

      /* Each row is a <badge-ui> chip — the canonical AdiaUI primitive for
         legend pills (per badge.yaml's documented "chart-legend" example).
         badge-ui carries the pill chrome (radius, padding, font-size, hover
         affordance), the legend layers shape-specific swatch CSS + the
         click-to-toggle behavior on top. Interactive rows get
         role="button" + tabindex="0" + Enter/Space handlers so they're
         keyboard-operable without a wrapping <button>. */
      const row = document.createElement('badge-ui');
      row.setAttribute('data-row', '');
      row.setAttribute('role', this.static ? 'listitem' : 'button');
      row.setAttribute('text', label);
      if (key) row.setAttribute('data-key', key);
      if (active) row.setAttribute('data-active', '');
      if (!this.static) {
        row.setAttribute('tabindex', '0');
        row.setAttribute('aria-pressed', active ? 'true' : 'false');
      }

      /* Swatch — composed via `<swatch-ui>` with the legend's [shape] attr
         passed through. Pre-2026-05-01 this was a hand-rolled `<span
         data-swatch>` plus per-shape CSS in this file; that drift caused
         a cascade-leak collision with the docs-shell's `[data-swatch]`
         demo rule (token-colors page). Compose the primitive instead and
         the swatch's dot / square / line / dashed variants live in one
         place. Color resolves via --color-{key} with a --a-data-{slot}
         fallback; the legend can render standalone (without a chart-ui
         ancestor) so the fallback must reference the global data palette
         directly, not chart-ui's scoped --chart-{N} alias. */
      const swatchShape = this.shape || 'dot';
      const swatch = document.createElement('swatch-ui');
      swatch.setAttribute('shape', swatchShape);
      swatch.setAttribute('size', 'sm');
      const swatchColor = key
        ? `var(--color-${key}, var(--a-data-${slot}))`
        : `var(--a-data-${slot})`;
      swatch.style.setProperty('--swatch-color', swatchColor);
      row.appendChild(swatch);

      if (!this.static) {
        row.addEventListener('click', (e) => this.#onRowToggle(e, key));
        row.addEventListener('keydown', (e) => {
          if (e.key === 'Enter' || e.key === ' ') {
            e.preventDefault();
            this.#onRowToggle(e, key);
          }
        });
      }

      this.appendChild(row);
    }
  }

  #onRowToggle(e, key) {
    if (!key) return;
    const currentlyActive = !this.#hiddenKeys.has(key);
    const newActive = !currentlyActive;
    if (newActive) this.#hiddenKeys.delete(key);
    else this.#hiddenKeys.add(key);

    const row = e.currentTarget;
    if (newActive) row.setAttribute('data-active', '');
    else row.removeAttribute('data-active');
    row.setAttribute('aria-pressed', newActive ? 'true' : 'false');

    this.dispatchEvent(new CustomEvent('toggle', {
      bubbles: true,
      detail: { key, active: newActive, mode: this.onToggle || 'hide' },
    }));
  }
}
