import { defineComponent, h, ref, watch, computed, nextTick, shallowRef } from 'vue'
import { CCollapse } from '../collapse'
import type { StepperStepData } from './types'

export const CStepper = defineComponent({
  name: 'CStepper',
  inheritAttrs: false,
  props: {
    /**
     * The default active step index when not using `v-model`.
     */
    activeStepNumber: {
      type: Number,
      default: 1,
    },

    /**
     * Optional unique ID used for accessibility attributes like `aria-labelledby`.
     */
    id: String,

    /**
     * Sets the layout direction of the Vue Stepper component.
     *
     * - `'horizontal'` – Steps are placed side-by-side.
     * - `'vertical'` – Steps are stacked vertically (ideal for mobile).
     *
     * This makes the Vue Form Wizard adaptable to various screen sizes.
     */
    layout: {
      type: String as () => 'horizontal' | 'vertical',
      default: 'horizontal',
    },

    /**
     * Enables linear step progression in the Vue Form Wizard.
     *
     * - `true`: Steps must be completed in order.
     * - `false`: Users can navigate freely between steps.
     *
     * Useful for enforcing validation and structured data entry in Vue multi-step forms.
     */
    linear: {
      type: Boolean,
      default: true,
    },

    /**
     * The current active step index of the Vue Stepper (used for controlled mode with `v-model`).
     *
     * If this prop is not provided, the Vue Form Wizard will use `activeStepNumber` as its initial value.
     */
    modelValue: Number,

    /**
     * Defines the list of steps in the Vue Stepper.
     *
     * Each step should include:
     * - `label`: The title displayed in the step.
     * - `indicator` (optional): Custom icon, number, or marker.
     * - `formRef` (optional): A reference to the `<form>` used for validation in Vue Form Wizard scenarios.
     */
    steps: {
      type: Array as () => (StepperStepData | string)[],
      required: true,
    },

    /**
     * Controls the layout of the step indicator and label.
     *
     * - `'horizontal'`: Icon and label are side-by-side.
     * - `'vertical'`: Label is shown below the icon.
     *
     * Applies only when `layout` is set to `'horizontal'`.
     */
    stepButtonLayout: {
      type: String as () => 'horizontal' | 'vertical',
      default: 'horizontal',
    },

    /**
     * Enables validation of forms within each step of the Vue Form Wizard.
     *
     * When set to `true`, the user cannot proceed unless the current step's form passes `checkValidity()`.
     * Each step must expose a native form element via the `formRef` slot binding.
     */
    validation: {
      type: Boolean,
      default: true,
    },
  },
  emits: [
    /**
     * Emitted when the user successfully finishes all steps in the Vue Form Wizard.
     */
    'finish',

    /**
     * Emitted when the stepper is reset to its initial state.
     */
    'reset',

    /**
     * Emitted on any manual or programmatic step change.
     */
    'stepChange',

    /**
     * Emitted after each step's form is validated (when `validation: true`).
     */
    'stepValidationComplete',

    /**
     * Emitted when the current active step changes.
     *
     * Useful for syncing state with `v-model`.
     */
    'update:modelValue',
  ],
  setup(props, { emit, slots, attrs, expose }) {
    const activeStepNumber = ref<number>(props.modelValue ?? props.activeStepNumber)
    const isControlled = computed(() => props.modelValue !== undefined)
    const isFinished = ref(false)
    const stepsRef = ref<HTMLOListElement | null>(null)
    const stepButtonRefs = shallowRef<(HTMLButtonElement | null)[]>([])
    const formRefs = shallowRef<(HTMLFormElement | null)[]>([])

    const registerFormRef = (stepNumber: number) => (el: HTMLFormElement | null) => {
      formRefs.value[stepNumber - 1] = el
    }

    watch(
      () => props.modelValue,
      (val) => {
        if (val !== undefined) activeStepNumber.value = val
      }
    )

    watch(activeStepNumber, (val) => {
      if (isControlled.value) emit('update:modelValue', val)
    })

    const isStepValid = (stepNumber: number): boolean => {
      if (!props.validation) {
        return true
      }

      const form = formRefs.value[stepNumber - 1]

      if (form) {
        const isValid = form.checkValidity()
        emit('stepValidationComplete', { stepNumber: stepNumber, isValid: isValid })

        if (form && !isValid) {
          if (!form.noValidate) {
            form.reportValidity()
          }

          return false
        }
      }

      return true
    }

    const setActiveStep = (stepNumber: number, bypassValidation = false) => {
      if (
        stepNumber < 1 ||
        stepNumber > props.steps.length ||
        stepNumber === activeStepNumber.value
      ) {
        return
      }

      if (
        !bypassValidation &&
        stepNumber > activeStepNumber.value &&
        !isStepValid(activeStepNumber.value)
      )
        return

      activeStepNumber.value = stepNumber
      emit('stepChange', stepNumber)
    }

    const next = () => {
      if (activeStepNumber.value <= props.steps.length) {
        setActiveStep(activeStepNumber.value + 1)
      } else {
        finish()
      }
    }

    const prev = () => {
      if (activeStepNumber.value > 1) {
        setActiveStep(activeStepNumber.value - 1, true)
      }
    }

    const finish = () => {
      if (activeStepNumber.value === props.steps.length && isStepValid(activeStepNumber.value)) {
        isFinished.value = true
        emit('finish')
      }
    }

    const reset = () => {
      formRefs.value.forEach((form) => form?.reset?.())
      activeStepNumber.value = props.activeStepNumber
      isFinished.value = false
      emit('reset')
      emit('stepChange', props.activeStepNumber)
      nextTick(() => {
        stepButtonRefs.value[props.activeStepNumber - 1]?.focus()
      })
    }

    const handleKeyDown = (event: KeyboardEvent) => {
      const buttons = stepButtonRefs.value
      const current = event.target as HTMLButtonElement
      const index = buttons.indexOf(current)
      if (index === -1) return

      let nextIndex = index
      switch (event.key) {
        case 'ArrowRight':
        case 'ArrowDown': {
          nextIndex = (index + 1) % buttons.length
          break
        }
        case 'ArrowLeft':
        case 'ArrowUp': {
          nextIndex = (index - 1 + buttons.length) % buttons.length
          break
        }
        case 'Home': {
          nextIndex = 0
          break
        }
        case 'End': {
          nextIndex = buttons.length - 1
          break
        }
        default: {
          return
        }
      }

      event.preventDefault()
      buttons[nextIndex]?.focus()
    }

    expose({ next, prev, finish, reset })

    return () => {
      const isVertical = props.layout === 'vertical'
      stepButtonRefs.value = []

      return h(
        'div',
        {
          ...attrs,
          class: ['stepper', { 'stepper-vertical': isVertical }, attrs.class],
        },
        [
          h(
            'ol',
            {
              class: 'stepper-steps',
              role: 'tablist',
              'aria-orientation': isVertical ? 'vertical' : 'horizontal',
              onKeydown: handleKeyDown,
              ref: stepsRef,
            },
            props.steps.map((step, index) => {
              const stepNumber = index + 1

              const isActive = !isFinished.value && stepNumber === activeStepNumber.value
              const isComplete = isFinished.value || stepNumber < activeStepNumber.value
              const isDisabled =
                isFinished.value || (props.linear && stepNumber > activeStepNumber.value + 1)
              const stepId = `step-${props.id || 'stepper'}-${stepNumber}`
              const panelId = `panel-${props.id || 'stepper'}-${stepNumber}`

              return h(
                'li',
                {
                  key: stepNumber,
                  class: ['stepper-step', props.stepButtonLayout],
                  role: 'presentation',
                },
                [
                  h(
                    'button',
                    {
                      type: 'button',
                      class: ['stepper-step-button', { active: isActive, complete: isComplete }],
                      disabled: isDisabled,
                      id: stepId,
                      role: 'tab',
                      'aria-selected': isActive,
                      tabindex: isActive ? 0 : -1,
                      'aria-controls': panelId,
                      onClick: () =>
                        setActiveStep(
                          stepNumber,
                          !props.linear || stepNumber <= activeStepNumber.value
                        ),
                      ref: (el) => (stepButtonRefs.value[index] = el as HTMLButtonElement),
                    },
                    [
                      h('span', { class: 'stepper-step-indicator' }, [
                        isComplete
                          ? h('span', { class: 'stepper-step-indicator-icon' })
                          : h(
                              'span',
                              { class: 'stepper-step-indicator-text' },
                              typeof step === 'object' && 'indicator' in step
                                ? step.indicator
                                : stepNumber
                            ),
                      ]),
                      h(
                        'span',
                        { class: 'stepper-step-label' },
                        typeof step === 'object' && 'label' in step ? step.label : step
                      ),
                    ]
                  ),
                  stepNumber < props.steps.length && h('div', { class: 'stepper-step-connector' }),
                  isVertical &&
                    h(
                      CCollapse,
                      {
                        class: 'stepper-step-content',
                        id: panelId,
                        role: 'tabpanel',
                        visible: isActive,
                        'aria-hidden': !isActive,
                        'aria-labelledby': stepId,
                        'aria-live': 'polite',
                      },
                      () => slots[`step-${stepNumber}`]?.({ formRef: registerFormRef(stepNumber) })
                    ),
                ]
              )
            })
          ),
          !isVertical &&
            h(
              'div',
              { class: 'stepper-content' },
              props.steps.map((_, index) => {
                const stepNumber = index + 1

                const isActive = !isFinished.value && stepNumber === activeStepNumber.value
                const stepId = `step-${props.id || 'stepper'}-${stepNumber}`
                const panelId = `panel-${props.id || 'stepper'}-${stepNumber}`

                return h(
                  'div',
                  {
                    key: stepNumber,
                    id: panelId,
                    role: 'tabpanel',
                    'aria-hidden': !isActive,
                    'aria-labelledby': stepId,
                    'aria-live': 'polite',
                    class: ['stepper-pane', { active: isActive, show: isActive }],
                  },
                  {
                    default: () =>
                      slots[`step-${stepNumber}`]?.({ formRef: registerFormRef(stepNumber) }),
                  }
                )
              })
            ),
        ]
      )
    }
  },
})
