import { Resource, ResourceKind } from "../resource.js";
import { logger } from "../util/logger.js";
import { handleApiError } from "./api-error.js";
import { createCloudflareApi, } from "./api.js";
export function isTunnelRoute(resource) {
    return resource?.[ResourceKind] === "cloudflare::TunnelRoute";
}
/**
 * Creates and manages a Cloudflare Tunnel Route, which routes private network traffic
 * through a Cloudflare Tunnel. This resource handles the route lifecycle (create, update, delete).
 *
 * @remarks
 * Tunnel Routes enable private network access by routing CIDR ranges through Cloudflare Tunnels.
 * This is commonly used for Zero Trust network access scenarios where you need to connect
 * private networks to Cloudflare's edge.
 *
 * @see https://developers.cloudflare.com/cloudflare-one/connections/connect-networks/private-net/
 * @see https://developers.cloudflare.com/api/operations/zero-trust-networks-routes-create-a-tunnel-route
 *
 * @example
 * // Create a basic tunnel route for a private network
 * const tunnel = await Tunnel("my-tunnel", {
 *   name: "my-tunnel"
 * });
 *
 * const route = await TunnelRoute("private-network", {
 *   network: "172.16.0.0/16",
 *   tunnel: tunnel
 * });
 *
 * @example
 * // Create a route with a comment and virtual network
 * const route = await TunnelRoute("vpc-route", {
 *   network: "10.0.0.0/8",
 *   tunnel: "f70ff985-a4ef-4643-bbbc-4a0ed4fc8415",
 *   comment: "Main VPC network route",
 *   virtualNetworkId: "f70ff985-a4ef-4643-bbbc-4a0ed4fc8415"
 * });
 *
 * @example
 * // Adopt an existing route if it already exists
 * const route = await TunnelRoute("existing-route", {
 *   network: "192.168.1.0/24",
 *   tunnel: tunnel,
 *   adopt: true,
 *   comment: "Updated comment"
 * });
 *
 * @example
 * // Create a route without deleting it when removed from Alchemy
 * const route = await TunnelRoute("persistent-route", {
 *   network: "10.1.0.0/16",
 *   tunnel: tunnel,
 *   delete: false
 * });
 */
export const TunnelRoute = Resource("cloudflare::TunnelRoute", async function (id, props) {
    // Create Cloudflare API client with automatic account discovery
    const api = await createCloudflareApi(props);
    // Resolve tunnel ID from either string or Tunnel resource
    const tunnelId = typeof props.tunnel === "string" ? props.tunnel : props.tunnel.tunnelId;
    const routeId = props.routeId || this.output?.id;
    const adopt = props.adopt ?? this.scope.adopt;
    if (this.phase === "delete") {
        // For delete operations, check if the route ID exists in the output
        if (routeId && props.delete !== false) {
            await deleteRoute(api, routeId);
        }
        // Return destroyed state
        return this.destroy();
    }
    // Check if network is being changed - network is immutable
    if (this.phase === "update" &&
        this.output?.network &&
        this.output.network !== props.network) {
        logger.log(`Network changed from '${this.output.network}' to '${props.network}', replacing route`);
        this.replace(true);
    }
    // Check if tunnel is being changed - tunnel is immutable
    if (this.phase === "update" &&
        this.output?.tunnelId &&
        this.output.tunnelId !== tunnelId) {
        logger.log(`Tunnel changed from '${this.output.tunnelId}' to '${tunnelId}', replacing route`);
        this.replace(true);
    }
    let routeData;
    if (this.phase === "update" && routeId) {
        // Update existing route (only comment and virtualNetworkId can be updated)
        routeData = await updateRoute(api, routeId, {
            comment: props.comment,
            virtualNetworkId: props.virtualNetworkId,
        });
    }
    else {
        // Create new route
        try {
            routeData = await createRoute(api, {
                network: props.network,
                tunnelId,
                comment: props.comment,
                virtualNetworkId: props.virtualNetworkId,
            });
        }
        catch (error) {
            // Check if this is a "route already exists" error and adopt is enabled
            if (adopt &&
                error instanceof Error &&
                (error.message.includes("already exists") ||
                    error.message.includes("duplicate") ||
                    error.status === 409)) {
                logger.log(`Route for network '${props.network}' and tunnel '${tunnelId}' already exists, adopting it`);
                // Find the existing route by network and tunnel
                const existingRoute = await findRouteByNetworkAndTunnel(api, props.network, tunnelId);
                if (!existingRoute) {
                    throw new Error(`Failed to find existing route for network '${props.network}' and tunnel '${tunnelId}' for adoption`);
                }
                routeData = existingRoute;
                // Update comment/virtualNetworkId if provided
                if (props.comment !== undefined ||
                    props.virtualNetworkId !== undefined) {
                    routeData = await updateRoute(api, existingRoute.id, {
                        comment: props.comment,
                        virtualNetworkId: props.virtualNetworkId,
                    });
                }
            }
            else {
                // Re-throw the error if adopt is false or it's not an "already exists" error
                throw error;
            }
        }
    }
    // Transform API response to our interface
    return {
        id: routeData.id,
        network: routeData.network,
        tunnelId: routeData.tunnel_id,
        comment: routeData.comment,
        virtualNetworkId: routeData.virtual_network_id,
        createdAt: routeData.created_at,
        deletedAt: routeData.deleted_at,
        type: "cloudflare::TunnelRoute",
    };
});
/**
 * Get tunnel route details
 * @internal
 */
export async function getTunnelRoute(api, routeId) {
    const response = await api.get(`/accounts/${api.accountId}/teamnet/routes/${routeId}`);
    if (!response.ok) {
        await handleApiError(response, "get", "route", routeId);
    }
    const data = (await response.json());
    return data.result;
}
/**
 * List all tunnel routes with pagination support
 * @internal
 */
export async function listTunnelRoutes(api, options) {
    const routes = [];
    let page = 1;
    const perPage = 100; // Maximum allowed by API
    let hasMorePages = true;
    const limit = options?.limit;
    while (hasMorePages) {
        const params = new URLSearchParams({
            page: page.toString(),
            per_page: perPage.toString(),
        });
        const response = await api.get(`/accounts/${api.accountId}/teamnet/routes?${params.toString()}`);
        if (!response.ok) {
            await handleApiError(response, "list", "route", "all");
        }
        const data = (await response.json());
        routes.push(...data.result);
        const resultInfo = data.result_info;
        // Check if we've reached the limit
        if (limit && routes.length >= limit) {
            return routes.slice(0, limit);
        }
        // Check if we've seen all pages
        hasMorePages =
            resultInfo.page * resultInfo.per_page < resultInfo.total_count;
        page++;
    }
    return routes;
}
/**
 * Find a route by network and tunnel ID
 * @internal
 */
export async function findRouteByNetworkAndTunnel(api, network, tunnelId) {
    const routes = await listTunnelRoutes(api);
    // Look for a route with matching network and tunnel_id
    const match = routes.find((route) => route.network === network && route.tunnel_id === tunnelId);
    return match || null;
}
/**
 * Delete a route
 * @internal
 */
async function deleteRoute(api, routeId) {
    const response = await api.delete(`/accounts/${api.accountId}/teamnet/routes/${routeId}`);
    if (!response.ok && response.status !== 404) {
        await handleApiError(response, "delete", "route", routeId);
    }
}
/**
 * Create a new route
 * @internal
 */
async function createRoute(api, props) {
    const payload = {
        network: props.network,
        tunnel_id: props.tunnelId,
    };
    if (props.comment !== undefined) {
        payload.comment = props.comment;
    }
    if (props.virtualNetworkId !== undefined) {
        payload.virtual_network_id = props.virtualNetworkId;
    }
    const response = await api.post(`/accounts/${api.accountId}/teamnet/routes`, payload);
    if (!response.ok) {
        await handleApiError(response, "create", "route", `${props.network} -> ${props.tunnelId}`);
    }
    const data = (await response.json());
    return data.result;
}
/**
 * Update route configuration
 * @internal
 */
async function updateRoute(api, routeId, props) {
    const payload = {};
    if (props.comment !== undefined) {
        payload.comment = props.comment;
    }
    if (props.virtualNetworkId !== undefined) {
        payload.virtual_network_id = props.virtualNetworkId;
    }
    const response = await api.patch(`/accounts/${api.accountId}/teamnet/routes/${routeId}`, payload);
    if (!response.ok) {
        await handleApiError(response, "update", "route", routeId);
    }
    const data = (await response.json());
    return data.result;
}
//# sourceMappingURL=tunnel-route.js.map