import { fromIni } from "@aws-sdk/credential-providers";
import { ChangeResourceRecordSetsCommand, ListHostedZonesByNameCommand, ListResourceRecordSetsCommand, Route53Client } from "@aws-sdk/client-route-53";

//#region src/deploy/dns/Route53Provider.ts
/**
* Route53 DNS provider implementation.
*
* Uses AWS default credential chain for authentication.
* Region can be specified or will use AWS_REGION/AWS_DEFAULT_REGION env vars.
* Profile can be specified to use a named profile from ~/.aws/credentials.
*/
var Route53Provider = class {
	name = "route53";
	client;
	hostedZoneId;
	hostedZoneCache = /* @__PURE__ */ new Map();
	constructor(options = {}) {
		const region = options.region ?? process.env.AWS_REGION ?? "us-east-1";
		this.client = new Route53Client({
			region,
			...options.endpoint && { endpoint: options.endpoint },
			...options.profile && { credentials: fromIni({ profile: options.profile }) }
		});
		this.hostedZoneId = options.hostedZoneId;
	}
	/**
	* Get the hosted zone ID for a domain.
	* Uses cache to avoid repeated API calls.
	*/
	async getHostedZoneId(domain) {
		if (this.hostedZoneId) return this.hostedZoneId;
		if (this.hostedZoneCache.has(domain)) return this.hostedZoneCache.get(domain);
		const command = new ListHostedZonesByNameCommand({
			DNSName: domain,
			MaxItems: 1
		});
		const response = await this.client.send(command);
		const zones = response.HostedZones ?? [];
		const normalizedDomain = domain.endsWith(".") ? domain : `${domain}.`;
		const zone = zones.find((z) => z.Name === normalizedDomain);
		if (!zone?.Id) throw new Error(`No hosted zone found for domain: ${domain}. Create one in Route53 or provide hostedZoneId in config.`);
		const zoneId = zone.Id.replace("/hostedzone/", "");
		this.hostedZoneCache.set(domain, zoneId);
		return zoneId;
	}
	/**
	* Convert Route53 record type to our DnsRecordType.
	* Excludes NS and SOA which are auto-managed by Route53 for the zone.
	*/
	toRecordType(type) {
		const managedTypes = ["NS", "SOA"];
		if (managedTypes.includes(type)) return null;
		const validTypes = [
			"A",
			"AAAA",
			"CNAME",
			"MX",
			"TXT",
			"SRV",
			"CAA"
		];
		return validTypes.includes(type) ? type : null;
	}
	/**
	* Extract subdomain from full record name relative to domain.
	*/
	extractSubdomain(recordName, domain) {
		const normalizedDomain = domain.endsWith(".") ? domain : `${domain}.`;
		const normalizedName = recordName.endsWith(".") ? recordName : `${recordName}.`;
		if (normalizedName === normalizedDomain) return "@";
		const subdomain = normalizedName.replace(`.${normalizedDomain}`, "");
		return subdomain.replace(/\.$/, "");
	}
	async getRecords(domain) {
		const zoneId = await this.getHostedZoneId(domain);
		const records = [];
		let nextRecordName;
		let nextRecordType;
		do {
			const command = new ListResourceRecordSetsCommand({
				HostedZoneId: zoneId,
				StartRecordName: nextRecordName,
				StartRecordType: nextRecordType,
				MaxItems: 100
			});
			const response = await this.client.send(command);
			for (const recordSet of response.ResourceRecordSets ?? []) {
				const type = this.toRecordType(recordSet.Type ?? "");
				if (!type || !recordSet.Name) continue;
				const values = (recordSet.ResourceRecords ?? []).map((r) => r.Value).filter((v) => !!v);
				records.push({
					name: this.extractSubdomain(recordSet.Name, domain),
					type,
					ttl: recordSet.TTL ?? 300,
					values
				});
			}
			if (response.IsTruncated) {
				nextRecordName = response.NextRecordName;
				nextRecordType = response.NextRecordType;
			} else nextRecordName = void 0;
		} while (nextRecordName);
		return records;
	}
	async upsertRecords(domain, records) {
		const zoneId = await this.getHostedZoneId(domain);
		const results = [];
		const existingRecords = await this.getRecords(domain);
		const batchSize = 100;
		for (let i = 0; i < records.length; i += batchSize) {
			const batch = records.slice(i, i + batchSize);
			const changes = [];
			for (const record of batch) {
				const existing = existingRecords.find((r) => r.name === record.name && r.type === record.type);
				const existingValue = existing?.values?.[0];
				if (existing && existingValue === record.value) {
					results.push({
						record,
						created: false,
						unchanged: true
					});
					continue;
				}
				const recordName = record.name === "@" ? domain : `${record.name}.${domain}`;
				changes.push({
					Action: "UPSERT",
					ResourceRecordSet: {
						Name: recordName,
						Type: record.type,
						TTL: record.ttl,
						ResourceRecords: [{ Value: record.value }]
					}
				});
				results.push({
					record,
					created: !existing,
					unchanged: false
				});
			}
			if (changes.length > 0) {
				const command = new ChangeResourceRecordSetsCommand({
					HostedZoneId: zoneId,
					ChangeBatch: {
						Comment: "Upsert by gkm deploy",
						Changes: changes
					}
				});
				await this.client.send(command);
			}
		}
		return results;
	}
	async deleteRecords(domain, records) {
		const zoneId = await this.getHostedZoneId(domain);
		const results = [];
		const existingRecords = await this.getRecords(domain);
		const batchSize = 100;
		for (let i = 0; i < records.length; i += batchSize) {
			const batch = records.slice(i, i + batchSize);
			const changes = [];
			for (const record of batch) {
				const existing = existingRecords.find((r) => r.name === record.name && r.type === record.type);
				if (!existing) {
					results.push({
						record,
						deleted: false,
						notFound: true
					});
					continue;
				}
				const recordName = record.name === "@" ? domain : `${record.name}.${domain}`;
				changes.push({
					Action: "DELETE",
					ResourceRecordSet: {
						Name: recordName,
						Type: record.type,
						TTL: existing.ttl,
						ResourceRecords: existing.values.map((v) => ({ Value: v }))
					}
				});
				results.push({
					record,
					deleted: true,
					notFound: false
				});
			}
			if (changes.length > 0) try {
				const command = new ChangeResourceRecordSetsCommand({
					HostedZoneId: zoneId,
					ChangeBatch: {
						Comment: "Delete by gkm undeploy",
						Changes: changes
					}
				});
				await this.client.send(command);
			} catch (error) {
				for (const result of results.slice(-changes.length)) if (result.deleted) {
					result.deleted = false;
					result.error = String(error);
				}
			}
		}
		return results;
	}
};

//#endregion
export { Route53Provider };
//# sourceMappingURL=Route53Provider-QoPgcXxn.mjs.map