import { InferSourceForDictionary, InferSourceFromSchema, InferTargetForDictionary, InferTargetFromSchema, SchemaSource, SchemaTarget } from "./type-conversions";
export type DefaultBehaviour = {
    allowNull: boolean;
    optional: boolean;
};
interface NotNull extends DefaultBehaviour {
    allowNull: false;
}
interface Optional extends DefaultBehaviour {
    optional: true;
    allowNull: true;
}
type AllowNull<T, B extends DefaultBehaviour> = B extends NotNull ? T : B extends Optional ? T | null | undefined : T | null;
/**
 * Metadata for the schema type
 */
export interface TypedMetadata {
    /**
     * Data type
     */
    dataType: string;
    /**
     * True if null is accepted by the object
     */
    notNull: boolean;
    /**
     * True if the object is optional, i.e. the field can be absent or the object undefined
     */
    optional: boolean;
    /**
     * True if the object has a default/validation rule defined
     */
    hasDefaultRule?: boolean;
}
/**
 * Facade exposing a map child schemas of the schema object
 */
export type FieldsMap = {
    /**
     * Get the field's schema by field name
     * @param fieldName Field's name
     * @returns Schema object for the named field or undefined if the field does not exist
     */
    get: (fieldName: string) => Schema | undefined;
    /**
     * Executes for each key of the underlying object
     * @param func Function to execute for each field of the object
     */
    forEach: (func: (fieldName: string, schema: Schema) => void) => void;
    /**
     * Executes for each key of the underlying object and returns the value for each iteration
     * @param func Function to execute for each field of the object
     * @returns Array of objects returned from each consecutive field
     */
    map: <T>(func: (fieldName: string, schema: Schema) => T) => T[];
    /**
     * Returns only the fields matching the condition implemented by the function
     * Function returning true
     * @param func Function checking each field in the object. If true, the schema is added to the resulting array
     */
    filter: (func: (fieldName: string, schema: Schema) => boolean) => {
        key: string;
        schema: Schema;
    }[];
    /**
     * Returns the number of fields (and thus field schemas) returned by the object
     */
    get size(): number;
};
/**
 * Metadata for the object schema
 */
export interface ObjectMetadata extends TypedMetadata {
    dataType: "object";
    /**
     * Map of schemas for every field of the object
     */
    fields: FieldsMap;
}
/**
 * Metadata for the array schema
 */
export interface ArrayMetadata extends TypedMetadata {
    dataType: "array";
    /**
     * Schema for every element of the array. Only one schema because all the array elements are of the same type
     */
    elements: Schema;
}
/**
 * Metadata for the dictionary schema
 */
export interface DictionaryMetadata extends TypedMetadata {
    dataType: "dictionary";
    /**
     * Schema for every value of the dictionary. Only one schema because all the dictionary elements are of the same type
     */
    values: Schema;
}
/**
 * Setting that define the way the object described by the schema is unboxed
 */
export type UnboxingProperties = {
    /**
     * True if the "null" string is interpreted as a string. Otherwise it is unboxed as null
     */
    keepNullString?: boolean;
    /**
     * True if the "undefined" string is interpreted as a string. Otherwise it is unboxed as undefined
     */
    keepUndefinedString?: boolean;
};
export type MetadataForSchema<T> = T extends ObjectS<any> ? ObjectMetadata : T extends ArrayS<any> ? ArrayMetadata : T extends DictionaryS<any> ? DictionaryMetadata : TypedMetadata;
/**
 * Base for all schemas defining their common behaviour
 */
export interface Schema<Target = any, Sources = any, B extends DefaultBehaviour = DefaultBehaviour, Original extends Schema = any> {
    /**
     * Runtime information about the schema object
     */
    metadata: MetadataForSchema<Original>;
    /**
     * Converts the loosely-typed source to the exact type defined by the schema
     * @param source Source that can be of a type that can be converted to one defined by the schema
     * @param props Unboxing options. By default, "null" string is unboxed as null, but "undefined" string as an "undefined" string
     * @returns Value converted to the type managed by the schema
     */
    unbox: (source: AllowNull<Sources, B>, props?: UnboxingProperties) => AllowNull<Target, B>;
}
/**
 * Schema for complex types
 */
export interface ExtendedSchema<Target = any, Sources = any, B extends DefaultBehaviour = DefaultBehaviour> extends Schema<Target, Sources, B> {
    /**
     * Indicates that the unboxed value cannot be null. Forbids null source values if true
     */
    notNull: NotNullFacade<Target, Sources, B, this>;
    /**
     * Indicates that the unboxed value can be undefined or omitted if it is an object's field
     */
    optional: OptionalFacade<Target, Sources, B, this>;
    /**
     * Indicates the default unboxing behaviour of the schema depending on the source unboxing value
     * @param target Either a default value to set or a function returning that default value, or an error to throw, in which case this is acting as a validator
     * @param condition Function defining the condition when the default value is applied (or the error is thrown). By default, applied when the source is null
     */
    byDefault: (target: Target | Error | ((s: Sources) => Target), condition?: (source: Sources) => boolean) => ByDefaultFacade<Target, Sources, B, this>;
}
/**
 * Facade schema enforcing the not null restriction on any underlying unboxed object
 */
export interface NotNullFacade<Target, Sources, B extends DefaultBehaviour, Original extends Schema<Target, Sources, B>> extends Schema<Target, Sources, NotNull, Original> {
    /**
     * This flag can be used for discriminators
     */
    readonly notNullFlag: true;
    /**
     * Extends the object schema with additional fields
     * @param definition Object containing fields with type definitions
     * @returns Extended object schema
     */
    extend: Original extends ObjectS<infer X> ? <R extends SchemaDefinition>(definition: R) => ObjectS<X & R> : never;
}
/**
 * Facade schema allowing unboxed values to be undefined and making related object fields optional
 */
export interface OptionalFacade<Target, Sources, B extends DefaultBehaviour, Original extends Schema<Target, Sources, B>> extends Schema<Target, Sources, Optional, Original> {
    /**
     * This flag can be used for discriminators
     */
    readonly optionalFlag: true;
    /**
     * Extends the object schema with additional fields
     * @param definition Object containing fields with type definitions
     * @returns Extended object schema
     */
    extend: Original extends ObjectS<infer X> ? <R extends SchemaDefinition>(definition: R) => ObjectS<X & R> : never;
}
export interface ByDefaultFacade<Target, Sources, B extends DefaultBehaviour, Original extends Schema<Target, Sources, B>> extends Schema<Target, Sources, B, Original> {
    optional: OptionalFacade<Target, Sources, B, Original>;
}
/**
 * Abstract schema type, parent class of all the base schema definitions
 */
export declare abstract class TypeSchema<Target = any, Sources = any, B extends DefaultBehaviour = {
    allowNull: true;
    optional: false;
}> implements ExtendedSchema<Target, Sources, B> {
    #private;
    abstract get metadata(): TypedMetadata;
    protected abstract convert: (source: Sources, props?: UnboxingProperties) => Target;
    /** {@inheritdoc Schema.unbox} */
    unbox: (source: AllowNull<Sources, B>, props?: UnboxingProperties) => AllowNull<Target, B>;
    /** {@inheritdoc ExtendedSchema.unbox} */
    get notNull(): NotNullFacade<Target, Sources, B, this>;
    /** {@inheritdoc ExtendedSchema.unbox} */
    get optional(): OptionalFacade<Target, Sources, B, this>;
    /** {@inheritdoc ExtendedSchema.unbox} */
    byDefault: (target: Error | Target | ((s: Sources) => Target), condition?: (source: Sources) => boolean) => ByDefaultFacade<Target, Sources, B, this>;
}
/**
 * Defines how a complex object's schema must be structured
 */
export interface SchemaDefinition {
    [K: string]: Schema;
}
/**
 * Utility type to get the type hidden behind a .notNull or .optional facade extension
 */
export type ExtractFromFacade<T> = T extends NotNullFacade<any, any, any, infer S> ? S : T extends OptionalFacade<any, any, any, infer S> ? S : T;
/**
 * Gets the schema's signature as a string
 * @param schema Schema to obtain the string signature for
 * @returns Signature detailing the schema's structure
 */
export declare const getSchemaSignature: <T extends SchemaDefinition, B extends DefaultBehaviour>(schema: Schema<SchemaTarget<T>, SchemaSource<T>, B> | T) => string;
export type RecursiveS = {
    isRecursive: true;
} & Schema;
export declare const recursiveS: RecursiveS;
/**
 * Object schema representing a Typescript/Javascript object
 */
export interface ObjectS<T extends SchemaDefinition> extends ExtendedSchema<SchemaTarget<T>, SchemaSource<T>> {
    /**
     * Extended metadata containing names and schemas of object's fields
     */
    get metadata(): ObjectMetadata;
    /**
     * Extends the object schema with additional fields
     * @param definition Object containing fields with type definitions
     * @returns Extended object schema
     */
    extend: <R extends SchemaDefinition>(definition: R) => ObjectS<T & R>;
}
/**
 * Returns an object schema representing a Typescript/Javascript object with typed fields
 * @param definition Object containing fields with type definitions
 * @returns Object schema with typed fields schemas
 * @example The object schema defined like this:
 * ```ts
 * objectS({
 *      id: intS.notNull,
 *      name: stringS,
 *      valid: boolS.optional
 * })
 * ```
 * ...represents the Typescript object defined as
 * ```ts
 * {
 *      id: number,
 *      name: string | null,
 *      valid?: boolean | null
 * }
 * ```
 */
export declare const objectS: <T extends SchemaDefinition>(definition: T) => ObjectS<T>;
/**
 * Schema representing an array
 */
export interface ArrayS<S extends Schema> extends ExtendedSchema<InferTargetFromSchema<S>[], InferSourceFromSchema<S>[] | string> {
    /**
     * Extended metadata containing the information about the array elements' types
     */
    get metadata(): ArrayMetadata;
}
/**
 * Returns a schema object representing the array of elements of the same type
 * @param elements Schema type for each array element
 * @returns Object schema representing an array of containing type
 * @example A schema defined like this:
 * ```ts
 * arrayS(stringS.notNull)
 * ```
 * ...represents an object defined as
 * ```ts
 * string[] | null
 * ```
 */
export declare const arrayS: <S extends Schema<any, any, DefaultBehaviour, any>>(elements: S) => ArrayS<S>;
/**
 * Utility type returning an object schema even if the underlying object is `objectS(...).notNull` or `objectS(...).optional`
 */
export type ObjectOrFacadeS<T extends SchemaDefinition> = ObjectS<T> | NotNullFacade<SchemaTarget<T>, SchemaSource<T>, DefaultBehaviour, ObjectS<T>>;
/**
 * Schema representing a string-to-object dictionary
 */
export interface DictionaryS<V extends Schema> extends ExtendedSchema<InferTargetForDictionary<V>, InferSourceForDictionary<V> | string> {
    /**
     * Extended metadata containing the information about the dictionary values' types
     */
    get metadata(): DictionaryMetadata;
}
/**
 * Returns a schema object representing the string-to-object dictionary of elements of the same type
 * @param elements Schema type for each dictionary value
 * @returns Object schema representing an dictionary of values of containing type
 * @example A schema defined like this:
 * ```ts
 * dictionaryS(intS.notNull)
 * ```
 * ...represents an object defined as
 * ```ts
 * {
 *      [K:string]: number
 * } | null
 * ```
 */
export declare const dictionaryS: <S extends Schema<any, any, DefaultBehaviour, any>>(values: S) => DictionaryS<S>;
export {};
