import { type AnyFunction, type ArrayElement, type AtLeastTuple } from '@augment-vir/common';
import { type IsEqual, type IsNever, type LiteralToPrimitive, type Primitive, type Simplify, type UnionToIntersection, type WritableDeep } from 'type-fest';
import { type CustomSpecifier } from './custom-specifier.js';
import { isShapeDefinitionKey, isShapeSpecifierKey } from './shape-keys.js';
/**
 * ========================================
 *
 * Shape Definition
 *
 * ========================================
 */
/**
 * The output of `defineShape`. This is a shape definition which includes the shape itself (used for
 * shape testing), a `runtimeType`, and a `defaultValue`.
 *
 * @category Util
 */
export type ShapeDefinition<Shape, IsReadonly extends boolean> = {
    shape: Shape;
    runtimeType: ShapeToRuntimeType<Shape, false, IsReadonly>;
    isReadonly: IsReadonly;
    defaultValue: Readonly<ShapeToRuntimeType<Shape, false, IsReadonly>>;
    [isShapeDefinitionKey]: true;
};
/**
 * Checks if the given input is a {@link isShapeDefinition}.
 *
 * @category Util
 */
export declare function isShapeDefinition(input: unknown): input is ShapeDefinition<unknown, false>;
/**
 * ========================================
 *
 * Specifier Symbols
 *
 * ========================================
 */
/**
 * Values used to mark the outputs of each sub-shape function (like {@link or}).
 *
 * @category Internal
 */
export declare enum ShapeSpecifierType {
    And = "and",
    Class = "class",
    Enum = "enum",
    Exact = "exact",
    IndexedKeys = "indexed-keys",
    Or = "or",
    Unknown = "unknown",
    NumericRange = "numeric-range",
    Optional = "optional",
    Tuple = "tuple"
}
/** @category Internal */
export type BaseParts = AtLeastTuple<unknown, 0>;
/**
 * Output from the sub-shape defining functions (such as {@link or}).
 *
 * @category Internal
 */
export type ShapeSpecifier<Parts extends BaseParts, Type extends ShapeSpecifierType> = {
    [isShapeSpecifierKey]: true;
    parts: Parts;
    specifierType: Type;
};
/**
 * Allowed types for keys in the base input for the `indexedKeys` shape.
 *
 * @category Internal
 */
export type AllowedIndexKeysKeysSpecifiers = ShapeEnum<Readonly<[Record<string, number | string>]>> | ShapeExact<Readonly<AtLeastTuple<PropertyKey, 1>>> | ShapeUnknown<[unknown]> | PropertyKey | CustomSpecifier<any>;
/**
 * Base type for inputs to the `indexedKeys` shape.
 *
 * @category Internal
 */
export type BaseIndexedKeys = {
    keys: ShapeOr<AtLeastTuple<AllowedIndexKeysKeysSpecifiers, 1>> | AllowedIndexKeysKeysSpecifiers;
    values: unknown;
    required: boolean;
};
/**
 * ========================================
 *
 * Shape Types
 *
 * ========================================
 */
/**
 * {@link ShapeSpecifier} for {@link and}.
 *
 * @category Internal
 */
export type ShapeAnd<Parts extends AtLeastTuple<unknown, 1>> = ShapeSpecifier<Parts, ShapeSpecifierType.And>;
/**
 * Helper type for {@link ShapeClass}.
 *
 * @category Internal
 */
export type AnyConstructor = new (...args: any[]) => any;
/**
 * {@link ShapeSpecifier} for {@link classShape}.
 *
 * @category Internal
 */
export type ShapeClass<Parts extends [AnyConstructor]> = ShapeSpecifier<Parts, ShapeSpecifierType.Class>;
/**
 * {@link ShapeSpecifier} for {@link enumShape}.
 *
 * @category Internal
 */
export type ShapeEnum<Parts extends Readonly<[Record<string, number | string>, (number | string)?]>> = ShapeSpecifier<Parts, ShapeSpecifierType.Enum>;
/**
 * {@link ShapeSpecifier} for {@link exact}.
 *
 * @category Internal
 */
export type ShapeExact<Parts extends Readonly<AtLeastTuple<unknown, 1>>> = ShapeSpecifier<Parts, ShapeSpecifierType.Exact>;
/**
 * {@link ShapeSpecifier} for {@link indexedKeys}.
 *
 * @category Internal
 */
export type ShapeIndexedKeys<Parts extends Readonly<[BaseIndexedKeys]>> = ShapeSpecifier<Parts, ShapeSpecifierType.IndexedKeys>;
/**
 * {@link ShapeSpecifier} for {@link tupleShape}.
 *
 * @category Internal
 */
export type ShapeTuple<Parts extends Readonly<any[]>> = ShapeSpecifier<Parts, ShapeSpecifierType.Tuple>;
/**
 * {@link ShapeSpecifier} for {@link or}.
 *
 * @category Internal
 */
export type ShapeOr<Parts extends AtLeastTuple<unknown, 1>> = ShapeSpecifier<Parts, ShapeSpecifierType.Or>;
/**
 * {@link ShapeSpecifier} for {@link unknownShape}.
 *
 * @category Internal
 */
export type ShapeUnknown<Parts extends Readonly<[unknown]>> = ShapeSpecifier<Parts, ShapeSpecifierType.Unknown>;
/**
 * {@link ShapeSpecifier} for {@link numericRange}.
 *
 * @category Internal
 */
export type ShapeNumericRange<T extends number = number> = ShapeSpecifier<[
    T,
    T
], ShapeSpecifierType.NumericRange>;
/**
 * {@link ShapeSpecifier} for {@link optional}.
 *
 * @category Internal
 */
export type ShapeOptional<T = unknown> = ShapeSpecifier<[T], ShapeSpecifierType.Optional>;
/**
 * ========================================
 *
 * Shape Functions
 *
 * ========================================
 */
/**
 * Create a shape part that combines all of its inputs together with an intersection or "and".
 *
 * @category Shape Part
 * @example
 *
 * ```ts
 * import {and, defineShape} from 'object-shape-tester';
 *
 * const myShape = defineShape({
 *     a: and({q: ''}, {r: -1}, {s: true}),
 * });
 *
 * // `myShape.runtimeType` is `{a: {q: string, r: number, s: boolean}}`
 * ```
 */
export declare function and<Parts extends AtLeastTuple<unknown, 1>>(...parts: Parts): ShapeAnd<Parts>;
/**
 * Define a shape part that requires an instance of the given constructor.
 *
 * @category Shape Part
 * @example
 *
 * ```ts
 * import {classShape, defineShape} from 'object-shape-tester';
 *
 * const myShape = defineShape({
 *     a: classShape(RegExp),
 * });
 *
 * // `myShape.runtimeType` is `{a: RegExp}`
 * ```
 */
export declare function classShape<Parts extends [AnyConstructor]>(...parts: Parts): ShapeClass<Parts>;
/**
 * Define a shape part that requires an enum value.
 *
 * @category Shape Part
 * @example
 *
 * ```ts
 * import {enumShape, defineShape} from 'object-shape-tester';
 *
 * enum MyEnum {
 *     A = 'a',
 *     B = 'b',
 * }
 *
 * const myShape = defineShape({
 *     a: enumShape(MyEnum),
 * });
 *
 * // `myShape.runtimeType` is `{a: MyEnum}`
 * ```
 */
export declare function enumShape<const Parts extends Readonly<[Record<string, number | string>, (number | string)?]>>(...parts: Parts): ShapeEnum<Parts>;
/**
 * Define a shape part that requires _exactly_ the value given.
 *
 * @category Shape Part
 * @example
 *
 * ```ts
 * import {exact, defineShape} from 'object-shape-tester';
 *
 * const myShape = defineShape({
 *     a: or(exact('hi'), exact('bye')),
 * });
 *
 * // `myShape.runtimeType` is `{a: 'hi' | 'bye'}`
 * ```
 */
export declare function exact<const Parts extends Readonly<AtLeastTuple<unknown, 1>>>(...parts: Parts): ShapeExact<Parts>;
/**
 * Define a shape part that's an object with a specific set of keys and values.
 *
 * @category Shape Part
 * @example
 *
 * ```ts
 * import {exact, defineShape, indexedKeys} from 'object-shape-tester';
 *
 * const myShape = defineShape({
 *     a: indexedKeys({
 *         keys: or(exact('hi'), exact('bye')),
 *         values: {
 *             helloThere: 0,
 *         },
 *         required: false,
 *     }),
 * });
 *
 * // `myShape.runtimeType` is `{a: Partial<Record<'hi' | 'bye', {helloThere: number}>>}`
 * ```
 */
export declare function indexedKeys<Parts extends Readonly<[BaseIndexedKeys]>>(...parts: Parts): ShapeIndexedKeys<Parts>;
/**
 * Define a shape part requires a tuple.
 *
 * @category Shape Part
 * @example
 *
 * ```ts
 * import {exact, defineShape, tupleShape} from 'object-shape-tester';
 *
 * const myShape = defineShape({
 *     a: tupleShape('a', -1, exact('hi')),
 * });
 *
 * // `myShape.runtimeType` is `[string, number, 'hi']`
 * ```
 */
export declare function tupleShape<Parts extends Readonly<any[]>>(...parts: Parts): ShapeTuple<Parts>;
/**
 * Define a shape part that's a union of all its inputs.
 *
 * @category Shape Part
 * @example
 *
 * ```ts
 * import {or, defineShape} from 'object-shape-tester';
 *
 * const myShape = defineShape({
 *     a: or('', -1),
 * });
 *
 * // `myShape.runtimeType` is `{a: string | number}`
 * ```
 */
export declare function or<Parts extends AtLeastTuple<unknown, 1>>(...parts: Parts): ShapeOr<Parts>;
/**
 * Define a shape part that resolves simply to `unknown`.
 *
 * @category Shape Part
 * @example
 *
 * ```ts
 * import {unknownShape, defineShape} from 'object-shape-tester';
 *
 * const myShape = defineShape({
 *     a: unknownShape,
 * });
 *
 * // `myShape.runtimeType` is `{a: unknown}`
 * ```
 */
export declare function unknownShape(defaultValue?: unknown): ShapeUnknown<[unknown]>;
/**
 * Define a shape part that requires numbers to be within a specific range, inclusive.
 *
 * @category Shape Part
 * @example
 *
 * ```ts
 * import {numericRange, defineShape} from 'object-shape-tester';
 *
 * const myShape = defineShape({
 *     // This will simply produce a type of `number` but will validate runtime values against the range.
 *     a: numericRange(1, 10),
 * });
 * // `myShape.runtimeType` is just `{a: number}`
 *
 * const myShape2 = defineShape({
 *     // If you want type safety, you must specify the allowed numbers manually
 *     a: numericRange<1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10>(1, 10),
 * });
 * // `myShape2.runtimeType` is `{a: 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10}`
 * ```
 */
export declare function numericRange<T extends number = number>(min: NoInfer<T>, max: NoInfer<T>): ShapeNumericRange<T>;
/**
 * Define a shape part that is optional. This only makes sense as a property in an object.
 *
 * @category Shape Part
 * @example
 *
 * ```ts
 * import {optional, defineShape} from 'object-shape-tester';
 *
 * const myShape = defineShape({
 *     a: optional(-1),
 * });
 *
 * // `myShape.runtimeType` is `{a?: number}`
 * ```
 */
export declare function optional<T>(part: T): ShapeOptional<T>;
/**
 * ========================================
 *
 * Shape Specifier Type Guards
 *
 * ========================================
 */
/**
 * Checks if the input is an {@link and} shape specifier for internal type guarding purposes.
 *
 * @category Internal
 */
export declare function isAndShapeSpecifier(maybeSpecifier: unknown): maybeSpecifier is ShapeAnd<AtLeastTuple<unknown, 1>>;
/**
 * Checks if the input is a {@link classShape} shape specifier for internal type guarding purposes.
 *
 * @category Internal
 */
export declare function isClassShapeSpecifier(maybeSpecifier: unknown): maybeSpecifier is ShapeClass<[AnyConstructor]>;
/**
 * Checks if the input is an {@link enumShape} shape specifier for internal type guarding purposes.
 *
 * @category Internal
 */
export declare function isEnumShapeSpecifier(maybeSpecifier: unknown): maybeSpecifier is ShapeEnum<[Record<string, number | string>, (number | string)?]>;
/**
 * Checks if the input is an {@link exact} shape specifier for internal type guarding purposes.
 *
 * @category Internal
 */
export declare function isExactShapeSpecifier(maybeSpecifier: unknown): maybeSpecifier is ShapeExact<[unknown]>;
/**
 * Checks if the input is an {@link indexedKeys} shape specifier for internal type guarding purposes.
 *
 * @category Internal
 */
export declare function isIndexedKeysSpecifier(maybeSpecifier: unknown): maybeSpecifier is ShapeIndexedKeys<Readonly<[BaseIndexedKeys]>>;
/**
 * Checks if the input is an {@link tupleShape} shape specifier for internal type guarding purposes.
 *
 * @category Internal
 */
export declare function isTupleShapeSpecifier(maybeSpecifier: unknown): maybeSpecifier is ShapeTuple<Readonly<any[]>>;
/**
 * Checks if the input is an {@link or} shape specifier for internal type guarding purposes.
 *
 * @category Internal
 */
export declare function isOrShapeSpecifier(maybeSpecifier: unknown): maybeSpecifier is ShapeOr<AtLeastTuple<unknown, 1>>;
/**
 * Checks if the input is an {@link unknownShape} shape specifier for internal type guarding
 * purposes.
 *
 * @category Internal
 */
export declare function isUnknownShapeSpecifier(maybeSpecifier: unknown): maybeSpecifier is ShapeUnknown<[unknown]>;
/**
 * Checks if the input is a {@link numericRange} shape specifier for internal type guarding purposes.
 *
 * @category Internal
 */
export declare function isNumericRangeShapeSpecifier(maybeSpecifier: unknown): maybeSpecifier is ShapeNumericRange;
/**
 * Checks if the input is a {@link optional} shape specifier for internal type guarding purposes.
 *
 * @category Internal
 */
export declare function isOptionalShapeSpecifier(maybeSpecifier: unknown): maybeSpecifier is ShapeOptional;
/**
 * ========================================
 *
 * Shape Value Run Time Type
 *
 * ========================================
 */
/** @category Internal */
export type ExpandParts<Parts extends BaseParts, IsExact extends boolean, IsReadonly extends boolean> = Extract<ArrayElement<Parts>, ShapeDefinition<any, any>> extends never ? ShapeToRuntimeType<ArrayElement<Parts>, IsExact, IsReadonly> : ShapeToRuntimeType<Exclude<ArrayElement<Parts>, ShapeDefinition<any, any>>, IsExact, IsReadonly> | Extract<ArrayElement<Parts>, ShapeDefinition<any, any>>['runtimeType'];
type MaybePartial<T, IsPartial extends boolean> = IsPartial extends true ? T : Partial<T>;
/** @category Internal */
export type TupleParts<Parts extends ReadonlyArray<any>, IsExact extends boolean, IsReadonly extends boolean> = {
    [Index in keyof Parts]: SpecifierToRuntimeType<Parts[Index], IsExact, IsReadonly>;
};
/**
 * Converts a shape specifier to a runtime type.
 *
 * @category Internal
 */
export type SpecifierToRuntimeType<PossiblySpecifier, IsExact extends boolean, IsReadonly extends boolean> = PossiblySpecifier extends ShapeSpecifier<infer Parts, infer Type> ? Type extends ShapeSpecifierType.NumericRange ? Parts[0] : Type extends ShapeSpecifierType.And ? OptionallyReadonly<IsReadonly, UnionToIntersection<ExpandParts<Parts, IsExact, IsReadonly>>> : Type extends ShapeSpecifierType.Class ? Parts[0] extends AnyConstructor ? OptionallyReadonly<IsReadonly, InstanceType<Parts[0]>> : 'TypeError: classShape input must be a constructor.' : Type extends ShapeSpecifierType.Or ? OptionallyReadonly<IsReadonly, ExpandParts<Parts, IsExact, IsReadonly>> : Type extends ShapeSpecifierType.Exact ? OptionallyReadonly<IsReadonly, WritableDeep<ExpandParts<Parts, true, IsReadonly>>> : Type extends ShapeSpecifierType.Enum ? OptionallyReadonly<IsReadonly, Parts[0][keyof Parts[0]]> : Type extends ShapeSpecifierType.IndexedKeys ? Parts[0] extends {
    keys: unknown;
    values: unknown;
    required: boolean;
} ? ExpandParts<[
    Parts[0]['keys']
], IsExact, IsReadonly> extends PropertyKey ? OptionallyReadonly<IsReadonly, MaybePartial<Record<ExpandParts<[Parts[0]['keys']], IsExact, IsReadonly>, ExpandParts<[Parts[0]['values']], IsExact, IsReadonly>>, Parts[0]['required']>> : 'TypeError: indexedKeys keys be a subset of PropertyKey.' : 'TypeError: indexedKeys input is invalid.' : Type extends ShapeSpecifierType.Tuple ? TupleParts<Parts, IsExact, IsReadonly> : Type extends ShapeSpecifierType.Unknown ? unknown : Type extends ShapeSpecifierType.Optional ? ExpandParts<Parts, IsExact, IsReadonly> : 'TypeError: found no match for shape specifier type.' : PossiblySpecifier extends Primitive ? IsExact extends true ? PossiblySpecifier : PossiblySpecifier : PossiblySpecifier extends object ? PossiblySpecifier extends ShapeDefinition<any, any> ? PossiblySpecifier['runtimeType'] : OptionallyReadonly<IsReadonly, {
    [Prop in keyof PossiblySpecifier]: SpecifierToRuntimeType<PossiblySpecifier[Prop], IsExact, IsReadonly>;
}> : PossiblySpecifier;
/** @category Internal */
export type OptionallyReadonly<IsReadonly extends boolean, OriginalType> = IsReadonly extends true ? Readonly<OriginalType> : OriginalType;
/**
 * Converts a shape definition to a runtime type.
 *
 * @category Util
 */
export type ShapeToRuntimeType<Shape, IsExact extends boolean, IsReadonly extends boolean> = Shape extends AnyFunction ? Shape : Shape extends CustomSpecifier<infer T> ? T : Shape extends object ? Shape extends ShapeDefinition<infer InnerShape, any> ? ShapeToRuntimeType<InnerShape, IsExact, IsReadonly> : Shape extends ShapeSpecifier<any, any> ? Shape extends ShapeSpecifier<any, ShapeSpecifierType.Exact> ? SpecifierToRuntimeType<Shape, true, IsReadonly> : SpecifierToRuntimeType<Shape, IsExact, IsReadonly> : Shape extends Array<any> ? OptionallyReadonly<IsReadonly, {
    [Prop in keyof Shape]: Shape[Prop] extends ShapeSpecifier<any, ShapeSpecifierType.Exact> ? ShapeToRuntimeType<Shape[Prop], true, IsReadonly> : ShapeToRuntimeType<Shape[Prop], IsExact, IsReadonly>;
}> : OptionallyReadonly<IsReadonly, Simplify<{
    [Prop in keyof Shape as Shape[Prop] extends ShapeOptional<any> ? never : Prop]: Shape[Prop] extends ShapeOptional<any> ? never : Shape[Prop] extends ShapeSpecifier<any, ShapeSpecifierType.Exact> ? ShapeToRuntimeType<Shape[Prop], true, IsReadonly> : ShapeToRuntimeType<Shape[Prop], IsExact, IsReadonly>;
} & {
    [Prop in keyof Shape as Shape[Prop] extends ShapeOptional<any> ? Prop : never]?: Shape[Prop] extends ShapeOptional<any> ? Shape[Prop] extends ShapeSpecifier<any, ShapeSpecifierType.Exact> ? ShapeToRuntimeType<Shape[Prop], true, IsReadonly> : ShapeToRuntimeType<Shape[Prop], IsExact, IsReadonly> : never;
}>> : IsEqual<IsExact, true> extends true ? Shape : IsNever<LiteralToPrimitive<Shape>> extends true ? Shape : LiteralToPrimitive<Shape>;
/**
 * If the input is a shape specifier, return it type guarded as such.
 *
 * @category Internal
 * @returns `undefined` if the input is not
 */
export declare function getShapeSpecifier(input: unknown): ShapeSpecifier<BaseParts, ShapeSpecifierType> | undefined;
export {};
