import type { ReactNode } from 'react';
import React, { memo, useLayoutEffect, useMemo } from 'react';
// Fix issue no crypto in react-native
// https://stackoverflow.com/a/66852908
import { customAlphabet } from 'nanoid/non-secure';
import { usePortal } from './usePortal';
import { PortalProvider } from './PortalProvider';
import { PortalHost } from './PortalHost';
import type { Theme } from '../../theme';
import { ThemeProvider, useTheme } from '../../theme';

const nanoid = customAlphabet('abcdefghijklmnopqrstuvwxyz0123456789', 10);

interface PortalProps {
  /*
   * Name of the portal. If name is not provided, a random name will be generated.
   */
  name?: string;

  /*
   * Name of the portal host. If name is not provided, a default host will be used.
   */
  hostName?: string;

  /*
   * Content of the portal.
   */
  children?: ReactNode | ReactNode[];
  /**
   * Theme for portal children.
   */
  theme?: Theme;
}

const PortalComponent = ({ name, hostName, children, theme }: PortalProps) => {
  const defaultTheme = useTheme();
  const { addPortal: addUpdatePortal, removePortal } = usePortal(hostName);

  const nameOrRandom = useMemo(() => name || nanoid(), [name]);

  const ChildrenComponent = useMemo(
    () => (
      <ThemeProvider theme={theme || defaultTheme}>{children}</ThemeProvider>
    ),
    [theme, children, defaultTheme]
  );

  useLayoutEffect(() => {
    addUpdatePortal(nameOrRandom, ChildrenComponent);
    return () => {
      removePortal(nameOrRandom);
    };
  }, [addUpdatePortal]);

  useLayoutEffect(() => {
    addUpdatePortal(nameOrRandom, ChildrenComponent);
  }, [ChildrenComponent]);

  return null;
};

const Portal = memo(PortalComponent);
Portal.displayName = 'Portal';

export default Object.assign(Portal, {
  Provider: PortalProvider,
  Host: PortalHost,
});
