/**
 * `<tour-ui>` — spotlight-driven product tour / guided walkthrough.
 *
 * Orchestrates a sequence of `<tour-step>` children, each declaring a
 * target element via CSS selector. When active, dims the page with a
 * scrim, cuts a "hole" around the current step's target, and renders
 * a popover next to it with step content + Skip / Previous / Next.
 *
 * Architecture:
 *   - Host is `display: contents` (no chrome).
 *   - On start, mounts two surfaces into `document.body`:
 *     1. [data-tour-spotlight] — transparent rect with huge box-shadow
 *        scrim. Position via top/left, size via width/height (animated).
 *     2. [data-tour-popover] — manual popover with step content + nav,
 *        anchored to the target via core/anchor.js (same mechanism as
 *        menu-ui / context-menu-ui / popover-ui).
 *   - Spotlight repositions on window resize/scroll while active.
 *
 * @see ../../core/anchor.js
 * @see ../onboarding-checklist/ — peer onboarding primitive (user-paced).
 */

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

export class UITour extends UIElement {
  static properties = {
    active:        { type: Boolean, default: false, reflect: true },
    step:          { type: Number,  default: 0,     reflect: false },
    autoStart:     { type: Boolean, default: false, reflect: true, attribute: 'auto-start' },
    storageKey:    { type: String,  default: '',    reflect: true, attribute: 'storage-key' },
  };

  static template = () => null;

  #spotlight = null;
  #popover = null;
  #anchorCleanup = null;
  #onResize = null;
  #onScroll = null;
  #onKeydown = null;

  connected() {
    super.connected();
    if (this.autoStart && !this.#alreadyDone()) {
      // Defer one frame so consumer targets (which may also mount on the
      // same tick) have a chance to land in the DOM first.
      requestAnimationFrame(() => {
        if (this.isConnected && this.autoStart) this.start();
      });
    }
  }

  disconnected() {
    super.disconnected();
    this.#teardown();
  }

  /* ── Public API ──────────────────────────────────────────────────── */

  start() {
    if (this.active) return;
    if (!this.#steps.length) {
      // eslint-disable-next-line no-console
      console.warn('[tour-ui] start() called with no <tour-step> children — nothing to render.');
      return;
    }
    this.step = 0;
    this.active = true;
    this.#mount();
    this.#renderStep();
    this.dispatchEvent(new CustomEvent('tour-start', {
      bubbles: true,
      detail: { step: 0 },
    }));
  }

  next() {
    if (!this.active) return;
    const total = this.#steps.length;
    if (this.step >= total - 1) { this.finish(); return; }
    const from = this.step;
    this.step = from + 1;
    this.#renderStep();
    this.dispatchEvent(new CustomEvent('tour-step-change', {
      bubbles: true,
      detail: { from, to: this.step, totalSteps: total },
    }));
  }

  previous() {
    if (!this.active) return;
    if (this.step <= 0) return;
    const from = this.step;
    this.step = from - 1;
    this.#renderStep();
    this.dispatchEvent(new CustomEvent('tour-step-change', {
      bubbles: true,
      detail: { from, to: this.step, totalSteps: this.#steps.length },
    }));
  }

  skip() {
    if (!this.active) return;
    const total = this.#steps.length;
    const at = this.step;
    this.#recordDone();
    this.active = false;
    this.#teardown();
    this.dispatchEvent(new CustomEvent('tour-skip', {
      bubbles: true,
      detail: { step: at, totalSteps: total },
    }));
  }

  finish() {
    if (!this.active) return;
    const total = this.#steps.length;
    this.#recordDone();
    this.active = false;
    this.#teardown();
    this.dispatchEvent(new CustomEvent('tour-finish', {
      bubbles: true,
      detail: { totalSteps: total },
    }));
  }

  /* ── Internals ───────────────────────────────────────────────────── */

  get #steps() {
    return [...this.querySelectorAll(':scope > tour-step-ui')];
  }

  #alreadyDone() {
    if (!this.storageKey) return false;
    try { return localStorage.getItem(this.storageKey) === 'done'; }
    catch { return false; }
  }

  #recordDone() {
    if (!this.storageKey) return;
    try { localStorage.setItem(this.storageKey, 'done'); }
    catch { /* quota / privacy mode — silently ignore */ }
  }

  #mount() {
    if (!this.#spotlight) {
      const sp = document.createElement('div');
      sp.setAttribute('data-tour-spotlight', '');
      sp.setAttribute('aria-hidden', 'true');
      document.body.appendChild(sp);
      this.#spotlight = sp;
    }
    if (!this.#popover) {
      const pop = document.createElement('div');
      pop.setAttribute('data-tour-popover', '');
      pop.setAttribute('popover', 'manual');
      pop.setAttribute('role', 'dialog');
      pop.setAttribute('aria-modal', 'false');
      pop.tabIndex = -1;
      pop.style.outline = 'none';
      pop.addEventListener('click', this.#onPopoverClick);
      document.body.appendChild(pop);
      this.#popover = pop;
    }

    // Reposition spotlight on viewport changes while active. anchorPopover
    // (called per-step in #renderStep) handles popover repositioning.
    this.#onResize = () => this.#positionSpotlight();
    this.#onScroll = () => this.#positionSpotlight();
    window.addEventListener('resize', this.#onResize);
    window.addEventListener('scroll', this.#onScroll, true /* capture */);

    // Keyboard navigation
    this.#onKeydown = (e) => {
      if (!this.active) return;
      if (e.key === 'Escape') { e.preventDefault(); this.skip(); }
      else if (e.key === 'ArrowRight') { e.preventDefault(); this.next(); }
      else if (e.key === 'ArrowLeft')  { e.preventDefault(); this.previous(); }
    };
    document.addEventListener('keydown', this.#onKeydown);
  }

  #teardown() {
    this.#anchorCleanup?.();
    this.#anchorCleanup = null;
    if (this.#onResize) window.removeEventListener('resize', this.#onResize);
    if (this.#onScroll) window.removeEventListener('scroll', this.#onScroll, true);
    if (this.#onKeydown) document.removeEventListener('keydown', this.#onKeydown);
    this.#onResize = this.#onScroll = this.#onKeydown = null;
    try { this.#popover?.hidePopover?.(); } catch { /* noop */ }
    this.#popover?.removeEventListener('click', this.#onPopoverClick);
    this.#popover?.remove();
    this.#popover = null;
    this.#spotlight?.remove();
    this.#spotlight = null;
  }

  #renderStep() {
    const step = this.#steps[this.step];
    if (!step) return;

    const total = this.#steps.length;
    const isLast = this.step === total - 1;
    const isFirst = this.step === 0;
    const title = step.getAttribute('title') || '';
    const placement = step.getAttribute('placement') || 'bottom';
    const targetSel = step.getAttribute('target') || '';
    const target = targetSel ? document.querySelector(targetSel) : null;

    // Spotlight position
    this.#currentTarget = target;
    this.#positionSpotlight();

    // Popover content
    const indicator = `${this.step + 1} of ${total}`;
    const bodyHTML = step.innerHTML;
    this.#popover.innerHTML = `
      <div data-tour-header>
        <span data-tour-indicator>${indicator}</span>
        <span></span>
      </div>
      <h3 data-tour-title>${escapeHtml(title)}</h3>
      <div data-tour-body>${bodyHTML}</div>
      <div data-tour-footer>
        <button-ui data-tour-skip variant="ghost" size="sm" text="Skip"></button-ui>
        <div data-tour-nav>
          ${isFirst ? '' : '<button-ui data-tour-previous variant="outline" size="sm" text="Back"></button-ui>'}
          <button-ui data-tour-next variant="primary" size="sm" text="${isLast ? 'Done' : 'Next →'}"></button-ui>
        </div>
      </div>
    `;

    // Open the popover (idempotent) + position it.
    try { this.#popover.showPopover(); } catch { /* popover API unavailable */ }
    this.#anchorCleanup?.();
    if (target) {
      this.#anchorCleanup = anchorPopover(target, this.#popover, { placement, gap: 12 });
    } else {
      // No target — center the popover in the viewport
      this.#popover.style.position = 'fixed';
      this.#popover.style.top = '50%';
      this.#popover.style.left = '50%';
      this.#popover.style.transform = 'translate(-50%, -50%)';
    }

    // Move focus to the popover so keyboard nav works immediately
    queueMicrotask(() => this.#popover?.focus?.());
  }

  #currentTarget = null;

  #positionSpotlight() {
    if (!this.#spotlight) return;
    const target = this.#currentTarget;
    if (!target) {
      this.#spotlight.setAttribute('data-tour-no-target', '');
      return;
    }
    this.#spotlight.removeAttribute('data-tour-no-target');
    const rect = target.getBoundingClientRect();
    // Add halo padding around the target
    const cs = getComputedStyle(this);
    const halo = parseFloat(cs.getPropertyValue('--tour-spotlight-padding')) || 8;
    const top  = Math.max(0, rect.top - halo);
    const left = Math.max(0, rect.left - halo);
    const w = rect.width + halo * 2;
    const h = rect.height + halo * 2;
    this.#spotlight.style.top = `${top}px`;
    this.#spotlight.style.left = `${left}px`;
    this.#spotlight.style.width = `${w}px`;
    this.#spotlight.style.height = `${h}px`;
  }

  #onPopoverClick = (e) => {
    const t = e.target;
    if (!t || !t.closest) return;
    if (t.closest('[data-tour-skip]')) { this.skip(); return; }
    if (t.closest('[data-tour-previous]')) { this.previous(); return; }
    if (t.closest('[data-tour-next]')) { this.next(); return; }
  };
}

/**
 * `<tour-step>` — data carrier for a single step inside `<tour-ui>`.
 *
 * Renders nothing on its own (`display: none` via tour.css). tour-ui
 * reads target / title / placement / body content from it on activation.
 */
export class UITourStep extends UIElement {
  static properties = {
    target:    { type: String, default: '',       reflect: true },
    title:     { type: String, default: '',       reflect: true },
    placement: { type: String, default: 'bottom', reflect: true },
  };

  static template = () => null;

  // No connected/disconnected behavior needed — pure data carrier.
}

function escapeHtml(s) {
  return String(s ?? '')
    .replace(/&/g, '&amp;')
    .replace(/</g, '&lt;')
    .replace(/>/g, '&gt;')
    .replace(/"/g, '&quot;')
    .replace(/'/g, '&#39;');
}
