import fs from "node:fs/promises";
import path from "pathe";
import { Resource } from "../resource.js";
import { Scope } from "../scope.js";
import { isSecret } from "../secret.js";
import { assertNever } from "../util/assert-never.js";
import { isAiSearchNamespace } from "./ai-search-namespace.js";
import { isAiSearch } from "./ai-search.js";
import { createCloudflareApi } from "./api.js";
import { getCloudflareRegistryWithAccountNamespace } from "./container.js";
import { isQueueEventSource } from "./event-source.js";
import { isQueue } from "./queue.js";
import { unencryptSecrets } from "./util/filter-env-bindings.js";
import { getAccountSubdomain } from "./worker-subdomain.js";
import { isWorker, Worker } from "./worker.js";
// we are deprecating the WranglerJson resource (it is now just a funciton)
// but, a user may still have a resource that depends on it, so we register a no-op dummy resource so that it can be cleanly delted
Resource("cloudflare::WranglerJson", async function () {
    if (this.phase === "delete") {
        return this.destroy();
    }
    throw new Error("Not implemented");
});
/**
 * Resource for managing wrangler.json configuration files
 */
export async function WranglerJson(props) {
    const cwd = props.worker.cwd ? path.resolve(props.worker.cwd) : process.cwd();
    const toAbsolute = (input) => {
        return (input ? path.resolve(cwd, input) : undefined);
    };
    const main = toAbsolute(props.main ?? props.worker.entrypoint);
    let filePath = toAbsolute(props.path ?? cwd);
    if (!path.basename(filePath).match(".json")) {
        filePath = path.join(filePath, props.name ?? "wrangler.jsonc");
    }
    const dirname = path.dirname(filePath);
    if (!main) {
        throw new Error("Worker must have an entrypoint to generate a wrangler.json");
    }
    const worker = props.worker;
    const spec = {
        name: worker.name,
        // Use entrypoint as main if it exists
        main: path.relative(dirname, main),
        // see: https://developers.cloudflare.com/workers/configuration/compatibility-dates/
        compatibility_date: worker.compatibilityDate,
        compatibility_flags: props.worker.compatibilityFlags,
        assets: props.assets
            ? {
                directory: toAbsolute(props.assets.directory),
                binding: props.assets.binding,
                not_found_handling: props.worker.assets?.not_found_handling,
                html_handling: props.worker.assets?.html_handling,
                run_worker_first: props.worker.assets?.run_worker_first,
            }
            : undefined,
        placement: worker.placement,
        limits: worker.limits
            ? {
                cpu_ms: worker.limits.cpu_ms ?? 30_000,
                subrequests: worker.limits.subrequests,
            }
            : undefined,
        logpush: worker.logpush,
    };
    // Process bindings if they exist
    if (worker.bindings) {
        await processBindings(spec, worker.bindings, worker.eventSources, worker.name, cwd, props.secrets ?? false, Scope.current.local && !props.worker.dev?.remote, async () => worker.accountId ?? (await createCloudflareApi(worker)).accountId, async () => await getAccountSubdomain(await createCloudflareApi(worker)));
    }
    // Add environment variables as vars
    if (worker.env) {
        spec.vars = { ...worker.env };
    }
    if (worker.tailConsumers && worker.tailConsumers.length > 0) {
        spec.tail_consumers = worker.tailConsumers.map((consumer) => {
            if (isWorker(consumer)) {
                return { service: consumer.name };
            }
            return { service: consumer.service };
        });
    }
    if (worker.crons && worker.crons.length > 0) {
        spec.triggers = { crons: worker.crons };
    }
    if (spec.assets && spec.assets.directory) {
        spec.assets.directory = path.relative(dirname, spec.assets.directory);
    }
    // Apply the wrangler configuration hook as the final transformation
    const finalSpec = props.transform?.wrangler
        ? await props.transform.wrangler(spec)
        : spec;
    await fs.mkdir(dirname, { recursive: true });
    if (props.secrets) {
        // If secrets are enabled, decrypt them in the wrangler.json file,
        // but do not modify `finalSpec` so that way secrets aren't written to state unencrypted.
        const withSecretsUnwrapped = {
            ...finalSpec,
            vars: unencryptSecrets(finalSpec.vars ?? {}),
        };
        await writeJSON(filePath, withSecretsUnwrapped);
    }
    else {
        await writeJSON(filePath, finalSpec);
    }
    return {
        ...props,
        path: path.relative(cwd, filePath),
        spec: finalSpec,
        createdAt: Date.now(),
        updatedAt: Date.now(),
    };
}
const writeJSON = async (filePath, content) => {
    await fs.writeFile(filePath, `${JSON.stringify(content, null, 2)}\n`);
};
/**
 * Process worker bindings into wrangler.json format
 */
async function processBindings(spec, bindings, eventSources, workerName, workerCwd, writeSecrets, local, getAccountId, getAccountSubdomain) {
    // Arrays to collect different binding types
    const kvNamespaces = [];
    const durableObjects = [];
    const r2Buckets = [];
    const services = [];
    const sendEmailBindings = [];
    const secrets = [];
    const workflows = [];
    const d1Databases = [];
    const queues = {
        producers: [],
        consumers: [],
    };
    const new_sqlite_classes = [];
    const new_classes = [];
    const vectorizeIndexes = [];
    const analyticsEngineDatasets = [];
    const hyperdrive = [];
    const pipelines = [];
    const secretsStoreSecrets = [];
    const dispatchNamespaces = [];
    const ratelimits = [];
    const containers = [];
    const workerLoaders = [];
    const vpcServices = [];
    const aiSearchInstances = [];
    const aiSearchNamespaces = [];
    for (const eventSource of eventSources ?? []) {
        if (isQueueEventSource(eventSource)) {
            queues.consumers.push({
                queue: eventSource.queue.name,
                max_batch_size: eventSource.settings?.batchSize,
                max_concurrency: eventSource.settings?.maxConcurrency,
                max_retries: eventSource.settings?.maxRetries,
                max_batch_timeout: eventSource.settings?.maxWaitTimeMs
                    ? eventSource.settings?.maxWaitTimeMs / 1000
                    : undefined,
                retry_delay: eventSource.settings?.retryDelay,
            });
        }
        else if (isQueue(eventSource)) {
            queues.consumers.push({
                queue: eventSource.name,
            });
        }
    }
    // Process each binding
    for (const [bindingName, binding] of Object.entries(bindings)) {
        if (typeof binding === "function") {
            // this is only reachable in the
            throw new Error(`Invalid binding ${bindingName} is a function`);
        }
        if (typeof binding === "string") {
            // Plain text binding - add to vars
            spec.vars ??= {};
            spec.vars[bindingName] = binding;
        }
        else if (writeSecrets && isSecret(binding)) {
            spec.vars ??= {};
            spec.vars[bindingName] = binding;
        }
        else if (isAiSearch(binding)) {
            // AI Search discriminates via ResourceKind (isAiSearch) rather than
            // binding.type — `AiSearch.type` is the *source* type ("r2" |
            // "web-crawler") and so would collide with `r2_bucket` string
            // matching below. Checking here at the top of the chain mirrors the
            // ordering in worker-metadata.ts.
            aiSearchInstances.push({
                binding: bindingName,
                instance_name: binding.name,
                // AI Search is not natively supported by Miniflare — emitting
                // `remote: true` lets `wrangler dev` proxy to the deployed
                // instance (matches vectorize / browser / ai / vpc_service).
                remote: true,
            });
        }
        else if (isAiSearchNamespace(binding)) {
            aiSearchNamespaces.push({
                binding: bindingName,
                namespace: binding.namespace,
                remote: true,
            });
        }
        else if (binding.type === "cloudflare::Worker::Self") {
            // Self(service) binding
            services.push({
                binding: bindingName,
                service: workerName,
                entrypoint: binding.__entrypoint__,
            });
        }
        else if (binding.type === "cloudflare::Worker::DevDomain") {
            const subdomain = await getAccountSubdomain();
            // Subdomain binding
            (spec.vars ??= {})[bindingName] =
                `${workerName}.${subdomain}.workers.dev`;
        }
        else if (binding.type === "cloudflare::Worker::DevUrl") {
            const subdomain = await getAccountSubdomain();
            // Subdomain binding
            (spec.vars ??= {})[bindingName] =
                `https://${workerName}.${subdomain}.workers.dev`;
        }
        else if (binding.type === "service") {
            // Service binding
            services.push({
                binding: bindingName,
                service: "name" in binding ? binding.name : binding.service,
                entrypoint: "__entrypoint__" in binding ? binding.__entrypoint__ : undefined,
            });
        }
        else if (binding.type === "kv_namespace") {
            // KV Namespace binding
            const id = "dev" in binding && !binding.dev?.remote && local
                ? binding.dev.id
                : "namespaceId" in binding
                    ? binding.namespaceId
                    : binding.id;
            kvNamespaces.push({
                binding: bindingName,
                id: id,
                preview_id: id,
                ...("dev" in binding && binding.dev?.remote ? { remote: true } : {}),
            });
        }
        else if (typeof binding === "object" &&
            binding.type === "durable_object_namespace") {
            // Durable Object binding
            const doBinding = binding;
            durableObjects.push({
                name: bindingName,
                class_name: doBinding.className,
                script_name: doBinding.scriptName,
                environment: doBinding.environment,
            });
            if (doBinding.sqlite) {
                new_sqlite_classes.push(doBinding.className);
            }
            else {
                new_classes.push(doBinding.className);
            }
        }
        else if (binding.type === "r2_bucket") {
            const name = "dev" in binding && !binding.dev?.remote && local
                ? binding.dev.id
                : binding.name;
            r2Buckets.push({
                binding: bindingName,
                bucket_name: name,
                preview_bucket_name: name,
                jurisdiction: binding.jurisdiction === "default" ? undefined : binding.jurisdiction,
                ...(binding.dev?.remote ? { remote: true } : {}),
            });
        }
        else if (binding.type === "send_email") {
            sendEmailBindings.push({
                name: bindingName,
                destination_address: binding.destinationAddress,
                allowed_destination_addresses: binding.allowedDestinationAddresses,
                allowed_sender_addresses: binding.allowedSenderAddresses,
                ...(binding.dev?.remote ? { remote: true } : {}),
            });
        }
        else if (binding.type === "secret") {
            // Secret binding
            secrets.push(bindingName);
        }
        else if (binding.type === "assets") {
            spec.assets = {
                directory: path.resolve(workerCwd, binding.path),
                binding: bindingName,
            };
        }
        else if (binding.type === "workflow") {
            workflows.push({
                name: binding.workflowName,
                binding: bindingName,
                class_name: binding.className,
                script_name: binding.scriptName,
                ...(binding.limits ? { limits: binding.limits } : {}),
            });
        }
        else if (binding.type === "d1") {
            const id = "dev" in binding && !binding.dev?.remote && local
                ? binding.dev.id
                : binding.id;
            d1Databases.push({
                binding: bindingName,
                database_id: id,
                database_name: binding.name,
                migrations_dir: binding.migrationsDir,
                preview_database_id: id,
                ...(binding.dev?.remote ? { remote: true } : {}),
            });
        }
        else if (binding.type === "queue") {
            const id = "dev" in binding && !binding.dev?.remote && local
                ? binding.dev.id
                : binding.id;
            queues.producers.push({
                binding: bindingName,
                queue: id,
            });
        }
        else if (binding.type === "vectorize") {
            vectorizeIndexes.push({
                binding: bindingName,
                index_name: binding.name,
                // https://developers.cloudflare.com/workers/development-testing/#recommended-remote-bindings
                remote: true,
            });
        }
        else if (binding.type === "browser") {
            if (spec.browser) {
                throw new Error(`Browser already bound to ${spec.browser.binding}`);
            }
            spec.browser = {
                binding: bindingName,
                // https://developers.cloudflare.com/workers/development-testing/#recommended-remote-bindings
                remote: true,
            };
        }
        else if (binding.type === "ai") {
            if (spec.ai) {
                throw new Error(`AI already bound to ${spec.ai.binding}`);
            }
            spec.ai = {
                binding: bindingName,
                // https://developers.cloudflare.com/workers/development-testing/#recommended-remote-bindings
                remote: true,
            };
        }
        else if (binding.type === "images") {
            if (spec.images) {
                throw new Error(`Images already bound to ${spec.images.binding}`);
            }
            spec.images = {
                binding: bindingName,
                // https://developers.cloudflare.com/workers/development-testing/#recommended-remote-bindings
                remote: true,
            };
        }
        else if (binding.type === "analytics_engine") {
            analyticsEngineDatasets.push({
                binding: bindingName,
                dataset: binding.dataset,
            });
        }
        else if (binding.type === "version_metadata") {
            if (spec.version_metadata) {
                throw new Error(`Version metadata already bound to ${spec.version_metadata.binding}`);
            }
            spec.version_metadata = {
                binding: bindingName,
            };
        }
        else if (binding.type === "hyperdrive") {
            hyperdrive.push({
                binding: bindingName,
                id: binding.hyperdriveId,
                localConnectionString: writeSecrets
                    ? binding.dev?.origin.unencrypted
                    : undefined,
            });
        }
        else if (binding.type === "pipeline") {
            pipelines.push({
                binding: bindingName,
                pipeline: binding.name,
            });
        }
        else if (binding.type === "json") {
            // TODO(sam): anything to do here? not sure wrangler.json supports this
        }
        else if (binding.type === "secrets_store_secret") {
            secretsStoreSecrets.push({
                binding: bindingName,
                store_id: binding.storeId,
                secret_name: binding.name,
            });
        }
        else if (binding.type === "dispatch_namespace") {
            dispatchNamespaces.push({
                binding: bindingName,
                namespace: binding.namespaceName,
                remote: true,
            });
        }
        else if (binding.type === "ratelimit") {
            ratelimits.push({
                name: bindingName,
                namespace_id: binding.namespace_id.toString(),
                simple: binding.simple,
            });
        }
        else if (binding.type === "secret_key") {
            // no-op
        }
        else if (binding.type === "container") {
            durableObjects.push({
                name: bindingName,
                class_name: binding.className,
                script_name: binding.scriptName,
            });
            new_sqlite_classes.push(binding.className);
            // If there are build options, use the local image path, otherwise use the image reference
            const image = binding.image.build
                ? path.resolve(workerCwd, path.join(binding.image.build.context ?? process.cwd(), binding.image.build.dockerfile ?? "Dockerfile"))
                : getCloudflareRegistryWithAccountNamespace(await getAccountId(), binding.image.imageRef);
            containers.push({
                class_name: binding.className,
                image,
            });
        }
        else if (binding.type === "worker_loader") {
            workerLoaders.push({
                binding: bindingName,
            });
        }
        else if (binding.type === "vpc_service") {
            vpcServices.push({
                binding: bindingName,
                service_id: binding.serviceId,
                remote: true,
            });
        }
        else {
            console.log("binding", binding);
            return assertNever(binding);
        }
    }
    // Add collected bindings to the spec
    if (kvNamespaces.length > 0) {
        spec.kv_namespaces = kvNamespaces;
    }
    if (durableObjects.length > 0) {
        spec.durable_objects = {
            bindings: durableObjects,
        };
    }
    if (r2Buckets.length > 0) {
        spec.r2_buckets = r2Buckets;
    }
    if (services.length > 0) {
        spec.services = services;
    }
    if (sendEmailBindings.length > 0) {
        spec.send_email = sendEmailBindings;
    }
    if (d1Databases.length > 0) {
        spec.d1_databases = d1Databases;
    }
    if (queues.consumers.length > 0) {
        (spec.queues ??= {}).consumers = queues.consumers;
    }
    if (queues.producers.length > 0) {
        (spec.queues ??= {}).producers = queues.producers;
    }
    if (vectorizeIndexes.length > 0) {
        spec.vectorize = vectorizeIndexes;
    }
    if (new_sqlite_classes.length > 0 || new_classes.length > 0) {
        spec.migrations = [
            {
                tag: "v1",
                new_sqlite_classes,
                new_classes,
            },
        ];
    }
    if (workflows.length > 0) {
        spec.workflows = workflows;
    }
    if (analyticsEngineDatasets.length > 0) {
        spec.analytics_engine_datasets = analyticsEngineDatasets;
    }
    if (hyperdrive.length > 0) {
        spec.hyperdrive = hyperdrive;
    }
    if (pipelines.length > 0) {
        spec.pipelines = pipelines;
    }
    if (secretsStoreSecrets.length > 0) {
        spec.secrets_store_secrets = secretsStoreSecrets;
    }
    if (dispatchNamespaces.length > 0) {
        spec.dispatch_namespaces = dispatchNamespaces;
    }
    if (containers.length > 0) {
        spec.containers = containers;
    }
    if (ratelimits.length > 0) {
        spec.ratelimits = ratelimits;
    }
    if (workerLoaders.length > 0) {
        spec.worker_loaders = workerLoaders;
    }
    if (aiSearchInstances.length > 0) {
        spec.ai_search = aiSearchInstances;
    }
    if (aiSearchNamespaces.length > 0) {
        spec.ai_search_namespaces = aiSearchNamespaces;
    }
}
//# sourceMappingURL=wrangler.json.js.map