import { Resource, ResourceKind } from "../resource.js";
import { logger } from "../util/logger.js";
import { CloudflareApiError, handleApiError } from "./api-error.js";
import { createCloudflareApi, } from "./api.js";
export function isPipeline(resource) {
    return resource?.[ResourceKind] === "cloudflare::Pipeline";
}
/**
 * Creates and manages Cloudflare Pipelines.
 *
 * Pipelines provide a managed data pipeline service that lets you collect, transform,
 * and route data to various destinations like R2 buckets.
 *
 * @example
 * // Create a basic pipeline with an R2 bucket destination
 * const bucket = await R2Bucket("logs-bucket", {
 *   name: "logs-bucket"
 * });
 *
 * const accessKey = alchemy.secret(process.env.R2_ACCESS_KEY_ID!);
 * const secretKey = alchemy.secret(process.env.R2_SECRET_ACCESS_KEY!);
 *
 * const pipeline = await Pipeline("logs-pipeline", {
 *   name: "logs-pipeline",
 *   destination: {
 *     type: "r2",
 *     format: "json",
 *     path: {
 *       bucket: bucket.name,
 *       prefix: "app-logs",
 *     },
 *     credentials: {
 *       accessKeyId: accessKey,
 *       secretAccessKey: secretKey
 *     }
 *   },
 *   batch: {
 *     maxMb: 50,
 *     maxSeconds: 60
 *   }
 * });
 *
 * @example
 * // Create a pipeline with custom source configuration
 * const customPipeline = await Pipeline("custom-pipeline", {
 *   name: "custom-pipeline",
 *   source: [{
 *     type: "http",
 *     format: "json",
 *     authentication: true,
 *     cors: {
 *       origins: ["https://example.com"]
 *     }
 *   }],
 *   destination: {
 *     type: "r2",
 *     format: "json",
 *     path: {
 *       bucket: "my-bucket",
 *       prefix: "data"
 *     },
 *     credentials: {
 *       accessKeyId: alchemy.secret(process.env.R2_ACCESS_KEY_ID!),
 *       secretAccessKey: alchemy.secret(process.env.R2_SECRET_ACCESS_KEY!)
 *     },
 *     compression: {
 *       type: "gzip"
 *     }
 *   }
 * });
 *
 * @see https://developers.cloudflare.com/pipelines/
 */
export const Pipeline = Resource("cloudflare::Pipeline", async function (id, props) {
    const api = await createCloudflareApi(props);
    const pipelineName = props.name ?? this.output?.name ?? this.scope.createPhysicalName(id);
    if (this.scope.local && !props.dev?.remote) {
        return {
            type: "pipeline",
            id: this.output?.id ?? "",
            name: this.output?.name ?? pipelineName,
            endpoint: this.output?.endpoint ?? "",
            version: this.output?.version ?? 0,
            source: this.output?.source ?? [],
            destination: props.destination,
            compression: props.compression,
            accountId: this.output?.accountId ?? "",
        };
    }
    if (this.phase === "update" && this.output?.name !== pipelineName) {
        await renamePipeline(api, this.output.name, pipelineName);
    }
    if (this.phase === "delete") {
        if (props.delete !== false) {
            // Delete Pipeline
            await deletePipeline(api, pipelineName);
        }
        // Return void (a deleted pipeline has no content)
        return this.destroy();
    }
    let pipelineData;
    if (this.phase === "create" || !this.output?.id) {
        // Check if we should adopt an existing pipeline
        try {
            // Try to create pipeline first
            pipelineData = await createPipeline(api, pipelineName, props);
        }
        catch (error) {
            // If creation fails with 409 (conflict), adopt existing pipeline
            if (error instanceof CloudflareApiError &&
                (error.status === 409 ||
                    (error.status === 400 &&
                        error.message.includes("Pipeline with this name already exists")))) {
                if (props.adopt ?? this.scope.adopt) {
                    console.warn("Pipeline already exists, adopting existing Cloudflare Pipeline:", pipelineName);
                    pipelineData = await getPipeline(api, pipelineName);
                }
                else {
                    throw error;
                }
            }
            else {
                // For any other error, rethrow
                throw error;
            }
        }
    }
    else {
        // Update operation
        if (this.output?.id) {
            // Check if name is being changed, which is not allowed
            if (pipelineName !== this.output.name) {
                throw new Error("Cannot update Pipeline name after creation. Pipeline name is immutable.");
            }
            // Update the pipeline with new settings
            pipelineData = await updatePipeline(api, pipelineName, props);
        }
        else {
            // If no ID exists, fall back to creating a new pipeline
            logger.log("No existing Pipeline ID found, creating new Cloudflare Pipeline:", pipelineName);
            pipelineData = await createPipeline(api, pipelineName, props);
        }
    }
    return {
        type: "pipeline",
        id: pipelineData.result.id,
        name: pipelineName,
        endpoint: pipelineData.result.endpoint,
        version: pipelineData.result.version,
        source: pipelineData.result.source.map((s) => ({
            type: s.type,
            format: s.format,
            authentication: s.authentication,
            cors: s.cors,
        })),
        destination: props.destination, // Use the input destination, not the API response
        compression: props.compression,
        accountId: api.accountId,
    };
});
/**
 * Get a pipeline
 */
export async function getPipeline(api, pipelineName) {
    const response = await api.get(`/accounts/${api.accountId}/pipelines/${pipelineName}`);
    if (!response.ok) {
        return await handleApiError(response, "getting", "Pipeline", pipelineName);
    }
    return (await response.json());
}
/**
 * Delete a pipeline
 */
export async function deletePipeline(api, pipelineName) {
    // Delete Pipeline
    const deleteResponse = await api.delete(`/accounts/${api.accountId}/pipelines/${pipelineName}`);
    if (!deleteResponse.ok && deleteResponse.status !== 404) {
        const errorData = await deleteResponse.json().catch(() => ({
            errors: [{ message: deleteResponse.statusText }],
        }));
        throw new CloudflareApiError(`Error deleting Cloudflare Pipeline '${pipelineName}': ${errorData.errors?.[0]?.message || deleteResponse.statusText}`, deleteResponse);
    }
}
/**
 * Create a new pipeline
 */
export async function createPipeline(api, pipelineName, props, attempt = 0) {
    // Prepare the create payload
    const createPayload = preparePipelinePayload(api, pipelineName, props);
    const createResponse = await api.post(`/accounts/${api.accountId}/pipelines`, createPayload);
    if (!createResponse.ok) {
        if (createResponse.status === 404 && attempt < 3) {
            // bucket does not exist, this might be transient, let's retry
            await new Promise((resolve) => setTimeout(resolve, 1000 * (1 + attempt)));
            return await createPipeline(api, pipelineName, props, attempt + 1);
        }
        return await handleApiError(createResponse, "creating", "Pipeline", pipelineName);
    }
    return (await createResponse.json());
}
/**
 * Update a pipeline
 */
export async function updatePipeline(api, pipelineName, props) {
    // Get current pipeline to build update payload
    const currentPipeline = await getPipeline(api, pipelineName);
    // Prepare the update payload
    const updatePayload = preparePipelinePayload(api, pipelineName, props, currentPipeline);
    const updateResponse = await api.put(`/accounts/${api.accountId}/pipelines/${pipelineName}`, updatePayload);
    if (!updateResponse.ok) {
        return await handleApiError(updateResponse, "updating", "Pipeline", pipelineName);
    }
    return (await updateResponse.json());
}
/**
 * Rename a pipeline
 *
 * @param api - Cloudflare API instance
 * @param currentName - Current name of the pipeline
 * @param newName - New name for the pipeline
 * @returns Updated pipeline response with the new name
 *
 * @see https://developers.cloudflare.com/api/resources/pipelines/methods/update/
 */
export async function renamePipeline(api, currentName, newName) {
    // Get the current pipeline configuration
    const currentPipeline = await getPipeline(api, currentName);
    // Build the update payload with the new name
    const updatePayload = {
        name: newName,
        source: currentPipeline.result.source,
        destination: currentPipeline.result.destination,
    };
    // Use the update endpoint with the current name in the path and new name in the body
    const updateResponse = await api.put(`/accounts/${api.accountId}/pipelines/${currentName}`, updatePayload);
    if (!updateResponse.ok) {
        return await handleApiError(updateResponse, "renaming", "Pipeline", `${currentName} -> ${newName}`);
    }
    return (await updateResponse.json());
}
/**
 * Helper function to prepare pipeline payload for create/update operations
 */
function preparePipelinePayload(api, pipelineName, props, currentPipeline) {
    // Prepare the payload with name and source
    const payload = {
        name: pipelineName,
        source: props.source ||
            currentPipeline?.result.source || [
            {
                type: "http",
                format: "json",
                authentication: true,
                cors: { origins: ["*"] },
            },
        ],
    };
    // Handle destination
    if (props.destination) {
        payload.destination = { ...props.destination };
        // Handle special formatting for R2 destination
        const r2Dest = props.destination;
        // Format credentials for API
        if (payload.destination.credentials) {
            payload.destination.credentials = {
                access_key_id: r2Dest.credentials.accessKeyId.unencrypted,
                secret_access_key: r2Dest.credentials.secretAccessKey.unencrypted,
                endpoint: r2Dest.credentials.endpoint ??
                    `https://${api.accountId}.r2.cloudflarestorage.com`,
            };
        }
        // Format batch settings
        payload.destination.batch = convertBatchSettings(payload.destination.batch);
    }
    else if (currentPipeline?.result.destination) {
        payload.destination = currentPipeline.result.destination;
    }
    else if (!props.destination && !currentPipeline) {
        throw new Error("An R2 destination is required for creating/updating a pipeline");
    }
    // Add compression if not specified
    if (!payload.destination.compression) {
        payload.destination.compression = { type: "gzip" };
    }
    return payload;
}
/**
 * List all pipelines in an account
 */
export async function listPipelines(api) {
    const response = await api.get(`/accounts/${api.accountId}/pipelines`);
    if (!response.ok) {
        throw new CloudflareApiError(`Failed to list pipelines: ${response.statusText}`, response);
    }
    const data = (await response.json());
    if (!data.success) {
        const errorMessage = data.errors?.[0]?.message || "Unknown error";
        throw new Error(`Failed to list pipelines: ${errorMessage}`);
    }
    // Transform API response
    return (data.results || []).map((pipeline) => ({
        name: pipeline.name,
        id: pipeline.id,
    }));
}
function convertBatchSettings(batch) {
    const result = {};
    if (batch?.maxMb !== undefined) {
        // Convert MB to bytes
        result.max_bytes = batch.maxMb * 1024 * 1024;
    }
    if (batch?.maxRows !== undefined) {
        result.max_rows = batch.maxRows;
    }
    if (batch?.maxSeconds !== undefined) {
        result.max_duration_s = batch.maxSeconds;
    }
    return result;
}
//# sourceMappingURL=pipeline.js.map