import fs from "node:fs";
import os from "node:os";
import path from "node:path";
import { createRequire } from "node:module";
export const DEFAULT_TOS_MULTIPART_THRESHOLD = 64 * 1024 * 1024;
export const DEFAULT_TOS_PART_SIZE = 20 * 1024 * 1024;
export const DEFAULT_TOS_TASK_NUM = 1;
export const DEFAULT_TOS_UPLOAD_MAX_ATTEMPTS = 4;
export const DEFAULT_TOS_UPLOAD_RETRY_BASE_DELAY_MS = 1_000;
const RETRYABLE_UPLOAD_ERROR_RE = /\b(ECONNRESET|ECONNREFUSED|ETIMEDOUT|ENOTFOUND|EAI_AGAIN|EPIPE|UND_ERR_SOCKET|UND_ERR_CONNECT_TIMEOUT|UND_ERR_HEADERS_TIMEOUT|UND_ERR_BODY_TIMEOUT|fetch failed|socket hang up|network.?error|timeout|50[0234]|service.?unavailable|bad.?gateway|internal.?server)\b/i;
function createTosClient(credentials, bucket, endpointOverride) {
    const _require = createRequire(import.meta.url);
    /* eslint-disable @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-call */
    const { TosClient } = _require("@volcengine/tos-sdk");
    return new TosClient({
        accessKeyId: credentials.accessKeyId,
        accessKeySecret: credentials.accessKeySecret,
        region: credentials.region,
        endpoint: endpointOverride ?? credentials.endpoint,
        bucket,
        secure: true,
    });
    /* eslint-enable @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-call */
}
export function formatTosUri(bucket, key) {
    return `tos://${bucket}/${key}`;
}
export function isRetryableTosUploadError(err) {
    const seen = new Set();
    let cur = err;
    while (cur && !seen.has(cur)) {
        seen.add(cur);
        if (cur instanceof Error) {
            if (RETRYABLE_UPLOAD_ERROR_RE.test(cur.message))
                return true;
            const code = cur.code;
            if (typeof code === "string" && RETRYABLE_UPLOAD_ERROR_RE.test(code))
                return true;
            cur = cur.cause;
            continue;
        }
        if (typeof cur === "string") {
            return RETRYABLE_UPLOAD_ERROR_RE.test(cur);
        }
        break;
    }
    return false;
}
export function formatTosError(err) {
    if (err instanceof Error) {
        const cause = err.cause;
        if (cause instanceof Error && cause.message && cause.message !== err.message) {
            return `${err.message} (cause: ${cause.message})`;
        }
        return err.message;
    }
    return String(err);
}
async function sleep(ms) {
    await new Promise((resolve) => {
        setTimeout(resolve, ms);
    });
}
export async function uploadTosObject(options) {
    const maxAttempts = options.maxAttempts ?? DEFAULT_TOS_UPLOAD_MAX_ATTEMPTS;
    const retryBaseDelayMs = options.retryBaseDelayMs ?? DEFAULT_TOS_UPLOAD_RETRY_BASE_DELAY_MS;
    for (let attempt = 1; attempt <= maxAttempts; attempt += 1) {
        try {
            await uploadTosObjectOnce(options);
            return;
        }
        catch (err) {
            if (attempt >= maxAttempts || !isRetryableTosUploadError(err))
                throw err;
            const base = retryBaseDelayMs * 2 ** (attempt - 1);
            const jitter = Math.floor(Math.random() * retryBaseDelayMs);
            const delayMs = base + jitter;
            const message = `Upload failed (${formatTosError(err)}); retrying in ${(delayMs / 1000).toFixed(1)}s (${attempt}/${maxAttempts})`;
            options.onRetry?.(options.logPrefix ? `${options.logPrefix} ${message}` : message);
            await sleep(delayMs);
        }
    }
}
async function uploadTosObjectOnce(options) {
    const stat = fs.statSync(options.filePath);
    const multipartThreshold = options.multipartThreshold ?? DEFAULT_TOS_MULTIPART_THRESHOLD;
    const partSize = options.partSize ?? DEFAULT_TOS_PART_SIZE;
    const taskNum = options.taskNum ?? DEFAULT_TOS_TASK_NUM;
    const checkpointDir = options.checkpointDir ?? path.join(os.tmpdir(), ".tos-checkpoints");
    const client = createTosClient(options.credentials, options.bucket);
    if (stat.size >= multipartThreshold) {
        fs.mkdirSync(checkpointDir, { recursive: true });
        await client.uploadFile({
            bucket: options.bucket,
            key: options.key,
            file: options.filePath,
            partSize,
            taskNum,
            checkpoint: checkpointDir,
            contentType: options.contentType,
            progress: (percent) => {
                options.onProgress?.({
                    uploadedBytes: Math.min(stat.size, Math.round(stat.size * percent)),
                    totalBytes: stat.size,
                    percent,
                });
            },
        });
        return;
    }
    const buffer = fs.readFileSync(options.filePath);
    await client.putObject({
        bucket: options.bucket,
        key: options.key,
        body: buffer,
        contentType: options.contentType,
    });
    options.onProgress?.({
        uploadedBytes: stat.size,
        totalBytes: stat.size,
        percent: 1,
    });
}
function normalizeTosSigningEndpoint(endpoint, bucket) {
    const bucketPrefix = `${bucket}.`;
    return endpoint.startsWith(bucketPrefix) ? endpoint.slice(bucketPrefix.length) : endpoint;
}
export function createTosSignedUrl(options) {
    const endpoint = normalizeTosSigningEndpoint(options.publicEndpoint ?? options.credentials.endpoint, options.bucket);
    const client = createTosClient(options.credentials, options.bucket, endpoint);
    return client.getPreSignedUrl({
        bucket: options.bucket,
        key: options.key,
        method: "GET",
        expires: options.expiresSeconds,
    });
}
//# sourceMappingURL=tos-storage.js.map