import { XMLParser } from "fast-xml-parser";
import { Resource } from "../../resource.js";
import { ignore } from "../../util/ignore.js";
import { logger } from "../../util/logger.js";
import { mergeTimeoutConfig, waitForResourceState, } from "../../util/timeout.js";
import { retry } from "../retry.js";
import { callEC2Api, createEC2Client } from "./utils.js";
/**
 * Wait for subnet to be in available state
 */
async function waitForSubnetAvailable(client, subnetId, timeoutConfig) {
    const checkFunction = async () => {
        const response = await callEC2Api(client, "DescribeSubnets", parseSubnetXmlResponse, {
            "SubnetId.1": subnetId,
        });
        return response.Subnets?.[0];
    };
    const isReady = (subnet) => {
        return subnet?.State === "available";
    };
    await waitForResourceState(checkFunction, isReady, timeoutConfig, subnetId, "Subnet", "is now available");
}
/**
 * Wait for Subnet to be deleted
 */
async function waitForSubnetDeleted(client, subnetId, timeoutConfig) {
    const checkFunction = async () => {
        try {
            const response = await callEC2Api(client, "DescribeSubnets", parseSubnetXmlResponse, {
                "SubnetId.1": subnetId,
            });
            return response.Subnets?.[0];
        }
        catch (error) {
            // If Subnet is not found, it's been deleted - return undefined
            if (error.code === "InvalidSubnetID.NotFound" ||
                error.code === "InvalidSubnet.NotFound") {
                return undefined;
            }
            throw error;
        }
    };
    const isReady = (subnet) => {
        // Subnet is deleted if it doesn't exist
        return !subnet;
    };
    await waitForResourceState(checkFunction, isReady, timeoutConfig, subnetId, "Subnet", "be deleted");
}
/**
 * AWS Subnet Resource
 *
 * Creates and manages subnets within a VPC. Subnets are used to segment
 * your VPC into smaller networks for organizing resources. Supports AWS
 * credential overrides for multi-account and cross-region deployments.
 *
 * @example
 * // Create a public subnet for web servers
 * const publicSubnet = await Subnet("public-subnet", {
 *   vpc: myVpc,
 *   cidrBlock: "10.0.1.0/24",
 *   availabilityZone: `${process.env.AWS_REGION}a` || "us-east-1a",
 *   mapPublicIpOnLaunch: true,
 *   tags: {
 *     Name: "public-subnet",
 *     Type: "public"
 *   }
 * });
 *
 * @example
 * // Create a private subnet for databases
 * const privateSubnet = await Subnet("private-subnet", {
 *   vpc: myVpc,
 *   cidrBlock: "10.0.2.0/24",
 *   availabilityZone: "us-west-2b",
 *   mapPublicIpOnLaunch: false,
 *   tags: {
 *     Name: "private-subnet",
 *     Type: "private"
 *   }
 * });
 *
 * @example
 * // Create a subnet with custom timeout configuration
 * const customSubnet = await Subnet("custom-subnet", {
 *   vpc: myVpc,
 *   cidrBlock: "10.0.3.0/24",
 *   availabilityZone: "us-west-2c",
 *   timeout: {
 *     maxAttempts: 50,
 *     delayMs: 500
 *   }
 * });
 *
 * @example
 * // Create a subnet with AWS credential overrides for cross-account deployment
 * const crossAccountSubnet = await Subnet("cross-account-subnet", {
 *   vpc: myVpc,
 *   cidrBlock: "10.1.1.0/24",
 *   availabilityZone: "us-east-1a",
 *   region: "us-east-1",
 *   profile: "production-account",
 *   mapPublicIpOnLaunch: true,
 *   tags: {
 *     Name: "cross-account-subnet",
 *     Account: "production"
 *   }
 * });
 *
 * @example
 * // Create a subnet in a different region with explicit credentials
 * const multiRegionSubnet = await Subnet("multi-region-subnet", {
 *   vpc: "vpc-12345678", // VPC ID from different region
 *   cidrBlock: "10.2.1.0/24",
 *   availabilityZone: "eu-west-1a",
 *   region: "eu-west-1",
 *   accessKeyId: alchemy.secret("AKIA..."),
 *   secretAccessKey: alchemy.secret("..."),
 *   mapPublicIpOnLaunch: false,
 *   tags: {
 *     Name: "eu-subnet",
 *     Region: "europe"
 *   }
 * });
 *
 * @example
 * // Using scope-level credentials with resource-level overrides
 * await alchemy.run("multi-account", {
 *   aws: {
 *     region: "us-west-2",
 *     profile: "staging"
 *   }
 * }, async () => {
 *   // This subnet uses staging credentials from scope
 *   const stagingSubnet = await Subnet("staging-subnet", {
 *     vpc: stagingVpc,
 *     cidrBlock: "10.0.1.0/24",
 *     availabilityZone: "us-west-2a"
 *   });
 *
 *   // This subnet overrides to use production credentials
 *   const prodSubnet = await Subnet("prod-subnet", {
 *     vpc: prodVpc,
 *     cidrBlock: "10.1.1.0/24",
 *     availabilityZone: "us-east-1a",
 *     profile: "production",
 *     region: "us-east-1"
 *   });
 * });
 */
export const Subnet = Resource("aws::Subnet", async function (_id, props) {
    // Create EC2 client with credential resolution handled internally
    const client = await createEC2Client(props);
    const vpcId = typeof props.vpc === "string" ? props.vpc : props.vpc.vpcId;
    // Validate timeout config early, but use default for deletion if invalid
    let timeoutConfig;
    try {
        timeoutConfig = mergeTimeoutConfig(SUBNET_TIMEOUT, props.timeout);
    }
    catch (error) {
        if (this.phase === "delete") {
            // Use default timeout for deletion to avoid blocking cleanup
            timeoutConfig = SUBNET_TIMEOUT;
        }
        else {
            // Re-throw validation error during creation/update
            throw error;
        }
    }
    if (this.phase === "delete") {
        if (this.output?.subnetId) {
            logger.log(`🗑️ Deleting Subnet: ${this.output.subnetId}`);
            await retry(async () => {
                await ignore("InvalidSubnetID.NotFound", async () => {
                    await callEC2Api(client, "DeleteSubnet", parseSubnetXmlResponse, {
                        SubnetId: this.output.subnetId,
                    });
                });
            });
            // Wait for Subnet to be fully deleted to avoid dependency violations
            logger.log(`  Waiting for Subnet ${this.output.subnetId} to be fully deleted...`);
            await waitForSubnetDeleted(client, this.output.subnetId, timeoutConfig);
            logger.log(`  ✅ Subnet ${this.output.subnetId} deletion completed`);
        }
        return this.destroy();
    }
    let subnet;
    if (this.phase === "update" && this.output?.subnetId) {
        // Get existing subnet
        const response = await callEC2Api(client, "DescribeSubnets", parseSubnetXmlResponse, {
            "SubnetId.1": this.output.subnetId,
        });
        if (!response.Subnets?.[0]) {
            throw new Error(`Subnet ${this.output.subnetId} not found`);
        }
        subnet = response.Subnets[0];
        // Update public IP assignment if it has changed
        if (props.mapPublicIpOnLaunch !== undefined) {
            await callEC2Api(client, "ModifySubnetAttribute", parseSubnetXmlResponse, {
                SubnetId: subnet.SubnetId,
                "MapPublicIpOnLaunch.Value": props.mapPublicIpOnLaunch.toString(),
            });
        }
        // Update tags if provided
        if (props.tags) {
            const tagParams = {};
            Object.entries(props.tags).forEach(([key, value], index) => {
                tagParams[`Tag.${index + 1}.Key`] = key;
                tagParams[`Tag.${index + 1}.Value`] = value;
            });
            await callEC2Api(client, "CreateTags", parseSubnetXmlResponse, {
                "ResourceId.1": subnet.SubnetId,
                ...tagParams,
            });
        }
    }
    else {
        // Create new subnet
        const createSubnetParams = {
            VpcId: vpcId,
            CidrBlock: props.cidrBlock,
            AvailabilityZone: props.availabilityZone,
        };
        // Add tags if specified
        if (props.tags) {
            createSubnetParams.TagSpecifications = [
                {
                    ResourceType: "subnet",
                    Tags: Object.entries(props.tags).map(([key, value]) => ({
                        Key: key,
                        Value: value,
                    })),
                },
            ];
        }
        const createParams = convertCreateSubnetParamsToAwsFormat(createSubnetParams);
        const response = await callEC2Api(client, "CreateSubnet", parseSubnetXmlResponse, createParams);
        if (!response.Subnet) {
            throw new Error("Failed to create subnet");
        }
        subnet = response.Subnet;
        // Set public IP assignment if specified
        if (props.mapPublicIpOnLaunch !== undefined) {
            await callEC2Api(client, "ModifySubnetAttribute", parseSubnetXmlResponse, {
                SubnetId: subnet.SubnetId,
                "MapPublicIpOnLaunch.Value": props.mapPublicIpOnLaunch.toString(),
            });
        }
        // Wait for subnet to be available
        await waitForSubnetAvailable(client, subnet.SubnetId, timeoutConfig);
    }
    return {
        subnetId: subnet.SubnetId,
        vpcId: subnet.VpcId,
        state: subnet.State,
        availableIpAddressCount: subnet.AvailableIpAddressCount,
        defaultForAz: subnet.DefaultForAz,
        ...props,
        vpc: vpcId,
    };
});
/**
 * Parse XML responses specifically for Subnet operations
 */
function parseSubnetXmlResponse(xmlText) {
    const parser = new XMLParser({
        ignoreAttributes: true,
        parseAttributeValue: true,
        parseTagValue: true,
        trimValues: true,
    });
    const parsed = parser.parse(xmlText);
    const result = {};
    // Parse CreateSubnetResponse
    if (parsed.CreateSubnetResponse) {
        const subnet = parsed.CreateSubnetResponse.subnet;
        if (subnet) {
            result.Subnet = {
                SubnetId: subnet.subnetId,
                VpcId: subnet.vpcId,
                State: subnet.state,
                CidrBlock: subnet.cidrBlock,
                AvailabilityZone: subnet.availabilityZone,
                AvailableIpAddressCount: subnet.availableIpAddressCount,
                DefaultForAz: subnet.defaultForAz,
                MapPublicIpOnLaunch: subnet.mapPublicIpOnLaunch,
            };
        }
    }
    // Parse DescribeSubnetsResponse
    if (parsed.DescribeSubnetsResponse) {
        const subnetSet = parsed.DescribeSubnetsResponse.subnetSet;
        if (subnetSet?.item) {
            const subnets = Array.isArray(subnetSet.item)
                ? subnetSet.item
                : [subnetSet.item];
            result.Subnets = subnets.map((subnet) => ({
                SubnetId: subnet.subnetId,
                VpcId: subnet.vpcId,
                State: subnet.state,
                CidrBlock: subnet.cidrBlock,
                AvailabilityZone: subnet.availabilityZone,
                AvailableIpAddressCount: subnet.availableIpAddressCount,
                DefaultForAz: subnet.defaultForAz,
                MapPublicIpOnLaunch: subnet.mapPublicIpOnLaunch,
            }));
        }
        else {
            result.Subnets = [];
        }
    }
    // Generic success responses
    const successResponses = [
        "ModifySubnetAttributeResponse",
        "DeleteSubnetResponse",
    ];
    for (const responseType of successResponses) {
        if (parsed[responseType]) {
            result.success = true;
            break;
        }
    }
    return result;
}
/**
 * Subnet timeout constants
 */
export const SUBNET_TIMEOUT = {
    maxAttempts: 30,
    delayMs: 1000, // 1 second - Subnets are quick
};
/**
 * Helper functions to convert typed parameters to AWS API format
 */
function convertCreateSubnetParamsToAwsFormat(params) {
    const awsParams = {
        VpcId: params.VpcId,
    };
    if (params.CidrBlock) {
        awsParams.CidrBlock = params.CidrBlock;
    }
    if (params.Ipv6CidrBlock) {
        awsParams.Ipv6CidrBlock = params.Ipv6CidrBlock;
    }
    if (params.AvailabilityZone) {
        awsParams.AvailabilityZone = params.AvailabilityZone;
    }
    if (params.AvailabilityZoneId) {
        awsParams.AvailabilityZoneId = params.AvailabilityZoneId;
    }
    if (params.OutpostArn) {
        awsParams.OutpostArn = params.OutpostArn;
    }
    if (params.CustomerOwnedIpv4Pool) {
        awsParams.CustomerOwnedIpv4Pool = params.CustomerOwnedIpv4Pool;
    }
    if (params.MapPublicIpOnLaunch !== undefined) {
        awsParams.MapPublicIpOnLaunch = params.MapPublicIpOnLaunch.toString();
    }
    if (params.MapCustomerOwnedIpOnLaunch !== undefined) {
        awsParams.MapCustomerOwnedIpOnLaunch =
            params.MapCustomerOwnedIpOnLaunch.toString();
    }
    if (params.Ipv4IpamPoolId) {
        awsParams.Ipv4IpamPoolId = params.Ipv4IpamPoolId;
    }
    if (params.Ipv4NetmaskLength !== undefined) {
        awsParams.Ipv4NetmaskLength = params.Ipv4NetmaskLength.toString();
    }
    if (params.Ipv6IpamPoolId) {
        awsParams.Ipv6IpamPoolId = params.Ipv6IpamPoolId;
    }
    if (params.Ipv6NetmaskLength !== undefined) {
        awsParams.Ipv6NetmaskLength = params.Ipv6NetmaskLength.toString();
    }
    if (params.DryRun !== undefined) {
        awsParams.DryRun = params.DryRun.toString();
    }
    // Handle TagSpecifications
    if (params.TagSpecifications) {
        params.TagSpecifications.forEach((tagSpec, specIndex) => {
            const specNum = specIndex + 1;
            awsParams[`TagSpecification.${specNum}.ResourceType`] =
                tagSpec.ResourceType;
            tagSpec.Tags.forEach((tag, tagIndex) => {
                const tagNum = tagIndex + 1;
                awsParams[`TagSpecification.${specNum}.Tag.${tagNum}.Key`] = tag.Key;
                awsParams[`TagSpecification.${specNum}.Tag.${tagNum}.Value`] =
                    tag.Value;
            });
        });
    }
    return awsParams;
}
//# sourceMappingURL=subnet.js.map