// Stepper -- ordered progress through a sequence.
//
//   <Stepper steps='[{"label":"Account"},{"label":"Billing"}]' active={1} />
//
// Each step can be authored by hand instead of using the built-in body, by
// passing a slot named for its index:
//
//   <Stepper steps={steps} active={1}>
//     <div data-slot="step-1">...anything...</div>
//   </Stepper>
//
// NOTE: the style block uses /* */ comments only -- // is not a CSS comment
// and silently swallows the rule that follows it.
component Stepper {
  outputs {
    change(payload: { index: number; step: object })
    back(payload: { index: number; step: object })
    next(payload: { index: number; step: object })
    skip(payload: { index: number; step: object })
    finish(payload: { index: number; step: object })
  }

  props {
    color: string = "primary"
    size: string = "default"
    steps: unknown[] = []
    active: number = 0
    orientation: string = "horizontal"
    clickable: boolean = false
    label: string = "Progress"
    // Render each step body, showing only the active one -- same contract as
    // Tabs, so a wizard does not have to hand-roll the panel switching.
    showPanel: boolean = false
    controls: boolean = false
    allowSkip: boolean = false
    // Lets a form hold the step. The component never validates anything
    // itself: the page owns the form, so it sets this while the step is
    // incomplete and clears it once the step passes.
    nextDisabled: boolean = false
    backLabel: string = "Back"
    nextLabel: string = "Next"
    skipLabel: string = "Skip"
    finishLabel: string = "Finish"
    class: string = ""
  }

  functions {
    shared function stepList() {
      return Array.isArray(steps) ? steps : []
    }

    shared function activeIndex() {
      var count = stepList().length
      if (count < 1) {
        return 0
      }
      var value = Number(active)
      if (!value || value < 0) {
        return 0
      }
      return Math.min(value, count - 1)
    }

    shared function statusFor(index) {
      if (index < activeIndex()) {
        return "complete"
      }
      if (index === activeIndex()) {
        return "current"
      }
      return "upcoming"
    }

    // An empty orientation is how the roving runtime is told to stay out of
    // the way, so a read-only stepper never takes arrow-key focus.
    shared function rovingAxis() {
      if (!clickable) {
        return ""
      }
      return orientation === "vertical" ? "vertical" : "horizontal"
    }

    shared function isActiveStep(index) {
      return index === activeIndex()
    }

    shared function lastIndex() {
      return Math.max(0, stepList().length - 1)
    }

    shared function onLastStep() {
      return activeIndex() >= lastIndex()
    }

    shared function activeStep() {
      var list = stepList()
      return list.length ? list[activeIndex()] : {}
    }

    client function goBack() {
      var target = Math.max(0, activeIndex() - 1)
      output.back({ index: target, step: stepList()[target] || {} })
      output.change({ index: target, step: stepList()[target] || {} })
    }

    client function goNext() {
      if (nextDisabled) {
        return
      }
      if (onLastStep()) {
        output.finish({ index: activeIndex(), step: activeStep() })
        return
      }
      var target = Math.min(lastIndex(), activeIndex() + 1)
      output.next({ index: target, step: stepList()[target] || {} })
      output.change({ index: target, step: stepList()[target] || {} })
    }

    client function goSkip() {
      var target = Math.min(lastIndex(), activeIndex() + 1)
      output.skip({ index: target, step: stepList()[target] || {} })
      output.change({ index: target, step: stepList()[target] || {} })
    }

    client function selectStep(index, step) {
      if (!clickable) {
        return
      }
      output.change({ index: index, step: step })
    }
  }

  view {
    <div
      {...attrs}
      data-ui-component="Stepper"
      class='wire-stepper {class}'
      data-orientation='{orientation}'
      data-color='{color}'
      data-size='{size}'
      data-clickable='{clickable}'
    >
    <ol
      class="wire-stepper__list"
      data-wrn-roving='{rovingAxis()}'
      aria-label='{label}'
    >
      {#each stepList() as step, index}
        <li
          class="wire-stepper__step"
          data-status='{statusFor(index)}'
          aria-current='{statusFor(index) === "current" ? "step" : "false"}'
        >
          <button
            type="button"
            class="wire-stepper__button"
            data-wrn-roving-item='{clickable ? "true" : "false"}'
            @click='selectStep(index, step)'
          >
            <span class="wire-stepper__marker" aria-hidden="true">
              <span class='wire-stepper__icon {step.icon}' data-show="step.icon"></span>
              <span class="wire-stepper__number" data-show="!step.icon">{index + 1}</span>
            </span>
            <span class="wire-stepper__body">
              <span class="wire-stepper__label">{step.label}</span>
              <span class="wire-stepper__description" data-show="step.description">
                {step.description}
              </span>
            </span>
          </button>
          <span class="wire-stepper__custom">
            <slot name="step-{index}"></slot>
          </span>
        </li>
      {/each}
    </ol>

    <div class="wire-stepper__panels" data-show="showPanel">
      {#each stepList() as step, index}
        <div
          class="wire-stepper__panel"
          data-show='isActiveStep(index)'
          aria-hidden='{index === activeIndex() ? "false" : "true"}'
        >
          <h4 class="wire-stepper__panel-title" data-show="step.title">{step.title}</h4>
          <p class="wire-stepper__panel-body" data-show="step.content">{step.content}</p>
          <slot name="panel-{index}"></slot>
        </div>
      {/each}
    </div>

    <div class="wire-stepper__controls" data-show="controls">
      <button
        type="button"
        class="wire-stepper__button-control wire-stepper__back"
        disabled='{activeIndex() === 0}'
        @click='goBack()'
      >
        {backLabel}
      </button>
      <span class="wire-stepper__controls-spacer"></span>
      <button
        type="button"
        class="wire-stepper__button-control wire-stepper__skip"
        data-show="allowSkip && !onLastStep()"
        @click='goSkip()'
      >
        {skipLabel}
      </button>
      <button
        type="button"
        class="wire-stepper__button-control wire-stepper__next"
        data-primary="true"
        disabled='{nextDisabled}'
        @click='goNext()'
      >
        {onLastStep() ? finishLabel : nextLabel}
      </button>
    </div>

      <slot />
    </div>
  }

  style {
    .wire-stepper {
      --stepper-accent: var(--wire-color-primary);
      max-width: 100%;
    }

    .wire-stepper__list {
      display: flex;
      gap: 0.5rem;
      margin: 0;
      padding: 0;
      list-style: none;
      max-width: 100%;
    }

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

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

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

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

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

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

    .wire-stepper[data-orientation="vertical"] .wire-stepper__list {
      flex-direction: column;
    }

    .wire-stepper__step {
      display: flex;
      flex-direction: column;
      flex: 1 1 0;
      min-width: 0;
      gap: 0.35rem;
    }

    .wire-stepper__button {
      appearance: none;
      display: flex;
      align-items: center;
      gap: 0.6rem;
      width: 100%;
      padding: 0.5rem;
      border: 0;
      border-radius: var(--wire-radius-sm);
      background: transparent;
      color: inherit;
      font: inherit;
      text-align: left;
      cursor: default;
    }

    .wire-stepper[data-clickable="true"] .wire-stepper__button {
      cursor: pointer;
    }

    .wire-stepper[data-clickable="true"] .wire-stepper__button:hover {
      background: var(--wire-color-surface-soft);
    }

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

    .wire-stepper__marker {
      display: inline-flex;
      align-items: center;
      justify-content: center;
      flex: 0 0 auto;
      width: 2rem;
      height: 2rem;
      border: 1px solid var(--wire-color-border);
      border-radius: 999px;
      background: var(--wire-color-surface);
      font-size: 0.85rem;
      font-weight: 700;
    }

    .wire-stepper__step[data-status="complete"] .wire-stepper__marker {
      border-color: var(--stepper-accent);
      background: var(--stepper-accent);
      color: var(--wire-color-primary-contrast);
    }

    .wire-stepper__step[data-status="current"] .wire-stepper__marker {
      border-color: var(--stepper-accent);
      color: var(--stepper-accent);
      box-shadow: 0 0 0 3px color-mix(in srgb, var(--stepper-accent) 22%, transparent);
    }

    .wire-stepper__step[data-status="upcoming"] .wire-stepper__marker {
      color: var(--wire-color-text-muted);
    }

    .wire-stepper__body {
      display: flex;
      flex-direction: column;
      min-width: 0;
    }

    .wire-stepper__label {
      font-size: 0.9rem;
      font-weight: 600;
    }

    .wire-stepper__description {
      color: var(--wire-color-text-muted);
      font-size: 0.78rem;
    }

    .wire-stepper__step[data-status="upcoming"] .wire-stepper__label {
      color: var(--wire-color-text-muted);
    }

    .wire-stepper__panels {
      margin-top: 1rem;
    }

    .wire-stepper__panel {
      padding: 1rem;
      border: 1px solid var(--wire-color-border);
      border-radius: var(--wire-radius-md);
      background: var(--wire-color-surface);
      animation: wire-stepper-in 200ms ease both;
    }

    .wire-stepper__panel-title {
      margin: 0 0 0.35rem;
      font-size: 0.95rem;
      font-weight: 700;
    }

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

    .wire-stepper__controls {
      display: flex;
      align-items: center;
      gap: 0.5rem;
      margin-top: 0.85rem;
    }

    .wire-stepper__controls-spacer {
      flex: 1 1 auto;
    }

    .wire-stepper__button-control {
      appearance: none;
      padding: 0.45rem 0.9rem;
      border: 1px solid var(--wire-color-border);
      border-radius: var(--wire-radius-sm);
      background: var(--wire-color-surface);
      color: var(--wire-color-text);
      font: inherit;
      font-size: 0.86rem;
      font-weight: 600;
      cursor: pointer;
    }

    .wire-stepper__button-control:hover:not(:disabled) {
      background: var(--wire-color-surface-soft);
    }

    .wire-stepper__button-control:focus-visible {
      outline: 2px solid var(--stepper-accent);
      outline-offset: 2px;
    }

    .wire-stepper__button-control[data-primary="true"] {
      border-color: var(--stepper-accent);
      background: var(--stepper-accent);
      color: var(--wire-color-primary-contrast);
    }

    /* A held step has to look held, or the button reads as broken. */
    .wire-stepper__button-control:disabled {
      opacity: 0.45;
      cursor: not-allowed;
    }

    @keyframes wire-stepper-in {
      from {
        opacity: 0;
        transform: translateX(0.75rem);
      }
      to {
        opacity: 1;
        transform: none;
      }
    }

    /* A horizontal stepper cannot stay side by side on a phone. */
    @media (max-width: 639px) {
      .wire-stepper__list {
        flex-direction: column;
      }
    }
  }
}
