import type { EmitsOptions, MaybeRefOrGetter } from 'vue';

import {
  camelize,
  computed,
  getCurrentInstance,
  toHandlerKey,
  toValue,
} from 'vue';

function getEmitKeys(emits?: EmitsOptions | null) {
  if (Array.isArray(emits)) {
    return emits;
  }
  if (emits && typeof emits === 'object') {
    return Object.keys(emits);
  }
  return [];
}

function useEmitAsProps<Name extends string>(
  emit: (name: Name, ...args: any[]) => void,
) {
  const vm = getCurrentInstance();
  const emitKeys = getEmitKeys(vm?.type.emits);
  const emitProps: Record<string, any> = {};

  for (const key of emitKeys) {
    emitProps[toHandlerKey(camelize(key))] = (...args: any[]) =>
      emit(key as Name, ...args);
  }

  return emitProps;
}

function useForwardProps<T extends Record<string, any>>(
  props: MaybeRefOrGetter<T>,
) {
  const vm = getCurrentInstance();
  const defaultProps = Object.keys(vm?.type.props ?? {}).reduce(
    (defaults, key) => {
      const defaultValue = (vm?.type.props as Record<string, any>)[key]?.default;
      if (defaultValue !== undefined) {
        defaults[key] = defaultValue;
      }
      return defaults;
    },
    {} as Record<string, any>,
  );

  return computed(() => {
    const forwardedProps: Record<string, any> = {};
    const vnodeProps = Object.keys(vm?.vnode.props ?? {}).reduce(
      (normalizedProps, key) => {
        normalizedProps[camelize(key)] = vm?.vnode.props?.[key];
        return normalizedProps;
      },
      {} as Record<string, any>,
    );
    const currentProps = toValue(props);

    for (const key of Object.keys({ ...defaultProps, ...vnodeProps })) {
      const value = currentProps[key];
      if (value !== undefined) {
        forwardedProps[key] = value;
      }
    }

    return forwardedProps as T;
  });
}

function useForwardPropsEmits<
  T extends Record<string, any>,
  Name extends string,
>(
  props: MaybeRefOrGetter<T>,
  emit?: (name: Name, ...args: any[]) => void,
) {
  const forwardedProps = useForwardProps(props);
  const emitProps = emit ? useEmitAsProps(emit) : {};

  return computed(() => ({
    ...forwardedProps.value,
    ...emitProps,
  }));
}

export { useEmitAsProps, useForwardProps, useForwardPropsEmits };
