import React from 'react'
import PropTypes from 'prop-types'
import { hasOwnProperty } from './hasOwnProperty'

/**
 * Higher-order component that has no effect unless an additional prop `LinkRouter` is passed.
 * This may be used to provide custom wrappers for integrations with third party libraries.
 *
 * If LinkRouter is passed, LinkRouter is rendered in place of the main component and is passed:
 *
 *  - `linkRouterProps`: an optional object passed alongside LinkRouter, for props needed by the wrapper
 *    that are not valid props for the wrapped component.
 *  - `Component`: automatically provided, the original component to render inside the wrapper.
 *  - `ref`: forwarded `ref` passed down to `Component`
 *  - All other props passed to the outer component
 *
 * @example A LinkRouter component to be used with link-like components might look like:
 *
 * ```jsx
 * const LinkLinkRouter = React.forwardRef(({ Component, linkRouterProps: { to, options }, href, ...rest }, ref) => {
 *   const { href: resolvedHref, onClick } = useSomeRouterHook({ to, href, options })
 *   return <Component href={resolvedHref} onPress={onClick} {...rest} />
 * })
 * ```
 *
 * Any component that takes href and onPress props may then use this wrapper:
 *
 * ```jsx
 * <Link href={href} LinkRouter={LinkLinkRouter} linkRouterProps={{ to, options }}>Some link</Link>
 * <IconButton icon={SomeIcon} LinkRouter={LinkLinkRouter} linkRouterProps={{ to, options }} ref={iconRef} />
 * ```
 */
export const withLinkRouter = (Component) => {
  const wrappedComponent = React.forwardRef(({ LinkRouter, linkRouterProps, ...props }, ref) => {
    if (!LinkRouter) return <Component {...props} ref={ref} />
    return (
      <LinkRouter linkRouterProps={linkRouterProps} Component={Component} ref={ref} {...props} />
    )
  })

  // Ensure the returned component has appropriate outer properties set:
  /* eslint-disable-next-line react/forbid-foreign-prop-types */
  const { displayName, name, propTypes, ...otherProperties } = Component

  // Apply unique component name as a displayName
  wrappedComponent.displayName = Component.displayName || Component.name

  // Apply proptypes including wrapper props - is safely { ...undefined, ...undefined } in prod
  wrappedComponent.propTypes = { ...Component.propTypes, ...withLinkRouter.propTypes }

  // Forward any other properties explicitly set e.g. Component.SubComponent
  Object.keys(otherProperties).forEach((key) => {
    // Skip internal React properties from wrappedComponent's forwardRef (render, $$typeof, etc)
    if (hasOwnProperty(Component, key) && !hasOwnProperty(wrappedComponent, key)) {
      wrappedComponent[key] = Component[key]
    }
  })
  return wrappedComponent
}

withLinkRouter.propTypes = {
  /**
   * Wrapper component rendered in place of this component, for integrating with a third-party
   * router. Receives `Component`, `linkRouterProps`, the forwarded `ref` and all other props.
   *
   * @see {@link withLinkRouter}
   */
  LinkRouter: PropTypes.elementType,
  /**
   * Props passed to `LinkRouter` that are not valid props of this component, such as a router
   * `to` target. Has no effect unless `LinkRouter` is also passed.
   */
  linkRouterProps: PropTypes.object
}
