import { Ref, UnwrapRef, ComputedRef } from 'vue-demi'

declare type AnyFunction = (...args: any[]) => any
declare type DeepIndex<
  T,
  Keys extends readonly PropertyKey[],
  R = unknown
> = Keys extends [infer First, ...infer Rest]
  ? First extends keyof T
    ? Rest extends readonly PropertyKey[]
      ? DeepIndex<T[First], Rest>
      : R
    : R
  : T
declare type MaybeRef<T> = T extends Ref<infer V> ? T | V : Ref<T> | T
declare type ExcludePrimitives<T> = T extends AnyFunction
  ? never
  : T extends object
  ? T
  : never
declare type DeepPartial<T extends object> = T extends readonly any[]
  ? {
      [K in keyof T]: DeepPartialImpl<T[K]>
    }
  : {
      [K in keyof T]?: DeepPartialImpl<T[K]> | undefined
    }
declare type DeepPartialImpl<T> = T extends AnyFunction
  ? T
  : T extends object
  ? DeepPartial<T>
  : T

declare type ValidationBehaviorString = keyof ValidationBehaviorFunctions
interface ValidationBehaviorFunctions {}
declare type ValidationBehaviorInfo<T = any> = {
  /**
   * `True` if the paired rule of this behavior has an error.
   */
  hasError: boolean
  /**
   * The touched state of the field.
   */
  touched: boolean
  /**
   * The dirty state of the field.
   */
  dirty: boolean
  /**
   * `True` if the validation was triggered with the `force` flag.
   */
  force: boolean
  /**
   * `True` if the validation was triggered with the `submit` flag.
   */
  submit: boolean
  /**
   * The `$value` property of the field.
   */
  value: T
}
declare type ValidationBehaviorFunction = (
  info: ValidationBehaviorInfo
) => boolean
declare type ValidationBehavior =
  | ValidationBehaviorString
  | ValidationBehaviorFunction

declare type SimpleRule<Param = any> = (...value: Param[]) => any
declare type KeyedRule<Params extends readonly any[] = any[]> = (
  ...values: [...Params]
) => any
declare type RuleWithKey<T extends readonly any[] = any[]> = {
  key: string
  rule?: KeyedRule<T>
}
declare type FieldSimpleRule<Param = any> =
  | SimpleRule<Param>
  | [
      validationBehavior: ValidationBehavior,
      rule: SimpleRule<Param>,
      debounce?: number
    ]
  | [rule: SimpleRule<Param>, debounce: number]
declare type FieldRuleWithKey<Params extends readonly any[]> =
  | RuleWithKey<Params>
  | [
      validationBehavior: ValidationBehavior,
      rule: RuleWithKey<Params>,
      debounce?: number
    ]
  | [rule: RuleWithKey<Params>, debounce: number]
declare type FieldRule<
  SimpleParam,
  KeyedParams extends readonly any[] = any[]
> = FieldSimpleRule<UnwrapRef<SimpleParam>> | FieldRuleWithKey<KeyedParams>

declare type ValidateFieldsPredicateParameter = {
  key: string
  value: any
  path: string[]
}
declare type ValidateFieldsPredicate = (
  value: ValidateFieldsPredicateParameter
) => boolean

declare type Field<T, E extends object = Record<string, never>> = {
  /**
   * The field's default value.
   */
  $value: MaybeRef<T>
  /**
   * Rules to use for validation.
   */
  $rules?: FieldRule<T>[]
} & (E extends Record<string, never> ? unknown : E)
declare type ValidateOptions = {
  /**
   * Set the field touched when called.
   *
   * @default true
   */
  setTouched?: boolean
  /**
   * Validate with the `force` flag set.
   *
   * @default true
   */
  force?: boolean
}
declare type TransformedField<T, E extends object = Record<string, never>> = {
  /**
   * The unique id of this field.
   */
  $uid: number
  /**
   * The current field's value.
   */
  $value: T
  /**
   * A list of validation error messages.
   */
  $errors: string[]
  /**
   * The error status of this field.
   */
  $hasError: boolean
  /**
   * `True` while this field has any pending rules.
   */
  $validating: boolean
  /**
   * `True` if the field is touched. In most cases,
   * this value should be set together with the `blur` event.
   * Either through `$validate` or manually.
   */
  $touched: boolean
  /**
   * `True` if the `$value` of this field has changed at least once.
   */
  $dirty: boolean
  /**
   * Validate this field.
   *
   * @param options - Validate options to use
   * @default
   * ```
   * { setTouched: true, force: true }
   * ```
   */
  $validate(options?: ValidateOptions): Promise<void>
} & (E extends Record<string, never> ? unknown : UnwrapRef<E>)
/**
 * Unwrap the `$value` property of all fields in `FormData`.
 */
declare type ResultFormData<FormData> = FormData extends any
  ? {
      [K in keyof FormData]: ResultFormDataImpl<FormData[K]>
    }
  : never
declare type ResultFormDataImpl<T> = T extends {
  $value: infer V
}
  ? UnwrapRef<Exclude<MaybeRef<V>, Ref>>
  : T extends object
  ? ResultFormData<T>
  : T
/**
 * Receive the name of every field in `FormData` as a union of strings.
 */
declare type FieldNames<FormData extends object> = FieldNamesImpl<
  FormData,
  never
>
declare type FieldNamesImpl<FormData, K> =
  ExcludePrimitives<FormData> extends never
    ? never
    : FormData extends {
        $value: any
      }
    ? K
    : FormData extends readonly (infer A)[]
    ? FieldNamesImpl<A, K>
    : {
        [K in keyof FormData]-?: ExcludePrimitives<FormData[K]> extends {
          $value: any
        }
          ? K
          : FieldNamesImpl<ExcludePrimitives<FormData[K]>, K>
      }[keyof FormData]
/**
 * Transforms every field in `FormData` into transformed fields.
 */
declare type TransformFormData<FormData> = FormData extends any
  ? {
      [K in keyof FormData]: TransformFormDataImpl<FormData[K]>
    }
  : never
declare type TransformFormDataImpl<T> = T extends {
  $value: infer V
}
  ? TransformedField<
      UnwrapRef<Exclude<MaybeRef<V>, Ref>>,
      Omit<T, '$value' | '$rules'>
    >
  : T extends object
  ? TransformFormData<T>
  : T

/**
 * Vue composition function for form validation.
 * For type inference in `useValidation` make sure to define the structure of your
 * form data upfront and pass it as the generic parameter `FormData`.
 *
 * @param formData - The structure of your form data
 *
 * @example
 * ```
 * type FormData = {
 *   name: Field<string>,
 *   password: Field<string>
 * }
 *
 * const { form } = useValidation<FormData>({
 *   name: {
 *     $value: '',
 *     $rules: []
 *   },
 *   password: {
 *     $value: '',
 *     $rules: []
 *   }
 * })
 * ```
 */
declare function useValidation<FormData extends object>(
  formData: FormData
): UseValidation<FormData>
declare type UseValidation<FormData extends object> = {
  /**
   * A transformed reactive form data object.
   */
  form: TransformFormData<FormData>
  /**
   * `True` during validation after calling `validateFields` when there were rules returning a `Promise`.
   */
  submitting: Ref<boolean>
  /**
   * `True` while the form has any pending rules.
   */
  validating: ComputedRef<boolean>
  /**
   * `True` if the form has any error.
   */
  hasError: ComputedRef<boolean>
  /**
   * All current validation error messages.
   */
  errors: ComputedRef<string[]>
  /**
   * Validate all fields and return a `Promise` containing the resulting form data.
   *
   * @param options - Options to use for validation
   * @throws `ValidationError`
   */
  validateFields(options?: {
    /**
     * A list of field names to validate.
     *
     * @default
     * ```
     * undefined // meaning validate all
     * ```
     */
    names?: FieldNames<FormData>[]
    /**
     * Filter which values to keep in the resulting form data.
     * Used like `Array.prototype.filter` but for objects.
     *
     * @default
     * ```
     * () => true // meaning keep all
     * ```
     */
    predicate?: ValidateFieldsPredicate
  }): Promise<ResultFormData<FormData>>
  /**
   * Reset all fields to their default value or pass an object to set specific values.
   * It will not create any new fields not present in the form data initially.
   *
   * @param formData - Form data to set specific values. It has the same structure as the object passed to `useValidation`
   */
  resetFields(formData?: DeepPartial<ResultFormData<FormData>>): void
  /**
   * Adds a new property to the form data.
   * Fields with a `$value` are transformed.
   *
   * @param path - A path of `string` and `numbers`
   * @param value - The value to add at the specified path
   */
  add<Keys extends readonly (string | number)[]>(
    path: readonly [...Keys],
    value: DeepIndex<FormData, Keys> extends (infer TArray)[]
      ? TArray
      : DeepIndex<FormData, Keys>
  ): void
  /**
   * Removes a property from the form data.
   *
   * @param path - A path of `string` and `numbers` to the property to remove
   */
  remove(path: (string | number)[]): void
}

declare type Configuration = {
  defaultValidationBehavior: ValidationBehaviorString
  validationBehavior: {
    [K in ValidationBehaviorString]: ValidationBehaviorFunction
  }
}
declare type Validation = {
  install(): void
}
/**
 * Configure the validation behavior of `useValidation`.
 *
 * @param configuration - The form validation configuration
 */
declare function createValidation(configuration: Configuration): Validation

declare class ValidationError extends Error {
  constructor()
}

export {
  DeepPartial,
  Field,
  FieldNames,
  KeyedRule,
  ResultFormData,
  SimpleRule,
  TransformFormData,
  TransformedField,
  UseValidation,
  ValidateFieldsPredicate,
  ValidateFieldsPredicateParameter,
  ValidateOptions,
  Validation,
  ValidationBehavior,
  ValidationBehaviorFunction,
  ValidationBehaviorFunctions,
  ValidationBehaviorInfo,
  ValidationBehaviorString,
  ValidationError,
  createValidation,
  useValidation
}
