import { ThemeProvider, useTheme } from '@wise/components-theming';
import { clsx } from 'clsx';
import { MouseEvent, ReactNode, useCallback, useEffect, useRef, useState } from 'react';
import { CSSTransition } from 'react-transition-group';

import {
  addNoScrollClass,
  removeNoScrollClass,
  type CommonProps,
  type PositionBottom,
  type PositionCenter,
  type PositionTop,
} from '../common';
import { isIosDevice } from '../common/deviceDetection';
import FocusBoundary from '../common/focusBoundary';
import withNextPortal from '../withNextPortal/withNextPortal';

import DimmerManager from './dimmerManager';

export const EXIT_ANIMATION = 350;

const dimmerManager = new DimmerManager();

export type DimmerProps = CommonProps & {
  children?: ReactNode;
  /** @default false */
  disableClickToClose?: boolean;
  contentPosition?: PositionTop | PositionCenter | PositionBottom;
  /** @default false */
  fadeContentOnEnter?: boolean;
  /** @default false */
  fadeContentOnExit?: boolean;
  /** @default false */
  open?: boolean;
  /** @default false */
  scrollable?: boolean;
  /** @default false */
  transparent?: boolean;
  onClose?: (event: KeyboardEvent | MouseEvent) => void;
  onExited?: () => void;
};

export const handleTouchMove = (event: Event) => {
  const isTouchedElementDimmer = (event.target as HTMLDivElement).classList.contains('dimmer');

  // disable scroll on iOS devices for Dimmer area
  // this is because of bug in WebKit https://bugs.webkit.org/show_bug.cgi?id=220908
  // note: scrolling still works for children(s) as expected
  if (isIosDevice() && isTouchedElementDimmer) {
    event.stopPropagation();
    event.preventDefault();
  }
};

const Dimmer = ({
  children,
  className,
  disableClickToClose = false,
  contentPosition,
  fadeContentOnEnter = false,
  fadeContentOnExit = false,
  open = false,
  scrollable = false,
  transparent = false,
  onClose,
  onExited: handleExited,
}: DimmerProps) => {
  const [hasNotExited, setHasNotExited] = useState(false);
  const dimmerReference = useRef<HTMLDivElement>(null);

  const closeOnClick = (event: MouseEvent<HTMLDivElement>) => {
    if (event.target === dimmerReference.current) {
      onClose?.(event);
    }
  };

  const handleClick = (event: MouseEvent<HTMLDivElement>) => {
    if (disableClickToClose || !onClose) {
      return;
    }

    closeOnClick(event);
  };

  const handleKeyDown = useCallback(
    (event: KeyboardEvent) => {
      if (event.key !== 'Escape') {
        return;
      }
      event.stopPropagation();

      if (onClose && dimmerReference.current && dimmerManager.isTop(dimmerReference.current)) {
        onClose(event);
      }
    },
    [onClose],
  );

  const onEnter = () => {
    setHasNotExited(true);

    if (dimmerReference.current) {
      dimmerManager.add(dimmerReference.current);
    }
  };
  const onExited = () => {
    setHasNotExited(false);

    if (dimmerReference.current) {
      dimmerManager.remove(dimmerReference.current);
    }

    handleExited?.();
  };

  useEffect(() => {
    const localReferenceCopy = dimmerReference.current;

    if (open) {
      document.addEventListener('keydown', handleKeyDown);
      localReferenceCopy?.addEventListener('touchmove', handleTouchMove, { passive: true });
    }
    return () => {
      document.removeEventListener('keydown', handleKeyDown);

      localReferenceCopy?.removeEventListener('touchmove', handleTouchMove);
    };
  }, [handleKeyDown, open]);

  return (
    <DimmerWrapper open={open} hasNotExited={hasNotExited}>
      <CSSTransition
        nodeRef={dimmerReference}
        in={open}
        appear
        // Wait for animation to finish before unmount.
        timeout={{ enter: 0, exit: EXIT_ANIMATION }}
        classNames={{
          enter: clsx({ 'dimmer--enter-fade': fadeContentOnEnter }),
          enterDone: clsx('dimmer--enter-done', {
            'dimmer--enter-fade': fadeContentOnEnter,
          }),
          exit: clsx('dimmer--exit', { 'dimmer--exit-fade': fadeContentOnExit }),
        }}
        unmountOnExit
        onEnter={onEnter}
        onExited={onExited}
      >
        <DimmerContentWrapper scrollBody={!transparent}>
          <FocusBoundary>
            <div
              ref={dimmerReference}
              className={clsx(
                'dimmer',
                { 'dimmer--scrollable': scrollable, 'dimmer--transparent': transparent },
                className,
              )}
              role="presentation"
              onClick={handleClick}
            >
              <div
                className={clsx(
                  'dimmer-content-positioner',
                  contentPosition != null && [
                    'd-flex justify-content-center',
                    {
                      'align-items-start': contentPosition === 'top',
                      'align-items-center': contentPosition === 'center',
                      'align-items-end': contentPosition === 'bottom',
                    },
                  ],
                )}
              >
                {children}
              </div>
            </div>
          </FocusBoundary>
        </DimmerContentWrapper>
      </CSSTransition>
    </DimmerWrapper>
  );
};

const DimmerWrapper: React.ComponentType<{
  open: boolean;
  hasNotExited: boolean;
  children: React.ReactNode;
}> = ({ open, hasNotExited, children }) => {
  const { screenMode, theme } = useTheme();

  return open || hasNotExited ? (
    <ThemeProvider theme="personal" screenMode={theme === 'personal' ? screenMode : 'light'}>
      {children}
    </ThemeProvider>
  ) : (
    <>{children}</>
  );
};

export const DimmerContentWrapper = ({
  children,
  scrollBody,
}: {
  children: React.ReactElement;
  scrollBody: boolean;
}) => {
  useEffect(() => {
    if (scrollBody) {
      addNoScrollClass();
    }

    return () => {
      if (scrollBody) {
        removeNoScrollClass();
      }
    };
  }, [scrollBody]);

  return children;
};

// Export without the Portal for tests only
export { Dimmer };

export default withNextPortal(Dimmer);
