import { t as EmDashStorageError } from "../types-CUBbjgmP.mjs";
import { DeleteObjectCommand, GetObjectCommand, HeadObjectCommand, ListObjectsV2Command, PutObjectCommand, S3Client } from "@aws-sdk/client-s3";
import { getSignedUrl } from "@aws-sdk/s3-request-presigner";

//#region src/storage/s3.ts
/**
* S3-Compatible Storage Implementation
*
* Uses the AWS SDK v3 for S3 operations.
* Works with AWS S3, Cloudflare R2, Minio, and other S3-compatible services.
*/
const TRAILING_SLASH_PATTERN = /\/$/;
/** Type guard for AWS SDK errors (have a `name` property) */
function hasErrorName(error) {
	return error instanceof Error && typeof error.name === "string";
}
/**
* S3-compatible storage implementation
*/
var S3Storage = class {
	client;
	bucket;
	publicUrl;
	endpoint;
	constructor(config) {
		this.bucket = config.bucket;
		this.publicUrl = config.publicUrl;
		this.endpoint = config.endpoint;
		this.client = new S3Client({
			endpoint: config.endpoint,
			region: config.region || "auto",
			credentials: {
				accessKeyId: config.accessKeyId,
				secretAccessKey: config.secretAccessKey
			},
			forcePathStyle: true
		});
	}
	async upload(options) {
		try {
			let body;
			if (options.body instanceof ReadableStream) {
				const chunks = [];
				const reader = options.body.getReader();
				while (true) {
					const { done, value } = await reader.read();
					if (done) break;
					chunks.push(value);
				}
				body = Buffer.concat(chunks);
			} else body = options.body;
			await this.client.send(new PutObjectCommand({
				Bucket: this.bucket,
				Key: options.key,
				Body: body,
				ContentType: options.contentType
			}));
			return {
				key: options.key,
				url: this.getPublicUrl(options.key),
				size: body.length
			};
		} catch (error) {
			throw new EmDashStorageError(`Failed to upload file: ${options.key}`, "UPLOAD_FAILED", error);
		}
	}
	async download(key) {
		try {
			const response = await this.client.send(new GetObjectCommand({
				Bucket: this.bucket,
				Key: key
			}));
			if (!response.Body) throw new EmDashStorageError(`File not found: ${key}`, "NOT_FOUND");
			return {
				body: response.Body.transformToWebStream(),
				contentType: response.ContentType || "application/octet-stream",
				size: response.ContentLength || 0
			};
		} catch (error) {
			if (error instanceof EmDashStorageError || hasErrorName(error) && error.name === "NoSuchKey") throw new EmDashStorageError(`File not found: ${key}`, "NOT_FOUND", error);
			throw new EmDashStorageError(`Failed to download file: ${key}`, "DOWNLOAD_FAILED", error);
		}
	}
	async delete(key) {
		try {
			await this.client.send(new DeleteObjectCommand({
				Bucket: this.bucket,
				Key: key
			}));
		} catch (error) {
			if (!hasErrorName(error) || error.name !== "NoSuchKey") throw new EmDashStorageError(`Failed to delete file: ${key}`, "DELETE_FAILED", error);
		}
	}
	async exists(key) {
		try {
			await this.client.send(new HeadObjectCommand({
				Bucket: this.bucket,
				Key: key
			}));
			return true;
		} catch (error) {
			if (hasErrorName(error) && error.name === "NotFound") return false;
			throw new EmDashStorageError(`Failed to check file existence: ${key}`, "HEAD_FAILED", error);
		}
	}
	async list(options = {}) {
		try {
			const response = await this.client.send(new ListObjectsV2Command({
				Bucket: this.bucket,
				Prefix: options.prefix,
				MaxKeys: options.limit,
				ContinuationToken: options.cursor
			}));
			return {
				files: (response.Contents || []).map((item) => ({
					key: item.Key,
					size: item.Size || 0,
					lastModified: item.LastModified || /* @__PURE__ */ new Date(),
					etag: item.ETag
				})),
				nextCursor: response.NextContinuationToken
			};
		} catch (error) {
			throw new EmDashStorageError("Failed to list files", "LIST_FAILED", error);
		}
	}
	async getSignedUploadUrl(options) {
		try {
			const expiresIn = options.expiresIn || 3600;
			const command = new PutObjectCommand({
				Bucket: this.bucket,
				Key: options.key,
				ContentType: options.contentType,
				ContentLength: options.size
			});
			const url = await getSignedUrl(this.client, command, { expiresIn });
			const expiresAt = new Date(Date.now() + expiresIn * 1e3).toISOString();
			return {
				url,
				method: "PUT",
				headers: {
					"Content-Type": options.contentType,
					...options.size ? { "Content-Length": String(options.size) } : {}
				},
				expiresAt
			};
		} catch (error) {
			throw new EmDashStorageError(`Failed to generate signed URL for: ${options.key}`, "SIGNED_URL_FAILED", error);
		}
	}
	getPublicUrl(key) {
		if (this.publicUrl) return `${this.publicUrl.replace(TRAILING_SLASH_PATTERN, "")}/${key}`;
		return `${this.endpoint.replace(TRAILING_SLASH_PATTERN, "")}/${this.bucket}/${key}`;
	}
};
/**
* Create S3 storage adapter
* This is the factory function called at runtime
*/
function createStorage(config) {
	const { endpoint, bucket, accessKeyId, secretAccessKey, region, publicUrl } = config;
	if (typeof endpoint !== "string" || typeof bucket !== "string" || typeof accessKeyId !== "string" || typeof secretAccessKey !== "string") throw new Error("S3Storage requires 'endpoint', 'bucket', 'accessKeyId', and 'secretAccessKey' string config values");
	return new S3Storage({
		endpoint,
		bucket,
		accessKeyId,
		secretAccessKey,
		region: typeof region === "string" ? region : void 0,
		publicUrl: typeof publicUrl === "string" ? publicUrl : void 0
	});
}

//#endregion
export { S3Storage, createStorage };
//# sourceMappingURL=s3.mjs.map