/**
 * <display-field-ui label="Card on file" value="•••• 4242" variant="masked" icon="lock" hint="Expires 12/26"></display-field-ui>
 *
 * Display-only labeled value — a field-shaped read-out for a static or
 * masked value, or a labeled slot that hosts a third-party element (a
 * Stripe Element iframe) under a proper caption.
 *
 * Unlike <field-ui> it wraps NO interactive control: no focus, no
 * contenteditable, no form participation, no [for]/id-minting. The label
 * names the whole field for assistive tech via role="group" +
 * aria-labelledby, so a slotted foreign element is announced with its
 * caption.
 *
 * Value comes from EITHER the `value` attribute OR unnamed default-slot
 * children (the foreign-element path) — slot content wins when both are
 * present.
 */

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

// Monotonic id source for label/hint association (aria-labelledby /
// aria-describedby need stable ids; multiple instances must not collide).
let uid = 0;

class UIDisplayField extends UIElement {
  static properties = {
    label:   { type: String, default: '',        reflect: true },
    value:   { type: String, default: '',        reflect: true },
    variant: { type: String, default: 'default', reflect: true },
    hint:    { type: String, default: '',        reflect: true },
    icon:    { type: String, default: '',        reflect: true },
  };

  static template = () => null;

  #labelEl = null;
  #valueEl = null;
  #hintEl = null;
  #iconEl = null;

  connected() {
    // A non-interactive labeled group — the label is the accessible name,
    // which holds even when the value region is a foreign iframe.
    this.setAttribute('role', 'group');

    // The label element is the group's accessible-name source; create it
    // once and mint a stable id for aria-labelledby. Prepend so it reads
    // first for assistive tech (visual placement is grid-area driven).
    this.#labelEl = this.querySelector(':scope > [slot="label"]');
    if (!this.#labelEl) {
      this.#labelEl = document.createElement('span');
      this.#labelEl.setAttribute('slot', 'label');
      this.insertBefore(this.#labelEl, this.firstChild);
    }
    if (!this.#labelEl.id) this.#labelEl.id = `display-field-label-${++uid}`;

    // Re-bind any optional slot children that already exist in the light DOM.
    // On a DOM move (disconnect → reconnect — e.g. UIElement.reconcile's
    // insertBefore during a keyed list reorder) disconnected() nulled these
    // fields but left the nodes in place; without re-binding, render() would
    // see the null field and stamp a DUPLICATE. Query-or-leave-null mirrors
    // the label handling above.
    this.#valueEl = this.querySelector(':scope > [slot="value"]');
    this.#hintEl = this.querySelector(':scope > [slot="hint"]');
    this.#iconEl = this.querySelector(':scope > icon-ui[slot="icon"]');
  }

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

    // ── Label — the group's accessible name. ──
    if (this.label) {
      this.#labelEl.textContent = this.label;
      this.#labelEl.hidden = false;
      this.setAttribute('aria-labelledby', this.#labelEl.id);
    } else {
      this.#labelEl.hidden = true;
      this.removeAttribute('aria-labelledby');
    }

    // ── Value — attribute path. Suppressed when the consumer slotted
    //    foreign content (a Stripe mount, rich markup): that content owns
    //    the value region. ──
    const foreign = this.querySelector(':scope > :not([slot])');
    if (foreign) {
      if (this.#valueEl) { this.#valueEl.remove(); this.#valueEl = null; }
    } else {
      if (!this.#valueEl) {
        this.#valueEl = document.createElement('span');
        this.#valueEl.setAttribute('slot', 'value');
        this.appendChild(this.#valueEl);
      }
      this.#valueEl.textContent = this.value;
    }

    // ── Hint — optional caption; drives aria-describedby. Removed when
    //    absent so no empty grid row leaks a gap. ──
    if (this.hint) {
      if (!this.#hintEl) {
        this.#hintEl = document.createElement('span');
        this.#hintEl.setAttribute('slot', 'hint');
        this.#hintEl.id = `display-field-hint-${++uid}`;
        this.appendChild(this.#hintEl);
      }
      this.#hintEl.textContent = this.hint;
      this.setAttribute('aria-describedby', this.#hintEl.id);
    } else if (this.#hintEl) {
      this.#hintEl.remove();
      this.#hintEl = null;
      this.removeAttribute('aria-describedby');
    }

    // ── Icon — optional leading glyph. Removed when absent so the icon
    //    column collapses (the CSS gates the two-column layout on a
    //    present [slot="icon"] child). ──
    if (this.icon) {
      if (!this.#iconEl) {
        this.#iconEl = document.createElement('icon-ui');
        this.#iconEl.setAttribute('slot', 'icon');
        this.#iconEl.setAttribute('aria-hidden', 'true');
        this.appendChild(this.#iconEl);
      }
      this.#iconEl.setAttribute('name', this.icon);
    } else if (this.#iconEl) {
      this.#iconEl.remove();
      this.#iconEl = null;
    }
  }

  disconnected() {
    this.#labelEl = null;
    this.#valueEl = null;
    this.#hintEl = null;
    this.#iconEl = null;
  }
}

defineIfFree('display-field-ui', UIDisplayField);

export { UIDisplayField };
