import * as mf from "miniflare";
import { Resource, ResourceKind } from "../resource.js";
import { Scope } from "../scope.js";
import { withExponentialBackoff } from "../util/retry.js";
import { CloudflareApiError, handleApiError } from "./api-error.js";
import { extractCloudflareResult, } from "./api-response.js";
import { createCloudflareApi, } from "./api.js";
import { deleteMiniflareBinding } from "./miniflare/delete.js";
import { getDefaultPersistPath } from "./miniflare/paths.js";
export function isKVNamespace(resource) {
    return resource?.[ResourceKind] === "cloudflare::KVNamespace";
}
/**
 * A Cloudflare KV Namespace is a key-value store that can be used to store data for your application.
 *
 * @see https://developers.cloudflare.com/kv/concepts/kv-namespaces/
 *
 * @example
 * // Create a basic KV namespace for storing user data
 * const users = await KVNamespace("users", {
 *   title: "user-data"
 * });
 *
 * @example
 * // Create a KV namespace with initial values and TTL
 * const sessions = await KVNamespace("sessions", {
 *   title: "user-sessions",
 *   values: [{
 *     key: "session_123",
 *     value: { userId: "user_456", role: "admin" },
 *     expirationTtl: 3600 // Expires in 1 hour
 *   }]
 * });
 *
 * @example
 * // Create a KV namespace with metadata for caching
 * const assets = await KVNamespace("assets", {
 *   title: "static-assets",
 *   values: [{
 *     key: "main.js",
 *     value: "content...",
 *     metadata: {
 *       contentType: "application/javascript",
 *       etag: "abc123"
 *     }
 *   }]
 * });
 *
 * @example
 * // Adopt an existing namespace if it already exists instead of failing
 * const existingNamespace = await KVNamespace("existing-ns", {
 *   title: "existing-namespace",
 *   adopt: true,
 *   values: [{
 *     key: "config",
 *     value: { setting: "updated-value" }
 *   }]
 * });
 *
 * @example
 * // When removing from Alchemy state, keep the namespace in Cloudflare
 * const preservedNamespace = await KVNamespace("preserve-ns", {
 *   title: "preserved-namespace",
 *   delete: false
 * });
 */
export async function KVNamespace(id, props = {}) {
    return await _KVNamespace(id, {
        ...props,
        dev: {
            ...(props.dev ?? {}),
            force: Scope.current.local,
        },
    });
}
const _KVNamespace = Resource("cloudflare::KVNamespace", async function (id, props) {
    const title = props.title ?? this.output?.title ?? this.scope.createPhysicalName(id);
    const local = this.scope.local && !props.dev?.remote;
    const dev = {
        id: this.output?.dev?.id ?? this.output?.namespaceId ?? id,
        remote: props.dev?.remote ?? false,
    };
    if (local) {
        if (props.values) {
            await insertLocalKVRecords({
                namespaceId: dev.id,
                values: props.values,
                rootDir: this.scope.rootDir,
            });
        }
        return {
            type: "kv_namespace",
            namespaceId: this.output?.namespaceId ?? "",
            title,
            values: props.values,
            dev,
            createdAt: this.output?.createdAt ?? Date.now(),
            modifiedAt: Date.now(),
        };
    }
    const api = await createCloudflareApi(props);
    if (this.phase === "update" && this.output?.title !== title) {
        await renameKVNamespace(api, this.output.namespaceId, title);
    }
    if (this.phase === "delete") {
        if (this.output.dev?.id) {
            await deleteMiniflareBinding(this.scope, "kv", this.output.dev.id).catch(() => { });
        }
        if (this.output.namespaceId && props.delete !== false) {
            await deleteKVNamespace(api, this.output.namespaceId);
        }
        return this.destroy();
    }
    let result;
    if (this.phase === "create" || !this.output.namespaceId) {
        result = await createKVNamespace(api, {
            ...props,
            title,
        });
    }
    else {
        result = this.output;
    }
    await insertKVRecords(api, result.namespaceId, props);
    return {
        type: "kv_namespace",
        namespaceId: result.namespaceId,
        title,
        values: props.values,
        dev,
        createdAt: result.createdAt,
        modifiedAt: Date.now(),
    };
});
export async function createKVNamespace(api, props) {
    try {
        const { id } = await extractCloudflareResult(`create kv namespace "${props.title}"`, api.post(`/accounts/${api.accountId}/storage/kv/namespaces`, {
            title: props.title,
        }));
        return { namespaceId: id, createdAt: Date.now() };
    }
    catch (error) {
        if (error instanceof CloudflareApiError &&
            error.errorData.some((e) => e.code === 10014) &&
            (props.adopt ?? Scope.current.adopt)) {
            const existingNamespace = await findKVNamespaceByTitle(api, props.title);
            if (!existingNamespace) {
                throw new Error(`Failed to find existing namespace '${props.title}' for adoption`);
            }
            return {
                namespaceId: existingNamespace.id,
                createdAt: existingNamespace.createdAt ?? Date.now(),
            };
        }
        else {
            throw error;
        }
    }
}
export async function deleteKVNamespace(api, namespaceId) {
    // Delete KV namespace
    const deleteResponse = await api.delete(`/accounts/${api.accountId}/storage/kv/namespaces/${namespaceId}`);
    if (!deleteResponse.ok && deleteResponse.status !== 404) {
        await handleApiError(deleteResponse, "delete", "kv_namespace", namespaceId);
    }
}
export async function insertKVRecords(api, namespaceId, props) {
    if (props.values && props.values.length > 0) {
        // Process KV pairs in batches of 10000 (API limit)
        const BATCH_SIZE = 10000;
        for (let i = 0; i < props.values.length; i += BATCH_SIZE) {
            const bulkPayload = props.values
                .slice(i, i + BATCH_SIZE)
                .map((entry) => ({
                key: entry.key,
                value: typeof entry.value === "string"
                    ? entry.value
                    : JSON.stringify(entry.value),
                expiration: entry.expiration,
                expiration_ttl: entry.expirationTtl,
                metadata: entry.metadata,
            }));
            await withExponentialBackoff(async () => {
                const bulkResponse = await api.put(`/accounts/${api.accountId}/storage/kv/namespaces/${namespaceId}/bulk`, bulkPayload);
                if (!bulkResponse.ok) {
                    const errorData = await bulkResponse.json().catch(() => ({
                        errors: [{ message: bulkResponse.statusText }],
                    }));
                    const errorMessage = errorData.errors?.[0]?.message || bulkResponse.statusText;
                    // Throw error to trigger retry
                    throw new Error(`Error writing KV batch: ${errorMessage}`);
                }
                return bulkResponse;
            }, (error) => {
                // Retry on "namespace not found" errors as they're likely propagation delays
                return error.message?.includes("not found");
            }, 5, // 5 retry attempts
            1000);
        }
    }
}
export async function insertLocalKVRecords(input) {
    const miniflare = new mf.Miniflare({
        script: "",
        modules: true,
        defaultPersistRoot: getDefaultPersistPath(input.rootDir),
        kvNamespaces: { KV: input.namespaceId },
        kvPersist: true,
        log: process.env.DEBUG ? new mf.Log(mf.LogLevel.DEBUG) : undefined,
    });
    try {
        await miniflare.ready;
        const kv = await miniflare.getKVNamespace("KV");
        await Promise.all(input.values.map((record) => kv.put(record.key, typeof record.value === "string"
            ? record.value
            : JSON.stringify(record.value), {
            expiration: record.expiration,
            expirationTtl: record.expirationTtl,
            metadata: record.metadata,
        })));
    }
    finally {
        await miniflare.dispose();
    }
}
/**
 * Find a KV namespace by title with pagination support
 */
export async function findKVNamespaceByTitle(api, title) {
    let page = 1;
    const perPage = 100; // Maximum allowed by API
    let hasMorePages = true;
    while (hasMorePages) {
        const response = await api.get(`/accounts/${api.accountId}/storage/kv/namespaces?page=${page}&per_page=${perPage}`);
        if (!response.ok) {
            await handleApiError(response, "list", "kv_namespace", "all");
        }
        const data = (await response.json());
        const namespaces = data.result;
        const resultInfo = data.result_info;
        // Look for a namespace with matching title
        const match = namespaces.find((ns) => ns.title === title);
        if (match) {
            return {
                id: match.id,
                // Convert ISO string to timestamp if available, otherwise use current time
                createdAt: match.created_on
                    ? new Date(match.created_on).getTime()
                    : undefined,
            };
        }
        // Check if we've seen all pages
        hasMorePages =
            resultInfo.page * resultInfo.per_page < resultInfo.total_count;
        page++;
    }
    // No matching namespace found
    return null;
}
export async function renameKVNamespace(api, namespaceId, title) {
    const response = await api.put(`/accounts/${api.accountId}/storage/kv/namespaces/${namespaceId}`, { title });
    if (!response.ok) {
        await handleApiError(response, "update", "kv_namespace", namespaceId);
    }
}
//# sourceMappingURL=kv-namespace.js.map