import PropTypes from 'prop-types';

import { __RouterContext } from 'react-router';

import hoistStatics from 'hoist-non-react-statics';

export const WithRouterPropTypes = {
  match: PropTypes.object.isRequired,
  location: PropTypes.object.isRequired,
  history: PropTypes.object.isRequired,
};

export const WithOptionalRouterPropTypes = {
  match: PropTypes.object,
  location: PropTypes.object,
  history: PropTypes.object,
};

export interface OptionalRouterProps {
  ref: unknown;
  wrappedComponentRef?: string | ((...args: unknown[]) => unknown) | object;
}

// This is copied from https://github.com/remix-run/react-router/blob/v5.3.4/packages/react-router/modules/withRouter.js
// but does not fail if called outside of a React Router context
export function withOptionalRouter<
  ComponentType extends React.ComponentType<OptionalRouterProps>,
>(Component: ComponentType) {
  const displayName = `withRouter(${Component.displayName ?? Component.name})`;
  const C = (props: React.ComponentProps<ComponentType>) => {
    const { wrappedComponentRef, ...remainingProps } = props;

    return (
      <__RouterContext.Consumer>
        {(context) => {
          // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
          if (context) {
            return (
              // @ts-expect-error - Dynamic covariant generic components are tough to type.
              <Component
                {...remainingProps}
                {...context}
                ref={wrappedComponentRef}
              />
            );
          } else {
            // @ts-expect-error - Dynamic covariant generic components are tough to type.
            return <Component {...remainingProps} ref={wrappedComponentRef} />;
          }
        }}
      </__RouterContext.Consumer>
    );
  };

  C.displayName = displayName;
  C.WrappedComponent = Component;

  return hoistStatics(C, Component);
}
