import { Config, DefineConfig, OptionalProps, PrimaryKeyProp, Primary, PrimaryProperty, Opt, Ref, Scalar, Collection, Hidden, ScalarRef, PropertyOptions, EnumOptions, AnyEntity, OneToOneOptions, EntityName, OneToManyOptions, ManyToOneOptions, ManyToManyOptions, Options, MikroORM, EntityManager, QueryOrderMap, Cursor } from '@mikro-orm/core';
import { Class, Primitive } from 'type-fest';
import { ApiPropertyOptions, OpenAPIObject } from '@nestjs/swagger';
import { ValidationOptions } from 'class-validator';
import { ValidationPipe, PipeTransform, ArgumentMetadata, ValidationPipeOptions, Type } from '@nestjs/common';

declare abstract class TimestampedEntity<Optional = never> {
    [Config]?: DefineConfig<{
        forceObject: true;
    }>;
    [OptionalProps]?: 'createdAt' | 'updatedAt' | Optional;
    createdAt: Date;
    updatedAt: Date;
}

declare abstract class BaseEntity<Optional = never> extends TimestampedEntity<Optional> {
    [PrimaryKeyProp]?: 'id';
    id: string;
}

type IEntityRef<T extends object> = {
    [K in keyof T as K extends PrimaryProperty<T> ? K : never]: Primary<T>;
};

declare const EntityRefTypeClassMetadataPropertyKey: unique symbol;
declare function EntityRefType<T extends object>(classRef: Class<T>): Class<IEntityRef<T>>;

type ExcludeDefinedOpt<T> = T extends infer E & Opt ? E : T;
type ExcludeOpt<T> = ExcludeDefinedOpt<Exclude<T, undefined>>;

type IEntityRefPropertyDto<T> = T extends Scalar ? T : T extends object ? {
    [K in keyof T as K extends PrimaryProperty<T> ? K : never]: Primary<T>;
} : T;
type IEntityPropertyDto<T> = T extends Ref<infer U> ? IEntityRefPropertyDto<U> : T extends Collection<infer U> ? IEntityRefPropertyDto<U>[] : T;
type IEntityDto<T> = {
    [K in keyof T as T[K] extends (Hidden | symbol | Function | ScalarRef<any> | Ref<Primitive>) ? never : K]: IEntityPropertyDto<ExcludeOpt<T[K]>>;
};

type IEntityDtoClass<T> = Class<IEntityDto<T>> & {
    from(entity: T): IEntityDto<T>;
};
declare function EntityDtoType<T extends object>(entity: Class<T>): IEntityDtoClass<T>;

type ColumnTypeRequired<T> = {
    type: PropertyOptions<T>['type'];
} | {
    columnType: PropertyOptions<T>['columnType'];
};

declare function EntityEnum<T extends object>(options: EnumOptions<AnyEntity> & ColumnTypeRequired<T> & {
    example?: any;
    enumName?: string;
}): PropertyDecorator;

declare function EntityProperty<T extends object>(options?: PropertyOptions<T> & Pick<ApiPropertyOptions, 'example'> & ColumnTypeRequired<T>): PropertyDecorator;

declare function EntityTransient(): PropertyDecorator;

declare function EntityOneToOne<Target, Owner>(entity?: OneToOneOptions<Owner, Target> | string | ((e: Owner) => EntityName<Target>), mappedByOrOptions?: (string & keyof Target) | ((e: Target) => any) | Partial<OneToOneOptions<Owner, Target>>, options?: Partial<OneToOneOptions<Owner, Target>>): PropertyDecorator;

declare function EntityOneToMany<Target, Owner>(options: OneToManyOptions<Owner, Target>): PropertyDecorator;

declare function EntityManyToOne<T extends object, O>(entity?: ManyToOneOptions<T, O> | string | ((e?: any) => EntityName<T>), options?: Partial<ManyToOneOptions<T, O>>): PropertyDecorator;

declare function EntityManyToMany<T extends object, O>(entity?: ManyToManyOptions<T, O> | string | (() => EntityName<T>), mappedBy?: (string & keyof T) | ((e: T) => any), options?: Partial<ManyToManyOptions<T, O>>): PropertyDecorator;

declare class DatabaseConfig {
    debug: boolean;
    migration: boolean;
    dbName: string;
    host: string;
    port: number;
    user: string;
    password: string;
    timezone?: string | undefined;
    toMikroOrmOptions(config?: Partial<Options>): Options;
}

type ExcludeRef<T> = T extends Ref<infer U> ? U : T;

type ExcludeHidden<T> = T extends infer U & Hidden ? U : T;

declare function deepObjectifyQueries(openapi: OpenAPIObject): void;

type ModelKind = 'model' | 'entity' | 'dto' | 'bo' | 'ro';
interface ModelMetadata {
    kind: ModelKind;
    propertyKeys: (string | symbol)[];
}
declare function Model(): ClassDecorator;

declare const RELATION_METADATA_KEY = "buka:relation";
type RelationKind = '1:1' | '1:m' | 'm:1' | 'm:n';
interface RelationMetadata {
    kind: RelationKind;
    type: () => Class<object>;
}
declare function Relation$1(metadata: RelationMetadata): PropertyDecorator;

type PropertyKind = 'nested' | 'dictionary' | 'list';
interface PropertyMetadata {
    kind?: PropertyKind;
    optional?: boolean;
    type: () => Function;
    relation?: RelationMetadata;
    schema?: ApiPropertyOptions;
}
declare function Property(options?: Partial<PropertyMetadata>): PropertyDecorator;

declare class ModelRegister {
    static setModel(target: Class<any>, metadata: ModelMetadata): void;
    static getModel(target: Class<any>): ModelMetadata | undefined;
    static setProperty(target: Class<any>, propertyName: string | symbol, metadata: PropertyMetadata): void;
    static getProperty(target: Class<any>, propertyName: string | symbol): PropertyMetadata | undefined;
    static copyProperty(source: Class<any>, target: Class<any>, propertyName: string | symbol): void;
    static addModel(target: Class<any>, kind?: ModelKind): ModelMetadata;
    static addProperty(target: Class<any>, propertyName: string | symbol, metadata?: Partial<PropertyMetadata>): void;
    /**
     * 判断是否为已注册的 Model
     *
     * @example
     * ```typescript
     * const isModel = ModelRegister.isModel(Class.prototype)
     * ```
     */
    static isModel(target: Class<any>): boolean;
    /**
     * 获取 Model 的所有属性
     *
     * @example
     * ```typescript
     * const properties = ModelRegister.getProperties(Class.prototype)
     * ```
     */
    static getModelPropertyKeys(target: Class<any>): (string | symbol)[];
    static getProperties(target: Class<any>): PropertyMetadata[];
}

type NestedOptions = Pick<PropertyMetadata, 'type' | 'optional' | 'schema' | 'relation'>;
declare function Nested(options: NestedOptions): PropertyDecorator;

interface NestedPropertiesOptions extends Pick<PropertyMetadata, 'relation' | 'optional' | 'schema'> {
    each?: boolean;
}
declare function NestedProperty(type: () => Class<object>, options?: NestedPropertiesOptions): PropertyDecorator;

declare const ReferencePropertiesMetadataKey = "buka:reference-properties";
/**
 *
 * @param entity MikroORM Entity
 */
declare function ReferenceProperty(entity: () => Class<object>): PropertyDecorator;

declare const HAS_ANY_KEY = "hasAnyKey";
declare function HasAnyKey(keys: readonly string[], validationOptions?: ValidationOptions): (object: object, propertyName: string) => void;

declare const MATCH_JSON_SCHEMA = "matchJsonSchema";
declare function MatchJsonSchema(schema: any, validationOptions?: ValidationOptions): (object: object, propertyName: string) => void;

declare function PageQuery(mode?: 'cursor' | 'offset'): ParameterDecorator;
declare function OptionalPageQuery(mode?: 'cursor' | 'offset'): ParameterDecorator;

declare class BukaValidationPipe extends ValidationPipe implements PipeTransform {
    private readonly orm;
    private readonly em;
    private readonly logger;
    constructor(orm: MikroORM, em: EntityManager);
    transform(value: any, metadata: ArgumentMetadata): Promise<any>;
    static withParams(options: ValidationPipeOptions): Type<PipeTransform>;
}

/**
 * 表示该字段是关联到另一个表的实体，而不是 JSON 类型
 */
type Relation<T = unknown> = T & Relation.Brand;
declare namespace Relation {
    const __relation: unique symbol;
    interface Brand {
        [__relation]?: 1;
    }
}

interface IFilterQueryCondition<T> {
    $lt?: T;
    $gt?: T;
    $lte?: T;
    $gte?: T;
    $eq?: T;
    $ne?: T;
    $in?: T[];
    $nin?: T[];
}
interface IFilterQueryCollectionCondition<T> {
    $some?: IFilterQueryNestedProperty<T>;
    $every?: IFilterQueryNestedProperty<T>;
    $none?: IFilterQueryNestedProperty<T>;
}
type IFilterQueryNestedProperty<T> = T extends Array<infer U> ? IFilterQueryCollectionCondition<U> : IFilterQueryObject<T>;
type IFilterQueryProperty<T> = T extends Relation ? IFilterQueryNestedProperty<T> : IFilterQueryCondition<T>;
type IFilterQueryObject<T> = {
    [K in keyof T as K extends string ? K : never]: IFilterQueryProperty<T[K]>;
};
interface IFilterQuery<T> {
    filter?: IFilterQueryObject<T>;
}

declare function FilterQueryType<T>(classRef: Class<T>): Class<IFilterQuery<T>>;

type FilterQueryOperator = '$eq' | '$ne' | '$lt' | '$gt' | '$lte' | '$gte' | '$in' | '$nin' | '$some' | '$every' | '$none';
declare const FilterQueryOperatorsMetadataKey = "buka:filter-query-operators";
declare function FilterQueryOperators(operators: FilterQueryOperator[]): PropertyDecorator;
declare function getFilterQueryOperators(target: object, propertyKey: string | symbol): readonly FilterQueryOperator[];

declare function PickType<T, K extends keyof T>(classRef: Class<T>, keys: K[]): Class<Pick<T, typeof keys[number]>>;

declare function OmitType<T, K extends keyof T>(classRef: Class<T>, keys: K[]): Class<Omit<T, typeof keys[number]>>;

declare function PartialType<T>(classRef: Class<T>): Class<Partial<T>>;

interface IOrderQuery<T> {
    orderBy?: QueryOrderMap<T> | QueryOrderMap<T>[];
}

declare function OrderQueryType<T>(classRef: Class<T>): Class<IOrderQuery<T>>;

interface IResponseBody<DATA> {
    data: DATA;
    meta: {
        [key: string]: any;
    };
}

declare class OffsetPaginationRo {
    total: number;
    limit: number;
    offset: number;
}

declare class CursorPaginationRo {
    total?: number;
    limit: number;
    startCursor: string;
    endCursor: string;
    hasNextPage: boolean;
    hasPrevPage: boolean;
}

interface IListResponseBody<DATA, MODE extends 'offset' | 'cursor' = 'offset' | 'cursor'> extends IResponseBody<DATA[]> {
    meta: {
        pagination: MODE extends 'cursor' ? CursorPaginationRo : MODE extends 'offset' ? OffsetPaginationRo : CursorPaginationRo | OffsetPaginationRo;
        [key: string]: any;
    };
}

type IOffsetPageParameters = {
    limit: number;
    offset: number;
};
type INextCursorPageParameters = {
    first: number;
    after: string;
};
type IPreviousCursorPageParameters = {
    last: number;
    before: string;
};
type IPageQuery<T extends 'cursor' | 'offset' = 'cursor' | 'offset'> = {
    page: T extends 'cursor' ? INextCursorPageParameters | IPreviousCursorPageParameters : T extends 'offset' ? IOffsetPageParameters : INextCursorPageParameters | IPreviousCursorPageParameters | IOffsetPageParameters;
};

declare class OffsetPagination {
    total: number;
    limit: number;
    offset: number;
    constructor(total: number, parameters: {
        limit: number;
        offset: number;
    });
}

declare class CursorPagination {
    total?: number;
    limit: number;
    startCursor: string | null;
    endCursor: string | null;
    hasNextPage: boolean;
    hasPrevPage: boolean;
    constructor(cursor: Cursor<any>);
}

declare class Slice<T extends object> {
    data: Array<T>;
    pagination: OffsetPagination | CursorPagination;
    constructor(data: Array<T>, pagination: OffsetPagination | CursorPagination);
    [Symbol.iterator](): Iterator<T>;
    static fromOffset<T extends object>(data: Array<T>, total: number, parameters: IOffsetPageParameters): Slice<T>;
    static fromCursor<T extends object>(cursor: Cursor<T>): Slice<T>;
    map<R extends object>(fn: (item: T, index: number) => R): Slice<R>;
}

type IListResponseBodyClass<T extends object, MODE extends 'offset' | 'cursor'> = Class<IListResponseBody<T, MODE>> & {
    fromSlice(slice: Slice<T>): IListResponseBody<T, MODE>;
};
declare function ListResponseBodyType<T extends object, MODE extends 'offset' | 'cursor' = 'offset' | 'cursor'>(classRef: Type<T>, mode?: MODE): IListResponseBodyClass<T, MODE>;

type IResponseBodyClass<T> = Class<IResponseBody<T>> & {
    from(data: T): IResponseBody<T>;
};
declare function ResponseBodyType<T>(classRef: Class<T>): IResponseBodyClass<T>;

export { BaseEntity, BukaValidationPipe, type ColumnTypeRequired, DatabaseConfig, EntityDtoType, EntityEnum, EntityManyToMany, EntityManyToOne, EntityOneToMany, EntityOneToOne, EntityProperty, EntityRefType, EntityRefTypeClassMetadataPropertyKey, EntityTransient, type ExcludeDefinedOpt, type ExcludeHidden, type ExcludeOpt, type ExcludeRef, type FilterQueryOperator, FilterQueryOperators, FilterQueryOperatorsMetadataKey, FilterQueryType, HAS_ANY_KEY, HasAnyKey, type IEntityDto, type IEntityDtoClass, type IEntityPropertyDto, type IEntityRef, type IEntityRefPropertyDto, type IFilterQuery, type IFilterQueryCollectionCondition, type IFilterQueryCondition, type IFilterQueryNestedProperty, type IFilterQueryObject, type IFilterQueryProperty, type IListResponseBody, type IOrderQuery, type IPageQuery, type IResponseBody, ListResponseBodyType, MATCH_JSON_SCHEMA, MatchJsonSchema, Model, type ModelKind, type ModelMetadata, ModelRegister, Nested, NestedProperty, OmitType, OptionalPageQuery, OrderQueryType, PageQuery, PartialType, PickType, Property, type PropertyKind, type PropertyMetadata, RELATION_METADATA_KEY, ReferencePropertiesMetadataKey, ReferenceProperty, Relation$1 as Relation, type RelationKind, type RelationMetadata, ResponseBodyType, Slice, TimestampedEntity, deepObjectifyQueries, getFilterQueryOperators };
