component ToggleCount {
  outputs {
    change(payload: { value?: string | number | boolean | null; values?: Array<string | number | boolean | null | object>; sourceEvent?: Event; [key: string]: string | number | boolean | null | object } | string | number | boolean | null)
    toggle(payload: { sourceEvent?: Event; [key: string]: string | number | boolean | null | object })
  }

  props {
    size: string = "default"
    color: string = "primary"
    variant: string = "segmented"
    class: string = ""

    name: string = "billing-cycle"
    value: string = "monthly"
    firstValue: string = "monthly"
    firstLabel: string = "Monthly"
    secondValue: string = "annual"
    secondLabel: string = "Annual"
    ariaLabel: string = "Billing frequency"

    items: unknown[] = []
    currency: string = "$"
    suffix: string = ""
    firstValueKey: string = "monthly"
    secondValueKey: string = "annual"
    emptyValue: string = "—"

    align: string = "end"
    fullWidth: boolean = true
    disabled: boolean = false
    animate: boolean = true
    animationDuration: number = 450
    animationSteps: number = 18

  }

  state selectedValue = value

  functions {
    shared function isFirstSelected() {
      return selectedValue === firstValue
    }

    shared function isSecondSelected() {
      return selectedValue === secondValue
    }

    shared function displayValue(item) {
      if (isSecondSelected()) {
        return item[secondValueKey] !== undefined
          ? item[secondValueKey]
          : emptyValue
      }

      return item[firstValueKey] !== undefined
        ? item[firstValueKey]
        : emptyValue
    }

    client function dispatchToggleEvent(sourceEvent, previousValue, payload) {
      payload = {
        component: "ToggleCount",
        name: name,
        value: selectedValue,
        previousValue: previousValue,
        firstValue: firstValue,
        secondValue: secondValue
      }
      output.change(payload)
      output.toggle(payload)
    }

    client function selectValue(sourceEvent, nextValue, previousValue) {
      if (disabled || selectedValue === nextValue) {
        return
      }

      previousValue = selectedValue
      selectedValue = nextValue

      if (
        animate &&
        !window.matchMedia("(prefers-reduced-motion: reduce)").matches
      ) {
        animateDisplayedValues(sourceEvent, previousValue)
      }

      dispatchToggleEvent(sourceEvent, previousValue)
    }

    client function animateDisplayedValues(sourceEvent, previousValue, root, nodes, animationToken) {
      root = sourceEvent.currentTarget.closest("[data-wrn-toggle-count]")

      if (!root) {
        return
      }

      animationToken = String(Date.now()) + ":" + String(selectedValue)
      root.setAttribute("data-animation-token", animationToken)
      nodes = root.querySelectorAll("[data-toggle-count-value]")
      animateValueAt(nodes, 0, previousValue, root, animationToken)
    }

    client function animateValueAt(
      nodes,
      index,
      previousValue,
      root,
      animationToken,
      node,
      fromValue,
      toValue
    ) {
      if (index >= nodes.length) {
        return
      }

      node = nodes[index]
      fromValue = Number(
        previousValue === secondValue
          ? node.getAttribute("data-second-value")
          : node.getAttribute("data-first-value")
      )
      toValue = Number(
        selectedValue === secondValue
          ? node.getAttribute("data-second-value")
          : node.getAttribute("data-first-value")
      )

      if (!Number.isNaN(fromValue) && !Number.isNaN(toValue)) {
        node.replaceChildren(String(fromValue))
        scheduleValueFrame(
          node,
          fromValue,
          toValue,
          1,
          Math.max(1, Number(animationSteps) || 1),
          Math.max(1, Number(animationDuration) || 1),
          root,
          animationToken
        )
      }

      animateValueAt(
        nodes,
        index + 1,
        previousValue,
        root,
        animationToken
      )
    }

    client function scheduleValueFrame(
      node,
      fromValue,
      toValue,
      frame,
      totalFrames,
      duration,
      root,
      animationToken,
      nextValue
    ) {
      if (frame > totalFrames) {
        return
      }

      nextValue = Math.round(
        fromValue +
        (toValue - fromValue) * (frame / totalFrames)
      )

      setTimeout(
        applyAnimatedValue,
        Math.max(1, duration * frame / totalFrames),
        node,
        nextValue,
        root,
        animationToken
      )

      scheduleValueFrame(
        node,
        fromValue,
        toValue,
        frame + 1,
        totalFrames,
        duration,
        root,
        animationToken
      )
    }

    client function applyAnimatedValue(
      node,
      nextValue,
      root,
      animationToken
    ) {
      if (
        root &&
        root.getAttribute("data-animation-token") !== animationToken
      ) {
        return
      }

      node.replaceChildren(String(nextValue))
    }

    client function toggleValue(sourceEvent) {
      selectValue(
        sourceEvent,
        isFirstSelected() ? secondValue : firstValue
      )
    }
  }

  view {
    <section
      {...attrs}
      data-wrn-toggle-count
      data-variant="{variant}"
      data-value="{selectedValue}"
      data-disabled="{disabled ? 'true' : 'false'}"
      class="wrn-component wrn-component--color-{color} wrn-component--size-{size} wrn-next--toggle-count wrn-next--toggle-count-{variant} {fullWidth ? 'wrn-next--toggle-count-full' : ''} {disabled ? 'wrn-next--disabled' : ''} {class}"
    >
      <input type="hidden" name="{name}" value="{selectedValue}" />

      <div class="wrn-next__toggle-count-control wrn-next__toggle-count-control--{align}">
        {#if variant === "switch"}
          <span data-selected="{isFirstSelected() ? 'true' : 'false'}">{firstLabel}</span>
          <button
            type="button"
            role="switch"
            aria-label="{ariaLabel}"
            aria-checked="{isSecondSelected() ? 'true' : 'false'}"
            disabled="{disabled}"
            @click="toggleValue(event)"
            class="wrn-next__toggle-count-switch"
          >
            <span aria-hidden="true"></span>
          </button>
          <span data-selected="{isSecondSelected() ? 'true' : 'false'}">{secondLabel}</span>
        {:else}
          <div class="wrn-next__toggle-count-segmented" role="group" aria-label="{ariaLabel}">
            <button
              type="button"
              aria-pressed="{isFirstSelected() ? 'true' : 'false'}"
              disabled="{disabled}"
              @click="selectValue(event, firstValue)"
            >{firstLabel}</button>
            <button
              type="button"
              aria-pressed="{isSecondSelected() ? 'true' : 'false'}"
              disabled="{disabled}"
              @click="selectValue(event, secondValue)"
            >{secondLabel}</button>
          </div>
        {/if}
      </div>

      {#if items.length > 0}
        <div class="wrn-next__toggle-count-items">
          {#each items as item}
            <article>
              <span>{item.label || item.name}</span>
              <strong>
                {#if currency}<small>{currency}</small>{/if}
                <span
                  data-toggle-count-value
                  data-first-value="{item[firstValueKey]}"
                  data-second-value="{item[secondValueKey]}"
                >{displayValue(item)}</span>
                {#if suffix}<small>{suffix}</small>{/if}
              </strong>
              {#if item.description}<small>{item.description}</small>{/if}
            </article>
          {/each}
        </div>
      {/if}
    </section>
  }

  style {
    .wrn-next__toggle-count-control--start {
      justify-content: flex-start;
    }
    .wrn-next__toggle-count-control--center {
      justify-content: center;
    }
    .wrn-next__toggle-count-control--end {
      justify-content: flex-end;
    }

    .wrn-next--toggle-count {
      --wrn-toggle-count-accent: var(--wrn-component-color);
      display: grid;
      width: fit-content;
      gap: 0.75rem;
      color: var(--wrn-color-text);
    }

    .wrn-next--toggle-count-full {
      width: 100%;
    }

    .wrn-next__toggle-count-control {
      display: flex;
    }

    .wrn-next__toggle-count-segmented {
      display: inline-flex;
      gap: 0.2rem;
      padding: 0.2rem;
      border-radius: calc(var(--wrn-radius-sm) + 0.15rem);
      background: var(--wrn-color-surface-2);
      box-shadow: inset 0 0 0 1px var(--wrn-color-border);
    }

    .wrn-next__toggle-count-segmented button {
      min-height: 2.35rem;
      padding: 0.45rem 0.8rem;
      border: 0;
      border-radius: var(--wrn-radius-sm);
      background: transparent;
      color: var(--wrn-color-muted);
      font: inherit;
      font-size: 0.78rem;
      font-weight: 700;
      cursor: pointer;
    }

    .wrn-next__toggle-count-segmented button[aria-pressed="true"] {
      background: var(--wrn-color-surface);
      color: var(--wrn-color-text);
      box-shadow:
        var(--wrn-shadow-1),
        inset 0 -2px var(--wrn-toggle-count-accent);
    }

    .wrn-next__toggle-count-segmented button:focus-visible,
    .wrn-next__toggle-count-switch:focus-visible {
      outline: 2px solid var(--wrn-toggle-count-accent);
      outline-offset: 2px;
    }

    .wrn-next__toggle-count-segmented button:disabled,
    .wrn-next__toggle-count-switch:disabled {
      cursor: not-allowed;
    }

    .wrn-next__toggle-count-control:has(.wrn-next__toggle-count-switch) {
      align-items: center;
      gap: 0.7rem;
      font-size: 0.78rem;
      font-weight: 700;
    }

    .wrn-next__toggle-count-control > span {
      color: var(--wrn-color-muted);
    }

    .wrn-next__toggle-count-control > span[data-selected="true"] {
      color: var(--wrn-color-text);
    }

    .wrn-next__toggle-count-switch {
      position: relative;
      width: 2.65rem;
      height: 1.45rem;
      padding: 0.15rem;
      border: 1px solid var(--wrn-color-border);
      border-radius: 999px;
      background: var(--wrn-color-surface-2);
      cursor: pointer;
      transition: background-color var(--wrn-motion-fast) var(--wrn-ease-standard);
    }

    .wrn-next__toggle-count-switch > span {
      display: block;
      width: 1rem;
      height: 1rem;
      border-radius: 50%;
      background: var(--wrn-color-text);
      box-shadow: var(--wrn-shadow-1);
      transition: transform var(--wrn-motion-fast) var(--wrn-ease-standard);
    }

    .wrn-next__toggle-count-switch[aria-checked="true"] {
      border-color: var(--wrn-toggle-count-accent);
      background: var(--wrn-toggle-count-accent);
    }

    .wrn-next__toggle-count-switch[aria-checked="true"] > span {
      background: white;
      transform: translateX(1.15rem);
    }

    .wrn-next__toggle-count-items {
      display: grid;
      grid-template-columns: repeat(auto-fit, minmax(8rem, 1fr));
      overflow: hidden;
      border: 1px solid var(--wrn-color-border);
      border-radius: var(--wrn-radius-md);
      background: var(--wrn-color-surface);
    }

    .wrn-next__toggle-count-items article {
      display: grid;
      align-content: start;
      gap: 0.35rem;
      min-width: 0;
      padding: 1rem;
    }

    .wrn-next__toggle-count-items article + article {
      border-left: 1px solid var(--wrn-color-border);
    }

    .wrn-next__toggle-count-items article > span {
      color: var(--wrn-color-muted);
      font-size: 0.75rem;
      font-weight: 600;
    }

    .wrn-next__toggle-count-items strong {
      display: flex;
      align-items: baseline;
      gap: 0.2rem;
      font-size: 1.5rem;
      line-height: 1;
    }

    .wrn-next__toggle-count-items [data-toggle-count-value] {
      min-width: 2ch;
      font-variant-numeric: tabular-nums;
      transition:
        color var(--wrn-motion-fast) var(--wrn-ease-standard),
        transform var(--wrn-motion-fast) var(--wrn-ease-standard);
    }

    .wrn-next__toggle-count-items strong small {
      color: inherit;
      font-size: 0.65em;
    }

    .wrn-next__toggle-count-items article > small {
      color: var(--wrn-color-muted);
      font-size: 0.68rem;
    }

    @media (max-width: 36rem) {
    .wrn-next__toggle-count-control {
        justify-content: center;
      }
    .wrn-next__toggle-count-items {
        grid-template-columns: 1fr;
      }
    .wrn-next__toggle-count-items article + article {
        border-top: 1px solid var(--wrn-color-border);
        border-left: 0;
      }
    }

    @media (prefers-reduced-motion: reduce) {
    .wrn-next__toggle-count-items [data-toggle-count-value] {
        transition: none;
      }
    }

    /* Shared component foundation. */

        .wrn-component {
          --wrn-component-color: var(--wrn-color-primary);
          --wrn-component-scale: 1;
          margin: 0;
          color: var(--wrn-color-text);
          font-family: var(--wrn-font-sans, inherit);
        }

        .wrn-component--color-primary {
          --wrn-component-color: var(--wrn-color-primary);
        }

        .wrn-component--color-secondary {
          --wrn-component-color: var(--wrn-color-secondary);
        }

        .wrn-component--color-success {
          --wrn-component-color: var(--wrn-color-success);
        }

        .wrn-component--color-warning {
          --wrn-component-color: var(--wrn-color-warning);
        }

        .wrn-component--color-danger {
          --wrn-component-color: var(--wrn-color-danger);
        }

        .wrn-component--color-info {
          --wrn-component-color: var(--wrn-color-info);
        }

        .wrn-component--size-xs {
          --wrn-component-scale: 0.78;
        }

        .wrn-component--size-sm {
          --wrn-component-scale: 0.88;
        }

        .wrn-component--size-default,
        .wrn-component--size-md {
          --wrn-component-scale: 1;
        }

        .wrn-component--size-lg {
          --wrn-component-scale: 1.14;
        }

        .wrn-component--size-xl {
          --wrn-component-scale: 1.28;
        }

        .wrn-component:not(.wrn-btn) {
          font-size: calc(1em * var(--wrn-component-scale));
        }

        .wrn-component :is(a, button, input, select, textarea):focus-visible {
          outline-color: var(--wrn-component-color);
        }

        .wrn-component p {
          margin: 0;
          color: var(--wrn-color-muted);
          line-height: 1.6;
        }
  }
}
