import { Dialog as MuiDialog, SxProps, Theme } from '@mui/material';
import {
  ForwardRefExoticComponent,
  PropsWithChildren,
  ReactNode,
  RefAttributes,
  createContext,
  forwardRef,
  useImperativeHandle,
  useMemo,
  useState,
} from 'react';

import { Panel } from '../Panels';
import Spinner from '../Spinner';

const DialogContext = createContext<DialogContextContent | undefined>(
  undefined,
);

const Dialog: ForwardRefExoticComponent<
  PropsWithChildren<DialogProps> & RefAttributes<DialogForwardedRefContent>
> = forwardRef<DialogForwardedRefContent, DialogProps>((props, ref) => {
  const {
    children: externalChildren,
    dialogProps = {},
    loading,
    openInitially = false,
    wide,
  } = props;

  const {
    // Do not initialize the external open state because we need it to
    // determine whether the dialog is controlled or uncontrolled.
    open: externalOpen,
    PaperProps: paperProps = {},
    ...restDialogProps
  } = dialogProps;

  const { sx: externalPaperSx, ...restPaperProps } = paperProps;

  const [controlOpen, setControlOpen] = useState<boolean>(openInitially);

  const open = useMemo<boolean>(
    () => externalOpen ?? controlOpen,
    [controlOpen, externalOpen],
  );

  const children = useMemo<ReactNode>(
    () => (loading ? <Spinner mt={0} /> : externalChildren),
    [externalChildren, loading],
  );

  const paperSx = useMemo<SxProps<Theme>>(
    () => ({
      minWidth: wide ? { xs: 'calc(100%)', md: '50em' } : null,
      overflow: 'visible',
      ...externalPaperSx,
    }),
    [externalPaperSx, wide],
  );

  useImperativeHandle(
    ref,
    () => ({
      open,
      setOpen: setControlOpen,
    }),
    [open],
  );

  return (
    <MuiDialog
      open={open}
      PaperComponent={Panel}
      PaperProps={{ ...restPaperProps, sx: paperSx }}
      {...restDialogProps}
    >
      <DialogContext.Provider value={{ open, setOpen: setControlOpen }}>
        {children}
      </DialogContext.Provider>
    </MuiDialog>
  );
});

Dialog.displayName = 'Dialog';

export { DialogContext };

export default Dialog;
