import fs from "node:fs/promises";
import path from "pathe";
import { AsyncQueue } from "../util/async-queue.js";
import { withExponentialBackoff } from "../util/retry.js";
import { extractCloudflareResult } from "./api-response.js";
import { Assets } from "./assets.js";
/**
 * Uploads assets to Cloudflare and returns a completion token
 *
 * @param api CloudflareApi instance
 * @param workerName Name of the worker
 * @param assets Assets resource containing files to upload
 * @param assetConfig Configuration for the assets
 * @returns Completion token for the assets upload
 */
export async function uploadAssets(api, { workerName, assets, assetConfig, namespace, }) {
    // Process the assets configuration once at the beginning
    const processedConfig = await createAssetConfigWithFiles(assets.path, assetConfig);
    const manifest = {};
    const filesByHash = new Map();
    for (const file of await Assets.read(assets.path)) {
        manifest[file.name] = { hash: file.hash, size: file.size };
        filesByHash.set(file.hash, file);
    }
    // Start the upload session
    const uploadSession = await extractCloudflareResult(`create assets upload session for worker "${workerName}"`, api.post(namespace
        ? `/accounts/${api.accountId}/workers/dispatch/namespaces/${namespace}/scripts/${workerName}/assets-upload-session`
        : `/accounts/${api.accountId}/workers/scripts/${workerName}/assets-upload-session`, { manifest }));
    // If there are no buckets, assets are already uploaded or empty
    if (!uploadSession.buckets || uploadSession.buckets.length === 0) {
        return {
            completionToken: uploadSession.jwt,
            assetConfig: processedConfig,
        };
    }
    // Upload the files in batches as specified by the API
    let completionToken = uploadSession.jwt;
    const buckets = uploadSession.buckets;
    const queue = new AsyncQueue(3);
    await Promise.all(buckets.map((bucket) => queue.add(async () => {
        const jwt = await withExponentialBackoff(async () => await uploadBucket(api, uploadSession.jwt, bucket, filesByHash), () => true);
        if (jwt) {
            completionToken = jwt;
        }
    })));
    // Return the final completion token with asset configuration
    return {
        completionToken,
        assetConfig: processedConfig,
    };
}
async function createAssetConfigWithFiles(assetsPath, config = {}) {
    const [_headers, _redirects] = await Promise.all([
        readFileOrProperty("_headers"),
        readFileOrProperty("_redirects"),
    ]);
    return createAssetConfig({
        ...config,
        _headers,
        _redirects,
    });
    async function readFileOrProperty(property) {
        const content = await fs
            .readFile(path.join(assetsPath, property), "utf-8")
            .catch(() => undefined);
        if (content && config[property]) {
            throw new Error(`Cannot use both ${property} file and ${property} property`);
        }
        return content ?? config[property];
    }
}
/**
 * Creates asset configuration object from provided config or defaults
 */
export function createAssetConfig(config) {
    const assetConfig = {
        html_handling: "auto-trailing-slash",
    };
    if (config) {
        if (config._headers !== undefined) {
            assetConfig._headers = config._headers;
        }
        if (config._redirects !== undefined) {
            assetConfig._redirects = config._redirects;
        }
        if (config.html_handling !== undefined) {
            assetConfig.html_handling = config.html_handling;
        }
        if (config.not_found_handling !== undefined) {
            assetConfig.not_found_handling = config.not_found_handling;
        }
        if (config.run_worker_first !== undefined) {
            assetConfig.run_worker_first = config.run_worker_first;
        }
    }
    return assetConfig;
}
async function uploadBucket(api, jwt, bucket, filesByHash) {
    const formData = new FormData();
    await Promise.all(bucket.map(async (fileHash) => {
        const file = filesByHash.get(fileHash);
        if (!file) {
            throw new Error(`Could not find file with hash ${fileHash}`);
        }
        const fileContent = await fs.readFile(file.path);
        const blob = new Blob([fileContent.toString("base64")], {
            type: file.type,
        });
        formData.append(fileHash, blob, fileHash);
    }));
    const uploadResult = await extractCloudflareResult("upload asset files", api.post(`/accounts/${api.accountId}/workers/assets/upload?base64=true`, formData, {
        headers: {
            Authorization: `Bearer ${jwt}`,
        },
    }));
    return uploadResult.jwt;
}
//# sourceMappingURL=worker-assets.js.map