import React, { useCallback, useMemo, useState } from 'react';
import * as BaseToast from '@radix-ui/react-toast';

import { createContextSelector } from '../utils/react';
import { type ToastStatus, Toast } from './toast';
import { ToastViewport } from './toast-viewport';

export type ToastContextValues = {
  vertical: 'top' | 'bottom';
  horizontal: 'left' | 'right';
  enqueueToast: (text: string, options?: EnqueuedToastOptions) => ToastId;
  closeToast: (id: ToastId) => void;
  closeAll: () => void;
};

type ToastId = number;

const getToastId = (() => {
  let nextToastId = 1;
  return () => nextToastId++;
})();

type EnqueuedToastOptions = {
  status?: ToastStatus;
  duration?: number;
  closable?: boolean;
};

type EnqueuedToast = {
  id: ToastId;
  text: string;
  options?: EnqueuedToastOptions;
};

export interface ToastProviderProps
  extends Omit<BaseToast.ToastProviderProps, 'swipeDirection'> {
  vertical?: 'top' | 'bottom';
  horizontal?: 'left' | 'right';
  maxQueueSize?: number;
}

const [ToastContext, useToastContext] =
  createContextSelector<ToastContextValues>({
    vertical: 'bottom',
    horizontal: 'right',
    enqueueToast: () => 0,
    closeToast: () => {},
    closeAll: () => {},
  });

export const ToastProvider = ({
  children,
  vertical = 'bottom',
  horizontal = 'right',
  maxQueueSize = 3,
  ...rest
}: ToastProviderProps) => {
  const [queue, setQueue] = useState<EnqueuedToast[]>([]);

  const addToQueue = useCallback(
    (text: string, options?: EnqueuedToastOptions) => {
      const id = getToastId();

      setQueue(state =>
        [{ id, text, options }, ...state].slice(0, maxQueueSize),
      );

      return id;
    },
    [maxQueueSize],
  );

  const removeFromQueue = useCallback(
    (id: ToastId) => setQueue(state => state.filter(entry => entry.id !== id)),
    [],
  );

  const clearQueue = useCallback(() => setQueue([]), []);

  const contextValue = useMemo(
    () => ({
      vertical,
      horizontal,
      enqueueToast: addToQueue,
      closeToast: removeFromQueue,
      closeAll: clearQueue,
    }),
    [vertical, horizontal, addToQueue, removeFromQueue, clearQueue],
  );

  const renderQueueItem = useCallback(
    (toast: EnqueuedToast) => (
      <Toast
        {...toast.options}
        key={toast.id}
        vertical={vertical}
        horizontal={horizontal}
      >
        {toast.text}
      </Toast>
    ),
    [vertical, horizontal],
  );

  return (
    <ToastContext.Provider value={contextValue}>
      <BaseToast.ToastProvider {...rest} swipeDirection={horizontal}>
        {children}

        {queue.map(item => renderQueueItem(item))}

        <ToastViewport vertical={vertical} horizontal={horizontal} />
      </BaseToast.ToastProvider>
    </ToastContext.Provider>
  );
};

export { useToastContext };
