import { Validator, Watcher, KeyPath } from '@mobx-sentinel/core';
import * as mobx from 'mobx';

declare const internalToken$1: unique symbol;
declare class FormField {
    #private;
    readonly id: string;
    readonly fieldName: string;
    readonly validator: Validator<any>;
    /** @ignore */
    constructor(args: {
        fieldName: string;
        validator: Validator<any>;
        getFinalizationDelayMs: () => number;
    });
    /** Whether the field is touched */
    get isTouched(): boolean;
    /**
     * Whether the field value is intermediate (partial input).
     *
     * The input is incomplete and does not yet conform to the expected format.
     * Example: Typing "user@" in an email field.
     */
    get isIntermediate(): boolean;
    /** Whether the field value is changed */
    get isChanged(): boolean;
    /**
     * Whether the error states has been reported
     *
     * Check this value to determine if errors should be displayed.
     *
     * @returns
     * - 'undefined': Validity of the field is undetermined.
     * - 'false': The field is valid.
     * - 'true': The field is invalid.
     *
     * This distinction is essential for `aria-invalid` attribute.
     */
    get isErrorReported(): boolean | undefined;
    /**
     * Error messages for the field
     *
     * Regardless of {@link isErrorReported}, this value is always up-to-date.
     */
    get errors(): ReadonlySet<string>;
    /**
     * Whether the field has errors
     *
     * Regardless of {@link isErrorReported}, this value is always up-to-date.
     */
    get hasErrors(): boolean;
    /** Reset the field to its initial state */
    reset(): void;
    /** Mark the field as touched (usually triggered by onFocus) */
    markAsTouched(): void;
    /** Mark the field as changed (usually triggered by onChange) */
    markAsChanged(type?: FormField.ChangeType): void;
    /**
     * Report the errors of the field.
     *
     * It will wait until the validation is up-to-date before reporting the errors.
     */
    reportError(): void;
    /** Finalize the intermediate change if needed (usually triggered by onBlur) */
    finalizeChangeIfNeeded(): void;
    /** @internal @ignore */
    [internalToken$1](): {
        isReported: mobx.IObservableValue<boolean>;
    };
}
declare namespace FormField {
    /** Strict field name */
    type NameStrict<T> = keyof T & string;
    /** Augmented field name with an arbitrary suffix followed by a colon */
    type NameAugmented<T> = `${NameStrict<T>}:${string}`;
    /** Field name */
    type Name<T> = NameStrict<T> | NameAugmented<T>;
    /**
     * The type of change that has occurred in the field.
     *
     * - "final" - The input is complete and no further update is necessary to make it valid.
     * - "intermediate" - The input is incomplete and does not yet conform to the expected format.
     */
    type ChangeType = "final" | "intermediate";
}

type ConfigOf<T> = T extends new (form: Form<any>, config: infer Config) => FormBinding ? Config : T extends new (field: FormField, config: infer Config) => FormBinding ? Config : T extends new (fields: FormField[], config: infer Config) => FormBinding ? Config : never;
/** Interface for form binding classes */
interface FormBinding {
    /** Configuration of the binding */
    config?: object;
    /** Binding properties which passed to the view component */
    readonly props: object;
}
/** Polymorphic function of Form#bind */
interface FormBindingFunc<T> extends FormBindingFunc.ForField<T>, FormBindingFunc.ForMultiField<T>, FormBindingFunc.ForForm<T> {
}
declare namespace FormBindingFunc {
    /** Bind configuration */
    type Config = {
        /** Cache key for the binding */
        cacheKey?: string;
    };
    /** Bind to a field */
    interface ForField<T> {
        /** Create a binding for the field */
        <Binding extends new (field: FormField) => FormBinding>(fieldName: FormField.Name<T>, binding: Binding, config?: Config): InstanceType<Binding>["props"];
        /** Create a binding for the field with the config */
        <Binding extends new (field: FormField, config: any) => FormBinding>(fieldName: FormField.Name<T>, binding: Binding, config: NoInfer<ConfigOf<Binding>> & Config): InstanceType<Binding>["props"];
    }
    /** Bind to multiple fields */
    interface ForMultiField<T> {
        /** Create a binding for the multiple fields */
        <Binding extends new (fields: FormField[]) => FormBinding>(fieldNames: FormField.Name<T>[], binding: Binding, config?: Config): InstanceType<Binding>["props"];
        /** Create a binding for the multiple fields with the config */
        <Binding extends new (fields: FormField[], config: any) => FormBinding>(fieldNames: FormField.Name<T>[], binding: Binding, config: NoInfer<ConfigOf<Binding>> & Config): InstanceType<Binding>["props"];
    }
    /** Bind to the form */
    interface ForForm<T> {
        /** Create a binding for the form */
        <Binding extends new (form: Form<T>) => FormBinding>(binding: Binding, config?: Config): InstanceType<Binding>["props"];
        /** Create a binding for the form with the config */
        <Binding extends new (form: Form<T>, config: any) => FormBinding>(binding: Binding, config: NoInfer<ConfigOf<Binding>> & Config): InstanceType<Binding>["props"];
    }
}
declare namespace FormBindingFuncExtension {
    /** Bind configuration */
    type Config = FormBindingFunc.Config;
    /** Bind to a field */
    namespace ForField {
        /** Create a binding for the field with an optional config */
        type OptionalConfig<T, Binding extends new (field: FormField, config?: any) => FormBinding> = (fieldName: FormField.Name<T>, config?: NoInfer<ConfigOf<Binding>> & Config) => InstanceType<Binding>["props"];
        /** Create a binding for the field with a required config */
        type RequiredConfig<T, Binding extends new (field: FormField, config: any) => FormBinding> = (fieldName: FormField.Name<T>, config: NoInfer<ConfigOf<Binding>> & Config) => InstanceType<Binding>["props"];
    }
    /** Bind to multiple fields */
    namespace ForMultiField {
        /** Create a binding for the multiple fields */
        type OptionalConfig<T, Binding extends new (fields: FormField[], config?: any) => FormBinding> = (fieldNames: FormField.Name<T>[], config?: NoInfer<ConfigOf<Binding>> & Config) => InstanceType<Binding>["props"];
        /** Create a binding for the multiple fields with the config */
        type RequiredConfig<T, Binding extends new (fields: FormField[], config: any) => FormBinding> = (fieldNames: FormField.Name<T>[], config: NoInfer<ConfigOf<Binding>> & Config) => InstanceType<Binding>["props"];
    }
    /** Bind to the form */
    namespace ForForm {
        /** Create a binding for the form */
        type OptionalConfig<T, Binding extends new (form: Form<T>, config: any) => FormBinding> = (config?: NoInfer<ConfigOf<Binding>> & Config) => InstanceType<Binding>["props"];
        /** Create a binding for the form with the config */
        type RequiredConfig<T, Binding extends new (form: Form<T>, config: any) => FormBinding> = (config: NoInfer<ConfigOf<Binding>> & Config) => InstanceType<Binding>["props"];
    }
}

/** Form configuration */
type FormConfig = {
    /**
     * Automatically finalize the form when the input is intermediate (partial input). [in milliseconds]
     *
     * @default 3000
     */
    autoFinalizationDelayMs: number;
    /**
     * Allow submission even if the form is not dirty.
     *
     * @default false
     */
    allowSubmitNonDirty: boolean;
    /**
     * Allow submission even if the form is invalid.
     *
     * @default false
     */
    allowSubmitInvalid: boolean;
};
/** Update the global configuration */
declare function configureForm(config: Partial<Readonly<FormConfig>>): Readonly<FormConfig>;
/** Reset the global configuration to the default */
declare function configureForm(reset: true): Readonly<FormConfig>;

declare class Submission {
    #private;
    /** Whether the submission is running */
    get isRunning(): boolean;
    /** Add a handler for the specific event */
    addHandler<K extends keyof Submission.Handlers>(event: K, handler: Submission.Handlers[K]): () => void;
    /** Execute the submission */
    exec(): Promise<boolean>;
}
declare namespace Submission {
    type Handlers = {
        willSubmit: () => void;
        submit: (abortSignal: AbortSignal) => Promise<boolean>;
        didSubmit: (succeed: boolean) => void;
    };
}

declare const internalToken: unique symbol;
declare class Form<T> {
    #private;
    readonly id: string;
    readonly watcher: Watcher;
    readonly validator: Validator<T>;
    /** Extension fields for bindings */
    [k: `bind${Capitalize<string>}`]: unknown;
    /**
     * Get the form instance for a subject.
     *
     * - Returns the existing form instance if the subject is already associated with one.\
     *   Otherwise, creates a new form instance and associates it with the subject.
     * - The form instance is cached in the internal registry,
     *   and it will be garbage collected when the subject is no longer in use.\
     *   In rare cases, you may need to manually dispose the form instance using {@link Form.dispose}.
     *
     * @param subject The subject to associate with the form
     * @param formKey The key to associate with the form.
     *   If you need to associate multiple forms with the same subject, use different keys.
     *
     * @throws TypeError when the subject is not an object.
     */
    static get<T extends object>(subject: T, formKey?: symbol): Form<T>;
    /**
     * Get the form instance for a subject.
     *
     * Same as {@link Form.get} but returns null instead of throwing an error.
     */
    static getSafe<T extends object>(subject: T, formKey?: symbol): Form<T> | null;
    /**
     * Manually dispose the form instance for a subject.
     *
     * Use with caution.\
     * You don't usually need to use this method at all.\
     * It's only for advanced use cases, such as testing.
     *
     * @see {@link Form.get}
     */
    static dispose(subject: object, formKey?: symbol): void;
    private constructor();
    /**
     * The configuration of the form
     *
     * This is a computed value that combines the global configuration and the local configuration.
     */
    get config(): Readonly<FormConfig>;
    /** Configure the form locally */
    configure: {
        /** Override the global configuration locally */
        (config: Partial<Readonly<FormConfig>>): void;
        /** Reset to the global configuration */
        (reset: true): void;
    };
    /** Whether the form is dirty (including sub-forms) */
    get isDirty(): boolean;
    /** Whether the form is valid (including sub-forms) */
    get isValid(): boolean;
    /** The number of invalid fields */
    get invalidFieldCount(): number;
    /** The number of total invalid field paths (counts invalid fields in sub-forms) */
    get invalidFieldPathCount(): number;
    /** Whether the form is in validator state */
    get isValidating(): boolean;
    /** Whether the form is in submitting state */
    get isSubmitting(): boolean;
    /** Whether the form is busy (submitting or validating) */
    get isBusy(): boolean;
    /** Whether the form can be submitted */
    get canSubmit(): boolean;
    /**
     * Sub-forms within the form.
     *
     * Forms are collected via `@nested` annotation.
     */
    get subForms(): ReadonlyMap<KeyPath, Form<any>>;
    /** Report error states on all fields and sub-forms */
    reportError(): void;
    /**
     * Reset the form's state
     *
     * It also resets the watcher but not the validator.
     */
    reset(): void;
    /** Mark the form as dirty */
    markAsDirty(): void;
    /**
     * Submit the form.
     *
     * @returns true when the submission succeeded.
     */
    submit(args?: {
        force?: boolean;
    }): Promise<boolean>;
    /**
     * Add a handler to the form
     *
     * @returns A function to remove the handler.
     */
    addHandler: Submission["addHandler"];
    /** Get a field by name */
    getField(fieldName: FormField.Name<T>): FormField;
    /** Bind to a field or the form */
    bind: FormBindingFunc<T>;
    /**
     * Get the error messages for a field
     *
     * @param fieldName - The field name to get errors for.
     * @param includePreReported - Whether to include errors that are yet to be reported.
     */
    getErrors(fieldName: FormField.Name<T>, includePreReported?: boolean): ReadonlySet<string>;
    /**
     * Get all error messages for the form
     *
     * @param fieldName - The field name to get errors for. If omitted, all errors are returned.
     */
    getAllErrors(fieldName?: FormField.Name<T>): Set<string>;
    /** The first error message (including nested objects) */
    get firstErrorMessage(): string | null;
    /** @internal @ignore */
    [internalToken](): {
        fields: Map<string, FormField>;
        bindings: Map<string, FormBinding>;
        submission: Submission;
    };
}
declare namespace Form {
    type Handlers = Submission.Handlers;
}

export { Form, type FormBinding, FormBindingFunc, FormBindingFuncExtension, type FormConfig, FormField, configureForm };
