import { Resource } from "../resource.js";
import { logger } from "../util/logger.js";
import { handleApiError } from "./api-error.js";
import { createCloudflareApi, } from "./api.js";
import { inferZoneIdFromPattern } from "./route.js";
/**
 * Configure custom domain for a Cloudflare Worker using the Cloudflare Custom Domains API
 * This attaches a worker to a specific hostname within a zone.
 *
 * @example
 * // Bind a domain to a standard Cloudflare Worker
 * const apiWorker = await Worker("api", {
 *   name: "my-api-worker",
 *   entrypoint: "./src/api-worker.ts"
 * });
 *
 * const apiDomain = await CustomDomain("api-domain-binding", {
 *   name: "api.example.com",
 *   zoneId: "YOUR_ZONE_ID", // Replace with actual Zone ID
 *   workerName: apiWorker.name // Use the name from the Worker resource
 * });
 *
 * @example
 * // Adopt an existing domain binding during creation
 * const existingDomain = await CustomDomain("api-domain", {
 *   name: "api.example.com",
 *   zoneId: "YOUR_ZONE_ID",
 *   workerName: "my-api-worker",
 *   adopt: true  // If domain already exists, adopt it instead of failing
 * });
 *
 * @see https://developers.cloudflare.com/api/resources/workers/subresources/domains/
 */
export const CustomDomain = Resource("cloudflare::CustomDomain", async function (logicalId, // Changed param name from id to logicalId for clarity
props) {
    if (this.scope.local && props.dev) {
        const now = Date.now();
        return {
            ...props,
            id: this.output?.id ?? "noop-domain",
            zoneId: props.zoneId ?? "noop-zone",
            environment: props.environment ?? "production",
            createdAt: this.output?.createdAt ?? now,
            updatedAt: now,
        };
    }
    // Create Cloudflare API client with automatic account discovery
    const api = await createCloudflareApi(props);
    if (this.phase === "delete") {
        if (props.delete !== false) {
            await deleteCustomDomain(this, api, logicalId, props);
        }
        return this.destroy();
    }
    // Create or Update phase
    return await ensureCustomDomain(this, api, logicalId, {
        ...props,
        zoneId: props.zoneId ?? (await inferZoneIdFromPattern(api, props.name)),
    });
});
// Helper function to delete the custom domain binding
async function deleteCustomDomain(context, api, logicalId, props) {
    const domainHostname = props.name;
    const domainIdToDelete = context.output?.id;
    if (!domainIdToDelete) {
        logger.warn(`Cannot delete CustomDomain ${logicalId} (${domainHostname}): Missing domain ID in state. Assuming already deleted.`);
        return; // Exit early if no ID
    }
    const response = await api.delete(`/accounts/${api.accountId}/workers/domains/${domainIdToDelete}`);
    // 404 is acceptable during deletion for idempotency
    if (!response.ok && response.status !== 404) {
        await handleApiError(response, "deleting", "custom domain binding", domainIdToDelete);
        // Throw after handling to ensure failure is reported
        throw new Error(`Failed to delete custom domain binding ${domainIdToDelete}: ${response.statusText}`);
    }
}
// Helper function to create or update the custom domain binding
async function ensureCustomDomain(context, api, _logicalId, props) {
    const environment = props.environment || "production";
    const domainHostname = props.name;
    const adopt = props.adopt ?? context.scope.adopt;
    // Check if domain binding already exists for this account
    const listResponse = await api.get(`/accounts/${api.accountId}/workers/domains`);
    if (!listResponse.ok) {
        // Fix: Added the 4th argument (resource identifier/context)
        await handleApiError(listResponse, "listing", "worker domains", `Account ${api.accountId}`);
        // If listing fails, we cannot proceed reliably
        throw new Error(`Failed to list worker domains for account ${api.accountId}: ${listResponse.statusText}`);
    }
    const listData = (await listResponse.json());
    if (!listData.success || !listData.result) {
        throw new Error(`Failed to parse list worker domains response: ${JSON.stringify(listData)}`);
    }
    // Find the specific binding by hostname AND zoneId
    const existingBinding = listData.result.find((b) => b.hostname === domainHostname && b.zone_id === props.zoneId);
    let currentDomainId = existingBinding?.id;
    const bindingExists = !!existingBinding;
    // Handle the case where domain already exists during create phase
    if (context.phase === "create" && bindingExists) {
        if (!adopt) {
            throw new Error(`CustomDomain for ${domainHostname} already exists in zone ${props.zoneId}. ` +
                "Set adopt: true to take control of the existing domain binding.");
        }
    }
    // Determine if we need to update (binding exists but has different service or environment)
    const needsUpdate = bindingExists &&
        (existingBinding.service !== props.workerName ||
            existingBinding.environment !== environment);
    let operationPerformed = "none";
    let resultantBinding = existingBinding;
    // Create or Update the binding using PUT
    // Cloudflare's PUT /accounts/{account_id}/workers/domains acts as an upsert
    if (!bindingExists || needsUpdate) {
        operationPerformed = bindingExists ? "update" : "create";
        const putPayload = {
            zone_id: props.zoneId,
            hostname: domainHostname,
            service: props.workerName,
            environment: environment,
        };
        if (props.overrideExistingOrigin !== undefined) {
            putPayload.override_existing_origin = props.overrideExistingOrigin;
        }
        const putResponse = await api.put(`/accounts/${api.accountId}/workers/domains`, putPayload);
        if (!putResponse.ok) {
            await handleApiError(putResponse, operationPerformed === "update" ? "updating" : "creating", "custom domain binding", domainHostname);
            // Throw after handling to prevent inconsistent state
            throw new Error(`Failed to ${operationPerformed} custom domain binding: ${putResponse.statusText}`);
        }
        const putResult = (await putResponse.json());
        if (!putResult.success || !putResult.result) {
            throw new Error(`Failed to parse ${operationPerformed} domain binding response: ${JSON.stringify(putResult)}`);
        }
        resultantBinding = putResult.result;
        currentDomainId = resultantBinding.id; // Update ID from the PUT response
    }
    // Ensure we have the final binding details
    if (!resultantBinding || !currentDomainId) {
        // This case should ideally not happen if API calls succeed
        logger.error("Error: Could not determine final domain binding state.", {
            existingBinding,
            resultantBinding,
            currentDomainId,
        });
        throw new Error(`Failed to get final state for custom domain ${domainHostname}`);
    }
    const now = Date.now();
    // Construct the output state
    return context({
        ...props, // Include all input props
        id: currentDomainId, // Use the definitive ID
        environment: resultantBinding.environment, // Use actual environment from CF
        createdAt: context.output?.createdAt || now, // Preserve create time or set new
        updatedAt: operationPerformed !== "none" ? now : context.output?.updatedAt || now, // Update time only if changed
    });
}
//# sourceMappingURL=custom-domain.js.map