import type { StandardSchemaV1 } from "@standard-schema/spec";
import type {
	Awaitable,
	BetterAuthOptions,
	LiteralString,
	UnionToIntersection,
} from "../types";

export type BaseModelNames = "user" | "account" | "session" | "verification";

export type ModelNames<T extends string = LiteralString> =
	| BaseModelNames
	| T
	| "rate-limit";

export type InferDBValueType<T extends DBFieldType> = T extends "string"
	? string
	: T extends "number"
		? number
		: T extends "boolean"
			? boolean
			: T extends "date"
				? Date
				: T extends "json"
					? Record<string, any>
					: T extends `${infer U}[]`
						? U extends "string"
							? string[]
							: number[]
						: T extends Array<any>
							? T[number]
							: never;

export type InferDBFieldOutput<T extends DBFieldAttribute> =
	T["returned"] extends false
		? never
		: T["required"] extends false
			? InferDBValueType<T["type"]> | undefined | null
			: InferDBValueType<T["type"]>;

export type InferDBFieldInput<T extends DBFieldAttribute> = InferDBValueType<
	T["type"]
>;

export type InferDBFieldsInput<Field> =
	Field extends Record<infer Key, DBFieldAttribute>
		? {
				[key in Key as Field[key]["required"] extends false
					? never
					: Field[key]["defaultValue"] extends string | number | boolean | Date
						? never
						: Field[key]["input"] extends false
							? never
							: key]: InferDBFieldInput<Field[key]>;
			} & {
				[key in Key as Field[key]["input"] extends false ? never : key]?:
					| InferDBFieldInput<Field[key]>
					| undefined
					| null;
			}
		: {};

export type InferDBFieldsOutput<
	Fields extends Record<string, DBFieldAttribute>,
> =
	Fields extends Record<infer Key, DBFieldAttribute>
		? {
				[key in Key as Fields[key]["returned"] extends false
					? never
					: Fields[key]["required"] extends false
						? Fields[key]["defaultValue"] extends
								| boolean
								| string
								| number
								| Date
							? key
							: never
						: key]: InferDBFieldOutput<Fields[key]>;
			} & {
				[key in Key as Fields[key]["returned"] extends false
					? never
					: Fields[key]["required"] extends false
						? Fields[key]["defaultValue"] extends
								| boolean
								| string
								| number
								| Date
							? never
							: key
						: never]?: InferDBFieldOutput<Fields[key]> | null;
			}
		: never;

export type InferDBFieldsFromOptionsInput<
	DBOptions extends
		| BetterAuthOptions["session"]
		| BetterAuthOptions["user"]
		| BetterAuthOptions["verification"]
		| BetterAuthOptions["account"]
		| BetterAuthOptions["rateLimit"],
> = DBOptions extends {
	additionalFields: Record<string, DBFieldAttribute>;
}
	? InferDBFieldsInput<DBOptions["additionalFields"]>
	: {};

export type InferDBFieldsFromOptions<
	DBOptions extends
		| BetterAuthOptions["session"]
		| BetterAuthOptions["user"]
		| BetterAuthOptions["verification"]
		| BetterAuthOptions["account"]
		| BetterAuthOptions["rateLimit"],
> = DBOptions extends {
	additionalFields: Record<string, DBFieldAttribute>;
}
	? InferDBFieldsOutput<DBOptions["additionalFields"]>
	: {};

export type InferDBFieldsFromPluginsInput<
	ModelName extends string,
	Plugins extends unknown[] | undefined,
> = Plugins extends []
	? {}
	: Plugins extends [infer P, ...infer Rest]
		? P extends {
				schema: {
					[key in ModelName]: {
						fields: infer Fields;
					};
				};
			}
			? Fields extends Record<string, DBFieldAttribute>
				? UnionToIntersection<
						InferDBFieldsInput<Fields> &
							InferDBFieldsFromPluginsInput<ModelName, Rest>
					>
				: InferDBFieldsFromPluginsInput<ModelName, Rest>
			: InferDBFieldsFromPluginsInput<ModelName, Rest>
		: {};

export type InferDBFieldsFromPlugins<
	ModelName extends string,
	Plugins extends unknown[] | undefined,
> = Plugins extends []
	? {}
	: Plugins extends [infer P, ...infer Rest]
		? P extends {
				schema: {
					[key in ModelName]: {
						fields: infer Fields;
					};
				};
			}
			? Fields extends Record<string, DBFieldAttribute>
				? UnionToIntersection<
						InferDBFieldsOutput<Fields> &
							InferDBFieldsFromPlugins<ModelName, Rest>
					>
				: InferDBFieldsFromPlugins<ModelName, Rest>
			: InferDBFieldsFromPlugins<ModelName, Rest>
		: {};

export type DBFieldType =
	| "string"
	| "number"
	| "boolean"
	| "date"
	| "json"
	| `${"string" | "number"}[]`
	| Array<LiteralString>;

export type DBPrimitive =
	| string
	| number
	| boolean
	| Date
	| null
	| undefined
	| string[]
	| number[]
	| (Record<string, unknown> | unknown[]);

export type DBFieldAttributeConfig = {
	/**
	 * If the field should be required on a new record.
	 * @default true
	 */
	required?: boolean | undefined;
	/**
	 * If the value should be returned on a response body.
	 * @default true
	 */
	returned?: boolean | undefined;
	/**
	 * If a value should be provided when creating a new record.
	 * @default true
	 */
	input?: boolean | undefined;
	/**
	 * Default value for the field
	 *
	 * Note: This will not create a default value on the database level. It will only
	 * be used when creating a new record.
	 */
	defaultValue?: (DBPrimitive | (() => DBPrimitive)) | undefined;
	/**
	 * Update value for the field
	 *
	 * Note: This will create an onUpdate trigger on the database level for supported adapters.
	 * It will be called when updating a record.
	 */
	onUpdate?: (() => DBPrimitive) | undefined;
	/**
	 * transform the value before storing it.
	 */
	transform?:
		| {
				input?: (value: DBPrimitive) => Awaitable<DBPrimitive>;
				output?: (value: DBPrimitive) => Awaitable<DBPrimitive>;
		  }
		| undefined;
	/**
	 * Reference to another model.
	 */
	references?:
		| {
				/**
				 * The model to reference.
				 */
				model: string;
				/**
				 * The field on the referenced model.
				 */
				field: string;
				/**
				 * The action to perform when the reference is deleted.
				 * @default "cascade"
				 */
				onDelete?:
					| "no action"
					| "restrict"
					| "cascade"
					| "set null"
					| "set default";
		  }
		| undefined;
	unique?: boolean | undefined;
	/**
	 * If the field should be a bigint on the database instead of integer.
	 */
	bigint?: boolean | undefined;
	/**
	 * A zod schema to validate the value.
	 */
	validator?:
		| {
				input?: StandardSchemaV1;
				output?: StandardSchemaV1;
		  }
		| undefined;
	/**
	 * The name of the field on the database.
	 */
	fieldName?: string | undefined;
	/**
	 * If the field should be sortable.
	 *
	 * applicable only for `text` type.
	 * It's useful to mark fields varchar instead of text.
	 */
	sortable?: boolean | undefined;
	/**
	 * If the field should be indexed.
	 * @default false
	 */
	index?: boolean | undefined;
};

export type DBFieldAttribute<T extends DBFieldType = DBFieldType> = {
	type: T;
} & DBFieldAttributeConfig;

export type BetterAuthDBSchema = Record<
	string,
	{
		/**
		 * The name of the table in the database
		 */
		modelName: string;
		/**
		 * The fields of the table
		 */
		fields: Record<string, DBFieldAttribute>;
		/**
		 * Whether to disable migrations for this table
		 * @default false
		 */
		disableMigrations?: boolean | undefined;
		/**
		 * The order of the table
		 */
		order?: number | undefined;
	}
>;

export interface SecondaryStorage {
	/**
	 *
	 * @param key - Key to get
	 * @returns - Value of the key
	 */
	get: (key: string) => Awaitable<unknown>;
	set: (
		/**
		 * Key to store
		 */
		key: string,
		/**
		 * Value to store
		 */
		value: string,
		/**
		 * Time to live in seconds
		 */
		ttl?: number | undefined,
	) => Awaitable<void | null | unknown>;
	/**
	 *
	 * @param key - Key to delete
	 */
	delete: (key: string) => Awaitable<void | null | string>;
}
