import { Resource } from "../resource.js";
import { ignore } from "../util/ignore.js";
import { importPeer } from "../util/peer.js";
import { retry } from "./retry.js";
/**
 * AWS DynamoDB Table Resource
 *
 * Creates and manages DynamoDB tables with support for partition and sort keys,
 * flexible billing modes, and automatic table status monitoring.
 *
 * @example
 * // Create a table with partition and sort key
 * const table = await Table("user-events", {
 *   tableName: "user-events",
 *   partitionKey: {
 *     name: "id",
 *     type: "S"
 *   },
 *   sortKey: {
 *     name: "timestamp",
 *     type: "N"
 *   },
 *   tags: {
 *     Environment: "test"
 *   }
 * });
 *
 * @example
 * // Create a table with provisioned capacity
 * const table = await Table("high-throughput", {
 *   tableName: "high-throughput",
 *   partitionKey: {
 *     name: "userId",
 *     type: "S"
 *   },
 *   billingMode: "PROVISIONED",
 *   readCapacity: 100,
 *   writeCapacity: 50
 * });
 */
export const Table = Resource("dynamo::Table", async function (id, props) {
    const { CreateTableCommand, DeleteTableCommand, DescribeTableCommand, DynamoDBClient, ResourceNotFoundException, } = await importPeer(import("@aws-sdk/client-dynamodb"), "dynamo::Table");
    const client = new DynamoDBClient({});
    const tableName = props.tableName ??
        this.output?.tableName ??
        this.scope.createPhysicalName(id);
    if (this.phase === "update" && this.output?.tableName !== tableName) {
        this.replace();
    }
    if (this.phase === "delete") {
        await retry(async () => {
            await ignore(ResourceNotFoundException.name, () => client.send(new DeleteTableCommand({
                TableName: tableName,
            })));
        });
        // Wait for table to be deleted
        let tableDeleted = false;
        let retryCount = 0;
        const maxRetries = 60; // Wait up to 60 seconds
        while (!tableDeleted && retryCount < maxRetries) {
            try {
                await client.send(new DescribeTableCommand({
                    TableName: tableName,
                }));
                // If we get here, table still exists
                retryCount++;
                // Increasing delay for each retry with some jitter
                const delay = Math.min(1000 * (1 + 0.1 * Math.random()), 5000);
                await new Promise((resolve) => setTimeout(resolve, delay));
            }
            catch (error) {
                if (error instanceof ResourceNotFoundException) {
                    tableDeleted = true;
                }
                else {
                    throw error;
                }
            }
        }
        if (!tableDeleted) {
            throw new Error(`Timed out waiting for table ${tableName} to be deleted`);
        }
        return this.destroy();
    }
    // Setup for table creation
    const attributeDefinitions = [
        {
            AttributeName: props.partitionKey.name,
            AttributeType: props.partitionKey.type,
        },
    ];
    const keySchema = [
        {
            AttributeName: props.partitionKey.name,
            KeyType: "HASH",
        },
    ];
    if (props.sortKey) {
        attributeDefinitions.push({
            AttributeName: props.sortKey.name,
            AttributeType: props.sortKey.type,
        });
        keySchema.push({
            AttributeName: props.sortKey.name,
            KeyType: "RANGE",
        });
    }
    // Attempt to create the table with retry for retryable errors
    await retry(async () => {
        try {
            // First check if table already exists
            const describeResponse = await client.send(new DescribeTableCommand({
                TableName: tableName,
            }));
            // If table exists and is ACTIVE, no need to create it
            if (describeResponse.Table?.TableStatus === "ACTIVE") {
                return;
            }
            // If table exists but not ACTIVE, wait for it in the polling loop below
            if (describeResponse.Table) {
                return;
            }
        }
        catch (error) {
            if (error instanceof ResourceNotFoundException) {
                // Table doesn't exist, try to create it
                await client.send(new CreateTableCommand({
                    TableName: tableName,
                    AttributeDefinitions: attributeDefinitions,
                    KeySchema: keySchema,
                    BillingMode: props.billingMode || "PAY_PER_REQUEST",
                    ProvisionedThroughput: props.billingMode === "PROVISIONED"
                        ? {
                            ReadCapacityUnits: props.readCapacity || 5,
                            WriteCapacityUnits: props.writeCapacity || 5,
                        }
                        : undefined,
                    Tags: props.tags
                        ? Object.entries(props.tags).map(([Key, Value]) => ({
                            Key,
                            Value,
                        }))
                        : undefined,
                }));
            }
            else {
                throw error;
            }
        }
    });
    // Wait for table to be active with timeout
    let tableActive = false;
    let tableDescription;
    let retryCount = 0;
    const maxRetries = 60; // Wait up to 60 seconds
    while (!tableActive && retryCount < maxRetries) {
        try {
            const response = await retry(async () => await client.send(new DescribeTableCommand({
                TableName: tableName,
            })));
            tableActive = response.Table?.TableStatus === "ACTIVE";
            if (tableActive) {
                tableDescription = response.Table;
            }
            else {
                retryCount++;
                // Increasing delay for each retry with some jitter
                const delay = Math.min(1000 * (1 + 0.1 * Math.random()), 5000);
                await new Promise((resolve) => setTimeout(resolve, delay));
            }
        }
        catch (error) {
            retryCount++;
            if (!(error instanceof ResourceNotFoundException)) {
                throw error;
            }
            await new Promise((resolve) => setTimeout(resolve, 1000));
        }
    }
    if (!tableActive) {
        throw new Error(`Timed out waiting for table ${tableName} to become active`);
    }
    return {
        ...props,
        arn: tableDescription.TableArn,
        tableName,
        streamArn: tableDescription.LatestStreamArn,
        tableId: tableDescription.TableId,
    };
});
//# sourceMappingURL=table.js.map