type EmptyCheckConfig = {
  removeNull?: boolean
  removeUndefined?: boolean
  removeFalse?: boolean
  removeEmptyString?: boolean
  removeZero?: boolean
  removeEmptyArray?: boolean
  removeEmptyObject?: boolean
  customCheck?: (value: unknown) => boolean
}

const DEFAULT_CONFIG: Required<Omit<EmptyCheckConfig, "customCheck">> = {
  removeNull: true,
  removeUndefined: true,
  removeFalse: false,
  removeEmptyString: false,
  removeZero: false,
  removeEmptyArray: false,
  removeEmptyObject: false,
}

const isEmpty = (value: unknown, config: EmptyCheckConfig): boolean => {
  const checks: Array<() => boolean> = [
    () => config.removeNull === true && value === null,
    () => config.removeUndefined === true && value === undefined,
    () => config.removeFalse === true && value === false,
    () => config.removeEmptyString === true && value === "",
    () => config.removeZero === true && value === 0,
    () =>
      config.removeEmptyArray === true &&
      Array.isArray(value) &&
      value.length === 0,
    () =>
      config.removeEmptyObject === true &&
      typeof value === "object" &&
      value !== null &&
      !Array.isArray(value) &&
      Object.keys(value).length === 0,
    () => config.customCheck?.(value) ?? false,
  ]

  return checks.some((check) => check())
}

const recurse = (value: unknown, config: EmptyCheckConfig): unknown => {
  if (Array.isArray(value)) {
    return value
      .map((item) => recurse(item, config))
      .filter((item) => !isEmpty(item, config))
  }

  if (typeof value === "object" && value !== null) {
    return Object.fromEntries(
      Object.entries(value)
        .map(([key, val]) => [key, recurse(val, config)] as const)
        .filter(([, val]) => !isEmpty(val, config)),
    )
  }

  return value
}

export const removeEmptyValues = <T extends object>(
  obj: T,
  config: EmptyCheckConfig = {},
): Partial<T> => {
  const mergedConfig = { ...DEFAULT_CONFIG, ...config }
  return recurse(obj, mergedConfig) as Partial<T>
}
