/**
 * FormValidation (https://formvalidation.io)
 * The best validation library for JavaScript
 * (c) 2013 - 2023 Nguyen Huu Phuoc <me@phuoc.ng>
 */

export interface FieldsOptions {
    [field: string]: FieldOptions;
}

export interface FieldOptions {
    // Field can be defined by given CSS selector
    // By default, we use `name` attribute to indicate the field element
    selector?: string;
    validators: {
        [validatorName: string]: ValidatorOptions;
    };
}

export interface Localization {
    [validator: string]: string | Localization;
}

export interface LocalizationMessage {
    [locale: string]: string;
}
export type LocalizationMessageType = LocalizationMessage | string;

export type ValidateFunction<K extends ValidateOptions> = ValidateFunctionInterface<
    K,
    ValidateResult | Promise<ValidateResult>
>;

export interface ValidateFunctionInterface<K extends ValidateOptions, T> {
    validate(input: ValidateInput<K, Localization>): T;
}

export interface ValidateInput<T extends ValidateOptions, L extends Localization> {
    element?: HTMLElement;
    elements?: HTMLElement[];
    field?: string;
    options?: T;
    l10n?: L;
    value: string;
}

export interface ValidateOptions {
    message?: string;
}

export interface ValidateResult {
    message?: LocalizationMessageType;
    meta?: unknown;
    valid: boolean;
}

export interface ValidatorOptions {
    enabled?: boolean;
    message?: string;
    [option: string]: unknown;
}

// Events

export interface DynamicFieldEvent {
    elements: HTMLElement[];
    field: string;
    options: FieldOptions;
}
export interface ValidatorNotValidatedEvent {
    element: HTMLElement;
    elements: HTMLElement[];
    field: string;
    validator: string;
}
export interface ValidatorValidatedEvent {
    element: HTMLElement;
    elements: HTMLElement[];
    field: string;
    result: ValidateResult;
    validator: string;
}
export interface ElementIgnoredEvent {
    element: HTMLElement;
    elements: HTMLElement[];
    field: string;
}
export interface ElementNotValidatedEvent {
    element: HTMLElement;
    elements: HTMLElement[];
    field: string;
}
export interface ElementValidatingEvent {
    element: HTMLElement;
    elements: HTMLElement[];
    field: string;
}
export interface ElementValidatedEvent {
    element: HTMLElement;
    elements: HTMLElement[];
    field: string;
    valid: boolean;
}
export interface FormResetEvent {
    reset: boolean;
}
export interface FieldResetEvent {
    field: string;
    reset: boolean;
}

export declare class Core {
    constructor(form: HTMLElement, fields?: FieldsOptions);

    on(event: string, func: (...arg: unknown[]) => unknown): Core;
    off(event: string, func: (...arg: unknown[]) => unknown): Core;
    emit(event: string, ...args: unknown[]): Core;

    registerPlugin(name: string, plugin: Plugin<unknown>): Core;
    deregisterPlugin(name: string): Core;
    enablePlugin(name: string): Core;
    disablePlugin(name: string): Core;
    isPluginEnabled(name: string): boolean;

    registerValidator(name: string, func: () => ValidateFunction<ValidateOptions>): Core;
    registerValidator<K extends ValidateOptions>(name: string, func: () => ValidateFunction<K>): Core;
    registerFilter(name: string, func: (...arg: unknown[]) => unknown): Core;
    deregisterFilter(name: string, func: (...arg: unknown[]) => unknown): Core;
    executeFilter<T>(name: string, defaultValue: T, args: unknown[]): T;

    addField(field: string, options?: FieldOptions): Core;
    removeField(field: string): Core;

    revalidateField(field: string): Promise<string>;
    validate(): Promise<string>;
    validateField(field: string): Promise<string>;
    validateElement(field: string, ele: HTMLElement): Promise<string>;
    executeValidator(field: string, ele: HTMLElement, v: string, opts: ValidatorOptions): Promise<string>;

    getElementValue(field: string, ele: HTMLElement, validator?: string): string;
    getElements(field: string): HTMLElement[];
    getFields(): FieldsOptions;
    getFormElement(): HTMLElement;
    getLocale(): string;
    getPlugin(name: string): Plugin<any>;

    updateFieldStatus(field: string, status: string, validator?: string): Core;
    updateElementStatus(field: string, ele: HTMLElement, status: string, validator?: string): Core;

    resetForm(reset?: boolean): Core;
    resetField(field: string, reset?: boolean): Core;

    disableValidator(field: string, validator?: string): Core;
    enableValidator(field: string, validator?: string): Core;
    updateValidatorOption(field: string, validator: string, name: string, value: unknown): Core;
    setFieldOptions(field: string, options: FieldOptions): Core;

    destroy(): Core;

    setLocale(locale: string, localization: Localization): Core;
}

export declare class Plugin<T> {
    protected core: Core;
    protected opts?: T;
    protected isEnabled: boolean;
    constructor(opts?: T);
    install(): void;
    uninstall(): void;
    enable(): void;
    disable(): void;
    isPluginEnabled(): boolean;
    protected onEnabled(): void;
    protected onDisabled(): void;
}

export interface Options {
    fields?: FieldsOptions;
    locale?: string;
    localization?: Localization;
    plugins?: {
        [name: string]: Plugin<unknown>;
    };
    init?(core: Core): void;
}

export function formValidation(form: HTMLElement, options?: Options): Core;

// Algorithms

export declare namespace algorithms {
    function luhn(value: string): boolean;
    function mod11And10(value: string): boolean;
    function mod37And36(value: string, alphabet?: string): boolean;
    function mod97And10(value: string): boolean;
    function verhoeff(value: number[]): boolean;
}

// Utils

export declare namespace utils {
    function call(functionName: ((...arg: unknown[]) => unknown) | string, args: unknown[]): unknown;
    function classSet(element: HTMLElement, classes: { [clazz: string]: boolean }): void;
    function closest(element: HTMLElement, selector: string): HTMLElement;

    interface FetchOptions {
        // Does it request to other domain? Default value is `false`
        crossDomain?: boolean;
        // Additional headers
        headers?: {
            [name: string]: string;
        };
        // The request method. For example, `GET` (default), `POST`
        method?: string;
        params: {
            [name: string]: unknown;
        };
    }
    function fetch(url: string, options: FetchOptions): Promise<unknown>;

    function format(message: string, parameters: string | string[]): string;
    function hasClass(element: Element, clazz: string): boolean;
    function isValidDate(year: number, month: number, day: number, notInFuture?: boolean): boolean;
    function removeUndefined<T extends object>(obj?: T): T;
}
