import type { IStepperProps, StepDefinition } from "./Stepper.d"
import type { JSX } from "react"

import { Check } from "lucide-react"
import { Fragment, FunctionComponent, useEffect, useMemo, useRef } from "react"

import cs from "classnames"
import { animate, motion, useScroll, useTransform } from "framer-motion"

import styles from "./Stepper.module.scss"

/* ------------------------------------------------------------------ */
/*  Phase grouping                                                      */
/* ------------------------------------------------------------------ */

interface Phase {
	label: string
	steps: StepDefinition[]
	phaseIndex: number
	isCompleted: boolean
	isActive: boolean
	isUpcoming: boolean
}

function groupStepsIntoPhases(
	steps: StepDefinition[],
	currentStep: string,
	completedSteps: string[],
): Phase[] {
	const phases: Phase[] = []

	for (const step of steps) {
		const label = step.phaseLabel ?? step.label
		const lastPhase = phases[phases.length - 1]

		if (lastPhase && lastPhase.label === label) {
			lastPhase.steps.push(step)
		} else {
			phases.push({
				label,
				steps: [step],
				phaseIndex: phases.length,
				isCompleted: false,
				isActive: false,
				isUpcoming: false,
			})
		}
	}

	for (const phase of phases) {
		const allCompleted = phase.steps.every((s) =>
			completedSteps.includes(s.key),
		)
		const hasActive = phase.steps.some((s) => s.key === currentStep)

		phase.isCompleted = allCompleted && !hasActive
		phase.isActive = hasActive
		phase.isUpcoming = !allCompleted && !hasActive
	}

	return phases
}

/* ------------------------------------------------------------------ */
/*  Connector between phases                                            */
/* ------------------------------------------------------------------ */

function getConnectorVariant(leftPhase: Phase, rightPhase: Phase): string {
	if (leftPhase.isCompleted && rightPhase.isCompleted)
		return styles.connectorCompleted
	if (leftPhase.isCompleted && rightPhase.isActive)
		return styles.connectorActive
	return styles.connectorUpcoming
}

/* ------------------------------------------------------------------ */
/*  Substep status                                                      */
/* ------------------------------------------------------------------ */

type SubstepStatus = "completed" | "active" | "upcoming"

function getSubstepStatus(
	stepKey: string,
	currentStep: string,
	completedSteps: string[],
): SubstepStatus {
	if (stepKey === currentStep) return "active"
	if (completedSteps.includes(stepKey)) return "completed"
	return "upcoming"
}

/* ------------------------------------------------------------------ */
/*  Component                                                           */
/* ------------------------------------------------------------------ */

export const Stepper: FunctionComponent<IStepperProps> = ({
	steps,
	currentStep,
	completedSteps,
	className,
}): JSX.Element => {
	const scrollContainerRef = useRef<HTMLDivElement>(null)
	const isFirstScroll = useRef(true)

	const { scrollXProgress } = useScroll({
		container: scrollContainerRef,
		axis: "x",
	})

	const fadeLeftOpacity = useTransform(scrollXProgress, [0, 0.03], [0, 1])
	const fadeRightOpacity = useTransform(scrollXProgress, [0.97, 1], [1, 0])

	const phases = useMemo(
		() => groupStepsIntoPhases(steps, currentStep, completedSteps),
		[steps, currentStep, completedSteps],
	)

	// Scroll active phase into view
	useEffect(() => {
		const container = scrollContainerRef.current
		if (!container) return

		const scrollToActive = () => {
			const activeEl =
				container.querySelector<HTMLElement>("[data-step-active]")
			if (!activeEl) return

			const containerRect = container.getBoundingClientRect()
			const activeRect = activeEl.getBoundingClientRect()
			const targetScroll =
				activeRect.left -
				containerRect.left +
				container.scrollLeft -
				containerRect.width / 2 +
				activeRect.width / 2

			const clampedTarget = Math.max(
				0,
				Math.min(targetScroll, container.scrollWidth - container.clientWidth),
			)

			if (isFirstScroll.current) {
				container.scrollLeft = clampedTarget
				isFirstScroll.current = false
				return
			}

			const controls = animate(container.scrollLeft, clampedTarget, {
				type: "spring",
				stiffness: 300,
				damping: 30,
				onUpdate: (v) => {
					container.scrollLeft = v
				},
			})

			return () => controls.stop()
		}

		const frame = requestAnimationFrame(scrollToActive)
		return () => cancelAnimationFrame(frame)
	}, [currentStep])

	return (
		<div className={cs(styles.root, className)} data-testid="Stepper">
			<motion.div
				className={styles.fadeLeft}
				style={{ opacity: fadeLeftOpacity }}
			/>

			<div ref={scrollContainerRef} className={styles.scrollContainer}>
				<div className={styles.phases}>
					{phases.map((phase, idx) => {
						const isLast = idx === phases.length - 1
						const connectorClass = !isLast
							? getConnectorVariant(phase, phases[idx + 1])
							: null

						return (
							<Fragment key={phase.label}>
								<div
									className={cs(styles.phase, {
										[styles.phaseCompleted]: phase.isCompleted,
										[styles.phaseActive]: phase.isActive,
										[styles.phaseUpcoming]: phase.isUpcoming,
									})}
									aria-current={phase.isActive ? "step" : undefined}
									{...(phase.isActive ? { "data-step-active": true } : {})}
								>
									<span
										className={cs(styles.indicator, {
											[styles.indicatorCompleted]: phase.isCompleted,
											[styles.indicatorActive]: phase.isActive,
											[styles.indicatorUpcoming]: phase.isUpcoming,
										})}
									>
										{phase.isCompleted ? (
											<Check size={12} strokeWidth={2.5} />
										) : (
											phase.phaseIndex + 1
										)}
									</span>

									<span className={styles.phaseLabel}>{phase.label}</span>

									{phase.isActive && (
										<div className={styles.substepDots}>
											{phase.steps.map((step) => {
												const status = getSubstepStatus(
													step.key,
													currentStep,
													completedSteps,
												)
												return (
													<span
														key={step.key}
														className={cs(styles.dot, {
															[styles.dotCompleted]: status === "completed",
															[styles.dotActive]: status === "active",
															[styles.dotUpcoming]: status === "upcoming",
														})}
														title={step.label}
													/>
												)
											})}
										</div>
									)}
								</div>

								{connectorClass && (
									<div className={cs(styles.connector, connectorClass)} />
								)}
							</Fragment>
						)
					})}
				</div>
			</div>

			<motion.div
				className={styles.fadeRight}
				style={{ opacity: fadeRightOpacity }}
			/>
		</div>
	)
}
