import {
  CloudFrontClient
} from "./chunk-01d86gt1.js";
import {
  ACMClient,
  ACMDnsValidator,
  Route53Provider,
  UnifiedDnsValidator,
  createDnsProvider
} from "./chunk-001z7khv.js";
import {
  CloudFormationClient
} from "./chunk-4cjrg98a.js";
import {
  Route53Client
} from "./chunk-32e7ya18.js";
import {
  S3Client
} from "./chunk-wj3s95p9.js";
import {
  __require
} from "./chunk-v0bahtg2.js";

// src/deploy/static-site-external-dns.ts
function generateExternalDnsStaticSiteTemplate(config) {
  const {
    bucketName,
    domain,
    aliases,
    certificateArn,
    defaultRootObject = "index.html",
    errorDocument = "404.html",
    passthroughUrls = false,
    singlePageApp = false,
    dynamicApp = false,
    computeOriginDomain,
    computeOriginPort = 3008,
    computeOriginId = "app-compute",
    retainOnStackDelete = false
  } = config;
  const retainPolicy = retainOnStackDelete ? { DeletionPolicy: "Retain", UpdateReplacePolicy: "Retain" } : {};
  const useComputeOrigin = dynamicApp && !!computeOriginDomain;
  const defaultAllowedMethods = useComputeOrigin ? ["GET", "HEAD", "OPTIONS", "PUT", "POST", "PATCH", "DELETE"] : ["GET", "HEAD"];
  const defaultCachedMethods = useComputeOrigin ? ["GET", "HEAD"] : ["GET", "HEAD"];
  const resources = {};
  const outputs = {};
  resources.S3Bucket = {
    Type: "AWS::S3::Bucket",
    ...retainPolicy,
    Properties: {
      BucketName: bucketName,
      PublicAccessBlockConfiguration: {
        BlockPublicAcls: true,
        BlockPublicPolicy: false,
        IgnorePublicAcls: true,
        RestrictPublicBuckets: false
      },
      WebsiteConfiguration: {
        IndexDocument: defaultRootObject,
        ErrorDocument: errorDocument
      }
    }
  };
  outputs.BucketName = {
    Description: "S3 Bucket Name",
    Value: { Ref: "S3Bucket" }
  };
  outputs.BucketArn = {
    Description: "S3 Bucket ARN",
    Value: { "Fn::GetAtt": ["S3Bucket", "Arn"] }
  };
  resources.CloudFrontOAC = {
    Type: "AWS::CloudFront::OriginAccessControl",
    ...retainPolicy,
    Properties: {
      OriginAccessControlConfig: {
        Name: `OAC-${bucketName}`,
        Description: `OAC for ${bucketName}`,
        OriginAccessControlOriginType: "s3",
        SigningBehavior: "always",
        SigningProtocol: "sigv4"
      }
    }
  };
  let installRootRedirectLogicalId;
  if (passthroughUrls && useComputeOrigin) {
    installRootRedirectLogicalId = "InstallRootRedirectFunction";
    resources[installRootRedirectLogicalId] = {
      Type: "AWS::CloudFront::Function",
      Properties: {
        Name: { "Fn::Sub": "${AWS::StackName}-install-root-redirect" },
        AutoPublish: true,
        FunctionConfig: {
          Comment: "Redirect curl pantry.dev | bash (GET /) to /install.sh on S3",
          Runtime: "cloudfront-js-2.0"
        },
        FunctionCode: `function handler(event) {
  var request = event.request;
  if (request.uri === '/' || request.uri === '') {
    return {
      statusCode: 302,
      statusDescription: 'Found',
      headers: {
        location: { value: 'https://' + request.headers.host.value + '/install.sh' }
      }
    };
  }
  return request;
}`
      }
    };
  }
  if (!passthroughUrls) {
    resources.UrlRewriteFunction = {
      Type: "AWS::CloudFront::Function",
      Properties: {
        Name: { "Fn::Sub": "${AWS::StackName}-url-rewrite" },
        AutoPublish: true,
        FunctionConfig: {
          Comment: "Append .html extension to URLs without extensions",
          Runtime: "cloudfront-js-2.0"
        },
        FunctionCode: `function handler(event) {
  const request = event.request;
  var uri = request.uri;

  // If URI ends with /, serve index.html
  if (uri.endsWith('/')) {
    request.uri = uri + 'index.html';
  }
  // If URI doesn't have an extension, append .html
  else if (!uri.includes('.')) {
    request.uri = uri + '.html';
  }

  return request;
}`
      }
    };
  }
  const s3Origin = {
    Id: `S3-${bucketName}`,
    DomainName: { "Fn::GetAtt": ["S3Bucket", "RegionalDomainName"] },
    S3OriginConfig: {
      OriginAccessIdentity: ""
    },
    OriginAccessControlId: { "Fn::GetAtt": ["CloudFrontOAC", "Id"] }
  };
  const computeOrigin = useComputeOrigin ? {
    Id: computeOriginId,
    DomainName: computeOriginDomain,
    CustomOriginConfig: {
      HTTPPort: computeOriginPort,
      HTTPSPort: 443,
      OriginProtocolPolicy: "http-only",
      OriginSSLProtocols: ["TLSv1.2"]
    }
  } : null;
  const defaultTargetOriginId = useComputeOrigin ? computeOriginId : `S3-${bucketName}`;
  const distributionConfig = {
    Enabled: true,
    DefaultRootObject: defaultRootObject,
    HttpVersion: "http2and3",
    IPV6Enabled: true,
    PriceClass: "PriceClass_100",
    Origins: computeOrigin ? [computeOrigin, s3Origin] : [s3Origin],
    DefaultCacheBehavior: {
      TargetOriginId: defaultTargetOriginId,
      ViewerProtocolPolicy: "redirect-to-https",
      AllowedMethods: defaultAllowedMethods,
      CachedMethods: defaultCachedMethods,
      Compress: true,
      ...useComputeOrigin ? {
        CachePolicyId: "4135ea2d-6df8-44a3-9df3-4b5a84be39ad",
        OriginRequestPolicyId: "b689b0a8-53d0-40ab-baf2-68738e2966ac",
        ...passthroughUrls && installRootRedirectLogicalId ? {
          FunctionAssociations: [
            {
              EventType: "viewer-request",
              FunctionARN: { "Fn::GetAtt": [installRootRedirectLogicalId, "FunctionARN"] }
            }
          ]
        } : {}
      } : {
        CachePolicyId: "658327ea-f89d-4fab-a63d-7e88639e58f6",
        ...!passthroughUrls && {
          FunctionAssociations: [
            {
              EventType: "viewer-request",
              FunctionARN: { "Fn::GetAtt": ["UrlRewriteFunction", "FunctionARN"] }
            }
          ]
        }
      }
    },
    ...useComputeOrigin && passthroughUrls ? {
      CacheBehaviors: [
        {
          PathPattern: "/install.sh",
          TargetOriginId: `S3-${bucketName}`,
          ViewerProtocolPolicy: "redirect-to-https",
          AllowedMethods: ["GET", "HEAD", "OPTIONS"],
          CachedMethods: ["GET", "HEAD", "OPTIONS"],
          Compress: true,
          CachePolicyId: "658327ea-f89d-4fab-a63d-7e88639e58f6"
        }
      ]
    } : {},
    CustomErrorResponses: singlePageApp ? [
      {
        ErrorCode: 403,
        ResponseCode: 200,
        ResponsePagePath: `/${defaultRootObject}`,
        ErrorCachingMinTTL: 300
      },
      {
        ErrorCode: 404,
        ResponseCode: 200,
        ResponsePagePath: `/${defaultRootObject}`,
        ErrorCachingMinTTL: 300
      }
    ] : [
      {
        ErrorCode: 403,
        ResponseCode: 404,
        ResponsePagePath: `/${errorDocument}`,
        ErrorCachingMinTTL: 300
      },
      {
        ErrorCode: 404,
        ResponseCode: 404,
        ResponsePagePath: `/${errorDocument}`,
        ErrorCachingMinTTL: 300
      }
    ]
  };
  if (domain && certificateArn) {
    distributionConfig.Aliases = aliases && aliases.length > 0 ? aliases : [domain];
    distributionConfig.ViewerCertificate = {
      AcmCertificateArn: certificateArn,
      SslSupportMethod: "sni-only",
      MinimumProtocolVersion: "TLSv1.2_2021"
    };
  } else {
    distributionConfig.ViewerCertificate = {
      CloudFrontDefaultCertificate: true
    };
  }
  resources.CloudFrontDistribution = {
    Type: "AWS::CloudFront::Distribution",
    ...retainPolicy,
    DependsOn: [
      "S3Bucket",
      "CloudFrontOAC",
      ...passthroughUrls && useComputeOrigin && installRootRedirectLogicalId ? [installRootRedirectLogicalId] : [],
      ...!passthroughUrls ? ["UrlRewriteFunction"] : []
    ],
    Properties: {
      DistributionConfig: distributionConfig
    }
  };
  outputs.DistributionId = {
    Description: "CloudFront Distribution ID",
    Value: { Ref: "CloudFrontDistribution" }
  };
  outputs.DistributionDomain = {
    Description: "CloudFront Distribution Domain",
    Value: { "Fn::GetAtt": ["CloudFrontDistribution", "DomainName"] }
  };
  resources.S3BucketPolicy = {
    Type: "AWS::S3::BucketPolicy",
    ...retainPolicy,
    DependsOn: ["S3Bucket", "CloudFrontDistribution"],
    Properties: {
      Bucket: { Ref: "S3Bucket" },
      PolicyDocument: {
        Version: "2012-10-17",
        Statement: [
          {
            Sid: "AllowCloudFrontServicePrincipal",
            Effect: "Allow",
            Principal: {
              Service: "cloudfront.amazonaws.com"
            },
            Action: "s3:GetObject",
            Resource: { "Fn::Sub": "arn:aws:s3:::${S3Bucket}/*" },
            Condition: {
              StringEquals: {
                "AWS:SourceArn": {
                  "Fn::Sub": "arn:aws:cloudfront::${AWS::AccountId}:distribution/${CloudFrontDistribution}"
                }
              }
            }
          }
        ]
      }
    }
  };
  outputs.SiteUrl = {
    Description: "Site URL",
    Value: domain ? `https://${domain}` : { "Fn::Sub": "https://${CloudFrontDistribution.DomainName}" }
  };
  return {
    AWSTemplateFormatVersion: "2010-09-09",
    Description: `Static site infrastructure for ${domain || bucketName} (External DNS)`,
    Resources: resources,
    Outputs: outputs
  };
}
async function deployStaticSiteWithExternalDns(config) {
  const region = config.region || "us-east-1";
  const cfRegion = "us-east-1";
  const domain = config.domain;
  const bucket = config.bucket || domain.replace(/\./g, "-");
  const stackName = config.stackName || `${config.siteName}-static-site`;
  const cf = new CloudFormationClient(cfRegion);
  const acm = new ACMClient("us-east-1");
  let dnsProvider = null;
  if (!config.skipDnsVerification) {
    dnsProvider = createDnsProvider(config.dnsProvider);
    console.log(`Verifying DNS provider can manage ${domain}...`);
    const verify = await dnsProvider.listRecords(domain);
    if (!verify.success) {
      const reason = verify.message || "unknown error — check API credentials and domain ownership";
      return {
        success: false,
        stackName,
        bucket,
        message: `DNS provider '${dnsProvider.name}' cannot manage domain ${domain}: ${reason}`
      };
    }
    console.log(`DNS provider '${dnsProvider.name}' verified for ${domain}`);
  } else {
    console.log(`Skipping DNS verification for ${domain}`);
  }
  let certificateArn = config.certificateArn;
  const domainParts = domain.split(".");
  const isApexDomain = domainParts.length === 2;
  const wwwDomain = isApexDomain ? `www.${domain}` : undefined;
  if (!certificateArn) {
    console.log(`Checking for existing SSL certificate for ${domain}...`);
    const existingCert = await acm.findCertificateByDomain(domain);
    let existingCertCoversWww = false;
    if (existingCert && existingCert.Status === "ISSUED") {
      if (wwwDomain && existingCert.SubjectAlternativeNames) {
        existingCertCoversWww = existingCert.SubjectAlternativeNames.includes(wwwDomain) || existingCert.SubjectAlternativeNames.some((san) => san === `*.${domain}`);
      } else {
        existingCertCoversWww = true;
      }
      if (existingCertCoversWww) {
        certificateArn = existingCert.CertificateArn;
        console.log(`Found existing certificate with www coverage: ${certificateArn}`);
      } else {
        console.log(`Existing certificate doesn't cover ${wwwDomain}, requesting new one...`);
      }
    }
    if (!certificateArn) {
      if (!dnsProvider) {
        return {
          success: false,
          stackName,
          bucket,
          message: `No DNS provider available to validate SSL certificate for ${domain}. Provide a certificateArn or enable DNS verification.`
        };
      }
      console.log(`Requesting new SSL certificate for ${domain}${wwwDomain ? ` (including ${wwwDomain})` : ""}...`);
      const validator = new UnifiedDnsValidator(dnsProvider, "us-east-1");
      const certResult = await validator.findOrCreateCertificate({
        domainName: domain,
        subjectAlternativeNames: wwwDomain ? [wwwDomain] : undefined,
        waitForValidation: true,
        maxWaitMinutes: 10
      });
      if (certResult.status !== "issued") {
        return {
          success: false,
          stackName,
          bucket,
          message: `SSL certificate validation failed. Status: ${certResult.status}`
        };
      }
      certificateArn = certResult.certificateArn;
      console.log(`Certificate issued: ${certificateArn}`);
    }
  }
  let stackExists = false;
  let existingBucketName;
  try {
    const existingStacks = await cf.describeStacks({ stackName });
    if (existingStacks.Stacks.length > 0) {
      const stack = existingStacks.Stacks[0];
      const stackStatus = stack.StackStatus;
      if (stackStatus === "DELETE_IN_PROGRESS") {
        console.log("Previous stack is still being deleted, waiting...");
        await cf.waitForStack(stackName, "stack-delete-complete");
        stackExists = false;
      } else if (stackStatus === "DELETE_COMPLETE") {
        stackExists = false;
      } else {
        stackExists = true;
        const outputs2 = stack.Outputs || [];
        existingBucketName = outputs2.find((o) => o.OutputKey === "BucketName")?.OutputValue;
      }
    }
  } catch (err) {
    if (err.message?.includes("does not exist") || err.code === "ValidationError") {
      stackExists = false;
    } else {
      throw err;
    }
  }
  let finalBucket = existingBucketName || bucket;
  if (!stackExists) {
    const s3 = new S3Client(region);
    const cloudfront = new CloudFrontClient;
    let hasExistingDistribution = false;
    if (domain) {
      try {
        console.log(`Checking for existing CloudFront distributions with alias ${domain}...`);
        const distributions = await cloudfront.listDistributions();
        for (const dist of distributions) {
          let aliases2 = [];
          if (dist.Aliases?.Items) {
            if (Array.isArray(dist.Aliases.Items)) {
              aliases2 = dist.Aliases.Items;
            } else if (typeof dist.Aliases.Items === "object") {
              const cname = dist.Aliases.Items.CNAME;
              if (typeof cname === "string") {
                aliases2 = [cname];
              } else if (Array.isArray(cname)) {
                aliases2 = cname;
              }
            }
          }
          if (aliases2.includes(domain)) {
            hasExistingDistribution = true;
            console.log(`Found existing CloudFront distribution ${dist.Id} with alias ${domain}`);
            console.log(`Reusing existing infrastructure for updates...`);
            const distConfig = await cloudfront.getDistributionConfig(dist.Id);
            const originsData = distConfig.DistributionConfig?.Origins?.Items;
            let originBucket;
            if (originsData) {
              let originList = [];
              if (Array.isArray(originsData)) {
                originList = originsData;
              } else if (originsData.Origin) {
                originList = Array.isArray(originsData.Origin) ? originsData.Origin : [originsData.Origin];
              } else {
                originList = [originsData];
              }
              for (const origin of originList) {
                const domainName = origin.DomainName || "";
                const s3Match = domainName.match(/^([^.]+)\.s3[\.-]/);
                if (s3Match) {
                  originBucket = s3Match[1];
                  break;
                }
              }
            }
            if (originBucket) {
              console.log(`Using existing S3 bucket: ${originBucket}`);
              return {
                success: true,
                stackName: `existing-${dist.Id}`,
                bucket: originBucket,
                distributionId: dist.Id,
                distributionDomain: dist.DomainName,
                domain,
                certificateArn,
                message: "Using existing CloudFront distribution",
                _existingInfrastructure: true
              };
            }
          }
        }
      } catch {}
    }
    if (!hasExistingDistribution) {
      try {
        const headResult = await s3.headBucket(bucket);
        if (headResult.exists) {
          console.log(`Found orphaned S3 bucket ${bucket}, cleaning up...`);
          try {
            const cleanupPromise = s3.emptyBucket(bucket).then(() => s3.deleteBucket(bucket));
            const timeoutPromise = new Promise((_, reject) => setTimeout(() => reject(new Error("Bucket cleanup timeout")), 30000));
            await Promise.race([cleanupPromise, timeoutPromise]);
            console.log(`Deleted orphaned S3 bucket ${bucket}`);
          } catch (cleanupErr) {
            console.log(`Note: Could not clean up S3 bucket: ${cleanupErr.message}`);
            const suffix = Date.now().toString(36);
            finalBucket = `${bucket}-${suffix}`;
            console.log(`Using alternative bucket name: ${finalBucket}`);
          }
        }
      } catch {}
    }
  }
  const aliases = wwwDomain ? [domain, wwwDomain] : [domain];
  const template = generateExternalDnsStaticSiteTemplate({
    bucketName: finalBucket,
    domain,
    aliases,
    certificateArn,
    defaultRootObject: config.defaultRootObject,
    errorDocument: config.errorDocument,
    passthroughUrls: config.passthroughUrls,
    singlePageApp: config.singlePageApp,
    dynamicApp: config.dynamicApp,
    computeOriginDomain: config.computeOriginDomain,
    computeOriginPort: config.computeOriginPort,
    computeOriginId: config.computeOriginId
  });
  const tags = Object.entries(config.tags || {}).map(([Key, Value]) => ({ Key, Value }));
  tags.push({ Key: "ManagedBy", Value: "ts-cloud" });
  tags.push({ Key: "Application", Value: config.siteName });
  if (dnsProvider) {
    tags.push({ Key: "DnsProvider", Value: dnsProvider.name });
  }
  let stackId;
  let isUpdate = false;
  if (stackExists) {
    isUpdate = true;
    console.log(`Updating CloudFormation stack: ${stackName}`);
    try {
      const result = await cf.updateStack({
        stackName,
        templateBody: JSON.stringify(template),
        capabilities: ["CAPABILITY_IAM"],
        tags
      });
      stackId = result.StackId;
      console.log(`Update initiated, stack ID: ${stackId}`);
    } catch (err) {
      if (err.message?.includes("No updates are to be performed")) {
        const stacks2 = await cf.describeStacks({ stackName });
        stackId = stacks2.Stacks[0].StackId;
        const outputs2 = stacks2.Stacks[0]?.Outputs || [];
        const getOutput2 = (key) => outputs2.find((o) => o.OutputKey === key)?.OutputValue;
        const distributionDomain2 = getOutput2("DistributionDomain");
        if (distributionDomain2) {
          await ensureDnsRecords(dnsProvider, domain, distributionDomain2);
        }
        return {
          success: true,
          stackId,
          stackName,
          bucket: getOutput2("BucketName") || finalBucket,
          distributionId: getOutput2("DistributionId"),
          distributionDomain: distributionDomain2,
          domain,
          certificateArn,
          message: "Static site infrastructure is already up to date"
        };
      } else {
        throw err;
      }
    }
  } else {
    console.log(`Creating CloudFormation stack: ${stackName}`);
    console.log(`Bucket name: ${finalBucket}`);
    console.log(`Domain: ${domain}`);
    console.log(`Certificate ARN: ${certificateArn}`);
    const result = await cf.createStack({
      stackName,
      templateBody: JSON.stringify(template),
      capabilities: ["CAPABILITY_IAM"],
      tags,
      onFailure: "DELETE"
    });
    stackId = result.StackId;
    console.log(`Create initiated, stack ID: ${stackId}`);
  }
  console.log(`Waiting for stack to reach ${isUpdate ? "stack-update-complete" : "stack-create-complete"}...`);
  try {
    await cf.waitForStack(stackName, isUpdate ? "stack-update-complete" : "stack-create-complete");
    console.log("Stack operation completed successfully!");
  } catch (err) {
    if (err.message?.includes("must be verified") || err.message?.includes("Access denied for operation")) {
      console.log("CloudFront account verification required - checking for existing infrastructure...");
      try {
        const cloudfront = new CloudFrontClient;
        const distributions = await cloudfront.listDistributions();
        for (const dist of distributions) {
          let aliases2 = [];
          if (dist.Aliases?.Items) {
            if (Array.isArray(dist.Aliases.Items)) {
              aliases2 = dist.Aliases.Items;
            } else if (typeof dist.Aliases.Items === "object") {
              const cname = dist.Aliases.Items.CNAME;
              if (typeof cname === "string") {
                aliases2 = [cname];
              } else if (Array.isArray(cname)) {
                aliases2 = cname;
              }
            }
          }
          if (aliases2.includes(domain)) {
            console.log(`Found existing CloudFront distribution ${dist.Id} with alias ${domain}`);
            console.log(`Using existing infrastructure despite account verification requirement...`);
            const distConfig = await cloudfront.getDistributionConfig(dist.Id);
            const originsData = distConfig.DistributionConfig?.Origins?.Items;
            let originBucket;
            if (originsData) {
              let originList = [];
              if (Array.isArray(originsData)) {
                originList = originsData;
              } else if (originsData.Origin) {
                originList = Array.isArray(originsData.Origin) ? originsData.Origin : [originsData.Origin];
              } else {
                originList = [originsData];
              }
              for (const origin of originList) {
                const domainName = origin.DomainName || "";
                const s3Match = domainName.match(/^([^.]+)\.s3[\.-]/);
                if (s3Match) {
                  originBucket = s3Match[1];
                  break;
                }
              }
            }
            if (originBucket) {
              console.log(`Using existing S3 bucket: ${originBucket}`);
              const distributionDomain2 = dist.DomainName;
              if (distributionDomain2) {
                await ensureDnsRecords(dnsProvider, domain, distributionDomain2);
              }
              return {
                success: true,
                stackName: `existing-${dist.Id}`,
                bucket: originBucket,
                distributionId: dist.Id,
                distributionDomain: dist.DomainName,
                domain,
                certificateArn,
                message: "Using existing CloudFront distribution (account verification pending for new distributions)"
              };
            }
          }
        }
      } catch {}
      const verificationMessage = `
┌─────────────────────────────────────────────────────────────────────────────┐
│  AWS ACCOUNT VERIFICATION REQUIRED                                          │
├─────────────────────────────────────────────────────────────────────────────┤
│  Your AWS account must be verified before you can create CloudFront         │
│  distributions. This is a one-time requirement for new AWS accounts.        │
│                                                                             │
│  To verify your account:                                                    │
│                                                                             │
│  1. Go to: https://console.aws.amazon.com/support/home#/                   │
│  2. Click "Create case"                                                     │
│  3. Select "Service limit increase"                                         │
│  4. For Service: Select "CloudFront"                                        │
│  5. For Request: "Please verify my account for CloudFront access"           │
│  6. Submit the case                                                         │
│                                                                             │
│  Verification typically takes 1-2 business days.                            │
│  After verification, re-run: bunx bunpress deploy                           │
└─────────────────────────────────────────────────────────────────────────────┘`;
      console.log(verificationMessage);
      return {
        success: false,
        stackId,
        stackName,
        bucket: finalBucket,
        message: "CloudFront account verification required. Please contact AWS Support."
      };
    }
    return {
      success: false,
      stackId,
      stackName,
      bucket: finalBucket,
      message: `Stack deployment failed: ${err.message}`
    };
  }
  const stacks = await cf.describeStacks({ stackName });
  const outputs = stacks.Stacks[0]?.Outputs || [];
  const getOutput = (key) => outputs.find((o) => o.OutputKey === key)?.OutputValue;
  const distributionDomain = getOutput("DistributionDomain");
  if (distributionDomain && dnsProvider) {
    console.log(`Creating DNS records via ${dnsProvider.name}...`);
    await ensureDnsRecords(dnsProvider, domain, distributionDomain);
  } else if (distributionDomain && !dnsProvider) {
    console.log(`Skipping DNS record creation (DNS verification was skipped)`);
  }
  return {
    success: true,
    stackId,
    stackName,
    bucket: getOutput("BucketName") || finalBucket,
    distributionId: getOutput("DistributionId"),
    distributionDomain,
    domain,
    certificateArn,
    message: "Static site infrastructure deployed successfully with external DNS"
  };
}
async function ensureDnsRecords(dnsProvider, domain, cloudfrontDomain) {
  if (!dnsProvider) {
    console.log(`Skipping DNS record creation (DNS verification was skipped)`);
    return;
  }
  const parts = domain.split(".");
  const isApexDomain = parts.length === 2;
  if (isApexDomain) {
    if (dnsProvider instanceof Route53Provider) {
      console.log(`Creating Route53 alias record for apex domain ${domain} -> ${cloudfrontDomain}`);
      const result = await dnsProvider.createCloudFrontAlias({
        domain,
        name: domain,
        cloudFrontDomainName: cloudfrontDomain
      });
      if (!result.success) {
        console.warn(`Warning: Could not create Route53 alias record: ${result.message}`);
        console.warn(`Please manually create an A-record alias in Route53:`);
        console.warn(`  ${domain} -> ${cloudfrontDomain}`);
      } else {
        console.log(`Created Route53 alias record: ${domain} -> ${cloudfrontDomain}`);
      }
      const wwwDomain = `www.${domain}`;
      console.log(`Creating Route53 alias record for ${wwwDomain} -> ${cloudfrontDomain}`);
      const wwwResult = await dnsProvider.createCloudFrontAlias({
        domain,
        name: wwwDomain,
        cloudFrontDomainName: cloudfrontDomain
      });
      if (!wwwResult.success) {
        console.warn(`Warning: Could not create www Route53 alias: ${wwwResult.message}`);
        console.warn(`Please manually create an A-record alias in Route53:`);
        console.warn(`  ${wwwDomain} -> ${cloudfrontDomain}`);
      } else {
        console.log(`Created Route53 alias record: ${wwwDomain} -> ${cloudfrontDomain}`);
      }
    } else {
      try {
        const existingA = await dnsProvider.listRecords(domain, "A");
        if (existingA.success) {
          for (const rec of existingA.records) {
            const recSub = rec.name.replace(/\.$/, "");
            if (recSub === domain || recSub === "") {
              console.log(`Removing existing root A record (${rec.content}) to make room for ALIAS...`);
              await dnsProvider.deleteRecord(domain, {
                name: domain,
                type: "A",
                content: rec.content
              });
            }
          }
        }
      } catch (err) {
        console.log(`Note: Could not check for conflicting A records: ${err instanceof Error ? err.message : err}`);
      }
      console.log(`Creating ALIAS record for apex domain ${domain} -> ${cloudfrontDomain}`);
      const result = await dnsProvider.upsertRecord(domain, {
        name: domain,
        type: "ALIAS",
        content: cloudfrontDomain,
        ttl: 600
      });
      if (!result.success) {
        console.warn(`Warning: Could not create ALIAS record: ${result.message}`);
        console.warn(`Please manually create an ALIAS record in your DNS provider:`);
        console.warn(`  ${domain} -> ${cloudfrontDomain}`);
      } else {
        console.log(`Created ALIAS record: ${domain} -> ${cloudfrontDomain}`);
      }
      const wwwDomain = `www.${domain}`;
      console.log(`Creating CNAME record for ${wwwDomain} -> ${cloudfrontDomain}`);
      const wwwResult = await dnsProvider.upsertRecord(domain, {
        name: wwwDomain,
        type: "CNAME",
        content: cloudfrontDomain,
        ttl: 600
      });
      if (!wwwResult.success) {
        console.warn(`Warning: Could not create www DNS record: ${wwwResult.message}`);
        console.warn(`Please manually create a CNAME record:`);
        console.warn(`  ${wwwDomain} -> ${cloudfrontDomain}`);
      } else {
        console.log(`Created CNAME record: ${wwwDomain} -> ${cloudfrontDomain}`);
      }
    }
  } else {
    console.log(`Creating CNAME record for ${domain} -> ${cloudfrontDomain}`);
    const result = await dnsProvider.upsertRecord(domain, {
      name: domain,
      type: "CNAME",
      content: cloudfrontDomain,
      ttl: 600
    });
    if (!result.success) {
      console.warn(`Warning: Could not create DNS record: ${result.message}`);
      console.warn(`Please manually create a CNAME record:`);
      console.warn(`  ${domain} -> ${cloudfrontDomain}`);
    } else {
      console.log(`Created CNAME record: ${domain} -> ${cloudfrontDomain}`);
    }
  }
}
async function deployStaticSiteWithExternalDnsFull(config) {
  const { sourceDir, cleanBucket = false, onProgress, ...siteConfig } = config;
  onProgress?.("infrastructure", "Deploying CloudFormation stack...");
  const infraResult = await deployStaticSiteWithExternalDns(siteConfig);
  if (!infraResult.success) {
    return infraResult;
  }
  if (cleanBucket) {
    onProgress?.("clean", "Cleaning old files from S3...");
    try {
      const s3 = new S3Client(siteConfig.region || "us-east-1");
      await s3.emptyBucket(infraResult.bucket);
    } catch (err) {
      console.log(`Note: Could not clean bucket: ${err.message}`);
    }
  }
  onProgress?.("upload", "Uploading files to S3...");
  const { uploadStaticFiles } = await import("./chunk-z3bakpnh.js");
  const uploadResult = await uploadStaticFiles({
    sourceDir,
    bucket: infraResult.bucket,
    region: siteConfig.region || "us-east-1",
    cacheControl: siteConfig.cacheControl,
    onProgress: (uploaded, total, file) => {
      onProgress?.("upload", `${uploaded}/${total}: ${file}`);
    }
  });
  if (uploadResult.errors.length > 0) {
    return {
      ...infraResult,
      success: false,
      message: `Upload errors: ${uploadResult.errors.join(", ")}`,
      filesUploaded: uploadResult.uploaded
    };
  }
  if (infraResult.distributionId && uploadResult.uploaded > 0) {
    onProgress?.("invalidate", "Invalidating CloudFront cache...");
    const { invalidateCache } = await import("./chunk-z3bakpnh.js");
    await invalidateCache(infraResult.distributionId);
  }
  onProgress?.("complete", "Deployment complete!");
  const message = uploadResult.skipped > 0 ? `Deployed ${uploadResult.uploaded} files (${uploadResult.skipped} unchanged) with external DNS` : `Deployed ${uploadResult.uploaded} files successfully with external DNS`;
  return {
    ...infraResult,
    filesUploaded: uploadResult.uploaded,
    filesSkipped: uploadResult.skipped,
    message
  };
}

// src/deploy/static-site.ts
function generateStaticSiteTemplate(config) {
  const {
    bucketName,
    domain,
    certificateArn,
    hostedZoneId,
    defaultRootObject = "index.html",
    errorDocument = "404.html"
  } = config;
  const resources = {};
  const outputs = {};
  resources.S3Bucket = {
    Type: "AWS::S3::Bucket",
    Properties: {
      BucketName: bucketName,
      PublicAccessBlockConfiguration: {
        BlockPublicAcls: true,
        BlockPublicPolicy: false,
        IgnorePublicAcls: true,
        RestrictPublicBuckets: false
      },
      WebsiteConfiguration: {
        IndexDocument: defaultRootObject,
        ErrorDocument: errorDocument
      }
    }
  };
  outputs.BucketName = {
    Description: "S3 Bucket Name",
    Value: { Ref: "S3Bucket" }
  };
  outputs.BucketArn = {
    Description: "S3 Bucket ARN",
    Value: { "Fn::GetAtt": ["S3Bucket", "Arn"] }
  };
  resources.CloudFrontOAC = {
    Type: "AWS::CloudFront::OriginAccessControl",
    Properties: {
      OriginAccessControlConfig: {
        Name: `OAC-${bucketName}`,
        Description: `OAC for ${bucketName}`,
        OriginAccessControlOriginType: "s3",
        SigningBehavior: "always",
        SigningProtocol: "sigv4"
      }
    }
  };
  resources.UrlRewriteFunction = {
    Type: "AWS::CloudFront::Function",
    Properties: {
      Name: { "Fn::Sub": "${AWS::StackName}-url-rewrite" },
      AutoPublish: true,
      FunctionConfig: {
        Comment: "Append .html extension to URLs without extensions",
        Runtime: "cloudfront-js-2.0"
      },
      FunctionCode: `function handler(event) {
  const request = event.request;
  var uri = request.uri;

  // If URI ends with /, serve index.html
  if (uri.endsWith('/')) {
    request.uri = uri + 'index.html';
  }
  // If URI doesn't have an extension, append .html
  else if (!uri.includes('.')) {
    request.uri = uri + '.html';
  }

  return request;
}`
    }
  };
  const distributionConfig = {
    Enabled: true,
    DefaultRootObject: defaultRootObject,
    HttpVersion: "http2and3",
    IPV6Enabled: true,
    PriceClass: "PriceClass_100",
    Origins: [
      {
        Id: `S3-${bucketName}`,
        DomainName: { "Fn::GetAtt": ["S3Bucket", "RegionalDomainName"] },
        S3OriginConfig: {
          OriginAccessIdentity: ""
        },
        OriginAccessControlId: { "Fn::GetAtt": ["CloudFrontOAC", "Id"] }
      }
    ],
    DefaultCacheBehavior: {
      TargetOriginId: `S3-${bucketName}`,
      ViewerProtocolPolicy: "redirect-to-https",
      AllowedMethods: ["GET", "HEAD"],
      CachedMethods: ["GET", "HEAD"],
      Compress: true,
      CachePolicyId: "658327ea-f89d-4fab-a63d-7e88639e58f6",
      FunctionAssociations: [
        {
          EventType: "viewer-request",
          FunctionARN: { "Fn::GetAtt": ["UrlRewriteFunction", "FunctionARN"] }
        }
      ]
    },
    CustomErrorResponses: [
      {
        ErrorCode: 403,
        ResponseCode: 200,
        ResponsePagePath: `/${defaultRootObject}`,
        ErrorCachingMinTTL: 300
      },
      {
        ErrorCode: 404,
        ResponseCode: 404,
        ResponsePagePath: `/${errorDocument}`,
        ErrorCachingMinTTL: 300
      }
    ]
  };
  if (domain && certificateArn) {
    distributionConfig.Aliases = [domain];
    distributionConfig.ViewerCertificate = {
      AcmCertificateArn: certificateArn,
      SslSupportMethod: "sni-only",
      MinimumProtocolVersion: "TLSv1.2_2021"
    };
  } else {
    distributionConfig.ViewerCertificate = {
      CloudFrontDefaultCertificate: true
    };
  }
  resources.CloudFrontDistribution = {
    Type: "AWS::CloudFront::Distribution",
    DependsOn: ["S3Bucket", "CloudFrontOAC", "UrlRewriteFunction"],
    Properties: {
      DistributionConfig: distributionConfig
    }
  };
  outputs.DistributionId = {
    Description: "CloudFront Distribution ID",
    Value: { Ref: "CloudFrontDistribution" }
  };
  outputs.DistributionDomain = {
    Description: "CloudFront Distribution Domain",
    Value: { "Fn::GetAtt": ["CloudFrontDistribution", "DomainName"] }
  };
  resources.S3BucketPolicy = {
    Type: "AWS::S3::BucketPolicy",
    DependsOn: ["S3Bucket", "CloudFrontDistribution"],
    Properties: {
      Bucket: { Ref: "S3Bucket" },
      PolicyDocument: {
        Version: "2012-10-17",
        Statement: [
          {
            Sid: "AllowCloudFrontServicePrincipal",
            Effect: "Allow",
            Principal: {
              Service: "cloudfront.amazonaws.com"
            },
            Action: "s3:GetObject",
            Resource: { "Fn::Sub": "arn:aws:s3:::${S3Bucket}/*" },
            Condition: {
              StringEquals: {
                "AWS:SourceArn": {
                  "Fn::Sub": "arn:aws:cloudfront::${AWS::AccountId}:distribution/${CloudFrontDistribution}"
                }
              }
            }
          }
        ]
      }
    }
  };
  if (domain && hostedZoneId) {
    resources.DNSRecord = {
      Type: "AWS::Route53::RecordSet",
      DependsOn: "CloudFrontDistribution",
      Properties: {
        HostedZoneId: hostedZoneId,
        Name: domain,
        Type: "A",
        AliasTarget: {
          DNSName: { "Fn::GetAtt": ["CloudFrontDistribution", "DomainName"] },
          HostedZoneId: "Z2FDTNDATAQYW2",
          EvaluateTargetHealth: false
        }
      }
    };
    resources.DNSRecordIPv6 = {
      Type: "AWS::Route53::RecordSet",
      DependsOn: "CloudFrontDistribution",
      Properties: {
        HostedZoneId: hostedZoneId,
        Name: domain,
        Type: "AAAA",
        AliasTarget: {
          DNSName: { "Fn::GetAtt": ["CloudFrontDistribution", "DomainName"] },
          HostedZoneId: "Z2FDTNDATAQYW2",
          EvaluateTargetHealth: false
        }
      }
    };
    outputs.SiteUrl = {
      Description: "Site URL",
      Value: { "Fn::Sub": "https://${DNSRecord}" }
    };
  }
  return {
    AWSTemplateFormatVersion: "2010-09-09",
    Description: `Static site infrastructure for ${domain || bucketName}`,
    Resources: resources,
    Outputs: outputs
  };
}
async function deployStaticSite(config) {
  if (config.dnsProvider && config.dnsProvider.provider !== "route53") {
    const domain2 = config.domain || (config.subdomain && config.baseDomain ? `${config.subdomain}.${config.baseDomain}` : undefined);
    if (!domain2) {
      return {
        success: false,
        stackName: config.stackName || `${config.siteName}-static-site`,
        bucket: config.bucket || `${config.siteName}-${Date.now()}`,
        message: "Domain is required when using external DNS provider"
      };
    }
    return deployStaticSiteWithExternalDns({
      siteName: config.siteName,
      region: config.region,
      domain: domain2,
      bucket: config.bucket,
      certificateArn: config.certificateArn,
      stackName: config.stackName,
      defaultRootObject: config.defaultRootObject,
      errorDocument: config.errorDocument,
      cacheControl: config.cacheControl,
      tags: config.tags,
      dnsProvider: config.dnsProvider
    });
  }
  const region = config.region || "us-east-1";
  const cfRegion = "us-east-1";
  let domain;
  if (config.domain) {
    domain = config.domain;
  } else if (config.subdomain && config.baseDomain) {
    domain = `${config.subdomain}.${config.baseDomain}`;
  }
  const bucket = config.bucket || (domain ? domain.replace(/\./g, "-") : `${config.siteName}-${Date.now()}`);
  const stackName = config.stackName || `${config.siteName}-static-site`;
  const cf = new CloudFormationClient(cfRegion);
  const route53 = new Route53Client;
  const acm = new ACMClient("us-east-1");
  const acmValidator = new ACMDnsValidator("us-east-1");
  let hostedZoneId = config.hostedZoneId;
  let certificateArn = config.certificateArn;
  if (domain && !hostedZoneId) {
    const zone = await route53.findHostedZoneForDomain(domain);
    if (zone) {
      hostedZoneId = zone.Id.replace("/hostedzone/", "");
    } else {
      return {
        success: false,
        stackName,
        bucket,
        message: `No Route53 hosted zone found for ${config.baseDomain || domain}. Please create one first.`
      };
    }
  }
  if (domain && !certificateArn && hostedZoneId) {
    const existingCert = await acm.findCertificateByDomain(domain);
    if (existingCert && existingCert.Status === "ISSUED") {
      certificateArn = existingCert.CertificateArn;
    } else {
      const certResult = await acmValidator.requestAndValidate({
        domainName: domain,
        hostedZoneId,
        waitForValidation: true,
        maxWaitMinutes: 10
      });
      certificateArn = certResult.certificateArn;
    }
  }
  let stackExists = false;
  let existingBucketName;
  try {
    const existingStacks = await cf.describeStacks({ stackName });
    if (existingStacks.Stacks.length > 0) {
      const stack = existingStacks.Stacks[0];
      const stackStatus = stack.StackStatus;
      if (stackStatus === "DELETE_IN_PROGRESS") {
        console.log("Previous stack is still being deleted, waiting...");
        await cf.waitForStack(stackName, "stack-delete-complete");
        stackExists = false;
      } else if (stackStatus === "DELETE_COMPLETE") {
        stackExists = false;
      } else {
        stackExists = true;
        const outputs2 = stack.Outputs || [];
        existingBucketName = outputs2.find((o) => o.OutputKey === "BucketName")?.OutputValue;
      }
    }
  } catch (err) {
    if (err.message?.includes("does not exist") || err.code === "ValidationError") {
      stackExists = false;
    } else {
      throw err;
    }
  }
  let finalBucket = existingBucketName || bucket;
  if (!stackExists) {
    const s3 = new S3Client(region);
    const cloudfront = new CloudFrontClient;
    let hasExistingDistribution = false;
    if (domain) {
      try {
        console.log(`Checking for existing CloudFront distribution for ${domain}...`);
        const distributions = await cloudfront.listDistributions();
        for (const dist of distributions) {
          let aliases = [];
          if (dist.Aliases?.Items) {
            if (Array.isArray(dist.Aliases.Items)) {
              aliases = dist.Aliases.Items;
            } else if (typeof dist.Aliases.Items === "object") {
              const cname = dist.Aliases.Items.CNAME;
              if (typeof cname === "string") {
                aliases = [cname];
              } else if (Array.isArray(cname)) {
                aliases = cname;
              }
            }
          }
          if (aliases.includes(domain)) {
            hasExistingDistribution = true;
            console.log(`Found existing CloudFront distribution ${dist.Id} for ${domain}`);
            const distConfig = await cloudfront.getDistributionConfig(dist.Id);
            const originsData = distConfig.DistributionConfig?.Origins?.Items;
            let originBucket;
            if (originsData) {
              let originList = [];
              if (Array.isArray(originsData)) {
                originList = originsData;
              } else if (originsData.Origin) {
                originList = Array.isArray(originsData.Origin) ? originsData.Origin : [originsData.Origin];
              } else {
                originList = [originsData];
              }
              for (const origin of originList) {
                const domainName = origin.DomainName || "";
                const s3Match = domainName.match(/^([^.]+)\.s3[\.-]/);
                if (s3Match) {
                  originBucket = s3Match[1];
                  break;
                }
              }
            }
            if (originBucket) {
              const expectedBucketPrefix = domain.replace(/\./g, "-");
              if (!originBucket.startsWith(expectedBucketPrefix) && !originBucket.includes(config.siteName)) {
                console.log(`Warning: Found distribution with mismatched bucket ${originBucket}, skipping...`);
                continue;
              }
              console.log(`Using existing S3 bucket: ${originBucket}`);
              if (hostedZoneId && dist.DomainName) {
                try {
                  console.log(`Ensuring Route53 records exist for ${domain}...`);
                  await route53.createAliasRecord({
                    HostedZoneId: hostedZoneId,
                    Name: domain,
                    Type: "A",
                    TargetHostedZoneId: Route53Client.CloudFrontHostedZoneId,
                    TargetDNSName: dist.DomainName,
                    EvaluateTargetHealth: false
                  });
                  await route53.createAliasRecord({
                    HostedZoneId: hostedZoneId,
                    Name: domain,
                    Type: "AAAA",
                    TargetHostedZoneId: Route53Client.CloudFrontHostedZoneId,
                    TargetDNSName: dist.DomainName,
                    EvaluateTargetHealth: false
                  });
                  console.log(`Route53 records ensured for ${domain}`);
                } catch (dnsErr) {
                  console.log(`Note: Could not update Route53 records: ${dnsErr.message}`);
                }
              }
              return {
                success: true,
                stackName: `existing-${dist.Id}`,
                bucket: originBucket,
                distributionId: dist.Id,
                distributionDomain: dist.DomainName,
                domain,
                certificateArn,
                message: "Using existing CloudFront distribution"
              };
            }
          }
        }
      } catch {}
      if (hostedZoneId) {
        try {
          const recordsResult = await route53.listResourceRecordSets({ HostedZoneId: hostedZoneId });
          const records = recordsResult.ResourceRecordSets || [];
          for (const record of records) {
            if (record.Name === `${domain}.` && (record.Type === "A" || record.Type === "AAAA")) {
              if (record.AliasTarget) {
                console.log(`Found orphaned Route53 ${record.Type} record for ${domain}, cleaning up...`);
                try {
                  await route53.deleteRecord({
                    HostedZoneId: hostedZoneId,
                    RecordSet: record
                  });
                  console.log(`Deleted orphaned Route53 ${record.Type} record for ${domain}`);
                } catch (recordErr) {
                  console.log(`Note: Could not delete Route53 record: ${recordErr.message}`);
                }
              }
            }
          }
        } catch {}
      }
    }
    if (!hasExistingDistribution) {
      try {
        const headResult = await s3.headBucket(bucket);
        if (headResult.exists) {
          console.log(`Found orphaned S3 bucket ${bucket}, cleaning up...`);
          try {
            const cleanupPromise = s3.emptyBucket(bucket).then(() => s3.deleteBucket(bucket));
            const timeoutPromise = new Promise((_, reject) => setTimeout(() => reject(new Error("Bucket cleanup timeout")), 30000));
            await Promise.race([cleanupPromise, timeoutPromise]);
            console.log(`Deleted orphaned S3 bucket ${bucket}`);
          } catch (cleanupErr) {
            console.log(`Note: Could not clean up S3 bucket: ${cleanupErr.message}`);
            const suffix = Date.now().toString(36);
            finalBucket = `${bucket}-${suffix}`;
            console.log(`Using alternative bucket name: ${finalBucket}`);
          }
        }
      } catch {}
    }
  }
  const template = generateStaticSiteTemplate({
    bucketName: finalBucket,
    domain,
    certificateArn,
    hostedZoneId,
    defaultRootObject: config.defaultRootObject,
    errorDocument: config.errorDocument
  });
  const tags = Object.entries(config.tags || {}).map(([Key, Value]) => ({ Key, Value }));
  tags.push({ Key: "ManagedBy", Value: "ts-cloud" });
  tags.push({ Key: "Application", Value: config.siteName });
  let stackId;
  let isUpdate = false;
  if (stackExists) {
    isUpdate = true;
    console.log(`Updating CloudFormation stack: ${stackName}`);
    console.log(`Using existing bucket: ${finalBucket}`);
    console.log(`Domain: ${domain || "not specified"}`);
    console.log(`Certificate ARN: ${certificateArn || "not specified"}`);
    try {
      const result = await cf.updateStack({
        stackName,
        templateBody: JSON.stringify(template),
        capabilities: ["CAPABILITY_IAM"],
        tags
      });
      stackId = result.StackId;
      console.log(`Update initiated, stack ID: ${stackId}`);
    } catch (err) {
      if (err.message?.includes("No updates are to be performed")) {
        const stacks2 = await cf.describeStacks({ stackName });
        stackId = stacks2.Stacks[0].StackId;
        const outputs2 = stacks2.Stacks[0]?.Outputs || [];
        const getOutput2 = (key) => outputs2.find((o) => o.OutputKey === key)?.OutputValue;
        return {
          success: true,
          stackId,
          stackName,
          bucket: getOutput2("BucketName") || finalBucket,
          distributionId: getOutput2("DistributionId"),
          distributionDomain: getOutput2("DistributionDomain"),
          domain,
          certificateArn,
          message: "Static site infrastructure is already up to date"
        };
      } else {
        throw err;
      }
    }
  } else {
    console.log(`Creating CloudFormation stack: ${stackName}`);
    console.log(`Bucket name: ${finalBucket}`);
    console.log(`Domain: ${domain || "not specified"}`);
    console.log(`Certificate ARN: ${certificateArn || "not specified"}`);
    console.log("Stack does not exist, creating...");
    const result = await cf.createStack({
      stackName,
      templateBody: JSON.stringify(template),
      capabilities: ["CAPABILITY_IAM"],
      tags,
      onFailure: "DELETE"
    });
    stackId = result.StackId;
    console.log(`Create initiated, stack ID: ${stackId}`);
  }
  console.log(`Waiting for stack to reach ${isUpdate ? "stack-update-complete" : "stack-create-complete"}...`);
  try {
    await cf.waitForStack(stackName, isUpdate ? "stack-update-complete" : "stack-create-complete");
    console.log("Stack operation completed successfully!");
  } catch (err) {
    if (err.message?.includes("must be verified") || err.message?.includes("Access denied for operation") || err.message?.includes("failed")) {
      console.log("CloudFormation deployment failed, trying direct API creation...");
      const cloudfront = new CloudFrontClient;
      if (domain) {
        try {
          const distributions = await cloudfront.listDistributions();
          for (const dist of distributions) {
            let aliases = [];
            if (dist.Aliases?.Items) {
              if (Array.isArray(dist.Aliases.Items)) {
                aliases = dist.Aliases.Items;
              } else if (typeof dist.Aliases.Items === "object") {
                const cname = dist.Aliases.Items.CNAME;
                if (typeof cname === "string") {
                  aliases = [cname];
                } else if (Array.isArray(cname)) {
                  aliases = cname;
                }
              }
            }
            if (aliases.includes(domain)) {
              console.log(`Found existing CloudFront distribution ${dist.Id} with alias ${domain}`);
              const distConfig = await cloudfront.getDistributionConfig(dist.Id);
              const originsData = distConfig.DistributionConfig?.Origins?.Items;
              let originBucket;
              if (originsData) {
                let originList = [];
                if (Array.isArray(originsData)) {
                  originList = originsData;
                } else if (originsData.Origin) {
                  originList = Array.isArray(originsData.Origin) ? originsData.Origin : [originsData.Origin];
                } else {
                  originList = [originsData];
                }
                for (const origin of originList) {
                  const domainName = origin.DomainName || "";
                  const s3Match = domainName.match(/^([^.]+)\.s3[\.-]/);
                  if (s3Match) {
                    originBucket = s3Match[1];
                    break;
                  }
                }
              }
              if (originBucket) {
                console.log(`Using existing S3 bucket: ${originBucket}`);
                return {
                  success: true,
                  stackName: `existing-${dist.Id}`,
                  bucket: originBucket,
                  distributionId: dist.Id,
                  distributionDomain: dist.DomainName,
                  domain,
                  certificateArn,
                  message: "Using existing CloudFront distribution (account verification pending for new distributions)"
                };
              }
            }
          }
        } catch {}
      }
      console.log("No existing infrastructure found, creating via direct API calls...");
      try {
        const s3Direct = new S3Client(region);
        const bucketExists = await s3Direct.headBucket(finalBucket);
        if (bucketExists.exists) {
          console.log(`Using existing S3 bucket: ${finalBucket}`);
        } else {
          console.log(`Creating S3 bucket: ${finalBucket}...`);
          await s3Direct.createBucket(finalBucket);
        }
        await s3Direct.putBucketWebsite(finalBucket, {
          IndexDocument: config.defaultRootObject || "index.html",
          ErrorDocument: config.errorDocument || "404.html"
        });
        await s3Direct.putPublicAccessBlock(finalBucket, {
          BlockPublicAcls: true,
          IgnorePublicAcls: true,
          BlockPublicPolicy: false,
          RestrictPublicBuckets: false
        });
        console.log(`S3 bucket ${finalBucket} configured`);
        const oacName = `OAC-${finalBucket}`;
        console.log(`Creating Origin Access Control: ${oacName}...`);
        const oac = await cloudfront.findOrCreateOriginAccessControl(oacName);
        console.log(`Origin Access Control ${oac.Id} ready`);
        console.log(`Creating CloudFront distribution...`);
        const distResult = await cloudfront.createDistributionForS3({
          bucketName: finalBucket,
          bucketRegion: region,
          originAccessControlId: oac.Id,
          aliases: domain ? [domain] : [],
          certificateArn,
          defaultRootObject: config.defaultRootObject || "index.html",
          comment: `Distribution for ${domain || finalBucket}`
        });
        console.log(`CloudFront distribution ${distResult.Id} created`);
        console.log(`Updating S3 bucket policy...`);
        const bucketPolicy = CloudFrontClient.getS3BucketPolicyForCloudFront(finalBucket, distResult.ARN);
        await s3Direct.putBucketPolicy(finalBucket, bucketPolicy);
        console.log(`S3 bucket policy updated`);
        if (domain && hostedZoneId) {
          console.log(`Creating Route53 records for ${domain}...`);
          try {
            await route53.createAliasRecord({
              HostedZoneId: hostedZoneId,
              Name: domain,
              Type: "A",
              TargetHostedZoneId: Route53Client.CloudFrontHostedZoneId,
              TargetDNSName: distResult.DomainName,
              EvaluateTargetHealth: false
            });
            await route53.createAliasRecord({
              HostedZoneId: hostedZoneId,
              Name: domain,
              Type: "AAAA",
              TargetHostedZoneId: Route53Client.CloudFrontHostedZoneId,
              TargetDNSName: distResult.DomainName,
              EvaluateTargetHealth: false
            });
            console.log(`Route53 records created for ${domain}`);
          } catch (dnsErr) {
            console.log(`Note: Could not create Route53 records: ${dnsErr.message}`);
          }
        }
        return {
          success: true,
          stackName: `direct-${distResult.Id}`,
          bucket: finalBucket,
          distributionId: distResult.Id,
          distributionDomain: distResult.DomainName,
          domain,
          certificateArn,
          message: "Static site infrastructure created via direct API calls"
        };
      } catch (directErr) {
        console.log(`Direct API creation failed: ${directErr.message}`);
        return {
          success: false,
          stackId,
          stackName,
          bucket: finalBucket,
          message: `Deployment failed: ${directErr.message}`
        };
      }
    }
    return {
      success: false,
      stackId,
      stackName,
      bucket: finalBucket,
      message: `Stack deployment failed: ${err.message}`
    };
  }
  const stacks = await cf.describeStacks({ stackName });
  const outputs = stacks.Stacks[0]?.Outputs || [];
  const getOutput = (key) => outputs.find((o) => o.OutputKey === key)?.OutputValue;
  return {
    success: true,
    stackId,
    stackName,
    bucket: getOutput("BucketName") || finalBucket,
    distributionId: getOutput("DistributionId"),
    distributionDomain: getOutput("DistributionDomain"),
    domain,
    certificateArn,
    message: "Static site infrastructure deployed successfully"
  };
}
async function uploadStaticFiles(options) {
  const { sourceDir, bucket, region, cacheControl = "max-age=31536000, public", onProgress } = options;
  const s3 = new S3Client(region);
  const { readdir } = await import("node:fs/promises");
  const { join, relative } = await import("node:path");
  const { createHash } = await import("node:crypto");
  async function listFiles(dir) {
    const files2 = [];
    const entries = await readdir(dir, { withFileTypes: true });
    for (const entry of entries) {
      const fullPath = join(dir, entry.name);
      if (entry.isDirectory()) {
        files2.push(...await listFiles(fullPath));
      } else {
        files2.push(fullPath);
      }
    }
    return files2;
  }
  function getContentType(filePath) {
    const ext = filePath.split(".").pop()?.toLowerCase();
    const types = {
      html: "text/html; charset=utf-8",
      css: "text/css; charset=utf-8",
      js: "application/javascript; charset=utf-8",
      json: "application/json; charset=utf-8",
      png: "image/png",
      jpg: "image/jpeg",
      jpeg: "image/jpeg",
      gif: "image/gif",
      svg: "image/svg+xml",
      ico: "image/x-icon",
      webp: "image/webp",
      woff: "font/woff",
      woff2: "font/woff2",
      ttf: "font/ttf",
      xml: "application/xml",
      txt: "text/plain; charset=utf-8",
      sh: "text/plain; charset=utf-8",
      bash: "text/plain; charset=utf-8",
      ps1: "text/plain; charset=utf-8"
    };
    return types[ext || ""] || "application/octet-stream";
  }
  function computeMD5(content) {
    return createHash("md5").update(content).digest("hex");
  }
  async function getExistingETags() {
    const etagMap = new Map;
    let continuationToken;
    do {
      const result = await s3.listObjects({
        bucket,
        maxKeys: 1000,
        continuationToken
      });
      for (const obj of result.objects) {
        const etag = obj.ETag?.replace(/"/g, "") || "";
        etagMap.set(obj.Key, etag);
      }
      continuationToken = result.nextContinuationToken;
    } while (continuationToken);
    return etagMap;
  }
  const files = await listFiles(sourceDir);
  const errors = [];
  let uploaded = 0;
  let skipped = 0;
  const existingETags = await getExistingETags();
  for (const file of files) {
    const key = relative(sourceDir, file);
    const contentType = getContentType(file);
    const fileCacheControl = file.endsWith(".html") ? "max-age=3600, public" : cacheControl;
    try {
      const content = Buffer.from(await Bun.file(file).arrayBuffer());
      const localMD5 = computeMD5(content);
      const existingETag = existingETags.get(key);
      if (existingETag && existingETag === localMD5) {
        skipped++;
        onProgress?.(uploaded + skipped, files.length, key);
        continue;
      }
      await s3.putObject({
        bucket,
        key,
        body: content,
        contentType,
        cacheControl: fileCacheControl
      });
      uploaded++;
      onProgress?.(uploaded + skipped, files.length, key);
    } catch (err) {
      errors.push(`Failed to upload ${key}: ${err.message}`);
    }
  }
  return { uploaded, skipped, errors };
}
async function invalidateCache(distributionId) {
  const cloudfront = new CloudFrontClient;
  const result = await cloudfront.invalidateAll(distributionId);
  return { invalidationId: result.Id };
}
async function deleteStaticSite(stackName, region = "us-east-1") {
  const cf = new CloudFormationClient(region);
  try {
    const stacks = await cf.describeStacks({ stackName });
    const outputs = stacks.Stacks[0]?.Outputs || [];
    const bucketName = outputs.find((o) => o.OutputKey === "BucketName")?.OutputValue;
    if (bucketName) {
      const s3 = new S3Client(region);
      await s3.emptyBucket(bucketName);
    }
  } catch {}
  await cf.deleteStack(stackName);
  const result = await cf.waitForStackComplete(stackName, 60, 1e4);
  return {
    success: result.success || result.status === "DELETE_COMPLETE",
    message: result.success ? "Static site deleted successfully" : `Deletion failed: ${result.status}`
  };
}
async function deployStaticSiteFull(config) {
  const { sourceDir, cleanBucket = false, onProgress, ...siteConfig } = config;
  onProgress?.("infrastructure", "Deploying CloudFormation stack...");
  const infraResult = await deployStaticSite(siteConfig);
  if (!infraResult.success) {
    return infraResult;
  }
  if (cleanBucket) {
    onProgress?.("clean", "Cleaning old files from S3...");
    try {
      const s3 = new S3Client(siteConfig.region || "us-east-1");
      await s3.emptyBucket(infraResult.bucket);
    } catch (err) {
      console.log(`Note: Could not clean bucket: ${err.message}`);
    }
  }
  onProgress?.("upload", "Uploading files to S3...");
  const uploadResult = await uploadStaticFiles({
    sourceDir,
    bucket: infraResult.bucket,
    region: siteConfig.region || "us-east-1",
    cacheControl: siteConfig.cacheControl,
    onProgress: (uploaded, total, file) => {
      onProgress?.("upload", `${uploaded}/${total}: ${file}`);
    }
  });
  if (uploadResult.errors.length > 0) {
    return {
      ...infraResult,
      success: false,
      message: `Upload errors: ${uploadResult.errors.join(", ")}`,
      filesUploaded: uploadResult.uploaded
    };
  }
  if (infraResult.distributionId && uploadResult.uploaded > 0) {
    onProgress?.("invalidate", "Invalidating CloudFront cache...");
    await invalidateCache(infraResult.distributionId);
  }
  onProgress?.("complete", "Deployment complete!");
  const message = uploadResult.skipped > 0 ? `Deployed ${uploadResult.uploaded} files (${uploadResult.skipped} unchanged)` : `Deployed ${uploadResult.uploaded} files successfully`;
  return {
    ...infraResult,
    filesUploaded: uploadResult.uploaded,
    filesSkipped: uploadResult.skipped,
    message
  };
}

export { generateExternalDnsStaticSiteTemplate, deployStaticSiteWithExternalDns, deployStaticSiteWithExternalDnsFull, generateStaticSiteTemplate, deployStaticSite, uploadStaticFiles, invalidateCache, deleteStaticSite, deployStaticSiteFull };
