/// <reference types="react" />

import * as React from 'react';

type FieldOption<T> = {
    /**
     * 所有组件的change都会到达这里[setValue不会触发该函数]
     */
    onChange?: (name: keyof T, value: T[typeof name]) => void;

    /**
     * 是否翻译init(name)中的name(getValues会把带.的字符串转换成对象)
     * @default false
     */
    parseName?: boolean;

    /**
     * 仅建议PureComponent的组件打开此强制刷新功能，会带来性能问题(500个组件为例：打开的时候render花费700ms, 关闭时候render花费400ms)
     * @default false
     */
    forceUpdate?: boolean;

    /**
     * field.validate的时候滚动到第一个出错的组件 (默认false, 1.0版本会改成true)
     * @default false
     */
    scrollToFirstError?: boolean;

    /**
     * 自动删除(remove) Unmout 元素, getValues不会出现冗余数据
     * @default false
     */
    autoUnmount?: boolean;

    /**
     * 强制重置数据(设置所有数据为undefined，业务组件需要自己支持value=undefined情况清空数据)(版本要求>next@0.15)  (1.0版本会改成true)
     * @default false
     */
    deepReset?: boolean;

    /**
     * 默认值
     */
    values?: T | Readonly<T>;
};

type InitResult<T> = {
    id: string;
    value: T;
    onChange(value: T): void;
};

type InitResult2<T, T2> = {
    id: string;
    value: T;
    onChange(value: T, extra: T2): void;
};

type Rule<T> = {
    /**
     * 不能为空 (不能和pattern同时使用)
     * @default true
     */
    required?: boolean;

    /**
     * 出错时候信息
     */
    message?: string;

    /**
     * 被校验数据类型(注意 type:'number' 表示数据类型为Number,而不是字符串形式的数字,字符串形式的数字请用pattern:/^[0-9]*$/) String/Array/url/email/...
     */
    type?:
        | 'string'
        | 'number'
        | 'boolean'
        | 'method'
        | 'regexp'
        | 'integer'
        | 'float'
        | 'array'
        | 'object'
        | 'enum'
        | 'date'
        | 'url'
        | 'hex'
        | 'email'
        | string;

    /**
     * 校验正则表达式
     */
    pattern?: RegExp;

    /**
     * 长度校验，如果max、mix混合配置，len的优先级最高
     */
    len?: number;

    /**
     * 字符最小长度
     */
    min?: number;

    /**
     * 字符最大长度
     */
    max?: number;

    /**
     * 是否进行空白字符校验（true进行校验)
     */
    whitespace: boolean;

    /**
     * 自定义校验,(校验成功的时候不要忘记执行 callback(),否则会校验不返回)
     */
    validator: (
        rule: Rule<T>,
        value: T,
        callback: (error?: null | string | Error) => void
    ) => void;

    /**
     * 触发校验的事件名称
     */
    trigger: 'onChange' | 'onBlur' | string;
};

type InitOption<T> = {
    /**
     * 组件值的属性名称，如 Checkbox 的是 checked，Input是 value
     */
    valueName?: string;

    /**
     * 组件初始值(组件第一次render的时候才会读取，后面再修改此值无效),类似defaultValue
     */
    initValue?: T;

    /**
     * 触发数据变化的事件名称
     */
    trigger?: string | 'onChange' | 'onBlur';

    /**
     * 校验规则
     */
    rules?: Rule<T>[] | Rule<T>;

    /**
     * 组件自定义的事件可以写在这里，其他会透传(小包版本^0.3.0支持，大包^0.7.0支持)
     */
    props?: object;

    /**
     * 自定义从onChange事件中获取value的方式，一般不需要设置. 详细用法查看demo 自定义数据获取  参数顺序和组件是完全一致的
     */
    getValueFromEvent?: (eventArgs: object) => T;
};

export default class Field<T> {
    /**
     *
     * @param contextComp 传入调用class的this
     * @param options 一些事件配置
     */
    constructor(contextComp: React.Component, options?: FieldOption<T>);

    /**
     * 初始化每个组件
     */
    init<K extends keyof T>(name: K, options: InitOption<T[K]>): InitResult<T[K]>;

    /**
     * 初始化每个组件
     */
    init<K extends keyof T, T2>(name: K, option?: InitOption<T[K]>): InitResult2<T[K], T2>;

    /**
     *
     * 重置一组输入控件的值、清空校验, 第二个参数控制是否回到defaultValue
     * @param names 重置的字段名
     * @param backToDefault
     */
    reset<K extends keyof T>(names?: K[], backToDefault?: InitOption<T[K]>): void;

    /**
     * 删除某一个或者一组控件的数据，删除后与之相关的validate/value都会被清空
     * @param name 字段名称
     */
    remove<K extends keyof T>(name: K | K[]): void;

    /**
     * 校验
     * @param names
     * @param options
     * @param callback
     */
    validate(callback?: (errorData: Partial<Record<keyof T, { errors: string[] }>> | null, data: T) => void): void;

    /**
     * 校验
     * @param names
     * @param callback
     */
    validate<K extends keyof T>(
        names: K[],
        callback?: (errorData: Partial<Record<keyof T, { errors: string[] }>> | null, data: T) => void
    ): void;

    /**
     * 校验并获取一组输入域的值与Error对象
     */
    /**
     * 	获取所有组件的key
     */
    getNames(): Array<keyof T>;

    /**
     * 	获取单个输入控件的值
     * @param 字段名
     */
    getValue<K extends keyof T>(name: K): T[K];

    /**
     * 获取一组输入控件的值，如不传入参数，则获取全部组件的值
     * @param names
     */
    getValues<K extends keyof T>(names: K[]): Pick<T, K>;
    getValues(): T;

    /**
     * 设置单个输入控件的值 （会触发render，请遵循react时机使用)
     */
    setValue<K extends keyof T>(name: K, value: T[K]): void;

    /**
     * 设置一组输入控件的值（会触发render，请遵循react时机使用)
     */
    setValues(obj: Partial<T>): void;

    /**
     * 判断校验状态
     */
    getState<K extends keyof T>(name: K): 'error' | 'success' | 'validating';

    /**
     * 获取单个输入控件的 Error
     */
    getError<K extends keyof T>(name: K): null | string[];

    /**
     * 获取一组输入控件的 Error
     * @param names 字段名
     */
    getErrors<K extends keyof T>(names: K[]): Record<K, null | string[]>;

    /**
     * 设置单个输入控件的 Error
     * @param name
     * @param errors
     */
    setError<K extends keyof T>(key: K, err: null | string | string[]): void;

    /**
     * 设置一组输入控件的 Error
     */
    setErrors<K extends keyof T>(obj: Partial<Record<K, null | string | string[]>>): void;

    /**
     * react hooks 风格使用 Field
     * @param options
     */
    static useField<T>(options?: FieldOption<T>): Field<T>;
}
