import {
  AWSClient
} from "./chunk-zqtpg06c.js";

// src/aws/cloudfront.ts
class CloudFrontClient {
  client;
  constructor(profile, transport) {
    this.client = transport ?? new AWSClient(undefined, { profile });
  }
  collectionItems(value, child) {
    const items = value?.Items ?? value?.[child];
    if (!items)
      return [];
    if (Array.isArray(items))
      return items;
    const nested = items[child] ?? items.Item;
    if (nested === undefined)
      return typeof items === "object" && (items.Id || items.PathPattern) ? [items] : [];
    return Array.isArray(nested) ? nested : [nested];
  }
  validateExistingOriginInput(distributionId, input) {
    if (!/^[A-Z0-9]{8,32}$/.test(distributionId))
      throw new Error("CloudFront distribution ID is invalid");
    if (!/^[A-Za-z0-9][A-Za-z0-9_.-]{0,127}$/.test(input.id))
      throw new Error("CloudFront origin ID is invalid");
    if (!/^(?=.{1,253}$)(?:[A-Za-z0-9](?:[A-Za-z0-9-]{0,61}[A-Za-z0-9])?\.)+[A-Za-z0-9](?:[A-Za-z0-9-]{0,61}[A-Za-z0-9])?$/.test(input.domainName))
      throw new Error("CloudFront origin must be a hostname without a scheme or path");
    const pathPattern = input.pathPattern.startsWith("/") ? input.pathPattern : `/${input.pathPattern}`;
    if (pathPattern === "/" || pathPattern === "/*" || !/^\/[A-Za-z0-9_.*?~!$&'()+,;=:@%/-]{1,255}$/.test(pathPattern))
      throw new Error("CloudFront path pattern must be a non-default absolute pattern");
    if (input.originPath && (!input.originPath.startsWith("/") || input.originPath.includes("..")))
      throw new Error("CloudFront origin path must be absolute and traversal-free");
    if (Object.entries(input.customHeaders ?? {}).some(([name, value]) => !/^[A-Za-z0-9-]{1,64}$/.test(name) || !value || value.length > 1024))
      throw new Error("CloudFront custom origin headers are invalid");
    return { ...input, pathPattern };
  }
  async upsertExistingDistributionOrigin(distributionId, rawInput) {
    const input = this.validateExistingOriginInput(distributionId, rawInput);
    const getResult = await this.client.request({
      service: "cloudfront",
      region: "us-east-1",
      method: "GET",
      path: `/2020-05-31/distribution/${distributionId}/config`,
      returnHeaders: true
    });
    const etag = getResult.headers?.etag || getResult.headers?.ETag || getResult.ETag || "";
    const config = getResult.body?.DistributionConfig || getResult.DistributionConfig || getResult.body;
    if (!config || config.Enabled === undefined || !etag)
      throw new Error("Failed to read an ETag-protected CloudFront distribution config");
    const origins = this.collectionItems(config.Origins, "Origin");
    const behaviors = this.collectionItems(config.CacheBehaviors, "CacheBehavior");
    const existingOrigin = origins.find((value) => String(value.Id) === input.id);
    const existingBehavior = behaviors.find((value) => String(value.PathPattern) === input.pathPattern);
    if (existingOrigin && String(existingOrigin.DomainName) !== input.domainName && !input.replaceExisting)
      throw new Error(`Origin ${input.id} already targets ${existingOrigin.DomainName}; pass replaceExisting only after reviewing the live config`);
    if (existingBehavior && String(existingBehavior.TargetOriginId) !== input.id && !input.replaceExisting)
      throw new Error(`Path ${input.pathPattern} already targets ${existingBehavior.TargetOriginId}; pass replaceExisting only after reviewing the live config`);
    const origin = {
      Id: input.id,
      DomainName: input.domainName,
      OriginPath: input.originPath ?? "",
      CustomHeaders: {
        Quantity: Object.keys(input.customHeaders ?? {}).length,
        ...Object.keys(input.customHeaders ?? {}).length ? {
          Items: {
            OriginCustomHeader: Object.entries(input.customHeaders).map(([HeaderName, HeaderValue]) => ({
              HeaderName,
              HeaderValue
            }))
          }
        } : {}
      },
      CustomOriginConfig: {
        HTTPPort: 80,
        HTTPSPort: 443,
        OriginProtocolPolicy: input.protocolPolicy ?? "https-only",
        OriginSslProtocols: { Quantity: 1, Items: { SslProtocol: ["TLSv1.2"] } },
        OriginReadTimeout: 30,
        OriginKeepaliveTimeout: 5
      },
      ConnectionAttempts: 3,
      ConnectionTimeout: 10,
      ...input.originAccessControlId ? { OriginAccessControlId: input.originAccessControlId } : {}
    };
    const methods = ["GET", "HEAD", "OPTIONS", "PUT", "POST", "PATCH", "DELETE"];
    const behavior = {
      PathPattern: input.pathPattern,
      TargetOriginId: input.id,
      ViewerProtocolPolicy: "redirect-to-https",
      Compress: true,
      CachePolicyId: "4135ea2d-6df8-44a3-9df3-4b5a84be39ad",
      OriginRequestPolicyId: "b689b0a8-53d0-40ab-baf2-68738e2966ac",
      AllowedMethods: {
        Quantity: methods.length,
        Items: { Method: methods },
        CachedMethods: { Quantity: 2, Items: { Method: ["GET", "HEAD"] } }
      },
      SmoothStreaming: false,
      FieldLevelEncryptionId: "",
      TrustedSigners: { Enabled: false, Quantity: 0 },
      TrustedKeyGroups: { Enabled: false, Quantity: 0 },
      LambdaFunctionAssociations: { Quantity: 0 },
      FunctionAssociations: { Quantity: 0 }
    };
    const nextOrigins = existingOrigin ? origins.map((value) => String(value.Id) === input.id ? origin : value) : [...origins, origin];
    const nextBehaviors = existingBehavior ? behaviors.map((value) => String(value.PathPattern) === input.pathPattern ? behavior : value) : [...behaviors, behavior];
    const changed = JSON.stringify(origins) !== JSON.stringify(nextOrigins) || JSON.stringify(behaviors) !== JSON.stringify(nextBehaviors);
    if (!changed || input.dryRun)
      return {
        distributionId,
        originId: input.id,
        domainName: input.domainName,
        pathPattern: input.pathPattern,
        changed,
        applied: false,
        etag
      };
    config.Origins = { Quantity: nextOrigins.length, Items: { Origin: nextOrigins } };
    config.CacheBehaviors = {
      Quantity: nextBehaviors.length,
      ...nextBehaviors.length ? { Items: { CacheBehavior: nextBehaviors } } : {}
    };
    const result = await this.client.request({
      service: "cloudfront",
      region: "us-east-1",
      method: "PUT",
      path: `/2020-05-31/distribution/${distributionId}/config`,
      body: this.buildDistributionConfigXml(config),
      headers: { "Content-Type": "application/xml", "If-Match": etag }
    });
    return {
      distributionId,
      originId: input.id,
      domainName: input.domainName,
      pathPattern: input.pathPattern,
      changed: true,
      applied: true,
      etag: result.ETag || result.headers?.etag || ""
    };
  }
  async removeExistingDistributionOrigin(distributionId, rawInput) {
    const input = this.validateExistingOriginInput(distributionId, rawInput);
    const getResult = await this.client.request({
      service: "cloudfront",
      region: "us-east-1",
      method: "GET",
      path: `/2020-05-31/distribution/${distributionId}/config`,
      returnHeaders: true
    });
    const etag = getResult.headers?.etag || getResult.headers?.ETag || getResult.ETag || "";
    const config = getResult.body?.DistributionConfig || getResult.DistributionConfig || getResult.body;
    if (!config || config.Enabled === undefined || !etag)
      throw new Error("Failed to read an ETag-protected CloudFront distribution config");
    const origins = this.collectionItems(config.Origins, "Origin");
    const behaviors = this.collectionItems(config.CacheBehaviors, "CacheBehavior");
    const origin = origins.find((value) => String(value.Id) === input.id);
    const behavior = behaviors.find((value) => String(value.PathPattern) === input.pathPattern);
    if (origin && String(origin.DomainName) !== input.domainName)
      throw new Error(`Origin ${input.id} no longer targets ${input.domainName}; refusing ambiguous removal`);
    if (behavior && String(behavior.TargetOriginId) !== input.id)
      throw new Error(`Path ${input.pathPattern} no longer targets ${input.id}; refusing ambiguous removal`);
    if (!behavior)
      return {
        distributionId,
        originId: input.id,
        domainName: input.domainName,
        pathPattern: input.pathPattern,
        changed: false,
        applied: false,
        originRemoved: false,
        etag
      };
    const nextBehaviors = behaviors.filter((value) => String(value.PathPattern) !== input.pathPattern);
    const stillReferenced = String(config.DefaultCacheBehavior?.TargetOriginId) === input.id || nextBehaviors.some((value) => String(value.TargetOriginId) === input.id);
    const nextOrigins = stillReferenced ? origins : origins.filter((value) => String(value.Id) !== input.id);
    if (input.dryRun)
      return {
        distributionId,
        originId: input.id,
        domainName: input.domainName,
        pathPattern: input.pathPattern,
        changed: true,
        applied: false,
        originRemoved: !stillReferenced && !!origin,
        etag
      };
    config.Origins = { Quantity: nextOrigins.length, Items: { Origin: nextOrigins } };
    config.CacheBehaviors = {
      Quantity: nextBehaviors.length,
      ...nextBehaviors.length ? { Items: { CacheBehavior: nextBehaviors } } : {}
    };
    const result = await this.client.request({
      service: "cloudfront",
      region: "us-east-1",
      method: "PUT",
      path: `/2020-05-31/distribution/${distributionId}/config`,
      body: this.buildDistributionConfigXml(config),
      headers: { "Content-Type": "application/xml", "If-Match": etag }
    });
    return {
      distributionId,
      originId: input.id,
      domainName: input.domainName,
      pathPattern: input.pathPattern,
      changed: true,
      applied: true,
      originRemoved: !stillReferenced && !!origin,
      etag: result.ETag || result.headers?.etag || ""
    };
  }
  async createInvalidation(options) {
    const callerReference = options.callerReference || Date.now().toString();
    const invalidationBatchXml = `<?xml version="1.0" encoding="UTF-8"?>
<InvalidationBatch>
  <Paths>
    <Quantity>${options.paths.length}</Quantity>
    <Items>
      ${options.paths.map((path) => `<Path>${path}</Path>`).join(`
      `)}
    </Items>
  </Paths>
  <CallerReference>${callerReference}</CallerReference>
</InvalidationBatch>`;
    const result = await this.client.request({
      service: "cloudfront",
      region: "us-east-1",
      method: "POST",
      path: `/2020-05-31/distribution/${options.distributionId}/invalidation`,
      body: invalidationBatchXml,
      headers: {
        "Content-Type": "application/xml"
      }
    });
    return {
      Id: result.Id || result.Invalidation?.Id,
      Status: result.Status || result.Invalidation?.Status || "InProgress",
      CreateTime: result.CreateTime || result.Invalidation?.CreateTime || new Date().toISOString()
    };
  }
  async getInvalidation(distributionId, invalidationId) {
    const result = await this.client.request({
      service: "cloudfront",
      region: "us-east-1",
      method: "GET",
      path: `/2020-05-31/distribution/${distributionId}/invalidation/${invalidationId}`
    });
    return {
      Id: result.Id || result.Invalidation?.Id,
      Status: result.Status || result.Invalidation?.Status,
      CreateTime: result.CreateTime || result.Invalidation?.CreateTime
    };
  }
  async listInvalidations(distributionId) {
    const result = await this.client.request({
      service: "cloudfront",
      region: "us-east-1",
      method: "GET",
      path: `/2020-05-31/distribution/${distributionId}/invalidation`
    });
    const invalidations = [];
    if (result.InvalidationSummary) {
      const summaries = Array.isArray(result.InvalidationSummary) ? result.InvalidationSummary : [result.InvalidationSummary];
      invalidations.push(...summaries.map((item) => ({
        Id: item.Id,
        Status: item.Status,
        CreateTime: item.CreateTime
      })));
    }
    return invalidations;
  }
  async waitForInvalidation(distributionId, invalidationId) {
    const maxAttempts = 60;
    let attempts = 0;
    while (attempts < maxAttempts) {
      const invalidation = await this.getInvalidation(distributionId, invalidationId);
      if (invalidation.Status === "Completed") {
        return;
      }
      await new Promise((resolve) => setTimeout(resolve, 5000));
      attempts++;
    }
    throw new Error(`Timeout waiting for invalidation ${invalidationId} to complete`);
  }
  async listDistributions() {
    const result = await this.client.request({
      service: "cloudfront",
      region: "us-east-1",
      method: "GET",
      path: "/2020-05-31/distribution"
    });
    const distributions = [];
    const distList = result.DistributionList || result;
    const items = distList.Items;
    let summaries = [];
    if (Array.isArray(items)) {
      summaries = items;
    } else if (items?.DistributionSummary) {
      summaries = Array.isArray(items.DistributionSummary) ? items.DistributionSummary : [items.DistributionSummary];
    }
    distributions.push(...summaries.map((item) => ({
      Id: item.Id,
      ARN: item.ARN,
      Status: item.Status,
      DomainName: item.DomainName,
      Aliases: item.Aliases || undefined,
      Enabled: item.Enabled === "true" || item.Enabled === true
    })));
    return distributions;
  }
  async getDistribution(distributionId) {
    const result = await this.client.request({
      service: "cloudfront",
      region: "us-east-1",
      method: "GET",
      path: `/2020-05-31/distribution/${distributionId}`
    });
    const dist = result.Distribution || result;
    return {
      Id: dist.Id,
      ARN: dist.ARN,
      Status: dist.Status,
      DomainName: dist.DomainName,
      Aliases: dist.DistributionConfig?.Aliases?.Items || dist.Aliases?.Items || [],
      Enabled: dist.DistributionConfig?.Enabled === "true" || dist.DistributionConfig?.Enabled === true
    };
  }
  async getDistributionConfig(distributionId) {
    const result = await this.client.request({
      service: "cloudfront",
      region: "us-east-1",
      method: "GET",
      path: `/2020-05-31/distribution/${distributionId}/config`
    });
    return {
      ETag: result.ETag || "",
      DistributionConfig: result.DistributionConfig || result
    };
  }
  async invalidateAll(distributionId) {
    return this.createInvalidation({
      distributionId,
      paths: ["/*"]
    });
  }
  async invalidatePaths(distributionId, paths) {
    const formattedPaths = paths.map((path) => path.startsWith("/") ? path : `/${path}`);
    return this.createInvalidation({
      distributionId,
      paths: formattedPaths
    });
  }
  async invalidatePattern(distributionId, pattern) {
    const path = pattern.startsWith("/") ? pattern : `/${pattern}`;
    return this.createInvalidation({
      distributionId,
      paths: [path]
    });
  }
  async invalidateAfterDeployment(options) {
    const { distributionId, changedPaths, invalidateAll = false, wait = false } = options;
    let result;
    if (invalidateAll || !changedPaths || changedPaths.length === 0) {
      result = await this.invalidateAll(distributionId);
    } else {
      result = await this.invalidatePaths(distributionId, changedPaths);
    }
    if (wait) {
      await this.waitForInvalidation(distributionId, result.Id);
    }
    return {
      invalidationId: result.Id,
      status: result.Status
    };
  }
  async findDistributionByDomain(domain) {
    const distributions = await this.listDistributions();
    const found = distributions.find((dist) => {
      if (dist.DomainName === domain) {
        return true;
      }
      if (dist.Aliases?.Items && dist.Aliases.Items.includes(domain)) {
        return true;
      }
      return false;
    });
    return found || null;
  }
  async batchInvalidate(distributionIds, paths = ["/*"]) {
    const results = await Promise.all(distributionIds.map(async (distributionId) => {
      const result = await this.createInvalidation({
        distributionId,
        paths
      });
      return {
        distributionId,
        invalidationId: result.Id,
        status: result.Status
      };
    }));
    return results;
  }
  async updateCustomErrorResponses(options) {
    const { distributionId, customErrorResponses } = options;
    const getResult = await this.client.request({
      service: "cloudfront",
      region: "us-east-1",
      method: "GET",
      path: `/2020-05-31/distribution/${distributionId}/config`,
      returnHeaders: true
    });
    const etag = getResult.headers?.etag || getResult.headers?.ETag || "";
    const currentConfig = getResult.body?.DistributionConfig || (getResult.body?.Enabled !== undefined ? getResult.body : undefined) || getResult.DistributionConfig;
    if (!currentConfig || currentConfig.Enabled === undefined) {
      throw new Error("Failed to get current distribution config");
    }
    if (customErrorResponses.length === 0) {
      currentConfig.CustomErrorResponses = {
        Quantity: 0
      };
    } else {
      currentConfig.CustomErrorResponses = {
        Quantity: customErrorResponses.length,
        Items: {
          CustomErrorResponse: customErrorResponses.map((err) => ({
            ErrorCode: err.errorCode,
            ...err.responsePagePath && { ResponsePagePath: err.responsePagePath },
            ...err.responseCode && { ResponseCode: err.responseCode },
            ...err.errorCachingMinTTL !== undefined && { ErrorCachingMinTTL: err.errorCachingMinTTL }
          }))
        }
      };
    }
    const configXml = this.buildDistributionConfigXml(currentConfig);
    const result = await this.client.request({
      service: "cloudfront",
      region: "us-east-1",
      method: "PUT",
      path: `/2020-05-31/distribution/${distributionId}/config`,
      body: configXml,
      headers: {
        "Content-Type": "application/xml",
        "If-Match": etag
      }
    });
    const dist = result.Distribution || result;
    return {
      Distribution: {
        Id: dist.Id,
        ARN: dist.ARN,
        Status: dist.Status,
        DomainName: dist.DomainName,
        Aliases: dist.DistributionConfig?.Aliases?.Items || [],
        Enabled: dist.DistributionConfig?.Enabled === "true" || dist.DistributionConfig?.Enabled === true
      },
      ETag: result.ETag || ""
    };
  }
  async removeCustomErrorResponses(distributionId) {
    return this.updateCustomErrorResponses({
      distributionId,
      customErrorResponses: []
    });
  }
  async updateDistribution(options) {
    const { distributionId, aliases, certificateArn, comment } = options;
    const getResult = await this.client.request({
      service: "cloudfront",
      region: "us-east-1",
      method: "GET",
      path: `/2020-05-31/distribution/${distributionId}/config`,
      returnHeaders: true
    });
    const etag = getResult.headers?.etag || getResult.headers?.ETag || "";
    const currentConfig = getResult.body?.DistributionConfig || (getResult.body?.Enabled !== undefined ? getResult.body : undefined) || getResult.DistributionConfig;
    if (!currentConfig || currentConfig.Enabled === undefined) {
      throw new Error("Failed to get current distribution config");
    }
    if (aliases && aliases.length > 0) {
      currentConfig.Aliases = {
        Quantity: aliases.length,
        Items: { Item: aliases }
      };
    }
    if (certificateArn) {
      currentConfig.ViewerCertificate = {
        ACMCertificateArn: certificateArn,
        SSLSupportMethod: "sni-only",
        MinimumProtocolVersion: "TLSv1.2_2021",
        CertificateSource: "acm"
      };
    }
    if (comment) {
      currentConfig.Comment = comment;
    }
    const configXml = this.buildDistributionConfigXml(currentConfig);
    const result = await this.client.request({
      service: "cloudfront",
      region: "us-east-1",
      method: "PUT",
      path: `/2020-05-31/distribution/${distributionId}/config`,
      body: configXml,
      headers: {
        "Content-Type": "application/xml",
        "If-Match": etag
      }
    });
    const dist = result.Distribution || result;
    return {
      Distribution: {
        Id: dist.Id,
        ARN: dist.ARN,
        Status: dist.Status,
        DomainName: dist.DomainName,
        Aliases: aliases ? { Quantity: aliases.length, Items: aliases } : { Quantity: 0, Items: [] },
        Enabled: dist.DistributionConfig?.Enabled === "true" || dist.DistributionConfig?.Enabled === true
      },
      ETag: result.ETag || ""
    };
  }
  buildDistributionConfigXml(config) {
    const escapeXml = (str) => {
      return str.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&apos;");
    };
    const arrayChildNames = {
      Items: "",
      Methods: "Method",
      Headers: "Name",
      Cookies: "Name",
      QueryStringCacheKeys: "Name",
      TrustedKeyGroups: "KeyGroup",
      TrustedSigners: "AwsAccountNumber",
      LambdaFunctionAssociations: "LambdaFunctionAssociation",
      FunctionAssociations: "FunctionAssociation",
      CacheBehaviors: "CacheBehavior",
      CustomErrorResponses: "CustomErrorResponse",
      GeoRestriction: "Location"
    };
    const itemsChildNames = {
      Origins: "Origin",
      Aliases: "CNAME",
      AllowedMethods: "Method",
      CachedMethods: "Method",
      CustomErrorResponses: "CustomErrorResponse",
      CacheBehaviors: "CacheBehavior"
    };
    const buildXmlElement = (name, value, indent = "", parentContext = "") => {
      if (value === null || value === undefined) {
        return "";
      }
      if (name.startsWith("@_") || name === "?xml") {
        return "";
      }
      if (typeof value === "boolean") {
        return `${indent}<${name}>${value}</${name}>
`;
      }
      if (typeof value === "number" || typeof value === "string") {
        return `${indent}<${name}>${escapeXml(String(value))}</${name}>
`;
      }
      if (Array.isArray(value)) {
        const childName = arrayChildNames[name] || name.replace(/s$/, "");
        return value.map((item) => buildXmlElement(childName, item, indent, name)).join("");
      }
      if (typeof value === "object") {
        if (name === "Items") {
          const childElementName = itemsChildNames[parentContext] || "";
          const keys = Object.keys(value).filter((k) => !k.startsWith("@_"));
          if (keys.length === 1 && !Array.isArray(value[keys[0]])) {
            const childKey = keys[0];
            const childValue = value[childKey];
            if (typeof childValue === "string") {
              return `${indent}<Items>
${indent}  <${childKey}>${escapeXml(childValue)}</${childKey}>
${indent}</Items>
`;
            } else if (typeof childValue === "object" && !Array.isArray(childValue)) {
              return `${indent}<Items>
${buildXmlElement(childKey, childValue, indent + "  ", name)}${indent}</Items>
`;
            }
          }
          if (keys.length === 1 && Array.isArray(value[keys[0]])) {
            const childKey = keys[0];
            const childArray = value[childKey];
            let children2 = "";
            for (const item of childArray) {
              if (typeof item === "string") {
                children2 += `${indent}  <${childKey}>${escapeXml(item)}</${childKey}>
`;
              } else {
                children2 += buildXmlElement(childKey, item, indent + "  ", name);
              }
            }
            return `${indent}<Items>
${children2}${indent}</Items>
`;
          }
          if (Array.isArray(value)) {
            let children2 = "";
            const childName = childElementName || "Item";
            for (const item of value) {
              if (typeof item === "string") {
                children2 += `${indent}  <${childName}>${escapeXml(item)}</${childName}>
`;
              } else {
                children2 += buildXmlElement(childName, item, indent + "  ", name);
              }
            }
            return `${indent}<Items>
${children2}${indent}</Items>
`;
          }
        }
        let children = "";
        for (const [key, val] of Object.entries(value)) {
          if (!key.startsWith("@_")) {
            children += buildXmlElement(key, val, indent + "  ", name);
          }
        }
        if (children === "") {
          return `${indent}<${name}/>
`;
        }
        return `${indent}<${name}>
${children}${indent}</${name}>
`;
      }
      return "";
    };
    return `<?xml version="1.0" encoding="UTF-8"?>
<DistributionConfig xmlns="http://cloudfront.amazonaws.com/doc/2020-05-31/">
${Object.entries(config).filter(([k]) => !k.startsWith("@_")).map(([key, val]) => buildXmlElement(key, val, "  ", "DistributionConfig")).join("")}</DistributionConfig>`;
  }
  async ensureDynamicHttpMethods(distributionId) {
    const getResult = await this.client.request({
      service: "cloudfront",
      region: "us-east-1",
      method: "GET",
      path: `/2020-05-31/distribution/${distributionId}/config`,
      returnHeaders: true
    });
    const etag = getResult.headers?.etag || getResult.headers?.ETag || "";
    const currentConfig = getResult.body?.DistributionConfig || getResult.DistributionConfig || getResult.body;
    if (!currentConfig) {
      throw new Error("Failed to get current distribution config");
    }
    let changed = false;
    const dynamicMethods = ["GET", "HEAD", "OPTIONS", "PUT", "POST", "PATCH", "DELETE"];
    const parseAllowedMethods = (allowedMethods) => {
      if (!allowedMethods)
        return [];
      const items = allowedMethods.Items;
      if (!items)
        return [];
      if (Array.isArray(items))
        return items.map(String);
      if (typeof items === "object" && items.Method) {
        const method = items.Method;
        return Array.isArray(method) ? method.map(String) : [String(method)];
      }
      if (items.Item) {
        return Array.isArray(items.Item) ? items.Item.map(String) : [String(items.Item)];
      }
      return [];
    };
    const defaultBehavior = currentConfig.DefaultCacheBehavior;
    if (defaultBehavior) {
      const allowed = parseAllowedMethods(defaultBehavior.AllowedMethods);
      if (!allowed.includes("POST")) {
        defaultBehavior.AllowedMethods = {
          Quantity: dynamicMethods.length,
          Items: { Method: dynamicMethods },
          CachedMethods: {
            Quantity: 2,
            Items: { Method: ["GET", "HEAD"] }
          }
        };
        changed = true;
      }
    }
    if (!changed) {
      return false;
    }
    const configXml = this.buildDistributionConfigXml(currentConfig);
    await this.client.request({
      service: "cloudfront",
      region: "us-east-1",
      method: "PUT",
      path: `/2020-05-31/distribution/${distributionId}/config`,
      body: configXml,
      headers: {
        "Content-Type": "application/xml",
        "If-Match": etag
      }
    });
    return true;
  }
  async addAliases(distributionId, aliases, certificateArn) {
    return this.updateDistribution({
      distributionId,
      aliases,
      certificateArn
    });
  }
  async createFunction(options) {
    const { name, code, comment = "", runtime = "cloudfront-js-2.0" } = options;
    const functionXml = `<?xml version="1.0" encoding="UTF-8"?>
<CreateFunctionRequest xmlns="http://cloudfront.amazonaws.com/doc/2020-05-31/">
  <Name>${name}</Name>
  <FunctionConfig>
    <Comment>${comment}</Comment>
    <Runtime>${runtime}</Runtime>
  </FunctionConfig>
  <FunctionCode>${Buffer.from(code).toString("base64")}</FunctionCode>
</CreateFunctionRequest>`;
    const result = await this.client.request({
      service: "cloudfront",
      region: "us-east-1",
      method: "POST",
      path: "/2020-05-31/function",
      body: functionXml,
      headers: {
        "Content-Type": "application/xml"
      }
    });
    const func = result.FunctionSummary || result;
    return {
      FunctionARN: func.FunctionMetadata?.FunctionARN || func.FunctionARN,
      Name: func.Name || name,
      Stage: func.FunctionMetadata?.Stage || "DEVELOPMENT",
      ETag: result.ETag || ""
    };
  }
  async listFunctions() {
    const result = await this.client.request({
      service: "cloudfront",
      region: "us-east-1",
      method: "GET",
      path: "/2020-05-31/function"
    });
    const functions = [];
    const items = result.FunctionList?.Items?.FunctionSummary;
    if (items) {
      const list = Array.isArray(items) ? items : [items];
      for (const item of list) {
        functions.push({
          Name: item.Name,
          FunctionARN: item.FunctionMetadata?.FunctionARN,
          Stage: item.FunctionMetadata?.Stage,
          CreatedTime: item.FunctionMetadata?.CreatedTime,
          LastModifiedTime: item.FunctionMetadata?.LastModifiedTime
        });
      }
    }
    return functions;
  }
  async getFunction(name, stage = "LIVE") {
    try {
      const result = await this.client.request({
        service: "cloudfront",
        region: "us-east-1",
        method: "GET",
        path: `/2020-05-31/function/${name}`,
        queryParams: { Stage: stage },
        returnHeaders: true
      });
      const func = result.body?.FunctionSummary || result.FunctionSummary || result.body || result;
      return {
        FunctionARN: func.FunctionMetadata?.FunctionARN,
        Name: func.Name || name,
        Stage: func.FunctionMetadata?.Stage || stage,
        ETag: result.headers?.etag || result.ETag || "",
        FunctionCode: func.FunctionCode
      };
    } catch (err) {
      if (err.message?.includes("404") || err.message?.includes("NoSuchFunctionExists")) {
        return null;
      }
      throw err;
    }
  }
  async publishFunction(nameOrOptions, etag) {
    let name;
    let functionETag;
    if (typeof nameOrOptions === "object") {
      name = nameOrOptions.Name;
      functionETag = nameOrOptions.IfMatch;
    } else {
      name = nameOrOptions;
      functionETag = etag;
    }
    if (!functionETag) {
      const func2 = await this.getFunction(name, "DEVELOPMENT");
      if (!func2) {
        throw new Error(`Function ${name} not found`);
      }
      functionETag = func2.ETag;
    }
    const result = await this.client.request({
      service: "cloudfront",
      region: "us-east-1",
      method: "POST",
      path: `/2020-05-31/function/${name}/publish`,
      headers: {
        "If-Match": functionETag
      }
    });
    const func = result.FunctionSummary || result;
    return {
      FunctionARN: func.FunctionMetadata?.FunctionARN,
      Stage: func.FunctionMetadata?.Stage || "LIVE",
      FunctionSummary: func
    };
  }
  async describeFunction(options) {
    const { Name, Stage = "DEVELOPMENT" } = options;
    const result = await this.client.request({
      service: "cloudfront",
      region: "us-east-1",
      method: "GET",
      path: `/2020-05-31/function/${Name}/describe`,
      queryParams: { Stage },
      returnHeaders: true
    });
    return {
      ETag: result.headers?.etag || result.ETag || "",
      FunctionSummary: result.body?.FunctionSummary || result.FunctionSummary || result.body || result
    };
  }
  async updateFunction(options) {
    const { Name, FunctionCode, FunctionConfig, IfMatch } = options;
    const functionXml = `<?xml version="1.0" encoding="UTF-8"?>
<UpdateFunctionRequest xmlns="http://cloudfront.amazonaws.com/doc/2020-05-31/">
  <FunctionConfig>
    <Comment>${FunctionConfig.Comment}</Comment>
    <Runtime>${FunctionConfig.Runtime}</Runtime>
  </FunctionConfig>
  <FunctionCode>${Buffer.from(FunctionCode).toString("base64")}</FunctionCode>
</UpdateFunctionRequest>`;
    const result = await this.client.request({
      service: "cloudfront",
      region: "us-east-1",
      method: "PUT",
      path: `/2020-05-31/function/${Name}`,
      body: functionXml,
      headers: {
        "Content-Type": "application/xml",
        "If-Match": IfMatch
      },
      returnHeaders: true
    });
    return {
      ETag: result.headers?.etag || result.ETag || "",
      FunctionSummary: result.body?.FunctionSummary || result.FunctionSummary || result.body || result
    };
  }
  async deleteFunction(name, etag) {
    let functionETag = etag;
    if (!functionETag) {
      const func = await this.getFunction(name, "DEVELOPMENT");
      if (!func) {
        return;
      }
      functionETag = func.ETag;
    }
    await this.client.request({
      service: "cloudfront",
      region: "us-east-1",
      method: "DELETE",
      path: `/2020-05-31/function/${name}`,
      headers: {
        "If-Match": functionETag
      }
    });
  }
  async createIndexRewriteFunction(name) {
    const code = `function handler(event) {
    const request = event.request;
    var uri = request.uri;

    // Check if the request is for a directory (ends with /)
    if (uri.endsWith('/')) {
        request.uri += 'index.html';
    }
    // Check if the request doesn't have a file extension
    else if (!uri.includes('.')) {
        // Add trailing slash to redirect to directory
        request.uri += '/index.html';
    }

    return request;
}`;
    return this.createFunction({
      name,
      code,
      comment: "Rewrite directory requests to index.html for S3 static sites",
      runtime: "cloudfront-js-2.0"
    });
  }
  async listOriginAccessControls() {
    const result = await this.client.request({
      service: "cloudfront",
      region: "us-east-1",
      method: "GET",
      path: "/2020-05-31/origin-access-control"
    });
    const items = [];
    if (result.OriginAccessControlList?.Items?.OriginAccessControlSummary) {
      const summaries = Array.isArray(result.OriginAccessControlList.Items.OriginAccessControlSummary) ? result.OriginAccessControlList.Items.OriginAccessControlSummary : [result.OriginAccessControlList.Items.OriginAccessControlSummary];
      items.push(...summaries.map((item) => ({
        Id: item.Id,
        Name: item.Name,
        Description: item.Description,
        SigningProtocol: item.SigningProtocol,
        SigningBehavior: item.SigningBehavior,
        OriginAccessControlOriginType: item.OriginAccessControlOriginType
      })));
    }
    return items;
  }
  async createOriginAccessControl(options) {
    const {
      name,
      description = `OAC for ${name}`,
      signingProtocol = "sigv4",
      signingBehavior = "always",
      originType = "s3"
    } = options;
    const body = `<?xml version="1.0" encoding="UTF-8"?>
<OriginAccessControlConfig xmlns="http://cloudfront.amazonaws.com/doc/2020-05-31/">
  <Name>${name}</Name>
  <Description>${description}</Description>
  <SigningProtocol>${signingProtocol}</SigningProtocol>
  <SigningBehavior>${signingBehavior}</SigningBehavior>
  <OriginAccessControlOriginType>${originType}</OriginAccessControlOriginType>
</OriginAccessControlConfig>`;
    const result = await this.client.request({
      service: "cloudfront",
      region: "us-east-1",
      method: "POST",
      path: "/2020-05-31/origin-access-control",
      body,
      headers: {
        "Content-Type": "application/xml"
      },
      returnHeaders: true
    });
    const oac = result.body?.OriginAccessControl || result.OriginAccessControl || result.body || result;
    return {
      Id: oac.Id,
      Name: oac.OriginAccessControlConfig?.Name || name,
      Description: oac.OriginAccessControlConfig?.Description || description,
      SigningProtocol: oac.OriginAccessControlConfig?.SigningProtocol || signingProtocol,
      SigningBehavior: oac.OriginAccessControlConfig?.SigningBehavior || signingBehavior,
      OriginAccessControlOriginType: oac.OriginAccessControlConfig?.OriginAccessControlOriginType || originType,
      ETag: result.headers?.etag || result.ETag || ""
    };
  }
  async findOrCreateOriginAccessControl(name, originType = "s3") {
    const oacs = await this.listOriginAccessControls();
    const existing = oacs.find((oac) => oac.Name === name && oac.OriginAccessControlOriginType === originType);
    if (existing) {
      return { Id: existing.Id, Name: existing.Name, isNew: false };
    }
    const created = await this.createOriginAccessControl({ name, originType });
    return { Id: created.Id, Name: created.Name, isNew: true };
  }
  async createDistributionForS3(options) {
    const {
      bucketName,
      bucketRegion,
      originAccessControlId,
      aliases = [],
      certificateArn,
      defaultRootObject = "index.html",
      comment = `Distribution for ${bucketName}`,
      priceClass = "PriceClass_100",
      enabled = true,
      trustedKeyGroupIds = [],
      mediaDelivery = false
    } = options;
    const originId = `S3-${bucketName}`;
    const s3DomainName = `${bucketName}.s3.${bucketRegion}.amazonaws.com`;
    const callerReference = `${Date.now()}-${Math.random().toString(36).slice(2)}`;
    const trustedKeyGroupsXml = trustedKeyGroupIds.length > 0 ? `<TrustedKeyGroups>
      <Enabled>true</Enabled>
      <Quantity>${trustedKeyGroupIds.length}</Quantity>
      <Items>${trustedKeyGroupIds.map((id) => `<KeyGroup>${id}</KeyGroup>`).join("")}</Items>
    </TrustedKeyGroups>` : `<TrustedKeyGroups><Enabled>false</Enabled><Quantity>0</Quantity></TrustedKeyGroups>`;
    let aliasesXml = "<Aliases><Quantity>0</Quantity></Aliases>";
    if (aliases.length > 0) {
      aliasesXml = `<Aliases>
    <Quantity>${aliases.length}</Quantity>
    <Items>
      ${aliases.map((a) => `<CNAME>${a}</CNAME>`).join(`
      `)}
    </Items>
  </Aliases>`;
    }
    let viewerCertificateXml = `<ViewerCertificate>
    <CloudFrontDefaultCertificate>true</CloudFrontDefaultCertificate>
  </ViewerCertificate>`;
    if (certificateArn && aliases.length > 0) {
      viewerCertificateXml = `<ViewerCertificate>
    <ACMCertificateArn>${certificateArn}</ACMCertificateArn>
    <SSLSupportMethod>sni-only</SSLSupportMethod>
    <MinimumProtocolVersion>TLSv1.2_2021</MinimumProtocolVersion>
    <CertificateSource>acm</CertificateSource>
  </ViewerCertificate>`;
    }
    const body = `<?xml version="1.0" encoding="UTF-8"?>
<DistributionConfig xmlns="http://cloudfront.amazonaws.com/doc/2020-05-31/">
  <CallerReference>${callerReference}</CallerReference>
  <Comment>${comment}</Comment>
  <DefaultRootObject>${defaultRootObject}</DefaultRootObject>
  <Origins>
    <Quantity>1</Quantity>
    <Items>
      <Origin>
        <Id>${originId}</Id>
        <DomainName>${s3DomainName}</DomainName>
        <OriginPath></OriginPath>
        <S3OriginConfig>
          <OriginAccessIdentity></OriginAccessIdentity>
        </S3OriginConfig>
        <OriginAccessControlId>${originAccessControlId}</OriginAccessControlId>
      </Origin>
    </Items>
  </Origins>
  <DefaultCacheBehavior>
    <TargetOriginId>${originId}</TargetOriginId>
    <ViewerProtocolPolicy>redirect-to-https</ViewerProtocolPolicy>
    <AllowedMethods>
      <Quantity>2</Quantity>
      <Items>
        <Method>GET</Method>
        <Method>HEAD</Method>
      </Items>
      <CachedMethods>
        <Quantity>2</Quantity>
        <Items>
          <Method>GET</Method>
          <Method>HEAD</Method>
        </Items>
      </CachedMethods>
    </AllowedMethods>
    <Compress>true</Compress>
    <CachePolicyId>658327ea-f89d-4fab-a63d-7e88639e58f6</CachePolicyId>
    ${trustedKeyGroupsXml}
  </DefaultCacheBehavior>
  ${aliasesXml}
  ${viewerCertificateXml}
  <PriceClass>${priceClass}</PriceClass>
  <Enabled>${enabled}</Enabled>
  <HttpVersion>http2and3</HttpVersion>
  <IsIPV6Enabled>true</IsIPV6Enabled>
  ${mediaDelivery ? "<CustomErrorResponses><Quantity>0</Quantity></CustomErrorResponses>" : `<CustomErrorResponses>
    <Quantity>1</Quantity>
    <Items>
      <CustomErrorResponse>
        <ErrorCode>403</ErrorCode>
        <ResponsePagePath>/index.html</ResponsePagePath>
        <ResponseCode>200</ResponseCode>
        <ErrorCachingMinTTL>300</ErrorCachingMinTTL>
      </CustomErrorResponse>
    </Items>
  </CustomErrorResponses>`}
</DistributionConfig>`;
    const result = await this.client.request({
      service: "cloudfront",
      region: "us-east-1",
      method: "POST",
      path: "/2020-05-31/distribution",
      body,
      headers: {
        "Content-Type": "application/xml"
      },
      returnHeaders: true
    });
    const dist = result.body?.Distribution || result.Distribution || result.body || result;
    return {
      Id: dist.Id,
      ARN: dist.ARN,
      DomainName: dist.DomainName,
      Status: dist.Status,
      ETag: result.headers?.etag || result.ETag || ""
    };
  }
  async createMediaDistributionForS3(options) {
    return this.createDistributionForS3({
      ...options,
      defaultRootObject: "",
      mediaDelivery: true,
      comment: options.comment ?? `Media distribution for ${options.bucketName}`
    });
  }
  async listPublicKeys() {
    const result = await this.client.request({
      service: "cloudfront",
      region: "us-east-1",
      method: "GET",
      path: "/2020-05-31/public-key"
    });
    const values = this.collectionItems(result.PublicKeyList ?? result, "PublicKeySummary");
    return values.map((value) => ({
      Id: String(value.Id),
      Name: String(value.Name),
      Comment: value.Comment ? String(value.Comment) : undefined
    }));
  }
  async createPublicKey(options) {
    if (!/^[A-Za-z0-9_-]{1,128}$/.test(options.name))
      throw new TypeError("CloudFront public key name is invalid");
    if (!options.encodedKey.includes("BEGIN PUBLIC KEY"))
      throw new TypeError("CloudFront public key must be PEM encoded");
    const escape = (value2) => value2.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;");
    const result = await this.client.request({
      service: "cloudfront",
      region: "us-east-1",
      method: "POST",
      path: "/2020-05-31/public-key",
      headers: { "Content-Type": "application/xml" },
      body: `<PublicKeyConfig xmlns="http://cloudfront.amazonaws.com/doc/2020-05-31/"><CallerReference>${escape(options.callerReference ?? `${Date.now()}-${options.name}`)}</CallerReference><Name>${escape(options.name)}</Name><EncodedKey>${escape(options.encodedKey)}</EncodedKey><Comment>${escape(options.comment ?? "")}</Comment></PublicKeyConfig>`
    });
    const value = result.PublicKey ?? result;
    return {
      Id: String(value.Id),
      Name: String(value.PublicKeyConfig?.Name ?? options.name),
      EncodedKey: String(value.PublicKeyConfig?.EncodedKey ?? options.encodedKey),
      Comment: String(value.PublicKeyConfig?.Comment ?? options.comment ?? "")
    };
  }
  async listKeyGroups() {
    const result = await this.client.request({
      service: "cloudfront",
      region: "us-east-1",
      method: "GET",
      path: "/2020-05-31/key-group"
    });
    const values = this.collectionItems(result.KeyGroupList ?? result, "KeyGroupSummary");
    return values.map((value) => ({
      Id: String(value.KeyGroup?.Id ?? value.Id),
      Name: String(value.KeyGroup?.KeyGroupConfig?.Name ?? value.KeyGroupConfig?.Name ?? value.Name),
      Items: this.collectionItems(value.KeyGroup?.KeyGroupConfig?.Items ?? value.KeyGroupConfig?.Items ?? value.Items, "Item").map(String),
      Comment: value.KeyGroup?.KeyGroupConfig?.Comment ?? value.KeyGroupConfig?.Comment ?? value.Comment
    }));
  }
  async createKeyGroup(options) {
    if (!/^[A-Za-z0-9_-]{1,128}$/.test(options.name))
      throw new TypeError("CloudFront key group name is invalid");
    const publicKeyIds = [...new Set(options.publicKeyIds)];
    if (publicKeyIds.length === 0)
      throw new TypeError("CloudFront key group requires at least one public key");
    const escape = (value2) => value2.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;");
    const items = publicKeyIds.map((id) => `<Item>${escape(id)}</Item>`).join("");
    const result = await this.client.request({
      service: "cloudfront",
      region: "us-east-1",
      method: "POST",
      path: "/2020-05-31/key-group",
      headers: { "Content-Type": "application/xml" },
      body: `<KeyGroupConfig xmlns="http://cloudfront.amazonaws.com/doc/2020-05-31/"><Name>${escape(options.name)}</Name><Items>${items}</Items><Comment>${escape(options.comment ?? "")}</Comment></KeyGroupConfig>`
    });
    const value = result.KeyGroup ?? result;
    return {
      Id: String(value.Id),
      Name: String(value.KeyGroupConfig?.Name ?? options.name),
      Items: publicKeyIds,
      Comment: String(value.KeyGroupConfig?.Comment ?? options.comment ?? "")
    };
  }
  static getS3BucketPolicyForCloudFront(bucketName, distributionArn) {
    return {
      Version: "2012-10-17",
      Statement: [
        {
          Sid: "AllowCloudFrontServicePrincipal",
          Effect: "Allow",
          Principal: {
            Service: "cloudfront.amazonaws.com"
          },
          Action: "s3:GetObject",
          Resource: `arn:aws:s3:::${bucketName}/*`,
          Condition: {
            StringEquals: {
              "AWS:SourceArn": distributionArn
            }
          }
        }
      ]
    };
  }
  async waitForDistributionDeployed(distributionId, maxAttempts = 60) {
    for (let i = 0;i < maxAttempts; i++) {
      const dist = await this.getDistribution(distributionId);
      if (dist.Status === "Deployed") {
        return true;
      }
      await new Promise((resolve) => setTimeout(resolve, 30000));
    }
    return false;
  }
  async disableDistribution(distributionId) {
    const getResult = await this.client.request({
      service: "cloudfront",
      region: "us-east-1",
      method: "GET",
      path: `/2020-05-31/distribution/${distributionId}/config`,
      returnHeaders: true
    });
    const etag = getResult.headers?.etag || getResult.headers?.ETag || "";
    const currentConfig = getResult.body?.DistributionConfig || (getResult.body?.Enabled !== undefined ? getResult.body : undefined) || getResult.DistributionConfig;
    if (!currentConfig || currentConfig.Enabled === undefined) {
      throw new Error("Failed to get current distribution config");
    }
    currentConfig.Enabled = false;
    const configXml = this.buildDistributionConfigXml(currentConfig);
    const result = await this.client.request({
      service: "cloudfront",
      region: "us-east-1",
      method: "PUT",
      path: `/2020-05-31/distribution/${distributionId}/config`,
      body: configXml,
      headers: {
        "Content-Type": "application/xml",
        "If-Match": etag
      },
      returnHeaders: true
    });
    return { ETag: result.headers?.etag || result.headers?.ETag || result.ETag || "" };
  }
  async deleteDistribution(distributionId, etag) {
    let etagToUse = etag || "";
    if (!etagToUse) {
      const getResult = await this.client.request({
        service: "cloudfront",
        region: "us-east-1",
        method: "GET",
        path: `/2020-05-31/distribution/${distributionId}`,
        returnHeaders: true
      });
      etagToUse = getResult.headers?.etag || getResult.headers?.ETag || "";
    }
    await this.client.request({
      service: "cloudfront",
      region: "us-east-1",
      method: "DELETE",
      path: `/2020-05-31/distribution/${distributionId}`,
      headers: {
        "If-Match": etagToUse
      }
    });
  }
  async waitForDistributionDisabled(distributionId, maxAttempts = 60) {
    for (let i = 0;i < maxAttempts; i++) {
      const dist = await this.getDistribution(distributionId);
      if (dist.Status === "Deployed" && !dist.Enabled) {
        return true;
      }
      await new Promise((resolve) => setTimeout(resolve, 30000));
    }
    return false;
  }
  async removeAlias(distributionId, alias) {
    const getResult = await this.client.request({
      service: "cloudfront",
      region: "us-east-1",
      method: "GET",
      path: `/2020-05-31/distribution/${distributionId}/config`,
      returnHeaders: true
    });
    const etag = getResult.headers?.etag || getResult.headers?.ETag || "";
    const currentConfig = getResult.body?.DistributionConfig || (getResult.body?.Enabled !== undefined ? getResult.body : undefined) || getResult.DistributionConfig;
    if (!currentConfig || currentConfig.Enabled === undefined) {
      throw new Error("Failed to get current distribution config");
    }
    let items = [];
    if (currentConfig.Aliases?.Items) {
      if (Array.isArray(currentConfig.Aliases.Items)) {
        items = currentConfig.Aliases.Items;
      } else if (typeof currentConfig.Aliases.Items === "object") {
        const cname = currentConfig.Aliases.Items.CNAME;
        if (typeof cname === "string") {
          items = [cname];
        } else if (Array.isArray(cname)) {
          items = cname;
        }
      }
    }
    if (items.length === 0) {
      throw new Error(`Distribution has no aliases to remove`);
    }
    const newItems = items.filter((a) => a !== alias);
    if (newItems.length === items.length) {
      throw new Error(`Alias ${alias} not found in distribution`);
    }
    currentConfig.Aliases.Quantity = newItems.length;
    currentConfig.Aliases.Items = newItems.length > 0 ? newItems : undefined;
    if (newItems.length === 0) {
      currentConfig.ViewerCertificate = {
        CloudFrontDefaultCertificate: true,
        MinimumProtocolVersion: "TLSv1.2_2021"
      };
    }
    const configXml = this.buildDistributionConfigXml(currentConfig);
    const result = await this.client.request({
      service: "cloudfront",
      region: "us-east-1",
      method: "PUT",
      path: `/2020-05-31/distribution/${distributionId}/config`,
      body: configXml,
      headers: {
        "Content-Type": "application/xml",
        "If-Match": etag
      },
      returnHeaders: true
    });
    return { ETag: result.headers?.etag || result.headers?.ETag || result.ETag || "" };
  }
}

export { CloudFrontClient };
