import React, { ElementType, PropsWithChildren, PropsWithoutRef, ReactElement } from 'react'

import { PropsOf, RefPropsOf } from '../utils'

/**
 * Component creation utility, which combines forwardRef, memo and ensures correct typing with all props included.
 * Config object allows toggling memoization and providing custom 'areEqual' function.
 */
export function vui<C extends React.ElementType, Props = object>(
  component: React.ForwardRefRenderFunction<any, PropsOf<C, Props>>,
  config?: VuiComponentConfig<C, Props>,
) {
  const { memoCompare, isMemo = true } = config ?? {}

  if (isMemo) {
    return React.memo(
      React.forwardRef(component as unknown as any),
      memoCompare as unknown as any,
    ) as unknown as VuiComponent<C, Props>
  }

  return React.forwardRef(component as unknown as any) as unknown as VuiComponent<C, Props>
}

/** VUI component type, which allows element props inference and extension, 'as' and 'ref'. */
export type VuiComponent<C extends ElementType, Props = object> = {
  // 'as' component call signature
  <AsC extends ElementType>(
    props: PropsWithChildren<RefPropsOf<AsC, Props>> & {
      as: AsC
    },
  ): ReactElement<any, any> | null
  // Regular call signature should be last, so that 'styled' can infer props correctly
  (props: RefPropsOf<C, Props>): ReactElement<any, any> | null

  defaultProps?: Partial<PropsOf<C, Props>>
  displayName?: string
}

/** Config for VUI component creator helper, which allows toggling memoization and 'areEqual' function. */
export type VuiComponentConfig<C extends ElementType, Props = object> = {
  isMemo?: boolean
  memoCompare?: (
    prevProps: Readonly<PropsWithChildren<PropsWithoutRef<PropsOf<C, Props>>>>,
    nextProps: Readonly<PropsWithChildren<PropsWithoutRef<PropsOf<C, Props>>>>,
  ) => boolean
}

/**
 * @deprecated Use FC or plain functions instead. If you need typing for HTML attributes of a given element,
 * use PropsOf type, like: type MyBoxProps = PropsOf<'div', MyProps>
 */
export type VC<C extends ElementType, Props = object> = {
  (props: PropsOf<C, Props>): ReactElement<any, any> | null

  defaultProps?: Partial<PropsOf<C, Props>>
  displayName?: string
}

export default vui
