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

/**
 * <agent-questions-ui> — Multi-choice clarifying-question cards.
 *
 * The agent asks a structured question and offers 2-6 options as stacked cards
 * (claude.ai style). Supports single- or multi-select. On submit, host receives
 * the picked option ids.
 *
 * JS API:
 *   el.question = 'Which direction fits best?';
 *   el.helper   = 'Pick one — we can revisit later.';
 *   el.options  = [
 *     { id: 'lean',      label: 'Lean & minimal',   description: 'Whitespace-heavy, neutral palette.', icon: 'square' },
 *     { id: 'expressive',label: 'Expressive',       description: 'Bold type, vivid accents.',         icon: 'sparkle' },
 *     { id: 'utilitarian',label: 'Utilitarian',     description: 'Dense, information-first.',          icon: 'gear' },
 *   ];
 *
 *   el.addEventListener('questions-answer', (e) => {
 *     // detail: { selected: string[], option: optionObject | null }
 *   });
 *
 * Attributes:
 *   multi      — allow multiple selections (default: false)
 *   submit-label — label for the submit button (shown only when multi)
 *
 * Events:
 *   questions-answer — detail: { selected: string[], option: chosen | null }
 */

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

export class UIAgentQuestions extends UIElement {
  // gh issue 276 wave 4: options now render as <option-card-ui> (which
  // composes icon-ui itself for the leading `icon` prop). This primitive
  // no longer stamps any icon directly, so no requiredIcons entry is
  // needed here — see check-required-icons.mjs (slot 11).

  static properties = {
    multi:       { type: Boolean, default: false, reflect: true },
    question:    { type: String,  default: '',    reflect: true },
    helper:      { type: String,  default: '',    reflect: true },
    submitLabel: { type: String,  default: 'Continue', reflect: true, attribute: 'submit-label' },
  };

  static template = () => null;

  #options = [];
  #selected = new Set();
  #answered = false;
  #questionEl = null;
  #helperEl = null;
  #optionsEl = null;
  #submitEl = null;

  get options() { return this.#options; }
  set options(v) {
    this.#options = Array.isArray(v) ? v.slice() : [];
    this.#selected.clear();
    this.#answered = false;
    this.#render();
  }

  connected() {
    this.#build();
  }

  disconnected() {
    this.#submitEl?.removeEventListener('press', this.#onSubmit);
    this.#optionsEl?.removeEventListener('change', this.#onCardChange);
    this.#optionsEl?.removeEventListener('click', this.#onOptionsClickCapture, true);
    this.#optionsEl?.removeEventListener('keydown', this.#onOptionsKeydownCapture, true);
    this.#questionEl = this.#helperEl = this.#optionsEl = this.#submitEl = null;
  }

  render() {
    // Keep question/helper text in sync with attribute changes
    if (this.#questionEl) {
      this.#questionEl.textContent = this.question || '';
      this.#questionEl.hidden = !this.question;
    }
    if (this.#helperEl) {
      this.#helperEl.textContent = this.helper || '';
      this.#helperEl.hidden = !this.helper;
    }
    if (this.#submitEl) {
      this.#submitEl.setAttribute('text', this.submitLabel || 'Continue');
      this.#submitEl.hidden = !this.multi;
    }
  }

  #build() {
    this.innerHTML = '';

    this.#questionEl = document.createElement('div');
    this.#questionEl.setAttribute('data-questions-q', '');
    if (this.question) this.#questionEl.textContent = this.question;
    else this.#questionEl.hidden = true;

    this.#helperEl = document.createElement('div');
    this.#helperEl.setAttribute('data-questions-helper', '');
    if (this.helper) this.#helperEl.textContent = this.helper;
    else this.#helperEl.hidden = true;

    this.#optionsEl = document.createElement('div');
    this.#optionsEl.setAttribute('data-questions-options', '');
    // Single-select: option-card-ui's own radiogroup machinery handles
    // click + keyboard natively and fires `change` when a card becomes
    // selected — we just listen for it.
    this.#optionsEl.addEventListener('change', this.#onCardChange);
    // Multi-select: option-card-ui is radio-only (its #select() no-ops
    // once checked, and Enter/Space call that private method directly
    // rather than dispatching a click), so there is no toggle-off hook
    // to attach to. Intercept at the container's CAPTURE phase — before
    // the card's own bubble-phase listeners ever run — and drive
    // checked/aria state ourselves. Single mode never reaches these
    // (guarded by `if (!this.multi) return`), so its native behavior is
    // untouched.
    this.#optionsEl.addEventListener('click', this.#onOptionsClickCapture, true);
    this.#optionsEl.addEventListener('keydown', this.#onOptionsKeydownCapture, true);

    this.#submitEl = document.createElement('button-ui');
    this.#submitEl.setAttribute('text', this.submitLabel || 'Continue');
    this.#submitEl.setAttribute('variant', 'primary');
    this.#submitEl.setAttribute('size', 'sm');
    this.#submitEl.setAttribute('disabled', '');
    if (!this.multi) this.#submitEl.hidden = true;
    this.#submitEl.addEventListener('press', this.#onSubmit);

    this.append(this.#questionEl, this.#helperEl, this.#optionsEl, this.#submitEl);
    this.#render();
  }

  #render() {
    if (!this.#optionsEl) return;
    this.#optionsEl.innerHTML = '';

    for (const opt of this.#options) {
      const card = document.createElement('option-card-ui');
      card.setAttribute('data-questions-option', '');
      card.setAttribute('data-questions-option-id', opt.id);
      // Shared name → siblings form option-card-ui's own radiogroup
      // (single mode). Inert in multi mode: #select() never runs there
      // (see the capture-phase override below), so the shared name has
      // no sibling-uncheck effect to guard against.
      card.name = 'questions-option';
      card.value = opt.id;
      card.heading = opt.label || '';
      card.description = opt.description || '';
      card.icon = opt.icon || '';

      this.#optionsEl.appendChild(card);

      // Multi mode: override the radio role option-card-ui sets on
      // connect — this is a checkbox group, and aria-checked (which the
      // primitive keeps in sync with `.checked` regardless of role) is
      // valid for either.
      if (this.multi) card.setAttribute('role', 'checkbox');
      card.checked = this.#selected.has(opt.id);
      card.disabled = this.#answered;
    }

    if (this.#submitEl) {
      this.#submitEl.disabled = this.#answered || this.#selected.size === 0;
    }
  }

  /** Single mode — option-card-ui's own radiogroup fired `change` because
   *  a card just became selected (it never fires on becoming unselected,
   *  so this is always a genuine new pick). */
  #onCardChange = (e) => {
    if (this.multi || this.#answered) return;
    const id = e.detail?.value;
    const opt = this.#options.find((o) => o.id === id);
    if (!opt) return;
    this.#selected.clear();
    this.#selected.add(opt.id);
    this.#answered = true;
    this.#render();
    this.dispatchEvent(new CustomEvent('questions-answer', {
      bubbles: true,
      detail: { selected: [opt.id], option: opt },
    }));
  };

  /** Multi mode — capture-phase click override (see #build). Stops the
   *  event before option-card-ui's own bubble-phase click handler runs,
   *  since that handler can only select, never deselect. */
  #onOptionsClickCapture = (e) => {
    if (!this.multi) return;
    const card = e.target.closest?.('option-card-ui[data-questions-option]');
    if (!card || !this.#optionsEl.contains(card)) return;
    e.stopPropagation();
    this.#toggleMulti(card);
  };

  /** Multi mode — capture-phase keydown override. Space/Enter toggle
   *  (mirroring click); arrow keys are swallowed rather than forwarded
   *  to option-card-ui's own forced-select navigation, since checkbox
   *  groups here never had arrow-key nav (Tab-only, as before). */
  #onOptionsKeydownCapture = (e) => {
    if (!this.multi) return;
    const card = e.target.closest?.('option-card-ui[data-questions-option]');
    if (!card || !this.#optionsEl.contains(card)) return;
    if (e.key === ' ' || e.key === 'Enter') {
      e.preventDefault();
      e.stopPropagation();
      this.#toggleMulti(card);
      return;
    }
    if (e.key === 'ArrowDown' || e.key === 'ArrowUp' || e.key === 'ArrowLeft' || e.key === 'ArrowRight') {
      e.stopPropagation();
    }
  };

  #toggleMulti(card) {
    if (this.#answered) return;
    const id = card.dataset.questionsOptionId;
    const opt = this.#options.find((o) => o.id === id);
    if (!opt) return;
    if (this.#selected.has(opt.id)) this.#selected.delete(opt.id);
    else this.#selected.add(opt.id);
    this.#render();
  }

  #onSubmit = () => {
    if (this.#answered || this.#selected.size === 0) return;
    this.#answered = true;
    this.#render();
    const ids = [...this.#selected];
    const first = this.#options.find(o => o.id === ids[0]) || null;
    this.dispatchEvent(new CustomEvent('questions-answer', {
      bubbles: true,
      detail: { selected: ids, option: first },
    }));
  };
}
