import { alchemy } from "../alchemy.js";
import { Resource, ResourceKind } from "../resource.js";
import { AccountApiToken } from "./account-api-token.js";
import { CloudflareApiError } from "./api-error.js";
import { extractCloudflareResult } from "./api-response.js";
import { CloudflareApi, createCloudflareApi, } from "./api.js";
/**
 * Type guard for AiSearchToken
 */
export function isAiSearchToken(resource) {
    return (typeof resource === "object" &&
        resource !== null &&
        (resource[ResourceKind] === "cloudflare::AiSearchToken" ||
            resource.type === "ai_search_token"));
}
/**
 * Creates an AI Search token for accessing AI Search instances.
 *
 * AI Search tokens are used to authenticate with the AI Search API and provide
 * access to R2 buckets or other data sources for indexing.
 *
 * This resource automatically:
 * 1. Creates an account API token with AI Search Index Engine and R2 Storage Write permissions
 * 2. Registers that token with the AI Search service
 *
 * @example
 * // Create an AI Search token
 * const token = await AiSearchToken("my-token", {
 *   name: "docs-search-token",
 * });
 *
 * // Use the token with an AI Search instance
 * const search = await AiSearch("docs-search", {
 *   source: {
 *     type: "r2",
 *     bucket: myBucket,
 *     token: token,
 *   },
 * });
 *
 * @example
 * // Let AiSearch auto-create a token (recommended)
 * const search = await AiSearch("docs-search", {
 *   source: {
 *     type: "r2",
 *     bucket: myBucket,
 *   },
 * });
 */
export const AiSearchToken = Resource("cloudflare::AiSearchToken", async function (id, props) {
    const api = await createCloudflareApi(props);
    const tokenName = props.name ?? this.scope.createPhysicalName(id);
    if (this.phase === "delete") {
        if (this.output?.tokenId && props.delete !== false) {
            await deleteAiSearchToken(api, this.output.tokenId);
        }
        // The AccountApiToken will be cleaned up automatically by Alchemy's
        // resource lifecycle management since it's a child resource
        return this.destroy();
    }
    // For update, we can't really update tokens - just return existing
    if (this.phase === "update" && this.output?.tokenId) {
        return this.output;
    }
    // Create an account API token with AI Search + R2 permissions
    const accountToken = await AccountApiToken("account-token", {
        name: `${tokenName} (AI Search Service Token)`,
        policies: [
            {
                effect: "allow",
                permissionGroups: [
                    "AI Search Index Engine",
                    "Workers R2 Storage Write",
                ],
                resources: {
                    "com.cloudflare.api.account": "*",
                },
            },
        ],
        baseUrl: props.baseUrl,
        profile: props.profile,
        apiKey: props.apiKey,
        apiToken: props.apiToken,
        accountId: props.accountId,
        email: props.email,
        delete: props.delete,
    });
    if (!accountToken.value) {
        throw new Error("Failed to create account API token for AI Search - no token value returned");
    }
    const cfApiId = accountToken.id;
    const cfApiKey = accountToken.value.unencrypted;
    // Register the token with AI Search
    try {
        const result = await createAiSearchToken(api, {
            name: tokenName,
            cf_api_id: cfApiId,
            cf_api_key: cfApiKey,
        });
        return {
            type: "ai_search_token",
            tokenId: result.id,
            accountTokenId: accountToken.id,
            accountId: result.account_id,
            accountTag: result.account_tag,
            name: result.name,
            cfApiId: result.cf_api_id,
            cfApiKey: alchemy.secret(cfApiKey),
            enabled: result.enabled,
            createdAt: result.created_at,
            modifiedAt: result.modified_at,
        };
    }
    catch (error) {
        // Check if token already exists and we should adopt it
        if (error instanceof CloudflareApiError && props.adopt) {
            // List tokens and find by name
            const tokens = await listAiSearchTokens(api);
            const existing = tokens.find((t) => t.name === tokenName);
            if (existing) {
                return {
                    type: "ai_search_token",
                    tokenId: existing.id,
                    accountTokenId: accountToken.id,
                    accountId: existing.account_id,
                    accountTag: existing.account_tag,
                    name: existing.name,
                    cfApiId: existing.cf_api_id,
                    cfApiKey: alchemy.secret(cfApiKey),
                    enabled: existing.enabled,
                    createdAt: existing.created_at,
                    modifiedAt: existing.modified_at,
                };
            }
        }
        throw error;
    }
});
/**
 * Create an AI Search token
 */
export async function createAiSearchToken(api, payload) {
    return await extractCloudflareResult(`create AI Search token "${payload.name}"`, api.post(`/accounts/${api.accountId}/ai-search/tokens`, payload));
}
/**
 * List all AI Search tokens in an account
 */
export async function listAiSearchTokens(api) {
    return await extractCloudflareResult(`list AI Search tokens`, api.get(`/accounts/${api.accountId}/ai-search/tokens`));
}
/**
 * Delete an AI Search token.
 *
 * Retries briefly on `7076 token_in_use_by_instances` to cover residual
 * cross-colo lag (CF's server retries the guard internally once after
 * 500ms; the spurious-409 window is typically sub-second). We layer two
 * client-side retries with 1s + 2s delays on top.
 *
 * A persistent 409 after the short retry window means the token is
 * genuinely still referenced by another AI Search instance. We throw an
 * actionable error rather than silently leaking the token — shared tokens
 * should be modeled explicitly via an `AiSearchToken` resource.
 */
export async function deleteAiSearchToken(api, tokenId) {
    const retryDelaysMs = [1000, 2000]; // 2 retries, ~3s total
    for (let attempt = 0; attempt <= retryDelaysMs.length; attempt++) {
        try {
            await extractCloudflareResult(`delete AI Search token "${tokenId}"`, api.delete(`/accounts/${api.accountId}/ai-search/tokens/${tokenId}`));
            return;
        }
        catch (error) {
            if (error instanceof CloudflareApiError && error.status === 404) {
                return;
            }
            const isTokenInUse = error instanceof CloudflareApiError &&
                error.status === 409 &&
                Array.isArray(error.errorData) &&
                error.errorData.some((e) => e?.code === 7076);
            if (isTokenInUse && attempt < retryDelaysMs.length) {
                await new Promise((resolve) => setTimeout(resolve, retryDelaysMs[attempt]));
                continue;
            }
            if (isTokenInUse) {
                throw new Error(`AI Search token "${tokenId}" is still referenced by another instance. ` +
                    "If the token is intentionally shared across instances, manage it " +
                    "explicitly via an `AiSearchToken` resource with `delete: false`, " +
                    "or delete all referencing instances before deleting the token.", { cause: error });
            }
            throw error;
        }
    }
}
/**
 * Get an AI Search token
 */
export async function getAiSearchToken(api, tokenId) {
    return await extractCloudflareResult(`get AI Search token "${tokenId}"`, api.get(`/accounts/${api.accountId}/ai-search/tokens/${tokenId}`));
}
//# sourceMappingURL=ai-search-token.js.map