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

/**
 * <link-ui> — Inline navigation primitive.
 *
 *   <link-ui text="Terms of Service" href="/terms"></link-ui>
 *   <link-ui href="https://example.com" target="_blank">Read more</link-ui>
 *   <link-ui text="Sign in" variant="quiet" href="/signin"></link-ui>
 *
 * Semantic <a href> wrapper for cross-page navigation. Sibling of
 * <button-ui> — the two are NOT interchangeable:
 *   - <button-ui>: triggers an action (submit, open modal, copy, run handler)
 *   - <link-ui>:   navigates to a URL (real <a href> under the hood)
 *
 * The previous design conflated these via `<button-ui variant="link">`,
 * which produced a button DOM with link-look styling — accessibility
 * trees announced "button" for things screen-reader users expected to
 * be links, middle-click did nothing instead of opening new tabs,
 * search engines didn't see the link targets.
 *
 * Implementation: stamps an internal <a> element on connected() unless
 * a child <a> is already present (slot-passthrough mode for advanced
 * authoring). Renders the `text` prop into a <span> inside the <a>.
 * For target="_blank", auto-adds rel="noopener noreferrer" unless an
 * explicit rel is set.
 *
 * Properties:
 *   text     — visible label
 *   href     — destination URL (required for native nav semantics)
 *   target   — _self | _blank | _parent | _top
 *   rel      — explicit rel (defaults to noopener noreferrer for _blank)
 *   variant  — default | subtle | quiet
 *   block    — full-width row
 *   disabled — suppress activation
 *   icon     — optional leading Phosphor icon
 *
 * Events:
 *   press — bubbles on click/Enter with detail { href, target }. Fires
 *           BEFORE native navigation. preventDefault() the underlying
 *           click event to suppress native nav and route through the
 *           A2UI action-handler system.
 */

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

export class UILink extends UIElement {
  static properties = {
    text:     { type: String,  default: '', reflect: true },
    href:     { type: String,  default: '', reflect: true },
    target:   { type: String,  default: '', reflect: true },
    rel:      { type: String,  default: '', reflect: true },
    variant:  { type: String,  default: 'default', reflect: true },
    block:    { type: Boolean, default: false, reflect: true },
    disabled: { type: Boolean, default: false, reflect: true },
    icon:     { type: String,  default: '', reflect: true },
  };

  static template = () => null;

  #onClick = (e) => {
    if (this.disabled) {
      e.preventDefault();
      return;
    }
    // Dispatch press BEFORE native navigation so handlers can intercept.
    const detail = { href: this.href, target: this.target };
    const press = new CustomEvent('press', {
      bubbles: true,
      cancelable: true,
      detail,
    });
    const allowed = this.dispatchEvent(press);
    // If a handler called preventDefault on the press event, suppress
    // the underlying anchor's native navigation too.
    if (!allowed) e.preventDefault();
  };

  #onKey = (e) => {
    // Links are activated by Enter only (NOT Space — that's button
    // semantics). The native <a> handles this already, but we wire it
    // explicitly for the disabled-suppression case.
    if (this.disabled && e.key === 'Enter') {
      e.preventDefault();
    }
  };

  #ensureAnchor() {
    // If author provided their own <a> child, leave it alone. Otherwise
    // stamp one with the configured href/target/rel and mark it as
    // autostamped so render() knows it owns the anchor's attrs +
    // content. Slot-passthrough mode (author <a> child) preserves all
    // author-set attrs across re-renders.
    let a = this.querySelector(':scope > a');
    if (!a) {
      a = document.createElement('a');
      a.dataset.autostamped = 'true';
      this.appendChild(a);
    }
    // Move any direct text/icon children into the anchor so they inherit
    // its semantics. Skip nodes already inside the anchor.
    const toMove = [];
    for (const node of this.childNodes) {
      if (node === a) continue;
      if (node.parentNode === this) toMove.push(node);
    }
    for (const node of toMove) a.appendChild(node);
    return a;
  }

  connected() {
    const a = this.#ensureAnchor();
    this.addEventListener('click', this.#onClick);
    this.addEventListener('keydown', this.#onKey);
    // Disabled handling — aria-disabled rather than removing href, so
    // the disabled state is announced to screen readers.
    if (this.disabled) this.setAttribute('aria-disabled', 'true');
  }

  render() {
    const a = this.querySelector(':scope > a');
    if (!a) return;

    // Slot-passthrough detection: if the author provided their own <a>
    // child, leave its href/target/rel alone — they intentionally
    // configured the anchor directly. We only manage anchors we
    // auto-stamped (marked via data-autostamped).
    const isAutostamped = a.dataset.autostamped === 'true';

    if (isAutostamped) {
      // href: empty string = no native nav, but element is still
      // activatable via press event (handler-driven navigation).
      if (this.href) a.setAttribute('href', this.href);
      else a.removeAttribute('href');

      // target: passed through verbatim.
      if (this.target) a.setAttribute('target', this.target);
      else a.removeAttribute('target');

      // rel: explicit rel wins; otherwise auto-add noopener noreferrer
      // for _blank to prevent tab-napping / referrer leakage.
      if (this.rel) {
        a.setAttribute('rel', this.rel);
      } else if (this.target === '_blank') {
        a.setAttribute('rel', 'noopener noreferrer');
      } else {
        a.removeAttribute('rel');
      }
    }

    // Disabled — keep the anchor in the tree (so SR announcement works)
    // but communicate state via aria-disabled. Click handler short-
    // circuits when disabled is set. This applies to both autostamped
    // and slot-passthrough modes.
    if (this.disabled) {
      this.setAttribute('aria-disabled', 'true');
      a.setAttribute('tabindex', '-1');
    } else {
      this.removeAttribute('aria-disabled');
      a.removeAttribute('tabindex');
    }

    // text + icon — stamp into the anchor when the props are set and
    // this is an autostamped anchor. Don't touch author-provided
    // anchors' content.
    if (isAutostamped) {
      const iconHTML = this.icon ? `<icon-ui name="${this.icon}"></icon-ui>` : '';
      const textHTML = this.text ? `<span>${this.text}</span>` : '';
      a.innerHTML = `${iconHTML}${textHTML}`;
    }
  }

  disconnected() {
    this.removeEventListener('click', this.#onClick);
    this.removeEventListener('keydown', this.#onKey);
  }
}
