import React, {
  useState,
  useEffect,
  useRef,
  useImperativeHandle,
  forwardRef,
  HTMLAttributes,
  KeyboardEvent,
  useCallback,
  useId,
} from 'react'
import PropTypes from 'prop-types'
import classNames from 'classnames'

import { CCollapse } from '../collapse'

import { getNextActiveElement } from '../../utils'
import type { StepperRef, StepperStepData, StepperStepValidationResult } from './types'

export interface CStepperProps extends Omit<HTMLAttributes<HTMLDivElement>, 'onReset'> {
  /**
   * Controls the currently active step in the React Stepper component (Controlled mode).
   *
   * Use this prop when you want full control over the active step from the parent component.
   *
   * Use `activeStepNumber` instead. This prop uses 0-based indexing which can be confusing.
   *
   * @deprecated 5.16.0
   */
  activeStepIndex?: number

  /**
   * Controls the currently active step number in the React Stepper component (Controlled mode).
   *
   * Use this prop when you want full control over the active step from the parent component.
   * Step numbers are 1-based (1, 2, 3, ...).
   *
   * @since 5.16.0
   */
  activeStepNumber?: number

  /**
   * Custom CSS class name applied to the main React Stepper container.
   */
  className?: string

  /**
   * Sets the initial active step index for the Form Wizard when the React Stepper is used in Uncontrolled mode.
   *
   * Useful when you want the Stepper to manage its own state internally.
   *
   * Use `defaultActiveStepNumber` instead. This prop uses 0-based indexing which can be confusing.
   *
   * @deprecated 5.16.0
   */
  defaultActiveStepIndex?: number

  /**
   * Sets the initial active step number for the Form Wizard when the React Stepper is used in Uncontrolled mode.
   *
   * Useful when you want the Stepper to manage its own state internally.
   * Step numbers are 1-based (1, 2, 3, ...).
   *
   * @since 5.16.0
   */
  defaultActiveStepNumber?: number

  /**
   * Defines the overall layout orientation of the React Stepper component.
   *
   * - `'horizontal'`: Step indicators and content are placed horizontally (default).
   * - `'vertical'`: Step indicators and content are stacked vertically.
   *
   * Choose `'vertical'` layout for mobile or narrow designs.
   */
  layout?: 'horizontal' | 'vertical'

  /**
   * Defines whether the Stepper enforces linear progression (cannot skip steps).
   *
   * - `true`: Users must complete steps sequentially.
   * - `false`: Users can jump freely between steps.
   */
  linear?: boolean

  /**
   * Callback fired when the user completes the last step of the Form Wizard.
   *
   * Use this to trigger a submit action or redirect after the final step.
   */
  onFinish?: () => void

  /**
   * Callback fired when the user triggers the reset action on the React Stepper.
   *
   * Use this to reset the Stepper state or clear related form data.
   */
  onReset?: () => void

  /**
   * Callback fired when the active step changes in the React Stepper component.
   *
   * @param stepNumber The new active step number (1-based)
   */
  onStepChange?: (stepNumber: number) => void

  /**
   * Callback fired after a validation check completes for a specific step in the Form Wizard.
   *
   * Receives an object with the step index and the validation result (`true` or `false`).
   *
   * Useful for custom validation handling across multi-step forms.
   */
  onStepValidationComplete?: ({ stepNumber, isValid }: StepperStepValidationResult) => void

  /**
   * Defines the steps for the React Stepper component.
   *
   * Each step can be either:
   * - A string (simplified format): `['Step 1', 'Step 2', 'Step 3']`
   * - An object (full format): containing a label, optional content, and an optional `formRef` for validation.
   */
  steps: string[] | StepperStepData[]

  /**
   * Controls the internal layout of the step indicator (icon and label) inside each step when using a horizontal layout.
   *
   * - `'vertical'` – Places the label below the indicator icon.
   * - `'horizontal'` – Places the label beside the indicator icon (default).
   *
   * This prop has no effect when `layout="vertical"` is used.
   */
  stepButtonLayout?: 'vertical' | 'horizontal'

  /**
   * Enables or disables built-in form validation inside the React Stepper.
   *
   * When enabled (`true`), each step's associated form must be valid before advancing to the next step.
   *
   * Requires each step to provide a `formRef`.
   */
  validation?: boolean
}

export const CStepper = forwardRef<StepperRef, CStepperProps>(
  (
    {
      activeStepNumber: controlledActiveStepNumber,
      activeStepIndex: controlledActiveStepIndex,
      className,
      defaultActiveStepNumber,
      defaultActiveStepIndex = 0,
      layout = 'horizontal',
      linear = true,
      onFinish,
      onReset,
      onStepChange,
      onStepValidationComplete,
      steps = [],
      stepButtonLayout = 'horizontal',
      validation = true,
      ...rest
    },
    ref
  ) => {
    const stepperRef = useRef<HTMLDivElement>(null)
    const stepsRef = useRef<HTMLOListElement>(null)
    const stepButtonRefs = useRef<(HTMLButtonElement | null)[]>([])
    const uniqueId = useId()

    // Handle backward compatibility and determine controlled vs uncontrolled mode
    const isControlledByNumber = controlledActiveStepNumber !== undefined
    const isControlledByIndex = controlledActiveStepIndex !== undefined
    const isControlled = isControlledByNumber || isControlledByIndex

    // Convert between step numbers (1-based) and indices (0-based)
    const getDefaultActiveStepIndex = () => {
      if (defaultActiveStepNumber !== undefined) {
        return Math.max(0, defaultActiveStepNumber - 1)
      }
      return defaultActiveStepIndex
    }

    const getControlledActiveStepIndex = () => {
      if (isControlledByNumber) {
        return Math.max(0, controlledActiveStepNumber! - 1)
      }
      if (isControlledByIndex) {
        return controlledActiveStepIndex!
      }
      return 0
    }

    const [internalActiveStepIndex, setInternalActiveStepIndex] = useState(
      isControlled ? getControlledActiveStepIndex() : getDefaultActiveStepIndex()
    )
    const [isFinished, setIsFinished] = useState(false)

    const activeStepIndex = isControlled ? getControlledActiveStepIndex() : internalActiveStepIndex

    // Sync state if controlled prop changes
    useEffect(() => {
      if (isControlled) {
        setInternalActiveStepIndex(getControlledActiveStepIndex())
      }
    }, [controlledActiveStepNumber, controlledActiveStepIndex, isControlled])

    // Ensure stepButtonRefs array has the correct size
    useEffect(() => {
      stepButtonRefs.current = stepButtonRefs.current.slice(0, steps.length)
    }, [steps.length])

    const isStepValid = useCallback(
      (index: number): boolean => {
        if (!validation) {
          return true
        }

        const currentStep = steps[index]
        const currentStepData =
          typeof currentStep === 'string' ? { label: currentStep } : currentStep
        const form = currentStepData?.formRef?.current

        if (form) {
          const isValid = form.checkValidity()
          onStepValidationComplete?.({
            stepNumber: index + 1,
            isValid,
          })

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

            return false
          }
        }

        return true
      },
      [steps, validation, onStepValidationComplete]
    )

    const setActiveStep = useCallback(
      (index: number, bypassValidation = false) => {
        if (index < 0 || index >= steps.length || index === activeStepIndex) {
          return
        }

        // Validate current step before moving forward
        if (!bypassValidation && index > activeStepIndex && !isStepValid(activeStepIndex)) {
          return
        }

        if (!isControlled) {
          setInternalActiveStepIndex(index)
        }
        onStepChange?.(index + 1) // Always call with step number (1-based)
      },
      [steps.length, activeStepIndex, isStepValid, isControlled, onStepChange]
    )

    const handleStepClick = (index: number) => {
      if (linear) {
        setActiveStep(index, index <= activeStepIndex)
        return
      }

      setActiveStep(index, true)
    }

    const handleNext = () => {
      if (activeStepIndex < steps.length - 1) {
        setActiveStep(activeStepIndex + 1)
      } else {
        handleFinish() // Attempt finish if already on last step
      }
    }

    const handlePrev = () => {
      if (activeStepIndex > 0) {
        setActiveStep(activeStepIndex - 1, true) // Bypass validation when going back
      }
    }

    const handleFinish = () => {
      if (activeStepIndex === steps.length - 1 && isStepValid(activeStepIndex)) {
        setIsFinished(true)
        onFinish?.()
      }
    }

    const handleReset = () => {
      if (validation) {
        steps.forEach((step) => {
          const stepData = typeof step === 'string' ? { label: step } : step
          stepData.formRef?.current?.reset()
        })
      }

      const resetIndex = getDefaultActiveStepIndex()
      if (!isControlled) {
        setInternalActiveStepIndex(resetIndex)
      }

      setIsFinished(false)
      onStepChange?.(resetIndex + 1) // Call with step number (1-based)
      onReset?.()
      stepButtonRefs.current[resetIndex]?.focus()
    }

    const handleKeyDown = (event: KeyboardEvent<HTMLOListElement>) => {
      const target = event.target as HTMLElement
      const currentButton = target.closest('button.stepper-step-button') as HTMLButtonElement

      if (!currentButton || !stepsRef.current) {
        return
      }

      const buttons = [
        ...stepsRef.current.querySelectorAll<HTMLButtonElement>('button.stepper-step-button'),
      ]

      let nextElement: HTMLElement | null = null

      switch (event.key) {
        case 'ArrowRight':
        case 'ArrowDown': {
          nextElement = getNextActiveElement(buttons, currentButton, true, false)
          break
        }
        case 'ArrowLeft':
        case 'ArrowUp': {
          nextElement = getNextActiveElement(buttons, currentButton, false, false)
          break
        }
        case 'Home': {
          nextElement = buttons[0]
          break
        }
        case 'End': {
          nextElement = buttons.at(-1) as HTMLButtonElement
          break
        }
        default: {
          return
        }
      }

      if (nextElement) {
        event.preventDefault()
        nextElement.focus()
      }
    }

    // Expose methods via ref
    useImperativeHandle(ref, () => ({
      next: handleNext,
      prev: handlePrev,
      finish: handleFinish,
      reset: handleReset,
    }))

    const isVertical = layout === 'vertical'

    return (
      <div
        className={classNames(
          'stepper',
          {
            'stepper-vertical': isVertical,
          },
          className
        )}
        ref={stepperRef}
        {...rest}
      >
        <ol
          className="stepper-steps"
          aria-orientation={isVertical ? 'vertical' : 'horizontal'}
          ref={stepsRef}
          onKeyDown={handleKeyDown}
          role="tablist"
        >
          {steps.map((step, index) => {
            const stepData = typeof step === 'string' ? { label: step } : step
            const isActive = !isFinished && index === activeStepIndex
            const isComplete = isFinished || index < activeStepIndex
            const isDisabled = isFinished || (linear && index > activeStepIndex + 1)
            const stepId = `stepper-${rest.id || uniqueId}-step-${index}`
            const panelId = `stepper-${rest.id || uniqueId}-panel-${index}`

            return (
              <li
                key={index}
                className={classNames('stepper-step', stepButtonLayout)}
                role="presentation"
              >
                <button
                  type="button"
                  className={classNames('stepper-step-button', {
                    active: isActive,
                    complete: isComplete,
                  })}
                  disabled={isDisabled}
                  id={stepId}
                  role="tab"
                  onClick={() => handleStepClick(index)}
                  ref={(el) => {
                    stepButtonRefs.current[index] = el
                  }}
                  aria-selected={isActive}
                  {...(stepData.content && {
                    'aria-controls': panelId,
                  })}
                  tabIndex={isActive ? 0 : -1}
                >
                  <span className="stepper-step-indicator">
                    {isComplete ? (
                      <span className="stepper-step-indicator-icon" />
                    ) : (
                      <span className="stepper-step-indicator-text">
                        {stepData.indicator ?? index + 1}
                      </span>
                    )}
                  </span>
                  <span className="stepper-step-label">{stepData.label}</span>
                </button>
                {index < steps.length - 1 && <div className="stepper-step-connector" />}
                {stepData.content && isVertical && (
                  <CCollapse
                    className="stepper-step-content"
                    id={panelId}
                    role="tabpanel"
                    visible={isActive}
                    aria-hidden={!isActive}
                    aria-labelledby={stepId}
                    aria-live="polite"
                  >
                    {stepData.content}
                  </CCollapse>
                )}
              </li>
            )
          })}
        </ol>
        {!isVertical &&
          steps.some((step) => {
            const stepData = typeof step === 'string' ? { label: step } : step
            return stepData.content !== undefined && stepData.content !== null
          }) && (
            <div className="stepper-content">
              {steps.map((step, index) => {
                const stepData = typeof step === 'string' ? { label: step } : step
                const isActive = !isFinished && index === activeStepIndex
                const stepId = `stepper-${rest.id || uniqueId}-step-${index}`
                const panelId = `stepper-${rest.id || uniqueId}-panel-${index}`
                return (
                  <div
                    key={index}
                    className={classNames('stepper-pane', {
                      show: isActive,
                      active: isActive,
                    })}
                    id={panelId}
                    role="tabpanel"
                    aria-hidden={!isActive}
                    aria-labelledby={stepId}
                    aria-live="polite"
                  >
                    {stepData.content}
                  </div>
                )
              })}
            </div>
          )}
      </div>
    )
  }
)

CStepper.displayName = 'CStepper'

CStepper.propTypes = {
  activeStepNumber: PropTypes.number,
  activeStepIndex: PropTypes.number,
  className: PropTypes.string,
  defaultActiveStepNumber: PropTypes.number,
  defaultActiveStepIndex: PropTypes.number,
  layout: PropTypes.oneOf(['horizontal', 'vertical']),
  linear: PropTypes.bool,
  onFinish: PropTypes.func,
  onReset: PropTypes.func,
  onStepChange: PropTypes.func,
  onStepValidationComplete: PropTypes.func,
  steps: PropTypes.arrayOf(
    PropTypes.oneOfType([
      PropTypes.string,
      PropTypes.shape({
        label: PropTypes.node.isRequired,
        content: PropTypes.node,
        formRef: PropTypes.object, // Check for object shape might be better
      }).isRequired,
    ])
  ).isRequired,
  stepButtonLayout: PropTypes.oneOf(['horizontal', 'vertical']),
  validation: PropTypes.bool,
}
