'use client';

import * as AlertDialog from '@radix-ui/react-alert-dialog';
import React, {
  createContext,
  useCallback,
  useContext,
  useMemo,
  useState,
} from 'react';
import { twMerge } from 'tailwind-merge';

import Button, {
  Props as ButtonProps,
  ButtonShape,
  ButtonSize,
  ButtonVariant,
} from '@/components/button/Button';
import { PolymorphicComponent } from '@/utils/polymorphic';

type DialogComponentChild<A = any> = React.ComponentType<{
  onDialogAction: (action: A) => void;
}>;

type DialogActions<A = any> =
  | DialogComponentChild<A>
  | Array<
      Pick<
        ButtonProps,
        | 'className'
        | 'iconClassName'
        | 'contentClassName'
        | 'active'
        | 'variant'
        | 'size'
        | 'shape'
        | 'backgroundImage'
        | 'backgroundHoverAnimationSpeed'
        | 'iconStart'
        | 'icon'
        | 'iconEnd'
      > & {
        label: ButtonProps['children'];
        action: A;
        autoFocus?: boolean;
        isCancel?: boolean;
      }
    >;

type Dialog<A = any> = {
  className?: string;
  contentClassName?: string;
  actionsClassName?: string;
  content: React.ReactNode | DialogComponentChild<A>;
  onEscapeKeyDown?: (event: KeyboardEvent) => void;
  actions: React.ReactNode | DialogComponentChild<A>;
};

type LaunchDialogFn = <A = any>(
  content: React.ReactNode | DialogComponentChild<A>,
  actions: DialogActions<A>,
  options?: {
    className?: string;
    contentClassName?: string;
    actionsClassName?: string;
  }
) => Promise<A>;

export const DialogModalContext = createContext<{
  launchDialog: LaunchDialogFn;
  currentDialog?: Dialog;
} | null>(null);

export function useDialogModal() {
  const context = useContext(DialogModalContext);
  if (!context) {
    throw new Error('useDialogModal must be used within a DialogProvider');
  }
  return context;
}

export const DialogContextProvider: React.FC<{
  children?: React.ReactNode;
}> = (props) => {
  const { children } = props;
  const [dialogQueue, setDialogQueue] = useState<Dialog[]>([]);

  const launchDialog = useCallback(
    // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-constraint
    <A extends any = any>(
      content: React.ReactNode | DialogComponentChild<A>,
      actions: DialogActions<A>,
      options?: {
        className?: string;
        contentClassName?: string;
        actionsClassName?: string;
      }
    ) => {
      const { className, contentClassName, actionsClassName } = options || {};
      return new Promise<A>((resolve) => {
        const handleDialogAction = (action: A) => {
          resolve(action);
          setDialogQueue((prevDialogs) => prevDialogs.slice(1));
        };
        const cancelAction =
          typeof actions !== 'function'
            ? (
                actions.find(({ isCancel }) => isCancel) ||
                actions.find(({ action }) => action === false)
              )?.action
            : undefined;

        let primaryActionIndex = 0;
        let secondaryActionIndex = 1;
        if (typeof actions !== 'function') {
          // Primary action: Autofocus button, or first button that is not a cancel button
          primaryActionIndex = actions.findIndex(
            ({ autoFocus, isCancel }) => autoFocus && !isCancel
          );
          if (primaryActionIndex === -1) {
            primaryActionIndex = actions.findIndex(({ isCancel }) => !isCancel);
          }
          // Secondary action: Cancel button, or first button that is not the primary action
          secondaryActionIndex = actions.findIndex(({ isCancel }) => isCancel);
          if (secondaryActionIndex === -1) {
            secondaryActionIndex = primaryActionIndex ? 0 : 1;
          }
        }
        const componentChildProps = {
          onDialogAction: handleDialogAction,
        };
        const dialog = {
          className,
          contentClassName,
          actionsClassName,
          content:
            typeof content === 'function'
              ? () => React.createElement(content, componentChildProps)
              : content,
          onEscapeKeyDown:
            cancelAction !== undefined
              ? () => handleDialogAction(cancelAction)
              : undefined,
          actions:
            typeof actions === 'function' ? (
              () => React.createElement(actions, componentChildProps)
            ) : (
              <>
                {actions.map(
                  (
                    {
                      action,
                      label,
                      isCancel = action === false,
                      autoFocus,
                      ...buttonProps
                    },
                    i
                  ) => {
                    const AlertDialogAction = isCancel
                      ? AlertDialog.Cancel
                      : AlertDialog.Action;

                    return (
                      <AlertDialogAction
                        asChild
                        key={`action-${i}`}
                        autoFocus={autoFocus}
                      >
                        <Button
                          variant={
                            i === primaryActionIndex
                              ? ButtonVariant.Primary
                              : i === secondaryActionIndex
                                ? ButtonVariant.Secondary
                                : undefined
                          }
                          shape={ButtonShape.Rounded}
                          size={ButtonSize.Small}
                          {...buttonProps}
                          onClick={() => handleDialogAction(action)}
                        >
                          {label}
                        </Button>
                      </AlertDialogAction>
                    );
                  }
                )}
              </>
            ),
        };
        // Defer the new dialog to avoid a race condition when launching from a popover menu
        setTimeout(() => {
          setDialogQueue((prevDialogs) => prevDialogs.concat(dialog));
        }, 0);
      });
    },
    []
  );

  const value = useMemo(
    () => ({
      launchDialog,
      currentDialog: dialogQueue[0],
    }),
    [launchDialog, dialogQueue]
  );

  return (
    <DialogModalContext.Provider value={value}>
      {children}
    </DialogModalContext.Provider>
  );
};

export type Props = {
  contentClassName?: string;
  actionsClassName?: string;
  content?: React.ReactNode | DialogComponentChild;
  actions?: React.ReactNode | DialogComponentChild;
};

const DialogModal: PolymorphicComponent<Props> = (props) => {
  const {
    as: Component = 'div',
    content,
    actions,
    children,
    className,
    contentClassName,
    actionsClassName,
    ...restProps
  } = props;
  return (
    <Component
      {...restProps}
      className={twMerge(
        '@container relative flex flex-col items-center justify-center rounded-lg',
        'w-[400px] max-w-full min-w-[200px] gap-4 py-4',
        'shdaow-lg bg-background-tertiary text-foreground-primary',
        className
      )}
    >
      {children}
      {content && (
        <div
          className={twMerge(
            'w-full px-4 text-center font-sans text-base',
            contentClassName
          )}
        >
          {typeof content === 'function'
            ? React.createElement(content)
            : content}
        </div>
      )}
      {actions && (
        <div
          className={twMerge(
            'flex flex-row items-center justify-center gap-2 px-4',
            actionsClassName
          )}
        >
          {typeof actions === 'function'
            ? React.createElement(actions)
            : actions}
        </div>
      )}
    </Component>
  );
};

export const DialogModalContainer = () => {
  const { currentDialog } = useDialogModal();
  return !currentDialog ? null : (
    <AlertDialog.Root open>
      <AlertDialog.Portal>
        <AlertDialog.Overlay className='fixed inset-0 z-100001 animate-fade-in bg-black/60 animate-duration-100' />
        <AlertDialog.Content
          className='fixed inset-0 z-100001 flex animate-slide-in-modal flex-col items-start justify-start overflow-auto p-2 animate-duration-100'
          onEscapeKeyDown={currentDialog.onEscapeKeyDown}
        >
          <DialogModal
            className={twMerge('m-auto', currentDialog.className)}
            content={currentDialog.content}
            actions={currentDialog.actions}
            contentClassName={currentDialog.contentClassName}
            actionsClassName={currentDialog.actionsClassName}
          />
        </AlertDialog.Content>
      </AlertDialog.Portal>
    </AlertDialog.Root>
  );
};

export const DialogModalTitle: React.FC<AlertDialog.AlertDialogTitleProps> = (
  props
) => {
  return <AlertDialog.Title className='text-xl font-medium' {...props} />;
};

export const DialogModalBody: React.FC<
  AlertDialog.AlertDialogDescriptionProps
> = (props) => {
  return <AlertDialog.Description className='text-base' {...props} />;
};

export default DialogModal;
