import React, { PropsWithChildren } from 'react'

import { WizardStepController } from './wizard-types'
import * as logger from './logger'

import WizardContext from './wizard-context'

interface WizardProps extends PropsWithChildren {
  wrapper?: React.ReactElement
}

const Wizard: React.FC<WizardProps> = ({ children, wrapper: Wrapper }) => {
  const [activeStep, setActiveStep] = React.useState(0)
  const [loading, setIsLoading] = React.useState(false)
  const hasNextStep = React.useRef(true)
  const hasPreviousStep = React.useRef(false)
  const nextStepHandler = React.useRef<WizardStepController | null>({
    next: () => true
  })
  const stepCount = React.Children.toArray(children).length

  hasNextStep.current = activeStep < stepCount - 1
  hasPreviousStep.current = activeStep > 0

  const goToNextStep = React.useRef(() => {
    if (hasNextStep.current) {
      setActiveStep((activeStep) => activeStep + 1)
    }
  })

  const goToPreviousStep = React.useRef(() => {
    if (hasPreviousStep.current) {
      nextStepHandler.current = null
      setActiveStep((activeStep) => activeStep - 1)
    }
  })

  const goToStep = React.useRef((stepIndex: number) => {
    if (stepIndex >= 0 && stepIndex < stepCount) {
      nextStepHandler.current = null
      setActiveStep(stepIndex)
    } else {
      if (process.env.NODE_ENV === 'development') {
        logger.log(
          'warn',
          [
            `Invalid step index [${stepIndex}] passed to 'goToStep'. `,
            `Ensure the given stepIndex is not out of boundaries.`
          ].join('')
        )
      }
    }
  })

  const registerStep = React.useRef((handler: WizardStepController) => {
    nextStepHandler.current = handler
  })

  const doNextStep = React.useRef(async () => {
    if (hasNextStep.current && nextStepHandler.current) {
      try {
        setIsLoading(true)
        const result = await nextStepHandler.current.next()
        setIsLoading(false)

        if (result) {
          nextStepHandler.current = null
          goToNextStep.current()
        }
      } catch (error) {
        setIsLoading(false)
        throw error
      }
    } else {
      goToNextStep.current()
    }
  })
  const wizardValue = React.useMemo(
    () => ({
      nextStep: doNextStep.current,
      previousStep: goToPreviousStep.current,
      registerStep: registerStep.current,
      loading,
      activeStep,
      stepCount,
      isFirstStep: !hasPreviousStep.current,
      isLastStep: !hasNextStep.current,
      goToStep: goToStep.current
    }),
    [activeStep, stepCount, loading]
  )

  const activeStepContent = React.useMemo(() => {
    const reactChildren = React.Children.toArray(children)

    if (process.env.NODE_ENV === 'development') {
      if (reactChildren.length === 0) {
        console.warn('Make sure to pass your steps as children in your <Wizard>')
      }
      if (activeStep > reactChildren.length) {
        console.warn('An invalid startIndex is passed to <Wizard>')
      }
    }

    return reactChildren[activeStep]
  }, [activeStep, children])

  const enhancedActiveStepContent = React.useMemo(
    () =>
      Wrapper ? React.cloneElement(Wrapper, { children: activeStepContent }) : activeStepContent,
    [Wrapper, activeStepContent]
  )

  return (
    <WizardContext.Provider value={wizardValue}>{enhancedActiveStepContent}</WizardContext.Provider>
  )
}

export default Wizard
