import type { GenericEndpointContext, LiteralString } from "@better-auth/core";
import type { InferOptionSchema, Session, User } from "better-auth/types";
import type { JWTPayload } from "jose";
import type { schema } from "../schema";
import type { Awaitable } from "./helpers";
import type { GrantType } from "./oauth";

export type {
	AuthServerMetadata,
	OAuthClient,
	OIDCMetadata,
	ResourceServerMetadata,
} from "./oauth";

export type StoreTokenType =
	| "access_token"
	| "refresh_token"
	| "authorization_code";

type InternallySupportedScopes =
	| "openid"
	| "profile"
	| "email"
	| "offline_access";
export type Scope = LiteralString | InternallySupportedScopes;
export type Prompt = "none" | "consent" | "login" | "create" | "select_account";
export type AuthorizePrompt =
	| Prompt
	| "login consent"
	| "select_account consent";

export interface OAuthOptions<
	Scopes extends readonly Scope[] = InternallySupportedScopes[],
> {
	/**
	 * Custom schema definitions
	 */
	schema?: InferOptionSchema<typeof schema>;
	/**
	 * The scopes that the client is allowed to request.
	 * Must contain "openid" to be considered an OIDC server,
	 * otherwise it is just an OAuth server.
	 *
	 * @see https://openid.net/specs/openid-connect-core-1_0.html#ScopeClaims
	 * @default
	 * ```ts
	 * ["openid", "profile", "email", "offline_access"]
	 * ```
	 */
	scopes?: Scopes;
	/**
	 * List of valid audiences if there are multiple.
	 *
	 * @default baseURL
	 * @example [
	 * 	"https://api.example.com",
	 * 	"https://api.example.com/mcp",
	 * ]
	 */
	validAudiences?: string[];
	/**
	 * Automatically cache trusted clients by client_id.
	 * Clients are cached at request.
	 *
	 * Additionally, cached trusted clients are immutable
	 * through the CRUD endpoints.
	 */
	cachedTrustedClients?: Set<string>;
	/**
	 * The amount of time in seconds that the access token is valid for.
	 *
	 * @default 3600 (1 hour) - Industry standard
	 */
	accessTokenExpiresIn?: number;
	/**
	 * The amount of time in seconds that a client
	 * credentials grant access token is valid for.
	 *
	 * @default 3600 (1 hour)
	 */
	m2mAccessTokenExpiresIn?: number;
	/**
	 * The amount of time in seconds that id token is valid for.
	 *
	 * @default 36000 (10 hours) - Recommended by the OIDC spec
	 */
	idTokenExpiresIn?: number;
	/**
	 * The amount of time in seconds that the refresh token is valid for.
	 * Typical industry standard is 30 days
	 *
	 * @default 2592000 (30 days)
	 */
	refreshTokenExpiresIn?: number;
	/**
	 * The amount of time in seconds that the authorization code is valid for.
	 *
	 * @default 600 (10 minutes) - Recommended by the OIDC spec
	 */
	codeExpiresIn?: number;
	/**
	 * Create access token expirations based on scope.
	 *
	 * This is useful for higher-privilege scopes that
	 * require shorter expiration times. The earliest
	 * expiration will take precedence. If not specified,
	 * the default will take place.
	 *
	 * Note: values should be lower than the defaults
	 * `accessTokenExpiresIn` and `m2mAccessTokenExpiresIn`
	 *
	 * @example
	 * { "write:payments": "5m", "read:payments": "30m" }
	 */
	scopeExpirations?: {
		[K in Scopes[number]]?: number | string | Date;
	};
	/**
	 * Allows /oauth2/public-client-prelogin endpoint to be
	 * requestable prior to login via a valid oauth_query.
	 */
	allowPublicClientPrelogin?: boolean;
	/**
	 * Allow unauthenticated dynamic client registration.
	 *
	 * Support for `allowUnauthenticatedClientRegistration` **will be deprecated**
	 * when the MCP protocol standardizes unauthenticated dynamic client registration.
	 * As of writing, both [Client ID Metadata Documents](https://github.com/modelcontextprotocol/modelcontextprotocol/issues/991)
	 * and [`software_statement` and `jwks_uri`](https://github.com/modelcontextprotocol/modelcontextprotocol/issues/1032) are under debate.
	 *
	 * @default false
	 */
	allowUnauthenticatedClientRegistration?: boolean;
	/**
	 * Allow dynamic client registration.
	 *
	 * @default false
	 */
	allowDynamicClientRegistration?: boolean;
	/**
	 * List of scopes for newly registered clients
	 * if not requested.
	 *
	 * For scopes that shall automatically adapt to your scopes
	 * list in the future (ie scopes: undefined), create that client
	 * using the server's `createOAuthClient` function.
	 *
	 * @default scopes
	 */
	clientRegistrationDefaultScopes?: Scopes;
	/**
	 * List of scopes for allowed clients in addition to
	 * those listed in the default scope. Finalized allowed list is
	 * the union of the default scopes and this list.
	 *
	 * If both clientRegistrationDefaultScopes and this
	 * are undefined, only scopes listed in the scopes option
	 * are allowed.
	 *
	 * @default - clientRegistrationDefaultScopes
	 */
	clientRegistrationAllowedScopes?: Scopes;
	/**
	 * How long a dynamically created confidential client
	 * should last for.
	 *
	 * - If a `number` is passed as an argument it is used as the claim directly.
	 * - If a `Date` instance is passed as an argument it is converted to unix timestamp and used as the
	 *   claim.
	 * - If a `string` is passed as an argument it is resolved to a time span, and then added to the
	 *   current unix timestamp and used as the claim.
	 *
	 * Format used for time span should be a number followed by a unit, such as "5 minutes" or "1
	 * day".
	 *
	 * Valid units are: "sec", "secs", "second", "seconds", "s", "minute", "minutes", "min", "mins",
	 * "m", "hour", "hours", "hr", "hrs", "h", "day", "days", "d", "week", "weeks", "w", "year",
	 * "years", "yr", "yrs", and "y". It is not possible to specify months. 365.25 days is used as an
	 * alias for a year.
	 *
	 * If the string is suffixed with "ago", or prefixed with a "-", the resulting time span gets
	 * subtracted from the current unix timestamp. A "from now" suffix can also be used for
	 * readability when adding to the current unix timestamp.
	 *
	 * @default - undefined (does not expire)
	 */
	clientRegistrationClientSecretExpiration?: number | string | Date;
	/**
	 * Returns the reference id which owns the oauth clients.
	 *
	 * For example, it can be an organization, team, etc.
	 * When provided, user_id of the client will be undefined
	 * and the owner is defined under the field `reference_id`.
	 *
	 * With the organization plugin: @example ({ session }) => {
	 * 	return session?.activeOrganizationId;
	 * }
	 */
	clientReference?: (context: {
		user?: User & Record<string, unknown>;
		session?: Session & Record<string, unknown>;
	}) => Awaitable<string | undefined>;
	/**
	 * RBAC on OAuth Clients.
	 *
	 * Provides context to help determine if a user can perform
	 * a specific action.
	 */
	clientPrivileges?: (context: {
		headers: Headers;
		action: "create" | "read" | "update" | "delete" | "list" | "rotate";
		user?: User & Record<string, unknown>;
		session?: Session & Record<string, unknown>;
	}) => Awaitable<boolean | undefined>;
	/**
	 * List default scopes when using the token endpoint's
	 * grant type "client_credentials". This is used
	 * only when oauthClients are stored in the database
	 * without a scope and you do not want all `scopes` to be given.
	 *
	 * @default undefined
	 */
	clientCredentialGrantDefaultScopes?: Scopes;
	/**
	 * Grant types supported by the token endpoint
	 *
	 * @default
	 * ["authorization_code", "client_credentials", "refresh_token"]
	 */
	grantTypes?: GrantType[];
	/**
	 * The URL to the login page. This is used if the client requests the `login`
	 * prompt.
	 */
	loginPage: string;
	/**
	 * A URL to the consent page where the user will be redirected if the client
	 * requests consent.
	 *
	 * After the user consents, they should be redirected by the client to the
	 * `redirect_uri` with the authorization code.
	 *
	 * When the server redirects the user to the consent page, it will include the
	 * following query parameters:
	 *
	 * - `client_id` - The ID of the client.
	 * - `scope` - The requested scopes.
	 * - `code` - The authorization code.
	 *
	 * once the user consents, you need to call the `/oauth2/consent` endpoint
	 * with the code and `accept: true` to complete the authorization. Which will
	 * then return the client to the `redirect_uri` with the authorization code.
	 *
	 * @example
	 * ```ts
	 * consentPage: "/consent"
	 * ```
	 */
	consentPage: string;
	/**
	 * Sign Up page settings associated with `prompt: "create"`
	 * @see https://openid.net/specs/openid-connect-prompt-create-1_0.html
	 */
	signup?: {
		/**
		 * A URL to the Sign Up page where the user will be redirected
		 * to continue a signup flow.
		 *
		 * Upon completion of signup, you need to call the `/oauth2/continue`
		 * with `created: true` to continue the login flow.
		 *
		 * @default loginPage
		 * @example `/sign-up`
		 */
		page?: string;
		/**
		 * To add registration steps, specify the page(s) to redirect to.
		 *
		 * Note: the account would need to be logged in (or selected)
		 * to specify steps.
		 *
		 * If true, user with redirect to `page`.
		 * If string, user with redirect to the page specified by string.
		 * If false, user has completed registration and will continue auth flow.
		 *
		 * @param context
		 */
		shouldRedirect?: (context: {
			headers: Headers;
			user: User & Record<string, unknown>;
			session: Session & Record<string, unknown>;
			scopes: Scopes;
		}) => Awaitable<boolean | string>;
	};
	/**
	 * Select Account page settings associated with `prompt: "select_account"`
	 */
	selectAccount?: {
		/**
		 * A URL to the account selection page where the user will be redirected if
		 * the user must select an account (eg. multi-session).
		 *
		 * Once the user selects an account, you need to call the `/oauth2/continue`
		 * with `selected: true` to continue the login flow.
		 *
		 * @default loginPage
		 */
		page?: string;
		/**
		 * Checks to see if an account needs selection
		 * for the `/oauth2/authorize` endpoint.
		 *
		 * @returns
		 * - `true`: account is not selected and needs selection
		 * - `false`: intended user or account already selected
		 */
		shouldRedirect: (context: {
			headers: Headers;
			user: User & Record<string, unknown>;
			session: Session & Record<string, unknown>;
			scopes: Scopes;
		}) => Awaitable<boolean>;
	};
	/**
	 * Post login page settings
	 */
	postLogin?: {
		/**
		 * The page `shouldRedirect` should redirect to.
		 */
		page: string;
		/**
		 * A value to tie to the consent reference_id.
		 *
		 * Note that YOU must fail in this function if the requested
		 * scope doesn't have a reference id and it should.
		 */
		consentReferenceId: (context: {
			user: User & Record<string, unknown>;
			session: Session & Record<string, unknown>;
			scopes: Scopes;
		}) => Awaitable<string | undefined>;
		/**
		 * After login and before consent, request the user to
		 * select an additional choice for `/oauth2/authorize`.
		 * For example, allow selection of an organization or team.
		 *
		 * Upon selection of a specific account, use `/oauth2/continue`
		 * with `postLogin: true` to continue the login flow.
		 *
		 * @returns
		 * - `true`: account is not selected and needs selection
		 * - `false`: intended user or account selected
		 */
		shouldRedirect: (context: {
			headers: Headers;
			user: User & Record<string, unknown>;
			session: Session & Record<string, unknown>;
			scopes: Scopes;
		}) => Awaitable<boolean>;
	};
	/**
	 * Format your refresh tokens the returned to oauth clients.
	 * For example with JWE encryption/decryption logic.
	 *
	 * If you changed the format after production deployment,
	 * ensure that the prior version can still be decoded.
	 *
	 * NOTE: `prefix.refreshToken` is internally handled,
	 * so `token` only contains the stored database token.
	 */
	formatRefreshToken?: {
		/**
		 * Custom session token format sent to client.
		 */
		encrypt: (token: string, sessionId?: string) => Awaitable<string>;
		/**
		 * Decodes the custom session token.
		 *
		 * @returns {string | undefined} sessionId - if returned,
		 * should be same as the one received in `encode`.
		 * There is an added benefit that updates to the session occur
		 * via id instead of token.
		 * @returns {string} token - should be same as the one
		 * received in `encode`
		 */
		decrypt: (
			token: string,
		) => Awaitable<{ sessionId?: string; token: string }>;
	};
	/**
	 * Store the client secret in your database in a secure way
	 * Note: This will not affect the client secret sent to the user,
	 * it will only affect the client secret stored in your database
	 *
	 * When disableJwtPlugin = false (recommended):
	 * - "hashed" - The client secret is hashed using the `hash` function.
	 * - {
	 * 	hash: (clientSecret: string) => Awaitable<string>,
	 * 	verify?: (clientSecret: string, storedHash: string) => Awaitable<boolean>
	 * } - A function that hashes the client secret.
	 *
	 * When disableJwtPlugin = true:
	 * - "encrypted" - The client secret is encrypted using the `encrypt` function.
	 * - {
	 * 	encrypt: (clientSecret: string) => Awaitable<string>,
	 * 	decrypt: (storedSecret: string) => Awaitable<string>
	 * } - A function that encrypts and decrypts the client secret.
	 *
	 * @default
	 * options.disableJwtPlugin ? "encrypted" : "hashed"
	 */
	storeClientSecret?:
		| "hashed"
		| "encrypted"
		| {
				hash: (clientSecret: string) => Awaitable<string>;
				verify?: (
					clientSecret: string,
					storedHash: string,
				) => Awaitable<boolean>;
		  }
		| {
				encrypt: (clientSecret: string) => Awaitable<string>;
				decrypt: (storedSecret: string) => Awaitable<string>;
		  };
	/**
	 * Storage method of opaque access tokens and refresh tokens on your database.
	 *
	 * - "hashed" - The client secret is hashed using the `hash` function.
	 * - {
	 * 	hash: (token: string, type: StoreTokenType) => Awaitable<string>
	 * } - A function that hashes the token
	 *
	 * @default "hashed"
	 */
	storeTokens?:
		| "hashed"
		| { hash: (token: string, type: StoreTokenType) => Awaitable<string> };
	/**
	 * Custom claims provided at the OIDC `userinfo` endpoint.
	 *
	 * @param info - context that may be useful when creating custom claims
	 * @returns Additional claims for userinfo request
	 */
	customUserInfoClaims?: (info: {
		/** The user object */
		user: User & Record<string, unknown>;
		/** The scopes from the access token used
		 * in the /userinfo request (matches jwt.scopes) */
		scopes: Scopes;
		/** The access token payload used in the /userinfo request */
		jwt: JWTPayload;
	}) => Awaitable<Record<string, any>>;
	/**
	 * Custom claims attached to OIDC id tokens.
	 *
	 * To remain OIDC-compliant, claims should be
	 * namespaced with a URI. For example, a site
	 * example.com should namespace an organization at
	 * https://example.com/organization.
	 *
	 * @param info - context that may be useful when creating custom claims
	 */
	customIdTokenClaims?: (info: {
		/** The user object if token is associated to a user. */
		user: User & Record<string, unknown>;
		/** Scopes granted for this token */
		scopes: Scopes;
		/** oAuthClient metadata */
		metadata?: Record<string, any>;
	}) => Awaitable<Record<string, any>>;
	/**
	 * Custom claims attached to access tokens.
	 *
	 * Claims are added for both the token and introspect endpoints.
	 *
	 * Use the user and referenceId fields to fetch
	 * for membership roles/permissions to attach for the token.
	 * Note that scopes are those that requested,
	 * permissions are what the the user can actually do which
	 * must be done in this function.
	 *
	 * @param info - context that may be useful when creating custom claims
	 */
	customAccessTokenClaims?: (info: {
		/** The user object if token is associated to a user. Null if user doesn't exist. Undefined if user not applicable. */
		user?: (User & Record<string, unknown>) | null;
		/** reference of the consent/authorization */
		referenceId?: string;
		/** Scopes granted for this token */
		scopes: Scopes;
		/** The resource requesting. Provided by the token endpoint. */
		resource?: string;
		/** oAuthClient metadata */
		metadata?: Record<string, any>;
	}) => Awaitable<Record<string, any>>;
	/**
	 * Custom fields to include in the token response body.
	 *
	 * Unlike `customAccessTokenClaims` (which adds claims inside the JWT payload),
	 * this adds fields to the JSON response envelope alongside `access_token`,
	 * `token_type`, etc. Standard OAuth fields (`access_token`, `token_type`,
	 * `expires_in`, `expires_at`, `refresh_token`, `scope`, `id_token`) cannot
	 * be overridden.
	 *
	 * @param info - context that may be useful when creating custom fields
	 */
	customTokenResponseFields?: (info: {
		/** The grant type being processed */
		grantType: GrantType;
		/**
		 * The user, if applicable.
		 * Undefined for `client_credentials` (M2M, no user).
		 * Always present for `authorization_code` and `refresh_token`.
		 */
		user?: (User & Record<string, unknown>) | null;
		/** Scopes granted for this token */
		scopes: Scopes;
		/** oAuthClient metadata */
		metadata?: Record<string, any>;
		/**
		 * The authorization code verification value.
		 * Only present for `authorization_code` grant. Contains the original
		 * authorization request parameters (`query`), `referenceId`, `sessionId`, etc.
		 */
		verificationValue?: VerificationValue;
	}) => Awaitable<Record<string, unknown>>;
	/**
	 * Overwrite specific /.well-known/openid-configuration
	 * values so they are not available publically.
	 * This may be important if not all clients need specific scopes.
	 */
	advertisedMetadata?: {
		/**
		 * Advertised scopes_supported located at /.well-known/openid-configuration
		 *
		 * All values must be found in the scope field
		 */
		scopes_supported?: Scopes;
		/**
		 * Advertised claims_supported located at /.well-known/openid-configuration
		 *
		 * Internally supported claims:
		 * ["sub", "iss", "aud", "exp", "iat", "sid", "scope", "azp"]
		 */
		claims_supported?: string[];
	};
	/**
	 * Attach prefixes to returned token types.
	 * NOTE: The prefix is not stored in the database.
	 *
	 * Useful when also using the [API Key Plugin](../api-key/index.ts)
	 * or Secret Scanners (ie Github Secret Scanning, GitGuardian, Trufflehog).
	 *
	 * We recommend to append an underscore to make it more identifiable.
	 */
	prefix?: {
		/**
		 * Prefix on returned opaque access tokens.
		 *
		 * Additionally, we recommend you add the prefix prior to the first deployment
		 * otherwise you must utilize this with `generateOpaqueAccessToken` (storing the full
		 * encoded value on the database).
		 *
		 * @example "domain_at_"
		 * @default undefined
		 */
		opaqueAccessToken?: string;
		/**
		 * Prefix on returned refresh tokens.
		 *
		 * Additionally, we recommend you add the prefix prior to the first deployment
		 * otherwise you must utilize this with `generateRefreshToken` (storing the full
		 * encoded value on the database).
		 *
		 * @example "domain_rt_"
		 * @default undefined
		 */
		refreshToken?: string;
		/**
		 * Prefix on returned client secrets.
		 *
		 * Additionally, we recommend you add the prefix prior to the first deployment
		 * otherwise you must utilize this with `generateClientSecret` (storing the full
		 * encoded value on the database).
		 *
		 * @example "domain_cs_"
		 * @default undefined
		 */
		clientSecret?: string;
	};
	/**
	 * Custom function to generate a client ID.
	 *
	 * @default
	 * generateRandomString(32, "A-Z", "a-z")
	 */
	generateClientId?: () => string;
	/**
	 * Custom function to generate a client secret.
	 *
	 * @default
	 * generateRandomString(32, "A-Z", "a-z")
	 */
	generateClientSecret?: () => string;
	/**
	 * Generate a unique access token to save on the database.
	 *
	 * @default
	 * generateRandomString(32, "A-Z", "a-z")
	 */
	generateOpaqueAccessToken?: () => Awaitable<string>;
	/**
	 * Generate a unique refresh token to save on the database.
	 *
	 * @default
	 * generateRandomString(32, "A-Z", "a-z")
	 */
	generateRefreshToken?: () => Awaitable<string>;
	/**
	 * Confirmations that individually silences specific well-known endpoint
	 * configuration warnings.
	 *
	 * Only set these specific values if you see the error as they
	 * are configuration specific.
	 */
	silenceWarnings?: {
		/**
		 * Config warning for `/.well-known/oauth-authorization-server/[issuer-path]`
		 *
		 * @default false
		 */
		oauthAuthServerConfig?: boolean;
		/**
		 * Config warning for `[issuer-path]/.well-known/openid-configuration`
		 *
		 * @default false
		 */
		openidConfig?: boolean;
	};
	/**
	 * By default, access and id tokens can be issued and verified
	 * through the JWT plugin.
	 *
	 * You can disable the JWT requirement in which access tokens
	 * will always be opaque and id tokens are always signed
	 * with HS256 using the client secret.
	 *
	 * @default false
	 */
	disableJwtPlugin?: boolean;
	/**
	 * Rate limit configuration for OAuth endpoints.
	 *
	 * Each endpoint can be configured with a `window` (in seconds) and `max` requests.
	 * Set to `false` to disable rate limiting for a specific endpoint.
	 *
	 * @default
	 * ```ts
	 * {
	 *   token: { window: 60, max: 20 },
	 *   authorize: { window: 60, max: 30 },
	 *   introspect: { window: 60, max: 100 },
	 *   revoke: { window: 60, max: 30 },
	 *   register: { window: 60, max: 5 },
	 *   userinfo: { window: 60, max: 60 },
	 * }
	 * ```
	 */
	rateLimit?: {
		/**
		 * Rate limit for /oauth2/token endpoint
		 * @default { window: 60, max: 20 }
		 */
		token?: { window: number; max: number } | false;
		/**
		 * Rate limit for /oauth2/authorize endpoint
		 * @default { window: 60, max: 30 }
		 */
		authorize?: { window: number; max: number } | false;
		/**
		 * Rate limit for /oauth2/introspect endpoint
		 * @default { window: 60, max: 100 }
		 */
		introspect?: { window: number; max: number } | false;
		/**
		 * Rate limit for /oauth2/revoke endpoint
		 * @default { window: 60, max: 30 }
		 */
		revoke?: { window: number; max: number } | false;
		/**
		 * Rate limit for /oauth2/register endpoint
		 * @default { window: 60, max: 5 }
		 */
		register?: { window: number; max: number } | false;
		/**
		 * Rate limit for /oauth2/userinfo endpoint
		 * @default { window: 60, max: 60 }
		 */
		userinfo?: { window: number; max: number } | false;
	};
	/**
	 * Secret used to compute pairwise subject identifiers (HMAC-SHA256).
	 * When set, clients with `subject_type: "pairwise"` receive unique,
	 * unlinkable `sub` values per sector identifier.
	 *
	 * @see https://openid.net/specs/openid-connect-core-1_0.html#PairwiseAlg
	 */
	pairwiseSecret?: string;
	/**
	 * Resolves a `request_uri` at the authorize endpoint (PAR support).
	 *
	 * When the authorize endpoint receives a `request_uri` parameter, this callback
	 * resolves it to the original authorization parameters. Return null if the URI
	 * is invalid or expired.
	 */
	requestUriResolver?: (input: {
		requestUri: string;
		clientId: string;
		ctx: GenericEndpointContext;
	}) => Promise<Record<string, string> | null>;
}

export interface OAuthAuthorizationQuery {
	/**
	 * The response type.
	 * - "code": authorization code flow.
	 * Optional in the query when using request_uri (PAR) — resolved from stored params.
	 */
	// NEVER SUPPORT "token" or "id_token" - depreciated in oAuth2.1
	response_type?: "code";
	/**
	 * PAR request_uri. When present, other params are resolved from the stored request.
	 */
	request_uri?: string;
	/**
	 * The redirect URI for the client. Must be one of the registered redirect URLs for the client.
	 */
	redirect_uri: string;
	/**
	 * The scope of the request. Must be a space-separated list of case sensitive strings.
	 *
	 * - "openid" is required for most requests to obtain user id (ie sub)
	 * - "profile" is required for requests that require user profile information.
	 * - "email" is required for requests that require user email information.
	 * - "offline_access" is required for requests that require a refresh token.
	 */
	scope?: string;
	/**
	 * Opaque value used to maintain state between the request and the callback. Typically,
	 * Cross-Site Request Forgery (CSRF, XSRF) mitigation is done by cryptographically binding the
	 * value of this parameter with a browser cookie.
	 *
	 * Note: Better Auth stores the state in a database instead of a cookie. - This is to minimize
	 * the complication with native apps and other clients that may not have access to cookies.
	 */
	state: string;
	/**
	 * The client ID. Must be the ID of a registered client.
	 */
	client_id: string;
	/**
	 * The prompt parameter is used to specify the type of user interaction that is required.
	 */
	prompt?: AuthorizePrompt;
	/**
	 * The display parameter is used to specify how the authorization server displays the
	 * authentication and consent user interface pages to the end user.
	 */
	display?: "page" | "popup" | "touch" | "wap";
	/**
	 * End-User's preferred languages and scripts for the user interface, represented as a
	 * space-separated list of BCP47 [RFC5646] language tag values, ordered by preference. For
	 * instance, the value "fr-CA fr en" represents a preference for French as spoken in Canada,
	 * then French (without a region designation), followed by English (without a region
	 * designation).
	 *
	 * Better Auth does not support this parameter yet. It'll not throw an error if it's provided,
	 *
	 * 🏗️ currently not implemented
	 */
	ui_locales?: string;
	/**
	 * The maximum authentication age.
	 *
	 * Specifies the allowable elapsed time in seconds since the last time the End-User was
	 * actively authenticated by the provider. If the elapsed time is greater than this value, the
	 * provider MUST attempt to actively re-authenticate the End-User.
	 *
	 * Note that max_age=0 is equivalent to prompt=login.
	 */
	max_age?: number;
	/**
	 * Requested Authentication Context Class Reference values.
	 *
	 * Space-separated string that
	 * specifies the acr values that the Authorization Server is being requested to use for
	 * processing this Authentication Request, with the values appearing in order of preference.
	 * The Authentication Context Class satisfied by the authentication performed is returned as
	 * the acr Claim Value, as specified in Section 2. The acr Claim is requested as a Voluntary
	 * Claim by this parameter.
	 */
	acr_values?: string;
	/**
	 * Hint to the Authorization Server about the login identifier the End-User might use to log in
	 * (if necessary). This hint can be used by an RP if it first asks the End-User for their
	 * e-mail address (or other identifier) and then wants to pass that value as a hint to the
	 * discovered authorization service. It is RECOMMENDED that the hint value match the value used
	 * for discovery. This value MAY also be a phone number in the format specified for the
	 * phone_number Claim. The use of this parameter is left to the OP's discretion.
	 */
	login_hint?: string;
	/**
	 * ID Token previously issued by the Authorization Server being passed as a hint about the
	 * End-User's current or past authenticated session with the Client.
	 *
	 * 🏗️ currently not implemented
	 */
	id_token_hint?: string;
	/**
	 * Code challenge
	 */
	code_challenge?: string;
	/**
	 * Code challenge method used
	 */
	code_challenge_method?: "S256";
	/**
	 * String value used to associate a Client session with an ID Token, and to mitigate replay
	 * attacks. The value is passed through unmodified from the Authentication Request to the ID Token.
	 * If present in the ID Token, Clients MUST verify that the nonce Claim Value is equal to the
	 * value of the nonce parameter sent in the Authentication Request. If present in the
	 * Authentication Request, Authorization Servers MUST include a nonce Claim in the ID Token
	 * with the Claim Value being the nonce value sent in the Authentication Request.
	 */
	nonce?: string;
}

/**
 * Stored within the verification.value field
 * in JSON format.
 *
 * It is stored in JSON to prevent
 * direct searches by field on the db
 */
export interface VerificationValue {
	type: "authorization_code";
	query: OAuthAuthorizationQuery;
	sessionId: string;
	userId: string;
	referenceId?: string;
	authTime?: number;
}

/**
 * Client registered values as used within the plugin
 */
export interface SchemaClient<
	Scopes extends readonly Scope[] = InternallySupportedScopes[],
> {
	//---- Required ----//
	/**
	 * Client ID
	 *
	 * size 32
	 *
	 * as described on https://www.rfc-editor.org/rfc/rfc6749.html#section-2.2
	 */
	clientId: string;
	/**
	 * Client Secret
	 *
	 * A secret for the client, if required by the authorization server.
	 *
	 * size 32
	 */
	clientSecret?: string;
	/** Whether the client is disabled or not. */
	disabled?: boolean;
	/**
	 * Restricts scopes allowed for the client.
	 *
	 * If not defined, any scope can be requested.
	 */
	scopes?: Scopes;
	//---- Recommended client data ----//
	/** User who owns this client */
	userId?: string | null;
	/** Created time */
	createdAt?: Date;
	/** Last updated time */
	updatedAt?: Date;
	/** Expires time */
	expiresAt?: Date;
	//---- UI Metadata ----//
	/** The name of the client. */
	name?: string;
	/** Linkable uri of the client. */
	uri?: string;
	/** The icon of the client. */
	icon?: string;
	/** List of contacts for the client. */
	contacts?: string[];
	/** Client Terms of Service Uri */
	tos?: string;
	/** Client Privacy Policy Uri */
	policy?: string;
	//---- User Software Identifiers ----//
	softwareId?: string;
	softwareVersion?: string;
	softwareStatement?: string;
	//---- Authentication Metadata ----//
	/**
	 * List of registered redirect URLs. Must include the whole URL, including the protocol, port,
	 * and path.
	 *
	 * For example, `https://example.com/auth/callback`
	 */
	redirectUris?: string[];
	/**
	 * List of registered post-logout redirect URIs. Used for RP-Initiated Logout.
	 * Must include the whole URL, including the protocol, port, and path.
	 *
	 * For example, `https://example.com/logout/callback`
	 */
	postLogoutRedirectUris?: string[];
	tokenEndpointAuthMethod?:
		| "none"
		| "client_secret_basic"
		| "client_secret_post";
	grantTypes?: GrantType[];
	responseTypes?: "code"[];
	//---- RFC6749 Spec ----//
	/**
	 * Indicates whether the client is public or confidential.
	 * If public, refreshing tokens doesn't require
	 * a client_secret. Clients are considered confidential by default.
	 *
	 * Uses `token_endpoint_auth_method` field or `type` field to determine
	 *
	 * Described https://www.rfc-editor.org/rfc/rfc6749.html#section-2.1
	 *
	 * @default undefined
	 */
	public?: boolean;
	/**
	 * The client type
	 *
	 * Described https://www.rfc-editor.org/rfc/rfc6749.html#section-2.1
	 *
	 * - web - A web application (confidential client)
	 * - native - A mobile application (public client)
	 * - user-agent-based - A user-agent-based application (public client)
	 */
	type?: "web" | "native" | "user-agent-based";
	/**
	 * Whether this client requires PKCE for authorization code flow.
	 *
	 * @default true
	 *
	 * Note: PKCE is always required for public clients and when
	 * requesting offline_access scope, regardless of this setting.
	 */
	requirePKCE?: boolean;
	//---- All other metadata ----//
	/** Used to indicate if consent screen can be skipped */
	skipConsent?: boolean;
	/** Used to enable client to logout via the `/oauth2/end-session` endpoint */
	enableEndSession?: boolean;
	/** Subject identifier type: "public" (default) or "pairwise" */
	subjectType?: "public" | "pairwise";
	/** Reference to the owner of this client. Eg. Organization, Team, Profile */
	referenceId?: string;
	/**
	 * Additional metadata about the client.
	 */
	metadata?: string; // in JSON format
}

export interface OAuthOpaqueAccessToken<
	Scopes extends readonly Scope[] = InternallySupportedScopes[],
> {
	/**
	 * The opaque access token.
	 */
	token: string;
	/**
	 * The client ID of the client that requested the access token.
	 */
	clientId: string;
	/**
	 * The session ID the access token is associated with.
	 *
	 * Not available in client credentials grant
	 * where no user session is involved.
	 */
	sessionId?: string;
	/**
	 * The user ID the access token is associated with.
	 *
	 * Not available in client credentials grant
	 * where no user is involved.
	 */
	userId?: string;
	/**
	 * Reference Id of the consent/authorization.
	 *
	 * Not available in client credentials grant
	 * where no user is involved.
	 */
	referenceId?: string;
	/**
	 * The refresh token the access token is associated with.
	 *
	 * Not available without the "offline_access" scope
	 */
	refreshId?: string;
	/** The expiration date of the access token. */
	expiresAt: Date;
	/** The creation date of the access token. */
	createdAt: Date;
	/**
	 * Scope granted for the access token.
	 *
	 * Shall match the refreshId.scopes if refreshId is provided.
	 */
	scopes: Scopes;
}

/**
 * Refresh Token Database Schema
 */
export interface OAuthRefreshToken<
	Scopes extends readonly Scope[] = InternallySupportedScopes[],
> {
	token: string;
	sessionId: string;
	userId: string;
	referenceId?: string;
	clientId?: string;
	expiresAt: Date;
	createdAt: Date;
	/**
	 * When token was revoked. If set, token is considered a replay attack.
	 */
	revoked?: Date;
	/**
	 * The time the user originally authenticated.
	 * Persisted so refreshed ID tokens can include a correct `auth_time` claim.
	 */
	authTime?: Date;
	/**
	 * Scopes granted for this refresh token.
	 *
	 * Considered Immutable once granted.
	 */
	scopes: Scopes;
}

/**
 * Consent Database Schema
 */
export type OAuthConsent<
	Scopes extends readonly Scope[] = InternallySupportedScopes[],
> = {
	id: string;
	clientId: string;
	userId: string;
	referenceId?: string;
	scopes: Scopes;
	createdAt: Date;
	updatedAt: Date;
};
