// Tabs -- a tablist over panels.
//
//   <Tabs items='[{"label":"Overview","value":"overview"}]' active="overview" />
//
// With mode="url" the selection is mirrored into a query parameter using
// history.pushState, so the panel swaps without a page load and the back
// button works. The query parameter is used rather than the hash because it
// survives a reload and does not collide with in-page anchors or Scrollspy.
//
// NOTE: the style block uses /* */ comments only -- // is not a CSS comment
// and silently swallows the rule that follows it.
component Tabs {
  outputs {
    change(payload: { value: string; item: object; index: number })
    select(payload: { value: string; item: object; index: number })
  }

  props {
    color: string = "primary"
    size: string = "default"
    items: unknown[] = []
    active: string = ""
    orientation: string = "horizontal"
    mode: string = "client"
    param: string = "tab"
    label: string = "Tabs"
    class: string = ""
  }

  state activeValue = ""
  // Which way the panel should slide in. Set on every change so the animation
  // follows the direction of travel rather than always coming from one side.
  state slideFrom = "forward"

  functions {
    shared function itemList() {
      return Array.isArray(items) ? items : []
    }

    shared function valueOf(item, index) {
      return String(item.value || item.id || index)
    }

    /*
     * In url mode the query parameter is the source of truth, not local state.
     *
     * The client router owns popstate and swaps the whole page on back and
     * forward, which discards component state anyway. Reading the URL means
     * the right tab simply falls out of whatever render happens next, with no
     * listener to lose and nothing to keep in step.
     */
    shared function currentValue() {
      if (mode === "url" && typeof window !== "undefined" && window.location) {
        var fromUrl = new URLSearchParams(window.location.search).get(param || "tab")
        return fromUrl ? fromUrl : defaultValue()
      }
      if (activeValue) {
        return activeValue
      }
      return defaultValue()
    }

    // The selection this instance started with. The runtime falls back to it
    // when the back button lands on a URL that has no tab parameter at all.
    shared function defaultValue() {
      if (active) {
        return active
      }
      var list = itemList()
      return list.length ? valueOf(list[0], 0) : ""
    }

    shared function isSelected(item, index) {
      return valueOf(item, index) === currentValue()
    }

    shared function rovingAxis() {
      return orientation === "vertical" ? "vertical" : "horizontal"
    }

    client function selectTab(item, index, sourceEvent) {
      if (item.disabled) {
        return
      }
      var value = valueOf(item, index)
      var list = itemList()
      var previous = -1
      for (var scan = 0; scan < list.length; scan += 1) {
        if (valueOf(list[scan], scan) === currentValue()) {
          previous = scan
        }
      }
      slideFrom = previous > index ? "back" : "forward"
      activeValue = value

      if (mode === "url" && window.history && window.history.pushState) {
        var url = new URL(window.location.href)
        url.searchParams.set(param || "tab", value)
        window.history.pushState({}, "", url.toString())
      }

      output.change({ value: value, item: item, index: index })
      output.select({ value: value, item: item, index: index })
    }
  }

  view {
    <section
      {...attrs}
      data-ui-component="Tabs"
      class='wire-tabs {class}'
      data-orientation='{orientation}'
      data-color='{color}'
      data-size='{size}'
      data-slide='{slideFrom}'
      data-mode='{mode}'
      data-param='{param}'
    >
      <div
        class="wire-tabs__list"
        role="tablist"
        aria-label='{label}'
        aria-orientation='{orientation}'
        data-wrn-roving='{rovingAxis()}'
      >
        {#each itemList() as item, index}
          <button
            type="button"
            class="wire-tabs__tab"
            role="tab"
            data-value='{valueOf(item, index)}'
            data-wrn-roving-item="true"
            id='wire-tab-{valueOf(item, index)}'
            aria-controls='wire-panel-{valueOf(item, index)}'
            aria-selected='{isSelected(item, index) ? "true" : "false"}'
            aria-disabled='{item.disabled ? "true" : "false"}'
            @click='selectTab(item, index, event)'
          >
            <span class='wire-tabs__icon {item.icon}' data-show="item.icon" aria-hidden="true"></span>
            <span class="wire-tabs__label">{item.label || item.title}</span>
            <span class="wire-tabs__badge" data-show="item.badge">{item.badge}</span>
          </button>
        {/each}
      </div>

      <div class="wire-tabs__panels">
        {#each itemList() as item, index}
          <div
            class="wire-tabs__panel"
            role="tabpanel"
            id='wire-panel-{valueOf(item, index)}'
            aria-labelledby='wire-tab-{valueOf(item, index)}'
            tabindex="0"
            data-show='isSelected(item, index)'
          >
            <h3 class="wire-tabs__title" data-show="item.title && item.title !== item.label">
              {item.title}
            </h3>
            <p class="wire-tabs__description" data-show="item.description">{item.description}</p>
            <div class="wire-tabs__content" data-show="item.content">{item.content}</div>
            <slot name="panel-{valueOf(item, index)}"></slot>
          </div>
        {/each}
        <slot />
      </div>
    </section>
  }

  style {
    .wire-tabs {
      --tabs-accent: var(--wire-color-primary);
      display: flex;
      flex-direction: column;
      gap: 1rem;
      max-width: 100%;
    }

    .wire-tabs[data-color="secondary"] {
      --tabs-accent: var(--wire-color-secondary);
    }

    .wire-tabs[data-color="success"] {
      --tabs-accent: var(--wire-color-success);
    }

    .wire-tabs[data-color="danger"] {
      --tabs-accent: var(--wire-color-danger);
    }

    .wire-tabs[data-color="info"] {
      --tabs-accent: var(--wire-color-info);
    }

    .wire-tabs[data-size="sm"] {
      font-size: 0.82rem;
    }

    .wire-tabs[data-size="lg"] {
      font-size: 1rem;
    }

    .wire-tabs[data-orientation="vertical"] {
      flex-direction: row;
      align-items: flex-start;
    }

    .wire-tabs__list {
      display: flex;
      gap: 0.25rem;
      min-width: 0;
      padding: 0.25rem;
      border: 1px solid var(--wire-color-border);
      border-radius: var(--wire-radius-md);
      background: var(--wire-color-surface-soft);
      /* A long tablist scrolls rather than wrapping into an unusable stack. */
      overflow-x: auto;
    }

    .wire-tabs[data-orientation="vertical"] .wire-tabs__list {
      flex-direction: column;
      flex: 0 0 auto;
      width: 14rem;
      overflow-x: visible;
    }

    .wire-tabs__tab {
      appearance: none;
      display: inline-flex;
      align-items: center;
      justify-content: center;
      gap: 0.4rem;
      flex: 1 0 auto;
      padding: 0.55rem 0.9rem;
      border: 0;
      border-radius: var(--wire-radius-sm);
      background: transparent;
      color: var(--wire-color-text-muted);
      font: inherit;
      font-size: 0.88rem;
      font-weight: 600;
      white-space: nowrap;
      cursor: pointer;
      transition:
        background 160ms ease,
        color 160ms ease;
    }

    .wire-tabs[data-orientation="vertical"] .wire-tabs__tab {
      justify-content: flex-start;
    }

    .wire-tabs__tab:hover {
      color: var(--wire-color-text);
    }

    .wire-tabs__tab:focus-visible {
      outline: 2px solid var(--tabs-accent);
      outline-offset: 2px;
    }

    .wire-tabs__tab[aria-selected="true"] {
      background: var(--wire-color-surface);
      color: var(--tabs-accent);
      box-shadow: var(--wire-shadow-1);
    }

    .wire-tabs__tab[aria-disabled="true"] {
      opacity: 0.5;
      pointer-events: none;
    }

    .wire-tabs__badge {
      padding: 0.05rem 0.4rem;
      border-radius: 999px;
      background: color-mix(in srgb, var(--tabs-accent) 16%, transparent);
      color: var(--tabs-accent);
      font-size: 0.72rem;
    }

    .wire-tabs__panels {
      min-width: 0;
      flex: 1 1 auto;
    }

    .wire-tabs__panel {
      outline: none;
      animation: wire-tabs-slide-forward 220ms cubic-bezier(0.22, 1, 0.36, 1) both;
    }

    .wire-tabs[data-slide="back"] .wire-tabs__panel {
      animation-name: wire-tabs-slide-back;
    }

    /* The panels clip their own slide so it never widens the page. */
    .wire-tabs__panels {
      overflow-x: clip;
    }

    .wire-tabs__panel:focus-visible {
      outline: 2px solid var(--tabs-accent);
      outline-offset: 4px;
      border-radius: var(--wire-radius-sm);
    }

    .wire-tabs__title {
      margin: 0 0 0.35rem;
      font-size: 1rem;
      font-weight: 700;
    }

    .wire-tabs__description {
      margin: 0;
      color: var(--wire-color-text-muted);
      line-height: 1.6;
    }

    .wire-tabs__content {
      margin-top: 0.6rem;
    }

    @keyframes wire-tabs-slide-forward {
      from {
        opacity: 0;
        transform: translateX(1.25rem);
      }
      to {
        opacity: 1;
        transform: none;
      }
    }

    @keyframes wire-tabs-slide-back {
      from {
        opacity: 0;
        transform: translateX(-1.25rem);
      }
      to {
        opacity: 1;
        transform: none;
      }
    }

    /* Respect a reduced-motion preference: swap instantly instead. */
    @media (prefers-reduced-motion: reduce) {
      .wire-tabs__panel {
        animation: none;
      }
    }

    @media (max-width: 639px) {
      .wire-tabs[data-orientation="vertical"] {
        flex-direction: column;
      }

      .wire-tabs[data-orientation="vertical"] .wire-tabs__list {
        width: 100%;
        flex-direction: row;
        overflow-x: auto;
      }
    }
  }
}
