import { PropType } from 'vue';

type ExtractNonArray<T> = T extends (infer U)[] ? never : T

type Prop<T> = ExtractNonArray<PropType<T>>

type NativeType = string | boolean | number | null | undefined | Function

type Constructor = new (...args: any[]) => any

interface PropOptions<T = any, D = T> {
  type?: PropType<T> | true | null
  required?: boolean
  default?: D | DefaultFactory<D> | null | undefined | object
  validator?(value: unknown, props: Record<string, unknown>): boolean
}

// see https://github.com/vuejs/vue-next/blob/22717772dd83b67ffaa6ad9805c6269e184c7e41/packages/runtime-core/src/componentProps.ts#L67
type InferType<T> = T extends { type: null | true }
  ? any
  : T extends ObjectConstructor | { type: ObjectConstructor }
    ? Record<string, any>
    : T extends Prop<infer V>
      ? V
      : T extends PropOptions<infer V>
        ? V
        : T extends VueTypeDef<infer V>
          ? V
          : T extends VueTypeValidableDef<infer V>
            ? V
            : T

type ValidatorFunction<T> = (
  value: T,
  props?: Record<string, unknown>,
) => boolean

type DefaultFactory<T> = (() => T) | T

type DefaultType<T> = T extends NativeType ? T : DefaultFactory<T>

interface VueTypeBaseDef<
  T = unknown,
  D = DefaultType<T>,
  U = T extends NativeType ? T : () => T,
> extends PropOptions<T> {
  _vueTypes_name: string
  type?: PropType<T>
  readonly def: (def?: D) => this & {
    default: U
  }
  readonly isRequired: this & { required: true }
}

type VueTypeDef<T = unknown> = VueTypeBaseDef<T>

interface VueTypeValidableDef<
  T = unknown,
  V = ValidatorFunction<T>,
> extends VueTypeBaseDef<T> {
  readonly validate: (fn: V) => this & { validator: V }
}

type VueProp<T> = VueTypeBaseDef<T> | PropOptions<T>

interface VueTypeShape<T> extends VueTypeBaseDef<
  T,
  DefaultType<Partial<T>>,
  () => Partial<T>
> {
  readonly loose: VueTypeLooseShape<T>
}

interface VueTypeLooseShape<T> extends VueTypeBaseDef<
  T,
  DefaultFactory<Partial<T & Record<string, any>>>,
  () => Partial<T> & Record<string, any>
> {
  readonly loose: VueTypeLooseShape<T>
  readonly _vueTypes_isLoose: true
}

interface VueTypesDefaults {
  func: (...args: any[]) => any
  bool: boolean
  string: string
  number: number
  array: () => any[]
  object: () => Record<string, any>
  integer: number
}

interface VueTypesConfig {
  silent: boolean
  logLevel: 'log' | 'warn' | 'error' | 'debug' | 'info'
}

declare const config: VueTypesConfig;

export { config as i };
export type { Constructor as C, InferType as I, Prop as P, ValidatorFunction as V, PropOptions as a, VueProp as b, VueTypeDef as c, VueTypeLooseShape as d, VueTypeShape as e, VueTypeValidableDef as f, VueTypesConfig as g, VueTypesDefaults as h };
