import { AnyVariables, DocumentInput, OperationContext, OperationResult, Operation, CombinedError, Exchange } from '@urql/core';

/** Utilities to use while refreshing authentication tokens. */
interface AuthUtilities {
    /** Sends a mutation to your GraphQL API, bypassing earlier exchanges and authentication.
     *
     * @param query - a GraphQL document containing the mutation operation that will be executed.
     * @param variables - the variables used to execute the operation.
     * @param context - {@link OperationContext} options that'll be used in future exchanges.
     * @returns A `Promise` of an {@link OperationResult} for the GraphQL mutation.
     *
     * @remarks
     * The `mutation()` utility method is useful when your authentication requires you to make a GraphQL mutation
     * request to update your authentication tokens. In these cases, you likely wish to bypass prior exchanges and
     * the authentication in the `authExchange` itself.
     *
     * This method bypasses the usual mutation flow of the `Client` and instead issues the mutation as directly
     * as possible. This also means that it doesn’t carry your `Client`'s default {@link OperationContext}
     * options, so you may have to pass them again, if needed.
     */
    mutate<Data = any, Variables extends AnyVariables = AnyVariables>(query: DocumentInput<Data, Variables>, variables: Variables, context?: Partial<OperationContext>): Promise<OperationResult<Data>>;
    /** Adds additional HTTP headers to an `Operation`.
     *
     * @param operation - An {@link Operation} to add headers to.
     * @param headers - The HTTP headers to add to the `Operation`.
     * @returns The passed {@link Operation} with the headers added to it.
     *
     * @remarks
     * The `appendHeaders()` utility method is useful to add additional HTTP headers
     * to an {@link Operation}. It’s a simple convenience function that takes
     * `operation.context.fetchOptions` into account, since adding headers for
     * authentication is common.
     */
    appendHeaders(operation: Operation, headers: Record<string, string>): Operation;
}
/** Configuration for the `authExchange` returned by the initializer function you write. */
interface AuthConfig {
    /** Called for every operation to add authentication data to your operation.
     *
     * @param operation - An {@link Operation} that needs authentication tokens added.
     * @returns a new {@link Operation} with added authentication tokens.
     *
     * @remarks
     * The {@link authExchange} will call this function you provide and expects that you
     * add your authentication tokens to your operation here, on the {@link Operation}
     * that is returned.
     *
     * Hint: You likely want to modify your `fetchOptions.headers` here, for instance to
     * add an `Authorization` header.
     */
    addAuthToOperation(operation: Operation): Operation;
    /** Called before an operation is forwaded onwards to make a request.
     *
     * @param operation - An {@link Operation} that needs authentication tokens added.
     * @returns a boolean, if true, authentication must be refreshed.
     *
     * @remarks
     * The {@link authExchange} will call this function before an {@link Operation} is
     * forwarded onwards to your following exchanges.
     *
     * When this function returns `true`, the `authExchange` will call
     * {@link AuthConfig.refreshAuth} before forwarding more operations
     * to prompt you to update your authentication tokens.
     *
     * Hint: If you define this function, you can use it to check whether your authentication
     * tokens have expired.
     */
    willAuthError?(operation: Operation): boolean;
    /** Called after receiving an operation result to check whether it has failed with an authentication error.
     *
     * @param error - A {@link CombinedError} that a result has come back with.
     * @param operation - The {@link Operation} of that has failed.
     * @returns a boolean, if true, authentication must be refreshed.
     *
     * @remarks
     * The {@link authExchange} will call this function if it sees an {@link OperationResult}
     * with a {@link CombinedError} on it, implying that it may have failed due to an authentication
     * error.
     *
     * When this function returns `true`, the `authExchange` will call
     * {@link AuthConfig.refreshAuth} before forwarding more operations
     * to prompt you to update your authentication tokens.
     * Afterwards, this operation will be retried once.
     *
     * Hint: You should define a function that detects your API’s authentication
     * errors, e.g. using `result.extensions`.
     */
    didAuthError(error: CombinedError, operation: Operation): boolean;
    /** Called to refresh the authentication state.
     *
     * @remarks
     * The {@link authExchange} will call this function if either {@link AuthConfig.willAuthError}
     * or {@link AuthConfig.didAuthError} have returned `true` prior, which indicates that the
     * authentication state you hold has expired or is out-of-date.
     *
     * When this function is called, you should refresh your authentication state.
     * For instance, if you have a refresh token and an access token, you should rotate
     * these tokens with your API by sending the refresh token.
     *
     * Hint: You can use the {@link fetch} API here, or use {@link AuthUtilities.mutate}
     * if your API requires a GraphQL mutation to refresh your authentication state.
     */
    refreshAuth(): Promise<void>;
}
/** Creates an `Exchange` handling control flow for authentication.
 *
 * @param init - An initializer function that returns an {@link AuthConfig} wrapped in a `Promise`.
 * @returns the created authentication {@link Exchange}.
 *
 * @remarks
 * The `authExchange` is used to create an exchange handling authentication and
 * the control flow of refresh authentication.
 *
 * You must pass an initializer function, which receives {@link AuthUtilities} and
 * must return an {@link AuthConfig} wrapped in a `Promise`.
 * When this exchange is used in your `Client`, it will first call your initializer
 * function, which gives you an opportunity to get your authentication state, e.g.
 * from local storage.
 *
 * You may then choose to validate this authentication state and update it, and must
 * then return an {@link AuthConfig}.
 *
 * This configuration defines how you add authentication state to {@link Operation | Operations},
 * when your authentication state expires, when an {@link OperationResult} has errored
 * with an authentication error, and how to refresh your authentication state.
 *
 * @example
 * ```ts
 * authExchange(async (utils) => {
 *   let token = localStorage.getItem('token');
 *   let refreshToken = localStorage.getItem('refreshToken');
 *   return {
 *     addAuthToOperation(operation) {
 *       return utils.appendHeaders(operation, {
 *         Authorization: `Bearer ${token}`,
 *       });
 *     },
 *     didAuthError(error) {
 *       return error.graphQLErrors.some(e => e.extensions?.code === 'FORBIDDEN');
 *     },
 *     async refreshAuth() {
 *       const result = await utils.mutate(REFRESH, { token });
 *       if (result.data?.refreshLogin) {
 *         token = result.data.refreshLogin.token;
 *         refreshToken = result.data.refreshLogin.refreshToken;
 *         localStorage.setItem('token', token);
 *         localStorage.setItem('refreshToken', refreshToken);
 *       }
 *     },
 *   };
 * });
 * ```
 */
declare function authExchange(init: (utilities: AuthUtilities) => Promise<AuthConfig>): Exchange;

export { AuthConfig, AuthUtilities, authExchange };
