// frontend\src\components\ui\ToastProvider.tsx
 
'use client'

import { useEffect, useRef, useState, useMemo, useCallback, createContext, useContext, type CSSProperties, type ReactNode, type PointerEvent } from 'react'
import { Transition } from '@headlessui/react'
import {
  CheckCircleIcon,
  XCircleIcon,
  InformationCircleIcon,
  ExclamationTriangleIcon,
} from '@heroicons/react/24/outline'
import { XMarkIcon } from '@heroicons/react/20/solid'

type ToastType = 'success' | 'error' | 'info' | 'warning'

export type Toast = {
  id: string
  type: ToastType
  title?: string
  message?: string
  imageUrl?: string
  imageAlt?: string
  durationMs?: number // auto close
  onClick?: () => void
  closeOnClick?: boolean // default true (bei klickbaren Toasts)
}

type ToastInternal = Toast & {
  open: boolean
}

type SwipeState = {
  pointerId: number | null
  startX: number
  startY: number
  dx: number
  dy: number
  dragging: boolean
  lock: 'none' | 'x' | 'y'
  dismissing: boolean
}

type ToastContextValue = {
  push: (t: Omit<Toast, 'id'>) => string
  remove: (id: string) => void
  clear: () => void
}

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

const TOAST_LEAVE_MS = 220
const TOAST_SWIPE_DISMISS_PX = 96
const TOAST_SWIPE_LOCK_PX = 10

const TOAST_MESSAGE_MAX_CHARS = 260

const toastMessageClampStyle: CSSProperties = {
  display: '-webkit-box',
  WebkitLineClamp: 3,
  WebkitBoxOrient: 'vertical',
  overflow: 'hidden',
}

function compactToastMessage(input?: string, max = TOAST_MESSAGE_MAX_CHARS) {
  const text = String(input || '')
    .replace(/\s+/g, ' ')
    .trim()

  if (!text) return ''

  if (text.length <= max) {
    return text
  }

  return `${text.slice(0, max).trimEnd()}…`
}

function iconFor(type: ToastType) {
  switch (type) {
    case 'success':
      return { Icon: CheckCircleIcon, cls: 'text-emerald-600 dark:text-emerald-400' }
    case 'error':
      return { Icon: XCircleIcon, cls: 'text-rose-600 dark:text-rose-400' }
    case 'warning':
      return { Icon: ExclamationTriangleIcon, cls: 'text-amber-600 dark:text-amber-400' }
    default:
      return { Icon: InformationCircleIcon, cls: 'text-sky-600 dark:text-sky-400' }
  }
}

function borderFor(type: ToastType) {
  switch (type) {
    case 'success':
      return 'border-emerald-200/80 dark:border-emerald-400/20'
    case 'error':
      return 'border-rose-200/80 dark:border-rose-400/20'
    case 'warning':
      return 'border-amber-200/80 dark:border-amber-400/20'
    default:
      return 'border-sky-200/80 dark:border-sky-400/20'
  }
}

function accentFor(type: ToastType) {
  switch (type) {
    case 'success':
      return {
        line: 'bg-emerald-500 dark:bg-emerald-400',
        progressTrack: 'bg-emerald-500/10 dark:bg-emerald-400/10',
        progressFill: 'bg-emerald-500/70 dark:bg-emerald-400/70',
        iconBg: 'bg-emerald-50 dark:bg-emerald-400/10',
      }
    case 'error':
      return {
        line: 'bg-rose-500 dark:bg-rose-400',
        progressTrack: 'bg-rose-500/10 dark:bg-rose-400/10',
        progressFill: 'bg-rose-500/70 dark:bg-rose-400/70',
        iconBg: 'bg-rose-50 dark:bg-rose-400/10',
      }
    case 'warning':
      return {
        line: 'bg-amber-500 dark:bg-amber-400',
        progressTrack: 'bg-amber-500/10 dark:bg-amber-400/10',
        progressFill: 'bg-amber-500/70 dark:bg-amber-400/70',
        iconBg: 'bg-amber-50 dark:bg-amber-400/10',
      }
    default:
      return {
        line: 'bg-sky-500 dark:bg-sky-400',
        progressTrack: 'bg-sky-500/10 dark:bg-sky-400/10',
        progressFill: 'bg-sky-500/70 dark:bg-sky-400/70',
        iconBg: 'bg-sky-50 dark:bg-sky-400/10',
      }
  }
}

function titleDefault(type: ToastType) {
  switch (type) {
    case 'success':
      return 'Erfolg'
    case 'error':
      return 'Fehler'
    case 'warning':
      return 'Hinweis'
    default:
      return 'Info'
  }
}

function uid() {
  return `${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 10)}`
}

export function ToastProvider({
  children,
  maxToasts = 3,
  defaultDurationMs = 3500,
  position = 'bottom-right',
}: {
  children: ReactNode
  maxToasts?: number
  defaultDurationMs?: number
  position?: 'bottom-right' | 'top-right' | 'bottom-left' | 'top-left'
}) {
  const [toasts, setToasts] = useState<ToastInternal[]>([])
  const [notificationsEnabled, setNotificationsEnabled] = useState(true)

  const autoCloseTimersRef = useRef<Record<string, number>>({})
  const finalizeRemoveTimersRef = useRef<Record<string, number>>({})

  const toastStartedAtRef = useRef<Record<string, number>>({})
  const toastRemainingMsRef = useRef<Record<string, number>>({})
  const toastPausedRef = useRef<Record<string, boolean>>({})

  const swipeStateRef = useRef<Record<string, SwipeState>>({})
  const [, forceSwipeRender] = useState(0)

  const clearTimersFor = useCallback((id: string) => {
    const autoId = autoCloseTimersRef.current[id]
    if (autoId) {
      window.clearTimeout(autoId)
      delete autoCloseTimersRef.current[id]
    }

    const finId = finalizeRemoveTimersRef.current[id]
    if (finId) {
      window.clearTimeout(finId)
      delete finalizeRemoveTimersRef.current[id]
    }

    delete toastStartedAtRef.current[id]
    delete toastRemainingMsRef.current[id]
    delete toastPausedRef.current[id]
    delete swipeStateRef.current[id]
  }, [])
  
  const loadNotificationSetting = useCallback(async () => {
    try {
      const r = await fetch('/api/settings', { cache: 'no-store' })
      if (!r.ok) return
      const data = await r.json()
      setNotificationsEnabled(!!(data?.enableNotifications ?? true))
    } catch {
      // ignorieren -> default true
    }
  }, [])

  useEffect(() => {
    loadNotificationSetting()

    const onUpdated = () => loadNotificationSetting()
    window.addEventListener('recorder-settings-updated', onUpdated)
    return () => window.removeEventListener('recorder-settings-updated', onUpdated)
  }, [loadNotificationSetting])

  useEffect(() => {
    if (!notificationsEnabled) {
      // Nur Fehler sichtbar lassen (animiert schließen)
      setToasts((prev) => prev.map((t) => (t.type === 'error' ? t : { ...t, open: false })))

      const ids = toasts.filter((t) => t.type !== 'error').map((t) => t.id)
      ids.forEach((id) => {
        clearTimersFor(id)
        finalizeRemoveTimersRef.current[id] = window.setTimeout(() => {
          setToasts((prev) => prev.filter((t) => t.id !== id))
          delete finalizeRemoveTimersRef.current[id]
        }, TOAST_LEAVE_MS)
      })
    }
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [notificationsEnabled])

  const remove = useCallback(
    (id: string) => {
      const cur = swipeStateRef.current[id]
      if (cur) {
        swipeStateRef.current[id] = {
          ...cur,
          dismissing: true,
          dragging: false,
        }
        forceSwipeRender((n) => n + 1)
      }

      setToasts((prev) => prev.map((t) => (t.id === id ? { ...t, open: false } : t)))

      clearTimersFor(id)
      finalizeRemoveTimersRef.current[id] = window.setTimeout(() => {
        setToasts((prev) => prev.filter((t) => t.id !== id))
        delete finalizeRemoveTimersRef.current[id]
        delete swipeStateRef.current[id]
      }, TOAST_LEAVE_MS)
    },
    [clearTimersFor]
  )

  const clear = useCallback(() => {
    setToasts((prev) => prev.map((t) => ({ ...t, open: false })))

    Object.keys(autoCloseTimersRef.current).forEach((id) => {
      window.clearTimeout(autoCloseTimersRef.current[id])
      delete autoCloseTimersRef.current[id]
    })
    Object.keys(finalizeRemoveTimersRef.current).forEach((id) => {
      window.clearTimeout(finalizeRemoveTimersRef.current[id])
      delete finalizeRemoveTimersRef.current[id]
    })

    window.setTimeout(() => setToasts([]), TOAST_LEAVE_MS)
  }, [])

  const startAutoCloseTimer = useCallback(
    (id: string, ms: number) => {
      if (!ms || ms <= 0) return

      // alten Timer sicher entfernen
      const old = autoCloseTimersRef.current[id]
      if (old) {
        window.clearTimeout(old)
        delete autoCloseTimersRef.current[id]
      }

      toastStartedAtRef.current[id] = Date.now()
      toastRemainingMsRef.current[id] = ms
      toastPausedRef.current[id] = false

      autoCloseTimersRef.current[id] = window.setTimeout(() => {
        remove(id)
        delete autoCloseTimersRef.current[id]
        delete toastStartedAtRef.current[id]
        delete toastRemainingMsRef.current[id]
        delete toastPausedRef.current[id]
      }, ms)
    },
    [remove]
  )

  const push = useCallback(
    (t: Omit<Toast, 'id'>) => {
      if (!notificationsEnabled && t.type !== 'error') return ''

      const id = uid()
      const durationMs = t.durationMs ?? defaultDurationMs

      setToasts((prev) => {
        const next: ToastInternal[] = [{ ...t, id, durationMs, open: true }, ...prev]

        const limit = Math.max(1, maxToasts)
        const kept = next.slice(0, limit)
        const dropped = next.slice(limit)

        dropped.forEach((d) => clearTimersFor(d.id))
        return kept
      })

      if (durationMs && durationMs > 0) {
        startAutoCloseTimer(id, durationMs)
      }

      return id
    },
    [defaultDurationMs, maxToasts, notificationsEnabled, clearTimersFor, startAutoCloseTimer]
  )

  const pauseAutoCloseTimer = useCallback((id: string) => {
    if (toastPausedRef.current[id]) return

    const timerId = autoCloseTimersRef.current[id]
    if (!timerId) return

    window.clearTimeout(timerId)
    delete autoCloseTimersRef.current[id]

    const startedAt = toastStartedAtRef.current[id] ?? Date.now()
    const remaining = toastRemainingMsRef.current[id] ?? 0
    const elapsed = Date.now() - startedAt
    const nextRemaining = Math.max(0, remaining - elapsed)

    toastRemainingMsRef.current[id] = nextRemaining
    toastPausedRef.current[id] = true
  }, [])

  const resumeAutoCloseTimer = useCallback(
    (id: string) => {
      if (!toastPausedRef.current[id]) return

      const remaining = toastRemainingMsRef.current[id] ?? 0
      if (remaining <= 0) {
        remove(id)
        return
      }

      toastPausedRef.current[id] = false
      toastStartedAtRef.current[id] = Date.now()

      autoCloseTimersRef.current[id] = window.setTimeout(() => {
        remove(id)
        delete autoCloseTimersRef.current[id]
        delete toastStartedAtRef.current[id]
        delete toastRemainingMsRef.current[id]
        delete toastPausedRef.current[id]
      }, remaining)
    },
    [remove]
  )

  const isTouchLikePointer = (e: PointerEvent) => {
    return e.pointerType === 'touch' || e.pointerType === 'pen'
  }

  const getSwipeState = (id: string): SwipeState => {
    return (
      swipeStateRef.current[id] ?? {
        pointerId: null,
        startX: 0,
        startY: 0,
        dx: 0,
        dy: 0,
        dragging: false,
        lock: 'none',
        dismissing: false,
      }
    )
  }

  const handleToastPointerDown = useCallback((id: string, e: PointerEvent<HTMLDivElement>) => {
    if (!isTouchLikePointer(e)) return

    swipeStateRef.current[id] = {
      pointerId: e.pointerId,
      startX: e.clientX,
      startY: e.clientY,
      dx: 0,
      dy: 0,
      dragging: false,
      lock: 'none',
      dismissing: false,
    }

    pauseAutoCloseTimer(id)
    forceSwipeRender((n) => n + 1)
  }, [pauseAutoCloseTimer])

  const handleToastPointerMove = useCallback((id: string, e: PointerEvent<HTMLDivElement>) => {
    const s = swipeStateRef.current[id]
    if (!s) return
    if (s.pointerId !== e.pointerId) return
    if (s.dismissing) return

    const dx = e.clientX - s.startX
    const dy = e.clientY - s.startY

    let lock = s.lock

    if (lock === 'none') {
      const absX = Math.abs(dx)
      const absY = Math.abs(dy)

      if (absX < TOAST_SWIPE_LOCK_PX && absY < TOAST_SWIPE_LOCK_PX) {
        swipeStateRef.current[id] = { ...s, dx, dy }
        forceSwipeRender((n) => n + 1)
        return
      }

      lock = absX > absY ? 'x' : 'y'
    }

    if (lock === 'y') {
      swipeStateRef.current[id] = { ...s, dx, dy, lock, dragging: false }
      forceSwipeRender((n) => n + 1)
      return
    }

    swipeStateRef.current[id] = {
      ...s,
      dx,
      dy,
      lock,
      dragging: true,
    }

    forceSwipeRender((n) => n + 1)
  }, [])

  const handleToastPointerEnd = useCallback((id: string, e?: PointerEvent<HTMLDivElement>) => {
    const s = swipeStateRef.current[id]
    if (!s) return
    if (e && s.pointerId !== e.pointerId) return

    const shouldDismiss = s.lock === 'x' && s.dx <= -TOAST_SWIPE_DISMISS_PX

    if (shouldDismiss) {
      remove(id)
      return
    }

    swipeStateRef.current[id] = {
      ...s,
      pointerId: null,
      dx: 0,
      dy: 0,
      dragging: false,
      lock: 'none',
      dismissing: false,
    }

    resumeAutoCloseTimer(id)
    forceSwipeRender((n) => n + 1)
  }, [remove, resumeAutoCloseTimer])

  const handleToastPointerCancel = useCallback((id: string, e?: PointerEvent<HTMLDivElement>) => {
    const s = swipeStateRef.current[id]
    if (!s) return
    if (e && s.pointerId !== e.pointerId) return

    swipeStateRef.current[id] = {
      ...s,
      pointerId: null,
      dx: 0,
      dy: 0,
      dragging: false,
      lock: 'none',
      dismissing: false,
    }

    resumeAutoCloseTimer(id)
    forceSwipeRender((n) => n + 1)
  }, [resumeAutoCloseTimer])

  useEffect(() => {
    return () => {
      Object.values(autoCloseTimersRef.current).forEach((n) => window.clearTimeout(n))
      Object.values(finalizeRemoveTimersRef.current).forEach((n) => window.clearTimeout(n))
      swipeStateRef.current= {}
    }
  }, [])

  const ctx = useMemo<ToastContextValue>(() => ({ push, remove, clear }), [push, remove, clear])

  const desktopPosCls =
    position.startsWith('top')
      ? 'sm:items-start'
      : 'sm:items-end'

  const desktopAlignCls = position.endsWith('left')
    ? 'sm:items-start'
    : 'sm:items-end'

  // Mobile immer oben mittig.
  // Desktop bleibt wie über `position` gesteuert.
  const insetCls = position.startsWith('top')
    ? 'top-0 bottom-auto'
    : 'top-0 bottom-auto sm:bottom-0 sm:top-auto'

  return (
    <ToastContext.Provider value={ctx}>
      {children}

      <style>{`
        @keyframes toast-progress {
          from { transform: scaleX(1); }
          to   { transform: scaleX(0); }
        }

        .toast-progress-bar {
          animation-name: toast-progress;
          animation-timing-function: linear;
          animation-fill-mode: forwards;
          transform-origin: left center;
          will-change: transform;
        }
      `}</style>

      {/* Live region */}
      <div
        aria-live="assertive"
        className={['pointer-events-none fixed z-[80] inset-x-0', insetCls].join(' ')}
      >
        <div
          className={[
            'flex w-full justify-center px-3 py-4 sm:px-6 sm:py-6',
            desktopPosCls,
          ].join(' ')}
        >
          <div
            className={[
              'flex w-full max-w-[22rem] flex-col items-center gap-2.5 sm:max-w-none sm:gap-3',
              desktopAlignCls,
            ].join(' ')}
          >
            {toasts.map((t) => {
              const { Icon, cls } = iconFor(t.type)
              const accents = accentFor(t.type)
              const title = (t.title || '').trim() || titleDefault(t.type)
              const fullMsg = (t.message || '').trim()
              const msg = compactToastMessage(fullMsg)
              const msgWasShortened = fullMsg.length > msg.length
              const img = (t.imageUrl || '').trim()
              const imgAlt = (t.imageAlt || title).trim()
              const isClickable = typeof t.onClick === 'function'

              const swipe = getSwipeState(t.id)
              const absDx = Math.abs(swipe.dx)
              const dragOpacity = swipe.dragging
                ? Math.max(0.35, 1 - absDx / 220)
                : 1

              const dragScale = swipe.dragging
                ? Math.max(0.985, 1 - absDx / 1600)
                : 1

              const swipeStyle: CSSProperties = {
                transform: `translateX(${swipe.dx}px) scale(${dragScale})`,
                opacity: dragOpacity,
                transition: swipe.dragging
                  ? 'none'
                  : swipe.dismissing
                    ? 'transform 180ms ease-out, opacity 180ms ease-out'
                    : 'transform 220ms ease, opacity 220ms ease',
                touchAction: 'pan-y',
              }

              return (
                <Transition
                  key={t.id}
                  appear
                  show={t.open}
                  enter="transform transition ease-out duration-250"
                  enterFrom="opacity-0 -translate-y-3 sm:-translate-y-2"
                  enterTo="opacity-100 translate-y-0"
                  leave="transform transition ease-in duration-200"
                  leaveFrom="opacity-100 translate-y-0"
                  leaveTo="opacity-0 -translate-y-2 sm:-translate-y-3"
                >
                  <div
                    role={isClickable ? 'button' : 'status'}
                    className={[
                      'pointer-events-auto relative w-[22rem] max-w-[calc(100vw-1.5rem)] overflow-hidden rounded-xl',
                      'border bg-white dark:bg-slate-900',
                      'shadow-sm',
                      'ring-1 ring-black/5 dark:ring-white/5',
                      isClickable
                        ? 'cursor-pointer transition-[box-shadow,background-color] duration-150 hover:bg-gray-50 dark:hover:bg-slate-800/90 hover:shadow-lg hover:ring-black/10 dark:hover:ring-white/10'
                        : '',
                      borderFor(t.type),
                    ].join(' ')}
                    style={swipeStyle}
                    onMouseEnter={() => pauseAutoCloseTimer(t.id)}
                    onMouseLeave={() => resumeAutoCloseTimer(t.id)}
                    onFocus={() => pauseAutoCloseTimer(t.id)}
                    onBlur={() => resumeAutoCloseTimer(t.id)}
                    onPointerDown={(e) => handleToastPointerDown(t.id, e)}
                    onPointerMove={(e) => handleToastPointerMove(t.id, e)}
                    onPointerUp={(e) => handleToastPointerEnd(t.id, e)}
                    onPointerCancel={(e) => handleToastPointerCancel(t.id, e)}
                    onLostPointerCapture={(e) => handleToastPointerCancel(t.id, e)}
                    onClick={() => {
                      if (swipe.dragging || Math.abs(swipe.dx) > 8) return
                      if (!t.onClick) return
                      t.onClick()
                      if (t.closeOnClick !== false) remove(t.id)
                    }}
                    onKeyDown={(e) => {
                      if (!t.onClick) return
                      if (e.key === 'Enter' || e.key === ' ') {
                        e.preventDefault()
                        t.onClick()
                        if (t.closeOnClick !== false) remove(t.id)
                      }
                    }}
                    tabIndex={isClickable ? 0 : undefined}
                    aria-label={isClickable ? `${title} öffnen` : undefined}
                  >
                    {img ? (
                      // ===== Layout MIT Bild: Bild links edge-to-edge (oben/unten ohne Padding) =====
                      <div className="relative pr-12 sm:pr-14">
                        <div className="flex items-stretch">
                          {/* Bild links: edge-to-edge */}
                          <div className="shrink-0">
                            <img
                              src={img}
                              alt={imgAlt}
                              loading="lazy"
                              referrerPolicy="no-referrer"
                              className={[
                                'h-full min-h-[72px] w-20 sm:w-24',
                                'object-cover object-center',
                                'rounded-l-xl',
                                'border-r border-black/5 dark:border-white/10',
                                'bg-gray-100 dark:bg-white/5',
                              ].join(' ')}
                            />
                          </div>

                          {/* Textbereich mit Padding */}
                          <div className="min-w-0 flex-1 py-3 pl-3 pr-2 sm:py-3.5 sm:pl-3.5 sm:pr-3">
                            <p className="truncate text-sm font-semibold leading-5 text-gray-900 dark:text-white">
                              {title}
                            </p>

                            {msg ? (
                              <p
                                className="mt-0.5 text-sm leading-5 text-gray-600 dark:text-gray-300 break-words"
                                style={toastMessageClampStyle}
                                title={msgWasShortened ? fullMsg : undefined}
                              >
                                {msg}
                              </p>
                            ) : null}
                          </div>
                        </div>

                        {/* Close-Button vertikal mittig rechts */}
                        <button
                          type="button"
                          onClick={(e) => {
                            e.stopPropagation()
                            remove(t.id)
                          }}
                          title="Schließen"
                          className={[
                            'absolute right-2 top-1/2 -translate-y-1/2',
                            'inline-flex h-9 w-9 sm:h-10 sm:w-10 items-center justify-center rounded-full',
                            'text-gray-400 hover:text-gray-700 dark:text-gray-300 dark:hover:text-white',
                            'hover:bg-gray-100 dark:hover:bg-white/10',
                            'active:scale-[0.98] active:bg-gray-200/80 dark:active:bg-white/15',
                            'transition',
                            'focus:outline-none focus-visible:ring-2 focus-visible:ring-indigo-500 focus-visible:ring-offset-2',
                            'dark:focus-visible:ring-indigo-400 dark:focus-visible:ring-offset-slate-900',
                          ].join(' ')}
                        >
                          <span className="sr-only">Schließen</span>
                          <XMarkIcon aria-hidden="true" className="size-5.5 sm:size-6" />
                        </button>
                      </div>
                    ) : (
                      // ===== Layout OHNE Bild: Icon mittig wie vorher =====
                      <div className="relative pr-12 py-2.5 sm:pr-14">
                        <div className="flex items-center gap-3 px-3 sm:px-3.5">
                          <div className="shrink-0">
                            <div
                              className={[
                                'inline-flex h-12 w-12 sm:h-14 sm:w-14 items-center justify-center rounded-full',
                                accents.iconBg,
                                'ring-1 ring-black/5 dark:ring-white/10',
                              ].join(' ')}
                            >
                              <Icon className={['size-5.5 sm:size-6', cls].join(' ')} aria-hidden="true" />
                            </div>
                          </div>

                          <div className="min-w-0 flex-1">
                            <p className="truncate text-sm font-semibold leading-5 text-gray-900 dark:text-white">
                              {title}
                            </p>

                            {msg ? (
                              <p
                                className="mt-0.5 text-sm leading-5 text-gray-600 dark:text-gray-300 break-words"
                                style={toastMessageClampStyle}
                                title={msgWasShortened ? fullMsg : undefined}
                              >
                                {msg}
                              </p>
                            ) : null}
                          </div>
                        </div>

                        {/* Close-Button vertikal mittig rechts */}
                        <button
                          type="button"
                          onClick={(e) => {
                            e.stopPropagation()
                            remove(t.id)
                          }}
                          title="Schließen"
                          className={[
                            'absolute right-2 top-1/2 -translate-y-1/2',
                            'inline-flex h-9 w-9 sm:h-10 sm:w-10 items-center justify-center rounded-full',
                            'text-gray-400 hover:text-gray-700 dark:text-gray-300 dark:hover:text-white',
                            'hover:bg-gray-100 dark:hover:bg-white/10',
                            'active:scale-[0.98] active:bg-gray-200/80 dark:active:bg-white/15',
                            'transition',
                            'focus:outline-none focus-visible:ring-2 focus-visible:ring-indigo-500 focus-visible:ring-offset-2',
                            'dark:focus-visible:ring-indigo-400 dark:focus-visible:ring-offset-slate-900',
                          ].join(' ')}
                        >
                          <span className="sr-only">Schließen</span>
                          <XMarkIcon aria-hidden="true" className="size-5.5 sm:size-6" />
                        </button>
                      </div>
                    )}
                  </div>
                </Transition>
              )
            })}
          </div>
        </div>
      </div>
    </ToastContext.Provider>
  )
}

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