/**
 * useToast / ToastProvider — React-idiomatic toast API.
 * @countermeasure-platform/web-components/services/toast-react
 *
 * Matches the shadcn `useToast` shape so consumer apps can drop the
 * library in as a replacement for their local hook + provider:
 *
 *   const { toast, dismiss, toasts } = useToast()
 *   const t = toast({ title: 'Saved', description: 'OK', variant: 'success' })
 *   t.dismiss()
 *   t.update({ title: 'Saving' })
 *
 * Visuals are rendered through the existing `<Toast>` primitive at
 * `react/primitives/toast` so the look stays consistent across surfaces.
 *
 * Variant `'destructive'` is treated as an alias of `'error'` so consumers
 * migrating from the local hook (which used the same naming as the legacy
 * Alert variant set) keep working without code churn.
 */

import * as React from 'react'
import { Toast as ToastPrimitive, type ToastData, type ToastLevel } from '../react/primitives/toast'
import { cn } from '../react/primitives/utils'

/* ------------------------------------------------------------------ */
/* Public API                                                          */
/* ------------------------------------------------------------------ */

export type ToastVariant = 'default' | 'destructive' | 'success' | 'info' | 'warning'

export interface ToastOptions {
  title?: React.ReactNode
  description?: React.ReactNode
  variant?: ToastVariant
  action?: React.ReactElement
  /** Auto-dismiss in ms. Falls back to provider's `duration`. */
  duration?: number
}

export interface ToasterToast extends ToastOptions {
  id: string
  open: boolean
}

export interface ToastReturn {
  id: string
  dismiss: () => void
  update: (opts: ToastOptions) => void
}

export interface UseToastResult {
  toast: (opts: ToastOptions) => ToastReturn
  dismiss: (id?: string) => void
  toasts: ToasterToast[]
}

/* ------------------------------------------------------------------ */
/* Internal helpers                                                    */
/* ------------------------------------------------------------------ */

function variantToLevel(variant: ToastVariant | undefined): ToastLevel {
  switch (variant) {
    case 'destructive':
      return 'error'
    case 'success':
      return 'success'
    case 'warning':
      return 'warning'
    case 'info':
      return 'info'
    case 'default':
    case undefined:
    default:
      return 'info'
  }
}

function reactNodeToString(node: React.ReactNode): string {
  if (node === null || node === undefined || typeof node === 'boolean') return ''
  if (typeof node === 'string') return node
  if (typeof node === 'number') return String(node)
  if (Array.isArray(node)) return node.map(reactNodeToString).join('')
  if (React.isValidElement(node)) {
    const props = (node as React.ReactElement<{ children?: React.ReactNode }>).props
    return reactNodeToString(props?.children)
  }
  return ''
}

function toastDataFromOptions(id: string, opts: ToastOptions): ToastData {
  const data: ToastData = {
    id,
    level: variantToLevel(opts.variant),
    // ToastData.title is `string`; coerce React nodes for the underlying
    // primitive while preserving the original ReactNode in `ToasterToast`.
    title: reactNodeToString(opts.title),
  }
  const desc = opts.description
  if (desc !== undefined && desc !== null && desc !== false) {
    data.description = reactNodeToString(desc)
  }
  if (opts.action !== undefined) {
    data.action = opts.action
  }
  if (opts.duration !== undefined) {
    data.duration = opts.duration
  }
  return data
}

/* ------------------------------------------------------------------ */
/* Context                                                             */
/* ------------------------------------------------------------------ */

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

export function useToast(): UseToastResult {
  const ctx = React.useContext(ToastContext)
  if (!ctx) {
    throw new Error(
      'useToast must be used within a <ToastProvider> from @countermeasure-platform/web-components/services/toast-react'
    )
  }
  return ctx
}

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

let toastCounter = 0

export interface ToastProviderProps {
  children: React.ReactNode
  /** Default auto-dismiss duration in ms. Defaults to 5000. */
  duration?: number
  /**
   * Direction of swipe-to-dismiss. The viewport renders at bottom-right
   * by default; this prop is captured as a `data-swipe-direction` attribute
   * for downstream tooling and Radix-style touch gestures.
   */
  swipeDirection?: 'right' | 'left' | 'up' | 'down'
  /** Optional className applied to the viewport container. */
  viewportClassName?: string
}

export function ToastProvider({
  children,
  duration = 5000,
  swipeDirection = 'right',
  viewportClassName,
}: ToastProviderProps) {
  const [toasts, setToasts] = React.useState<ToasterToast[]>([])

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

  const update = React.useCallback((id: string, patch: ToastOptions) => {
    setToasts(prev => prev.map(t => (t.id === id ? { ...t, ...patch } : t)))
  }, [])

  const toast = React.useCallback(
    (opts: ToastOptions): ToastReturn => {
      toastCounter += 1
      const id = `cmm-toast-${String(toastCounter)}`
      const entry: ToasterToast = { ...opts, id, open: true }
      setToasts(prev => [...prev, entry])
      return {
        id,
        dismiss: () => {
          dismiss(id)
        },
        update: (patch: ToastOptions) => {
          update(id, patch)
        },
      }
    },
    [dismiss, update]
  )

  const value = React.useMemo<UseToastResult>(
    () => ({ toast, dismiss, toasts }),
    [toast, dismiss, toasts]
  )

  return (
    <ToastContext.Provider value={value}>
      {children}
      <ToastViewport
        toasts={toasts}
        dismiss={dismiss}
        defaultDuration={duration}
        swipeDirection={swipeDirection}
        {...(viewportClassName !== undefined ? { className: viewportClassName } : {})}
      />
    </ToastContext.Provider>
  )
}

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

interface ToastViewportProps {
  toasts: ToasterToast[]
  dismiss: (id: string) => void
  defaultDuration: number
  swipeDirection: 'right' | 'left' | 'up' | 'down'
  className?: string | undefined
}

function ToastViewport({
  toasts,
  dismiss,
  defaultDuration,
  swipeDirection,
  className,
}: ToastViewportProps) {
  return (
    <div
      data-slot="toast-viewport"
      data-swipe-direction={swipeDirection}
      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 bottom-4 right-4',
        className
      )}
    >
      {toasts.map(t => (
        <ToastPrimitive
          key={t.id}
          data={toastDataFromOptions(t.id, t)}
          onDismiss={() => {
            dismiss(t.id)
          }}
          defaultDuration={defaultDuration}
        />
      ))}
    </div>
  )
}

/* ------------------------------------------------------------------ */
/* Headless re-exports                                                 */
/* ------------------------------------------------------------------ */

export type { ToastData, ToastLevel }
