import path from "pathe";
import { Resource, ResourceKind } from "../resource.js";
import { DeferredPromise } from "../util/deferred-promise.js";
import { logger } from "../util/logger.js";
import { withExponentialBackoff } from "../util/retry.js";
import { CloudflareApiError, handleApiError, isCloudflareApiError, } from "./api-error.js";
import { createCloudflareApi, } from "./api.js";
import { DEFAULT_COMPATIBILITY_DATE } from "./compatibility-date.js";
import { unionCompatibilityFlags, } from "./compatibility-presets.js";
import { ContainerApplication } from "./container.js";
import { CustomDomain } from "./custom-domain.js";
import { DurableObjectNamespace, isDurableObjectNamespace, } from "./durable-object-namespace.js";
import { isQueueEventSource } from "./event-source.js";
import { deleteMiniflareWorkerData } from "./miniflare/delete.js";
import { MiniflareController } from "./miniflare/miniflare-controller.js";
import { QueueConsumer, deleteQueueConsumer, listQueueConsumersForWorker, } from "./queue-consumer.js";
import { isQueue } from "./queue.js";
import { Route } from "./route.js";
import { uploadAssets } from "./worker-assets.js";
import { WorkerBundle, normalizeWorkerBundle, } from "./worker-bundle.js";
import { bumpMigrationTagVersion, getWorkerSettings, prepareWorkerMetadata, } from "./worker-metadata.js";
import { createWorkerUrl, disableWorkerSubdomain, enableWorkerSubdomain, } from "./worker-subdomain.js";
import { createTail } from "./worker-tail.js";
import { Workflow, deleteWorkflow, isWorkflow, upsertWorkflow, } from "./workflow.js";
// Previous versions of `Worker` used the `Bundle` resource.
// This import is here to avoid errors when destroying the `Bundle` resource.
import "../esbuild/bundle.js";
import { Scope } from "../scope.js";
import { extractCloudflareResult } from "./api-response.js";
import { createEmptyWorker, exists } from "./worker-stub.js";
export function isWorker(resource) {
    return resource?.[ResourceKind] === "cloudflare::Worker";
}
export function Worker(id, props) {
    return _Worker(id, props);
}
(function (Worker) {
    Worker.DevDomain = {
        type: "cloudflare::Worker::DevDomain",
    };
    Worker.DevUrl = {
        type: "cloudflare::Worker::DevUrl",
    };
})(Worker || (Worker = {}));
Worker.experimentalEntrypoint = (worker, entrypoint) => {
    if (Scope.getScope()?.local) {
        logger.warn("Worker.experimentalEntrypoint is not supported in local development. See: https://github.com/cloudflare/workers-sdk/issues/10681");
    }
    return {
        ...worker,
        // we rename the entrypoint in order to prevent collisions with entrypoint on Worker
        __entrypoint__: entrypoint,
    };
};
const _Worker = Resource("cloudflare::Worker", {
    alwaysUpdate: true,
}, async function (id, props) {
    let adopt = props.adopt ?? this.scope.adopt;
    const workerName = props.name ??
        this.output?.name ??
        this.scope.createPhysicalName(id).toLowerCase();
    if (this.phase === "create" && !props.adopt) {
        // it is possible that this worker already exists and was created by the old Website wrapper with a nested scope
        // we need to detect this and set adopt=true so that the previous version will be adopted seamlessly
        // HEURISTIC: `this.scope` would previously be the nested outer scope alchemy.run
        // so, if `this.scope` has a child of `wrangler.jsonc`, then it is likely that it was created by the old Website wrapper with a nested scope
        if (await this.scope.has("wrangler.jsonc", "cloudflare::WranglerJson")) {
            logger.warn(`Migrating Worker '${workerName}' from the legacy Website wrapper.`);
            props.adopt = true;
        }
    }
    const options = (() => {
        if (props.projectRoot) {
            logger.warn("projectRoot is deprecated, use cwd instead");
            props.cwd = props.projectRoot;
        }
        const cwd = path.resolve(props.cwd ?? process.cwd());
        const compatibilityDate = props.compatibilityDate ?? DEFAULT_COMPATIBILITY_DATE;
        const compatibilityFlags = unionCompatibilityFlags(props.compatibility, props.compatibilityFlags);
        const dispatchNamespace = typeof props.namespace === "string"
            ? props.namespace
            : props.namespace?.namespaceName;
        const bundle = normalizeWorkerBundle({
            id,
            entrypoint: props.entrypoint,
            script: props.script,
            format: props.format,
            noBundle: props.noBundle,
            rules: "rules" in props ? props.rules : undefined,
            bundle: props.bundle,
            cwd,
            compatibilityDate,
            compatibilityFlags,
            outdir: props.bundle?.outdir ??
                // the out folder can't be moved to the root of a monorepo, it must be ${cwd} or else miniflare throws a fit
                path.join(process.cwd(), ".alchemy", "out", workerName),
            sourceMap: "sourceMap" in props ? props.sourceMap : undefined,
        });
        let assets;
        const containers = [];
        const workflows = [];
        const durableObjects = [];
        for (const binding of Object.values(props.bindings ?? {})) {
            if (typeof binding !== "object")
                continue;
            if (binding.type === "assets") {
                assets = binding;
            }
            else if (binding.type === "container") {
                containers.push(binding);
            }
            else if (binding.type === "workflow" &&
                (!binding.scriptName || binding.scriptName === workerName)) {
                workflows.push(binding);
            }
            else if (binding.type === "durable_object_namespace" &&
                (!binding.scriptName || binding.scriptName === workerName)) {
                durableObjects.push(binding);
            }
        }
        return {
            name: workerName,
            cwd,
            compatibilityDate,
            compatibilityFlags,
            dispatchNamespace,
            bundle,
            assets,
            containers,
            workflows,
            durableObjects,
        };
    })();
    const scriptName = options.name;
    if (this.phase === "delete") {
        // Heuristic: we must detect the case where this is the Worker wrapped in the old Website nested scope and not delete it
        // we must not delete it because what we're actually doing is migrating to a flat worker
        // we will achieve this by checking for a sibling resource with ID wrangler.jsonc and type cloudflare::WranglerJson
        if (await this.scope.has("wrangler.jsonc", "cloudflare::WranglerJson")) {
            // skip deletion
            logger.warn(`Migrating Worker '${options.name}' from the legacy Website wrapper.`);
            return this.destroy(true);
        }
        if (options.bundle.isOk()) {
            await options.bundle.value.delete?.();
        }
        await deleteMiniflareWorkerData(this.scope, options.name, {
            durableObjects: options.durableObjects,
            workflows: options.workflows,
        });
        if (props.delete !== false && this.output?.dev?.hasRemote !== false) {
            const api = await createCloudflareApi(props);
            if (props.version) {
                //* if the worker exists we deploy an empty version so we can destroy
                if (await exists(api, options.name)) {
                    await createEmptyWorker(api, options.name, props.version);
                }
            }
            else {
                await Promise.all([
                    deleteQueueConsumers(api, options.name),
                    deleteWorkflows(api, options.name),
                ]);
                await deleteWorker(api, {
                    scriptName: options.name,
                    dispatchNamespace: options.dispatchNamespace,
                });
            }
        }
        return this.destroy();
    }
    if (options.bundle.isErr()) {
        throw new Error(options.bundle.error);
    }
    const bundle = options.bundle.value;
    const api = await createCloudflareApi(props);
    if (this.scope.local && !props.dev?.remote) {
        let url;
        if (props.dev?.url) {
            url = props.dev.url;
        }
        else {
            const controller = MiniflareController.singleton;
            url = await controller.add({
                api,
                id,
                name: options.name,
                compatibilityDate: options.compatibilityDate,
                compatibilityFlags: options.compatibilityFlags,
                bindings: props.bindings,
                eventSources: props.eventSources,
                assets: props.assets,
                bundle,
                port: props.dev?.port,
                tunnel: props.dev?.tunnel ?? this.scope.tunnel,
                cwd: props.cwd ?? process.cwd(),
            });
            this.onCleanup(() => controller.dispose());
        }
        await provisionResources({
            ...props,
            adopt,
        }, {
            name: options.name,
            local: true,
            dispatchNamespace: options.dispatchNamespace,
            containers: options.containers,
            hasDurableObjects: options.durableObjects.length > 0,
        });
        return {
            ...props,
            type: "service",
            id,
            name: options.name,
            cwd: path.relative(process.cwd(), options.cwd),
            compatibilityDate: options.compatibilityDate,
            compatibilityFlags: options.compatibilityFlags,
            format: props.format || "esm",
            bindings: normalizeExportBindings(options.name, props.bindings),
            createdAt: this.output?.createdAt ?? Date.now(),
            updatedAt: Date.now(),
            url,
            routes: [],
            domains: [],
            dev: {
                hasRemote: this.output?.dev?.hasRemote ?? false,
            },
            Env: undefined,
        };
    }
    if (this.phase === "create" || this.output.dev?.hasRemote === false) {
        if (props.version) {
            // When version is specified, we adopt existing workers or create them if they don't exist
            if (!(await workerExists(api, options))) {
                // Create the base worker first if it doesn't exist
                await putWorker(api, {
                    ...props,
                    version: undefined,
                    workerName: options.name,
                    scriptBundle: await bundle.create(),
                    dispatchNamespace: options.dispatchNamespace,
                    compatibilityDate: options.compatibilityDate,
                    compatibilityFlags: options.compatibilityFlags,
                    assetUploadResult: options.assets
                        ? await uploadAssets(api, {
                            workerName: options.name,
                            assets: options.assets,
                            assetConfig: props.assets,
                            namespace: options.dispatchNamespace,
                        })
                        : undefined,
                });
            }
            // We always "adopt" when publishing versions
        }
        else if (!adopt) {
            await assertWorkerDoesNotExist(api, options.name);
        }
        else if (adopt && !options.dispatchNamespace && props.url === false) {
            // explicitly disable the workers.dev subdomain
            await disableWorkerSubdomain(api, options.name);
        }
    }
    else if (this.phase === "update") {
        const oldName = this.output.name ?? this.output.id;
        const newName = options.name;
        if (oldName && oldName !== newName) {
            if (options.dispatchNamespace) {
                this.replace(true);
            }
            else {
                const renameResponse = await api.patch(`/accounts/${api.accountId}/workers/services/${oldName}`, { id: newName });
                if (!renameResponse.ok) {
                    await handleApiError(renameResponse, "rename", "worker", oldName);
                }
            }
        }
    }
    const assets = options.assets
        ? await uploadAssets(api, {
            workerName: options.name,
            assets: options.assets,
            assetConfig: props.assets,
            namespace: options.dispatchNamespace,
        })
        : undefined;
    let result;
    // cloudflare has awkward sequencing requirements for modifying subdomains
    // because conflicts with Durable Objects and preview_enabled is stateful
    // to solve this, we try and do both in parallel and push them through
    const bruteForce = (operation) => withExponentialBackoff(operation, (error) => {
        operation;
        return (error instanceof CloudflareApiError &&
            error.status === 400 &&
            error.message.includes("Cannot use Durable Objects with Preview URLs"));
    }, 10, 100, 1000);
    if (this.scope.watch) {
        const controller = new AbortController();
        [result] = await Promise.all([
            watchWorker(api, props, {
                id,
                name: options.name,
                dispatchNamespace: options.dispatchNamespace,
                bundle,
                compatibilityDate: options.compatibilityDate,
                compatibilityFlags: options.compatibilityFlags,
                version: props.version,
                assets,
                controller,
            }),
            bruteForce(updateSubdomain),
        ]);
        this.onCleanup(() => controller.abort());
        const tail = await createTail(api, id, options.name).catch((error) => {
            logger.error(`Failed to create tail for ${options.name}`, error);
        });
        this.onCleanup(() => tail?.close());
    }
    else {
        const scriptBundle = await bundle.create();
        const [_, _result] = await Promise.all([
            bruteForce(updateSubdomain),
            bruteForce(() => putWorker(api, {
                ...props,
                workerName: options.name,
                scriptBundle,
                dispatchNamespace: options.dispatchNamespace,
                compatibilityDate: options.compatibilityDate,
                compatibilityFlags: options.compatibilityFlags,
                assetUploadResult: assets,
            })),
        ]);
        result = _result;
    }
    async function updateSubdomain() {
        if (options.dispatchNamespace) {
            return;
        }
        if (props.url === false) {
            await disableWorkerSubdomain(api, scriptName);
        }
        else {
            const previewsEnabled = options.durableObjects.length === 0;
            await enableWorkerSubdomain(api, scriptName, props.previewSubdomains ?? previewsEnabled);
        }
    }
    if (props.crons) {
        await api.put(`/accounts/${api.accountId}/workers/scripts/${options.name}/schedules`, props.crons?.map((cron) => ({ cron })) ?? []);
    }
    await Promise.all(options.workflows.map((workflow) => upsertWorkflow(api, {
        workflowName: workflow.workflowName,
        className: workflow.className,
        scriptName: workflow.scriptName ?? options.name,
    })));
    const { domains, routes } = await provisionResources({
        ...props,
        adopt,
    }, {
        name: options.name,
        local: false,
        dispatchNamespace: options.dispatchNamespace,
        containers: options.containers,
        hasDurableObjects: options.durableObjects.length > 0,
        result,
        api,
    });
    const now = new Date();
    return {
        ...props,
        type: "service",
        id,
        entrypoint: props.entrypoint,
        name: options.name,
        cwd: path.relative(process.cwd(), options.cwd),
        compatibilityDate: options.compatibilityDate,
        compatibilityFlags: options.compatibilityFlags,
        format: props.format || "esm",
        bindings: normalizeExportBindings(options.name, props.bindings),
        env: props.env,
        observability: props.observability,
        createdAt: this.output?.createdAt ?? now,
        updatedAt: now,
        eventSources: props.eventSources,
        url: props.url !== false
            ? await createWorkerUrl(api, scriptName, props.version ? result?.id : undefined)
            : undefined,
        assets: props.assets,
        crons: props.crons,
        tailConsumers: props.tailConsumers,
        routes,
        domains,
        namespace: props.namespace,
        version: props.version,
        placement: props.placement,
        limits: props.limits,
        Env: undefined,
        dev: {
            hasRemote: true,
        },
    };
});
const normalizeExportBindings = (scriptName, bindings = {}) => {
    return Object.fromEntries(Object.entries(bindings).map(([bindingName, binding]) => [
        bindingName,
        isDurableObjectNamespace(binding) && binding.scriptName === undefined
            ? DurableObjectNamespace(binding.id, {
                ...binding,
                // re-export this binding mapping to the host worker (this worker)
                scriptName,
            })
            : isWorkflow(binding) && binding.scriptName === undefined
                ? Workflow(binding.id, {
                    ...binding,
                    // re-export this binding mapping to the host worker (this worker)
                    scriptName,
                })
                : binding,
    ]));
};
const assertUnique = (inputs, key, message) => {
    const ids = inputs.map((input) => input[key]);
    const duplicates = ids.filter((id, index) => ids.indexOf(id) !== index);
    if (duplicates.length > 0) {
        throw new Error(`Duplicate ${message} found: ${duplicates.join(", ")}`);
    }
};
async function provisionResources(props, options) {
    let metadataPromise;
    const input = {
        containers: options.containers,
        domains: props.domains?.map((domain) => {
            if (typeof domain === "string") {
                if (domain === "") {
                    throw new Error("Domain names cannot be empty strings");
                }
                return {
                    name: domain,
                    zoneId: undefined,
                    adopt: props.adopt,
                    overrideExistingOrigin: undefined,
                };
            }
            if (domain.domainName === "") {
                throw new Error("Domain names cannot be empty strings");
            }
            return {
                name: domain.domainName,
                zoneId: domain.zoneId,
                adopt: domain.adopt ?? props.adopt,
                overrideExistingOrigin: domain.overrideExistingOrigin,
            };
        }),
        eventSources: props.eventSources?.map((eventSource) => {
            if (isQueue(eventSource)) {
                return {
                    queue: eventSource,
                    settings: eventSource.dlq
                        ? { deadLetterQueue: eventSource.dlq }
                        : undefined,
                };
            }
            if (isQueueEventSource(eventSource)) {
                return {
                    queue: eventSource.queue,
                    settings: eventSource.settings,
                };
            }
            throw new Error(`Unsupported event source: ${eventSource}`);
        }),
        routes: props.routes?.map((route) => {
            if (typeof route === "string") {
                return {
                    pattern: route,
                    adopt: props.adopt,
                };
            }
            return {
                pattern: route.pattern,
                zoneId: route.zoneId,
                adopt: route.adopt ?? props.adopt,
            };
        }),
        api: {
            accountId: props.accountId,
            apiKey: props.apiKey,
            apiToken: props.apiToken,
            email: props.email,
            baseUrl: props.baseUrl,
            profile: props.profile,
        },
    };
    if (input.routes) {
        assertUnique(input.routes, "pattern", "Route");
    }
    if (input.domains) {
        assertUnique(input.domains, "name", "Custom Domain");
    }
    const [containers, domains, eventSources, routes] = await Promise.all([
        input.containers
            ? Promise.all(input.containers.map(async (container) => {
                return await ContainerApplication(container.id, {
                    ...container,
                    durableObjects: {
                        namespaceId: await getContainerNamespaceId(container),
                    },
                    delete: props.delete,
                    dev: options.local,
                    ...input.api,
                });
            }))
            : undefined,
        input.domains
            ? Promise.all(input.domains.map(async (domain) => {
                return await CustomDomain(domain.name, {
                    name: domain.name,
                    zoneId: domain.zoneId,
                    adopt: domain.adopt,
                    overrideExistingOrigin: domain.overrideExistingOrigin,
                    delete: props.delete,
                    workerName: options.name,
                    dev: options.local,
                    ...input.api,
                });
            }))
            : undefined,
        input.eventSources
            ? Promise.all(input.eventSources.map(async (eventSource) => {
                // In local dev mode, queue.id is "" (no Cloudflare API call).
                // Use queue.dev.id (resource ID, e.g. "email-queue") to avoid
                // all consumers colliding on the same "-consumer" resource ID.
                // See: https://github.com/alchemy-run/alchemy/issues/1363
                const queueConsumerId = options.local
                    ? eventSource.queue.dev?.id || eventSource.queue.id
                    : eventSource.queue.id;
                return await QueueConsumer(`${queueConsumerId}-consumer`, {
                    queue: eventSource.queue,
                    scriptName: options.name,
                    settings: eventSource.settings,
                    adopt: props.adopt,
                    delete: props.delete,
                    dev: options.local,
                    ...input.api,
                });
            }))
            : undefined,
        input.routes
            ? Promise.all(input.routes.map(async (route) => {
                return await Route(route.pattern, {
                    pattern: route.pattern,
                    script: options.name,
                    zoneId: route.zoneId,
                    adopt: route.adopt,
                    delete: props.delete,
                    dev: options.local,
                    ...input.api,
                });
            }))
            : undefined,
    ]);
    return { containers, domains, routes, eventSources };
    async function getContainerNamespaceId(container) {
        if (options.local) {
            return container.id;
        }
        metadataPromise ??= getVersionMetadata(options.api, options.name, options.result.deployment_id);
        const metadata = await metadataPromise;
        const binding = metadata.resources.bindings.find((binding) => binding.type === "durable_object_namespace" &&
            binding.class_name === container.className);
        if (!binding?.namespace_id) {
            throw new Error(`Container ${container.name} not found in version ${options.result.deployment_id}`);
        }
        return binding.namespace_id;
    }
}
const watchWorker = async (api, props, input) => {
    const promise = new DeferredPromise();
    const run = async () => {
        for await (const bundle of input.bundle.watch(input.controller.signal)) {
            if (promise.status === "pending") {
                try {
                    const result = await putWorker(api, {
                        ...props,
                        workerName: input.name,
                        scriptBundle: bundle,
                        dispatchNamespace: input.dispatchNamespace,
                        compatibilityDate: input.compatibilityDate,
                        compatibilityFlags: input.compatibilityFlags,
                        assetUploadResult: input.assets,
                    });
                    promise.resolve(result);
                }
                catch (error) {
                    input.controller.abort();
                    promise.reject(error);
                }
                continue;
            }
            logger.task("", {
                message: "reload",
                status: "success",
                resource: input.id,
                prefix: "build",
                prefixColor: "cyanBright",
            });
            await putWorker(api, {
                ...props,
                workerName: input.name,
                scriptBundle: bundle,
                dispatchNamespace: input.dispatchNamespace,
                version: input.version,
                compatibilityDate: input.compatibilityDate,
                compatibilityFlags: input.compatibilityFlags,
                assetUploadResult: input.assets
                    ? {
                        keepAssets: true,
                        assetConfig: props.assets,
                    }
                    : undefined,
                unstable_cacheWorkerSettings: true,
            });
            logger.task("", {
                message: "updated",
                status: "success",
                resource: input.id,
                prefix: "build",
                prefixColor: "greenBright",
            });
        }
    };
    void run();
    return await promise.value;
};
async function prepareWorkerUpload(api, props) {
    const scriptMetadata = await prepareWorkerMetadata(api, props);
    if (props.format === "cjs") {
        scriptMetadata.body_part = props.scriptBundle.entrypoint;
    }
    else {
        scriptMetadata.main_module = props.scriptBundle.entrypoint;
    }
    const body = await WorkerBundle.toFormData(props.scriptBundle);
    // Prepare metadata - add version annotations if this is a version
    const finalMetadata = props.version
        ? {
            ...scriptMetadata,
            // Exclude migrations for worker versions - they're not allowed
            migrations: undefined,
            annotations: {
                "workers/tag": props.version,
                "workers/message": `Version ${props.version}`,
            },
        }
        : {
            ...scriptMetadata,
            migrations: scriptMetadata.migrations
                ? {
                    ...scriptMetadata.migrations,
                    old_tag: props.migrationTag,
                    new_tag: bumpMigrationTagVersion(props.migrationTag),
                }
                : undefined,
        };
    body.append("metadata", new Blob([JSON.stringify(finalMetadata)], {
        type: "application/json",
    }));
    let endpoint;
    let method;
    if (props.version) {
        if (props.dispatchNamespace) {
            throw new Error("Worker Preview Versions are not supported in Workers for Platforms");
        }
        // Upload worker version using the versions API
        endpoint = `/accounts/${api.accountId}/workers/scripts/${props.workerName}/versions`;
        method = "POST";
    }
    else {
        // Upload worker script with bindings
        endpoint = props.dispatchNamespace
            ? `/accounts/${api.accountId}/workers/dispatch/namespaces/${props.dispatchNamespace}/scripts/${props.workerName}`
            : `/accounts/${api.accountId}/workers/scripts/${props.workerName}`;
        method = "PUT";
    }
    return {
        body,
        endpoint,
        method,
    };
}
export async function putWorker(api, props) {
    const { 
    //
    workerName, version, } = props;
    return withExponentialBackoff(async () => {
        const { body, endpoint, method } = await prepareWorkerUpload(api, props);
        try {
            return await extractCloudflareResult(version
                ? `upload version for worker "${workerName}"`
                : `upload worker script "${workerName}"`, api.fetch(endpoint, {
                method,
                headers: {
                    "Content-Type": "multipart/form-data",
                },
                body,
            }));
        }
        catch (error) {
            if (error instanceof CloudflareApiError && error.status === 412) {
                // this happens when adopting a Worker managed with Wrangler
                // because wrangler includes a migration tag and we do not
                // currently, the only way to discover the old_tag is through the error message
                // Get Worker Script Settings is meant to return it (according to the docs)
                // but it doesn't work at runtime
                //
                // so, we catch the error and parse out the tag and then retry
                const newTag = error.message.match(/when expected tag is ['"]?([^'"]+)['"]?/)?.[1];
                if (newTag) {
                    return await putWorker(api, {
                        ...props,
                        migrationTag: newTag,
                    });
                }
            }
            if (isCloudflareApiError(error, { code: 11001 }) &&
                !props.eventSources?.length) {
                // Error code 11001 means that the worker has event sources, but no `queue` handler is defined.
                // Normally, queue consumers are removed automatically since we're using the `QueueConsumer` resource,
                // but if no `queue` handler is defined, we need to remove them for Cloudflare to let us deploy.
                await deleteQueueConsumers(api, workerName);
                return await putWorker(api, props);
            }
            throw error;
        }
    }, (err) => err.status === 404 ||
        err.status === 500 ||
        err.status === 503 ||
        // this is a transient error that cloudflare throws randomly
        (err instanceof CloudflareApiError &&
            err.status === 400 &&
            err.message.match(/binding.*failed to generate/)), 
    // (err.status === 400 &&
    //   err.message.includes("100331") &&
    //   err.message.includes("Cannot use Durable Objects with Preview URLs")),
    10, 100);
}
const workerExists = async (api, options) => {
    const res = await api.get(options.dispatchNamespace
        ? `/accounts/${api.accountId}/workers/dispatch/namespaces/${options.dispatchNamespace}/scripts/${options.name}`
        : `/accounts/${api.accountId}/workers/scripts/${options.name}`);
    return res.status === 200;
};
async function assertWorkerDoesNotExist(api, scriptName) {
    const response = await api.get(`/accounts/${api.accountId}/workers/scripts/${scriptName}`);
    if (response.status === 404) {
        return true;
    }
    if (response.status === 200) {
        const metadata = await getScriptMetadata(api, scriptName);
        if (!metadata) {
            throw new Error(`Worker exists but failed to fetch metadata: ${response.status} ${response.statusText}`);
        }
        throw new Error(`Worker with name '${scriptName}' already exists. Please use a unique name.`);
    }
    throw new Error(`Error checking if worker exists: ${response.status} ${response.statusText} ${await response.text()}`);
}
export async function getScriptMetadata(api, scriptName) {
    const res = await api.get(`/accounts/${api.accountId}/workers/services/${scriptName}`);
    if (res.status === 404) {
        return;
    }
    if (!res.ok) {
        throw new Error(`Error getting worker script metadata: ${res.status} ${res.statusText}`);
    }
    const json = (await res.json());
    return json.result;
}
async function deleteQueueConsumers(api, scriptName) {
    const consumers = await listQueueConsumersForWorker(api, scriptName);
    await Promise.all(consumers.map(async (consumer) => {
        await deleteQueueConsumer(api, consumer.queueId, consumer.consumerId);
    }));
}
async function deleteWorkflows(api, scriptName) {
    const settings = await getWorkerSettings(api, scriptName);
    await Promise.all((settings?.bindings ?? []).map(async (binding) => {
        if (binding.type === "workflow" &&
            (binding.script_name === scriptName || !binding.script_name)) {
            await deleteWorkflow(api, binding.workflow_name);
        }
    }));
}
export async function deleteWorker(api, props) {
    await withExponentialBackoff(async () => {
        const deleteResponse = await api.delete(props.dispatchNamespace
            ? `/accounts/${api.accountId}/workers/dispatch/namespaces/${props.dispatchNamespace}/scripts/${props.scriptName}?force=true`
            : `/accounts/${api.accountId}/workers/scripts/${props.scriptName}?force=true`);
        if (!deleteResponse.ok && deleteResponse.status !== 404) {
            await handleApiError(deleteResponse, "delete", "worker", props.scriptName);
        }
    }, (err) => (err.status === 400 &&
        err.message.includes("is still referenced by service bindings in Workers")) ||
        err.status === 500 ||
        err.status === 503, 10, 100);
}
async function getVersionMetadata(api, scriptName, deploymentId) {
    const response = await api.get(`/accounts/${api.accountId}/workers/scripts/${scriptName}/versions/${deploymentId}`);
    const result = (await response.json());
    return result.result;
}
//# sourceMappingURL=worker.js.map