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

/**
 * <demo-toggle-ui>
 *   <section slot="off">…without trait…</section>
 *   <section slot="on">…with trait…</section>
 * </demo-toggle-ui>
 *
 * Side-by-side "with trait / without trait" comparison primitive.
 * Renders a header bar with a switch-ui that flips which child slot is
 * visible. Reflects [data-state="on"|"off"] on the host so consumer CSS
 * can theme the bar around the active variant.
 *
 * Two layout modes:
 *   default — only the active slot is in the layout (the other is `display: none`).
 *   data-mode="overlay" — both slots stack on the same coordinates; inactive
 *     slot is `visibility: hidden` so layout never shifts.
 *
 * Authoring API:
 *   [label-on]  — header label when state=on   (default: "On")
 *   [label-off] — header label when state=off  (default: "Off")
 *   [initial]   — "on" | "off"; only consulted when [state] is unset on connect
 *   [state]     — "on" | "off"; reflected; the live state attribute
 *
 * Events:
 *   change — { detail: { state: "on" | "off" } }
 *
 * Keyboard:
 *   The embedded <switch-ui> handles space + enter via its own keydown wiring.
 */

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

export class UIDemoToggle extends UIElement {
  static properties = {
    labelOn:  { type: String, default: 'On',  attribute: 'label-on',  reflect: true },
    labelOff: { type: String, default: 'Off', attribute: 'label-off', reflect: true },
    initial:  { type: String, default: 'off', reflect: true },
    state:    { type: String, default: '',    reflect: true },
  };

  // Header bar with a switch + active label. Stamped once per class via
  // static parts; live label text is updated in render() so the same
  // template covers every (label-on, label-off, state) tuple.
  static parts = {
    bar: `
      <row-ui slot="bar" gap="3" align="center" data-demo-toggle-bar>
        <text-ui slot="label" data-demo-toggle-label></text-ui>
        <switch-ui slot="switch" data-demo-toggle-switch></switch-ui>
      </row-ui>
    `,
  };

  static template = () => null;

  #bar = null;
  #switch = null;
  #label = null;
  #bound = false;

  connected() {
    // Resolve the initial state. Honor [state] if explicitly authored;
    // otherwise fall back to [initial] (default "off"). Anything other
    // than the literal "on" coerces to "off" so authors can't poison
    // the data-state attribute with arbitrary values.
    if (!this.state) {
      const seed = (this.initial === 'on') ? 'on' : 'off';
      this.state = seed;
    } else {
      this.state = (this.state === 'on') ? 'on' : 'off';
    }

    // Stamp the header bar lazily — `this.ensure('bar')` clones the
    // blueprint into the light DOM the first time it's called.
    this.#bar = this.ensure('bar');
    this.#switch = this.#bar.querySelector('[data-demo-toggle-switch]');
    this.#label = this.#bar.querySelector('[data-demo-toggle-label]');

    if (!this.#bound) {
      this.#bound = true;
      this.#switch.addEventListener('change', this.#onSwitchChange);
    }
  }

  disconnected() {
    if (this.#switch) {
      this.#switch.removeEventListener('change', this.#onSwitchChange);
    }
    this.#bar = null;
    this.#switch = null;
    this.#label = null;
    this.#bound = false;
  }

  render() {
    if (!this.#bar) return;
    const on = this.state === 'on';

    // Sync the embedded switch to the host's state. checked is a
    // reflected property; the assignment fires no event because
    // syncValue() short-circuits when the next state matches.
    if (this.#switch.checked !== on) {
      this.#switch.checked = on;
    }

    if (this.#label) {
      this.#label.textContent = on ? this.labelOn : this.labelOff;
    }
  }

  /**
   * Public toggle method — flips the host between on and off and emits
   * a `change` event. Mirrors the click-on-switch path so consumers
   * can drive the state from outside without touching the embedded
   * switch directly.
   */
  toggle() {
    this.state = (this.state === 'on') ? 'off' : 'on';
    this.dispatchEvent(new CustomEvent('change', {
      bubbles: true,
      detail: { state: this.state },
    }));
  }

  #onSwitchChange = (e) => {
    // The embedded <switch-ui> fires a bubbling `change`. Stop it at the
    // host boundary — we re-dispatch our own `change` with detail.state
    // so consumers see exactly one event per toggle, not the inner
    // switch's bare event AND our annotated one.
    e.stopPropagation();
    const next = this.#switch.checked ? 'on' : 'off';
    if (next === this.state) return;
    this.state = next;
    this.dispatchEvent(new CustomEvent('change', {
      bubbles: true,
      detail: { state: this.state },
    }));
  };
}
