import { Resource, ResourceKind } from "../resource.js";
import { logger } from "../util/logger.js";
import { CloudflareApiError, handleApiError } from "./api-error.js";
import { extractCloudflareResult } from "./api-response.js";
import { createCloudflareApi, } from "./api.js";
export function isWarpDeviceProfile(resource) {
    return resource?.[ResourceKind] === "cloudflare::WarpDeviceProfile";
}
/**
 * Creates and manages a Cloudflare WARP Device Profile, which defines WARP client
 * settings for specific sets of devices based on matching rules.
 *
 * Device profiles allow you to apply different WARP configurations to different
 * groups of devices based on user identity, groups, operating system, or other criteria.
 *
 * @see https://developers.cloudflare.com/cloudflare-one/connections/connect-devices/warp/configure-warp/device-profiles/
 *
 * @example
 * ## Basic device profile for a user group
 *
 * Create a profile that applies to all devices belonging to the Engineering group
 *
 * const engProfile = await WarpDeviceProfile("engineering", {
 *   name: "Engineering Team",
 *   match: 'identity.groups.name == "Engineering"',
 *   precedence: 100,
 *   serviceModeV2: { mode: "warp" },
 *   allowedToLeave: false,
 *   switchLocked: true
 * });
 *
 * @example
 * ## Profile with split tunnel configuration
 *
 * Create a profile that excludes internal network routes from the WARP tunnel
 *
 * const internalProfile = await WarpDeviceProfile("internal-network", {
 *   name: "Internal Network Access",
 *   match: 'identity.email.ends_with("@company.com")',
 *   precedence: 50,
 *   serviceModeV2: { mode: "warp" },
 *   splitTunnel: {
 *     mode: "exclude",
 *     entries: [
 *       { address: "10.0.0.0/8", description: "Internal network" },
 *       { address: "192.168.0.0/16", description: "Local network" }
 *     ]
 *   }
 * });
 *
 * @example
 * ## Profile with include mode split tunnel
 *
 * Only route specific networks through WARP
 *
 * const selectiveProfile = await WarpDeviceProfile("selective", {
 *   name: "Selective Routing",
 *   match: 'identity.groups.name == "Remote Workers"',
 *   precedence: 200,
 *   serviceModeV2: { mode: "warp" },
 *   splitTunnel: {
 *     mode: "include",
 *     entries: [
 *       { address: "10.0.0.0/8", description: "Company network" },
 *       { address: "company.com", description: "Company domain" }
 *     ]
 *   }
 * });
 *
 * @example
 * ## Adopt an existing profile
 *
 * Take over management of an existing device profile
 *
 * const existingProfile = await WarpDeviceProfile("existing", {
 *   name: "Existing Profile",
 *   adopt: true,
 *   match: 'identity.groups.name == "IT"',
 *   precedence: 10
 * });
 *
 * @example
 * ## Profile with all WARP settings
 *
 * Configure comprehensive WARP client behavior
 *
 * const fullProfile = await WarpDeviceProfile("comprehensive", {
 *   name: "Full Configuration",
 *   match: 'identity.email == "admin@example.com"',
 *   precedence: 1,
 *   enabled: true,
 *   serviceModeV2: { mode: "warp" },
 *   disableAutoFallback: false,
 *   allowModeSwitch: false,
 *   switchLocked: true,
 *   tunnelProtocol: "wireguard",
 *   autoConnect: 0,
 *   allowedToLeave: false,
 *   captivePortal: 180,
 *   supportUrl: "https://support.example.com",
 *   excludeOfficeIps: true,
 *   lanAllowMinutes: 5,
 *   lanAllowSubnetSize: 24
 * });
 */
export const WarpDeviceProfile = Resource("cloudflare::WarpDeviceProfile", async function (id, props = {}) {
    const api = await createCloudflareApi(props);
    const name = props.name ?? this.output?.name ?? this.scope.createPhysicalName(id);
    const adopt = props.adopt ?? this.scope.adopt;
    if (this.phase === "delete") {
        if (this.output?.policyId && props.delete !== false) {
            await deletePolicy(api, this.output.policyId);
        }
        return this.destroy();
    }
    // Handle replacement for immutable properties
    if (this.phase === "update" && this.output?.name !== name) {
        this.replace();
    }
    let policyId;
    let createdAt = this.output?.createdAt ?? Date.now();
    if (this.phase === "update" && this.output?.policyId) {
        // Update existing policy
        await updatePolicy(api, this.output.policyId, { ...props, name });
        policyId = this.output.policyId;
    }
    else {
        // Create new policy
        try {
            const result = await createPolicy(api, { ...props, name });
            policyId = result.policy_id ?? result.id;
            createdAt = Date.now();
        }
        catch (error) {
            if (adopt &&
                error instanceof CloudflareApiError &&
                (error.status === 400 || error.status === 409) &&
                (error.message.includes("already exists") ||
                    error.message.includes("duplicate") ||
                    error.message.includes("precedence must be unique"))) {
                logger.log(`WARP device profile '${name}' already exists, adopting it`);
                const existing = await findPolicyByName(api, name);
                if (!existing) {
                    throw new Error(`Failed to find existing WARP device profile '${name}' for adoption`);
                }
                policyId = existing.policy_id;
            }
            else {
                throw error;
            }
        }
    }
    // Update split tunnel configuration if provided
    if (props.splitTunnel) {
        await updateSplitTunnel(api, policyId, props.splitTunnel);
    }
    return {
        policyId,
        name,
        description: props.description,
        match: props.match,
        precedence: props.precedence,
        enabled: props.enabled ?? true,
        serviceModeV2: props.serviceModeV2,
        disableAutoFallback: props.disableAutoFallback,
        allowModeSwitch: props.allowModeSwitch,
        switchLocked: props.switchLocked,
        tunnelProtocol: props.tunnelProtocol,
        autoConnect: props.autoConnect,
        allowedToLeave: props.allowedToLeave,
        captivePortal: props.captivePortal,
        supportUrl: props.supportUrl,
        excludeOfficeIps: props.excludeOfficeIps,
        lanAllowMinutes: props.lanAllowMinutes,
        lanAllowSubnetSize: props.lanAllowSubnetSize,
        splitTunnel: props.splitTunnel,
        createdAt,
        modifiedAt: Date.now(),
    };
});
async function createPolicy(api, props) {
    const requestBody = buildRequestBody(props);
    const response = await api.post(`/accounts/${api.accountId}/devices/policy`, requestBody);
    if (!response.ok) {
        await handleApiError(response, "create", "warp_device_profile", props.name);
    }
    return await extractCloudflareResult(`create WARP device profile "${props.name}"`, Promise.resolve(response));
}
async function updatePolicy(api, policyId, props) {
    const requestBody = buildRequestBody(props);
    const response = await api.patch(`/accounts/${api.accountId}/devices/policy/${policyId}`, requestBody);
    if (!response.ok) {
        await handleApiError(response, "update", "warp_device_profile", policyId);
    }
}
async function deletePolicy(api, policyId) {
    const response = await api.delete(`/accounts/${api.accountId}/devices/policy/${policyId}`);
    if (!response.ok && response.status !== 404) {
        await handleApiError(response, "delete", "warp_device_profile", policyId);
    }
}
async function findPolicyByName(api, name) {
    const response = await api.get(`/accounts/${api.accountId}/devices/policies`);
    if (!response.ok) {
        await handleApiError(response, "list", "warp_device_profile", "all");
    }
    const data = (await response.json());
    const policy = data.result?.find((p) => p.name === name);
    if (!policy)
        return null;
    return {
        policy_id: policy.policy_id ?? policy.id,
    };
}
async function updateSplitTunnel(api, policyId, config) {
    const routes = config.entries.map((entry) => ({
        ...(entry.address && { address: entry.address }),
        ...(entry.host && { host: entry.host }),
        ...(entry.description && { description: entry.description }),
    }));
    if (config.mode === "include") {
        const response = await api.put(`/accounts/${api.accountId}/devices/policy/${policyId}/include`, routes);
        if (!response.ok) {
            await handleApiError(response, "update split tunnel includes", "warp_device_profile", policyId);
        }
    }
    else {
        const response = await api.put(`/accounts/${api.accountId}/devices/policy/${policyId}/exclude`, routes);
        if (!response.ok) {
            await handleApiError(response, "update split tunnel excludes", "warp_device_profile", policyId);
        }
    }
}
function buildRequestBody(props) {
    const requestBody = {
        name: props.name,
    };
    if (props.description !== undefined) {
        requestBody.description = props.description;
    }
    if (props.match !== undefined) {
        requestBody.match = props.match;
    }
    if (props.precedence !== undefined) {
        requestBody.precedence = props.precedence;
    }
    if (props.enabled !== undefined) {
        requestBody.enabled = props.enabled;
    }
    if (props.serviceModeV2) {
        requestBody.service_mode_v2 = {
            mode: props.serviceModeV2.mode,
            ...(props.serviceModeV2.port && { port: props.serviceModeV2.port }),
        };
    }
    if (props.disableAutoFallback !== undefined) {
        requestBody.disable_auto_fallback = props.disableAutoFallback;
    }
    if (props.allowModeSwitch !== undefined) {
        requestBody.allow_mode_switch = props.allowModeSwitch;
    }
    if (props.switchLocked !== undefined) {
        requestBody.switch_locked = props.switchLocked;
    }
    if (props.tunnelProtocol !== undefined) {
        requestBody.tunnel_protocol = props.tunnelProtocol;
    }
    if (props.autoConnect !== undefined) {
        requestBody.auto_connect = props.autoConnect;
    }
    if (props.allowedToLeave !== undefined) {
        requestBody.allowed_to_leave = props.allowedToLeave;
    }
    if (props.captivePortal !== undefined) {
        requestBody.captive_portal = props.captivePortal;
    }
    if (props.supportUrl !== undefined) {
        requestBody.support_url = props.supportUrl;
    }
    if (props.excludeOfficeIps !== undefined) {
        requestBody.exclude_office_ips = props.excludeOfficeIps;
    }
    if (props.lanAllowMinutes !== undefined) {
        requestBody.lan_allow_minutes = props.lanAllowMinutes;
    }
    if (props.lanAllowSubnetSize !== undefined) {
        requestBody.lan_allow_subnet_size = props.lanAllowSubnetSize;
    }
    return requestBody;
}
//# sourceMappingURL=warp-device-profile.js.map