'use client'

import * as React from 'react'

import { motion, type Transition, type HTMLMotionProps } from 'motion/react'

import { cn } from '@/lib/utils'
import { MotionHighlight, MotionHighlightItem } from '@/components/ui/motion-highlight'

type TabsContextType<T extends string> = {
  activeValue: T
  handleValueChange: (value: T) => void
  registerTrigger: (value: T, node: HTMLElement | null) => void
}

// eslint-disable-next-line @typescript-eslint/no-explicit-any
const TabsContext = React.createContext<TabsContextType<any> | undefined>(undefined)

function useTabs<T extends string = string>(): TabsContextType<T> {
  const context = React.useContext(TabsContext)

  if (!context) {
    throw new Error('useTabs must be used within a TabsProvider')
  }

  return context
}

type BaseTabsProps = React.ComponentProps<'div'> & {
  children: React.ReactNode
}

type UnControlledTabsProps<T extends string = string> = BaseTabsProps & {
  defaultValue?: T
  value?: never
  onValueChange?: never
}

type ControlledTabsProps<T extends string = string> = BaseTabsProps & {
  value: T
  onValueChange?: (value: T) => void
  defaultValue?: never
}

type TabsProps<T extends string = string> = UnControlledTabsProps<T> | ControlledTabsProps<T>

function Tabs<T extends string = string>({
  defaultValue,
  value,
  onValueChange,
  children,
  className,
  ...props
}: TabsProps<T>) {
  const [activeValue, setActiveValue] = React.useState<T | undefined>(defaultValue ?? undefined)
  const triggersRef = React.useRef(new Map<string, HTMLElement>())
  const initialSet = React.useRef(false)
  const isControlled = value !== undefined

  React.useEffect(() => {
    if (!isControlled && activeValue === undefined && triggersRef.current.size > 0 && !initialSet.current) {
      const firstTab = Array.from(triggersRef.current.keys())[0]

      setActiveValue(firstTab as T)
      initialSet.current = true
    }
  }, [activeValue, isControlled])

  const registerTrigger = (value: string, node: HTMLElement | null) => {
    if (node) {
      triggersRef.current.set(value, node)

      if (!isControlled && activeValue === undefined && !initialSet.current) {
        setActiveValue(value as T)
        initialSet.current = true
      }
    } else {
      triggersRef.current.delete(value)
    }
  }

  const handleValueChange = (val: T) => {
    if (!isControlled) setActiveValue(val)
    else onValueChange?.(val)
  }

  return (
    <TabsContext.Provider
      value={{
        activeValue: (value ?? activeValue)!,
        handleValueChange,
        registerTrigger
      }}
    >
      <div data-slot='tabs' className={cn('flex flex-col gap-2', className)} {...props}>
        {children}
      </div>
    </TabsContext.Provider>
  )
}

type TabsListProps = React.ComponentProps<'div'> & {
  children: React.ReactNode
  activeClassName?: string
  transition?: Transition
}

function TabsList({
  children,
  className,
  activeClassName,
  transition = {
    type: 'spring',
    stiffness: 200,
    damping: 25
  },
  ...props
}: TabsListProps) {
  const { activeValue } = useTabs()

  return (
    <MotionHighlight
      controlledItems
      className={cn('bg-background rounded-sm shadow-sm', activeClassName)}
      value={activeValue}
      transition={transition}
    >
      <div
        role='tablist'
        data-slot='tabs-list'
        className={cn(
          'bg-muted text-muted-foreground inline-flex h-10 w-fit items-center justify-center rounded-lg p-[4px]',
          className
        )}
        {...props}
      >
        {children}
      </div>
    </MotionHighlight>
  )
}

type TabsTriggerProps = HTMLMotionProps<'button'> & {
  value: string
  children: React.ReactNode
}

function TabsTrigger({ ref, value, children, className, ...props }: TabsTriggerProps) {
  const { activeValue, handleValueChange, registerTrigger } = useTabs()

  const localRef = React.useRef<HTMLButtonElement | null>(null)

  React.useImperativeHandle(ref, () => localRef.current as HTMLButtonElement)

  React.useEffect(() => {
    registerTrigger(value, localRef.current)

    return () => registerTrigger(value, null)
  }, [value, registerTrigger])

  return (
    <MotionHighlightItem value={value} className='size-full'>
      <motion.button
        ref={localRef}
        data-slot='tabs-trigger'
        role='tab'
        onClick={() => handleValueChange(value)}
        data-state={activeValue === value ? 'active' : 'inactive'}
        className={cn(
          'ring-offset-background focus-visible:ring-ring data-[state=active]:text-foreground z-[1] inline-flex size-full cursor-pointer items-center justify-center rounded-sm px-2 py-1 text-sm font-medium whitespace-nowrap transition-transform focus-visible:ring-2 focus-visible:ring-offset-2 focus-visible:outline-none disabled:pointer-events-none disabled:opacity-50',
          className
        )}
        {...props}
      >
        {children}
      </motion.button>
    </MotionHighlightItem>
  )
}

type TabsContentsProps = React.ComponentProps<'div'> & {
  children: React.ReactNode
  transition?: Transition
}

function TabsContents({
  children,
  className,
  transition = {
    type: 'spring',
    stiffness: 300,
    damping: 30,
    bounce: 0,
    restDelta: 0.01
  },
  ...props
}: TabsContentsProps) {
  const { activeValue } = useTabs()
  const childrenArray = React.Children.toArray(children)

  const activeIndex = childrenArray.findIndex(
    (child): child is React.ReactElement<{ value: string }> =>
      React.isValidElement(child) &&
      typeof child.props === 'object' &&
      child.props !== null &&
      'value' in child.props &&
      child.props.value === activeValue
  )

  return (
    <div data-slot='tabs-contents' className={cn('overflow-hidden', className)} {...props}>
      <motion.div className='-mx-2 flex' animate={{ x: activeIndex * -100 + '%' }} transition={transition}>
        {childrenArray.map((child, index) => (
          <div key={index} className='w-full shrink-0 px-2'>
            {child}
          </div>
        ))}
      </motion.div>
    </div>
  )
}

type TabsContentProps = HTMLMotionProps<'div'> & {
  value: string
  children: React.ReactNode
}

function TabsContent({ children, value, className, ...props }: TabsContentProps) {
  const { activeValue } = useTabs()
  const isActive = activeValue === value

  return (
    <motion.div
      role='tabpanel'
      data-slot='tabs-content'
      className={cn('overflow-hidden', className)}
      initial={{ filter: 'blur(0px)' }}
      animate={{ filter: isActive ? 'blur(0px)' : 'blur(2px)' }}
      exit={{ filter: 'blur(0px)' }}
      transition={{ type: 'spring', stiffness: 300, damping: 20 }}
      {...props}
    >
      {children}
    </motion.div>
  )
}

export {
  Tabs,
  TabsList,
  TabsTrigger,
  TabsContents,
  TabsContent,
  useTabs,
  type TabsContextType,
  type TabsProps,
  type TabsListProps,
  type TabsTriggerProps,
  type TabsContentsProps,
  type TabsContentProps
}
