import {
  AWSClient,
  resolveS3Endpoint
} from "./chunk-zqtpg06c.js";
import {
  __require
} from "./chunk-v0bahtg2.js";

// src/aws/s3.ts
import * as crypto from "node:crypto";
import { readFileSync } from "node:fs";
import { readdir } from "node:fs/promises";
import { join } from "node:path";

// src/aws/credentials.ts
function resolveCredentials(profile) {
  if (profile) {
    const creds = loadProfileFromFile(profile);
    if (!creds) {
      throw new Error(`AWS profile '${profile}' not found in ~/.aws/credentials`);
    }
    return creds;
  }
  const envAccessKey = process.env.AWS_ACCESS_KEY_ID;
  const envSecretKey = process.env.AWS_SECRET_ACCESS_KEY;
  if (envAccessKey && envSecretKey) {
    return {
      accessKeyId: envAccessKey,
      secretAccessKey: envSecretKey,
      sessionToken: process.env.AWS_SESSION_TOKEN
    };
  }
  return loadProfileFromFile(process.env.AWS_PROFILE || "default") ?? { accessKeyId: "", secretAccessKey: "" };
}
function loadProfileFromFile(profile) {
  const { existsSync, readFileSync } = __require("node:fs");
  const { homedir } = __require("node:os");
  const { join } = __require("node:path");
  const credentialsPath = process.env.AWS_SHARED_CREDENTIALS_FILE || join(homedir(), ".aws", "credentials");
  if (!existsSync(credentialsPath))
    return null;
  const content = readFileSync(credentialsPath, "utf-8");
  let currentProfile = null;
  let accessKeyId;
  let secretAccessKey;
  let sessionToken;
  for (const line of content.split(`
`)) {
    const trimmed = line.trim();
    if (!trimmed || trimmed.startsWith("#") || trimmed.startsWith(";"))
      continue;
    const profileMatch = trimmed.match(/^\[([^\]]+)\]$/);
    if (profileMatch) {
      if (currentProfile === profile && accessKeyId && secretAccessKey) {
        return { accessKeyId, secretAccessKey, sessionToken };
      }
      currentProfile = profileMatch[1];
      accessKeyId = undefined;
      secretAccessKey = undefined;
      sessionToken = undefined;
      continue;
    }
    if (currentProfile === profile) {
      const [key, ...valueParts] = trimmed.split("=");
      const value = valueParts.join("=").trim();
      switch (key.trim().toLowerCase()) {
        case "aws_access_key_id":
          accessKeyId = value;
          break;
        case "aws_secret_access_key":
          secretAccessKey = value;
          break;
        case "aws_session_token":
          sessionToken = value;
          break;
      }
    }
  }
  if (currentProfile === profile && accessKeyId && secretAccessKey) {
    return { accessKeyId, secretAccessKey, sessionToken };
  }
  return null;
}

// src/aws/s3.ts
function toFetchBody(data) {
  const { buffer, byteOffset, byteLength } = data;
  if (buffer instanceof ArrayBuffer) {
    return buffer.slice(byteOffset, byteOffset + byteLength);
  }
  const copy = new ArrayBuffer(byteLength);
  new Uint8Array(copy).set(new Uint8Array(data));
  return copy;
}

class S3Client {
  client;
  region;
  explicitProfile;
  endpoint;
  endpointProtocol = "https";
  forcePathStyle;
  explicitCredentials;
  constructor(regionOrOptions = "us-east-1", profile, options) {
    const resolvedOptions = typeof regionOrOptions === "string" ? options : regionOrOptions;
    const region = typeof regionOrOptions === "string" ? regionOrOptions : regionOrOptions.region ?? "us-east-1";
    const resolvedProfile = typeof regionOrOptions === "string" ? profile : regionOrOptions.profile;
    const endpoint = resolvedOptions?.endpoint ? resolveS3Endpoint({ region, path: "/", endpoint: resolvedOptions.endpoint }) : undefined;
    this.region = region;
    this.explicitProfile = resolvedProfile;
    this.endpoint = endpoint?.host;
    this.endpointProtocol = endpoint?.protocol ?? "https";
    this.forcePathStyle = resolvedOptions?.forcePathStyle;
    this.explicitCredentials = resolvedOptions?.credentials;
    this.client = new AWSClient(resolvedOptions?.credentials, {
      profile: resolvedProfile,
      endpoint: resolvedOptions?.endpoint,
      forcePathStyle: resolvedOptions?.forcePathStyle
    });
  }
  getCredentials() {
    if (this.explicitCredentials?.accessKeyId && this.explicitCredentials.secretAccessKey) {
      return this.explicitCredentials;
    }
    const creds = resolveCredentials(this.explicitProfile);
    if (creds.accessKeyId && creds.secretAccessKey) {
      return creds;
    }
    throw new Error("S3 credentials not found. Set AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY (or pass explicit credentials/profile), or configure ~/.aws/credentials.");
  }
  s3BaseHost() {
    return this.endpoint || `s3.${this.region}.amazonaws.com`;
  }
  s3VirtualHost(bucket) {
    return this.forcePathStyle ? this.s3BaseHost() : `${bucket}.${this.s3BaseHost()}`;
  }
  objectPath(bucket, encodedKey) {
    return this.forcePathStyle ? `/${bucket}/${encodedKey}` : `/${encodedKey}`;
  }
  objectUrl(bucket, encodedKey) {
    return `${this.endpointProtocol}://${this.s3VirtualHost(bucket)}${this.objectPath(bucket, encodedKey)}`;
  }
  async listBuckets() {
    const result = await this.client.request({
      service: "s3",
      region: this.region,
      method: "GET",
      path: "/"
    });
    const buckets = [];
    const root = result?.ListAllMyBucketsResult ?? result;
    const bucketList = root?.Buckets?.Bucket;
    if (bucketList) {
      const list = Array.isArray(bucketList) ? bucketList : [bucketList];
      for (const b of list) {
        buckets.push({
          Name: b.Name,
          CreationDate: b.CreationDate
        });
      }
    }
    return { Buckets: buckets };
  }
  async createBucket(bucket, options) {
    const headers = {};
    if (options?.acl) {
      headers["x-amz-acl"] = options.acl;
    }
    let body;
    if (this.region !== "us-east-1" && !this.endpoint) {
      body = `<?xml version="1.0" encoding="UTF-8"?>
<CreateBucketConfiguration xmlns="http://s3.amazonaws.com/doc/2006-03-01/">
  <LocationConstraint>${this.region}</LocationConstraint>
</CreateBucketConfiguration>`;
      headers["Content-Type"] = "application/xml";
    }
    await this.client.request({
      service: "s3",
      region: this.region,
      method: "PUT",
      path: `/${bucket}`,
      headers,
      body
    });
  }
  async deleteBucket(bucket) {
    await this.client.request({
      service: "s3",
      region: this.region,
      method: "DELETE",
      path: `/${bucket}`
    });
  }
  async emptyAndDeleteBucket(bucket) {
    let hasMore = true;
    while (hasMore) {
      const objects = await this.listAllObjects({ bucket });
      if (objects.length === 0) {
        hasMore = false;
        break;
      }
      const keys = objects.map((obj) => obj.Key);
      for (let i = 0;i < keys.length; i += 1000) {
        const batch = keys.slice(i, i + 1000);
        await this.deleteObjects(bucket, batch);
      }
    }
    await this.deleteBucket(bucket);
  }
  async listAllObjects(options) {
    const allObjects = [];
    let continuationToken;
    do {
      const params = {
        "list-type": "2",
        "max-keys": "1000"
      };
      if (options.prefix) {
        params.prefix = options.prefix;
      }
      if (continuationToken) {
        params["continuation-token"] = continuationToken;
      }
      const result = await this.client.request({
        service: "s3",
        region: this.region,
        method: "GET",
        path: `/${options.bucket}`,
        queryParams: params
      });
      const root = result?.ListBucketResult ?? result;
      const contents = root?.Contents;
      if (contents) {
        const list = Array.isArray(contents) ? contents : [contents];
        for (const obj of list) {
          allObjects.push({
            Key: obj.Key,
            LastModified: obj.LastModified || "",
            Size: Number.parseInt(obj.Size || "0"),
            ETag: obj.ETag
          });
        }
      }
      const isTruncated = root?.IsTruncated;
      continuationToken = isTruncated === "true" || isTruncated === true ? root?.NextContinuationToken : undefined;
    } while (continuationToken);
    return allObjects;
  }
  async list(options) {
    const result = await this.client.request({
      service: "s3",
      region: this.region,
      method: "GET",
      path: `/${options.bucket}`
    });
    const objects = [];
    const root = result?.ListBucketResult ?? result;
    const contents = root?.Contents;
    if (contents) {
      const items = Array.isArray(contents) ? contents : [contents];
      for (const item of items) {
        if (options.prefix && !item.Key?.startsWith(options.prefix)) {
          continue;
        }
        objects.push({
          Key: item.Key || "",
          LastModified: item.LastModified || "",
          Size: Number.parseInt(item.Size || "0"),
          ETag: item.ETag
        });
        if (options.maxKeys && objects.length >= options.maxKeys) {
          break;
        }
      }
    }
    return objects;
  }
  async putObject(options) {
    const headers = {};
    if (options.acl) {
      headers["x-amz-acl"] = options.acl;
    }
    if (options.cacheControl) {
      headers["Cache-Control"] = options.cacheControl;
    }
    if (options.contentType) {
      headers["Content-Type"] = options.contentType;
    }
    if (options.metadata) {
      for (const [key, value] of Object.entries(options.metadata)) {
        headers[`x-amz-meta-${key}`] = value;
      }
    }
    const encodedKey = options.key.split("/").map((seg) => encodeURIComponent(seg)).join("/");
    const normalizedBody = options.body instanceof Uint8Array && !Buffer.isBuffer(options.body) ? Buffer.from(options.body) : options.body;
    if (Buffer.isBuffer(normalizedBody) || normalizedBody instanceof Uint8Array) {
      const binaryBody = Buffer.isBuffer(normalizedBody) ? normalizedBody : Buffer.from(normalizedBody);
      const { accessKeyId, secretAccessKey, sessionToken } = this.getCredentials();
      const host = this.s3VirtualHost(options.bucket);
      const canonicalUri = this.objectPath(options.bucket, encodedKey);
      const url = this.objectUrl(options.bucket, encodedKey);
      const now = new Date;
      const amzDate = now.toISOString().replace(/[:-]|\.\d{3}/g, "");
      const dateStamp = now.toISOString().slice(0, 10).replace(/-/g, "");
      const payloadHash = crypto.createHash("sha256").update(binaryBody).digest("hex");
      const requestHeaders = {
        host,
        "x-amz-date": amzDate,
        "x-amz-content-sha256": payloadHash,
        ...headers
      };
      if (sessionToken) {
        requestHeaders["x-amz-security-token"] = sessionToken;
      }
      const canonicalHeaders = Object.keys(requestHeaders).sort().map((key) => `${key.toLowerCase()}:${requestHeaders[key].trim()}
`).join("");
      const signedHeaders = Object.keys(requestHeaders).sort().map((key) => key.toLowerCase()).join(";");
      const canonicalRequest = ["PUT", canonicalUri, "", canonicalHeaders, signedHeaders, payloadHash].join(`
`);
      const algorithm = "AWS4-HMAC-SHA256";
      const credentialScope = `${dateStamp}/${this.region}/s3/aws4_request`;
      const stringToSign = [
        algorithm,
        amzDate,
        credentialScope,
        crypto.createHash("sha256").update(canonicalRequest).digest("hex")
      ].join(`
`);
      const kDate = crypto.createHmac("sha256", `AWS4${secretAccessKey}`).update(dateStamp).digest();
      const kRegion = crypto.createHmac("sha256", kDate).update(this.region).digest();
      const kService = crypto.createHmac("sha256", kRegion).update("s3").digest();
      const kSigning = crypto.createHmac("sha256", kService).update("aws4_request").digest();
      const signature = crypto.createHmac("sha256", kSigning).update(stringToSign).digest("hex");
      const authorizationHeader = `${algorithm} Credential=${accessKeyId}/${credentialScope}, SignedHeaders=${signedHeaders}, Signature=${signature}`;
      const response = await fetch(url, {
        method: "PUT",
        headers: {
          ...requestHeaders,
          Authorization: authorizationHeader
        },
        body: toFetchBody(binaryBody)
      });
      if (!response.ok) {
        const errorText = await response.text();
        throw new Error(`S3 PUT failed: ${response.status} ${errorText}`);
      }
      return;
    }
    await this.client.request({
      service: "s3",
      region: this.region,
      method: "PUT",
      path: `/${encodedKey}`,
      bucket: options.bucket,
      headers,
      body: options.body
    });
  }
  async getObject(bucket, key) {
    const encodedKey = key.split("/").map((seg) => encodeURIComponent(seg)).join("/");
    const result = await this.client.request({
      service: "s3",
      region: this.region,
      method: "GET",
      path: `/${encodedKey}`,
      bucket,
      rawResponse: true
    });
    return result;
  }
  async getObjectBytes(bucket, key) {
    const { accessKeyId, secretAccessKey, sessionToken } = this.getCredentials();
    const host = this.s3VirtualHost(bucket);
    const encodedKey = key.split("/").map((seg) => encodeURIComponent(seg)).join("/");
    const canonicalUri = this.objectPath(bucket, encodedKey);
    const url = this.objectUrl(bucket, encodedKey);
    const now = new Date;
    const amzDate = now.toISOString().replace(/[:-]|\.\d{3}/g, "");
    const dateStamp = now.toISOString().slice(0, 10).replace(/-/g, "");
    const payloadHash = crypto.createHash("sha256").update("").digest("hex");
    const requestHeaders = {
      host,
      "x-amz-date": amzDate,
      "x-amz-content-sha256": payloadHash
    };
    if (sessionToken) {
      requestHeaders["x-amz-security-token"] = sessionToken;
    }
    const canonicalHeaders = Object.keys(requestHeaders).sort().map((k) => `${k.toLowerCase()}:${requestHeaders[k].trim()}
`).join("");
    const signedHeaders = Object.keys(requestHeaders).sort().map((k) => k.toLowerCase()).join(";");
    const canonicalRequest = ["GET", canonicalUri, "", canonicalHeaders, signedHeaders, payloadHash].join(`
`);
    const algorithm = "AWS4-HMAC-SHA256";
    const credentialScope = `${dateStamp}/${this.region}/s3/aws4_request`;
    const stringToSign = [
      algorithm,
      amzDate,
      credentialScope,
      crypto.createHash("sha256").update(canonicalRequest).digest("hex")
    ].join(`
`);
    const kDate = crypto.createHmac("sha256", `AWS4${secretAccessKey}`).update(dateStamp).digest();
    const kRegion = crypto.createHmac("sha256", kDate).update(this.region).digest();
    const kService = crypto.createHmac("sha256", kRegion).update("s3").digest();
    const kSigning = crypto.createHmac("sha256", kService).update("aws4_request").digest();
    const signature = crypto.createHmac("sha256", kSigning).update(stringToSign).digest("hex");
    const authorizationHeader = `${algorithm} Credential=${accessKeyId}/${credentialScope}, SignedHeaders=${signedHeaders}, Signature=${signature}`;
    const response = await fetch(url, {
      method: "GET",
      headers: { ...requestHeaders, Authorization: authorizationHeader }
    });
    if (!response.ok) {
      const errorText = await response.text();
      throw new Error(`S3 GET failed: ${response.status} ${errorText}`);
    }
    const buffer = await response.arrayBuffer();
    const contentLengthHeader = response.headers.get("content-length");
    return {
      body: new Uint8Array(buffer),
      contentType: response.headers.get("content-type") ?? undefined,
      contentLength: contentLengthHeader ? Number.parseInt(contentLengthHeader, 10) : undefined
    };
  }
  async copyObject(options) {
    const headers = {
      "x-amz-copy-source": `/${options.sourceBucket}/${options.sourceKey}`
    };
    if (options.metadataDirective) {
      headers["x-amz-metadata-directive"] = options.metadataDirective;
    }
    if (options.contentType) {
      headers["Content-Type"] = options.contentType;
    }
    if (options.metadata) {
      for (const [key, value] of Object.entries(options.metadata)) {
        headers[`x-amz-meta-${key}`] = value;
      }
    }
    await this.client.request({
      service: "s3",
      region: this.region,
      method: "PUT",
      path: `/${options.destinationBucket}/${options.destinationKey}`,
      headers
    });
  }
  async deleteObject(bucket, key) {
    const encodedKey = key.split("/").map((seg) => encodeURIComponent(seg)).join("/");
    await this.client.request({
      service: "s3",
      region: this.region,
      method: "DELETE",
      path: `/${encodedKey}`,
      bucket
    });
  }
  async deleteObjects(bucket, keys) {
    const deleteXml = `<?xml version="1.0" encoding="UTF-8"?>
<Delete>
  ${keys.map((key) => `<Object><Key>${key}</Key></Object>`).join(`
  `)}
</Delete>`;
    const contentMd5 = crypto.createHash("md5").update(deleteXml).digest("base64");
    await this.client.request({
      service: "s3",
      region: this.region,
      method: "POST",
      path: `/${bucket}`,
      queryParams: { delete: "" },
      body: deleteXml,
      headers: {
        "Content-Type": "application/xml",
        "Content-MD5": contentMd5
      }
    });
  }
  async bucketExists(bucket) {
    try {
      await this.client.request({
        service: "s3",
        region: this.region,
        method: "HEAD",
        path: `/${bucket}`
      });
      return true;
    } catch {
      return false;
    }
  }
  async copy(options) {
    const fileContent = readFileSync(options.source);
    await this.putObject({
      bucket: options.bucket,
      key: options.key,
      body: fileContent,
      acl: options.acl,
      cacheControl: options.cacheControl,
      contentType: options.contentType,
      metadata: options.metadata
    });
  }
  async sync(options) {
    const files = await this.listFilesRecursive(options.source);
    for (const file of files) {
      if (options.exclude && options.exclude.some((pattern) => file.includes(pattern))) {
        continue;
      }
      if (options.include && !options.include.some((pattern) => file.includes(pattern))) {
        continue;
      }
      const relativePath = file.substring(options.source.length + 1);
      const s3Key = options.prefix ? `${options.prefix}/${relativePath}` : relativePath;
      if (!options.dryRun) {
        const fileContent = readFileSync(file);
        await this.putObject({
          bucket: options.bucket,
          key: s3Key,
          body: fileContent,
          acl: options.acl,
          cacheControl: options.cacheControl,
          contentType: options.contentType,
          metadata: options.metadata
        });
      }
    }
  }
  async delete(bucket, key) {
    await this.deleteObject(bucket, key);
  }
  async deletePrefix(bucket, prefix) {
    const objects = await this.list({ bucket, prefix });
    const keys = objects.map((obj) => obj.Key);
    if (keys.length > 0) {
      await this.deleteObjects(bucket, keys);
    }
  }
  async getBucketSize(bucket, prefix) {
    const objects = await this.list({ bucket, prefix });
    return objects.reduce((total, obj) => total + obj.Size, 0);
  }
  async listFilesRecursive(dir) {
    const files = [];
    const entries = await readdir(dir, { withFileTypes: true });
    for (const entry of entries) {
      const fullPath = join(dir, entry.name);
      if (entry.isDirectory()) {
        const subFiles = await this.listFilesRecursive(fullPath);
        files.push(...subFiles);
      } else {
        files.push(fullPath);
      }
    }
    return files;
  }
  async putBucketPolicy(bucket, policy) {
    const { accessKeyId, secretAccessKey, sessionToken } = this.getCredentials();
    const host = this.s3BaseHost();
    const policyString = typeof policy === "string" ? policy : JSON.stringify(policy);
    const now = new Date;
    const amzDate = now.toISOString().replace(/[:-]|\.\d{3}/g, "");
    const dateStamp = now.toISOString().slice(0, 10).replace(/-/g, "");
    const payloadHash = crypto.createHash("sha256").update(policyString).digest("hex");
    const canonicalUri = "/" + bucket;
    const canonicalQuerystring = "policy=";
    const requestHeaders = {
      host,
      "x-amz-date": amzDate,
      "x-amz-content-sha256": payloadHash,
      "content-type": "application/json"
    };
    if (sessionToken) {
      requestHeaders["x-amz-security-token"] = sessionToken;
    }
    const canonicalHeaders = Object.keys(requestHeaders).sort().map((key) => `${key.toLowerCase()}:${requestHeaders[key].trim()}
`).join("");
    const signedHeaders = Object.keys(requestHeaders).sort().map((key) => key.toLowerCase()).join(";");
    const canonicalRequest = [
      "PUT",
      canonicalUri,
      canonicalQuerystring,
      canonicalHeaders,
      signedHeaders,
      payloadHash
    ].join(`
`);
    const algorithm = "AWS4-HMAC-SHA256";
    const credentialScope = `${dateStamp}/${this.region}/s3/aws4_request`;
    const stringToSign = [
      algorithm,
      amzDate,
      credentialScope,
      crypto.createHash("sha256").update(canonicalRequest).digest("hex")
    ].join(`
`);
    const kDate = crypto.createHmac("sha256", "AWS4" + secretAccessKey).update(dateStamp).digest();
    const kRegion = crypto.createHmac("sha256", kDate).update(this.region).digest();
    const kService = crypto.createHmac("sha256", kRegion).update("s3").digest();
    const kSigning = crypto.createHmac("sha256", kService).update("aws4_request").digest();
    const signature = crypto.createHmac("sha256", kSigning).update(stringToSign).digest("hex");
    const authHeader = `${algorithm} Credential=${accessKeyId}/${credentialScope}, SignedHeaders=${signedHeaders}, Signature=${signature}`;
    const url = `${this.endpointProtocol}://${host}${canonicalUri}?${canonicalQuerystring}`;
    const response = await fetch(url, {
      method: "PUT",
      headers: {
        ...requestHeaders,
        Authorization: authHeader
      },
      body: policyString
    });
    if (!response.ok) {
      const text = await response.text();
      throw new Error(`Failed to put bucket policy: ${response.status} ${text}`);
    }
  }
  async getBucketPolicy(bucket) {
    const { accessKeyId, secretAccessKey, sessionToken } = this.getCredentials();
    const host = this.s3BaseHost();
    const now = new Date;
    const amzDate = now.toISOString().replace(/[:-]|\.\d{3}/g, "");
    const dateStamp = now.toISOString().slice(0, 10).replace(/-/g, "");
    const payloadHash = crypto.createHash("sha256").update("").digest("hex");
    const canonicalUri = "/" + bucket;
    const canonicalQuerystring = "policy=";
    const requestHeaders = {
      host,
      "x-amz-date": amzDate,
      "x-amz-content-sha256": payloadHash
    };
    if (sessionToken) {
      requestHeaders["x-amz-security-token"] = sessionToken;
    }
    const canonicalHeaders = Object.keys(requestHeaders).sort().map((key) => `${key.toLowerCase()}:${requestHeaders[key].trim()}
`).join("");
    const signedHeaders = Object.keys(requestHeaders).sort().map((key) => key.toLowerCase()).join(";");
    const canonicalRequest = [
      "GET",
      canonicalUri,
      canonicalQuerystring,
      canonicalHeaders,
      signedHeaders,
      payloadHash
    ].join(`
`);
    const algorithm = "AWS4-HMAC-SHA256";
    const credentialScope = `${dateStamp}/${this.region}/s3/aws4_request`;
    const stringToSign = [
      algorithm,
      amzDate,
      credentialScope,
      crypto.createHash("sha256").update(canonicalRequest).digest("hex")
    ].join(`
`);
    const kDate = crypto.createHmac("sha256", "AWS4" + secretAccessKey).update(dateStamp).digest();
    const kRegion = crypto.createHmac("sha256", kDate).update(this.region).digest();
    const kService = crypto.createHmac("sha256", kRegion).update("s3").digest();
    const kSigning = crypto.createHmac("sha256", kService).update("aws4_request").digest();
    const signature = crypto.createHmac("sha256", kSigning).update(stringToSign).digest("hex");
    const authHeader = `${algorithm} Credential=${accessKeyId}/${credentialScope}, SignedHeaders=${signedHeaders}, Signature=${signature}`;
    const url = `${this.endpointProtocol}://${host}${canonicalUri}?${canonicalQuerystring}`;
    const response = await fetch(url, {
      method: "GET",
      headers: {
        ...requestHeaders,
        Authorization: authHeader
      }
    });
    if (response.status === 404) {
      return null;
    }
    if (!response.ok) {
      const text2 = await response.text();
      throw new Error(`Failed to get bucket policy: ${response.status} ${text2}`);
    }
    const text = await response.text();
    return JSON.parse(text);
  }
  async deleteBucketPolicy(bucket) {
    await this.client.request({
      service: "s3",
      region: this.region,
      method: "DELETE",
      path: `/${bucket}`,
      queryParams: { policy: "" }
    });
  }
  async headBucket(bucket) {
    try {
      const result = await this.client.request({
        service: "s3",
        region: this.region,
        method: "HEAD",
        path: `/${bucket}`,
        returnHeaders: true
      });
      return { exists: true, region: result?.headers?.["x-amz-bucket-region"] };
    } catch (e) {
      if (e.statusCode === 404) {
        return { exists: false };
      }
      throw e;
    }
  }
  async headObject(bucket, key) {
    try {
      const result = await this.client.request({
        service: "s3",
        region: this.region,
        method: "HEAD",
        path: `/${bucket}/${key}`,
        returnHeaders: true
      });
      return {
        ContentLength: result?.headers?.["content-length"] ? parseInt(result.headers["content-length"]) : undefined,
        ContentType: result?.headers?.["content-type"],
        ETag: result?.headers?.["etag"],
        LastModified: result?.headers?.["last-modified"]
      };
    } catch (e) {
      if (e.statusCode === 404) {
        return null;
      }
      throw e;
    }
  }
  async getObjectBuffer(bucket, key) {
    const content = await this.getObject(bucket, key);
    return Buffer.from(content);
  }
  async getObjectJson(bucket, key) {
    const content = await this.getObject(bucket, key);
    return JSON.parse(content);
  }
  async putObjectJson(bucket, key, data, options) {
    await this.putObject({
      bucket,
      key,
      body: JSON.stringify(data),
      contentType: "application/json",
      ...options
    });
  }
  async getBucketVersioning(bucket) {
    const result = await this.client.request({
      service: "s3",
      region: this.region,
      method: "GET",
      path: `/${bucket}`,
      queryParams: { versioning: "" }
    });
    return {
      Status: result?.VersioningConfiguration?.Status
    };
  }
  async putBucketVersioning(bucket, status) {
    const body = `<?xml version="1.0" encoding="UTF-8"?>
<VersioningConfiguration xmlns="http://s3.amazonaws.com/doc/2006-03-01/">
  <Status>${status}</Status>
</VersioningConfiguration>`;
    await this.client.request({
      service: "s3",
      region: this.region,
      method: "PUT",
      path: `/${bucket}`,
      queryParams: { versioning: "" },
      headers: { "Content-Type": "application/xml" },
      body
    });
  }
  async getBucketLifecycleConfiguration(bucket) {
    try {
      const result = await this.client.request({
        service: "s3",
        region: this.region,
        method: "GET",
        path: `/${bucket}`,
        queryParams: { lifecycle: "" }
      });
      return result?.LifecycleConfiguration;
    } catch (e) {
      if (e.statusCode === 404) {
        return null;
      }
      throw e;
    }
  }
  async putBucketLifecycleConfiguration(bucket, rules) {
    const rulesXml = rules.map((rule) => {
      let ruleXml = `<Rule><ID>${rule.ID}</ID><Status>${rule.Status}</Status>`;
      if (rule.Filter) {
        ruleXml += `<Filter><Prefix>${rule.Filter.Prefix || ""}</Prefix></Filter>`;
      } else {
        ruleXml += "<Filter><Prefix></Prefix></Filter>";
      }
      if (rule.Expiration) {
        if (rule.Expiration.Days) {
          ruleXml += `<Expiration><Days>${rule.Expiration.Days}</Days></Expiration>`;
        } else if (rule.Expiration.Date) {
          ruleXml += `<Expiration><Date>${rule.Expiration.Date}</Date></Expiration>`;
        }
      }
      if (rule.Transitions) {
        for (const t of rule.Transitions) {
          ruleXml += `<Transition><Days>${t.Days}</Days><StorageClass>${t.StorageClass}</StorageClass></Transition>`;
        }
      }
      if (rule.NoncurrentVersionExpiration) {
        ruleXml += `<NoncurrentVersionExpiration><NoncurrentDays>${rule.NoncurrentVersionExpiration.NoncurrentDays}</NoncurrentDays></NoncurrentVersionExpiration>`;
      }
      ruleXml += "</Rule>";
      return ruleXml;
    }).join("");
    const body = `<?xml version="1.0" encoding="UTF-8"?>
<LifecycleConfiguration xmlns="http://s3.amazonaws.com/doc/2006-03-01/">
  ${rulesXml}
</LifecycleConfiguration>`;
    await this.client.request({
      service: "s3",
      region: this.region,
      method: "PUT",
      path: `/${bucket}`,
      queryParams: { lifecycle: "" },
      headers: { "Content-Type": "application/xml" },
      body
    });
  }
  async deleteBucketLifecycleConfiguration(bucket) {
    await this.client.request({
      service: "s3",
      region: this.region,
      method: "DELETE",
      path: `/${bucket}`,
      queryParams: { lifecycle: "" }
    });
  }
  async getBucketCors(bucket) {
    try {
      const result = await this.client.request({
        service: "s3",
        region: this.region,
        method: "GET",
        path: `/${bucket}`,
        queryParams: { cors: "" }
      });
      return result?.CORSConfiguration;
    } catch (e) {
      if (e.statusCode === 404) {
        return null;
      }
      throw e;
    }
  }
  async putBucketCors(bucket, rules) {
    const rulesXml = rules.map((rule) => {
      let ruleXml = "<CORSRule>";
      for (const origin of rule.AllowedOrigins) {
        ruleXml += `<AllowedOrigin>${origin}</AllowedOrigin>`;
      }
      for (const method of rule.AllowedMethods) {
        ruleXml += `<AllowedMethod>${method}</AllowedMethod>`;
      }
      if (rule.AllowedHeaders) {
        for (const header of rule.AllowedHeaders) {
          ruleXml += `<AllowedHeader>${header}</AllowedHeader>`;
        }
      }
      if (rule.ExposeHeaders) {
        for (const header of rule.ExposeHeaders) {
          ruleXml += `<ExposeHeader>${header}</ExposeHeader>`;
        }
      }
      if (rule.MaxAgeSeconds) {
        ruleXml += `<MaxAgeSeconds>${rule.MaxAgeSeconds}</MaxAgeSeconds>`;
      }
      ruleXml += "</CORSRule>";
      return ruleXml;
    }).join("");
    const body = `<?xml version="1.0" encoding="UTF-8"?>
<CORSConfiguration>
  ${rulesXml}
</CORSConfiguration>`;
    await this.client.request({
      service: "s3",
      region: this.region,
      method: "PUT",
      path: `/${bucket}`,
      queryParams: { cors: "" },
      headers: { "Content-Type": "application/xml" },
      body
    });
  }
  async deleteBucketCors(bucket) {
    await this.client.request({
      service: "s3",
      region: this.region,
      method: "DELETE",
      path: `/${bucket}`,
      queryParams: { cors: "" }
    });
  }
  async getBucketEncryption(bucket) {
    try {
      const result = await this.client.request({
        service: "s3",
        region: this.region,
        method: "GET",
        path: `/${bucket}`,
        queryParams: { encryption: "" }
      });
      return result?.ServerSideEncryptionConfiguration;
    } catch (e) {
      if (e.statusCode === 404) {
        return null;
      }
      throw e;
    }
  }
  async putBucketEncryption(bucket, sseAlgorithm, kmsKeyId) {
    let ruleXml = `<ApplyServerSideEncryptionByDefault><SSEAlgorithm>${sseAlgorithm}</SSEAlgorithm>`;
    if (kmsKeyId) {
      ruleXml += `<KMSMasterKeyID>${kmsKeyId}</KMSMasterKeyID>`;
    }
    ruleXml += "</ApplyServerSideEncryptionByDefault>";
    const body = `<?xml version="1.0" encoding="UTF-8"?>
<ServerSideEncryptionConfiguration xmlns="http://s3.amazonaws.com/doc/2006-03-01/">
  <Rule>${ruleXml}</Rule>
</ServerSideEncryptionConfiguration>`;
    await this.client.request({
      service: "s3",
      region: this.region,
      method: "PUT",
      path: `/${bucket}`,
      queryParams: { encryption: "" },
      headers: { "Content-Type": "application/xml" },
      body
    });
  }
  async deleteBucketEncryption(bucket) {
    await this.client.request({
      service: "s3",
      region: this.region,
      method: "DELETE",
      path: `/${bucket}`,
      queryParams: { encryption: "" }
    });
  }
  async getBucketTagging(bucket) {
    try {
      const result = await this.client.request({
        service: "s3",
        region: this.region,
        method: "GET",
        path: `/${bucket}`,
        queryParams: { tagging: "" }
      });
      const tagSet = result?.Tagging?.TagSet?.Tag;
      if (!tagSet)
        return [];
      return Array.isArray(tagSet) ? tagSet : [tagSet];
    } catch (e) {
      if (e.statusCode === 404) {
        return [];
      }
      throw e;
    }
  }
  async putBucketTagging(bucket, tags) {
    const tagsXml = tags.map((t) => `<Tag><Key>${t.Key}</Key><Value>${t.Value}</Value></Tag>`).join("");
    const body = `<?xml version="1.0" encoding="UTF-8"?>
<Tagging>
  <TagSet>${tagsXml}</TagSet>
</Tagging>`;
    await this.client.request({
      service: "s3",
      region: this.region,
      method: "PUT",
      path: `/${bucket}`,
      queryParams: { tagging: "" },
      headers: { "Content-Type": "application/xml" },
      body
    });
  }
  async deleteBucketTagging(bucket) {
    await this.client.request({
      service: "s3",
      region: this.region,
      method: "DELETE",
      path: `/${bucket}`,
      queryParams: { tagging: "" }
    });
  }
  async getObjectTagging(bucket, key) {
    try {
      const result = await this.client.request({
        service: "s3",
        region: this.region,
        method: "GET",
        path: `/${bucket}/${key}`,
        queryParams: { tagging: "" }
      });
      const tagSet = result?.Tagging?.TagSet?.Tag;
      if (!tagSet)
        return [];
      return Array.isArray(tagSet) ? tagSet : [tagSet];
    } catch (e) {
      if (e.statusCode === 404) {
        return [];
      }
      throw e;
    }
  }
  async putObjectTagging(bucket, key, tags) {
    const tagsXml = tags.map((t) => `<Tag><Key>${t.Key}</Key><Value>${t.Value}</Value></Tag>`).join("");
    const body = `<?xml version="1.0" encoding="UTF-8"?>
<Tagging>
  <TagSet>${tagsXml}</TagSet>
</Tagging>`;
    await this.client.request({
      service: "s3",
      region: this.region,
      method: "PUT",
      path: `/${bucket}/${key}`,
      queryParams: { tagging: "" },
      headers: { "Content-Type": "application/xml" },
      body
    });
  }
  async deleteObjectTagging(bucket, key) {
    await this.client.request({
      service: "s3",
      region: this.region,
      method: "DELETE",
      path: `/${bucket}/${key}`,
      queryParams: { tagging: "" }
    });
  }
  async getBucketAcl(bucket) {
    const result = await this.client.request({
      service: "s3",
      region: this.region,
      method: "GET",
      path: `/${bucket}`,
      queryParams: { acl: "" }
    });
    return result?.AccessControlPolicy;
  }
  async putBucketAcl(bucket, acl) {
    await this.client.request({
      service: "s3",
      region: this.region,
      method: "PUT",
      path: `/${bucket}`,
      queryParams: { acl: "" },
      headers: { "x-amz-acl": acl }
    });
  }
  async getObjectAcl(bucket, key) {
    const result = await this.client.request({
      service: "s3",
      region: this.region,
      method: "GET",
      path: `/${bucket}/${key}`,
      queryParams: { acl: "" }
    });
    return result?.AccessControlPolicy;
  }
  async putObjectAcl(bucket, key, acl) {
    await this.client.request({
      service: "s3",
      region: this.region,
      method: "PUT",
      path: `/${bucket}/${key}`,
      queryParams: { acl: "" },
      headers: { "x-amz-acl": acl }
    });
  }
  async getBucketLocation(bucket) {
    const result = await this.client.request({
      service: "s3",
      region: this.region,
      method: "GET",
      path: `/${bucket}`,
      queryParams: { location: "" }
    });
    return result?.LocationConstraint || "us-east-1";
  }
  async getBucketLogging(bucket) {
    const result = await this.client.request({
      service: "s3",
      region: this.region,
      method: "GET",
      path: `/${bucket}`,
      queryParams: { logging: "" }
    });
    return result?.BucketLoggingStatus;
  }
  async putBucketLogging(bucket, targetBucket, targetPrefix) {
    const body = `<?xml version="1.0" encoding="UTF-8"?>
<BucketLoggingStatus xmlns="http://s3.amazonaws.com/doc/2006-03-01/">
  <LoggingEnabled>
    <TargetBucket>${targetBucket}</TargetBucket>
    <TargetPrefix>${targetPrefix}</TargetPrefix>
  </LoggingEnabled>
</BucketLoggingStatus>`;
    await this.client.request({
      service: "s3",
      region: this.region,
      method: "PUT",
      path: `/${bucket}`,
      queryParams: { logging: "" },
      headers: { "Content-Type": "application/xml" },
      body
    });
  }
  async getBucketNotificationConfiguration(bucket) {
    const result = await this.client.request({
      service: "s3",
      region: this.region,
      method: "GET",
      path: `/${bucket}`,
      queryParams: { notification: "" }
    });
    return result?.NotificationConfiguration;
  }
  async putBucketNotificationConfiguration(bucket, config) {
    let configXml = "";
    if (config.LambdaFunctionConfigurations) {
      for (const c of config.LambdaFunctionConfigurations) {
        configXml += "<CloudFunctionConfiguration>";
        if (c.Id)
          configXml += `<Id>${c.Id}</Id>`;
        configXml += `<CloudFunction>${c.LambdaFunctionArn}</CloudFunction>`;
        for (const event of c.Events) {
          configXml += `<Event>${event}</Event>`;
        }
        if (c.Filter?.Key?.FilterRules) {
          configXml += "<Filter><S3Key>";
          for (const rule of c.Filter.Key.FilterRules) {
            configXml += `<FilterRule><Name>${rule.Name}</Name><Value>${rule.Value}</Value></FilterRule>`;
          }
          configXml += "</S3Key></Filter>";
        }
        configXml += "</CloudFunctionConfiguration>";
      }
    }
    if (config.TopicConfigurations) {
      for (const c of config.TopicConfigurations) {
        configXml += "<TopicConfiguration>";
        if (c.Id)
          configXml += `<Id>${c.Id}</Id>`;
        configXml += `<Topic>${c.TopicArn}</Topic>`;
        for (const event of c.Events) {
          configXml += `<Event>${event}</Event>`;
        }
        configXml += "</TopicConfiguration>";
      }
    }
    if (config.QueueConfigurations) {
      for (const c of config.QueueConfigurations) {
        configXml += "<QueueConfiguration>";
        if (c.Id)
          configXml += `<Id>${c.Id}</Id>`;
        configXml += `<Queue>${c.QueueArn}</Queue>`;
        for (const event of c.Events) {
          configXml += `<Event>${event}</Event>`;
        }
        configXml += "</QueueConfiguration>";
      }
    }
    const body = `<?xml version="1.0" encoding="UTF-8"?>
<NotificationConfiguration xmlns="http://s3.amazonaws.com/doc/2006-03-01/">
  ${configXml}
</NotificationConfiguration>`;
    await this.client.request({
      service: "s3",
      region: this.region,
      method: "PUT",
      path: `/${bucket}`,
      queryParams: { notification: "" },
      headers: { "Content-Type": "application/xml" },
      body
    });
  }
  async getBucketWebsite(bucket) {
    try {
      const result = await this.client.request({
        service: "s3",
        region: this.region,
        method: "GET",
        path: `/${bucket}`,
        queryParams: { website: "" }
      });
      return result?.WebsiteConfiguration;
    } catch (e) {
      if (e.statusCode === 404) {
        return null;
      }
      throw e;
    }
  }
  async putBucketWebsite(bucket, config) {
    let configXml = "";
    if (config.RedirectAllRequestsTo) {
      configXml = `<RedirectAllRequestsTo>
        <HostName>${config.RedirectAllRequestsTo.HostName}</HostName>
        ${config.RedirectAllRequestsTo.Protocol ? `<Protocol>${config.RedirectAllRequestsTo.Protocol}</Protocol>` : ""}
      </RedirectAllRequestsTo>`;
    } else {
      configXml = `<IndexDocument><Suffix>${config.IndexDocument}</Suffix></IndexDocument>`;
      if (config.ErrorDocument) {
        configXml += `<ErrorDocument><Key>${config.ErrorDocument}</Key></ErrorDocument>`;
      }
    }
    const body = `<?xml version="1.0" encoding="UTF-8"?>
<WebsiteConfiguration xmlns="http://s3.amazonaws.com/doc/2006-03-01/">
  ${configXml}
</WebsiteConfiguration>`;
    await this.client.request({
      service: "s3",
      region: this.region,
      method: "PUT",
      path: `/${bucket}`,
      queryParams: { website: "" },
      headers: { "Content-Type": "application/xml" },
      body
    });
  }
  async deleteBucketWebsite(bucket) {
    await this.client.request({
      service: "s3",
      region: this.region,
      method: "DELETE",
      path: `/${bucket}`,
      queryParams: { website: "" }
    });
  }
  async getBucketReplication(bucket) {
    try {
      const result = await this.client.request({
        service: "s3",
        region: this.region,
        method: "GET",
        path: `/${bucket}`,
        queryParams: { replication: "" }
      });
      return result?.ReplicationConfiguration;
    } catch (e) {
      if (e.statusCode === 404) {
        return null;
      }
      throw e;
    }
  }
  async deleteBucketReplication(bucket) {
    await this.client.request({
      service: "s3",
      region: this.region,
      method: "DELETE",
      path: `/${bucket}`,
      queryParams: { replication: "" }
    });
  }
  async getPublicAccessBlock(bucket) {
    try {
      const result = await this.client.request({
        service: "s3",
        region: this.region,
        method: "GET",
        path: `/${bucket}`,
        queryParams: { publicAccessBlock: "" }
      });
      return result?.PublicAccessBlockConfiguration;
    } catch (e) {
      if (e.statusCode === 404) {
        return null;
      }
      throw e;
    }
  }
  async putPublicAccessBlock(bucket, config) {
    const body = `<?xml version="1.0" encoding="UTF-8"?>
<PublicAccessBlockConfiguration xmlns="http://s3.amazonaws.com/doc/2006-03-01/">
  <BlockPublicAcls>${config.BlockPublicAcls ?? true}</BlockPublicAcls>
  <IgnorePublicAcls>${config.IgnorePublicAcls ?? true}</IgnorePublicAcls>
  <BlockPublicPolicy>${config.BlockPublicPolicy ?? true}</BlockPublicPolicy>
  <RestrictPublicBuckets>${config.RestrictPublicBuckets ?? true}</RestrictPublicBuckets>
</PublicAccessBlockConfiguration>`;
    await this.client.request({
      service: "s3",
      region: this.region,
      method: "PUT",
      path: `/${bucket}`,
      queryParams: { publicAccessBlock: "" },
      headers: { "Content-Type": "application/xml" },
      body
    });
  }
  async deletePublicAccessBlock(bucket) {
    await this.client.request({
      service: "s3",
      region: this.region,
      method: "DELETE",
      path: `/${bucket}`,
      queryParams: { publicAccessBlock: "" }
    });
  }
  generatePresignedGetUrl(bucket, key, expiresInSeconds = 3600) {
    const { accessKeyId, secretAccessKey } = this.getCredentials();
    const host = this.s3VirtualHost(bucket);
    const now = new Date;
    const amzDate = now.toISOString().replace(/[:-]|\.\d{3}/g, "");
    const dateStamp = now.toISOString().slice(0, 10).replace(/-/g, "");
    const credentialScope = `${dateStamp}/${this.region}/s3/aws4_request`;
    const credential = `${accessKeyId}/${credentialScope}`;
    const encodedKey = key.split("/").map((seg) => encodeURIComponent(seg)).join("/");
    const queryParams = new URLSearchParams({
      "X-Amz-Algorithm": "AWS4-HMAC-SHA256",
      "X-Amz-Credential": credential,
      "X-Amz-Date": amzDate,
      "X-Amz-Expires": expiresInSeconds.toString(),
      "X-Amz-SignedHeaders": "host"
    });
    const canonicalRequest = [
      "GET",
      this.objectPath(bucket, encodedKey),
      queryParams.toString(),
      `host:${host}
`,
      "host",
      "UNSIGNED-PAYLOAD"
    ].join(`
`);
    const stringToSign = [
      "AWS4-HMAC-SHA256",
      amzDate,
      credentialScope,
      crypto.createHash("sha256").update(canonicalRequest).digest("hex")
    ].join(`
`);
    const kDate = crypto.createHmac("sha256", `AWS4${secretAccessKey}`).update(dateStamp).digest();
    const kRegion = crypto.createHmac("sha256", kDate).update(this.region).digest();
    const kService = crypto.createHmac("sha256", kRegion).update("s3").digest();
    const kSigning = crypto.createHmac("sha256", kService).update("aws4_request").digest();
    const signature = crypto.createHmac("sha256", kSigning).update(stringToSign).digest("hex");
    queryParams.append("X-Amz-Signature", signature);
    return `${this.endpointProtocol}://${host}${this.objectPath(bucket, encodedKey)}?${queryParams.toString()}`;
  }
  generatePresignedPutUrl(bucket, key, contentType, expiresInSeconds = 3600) {
    const { accessKeyId, secretAccessKey } = this.getCredentials();
    const host = this.s3VirtualHost(bucket);
    const now = new Date;
    const amzDate = now.toISOString().replace(/[:-]|\.\d{3}/g, "");
    const dateStamp = now.toISOString().slice(0, 10).replace(/-/g, "");
    const credentialScope = `${dateStamp}/${this.region}/s3/aws4_request`;
    const credential = `${accessKeyId}/${credentialScope}`;
    const encodedKey = key.split("/").map((seg) => encodeURIComponent(seg)).join("/");
    const queryParams = new URLSearchParams({
      "X-Amz-Algorithm": "AWS4-HMAC-SHA256",
      "X-Amz-Credential": credential,
      "X-Amz-Date": amzDate,
      "X-Amz-Expires": expiresInSeconds.toString(),
      "X-Amz-SignedHeaders": "content-type;host"
    });
    const canonicalRequest = [
      "PUT",
      this.objectPath(bucket, encodedKey),
      queryParams.toString(),
      `content-type:${contentType}
host:${host}
`,
      "content-type;host",
      "UNSIGNED-PAYLOAD"
    ].join(`
`);
    const stringToSign = [
      "AWS4-HMAC-SHA256",
      amzDate,
      credentialScope,
      crypto.createHash("sha256").update(canonicalRequest).digest("hex")
    ].join(`
`);
    const kDate = crypto.createHmac("sha256", `AWS4${secretAccessKey}`).update(dateStamp).digest();
    const kRegion = crypto.createHmac("sha256", kDate).update(this.region).digest();
    const kService = crypto.createHmac("sha256", kRegion).update("s3").digest();
    const kSigning = crypto.createHmac("sha256", kService).update("aws4_request").digest();
    const signature = crypto.createHmac("sha256", kSigning).update(stringToSign).digest("hex");
    queryParams.append("X-Amz-Signature", signature);
    return `${this.endpointProtocol}://${host}${this.objectPath(bucket, encodedKey)}?${queryParams.toString()}`;
  }
  async createMultipartUpload(bucket, key, options) {
    const headers = {};
    if (options?.contentType) {
      headers["Content-Type"] = options.contentType;
    }
    if (options?.metadata) {
      for (const [k, v] of Object.entries(options.metadata)) {
        headers[`x-amz-meta-${k}`] = v;
      }
    }
    const encodedKey = key.split("/").map((segment) => encodeURIComponent(segment)).join("/");
    const result = await this.client.request({
      service: "s3",
      region: this.region,
      method: "POST",
      path: `/${encodedKey}`,
      bucket,
      queryParams: { uploads: "" },
      headers
    });
    return { UploadId: result?.InitiateMultipartUploadResult?.UploadId };
  }
  async uploadPart(bucket, key, uploadId, partNumber, body) {
    const { accessKeyId, secretAccessKey, sessionToken } = this.getCredentials();
    const host = this.s3VirtualHost(bucket);
    const encodedKey = key.split("/").map((segment) => encodeURIComponent(segment)).join("/");
    const canonicalUri = this.objectPath(bucket, encodedKey);
    const url = `${this.objectUrl(bucket, encodedKey)}?partNumber=${partNumber}&uploadId=${encodeURIComponent(uploadId)}`;
    const now = new Date;
    const amzDate = now.toISOString().replace(/[:-]|\.\d{3}/g, "");
    const dateStamp = now.toISOString().slice(0, 10).replace(/-/g, "");
    const payloadHash = crypto.createHash("sha256").update(body).digest("hex");
    const requestHeaders = {
      host,
      "x-amz-date": amzDate,
      "x-amz-content-sha256": payloadHash
    };
    if (sessionToken) {
      requestHeaders["x-amz-security-token"] = sessionToken;
    }
    const canonicalHeaders = Object.keys(requestHeaders).sort().map((k) => `${k.toLowerCase()}:${requestHeaders[k].trim()}
`).join("");
    const signedHeaders = Object.keys(requestHeaders).sort().map((k) => k.toLowerCase()).join(";");
    const canonicalRequest = [
      "PUT",
      canonicalUri,
      `partNumber=${partNumber}&uploadId=${encodeURIComponent(uploadId)}`,
      canonicalHeaders,
      signedHeaders,
      payloadHash
    ].join(`
`);
    const algorithm = "AWS4-HMAC-SHA256";
    const credentialScope = `${dateStamp}/${this.region}/s3/aws4_request`;
    const stringToSign = [
      algorithm,
      amzDate,
      credentialScope,
      crypto.createHash("sha256").update(canonicalRequest).digest("hex")
    ].join(`
`);
    const kDate = crypto.createHmac("sha256", `AWS4${secretAccessKey}`).update(dateStamp).digest();
    const kRegion = crypto.createHmac("sha256", kDate).update(this.region).digest();
    const kService = crypto.createHmac("sha256", kRegion).update("s3").digest();
    const kSigning = crypto.createHmac("sha256", kService).update("aws4_request").digest();
    const signature = crypto.createHmac("sha256", kSigning).update(stringToSign).digest("hex");
    const authHeader = `${algorithm} Credential=${accessKeyId}/${credentialScope}, SignedHeaders=${signedHeaders}, Signature=${signature}`;
    const response = await fetch(url, {
      method: "PUT",
      headers: {
        ...requestHeaders,
        Authorization: authHeader
      },
      body: toFetchBody(body)
    });
    if (!response.ok) {
      const text = await response.text();
      throw new Error(`Upload part failed: ${response.status} ${text}`);
    }
    return { ETag: response.headers.get("etag") || "" };
  }
  async completeMultipartUpload(bucket, key, uploadId, parts) {
    const encodedKey = key.split("/").map((segment) => encodeURIComponent(segment)).join("/");
    const partsXml = parts.sort((a, b) => a.PartNumber - b.PartNumber).map((p) => `<Part><PartNumber>${p.PartNumber}</PartNumber><ETag>${p.ETag}</ETag></Part>`).join("");
    const body = `<?xml version="1.0" encoding="UTF-8"?>
<CompleteMultipartUpload>${partsXml}</CompleteMultipartUpload>`;
    await this.client.request({
      service: "s3",
      region: this.region,
      method: "POST",
      path: `/${encodedKey}`,
      bucket,
      queryParams: { uploadId },
      headers: { "Content-Type": "application/xml" },
      body
    });
  }
  async abortMultipartUpload(bucket, key, uploadId) {
    const encodedKey = key.split("/").map((segment) => encodeURIComponent(segment)).join("/");
    await this.client.request({
      service: "s3",
      region: this.region,
      method: "DELETE",
      path: `/${encodedKey}`,
      bucket,
      queryParams: { uploadId }
    });
  }
  async listMultipartUploads(bucket) {
    const result = await this.client.request({
      service: "s3",
      region: this.region,
      method: "GET",
      path: "/",
      bucket,
      queryParams: { uploads: "" }
    });
    const uploads = result?.ListMultipartUploadsResult?.Upload;
    if (!uploads)
      return [];
    const list = Array.isArray(uploads) ? uploads : [uploads];
    return list.map((u) => ({
      Key: u.Key,
      UploadId: u.UploadId,
      Initiated: u.Initiated
    }));
  }
  async restoreObject(bucket, key, days, tier = "Standard") {
    const body = `<?xml version="1.0" encoding="UTF-8"?>
<RestoreRequest>
  <Days>${days}</Days>
  <GlacierJobParameters>
    <Tier>${tier}</Tier>
  </GlacierJobParameters>
</RestoreRequest>`;
    await this.client.request({
      service: "s3",
      region: this.region,
      method: "POST",
      path: `/${bucket}/${key}`,
      queryParams: { restore: "" },
      headers: { "Content-Type": "application/xml" },
      body
    });
  }
  async selectObjectContent(bucket, key, expression, inputFormat, outputFormat = "JSON") {
    let inputSerialization = "";
    if (inputFormat === "CSV") {
      inputSerialization = "<CSV><FileHeaderInfo>USE</FileHeaderInfo></CSV>";
    } else if (inputFormat === "JSON") {
      inputSerialization = "<JSON><Type>DOCUMENT</Type></JSON>";
    } else {
      inputSerialization = "<Parquet/>";
    }
    let outputSerialization = "";
    if (outputFormat === "CSV") {
      outputSerialization = "<CSV/>";
    } else {
      outputSerialization = "<JSON/>";
    }
    const body = `<?xml version="1.0" encoding="UTF-8"?>
<SelectObjectContentRequest>
  <Expression>${expression}</Expression>
  <ExpressionType>SQL</ExpressionType>
  <InputSerialization>${inputSerialization}</InputSerialization>
  <OutputSerialization>${outputSerialization}</OutputSerialization>
</SelectObjectContentRequest>`;
    const result = await this.client.request({
      service: "s3",
      region: this.region,
      method: "POST",
      path: `/${bucket}/${key}`,
      queryParams: { select: "", "select-type": "2" },
      headers: { "Content-Type": "application/xml" },
      body,
      rawResponse: true
    });
    return result;
  }
  async getSignedUrl(options) {
    const { bucket, key, expiresIn = 3600, operation = "getObject" } = options;
    const { accessKeyId, secretAccessKey, sessionToken } = this.getCredentials();
    const now = new Date;
    const amzDate = now.toISOString().replace(/[:-]|\.\d{3}/g, "");
    const dateStamp = now.toISOString().slice(0, 10).replace(/-/g, "");
    const host = this.s3VirtualHost(bucket);
    const method = operation === "putObject" ? "PUT" : "GET";
    const algorithm = "AWS4-HMAC-SHA256";
    const credentialScope = `${dateStamp}/${this.region}/s3/aws4_request`;
    const credential = `${accessKeyId}/${credentialScope}`;
    const queryParams = {
      "X-Amz-Algorithm": algorithm,
      "X-Amz-Credential": credential,
      "X-Amz-Date": amzDate,
      "X-Amz-Expires": expiresIn.toString(),
      "X-Amz-SignedHeaders": "host"
    };
    if (sessionToken) {
      queryParams["X-Amz-Security-Token"] = sessionToken;
    }
    const sortedParams = Object.keys(queryParams).sort();
    const canonicalQuerystring = sortedParams.map((k) => `${encodeURIComponent(k)}=${encodeURIComponent(queryParams[k])}`).join("&");
    const encodedKey = key.split("/").map((seg) => encodeURIComponent(seg)).join("/");
    const canonicalUri = this.objectPath(bucket, encodedKey);
    const canonicalHeaders = `host:${host}
`;
    const signedHeaders = "host";
    const payloadHash = "UNSIGNED-PAYLOAD";
    const canonicalRequest = [
      method,
      canonicalUri,
      canonicalQuerystring,
      canonicalHeaders,
      signedHeaders,
      payloadHash
    ].join(`
`);
    const stringToSign = [
      algorithm,
      amzDate,
      credentialScope,
      crypto.createHash("sha256").update(canonicalRequest).digest("hex")
    ].join(`
`);
    const kDate = crypto.createHmac("sha256", `AWS4${secretAccessKey}`).update(dateStamp).digest();
    const kRegion = crypto.createHmac("sha256", kDate).update(this.region).digest();
    const kService = crypto.createHmac("sha256", kRegion).update("s3").digest();
    const kSigning = crypto.createHmac("sha256", kService).update("aws4_request").digest();
    const signature = crypto.createHmac("sha256", kSigning).update(stringToSign).digest("hex");
    const presignedUrl = `${this.endpointProtocol}://${host}${canonicalUri}?${canonicalQuerystring}&X-Amz-Signature=${signature}`;
    return presignedUrl;
  }
  async listObjects(options) {
    const { bucket, prefix, maxKeys = 1000, continuationToken } = options;
    const queryParams = {
      "list-type": "2",
      "max-keys": maxKeys.toString()
    };
    if (prefix)
      queryParams.prefix = prefix;
    if (continuationToken)
      queryParams["continuation-token"] = continuationToken;
    const result = await this.client.request({
      service: "s3",
      region: this.region,
      method: "GET",
      path: `/${bucket}`,
      queryParams
    });
    const objects = [];
    const listResult = result?.ListBucketResult ?? result;
    if (listResult?.Contents) {
      const items = Array.isArray(listResult.Contents) ? listResult.Contents : [listResult.Contents];
      for (const item of items) {
        objects.push({
          Key: item.Key || "",
          LastModified: item.LastModified || "",
          Size: Number.parseInt(item.Size || "0"),
          ETag: item.ETag
        });
      }
    }
    return {
      objects,
      nextContinuationToken: listResult?.NextContinuationToken
    };
  }
  async emptyBucket(bucket) {
    let deletedCount = 0;
    const objects = await this.listAllObjects({ bucket });
    if (objects.length > 0) {
      for (let i = 0;i < objects.length; i += 1000) {
        const batch = objects.slice(i, i + 1000);
        const keys = batch.map((obj) => obj.Key);
        await this.deleteObjects(bucket, keys);
        deletedCount += keys.length;
      }
    }
    try {
      let keyMarker;
      let versionIdMarker;
      do {
        const versionsResult = await this.listObjectVersions({
          bucket,
          keyMarker,
          versionIdMarker,
          maxKeys: 1000
        });
        const versionsToDelete = [];
        if (versionsResult.versions) {
          for (const version of versionsResult.versions) {
            versionsToDelete.push({ Key: version.Key, VersionId: version.VersionId });
          }
        }
        if (versionsResult.deleteMarkers) {
          for (const marker of versionsResult.deleteMarkers) {
            versionsToDelete.push({ Key: marker.Key, VersionId: marker.VersionId });
          }
        }
        if (versionsToDelete.length > 0) {
          await this.deleteObjectVersions(bucket, versionsToDelete);
          deletedCount += versionsToDelete.length;
        }
        keyMarker = versionsResult.nextKeyMarker;
        versionIdMarker = versionsResult.nextVersionIdMarker;
      } while (keyMarker);
    } catch {}
    return { deletedCount };
  }
  async listObjectVersions(options) {
    const { bucket, prefix, keyMarker, versionIdMarker, maxKeys = 1000 } = options;
    const queryParams = {
      versions: "",
      "max-keys": maxKeys.toString()
    };
    if (prefix)
      queryParams.prefix = prefix;
    if (keyMarker)
      queryParams["key-marker"] = keyMarker;
    if (versionIdMarker)
      queryParams["version-id-marker"] = versionIdMarker;
    const result = await this.client.request({
      service: "s3",
      region: this.region,
      method: "GET",
      path: `/${bucket}`,
      queryParams
    });
    const versions = [];
    const deleteMarkers = [];
    if (result.Version) {
      const versionList = Array.isArray(result.Version) ? result.Version : [result.Version];
      for (const v of versionList) {
        versions.push({
          Key: v.Key,
          VersionId: v.VersionId,
          IsLatest: v.IsLatest === "true"
        });
      }
    }
    if (result.DeleteMarker) {
      const markerList = Array.isArray(result.DeleteMarker) ? result.DeleteMarker : [result.DeleteMarker];
      for (const m of markerList) {
        deleteMarkers.push({
          Key: m.Key,
          VersionId: m.VersionId,
          IsLatest: m.IsLatest === "true"
        });
      }
    }
    return {
      versions,
      deleteMarkers,
      nextKeyMarker: result.NextKeyMarker,
      nextVersionIdMarker: result.NextVersionIdMarker
    };
  }
  async deleteObjectVersions(bucket, objects) {
    const deleteXml = `<?xml version="1.0" encoding="UTF-8"?>
<Delete>
  <Quiet>true</Quiet>
  ${objects.map((obj) => `<Object><Key>${obj.Key}</Key>${obj.VersionId ? `<VersionId>${obj.VersionId}</VersionId>` : ""}</Object>`).join(`
  `)}
</Delete>`;
    const contentMd5 = crypto.createHash("md5").update(deleteXml).digest("base64");
    await this.client.request({
      service: "s3",
      region: this.region,
      method: "POST",
      path: `/${bucket}`,
      queryParams: { delete: "" },
      body: deleteXml,
      headers: {
        "Content-Type": "application/xml",
        "Content-MD5": contentMd5
      }
    });
  }
}

export { resolveCredentials, S3Client };
