import {
  createContext,
  forwardRef,
  useCallback,
  useContext,
  useId,
  useMemo,
  useState,
  type HTMLAttributes,
  type ReactNode,
} from 'react'
import { cn } from '@/lib/utils'

const ChevronIcon = () => (
  <svg
    xmlns="http://www.w3.org/2000/svg"
    viewBox="0 0 24 24"
    fill="none"
    stroke="currentColor"
    strokeWidth={2}
    strokeLinecap="round"
    strokeLinejoin="round"
  >
    <path d="m6 9 6 6 6-6" />
  </svg>
)

// --- Context ---

interface AccordionContextValue {
  openItems: Set<string>
  toggle: (value: string) => void
}

const AccordionContext = createContext<AccordionContextValue | null>(null)

function useAccordionContext() {
  const ctx = useContext(AccordionContext)
  if (!ctx) throw new Error('Accordion components must be used within an Accordion')
  return ctx
}

interface AccordionItemContextValue {
  value: string
  isOpen: boolean
  triggerId: string
  contentId: string
  disabled: boolean
}

const AccordionItemContext = createContext<AccordionItemContextValue | null>(null)

function useAccordionItemContext() {
  const ctx = useContext(AccordionItemContext)
  if (!ctx) throw new Error('AccordionItem sub-components must be used within an AccordionItem')
  return ctx
}

// --- Accordion ---

export interface AccordionProps extends HTMLAttributes<HTMLDivElement> {
  type?: 'single' | 'multiple'
  collapsible?: boolean
  defaultValue?: string | string[]
  value?: string | string[]
  onValueChange?: (value: string | string[]) => void
}

export const Accordion = forwardRef<HTMLDivElement, AccordionProps>(
  (
    { type = 'single', collapsible = false, defaultValue, value, onValueChange, className, children, ...props },
    ref,
  ) => {
    const [internalOpen, setInternalOpen] = useState<Set<string>>(() => {
      if (defaultValue) return new Set(Array.isArray(defaultValue) ? defaultValue : [defaultValue])
      return new Set()
    })

    const isControlled = value !== undefined
    const openItems = isControlled
      ? new Set(Array.isArray(value) ? value : value ? [value] : [])
      : internalOpen

    const toggle = useCallback(
      (itemValue: string) => {
        const compute = (prev: Set<string>): Set<string> => {
          const next = new Set(prev)
          if (next.has(itemValue)) {
            if (type === 'single' && !collapsible) return prev
            next.delete(itemValue)
          } else {
            if (type === 'single') next.clear()
            next.add(itemValue)
          }
          return next
        }

        if (isControlled) {
          const next = compute(openItems)
          if (next !== openItems) {
            onValueChange?.(type === 'single' ? ([...next][0] ?? '') : [...next])
          }
        } else {
          setInternalOpen((prev) => {
            const next = compute(prev)
            if (next !== prev) {
              onValueChange?.(type === 'single' ? ([...next][0] ?? '') : [...next])
            }
            return next
          })
        }
      },
      [type, collapsible, isControlled, openItems, onValueChange],
    )

    const contextValue = useMemo(() => ({ openItems, toggle }), [openItems, toggle])

    return (
      <AccordionContext.Provider value={contextValue}>
        <div
          ref={ref}
          className={cn('overflow-hidden rounded-xl bg-surface', className)}
          {...props}
        >
          {children}
        </div>
      </AccordionContext.Provider>
    )
  },
)
Accordion.displayName = 'Accordion'

// --- AccordionItem ---

export interface AccordionItemProps extends HTMLAttributes<HTMLDivElement> {
  value: string
  disabled?: boolean
}

export const AccordionItem = forwardRef<HTMLDivElement, AccordionItemProps>(
  ({ value, disabled = false, className, children, ...props }, ref) => {
    const { openItems } = useAccordionContext()
    const isOpen = openItems.has(value)
    const id = useId()
    const triggerId = `${id}-trigger`
    const contentId = `${id}-content`

    const itemContext = useMemo(
      () => ({ value, isOpen, triggerId, contentId, disabled }),
      [value, isOpen, triggerId, contentId, disabled],
    )

    return (
      <AccordionItemContext.Provider value={itemContext}>
        <div
          ref={ref}
          data-state={isOpen ? 'open' : 'closed'}
          className={cn('border-b border-border last:border-b-0', className)}
          {...props}
        >
          {children}
        </div>
      </AccordionItemContext.Provider>
    )
  },
)
AccordionItem.displayName = 'AccordionItem'

// --- AccordionTrigger ---

export interface AccordionTriggerProps extends Omit<HTMLAttributes<HTMLButtonElement>, 'children'> {
  icon?: ReactNode
  children: ReactNode
}

export const AccordionTrigger = forwardRef<HTMLButtonElement, AccordionTriggerProps>(
  ({ icon, className, children, ...props }, ref) => {
    const { toggle } = useAccordionContext()
    const { value, isOpen, triggerId, contentId, disabled } = useAccordionItemContext()

    return (
      <button
        ref={ref}
        id={triggerId}
        type="button"
        aria-expanded={isOpen}
        aria-controls={contentId}
        disabled={disabled}
        onClick={() => toggle(value)}
        className={cn(
          'flex h-16 w-full items-center gap-3 px-6 text-left font-bold text-text transition-colors',
          'focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-info',
          isOpen ? 'bg-info/12' : 'bg-surface hover:bg-info/5',
          disabled && 'pointer-events-none opacity-50',
          className,
        )}
        {...props}
      >
        {icon && <span className="size-6 shrink-0 [&>svg]:size-full">{icon}</span>}
        <span className="flex-1">{children}</span>
        <span
          className={cn(
            'size-6 shrink-0 transition-transform duration-200 [&>svg]:size-full',
            isOpen && 'rotate-180',
          )}
        >
          <ChevronIcon />
        </span>
      </button>
    )
  },
)
AccordionTrigger.displayName = 'AccordionTrigger'

// --- AccordionContent ---

export interface AccordionContentProps extends HTMLAttributes<HTMLDivElement> {}

export const AccordionContent = forwardRef<HTMLDivElement, AccordionContentProps>(
  ({ className, children, ...props }, ref) => {
    const { isOpen, triggerId, contentId } = useAccordionItemContext()

    return (
      <div
        ref={ref}
        id={contentId}
        role="region"
        aria-labelledby={triggerId}
        aria-hidden={!isOpen}
        className={cn(
          'grid transition-[grid-template-rows] duration-200 ease-out',
          isOpen ? 'grid-rows-[1fr]' : 'grid-rows-[0fr]',
        )}
        {...props}
      >
        <div className="min-h-0 overflow-hidden">
          <div className={cn('px-6 pb-4 pt-2 text-text-secondary', className)}>{children}</div>
        </div>
      </div>
    )
  },
)
AccordionContent.displayName = 'AccordionContent'
