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

/**
 * fields-ui — Container for a group of <field-ui> children laid out
 * on a shared grid.
 *
 *   <fields-ui>                                      <!-- 6-col grid -->
 *     <field-ui label="Title">…</field-ui>           <!-- rows=6 (full row) -->
 *     <field-ui label="Status"   rows="2">…</field-ui>
 *     <field-ui label="Priority" rows="2">…</field-ui>
 *     <field-ui label="Group"    rows="2">…</field-ui>
 *   </fields-ui>
 *
 *   <fields-ui inline>            <!-- propagate inline to every child -->
 *     <field-ui label="Search">…</field-ui>
 *     <field-ui label="Kind">…</field-ui>
 *   </fields-ui>
 *
 * The shared grid keeps siblings on the same row aligned. The [inline]
 * attribute is mirrored onto every direct <field-ui> child so a whole
 * sub-form switches layout mode from one host attribute. A small
 * MutationObserver re-syncs when children are added/removed.
 *
 * Per-field column span is driven by the child's [rows="1..6"]
 * attribute — pure CSS via attribute selectors in fields.css. The
 * default span (no attr) is the full row.
 */
import { UIElement } from '../../core/element.js';

export class UIFields extends UIElement {
  static properties = {
    inline:  { type: Boolean, default: false, reflect: true },
    columns: { type: Number,  default: 6,     reflect: true },
  };

  static template = () => null;

  /** @type {MutationObserver | null} */
  #mo = null;

  connected() {
    this.#syncInline();
    this.#mo = new MutationObserver((records) => {
      // Only re-sync on childList changes that involve direct <field-ui>
      // children — attribute mutations on grandchildren aren't ours to
      // care about.
      for (const r of records) {
        if (r.type !== 'childList') continue;
        if (this.#hasFieldChild(r.addedNodes) || this.#hasFieldChild(r.removedNodes)) {
          this.#syncInline();
          return;
        }
      }
    });
    this.#mo.observe(this, { childList: true });
  }

  disconnected() {
    this.#mo?.disconnect();
    this.#mo = null;
  }

  render() {
    // UIElement calls render() on attribute → property reflection. The
    // inline attr's effect is propagation-to-children, not host markup.
    this.#syncInline();
  }

  // ── Private ────────────────────────────────────────────────────

  #syncInline() {
    const inline = this.hasAttribute('inline');
    // wrapper-trap-ok: fields-ui children are authored as static field-ui literals within a form group; dynamic .map() field collections use the programmatic API
    for (const child of this.children) {
      if (child.localName !== 'field-ui') continue;
      if (inline) {
        if (!child.hasAttribute('inline')) child.setAttribute('inline', '');
      } else {
        // Only remove if WE set it. We can't tell, so we always remove —
        // that's the documented contract: <fields-ui inline> is the
        // single source of truth for child inline-mode within the group.
        if (child.hasAttribute('inline')) child.removeAttribute('inline');
      }
    }
  }

  /** @param {NodeList} nodes */
  #hasFieldChild(nodes) {
    for (const n of nodes) {
      if (n.nodeType === 1 && /** @type {Element} */ (n).localName === 'field-ui') {
        return true;
      }
    }
    return false;
  }
}
