import { ConfigError } from "@distilled.cloud/core/errors";
import * as Config from "effect/Config";
import * as Context from "effect/Context";
import * as Data from "effect/Data";
import * as Effect from "effect/Effect";
import * as Layer from "effect/Layer";
import * as Option from "effect/Option";
import * as Redacted from "effect/Redacted";
export const DEFAULT_API_BASE_URL = "https://api.cloudflare.com/client/v4";
const CREDENTIAL_REFRESH_WINDOW_MS = 5 * 60 * 1000;
export class OAuthRefreshError extends Data.TaggedError("CloudflareOAuthRefreshError") {
}
export class Credentials extends Context.Service()("CloudflareCredentials") {
}
const resolveApiBaseUrl = (apiBaseUrl) => apiBaseUrl ?? DEFAULT_API_BASE_URL;
export const apiTokenCredentials = (config) => ({
    type: "apiToken",
    apiToken: Redacted.make(config.apiToken),
    apiBaseUrl: resolveApiBaseUrl(config.apiBaseUrl),
});
export const apiKeyCredentials = (config) => ({
    type: "apiKey",
    apiKey: Redacted.make(config.apiKey),
    email: config.email,
    apiBaseUrl: resolveApiBaseUrl(config.apiBaseUrl),
});
export const oauthCredentials = (config, apiBaseUrl) => ({
    type: "oauth",
    accessToken: Redacted.make(config.accessToken),
    refreshToken: config.refreshToken
        ? Redacted.make(config.refreshToken)
        : undefined,
    expiresAt: config.expiresAt,
    apiBaseUrl: resolveApiBaseUrl(apiBaseUrl),
});
const getRefreshAt = (credentials) => {
    if (credentials.type !== "oauth" || credentials.expiresAt === undefined) {
        return Number.POSITIVE_INFINITY;
    }
    return credentials.expiresAt <= Date.now() + CREDENTIAL_REFRESH_WINDOW_MS
        ? credentials.expiresAt
        : credentials.expiresAt - CREDENTIAL_REFRESH_WINDOW_MS;
};
const isExpired = (expiresAt) => expiresAt !== undefined &&
    Date.now() >= expiresAt - CREDENTIAL_REFRESH_WINDOW_MS;
const createCachedCredentialsEffect = (resolve) => {
    let cachedCreds;
    let refreshAt = 0;
    return Effect.suspend(() => {
        const now = Date.now();
        if (cachedCreds && now < refreshAt) {
            return Effect.succeed(cachedCreds);
        }
        return Effect.map(resolve, (credentials) => {
            cachedCreds = credentials;
            refreshAt = getRefreshAt(credentials);
            return credentials;
        });
    });
};
const wrapOAuthError = (message, effect) => effect.pipe(Effect.mapError((cause) => new OAuthRefreshError({
    message,
    cause,
})));
export const fromApiToken = (config) => Layer.succeed(Credentials, Effect.succeed(apiTokenCredentials(config)));
export const fromApiKey = (config) => Layer.succeed(Credentials, Effect.succeed(apiKeyCredentials(config)));
export const fromOAuth = (provider) => {
    let currentCredentials;
    const resolve = Effect.gen(function* () {
        const loadedCredentials = currentCredentials ??
            (yield* wrapOAuthError("Failed to load Cloudflare OAuth credentials.", provider.load));
        const credentials = isExpired(loadedCredentials.expiresAt)
            ? yield* wrapOAuthError("Failed to refresh Cloudflare OAuth credentials.", provider.refresh(loadedCredentials))
            : loadedCredentials;
        currentCredentials = credentials;
        return oauthCredentials(credentials, provider.apiBaseUrl);
    });
    return Layer.succeed(Credentials, createCachedCredentialsEffect(resolve));
};
const fromConfigError = (message) => () => new ConfigError({
    message,
});
const envConfig = Config.all({
    apiToken: Config.option(Config.string("CLOUDFLARE_API_TOKEN")),
    apiKey: Config.option(Config.string("CLOUDFLARE_API_KEY")),
    email: Config.option(Config.string("CLOUDFLARE_EMAIL")),
    apiBaseUrl: Config.string("CLOUDFLARE_API_BASE_URL").pipe(Config.withDefault(DEFAULT_API_BASE_URL)),
});
export const resolveFromEnv = Effect.gen(function* () {
    const config = yield* envConfig.pipe(Effect.mapError(fromConfigError("Failed to load Cloudflare credentials from config")));
    const apiToken = Option.getOrUndefined(config.apiToken);
    const apiKey = Option.getOrUndefined(config.apiKey);
    const email = Option.getOrUndefined(config.email);
    const apiBaseUrl = config.apiBaseUrl;
    if (apiToken) {
        return apiTokenCredentials({ apiToken, apiBaseUrl });
    }
    if (apiKey && email) {
        return apiKeyCredentials({ apiKey, email, apiBaseUrl });
    }
    if (apiKey && !email) {
        return yield* new ConfigError({
            message: "CLOUDFLARE_EMAIL environment variable is required when using CLOUDFLARE_API_KEY",
        });
    }
    if (!apiKey && email) {
        return yield* new ConfigError({
            message: "CLOUDFLARE_API_KEY environment variable is required when using CLOUDFLARE_EMAIL",
        });
    }
    return yield* new ConfigError({
        message: "Either CLOUDFLARE_API_TOKEN or CLOUDFLARE_API_KEY+CLOUDFLARE_EMAIL environment variables are required",
    });
});
export const fromEnv = () => Layer.succeed(Credentials, resolveFromEnv);
export const CredentialsFromEnv = fromEnv();
export const formatHeaders = (credentials) => {
    switch (credentials.type) {
        case "apiKey":
            return {
                "X-Auth-Key": Redacted.value(credentials.apiKey),
                "X-Auth-Email": credentials.email,
            };
        case "apiToken":
            return {
                Authorization: `Bearer ${Redacted.value(credentials.apiToken)}`,
            };
        case "oauth":
            return {
                Authorization: `Bearer ${Redacted.value(credentials.accessToken)}`,
            };
    }
};
//# sourceMappingURL=credentials.js.map