import * as React from 'react'
import { cva, type VariantProps } from 'class-variance-authority'
import { XIcon, CheckCircle2Icon, AlertTriangleIcon, InfoIcon, XCircleIcon } from 'lucide-react'

import { cn } from './utils'

/* ------------------------------------------------------------------ */
/* Types                                                               */
/* ------------------------------------------------------------------ */

export type ToastLevel = 'success' | 'error' | 'warning' | 'info'

export type ToastPosition = 'top-right' | 'top-left' | 'bottom-right' | 'bottom-left'

export interface ToastData {
  id: string
  level: ToastLevel
  title: string
  description?: string
  action?: React.ReactNode
  duration?: number
  persistent?: boolean
}

/* ------------------------------------------------------------------ */
/* Toast Variants                                                      */
/* ------------------------------------------------------------------ */

const toastVariants = cva(
  'group pointer-events-auto relative flex w-full items-start gap-3 overflow-hidden rounded-xl border p-4 shadow-xl backdrop-blur-xl transition-all',
  {
    variants: {
      level: {
        success: 'border-accent-cyan/30 bg-accent-cyan/5 text-foreground',
        error: 'border-accent-rose/30 bg-accent-rose/5 text-foreground',
        warning: 'border-accent-amber/30 bg-accent-amber/5 text-foreground',
        info: 'border-border-glass bg-glass text-foreground',
      },
    },
    defaultVariants: {
      level: 'info',
    },
  }
)

/* ------------------------------------------------------------------ */
/* Context & Hook                                                      */
/* ------------------------------------------------------------------ */

interface ToastContextValue {
  toasts: ToastData[]
  toast: (data: Omit<ToastData, 'id'>) => string
  success: (title: string, description?: string) => string
  error: (title: string, description?: string) => string
  warning: (title: string, description?: string) => string
  info: (title: string, description?: string) => string
  dismiss: (id: string) => void
  dismissAll: () => void
}

const ToastContext = React.createContext<ToastContextValue | null>(null)

function useToast(): ToastContextValue {
  const ctx = React.useContext(ToastContext)
  if (!ctx) {
    throw new Error('useToast must be used within a <ToastProvider>')
  }
  return ctx
}

/* ------------------------------------------------------------------ */
/* Provider                                                            */
/* ------------------------------------------------------------------ */

let toastCounter = 0

interface ToastProviderProps {
  children: React.ReactNode
  /** Position of the toast viewport */
  position?: ToastPosition
  /** Maximum number of visible toasts */
  maxVisible?: number
  /** Default auto-dismiss duration in ms */
  defaultDuration?: number
}

function ToastProvider({
  children,
  position = 'top-right',
  maxVisible = 5,
  defaultDuration = 5000,
}: ToastProviderProps) {
  const [toasts, setToasts] = React.useState<ToastData[]>([])

  const dismiss = React.useCallback((id: string) => {
    setToasts(prev => prev.filter(t => t.id !== id))
  }, [])

  const dismissAll = React.useCallback(() => {
    setToasts([])
  }, [])

  const addToast = React.useCallback(
    (data: Omit<ToastData, 'id'>) => {
      const id = `toast-${String(++toastCounter)}`
      const entry: ToastData = { id, ...data }

      setToasts(prev => {
        const next = [...prev, entry]
        // Enforce max visible
        if (next.length > maxVisible) {
          return next.slice(next.length - maxVisible)
        }
        return next
      })

      return id
    },
    [maxVisible]
  )

  const toast = React.useCallback((data: Omit<ToastData, 'id'>) => addToast(data), [addToast])

  const success = React.useCallback(
    (title: string, description?: string) => {
      const data: Omit<ToastData, 'id'> = { level: 'success', title }
      if (description !== undefined) data.description = description
      return addToast(data)
    },
    [addToast]
  )

  const error = React.useCallback(
    (title: string, description?: string) => {
      const data: Omit<ToastData, 'id'> = { level: 'error', title }
      if (description !== undefined) data.description = description
      return addToast(data)
    },
    [addToast]
  )

  const warning = React.useCallback(
    (title: string, description?: string) => {
      const data: Omit<ToastData, 'id'> = { level: 'warning', title }
      if (description !== undefined) data.description = description
      return addToast(data)
    },
    [addToast]
  )

  const info = React.useCallback(
    (title: string, description?: string) => {
      const data: Omit<ToastData, 'id'> = { level: 'info', title }
      if (description !== undefined) data.description = description
      return addToast(data)
    },
    [addToast]
  )

  const value = React.useMemo(
    () => ({ toasts, toast, success, error, warning, info, dismiss, dismissAll }),
    [toasts, toast, success, error, warning, info, dismiss, dismissAll]
  )

  return (
    <ToastContext.Provider value={value}>
      {children}
      <ToastViewport
        position={position}
        toasts={toasts}
        dismiss={dismiss}
        defaultDuration={defaultDuration}
      />
    </ToastContext.Provider>
  )
}

/* ------------------------------------------------------------------ */
/* Viewport                                                            */
/* ------------------------------------------------------------------ */

interface ToastViewportProps {
  position: ToastPosition
  toasts: ToastData[]
  dismiss: (id: string) => void
  defaultDuration: number
}

function ToastViewport({ position, toasts, dismiss, defaultDuration }: ToastViewportProps) {
  const positionClasses: Record<ToastPosition, string> = {
    'top-right': 'top-4 right-4',
    'top-left': 'top-4 left-4',
    'bottom-right': 'bottom-4 right-4',
    'bottom-left': 'bottom-4 left-4',
  }

  return (
    <div
      data-slot="toast-viewport"
      role="region"
      aria-label="Notifications"
      aria-live="polite"
      className={cn(
        'fixed z-[9999] flex flex-col gap-2 pointer-events-none w-full max-w-sm',
        positionClasses[position]
      )}
    >
      {toasts.map(t => (
        <Toast
          key={t.id}
          data={t}
          onDismiss={() => {
            dismiss(t.id)
          }}
          defaultDuration={defaultDuration}
        />
      ))}
    </div>
  )
}

/* ------------------------------------------------------------------ */
/* Toast                                                               */
/* ------------------------------------------------------------------ */

function Toast({
  data,
  onDismiss,
  defaultDuration,
}: {
  data: ToastData
  onDismiss: () => void
  defaultDuration: number
}) {
  const duration = data.duration ?? defaultDuration
  const timerRef = React.useRef<ReturnType<typeof setTimeout> | null>(null)
  const startRef = React.useRef(Date.now())
  const remainingRef = React.useRef(duration)

  // Keep onDismiss in a ref so that changes to the callback identity don't
  // restart the auto-dismiss timer.  The parent's ToastViewport creates a
  // new `() => dismiss(id)` closure on every render; without this ref the
  // timer would reset every time any toast is added/removed.
  const onDismissRef = React.useRef(onDismiss)
  React.useEffect(() => {
    onDismissRef.current = onDismiss
  }, [onDismiss])

  const startTimer = React.useCallback(() => {
    if (data.persistent === true) return
    // Clear any existing timer to prevent stacked timeouts (e.g. when
    // onBlur fires without a preceding onFocus/onMouseEnter pause).
    if (timerRef.current) {
      clearTimeout(timerRef.current)
      timerRef.current = null
    }
    // Guard against negative or zero remaining time — the toast has
    // already been auto-dismissed; do not schedule another callback.
    if (remainingRef.current <= 0) return
    startRef.current = Date.now()
    timerRef.current = setTimeout(() => {
      timerRef.current = null
      onDismissRef.current()
    }, remainingRef.current)
  }, [data.persistent])

  const pauseTimer = React.useCallback(() => {
    if (timerRef.current) {
      clearTimeout(timerRef.current)
      timerRef.current = null
      remainingRef.current -= Date.now() - startRef.current
      // Clamp to zero so a subsequent startTimer is a no-op.
      if (remainingRef.current < 0) remainingRef.current = 0
    }
  }, [])

  React.useEffect(() => {
    startTimer()
    return () => {
      if (timerRef.current) clearTimeout(timerRef.current)
    }
  }, [startTimer])

  const iconMap: Record<ToastLevel, React.ReactNode> = {
    success: <CheckCircle2Icon className="size-5 text-accent-cyan shrink-0" />,
    error: <XCircleIcon className="size-5 text-accent-rose shrink-0" />,
    warning: <AlertTriangleIcon className="size-5 text-accent-amber shrink-0" />,
    info: <InfoIcon className="size-5 text-foreground-secondary shrink-0" />,
  }

  return (
    <div
      data-slot="toast"
      role={data.level === 'error' || data.level === 'warning' ? 'alert' : 'status'}
      aria-live={data.level === 'error' ? 'assertive' : 'polite'}
      aria-atomic="true"
      className={toastVariants({ level: data.level })}
      onMouseEnter={pauseTimer}
      onMouseLeave={startTimer}
      onFocus={pauseTimer}
      onBlur={startTimer}
      onKeyDown={e => {
        if (e.key === 'Escape') {
          e.preventDefault()
          onDismiss()
        }
      }}
      tabIndex={0}
    >
      {iconMap[data.level]}

      <div className="flex-1 min-w-0">
        <ToastTitle>{data.title}</ToastTitle>
        {data.description !== undefined && <ToastDescription>{data.description}</ToastDescription>}
        {data.action !== undefined && <div className="mt-2">{data.action}</div>}
      </div>

      <ToastClose onClick={onDismiss} />
    </div>
  )
}

/* ------------------------------------------------------------------ */
/* Subcomponents                                                       */
/* ------------------------------------------------------------------ */

function ToastTitle({ className, ...props }: React.ComponentProps<'div'>) {
  return (
    <div data-slot="toast-title" className={cn('text-sm font-semibold', className)} {...props} />
  )
}

function ToastDescription({ className, ...props }: React.ComponentProps<'div'>) {
  return (
    <div
      data-slot="toast-description"
      className={cn('text-sm text-foreground-secondary mt-0.5', className)}
      {...props}
    />
  )
}

function ToastAction({ className, ...props }: React.ComponentProps<'button'>) {
  return (
    <button
      data-slot="toast-action"
      type="button"
      className={cn(
        'inline-flex h-7 items-center rounded-lg border border-border-glass bg-glass px-3 text-xs font-medium transition-colors hover:bg-glass-hover',
        className
      )}
      {...props}
    />
  )
}

function ToastClose({ className, onClick, ...props }: React.ComponentProps<'button'>) {
  return (
    <button
      data-slot="toast-close"
      type="button"
      aria-label="Dismiss notification"
      onClick={onClick}
      className={cn(
        'shrink-0 rounded-lg p-1 text-foreground-muted opacity-60 transition-opacity hover:opacity-100',
        className
      )}
      {...props}
    >
      <XIcon className="size-4" />
    </button>
  )
}

export {
  ToastProvider,
  Toast,
  ToastTitle,
  ToastDescription,
  ToastAction,
  ToastClose,
  useToast,
  toastVariants,
}
export type { ToastData as ToastProps, VariantProps }
