import {
  AWSClient
} from "./chunk-zqtpg06c.js";

// src/aws/ec2.ts
class EC2Client {
  client;
  region;
  constructor(region = "us-east-1") {
    this.region = region;
    this.client = new AWSClient;
  }
  async describeInstances(options) {
    const params = {
      Action: "DescribeInstances",
      Version: "2016-11-15"
    };
    if (options?.InstanceIds) {
      options.InstanceIds.forEach((id, i) => {
        params[`InstanceId.${i + 1}`] = id;
      });
    }
    if (options?.Filters) {
      options.Filters.forEach((filter, i) => {
        params[`Filter.${i + 1}.Name`] = filter.Name;
        filter.Values.forEach((val, j) => {
          params[`Filter.${i + 1}.Value.${j + 1}`] = val;
        });
      });
    }
    if (options?.MaxResults) {
      params.MaxResults = String(options.MaxResults);
    }
    if (options?.NextToken) {
      params.NextToken = options.NextToken;
    }
    const result = await this.client.request({
      service: "ec2",
      region: this.region,
      method: "POST",
      path: "/",
      headers: {
        "Content-Type": "application/x-www-form-urlencoded"
      },
      body: new URLSearchParams(params).toString()
    });
    const response = result.DescribeInstancesResponse || result;
    return {
      Reservations: this.parseReservations(response.reservationSet?.item),
      NextToken: response.nextToken
    };
  }
  async getInstance(instanceId) {
    const result = await this.describeInstances({ InstanceIds: [instanceId] });
    return result.Reservations?.[0]?.Instances?.[0];
  }
  async getConsoleOutput(instanceId, latest) {
    const params = {
      Action: "GetConsoleOutput",
      Version: "2016-11-15",
      InstanceId: instanceId
    };
    if (latest) {
      params.Latest = "true";
    }
    const result = await this.client.request({
      service: "ec2",
      region: this.region,
      method: "POST",
      path: "/",
      headers: {
        "Content-Type": "application/x-www-form-urlencoded"
      },
      body: new URLSearchParams(params).toString()
    });
    const response = result.GetConsoleOutputResponse || result;
    return {
      InstanceId: response.instanceId,
      Output: response.output,
      Timestamp: response.timestamp
    };
  }
  async getConsoleOutputDecoded(instanceId, options) {
    const result = await this.getConsoleOutput(instanceId, options?.latest);
    if (!result.Output) {
      return "No console output available yet";
    }
    const decoded = Buffer.from(result.Output, "base64").toString("utf-8");
    if (options?.tailLines) {
      const lines = decoded.split(`
`);
      return lines.slice(-options.tailLines).join(`
`);
    }
    return decoded;
  }
  async describeInstanceStatus(options) {
    const params = {
      Action: "DescribeInstanceStatus",
      Version: "2016-11-15"
    };
    if (options?.InstanceIds) {
      options.InstanceIds.forEach((id, i) => {
        params[`InstanceId.${i + 1}`] = id;
      });
    }
    if (options?.IncludeAllInstances) {
      params.IncludeAllInstances = "true";
    }
    if (options?.Filters) {
      options.Filters.forEach((filter, i) => {
        params[`Filter.${i + 1}.Name`] = filter.Name;
        filter.Values.forEach((val, j) => {
          params[`Filter.${i + 1}.Value.${j + 1}`] = val;
        });
      });
    }
    const result = await this.client.request({
      service: "ec2",
      region: this.region,
      method: "POST",
      path: "/",
      headers: {
        "Content-Type": "application/x-www-form-urlencoded"
      },
      body: new URLSearchParams(params).toString()
    });
    return {
      InstanceStatuses: this.parseArray(result.instanceStatusSet?.item).map((item) => ({
        InstanceId: item.instanceId,
        InstanceState: item.instanceState ? {
          Code: Number.parseInt(item.instanceState.code),
          Name: item.instanceState.name
        } : undefined,
        InstanceStatus: item.instanceStatus ? {
          Status: item.instanceStatus.status,
          Details: this.parseArray(item.instanceStatus.details?.item).map((d) => ({
            Name: d.name,
            Status: d.status
          }))
        } : undefined,
        SystemStatus: item.systemStatus ? {
          Status: item.systemStatus.status,
          Details: this.parseArray(item.systemStatus.details?.item).map((d) => ({
            Name: d.name,
            Status: d.status
          }))
        } : undefined
      }))
    };
  }
  async startInstances(instanceIds) {
    const params = {
      Action: "StartInstances",
      Version: "2016-11-15"
    };
    instanceIds.forEach((id, i) => {
      params[`InstanceId.${i + 1}`] = id;
    });
    const result = await this.client.request({
      service: "ec2",
      region: this.region,
      method: "POST",
      path: "/",
      headers: {
        "Content-Type": "application/x-www-form-urlencoded"
      },
      body: new URLSearchParams(params).toString()
    });
    return {
      StartingInstances: this.parseArray(result.instancesSet?.item).map((item) => ({
        InstanceId: item.instanceId,
        CurrentState: item.currentState ? { Name: item.currentState.name } : undefined,
        PreviousState: item.previousState ? { Name: item.previousState.name } : undefined
      }))
    };
  }
  async stopInstances(instanceIds, force) {
    const params = {
      Action: "StopInstances",
      Version: "2016-11-15"
    };
    instanceIds.forEach((id, i) => {
      params[`InstanceId.${i + 1}`] = id;
    });
    if (force) {
      params.Force = "true";
    }
    const result = await this.client.request({
      service: "ec2",
      region: this.region,
      method: "POST",
      path: "/",
      headers: {
        "Content-Type": "application/x-www-form-urlencoded"
      },
      body: new URLSearchParams(params).toString()
    });
    return {
      StoppingInstances: this.parseArray(result.instancesSet?.item).map((item) => ({
        InstanceId: item.instanceId,
        CurrentState: item.currentState ? { Name: item.currentState.name } : undefined,
        PreviousState: item.previousState ? { Name: item.previousState.name } : undefined
      }))
    };
  }
  async rebootInstances(instanceIds) {
    const params = {
      Action: "RebootInstances",
      Version: "2016-11-15"
    };
    instanceIds.forEach((id, i) => {
      params[`InstanceId.${i + 1}`] = id;
    });
    await this.client.request({
      service: "ec2",
      region: this.region,
      method: "POST",
      path: "/",
      headers: {
        "Content-Type": "application/x-www-form-urlencoded"
      },
      body: new URLSearchParams(params).toString()
    });
  }
  async terminateInstances(instanceIds) {
    const params = {
      Action: "TerminateInstances",
      Version: "2016-11-15"
    };
    instanceIds.forEach((id, i) => {
      params[`InstanceId.${i + 1}`] = id;
    });
    const result = await this.client.request({
      service: "ec2",
      region: this.region,
      method: "POST",
      path: "/",
      headers: {
        "Content-Type": "application/x-www-form-urlencoded"
      },
      body: new URLSearchParams(params).toString()
    });
    return {
      TerminatingInstances: this.parseArray(result.instancesSet?.item).map((item) => ({
        InstanceId: item.instanceId,
        CurrentState: item.currentState ? { Name: item.currentState.name } : undefined,
        PreviousState: item.previousState ? { Name: item.previousState.name } : undefined
      }))
    };
  }
  async createImage(options) {
    const params = {
      Action: "CreateImage",
      Version: "2016-11-15",
      InstanceId: options.InstanceId,
      Name: options.Name
    };
    if (options.Description)
      params.Description = options.Description;
    if (options.NoReboot !== undefined)
      params.NoReboot = String(options.NoReboot);
    if (options.TagSpecifications) {
      options.TagSpecifications.forEach((spec, i) => {
        params[`TagSpecification.${i + 1}.ResourceType`] = spec.ResourceType;
        spec.Tags.forEach((tag, j) => {
          params[`TagSpecification.${i + 1}.Tag.${j + 1}.Key`] = tag.Key;
          params[`TagSpecification.${i + 1}.Tag.${j + 1}.Value`] = tag.Value;
        });
      });
    }
    const result = await this.client.request({
      service: "ec2",
      region: this.region,
      method: "POST",
      path: "/",
      headers: { "Content-Type": "application/x-www-form-urlencoded" },
      body: new URLSearchParams(params).toString()
    });
    const response = result.CreateImageResponse || result;
    return { ImageId: response.imageId };
  }
  async deregisterImage(imageId) {
    await this.client.request({
      service: "ec2",
      region: this.region,
      method: "POST",
      path: "/",
      headers: { "Content-Type": "application/x-www-form-urlencoded" },
      body: new URLSearchParams({ Action: "DeregisterImage", Version: "2016-11-15", ImageId: imageId }).toString()
    });
  }
  async describeVpcs(options) {
    const params = {
      Action: "DescribeVpcs",
      Version: "2016-11-15"
    };
    if (options?.VpcIds) {
      options.VpcIds.forEach((id, i) => {
        params[`VpcId.${i + 1}`] = id;
      });
    }
    if (options?.Filters) {
      options.Filters.forEach((filter, i) => {
        params[`Filter.${i + 1}.Name`] = filter.Name;
        filter.Values.forEach((val, j) => {
          params[`Filter.${i + 1}.Value.${j + 1}`] = val;
        });
      });
    }
    const result = await this.client.request({
      service: "ec2",
      region: this.region,
      method: "POST",
      path: "/",
      headers: {
        "Content-Type": "application/x-www-form-urlencoded"
      },
      body: new URLSearchParams(params).toString()
    });
    return {
      Vpcs: this.parseArray(result.vpcSet?.item).map((item) => ({
        VpcId: item.vpcId,
        CidrBlock: item.cidrBlock,
        State: item.state,
        DhcpOptionsId: item.dhcpOptionsId,
        InstanceTenancy: item.instanceTenancy,
        IsDefault: item.isDefault === "true",
        Tags: this.parseTags(item.tagSet?.item)
      }))
    };
  }
  async describeSubnets(options) {
    const params = {
      Action: "DescribeSubnets",
      Version: "2016-11-15"
    };
    if (options?.SubnetIds) {
      options.SubnetIds.forEach((id, i) => {
        params[`SubnetId.${i + 1}`] = id;
      });
    }
    if (options?.Filters) {
      options.Filters.forEach((filter, i) => {
        params[`Filter.${i + 1}.Name`] = filter.Name;
        filter.Values.forEach((val, j) => {
          params[`Filter.${i + 1}.Value.${j + 1}`] = val;
        });
      });
    }
    const result = await this.client.request({
      service: "ec2",
      region: this.region,
      method: "POST",
      path: "/",
      headers: {
        "Content-Type": "application/x-www-form-urlencoded"
      },
      body: new URLSearchParams(params).toString()
    });
    return {
      Subnets: this.parseArray(result.subnetSet?.item).map((item) => ({
        SubnetId: item.subnetId,
        VpcId: item.vpcId,
        CidrBlock: item.cidrBlock,
        AvailabilityZone: item.availabilityZone,
        AvailableIpAddressCount: Number.parseInt(item.availableIpAddressCount),
        State: item.state,
        MapPublicIpOnLaunch: item.mapPublicIpOnLaunch === "true",
        Tags: this.parseTags(item.tagSet?.item)
      }))
    };
  }
  async describeSecurityGroups(options) {
    const params = {
      Action: "DescribeSecurityGroups",
      Version: "2016-11-15"
    };
    if (options?.GroupIds) {
      options.GroupIds.forEach((id, i) => {
        params[`GroupId.${i + 1}`] = id;
      });
    }
    if (options?.GroupNames) {
      options.GroupNames.forEach((name, i) => {
        params[`GroupName.${i + 1}`] = name;
      });
    }
    if (options?.Filters) {
      options.Filters.forEach((filter, i) => {
        params[`Filter.${i + 1}.Name`] = filter.Name;
        filter.Values.forEach((val, j) => {
          params[`Filter.${i + 1}.Value.${j + 1}`] = val;
        });
      });
    }
    const result = await this.client.request({
      service: "ec2",
      region: this.region,
      method: "POST",
      path: "/",
      headers: {
        "Content-Type": "application/x-www-form-urlencoded"
      },
      body: new URLSearchParams(params).toString()
    });
    return {
      SecurityGroups: this.parseArray(result.securityGroupInfo?.item).map((item) => ({
        GroupId: item.groupId,
        GroupName: item.groupName,
        Description: item.groupDescription,
        VpcId: item.vpcId,
        IpPermissions: this.parseIpPermissions(item.ipPermissions?.item),
        IpPermissionsEgress: this.parseIpPermissions(item.ipPermissionsEgress?.item),
        Tags: this.parseTags(item.tagSet?.item)
      }))
    };
  }
  async describeInternetGateways(options) {
    const params = {
      Action: "DescribeInternetGateways",
      Version: "2016-11-15"
    };
    if (options?.InternetGatewayIds) {
      options.InternetGatewayIds.forEach((id, i) => {
        params[`InternetGatewayId.${i + 1}`] = id;
      });
    }
    if (options?.Filters) {
      options.Filters.forEach((filter, i) => {
        params[`Filter.${i + 1}.Name`] = filter.Name;
        filter.Values.forEach((val, j) => {
          params[`Filter.${i + 1}.Value.${j + 1}`] = val;
        });
      });
    }
    const result = await this.client.request({
      service: "ec2",
      region: this.region,
      method: "POST",
      path: "/",
      headers: {
        "Content-Type": "application/x-www-form-urlencoded"
      },
      body: new URLSearchParams(params).toString()
    });
    return {
      InternetGateways: this.parseArray(result.internetGatewaySet?.item).map((item) => ({
        InternetGatewayId: item.internetGatewayId,
        Attachments: this.parseArray(item.attachmentSet?.item).map((a) => ({
          VpcId: a.vpcId,
          State: a.state
        })),
        Tags: this.parseTags(item.tagSet?.item)
      }))
    };
  }
  async describeAddresses(options) {
    const params = {
      Action: "DescribeAddresses",
      Version: "2016-11-15"
    };
    if (options?.AllocationIds) {
      options.AllocationIds.forEach((id, i) => {
        params[`AllocationId.${i + 1}`] = id;
      });
    }
    if (options?.PublicIps) {
      options.PublicIps.forEach((ip, i) => {
        params[`PublicIp.${i + 1}`] = ip;
      });
    }
    if (options?.Filters) {
      options.Filters.forEach((filter, i) => {
        params[`Filter.${i + 1}.Name`] = filter.Name;
        filter.Values.forEach((val, j) => {
          params[`Filter.${i + 1}.Value.${j + 1}`] = val;
        });
      });
    }
    const result = await this.client.request({
      service: "ec2",
      region: this.region,
      method: "POST",
      path: "/",
      headers: {
        "Content-Type": "application/x-www-form-urlencoded"
      },
      body: new URLSearchParams(params).toString()
    });
    return {
      Addresses: this.parseArray(result.addressesSet?.item).map((item) => ({
        PublicIp: item.publicIp,
        AllocationId: item.allocationId,
        AssociationId: item.associationId,
        InstanceId: item.instanceId,
        NetworkInterfaceId: item.networkInterfaceId,
        PrivateIpAddress: item.privateIpAddress,
        Domain: item.domain,
        Tags: this.parseTags(item.tagSet?.item)
      }))
    };
  }
  async allocateAddress(options) {
    const params = {
      Action: "AllocateAddress",
      Version: "2016-11-15"
    };
    if (options?.Domain) {
      params.Domain = options.Domain;
    }
    if (options?.TagSpecifications) {
      options.TagSpecifications.forEach((spec, i) => {
        params[`TagSpecification.${i + 1}.ResourceType`] = spec.ResourceType;
        spec.Tags.forEach((tag, j) => {
          params[`TagSpecification.${i + 1}.Tag.${j + 1}.Key`] = tag.Key;
          params[`TagSpecification.${i + 1}.Tag.${j + 1}.Value`] = tag.Value;
        });
      });
    }
    const result = await this.client.request({
      service: "ec2",
      region: this.region,
      method: "POST",
      path: "/",
      headers: {
        "Content-Type": "application/x-www-form-urlencoded"
      },
      body: new URLSearchParams(params).toString()
    });
    return {
      AllocationId: result.allocationId,
      PublicIp: result.publicIp,
      Domain: result.domain
    };
  }
  async associateAddress(options) {
    const params = {
      Action: "AssociateAddress",
      Version: "2016-11-15"
    };
    if (options.AllocationId) {
      params.AllocationId = options.AllocationId;
    }
    if (options.PublicIp) {
      params.PublicIp = options.PublicIp;
    }
    if (options.InstanceId) {
      params.InstanceId = options.InstanceId;
    }
    if (options.NetworkInterfaceId) {
      params.NetworkInterfaceId = options.NetworkInterfaceId;
    }
    if (options.PrivateIpAddress) {
      params.PrivateIpAddress = options.PrivateIpAddress;
    }
    if (options.AllowReassociation) {
      params.AllowReassociation = "true";
    }
    const result = await this.client.request({
      service: "ec2",
      region: this.region,
      method: "POST",
      path: "/",
      headers: {
        "Content-Type": "application/x-www-form-urlencoded"
      },
      body: new URLSearchParams(params).toString()
    });
    return {
      AssociationId: result.associationId
    };
  }
  async createTags(options) {
    const params = {
      Action: "CreateTags",
      Version: "2016-11-15"
    };
    options.Resources.forEach((id, i) => {
      params[`ResourceId.${i + 1}`] = id;
    });
    options.Tags.forEach((tag, i) => {
      params[`Tag.${i + 1}.Key`] = tag.Key;
      params[`Tag.${i + 1}.Value`] = tag.Value;
    });
    await this.client.request({
      service: "ec2",
      region: this.region,
      method: "POST",
      path: "/",
      headers: {
        "Content-Type": "application/x-www-form-urlencoded"
      },
      body: new URLSearchParams(params).toString()
    });
  }
  async waitForInstanceState(instanceId, targetState, options) {
    const maxWait = options?.maxWaitMs || 300000;
    const pollInterval = options?.pollIntervalMs || 5000;
    const startTime = Date.now();
    while (Date.now() - startTime < maxWait) {
      let instance;
      try {
        instance = await this.getInstance(instanceId);
      } catch {
        instance = undefined;
      }
      if (instance?.State?.Name === targetState) {
        return instance;
      }
      await new Promise((resolve) => setTimeout(resolve, pollInterval));
    }
    return;
  }
  async createVpc(options) {
    const params = {
      Action: "CreateVpc",
      Version: "2016-11-15",
      CidrBlock: options.CidrBlock
    };
    if (options.InstanceTenancy) {
      params.InstanceTenancy = options.InstanceTenancy;
    }
    if (options.TagSpecifications) {
      options.TagSpecifications.forEach((spec, i) => {
        params[`TagSpecification.${i + 1}.ResourceType`] = spec.ResourceType;
        spec.Tags.forEach((tag, j) => {
          params[`TagSpecification.${i + 1}.Tag.${j + 1}.Key`] = tag.Key;
          params[`TagSpecification.${i + 1}.Tag.${j + 1}.Value`] = tag.Value;
        });
      });
    }
    const result = await this.client.request({
      service: "ec2",
      region: this.region,
      method: "POST",
      path: "/",
      headers: {
        "Content-Type": "application/x-www-form-urlencoded"
      },
      body: new URLSearchParams(params).toString()
    });
    const response = result.CreateVpcResponse || result;
    const vpc = response.vpc;
    return {
      Vpc: vpc ? {
        VpcId: vpc.vpcId,
        CidrBlock: vpc.cidrBlock,
        State: vpc.state,
        DhcpOptionsId: vpc.dhcpOptionsId,
        InstanceTenancy: vpc.instanceTenancy,
        IsDefault: vpc.isDefault === "true",
        Tags: this.parseTags(vpc.tagSet?.item)
      } : undefined
    };
  }
  async createSubnet(options) {
    const params = {
      Action: "CreateSubnet",
      Version: "2016-11-15",
      VpcId: options.VpcId,
      CidrBlock: options.CidrBlock
    };
    if (options.AvailabilityZone) {
      params.AvailabilityZone = options.AvailabilityZone;
    }
    if (options.TagSpecifications) {
      options.TagSpecifications.forEach((spec, i) => {
        params[`TagSpecification.${i + 1}.ResourceType`] = spec.ResourceType;
        spec.Tags.forEach((tag, j) => {
          params[`TagSpecification.${i + 1}.Tag.${j + 1}.Key`] = tag.Key;
          params[`TagSpecification.${i + 1}.Tag.${j + 1}.Value`] = tag.Value;
        });
      });
    }
    const result = await this.client.request({
      service: "ec2",
      region: this.region,
      method: "POST",
      path: "/",
      headers: {
        "Content-Type": "application/x-www-form-urlencoded"
      },
      body: new URLSearchParams(params).toString()
    });
    const response = result.CreateSubnetResponse || result;
    const subnet = response.subnet;
    return {
      Subnet: subnet ? {
        SubnetId: subnet.subnetId,
        VpcId: subnet.vpcId,
        CidrBlock: subnet.cidrBlock,
        AvailabilityZone: subnet.availabilityZone,
        AvailableIpAddressCount: subnet.availableIpAddressCount ? Number.parseInt(subnet.availableIpAddressCount) : undefined,
        State: subnet.state,
        MapPublicIpOnLaunch: subnet.mapPublicIpOnLaunch === "true",
        Tags: this.parseTags(subnet.tagSet?.item)
      } : undefined
    };
  }
  async modifySubnetAttribute(options) {
    const params = {
      Action: "ModifySubnetAttribute",
      Version: "2016-11-15",
      SubnetId: options.SubnetId
    };
    if (options.MapPublicIpOnLaunch !== undefined) {
      params["MapPublicIpOnLaunch.Value"] = String(options.MapPublicIpOnLaunch.Value);
    }
    await this.client.request({
      service: "ec2",
      region: this.region,
      method: "POST",
      path: "/",
      headers: {
        "Content-Type": "application/x-www-form-urlencoded"
      },
      body: new URLSearchParams(params).toString()
    });
  }
  async createSecurityGroup(options) {
    const params = {
      Action: "CreateSecurityGroup",
      Version: "2016-11-15",
      GroupName: options.GroupName,
      GroupDescription: options.Description
    };
    if (options.VpcId) {
      params.VpcId = options.VpcId;
    }
    if (options.TagSpecifications) {
      options.TagSpecifications.forEach((spec, i) => {
        params[`TagSpecification.${i + 1}.ResourceType`] = spec.ResourceType;
        spec.Tags.forEach((tag, j) => {
          params[`TagSpecification.${i + 1}.Tag.${j + 1}.Key`] = tag.Key;
          params[`TagSpecification.${i + 1}.Tag.${j + 1}.Value`] = tag.Value;
        });
      });
    }
    const result = await this.client.request({
      service: "ec2",
      region: this.region,
      method: "POST",
      path: "/",
      headers: {
        "Content-Type": "application/x-www-form-urlencoded"
      },
      body: new URLSearchParams(params).toString()
    });
    const response = result.CreateSecurityGroupResponse || result;
    return {
      GroupId: response.groupId
    };
  }
  async authorizeSecurityGroupIngress(options) {
    const params = {
      Action: "AuthorizeSecurityGroupIngress",
      Version: "2016-11-15",
      GroupId: options.GroupId
    };
    this.encodeIpPermissions(params, options.IpPermissions);
    await this.client.request({
      service: "ec2",
      region: this.region,
      method: "POST",
      path: "/",
      headers: {
        "Content-Type": "application/x-www-form-urlencoded"
      },
      body: new URLSearchParams(params).toString()
    });
  }
  async authorizeSecurityGroupEgress(options) {
    const params = {
      Action: "AuthorizeSecurityGroupEgress",
      Version: "2016-11-15",
      GroupId: options.GroupId
    };
    this.encodeIpPermissions(params, options.IpPermissions);
    await this.client.request({
      service: "ec2",
      region: this.region,
      method: "POST",
      path: "/",
      headers: {
        "Content-Type": "application/x-www-form-urlencoded"
      },
      body: new URLSearchParams(params).toString()
    });
  }
  async describeRouteTables(options) {
    const params = {
      Action: "DescribeRouteTables",
      Version: "2016-11-15"
    };
    if (options?.RouteTableIds) {
      options.RouteTableIds.forEach((id, i) => {
        params[`RouteTableId.${i + 1}`] = id;
      });
    }
    if (options?.Filters) {
      options.Filters.forEach((filter, i) => {
        params[`Filter.${i + 1}.Name`] = filter.Name;
        filter.Values.forEach((val, j) => {
          params[`Filter.${i + 1}.Value.${j + 1}`] = val;
        });
      });
    }
    const result = await this.client.request({
      service: "ec2",
      region: this.region,
      method: "POST",
      path: "/",
      headers: {
        "Content-Type": "application/x-www-form-urlencoded"
      },
      body: new URLSearchParams(params).toString()
    });
    const response = result.DescribeRouteTablesResponse || result;
    return {
      RouteTables: this.parseArray(response.routeTableSet?.item).map((item) => ({
        RouteTableId: item.routeTableId,
        VpcId: item.vpcId,
        Routes: this.parseArray(item.routeSet?.item).map((r) => ({
          DestinationCidrBlock: r.destinationCidrBlock,
          GatewayId: r.gatewayId,
          NatGatewayId: r.natGatewayId,
          State: r.state
        })),
        Associations: this.parseArray(item.associationSet?.item).map((a) => ({
          RouteTableAssociationId: a.routeTableAssociationId,
          SubnetId: a.subnetId,
          Main: a.main === "true" || a.main === true
        })),
        Tags: this.parseTags(item.tagSet?.item)
      }))
    };
  }
  async deleteVpc(vpcId) {
    const params = {
      Action: "DeleteVpc",
      Version: "2016-11-15",
      VpcId: vpcId
    };
    await this.client.request({
      service: "ec2",
      region: this.region,
      method: "POST",
      path: "/",
      headers: {
        "Content-Type": "application/x-www-form-urlencoded"
      },
      body: new URLSearchParams(params).toString()
    });
  }
  async deleteSubnet(subnetId) {
    const params = {
      Action: "DeleteSubnet",
      Version: "2016-11-15",
      SubnetId: subnetId
    };
    await this.client.request({
      service: "ec2",
      region: this.region,
      method: "POST",
      path: "/",
      headers: {
        "Content-Type": "application/x-www-form-urlencoded"
      },
      body: new URLSearchParams(params).toString()
    });
  }
  async deleteSecurityGroup(groupId) {
    await this.client.request({
      service: "ec2",
      region: this.region,
      method: "POST",
      path: "/",
      headers: { "Content-Type": "application/x-www-form-urlencoded" },
      body: new URLSearchParams({ Action: "DeleteSecurityGroup", Version: "2016-11-15", GroupId: groupId }).toString()
    });
  }
  async describeNetworkInterfaces(options) {
    const params = {
      Action: "DescribeNetworkInterfaces",
      Version: "2016-11-15"
    };
    if (options?.NetworkInterfaceIds) {
      options.NetworkInterfaceIds.forEach((id, i) => {
        params[`NetworkInterfaceId.${i + 1}`] = id;
      });
    }
    if (options?.Filters) {
      options.Filters.forEach((filter, i) => {
        params[`Filter.${i + 1}.Name`] = filter.Name;
        filter.Values.forEach((val, j) => {
          params[`Filter.${i + 1}.Value.${j + 1}`] = val;
        });
      });
    }
    const result = await this.client.request({
      service: "ec2",
      region: this.region,
      method: "POST",
      path: "/",
      headers: {
        "Content-Type": "application/x-www-form-urlencoded"
      },
      body: new URLSearchParams(params).toString()
    });
    const response = result.DescribeNetworkInterfacesResponse || result;
    return {
      NetworkInterfaces: this.parseArray(response.networkInterfaceSet?.item).map((ni) => ({
        NetworkInterfaceId: ni.networkInterfaceId,
        SubnetId: ni.subnetId,
        VpcId: ni.vpcId,
        Status: ni.status,
        Attachment: ni.attachment ? {
          AttachmentId: ni.attachment.attachmentId,
          InstanceId: ni.attachment.instanceId,
          Status: ni.attachment.status
        } : undefined
      }))
    };
  }
  async detachNetworkInterface(attachmentId, force) {
    const params = {
      Action: "DetachNetworkInterface",
      Version: "2016-11-15",
      AttachmentId: attachmentId
    };
    if (force) {
      params.Force = "true";
    }
    await this.client.request({
      service: "ec2",
      region: this.region,
      method: "POST",
      path: "/",
      headers: {
        "Content-Type": "application/x-www-form-urlencoded"
      },
      body: new URLSearchParams(params).toString()
    });
  }
  async deleteNetworkInterface(networkInterfaceId) {
    const params = {
      Action: "DeleteNetworkInterface",
      Version: "2016-11-15",
      NetworkInterfaceId: networkInterfaceId
    };
    await this.client.request({
      service: "ec2",
      region: this.region,
      method: "POST",
      path: "/",
      headers: {
        "Content-Type": "application/x-www-form-urlencoded"
      },
      body: new URLSearchParams(params).toString()
    });
  }
  async describeRegions() {
    const params = {
      Action: "DescribeRegions",
      Version: "2016-11-15"
    };
    const result = await this.client.request({
      service: "ec2",
      region: this.region,
      method: "POST",
      path: "/",
      headers: {
        "Content-Type": "application/x-www-form-urlencoded"
      },
      body: new URLSearchParams(params).toString()
    });
    const response = result.DescribeRegionsResponse || result;
    return {
      Regions: this.parseArray(response.regionInfo?.item).map((r) => ({
        RegionName: r.regionName,
        Endpoint: r.regionEndpoint
      }))
    };
  }
  async runInstances(options) {
    const params = {
      Action: "RunInstances",
      Version: "2016-11-15",
      ImageId: options.ImageId,
      InstanceType: options.InstanceType,
      MinCount: String(options.MinCount),
      MaxCount: String(options.MaxCount)
    };
    if (options.SecurityGroupIds) {
      options.SecurityGroupIds.forEach((id, i) => {
        params[`SecurityGroupId.${i + 1}`] = id;
      });
    }
    if (options.SubnetId) {
      params.SubnetId = options.SubnetId;
    }
    if (options.UserData) {
      params.UserData = options.UserData;
    }
    if (options.IamInstanceProfile) {
      if (options.IamInstanceProfile.Name) {
        params["IamInstanceProfile.Name"] = options.IamInstanceProfile.Name;
      }
      if (options.IamInstanceProfile.Arn) {
        params["IamInstanceProfile.Arn"] = options.IamInstanceProfile.Arn;
      }
    }
    if (options.TagSpecifications) {
      options.TagSpecifications.forEach((spec, i) => {
        params[`TagSpecification.${i + 1}.ResourceType`] = spec.ResourceType;
        spec.Tags.forEach((tag, j) => {
          params[`TagSpecification.${i + 1}.Tag.${j + 1}.Key`] = tag.Key;
          params[`TagSpecification.${i + 1}.Tag.${j + 1}.Value`] = tag.Value;
        });
      });
    }
    const result = await this.client.request({
      service: "ec2",
      region: this.region,
      method: "POST",
      path: "/",
      headers: {
        "Content-Type": "application/x-www-form-urlencoded"
      },
      body: new URLSearchParams(params).toString()
    });
    const response = result.RunInstancesResponse || result;
    return {
      Instances: this.parseInstances(response.instancesSet?.item || response.instancesSet)
    };
  }
  encodeIpPermissions(params, permissions) {
    permissions.forEach((perm, i) => {
      const prefix = `IpPermissions.${i + 1}`;
      if (perm.IpProtocol !== undefined) {
        params[`${prefix}.IpProtocol`] = perm.IpProtocol;
      }
      if (perm.FromPort !== undefined) {
        params[`${prefix}.FromPort`] = String(perm.FromPort);
      }
      if (perm.ToPort !== undefined) {
        params[`${prefix}.ToPort`] = String(perm.ToPort);
      }
      if (perm.IpRanges) {
        perm.IpRanges.forEach((range, j) => {
          if (range.CidrIp) {
            params[`${prefix}.IpRanges.${j + 1}.CidrIp`] = range.CidrIp;
          }
          if (range.Description) {
            params[`${prefix}.IpRanges.${j + 1}.Description`] = range.Description;
          }
        });
      }
      if (perm.Ipv6Ranges) {
        perm.Ipv6Ranges.forEach((range, j) => {
          if (range.CidrIpv6) {
            params[`${prefix}.Ipv6Ranges.${j + 1}.CidrIpv6`] = range.CidrIpv6;
          }
          if (range.Description) {
            params[`${prefix}.Ipv6Ranges.${j + 1}.Description`] = range.Description;
          }
        });
      }
      if (perm.UserIdGroupPairs) {
        perm.UserIdGroupPairs.forEach((pair, j) => {
          if (pair.GroupId) {
            params[`${prefix}.Groups.${j + 1}.GroupId`] = pair.GroupId;
          }
          if (pair.UserId) {
            params[`${prefix}.Groups.${j + 1}.UserId`] = pair.UserId;
          }
          if (pair.Description) {
            params[`${prefix}.Groups.${j + 1}.Description`] = pair.Description;
          }
        });
      }
    });
  }
  parseArray(item) {
    if (!item)
      return [];
    return Array.isArray(item) ? item : [item];
  }
  parseTags(item) {
    return this.parseArray(item).map((t) => ({
      Key: t.key,
      Value: t.value
    }));
  }
  parseReservations(item) {
    return this.parseArray(item).map((r) => ({
      ReservationId: r.reservationId,
      Instances: this.parseInstances(r.instancesSet?.item || r.instancesSet)
    }));
  }
  parseInstances(item) {
    return this.parseArray(item).map((i) => ({
      InstanceId: i.instanceId,
      ImageId: i.imageId,
      InstanceType: i.instanceType,
      State: i.instanceState ? {
        Code: Number.parseInt(i.instanceState.code),
        Name: i.instanceState.name
      } : undefined,
      PrivateIpAddress: i.privateIpAddress,
      PublicIpAddress: i.ipAddress,
      SubnetId: i.subnetId,
      VpcId: i.vpcId,
      SecurityGroups: this.parseArray(i.groupSet?.item).map((g) => ({
        GroupId: g.groupId,
        GroupName: g.groupName
      })),
      Tags: this.parseTags(i.tagSet?.item),
      LaunchTime: i.launchTime,
      Placement: i.placement ? {
        AvailabilityZone: i.placement.availabilityZone,
        Tenancy: i.placement.tenancy
      } : undefined,
      Architecture: i.architecture,
      RootDeviceType: i.rootDeviceType,
      RootDeviceName: i.rootDeviceName,
      BlockDeviceMappings: this.parseArray(i.blockDeviceMapping?.item).map((b) => ({
        DeviceName: b.deviceName,
        Ebs: b.ebs ? {
          VolumeId: b.ebs.volumeId,
          Status: b.ebs.status,
          AttachTime: b.ebs.attachTime,
          DeleteOnTermination: b.ebs.deleteOnTermination === "true"
        } : undefined
      })),
      IamInstanceProfile: i.iamInstanceProfile ? {
        Arn: i.iamInstanceProfile.arn,
        Id: i.iamInstanceProfile.id
      } : undefined
    }));
  }
  parseIpPermissions(item) {
    return this.parseArray(item).map((p) => ({
      IpProtocol: p.ipProtocol,
      FromPort: p.fromPort ? Number.parseInt(p.fromPort) : undefined,
      ToPort: p.toPort ? Number.parseInt(p.toPort) : undefined,
      IpRanges: this.parseArray(p.ipRanges?.item).map((r) => ({
        CidrIp: r.cidrIp,
        Description: r.description
      })),
      Ipv6Ranges: this.parseArray(p.ipv6Ranges?.item).map((r) => ({
        CidrIpv6: r.cidrIpv6,
        Description: r.description
      })),
      UserIdGroupPairs: this.parseArray(p.groups?.item).map((g) => ({
        GroupId: g.groupId,
        UserId: g.userId,
        Description: g.description
      }))
    }));
  }
}

// src/aws/ssm.ts
class SSMClient {
  client;
  region;
  constructor(region = "us-east-1", profile) {
    this.region = region;
    this.client = new AWSClient;
  }
  async putParameter(options) {
    const params = {
      Name: options.Name,
      Value: options.Value
    };
    if (options.Type) {
      params.Type = options.Type;
    }
    if (options.Description) {
      params.Description = options.Description;
    }
    if (options.KeyId) {
      params.KeyId = options.KeyId;
    }
    if (options.Overwrite !== undefined) {
      params.Overwrite = options.Overwrite;
    }
    if (options.AllowedPattern) {
      params.AllowedPattern = options.AllowedPattern;
    }
    if (options.Tags && options.Tags.length > 0) {
      params.Tags = options.Tags;
    }
    if (options.Tier) {
      params.Tier = options.Tier;
    }
    if (options.DataType) {
      params.DataType = options.DataType;
    }
    const result = await this.client.request({
      service: "ssm",
      region: this.region,
      method: "POST",
      path: "/",
      headers: {
        "X-Amz-Target": "AmazonSSM.PutParameter",
        "Content-Type": "application/x-amz-json-1.1"
      },
      body: JSON.stringify(params)
    });
    return {
      Version: result.Version,
      Tier: result.Tier
    };
  }
  async getParameter(options) {
    const params = {
      Name: options.Name
    };
    if (options.WithDecryption !== undefined) {
      params.WithDecryption = options.WithDecryption;
    }
    const result = await this.client.request({
      service: "ssm",
      region: this.region,
      method: "POST",
      path: "/",
      headers: {
        "X-Amz-Target": "AmazonSSM.GetParameter",
        "Content-Type": "application/x-amz-json-1.1"
      },
      body: JSON.stringify(params)
    });
    return {
      Parameter: result.Parameter ? this.parseParameter(result.Parameter) : undefined
    };
  }
  async getParameters(options) {
    const params = {
      Names: options.Names
    };
    if (options.WithDecryption !== undefined) {
      params.WithDecryption = options.WithDecryption;
    }
    const result = await this.client.request({
      service: "ssm",
      region: this.region,
      method: "POST",
      path: "/",
      headers: {
        "X-Amz-Target": "AmazonSSM.GetParameters",
        "Content-Type": "application/x-amz-json-1.1"
      },
      body: JSON.stringify(params)
    });
    return {
      Parameters: result.Parameters?.map((p) => this.parseParameter(p)),
      InvalidParameters: result.InvalidParameters
    };
  }
  async getParametersByPath(options) {
    const params = {
      Path: options.Path
    };
    if (options.Recursive !== undefined) {
      params.Recursive = options.Recursive;
    }
    if (options.WithDecryption !== undefined) {
      params.WithDecryption = options.WithDecryption;
    }
    if (options.MaxResults) {
      params.MaxResults = options.MaxResults;
    }
    if (options.NextToken) {
      params.NextToken = options.NextToken;
    }
    if (options.ParameterFilters && options.ParameterFilters.length > 0) {
      params.ParameterFilters = options.ParameterFilters;
    }
    const result = await this.client.request({
      service: "ssm",
      region: this.region,
      method: "POST",
      path: "/",
      headers: {
        "X-Amz-Target": "AmazonSSM.GetParametersByPath",
        "Content-Type": "application/x-amz-json-1.1"
      },
      body: JSON.stringify(params)
    });
    return {
      Parameters: result.Parameters?.map((p) => this.parseParameter(p)),
      NextToken: result.NextToken
    };
  }
  async deleteParameter(options) {
    const params = {
      Name: options.Name
    };
    await this.client.request({
      service: "ssm",
      region: this.region,
      method: "POST",
      path: "/",
      headers: {
        "X-Amz-Target": "AmazonSSM.DeleteParameter",
        "Content-Type": "application/x-amz-json-1.1"
      },
      body: JSON.stringify(params)
    });
  }
  async deleteParameters(names) {
    const params = {
      Names: names
    };
    const result = await this.client.request({
      service: "ssm",
      region: this.region,
      method: "POST",
      path: "/",
      headers: {
        "X-Amz-Target": "AmazonSSM.DeleteParameters",
        "Content-Type": "application/x-amz-json-1.1"
      },
      body: JSON.stringify(params)
    });
    return {
      DeletedParameters: result.DeletedParameters,
      InvalidParameters: result.InvalidParameters
    };
  }
  async describeParameters(options) {
    const params = {};
    if (options?.Filters && options.Filters.length > 0) {
      params.Filters = options.Filters;
    }
    if (options?.ParameterFilters && options.ParameterFilters.length > 0) {
      params.ParameterFilters = options.ParameterFilters;
    }
    if (options?.MaxResults) {
      params.MaxResults = options.MaxResults;
    }
    if (options?.NextToken) {
      params.NextToken = options.NextToken;
    }
    const result = await this.client.request({
      service: "ssm",
      region: this.region,
      method: "POST",
      path: "/",
      headers: {
        "X-Amz-Target": "AmazonSSM.DescribeParameters",
        "Content-Type": "application/x-amz-json-1.1"
      },
      body: JSON.stringify(params)
    });
    return {
      Parameters: result.Parameters?.map((p) => this.parseParameter(p)),
      NextToken: result.NextToken
    };
  }
  async getParameterHistory(options) {
    const params = {
      Name: options.Name
    };
    if (options.WithDecryption !== undefined) {
      params.WithDecryption = options.WithDecryption;
    }
    if (options.MaxResults) {
      params.MaxResults = options.MaxResults;
    }
    if (options.NextToken) {
      params.NextToken = options.NextToken;
    }
    const result = await this.client.request({
      service: "ssm",
      region: this.region,
      method: "POST",
      path: "/",
      headers: {
        "X-Amz-Target": "AmazonSSM.GetParameterHistory",
        "Content-Type": "application/x-amz-json-1.1"
      },
      body: JSON.stringify(params)
    });
    return {
      Parameters: result.Parameters?.map((p) => ({
        Name: p.Name,
        Type: p.Type,
        KeyId: p.KeyId,
        LastModifiedDate: p.LastModifiedDate,
        LastModifiedUser: p.LastModifiedUser,
        Description: p.Description,
        Value: p.Value,
        Version: p.Version,
        Labels: p.Labels,
        Tier: p.Tier
      })),
      NextToken: result.NextToken
    };
  }
  async labelParameterVersion(options) {
    const params = {
      Name: options.Name,
      Labels: options.Labels
    };
    if (options.ParameterVersion !== undefined) {
      params.ParameterVersion = options.ParameterVersion;
    }
    const result = await this.client.request({
      service: "ssm",
      region: this.region,
      method: "POST",
      path: "/",
      headers: {
        "X-Amz-Target": "AmazonSSM.LabelParameterVersion",
        "Content-Type": "application/x-amz-json-1.1"
      },
      body: JSON.stringify(params)
    });
    return {
      InvalidLabels: result.InvalidLabels,
      ParameterVersion: result.ParameterVersion
    };
  }
  async addTagsToResource(options) {
    const params = {
      ResourceType: options.ResourceType,
      ResourceId: options.ResourceId,
      Tags: options.Tags
    };
    await this.client.request({
      service: "ssm",
      region: this.region,
      method: "POST",
      path: "/",
      headers: {
        "X-Amz-Target": "AmazonSSM.AddTagsToResource",
        "Content-Type": "application/x-amz-json-1.1"
      },
      body: JSON.stringify(params)
    });
  }
  async removeTagsFromResource(options) {
    const params = {
      ResourceType: options.ResourceType,
      ResourceId: options.ResourceId,
      TagKeys: options.TagKeys
    };
    await this.client.request({
      service: "ssm",
      region: this.region,
      method: "POST",
      path: "/",
      headers: {
        "X-Amz-Target": "AmazonSSM.RemoveTagsFromResource",
        "Content-Type": "application/x-amz-json-1.1"
      },
      body: JSON.stringify(params)
    });
  }
  async listTagsForResource(options) {
    const params = {
      ResourceType: options.ResourceType,
      ResourceId: options.ResourceId
    };
    const result = await this.client.request({
      service: "ssm",
      region: this.region,
      method: "POST",
      path: "/",
      headers: {
        "X-Amz-Target": "AmazonSSM.ListTagsForResource",
        "Content-Type": "application/x-amz-json-1.1"
      },
      body: JSON.stringify(params)
    });
    return {
      TagList: result.TagList
    };
  }
  async setString(name, value, options) {
    return this.putParameter({
      Name: name,
      Value: value,
      Type: "String",
      Description: options?.description,
      Overwrite: options?.overwrite ?? true,
      Tags: options?.tags
    });
  }
  async setSecureString(name, value, options) {
    return this.putParameter({
      Name: name,
      Value: value,
      Type: "SecureString",
      Description: options?.description,
      Overwrite: options?.overwrite ?? true,
      KeyId: options?.kmsKeyId,
      Tags: options?.tags
    });
  }
  async getValue(name) {
    const result = await this.getParameter({
      Name: name,
      WithDecryption: true
    });
    return result.Parameter?.Value;
  }
  async getAllByPath(path, recursive = true) {
    const allParams = [];
    let nextToken;
    do {
      const result = await this.getParametersByPath({
        Path: path,
        Recursive: recursive,
        WithDecryption: true,
        NextToken: nextToken
      });
      if (result.Parameters) {
        allParams.push(...result.Parameters);
      }
      nextToken = result.NextToken;
    } while (nextToken);
    return allParams;
  }
  parseParameter(p) {
    return {
      Name: p.Name,
      Type: p.Type,
      Value: p.Value,
      Version: p.Version,
      LastModifiedDate: p.LastModifiedDate,
      ARN: p.ARN,
      DataType: p.DataType,
      Description: p.Description,
      AllowedPattern: p.AllowedPattern,
      KeyId: p.KeyId,
      Tier: p.Tier
    };
  }
  async sendCommand(options) {
    if (!options.InstanceIds && !options.Targets) {
      throw new Error("SendCommand requires either InstanceIds or Targets");
    }
    const params = {
      DocumentName: options.DocumentName
    };
    if (options.InstanceIds) {
      params.InstanceIds = options.InstanceIds;
    }
    if (options.Targets) {
      params.Targets = options.Targets;
    }
    if (options.Parameters) {
      params.Parameters = options.Parameters;
    }
    if (options.TimeoutSeconds) {
      params.TimeoutSeconds = options.TimeoutSeconds;
    }
    if (options.Comment) {
      params.Comment = options.Comment;
    }
    if (options.OutputS3BucketName) {
      params.OutputS3BucketName = options.OutputS3BucketName;
    }
    if (options.OutputS3KeyPrefix) {
      params.OutputS3KeyPrefix = options.OutputS3KeyPrefix;
    }
    const result = await this.client.request({
      service: "ssm",
      region: this.region,
      method: "POST",
      path: "/",
      headers: {
        "X-Amz-Target": "AmazonSSM.SendCommand",
        "Content-Type": "application/x-amz-json-1.1"
      },
      body: JSON.stringify(params)
    });
    return {
      CommandId: result.Command?.CommandId,
      Status: result.Command?.Status,
      StatusDetails: result.Command?.StatusDetails
    };
  }
  async getCommandInvocation(options) {
    const params = {
      CommandId: options.CommandId,
      InstanceId: options.InstanceId
    };
    const result = await this.client.request({
      service: "ssm",
      region: this.region,
      method: "POST",
      path: "/",
      headers: {
        "X-Amz-Target": "AmazonSSM.GetCommandInvocation",
        "Content-Type": "application/x-amz-json-1.1"
      },
      body: JSON.stringify(params)
    });
    return {
      Status: result.Status,
      StatusDetails: result.StatusDetails,
      StandardOutputContent: result.StandardOutputContent,
      StandardErrorContent: result.StandardErrorContent,
      ResponseCode: result.ResponseCode
    };
  }
  async runShellCommand(instanceId, commands, options) {
    const sendResult = await this.sendCommand({
      InstanceIds: [instanceId],
      DocumentName: "AWS-RunShellScript",
      Parameters: {
        commands
      },
      TimeoutSeconds: options?.timeoutSeconds || 600
    });
    if (!sendResult.CommandId) {
      return { success: false, error: "Failed to send command" };
    }
    if (options?.waitForCompletion === false) {
      return { success: true, status: "Pending" };
    }
    const pollInterval = options?.pollIntervalMs || 2000;
    const maxWait = options?.maxWaitMs || 300000;
    const startTime = Date.now();
    while (Date.now() - startTime < maxWait) {
      await new Promise((resolve) => setTimeout(resolve, pollInterval));
      try {
        const invocation = await this.getCommandInvocation({
          CommandId: sendResult.CommandId,
          InstanceId: instanceId
        });
        if (invocation.Status === "Success") {
          return {
            success: true,
            output: invocation.StandardOutputContent,
            error: invocation.StandardErrorContent,
            status: invocation.Status
          };
        }
        if (invocation.Status === "Failed" || invocation.Status === "Cancelled" || invocation.Status === "TimedOut") {
          return {
            success: false,
            output: invocation.StandardOutputContent,
            error: invocation.StandardErrorContent || invocation.StatusDetails,
            status: invocation.Status
          };
        }
      } catch (e) {
        if (!e.message?.includes("InvocationDoesNotExist")) {
          return { success: false, error: e.message };
        }
      }
    }
    return { success: false, error: "Command timed out waiting for completion" };
  }
  async listCommandInvocations(options) {
    const params = {
      CommandId: options.CommandId
    };
    if (options.Details) {
      params.Details = true;
    }
    const result = await this.client.request({
      service: "ssm",
      region: this.region,
      method: "POST",
      path: "/",
      headers: {
        "X-Amz-Target": "AmazonSSM.ListCommandInvocations",
        "Content-Type": "application/x-amz-json-1.1"
      },
      body: JSON.stringify(params)
    });
    const invocations = result.CommandInvocations || [];
    return invocations.map((inv) => ({
      InstanceId: inv.InstanceId,
      Status: inv.Status,
      StatusDetails: inv.StatusDetails,
      StandardOutputContent: inv.CommandPlugins?.[0]?.Output,
      StandardErrorContent: inv.CommandPlugins?.[0]?.StandardErrorUrl
    }));
  }
  async sendCommandByTags(options) {
    const targets = Object.entries(options.tags).map(([key, value]) => ({
      Key: `tag:${key}`,
      Values: [value]
    }));
    const sendResult = await this.sendCommand({
      Targets: targets,
      DocumentName: "AWS-RunShellScript",
      Parameters: { commands: options.commands },
      TimeoutSeconds: options.timeoutSeconds || 600,
      Comment: options.comment
    });
    if (!sendResult.CommandId) {
      return { success: false, instanceCount: 0, perInstance: [], error: "Failed to send command" };
    }
    const pollInterval = options.pollIntervalMs || 3000;
    const maxWait = options.maxWaitMs || 600000;
    const startTime = Date.now();
    const terminalStatuses = new Set(["Success", "Failed", "Cancelled", "TimedOut"]);
    let lastInvocations = [];
    while (Date.now() - startTime < maxWait) {
      await new Promise((resolve) => setTimeout(resolve, pollInterval));
      try {
        lastInvocations = await this.listCommandInvocations({
          CommandId: sendResult.CommandId,
          Details: true
        });
        if (lastInvocations.length === 0)
          continue;
        const allDone = lastInvocations.every((inv) => terminalStatuses.has(inv.Status || ""));
        if (allDone) {
          const perInstance = lastInvocations.map((inv) => ({
            instanceId: inv.InstanceId,
            status: inv.Status || "Unknown",
            output: inv.StandardOutputContent,
            error: inv.StandardErrorContent || inv.StatusDetails
          }));
          const allSucceeded = lastInvocations.every((inv) => inv.Status === "Success");
          return {
            success: allSucceeded,
            instanceCount: lastInvocations.length,
            perInstance,
            error: allSucceeded ? undefined : "One or more instances reported a non-success status"
          };
        }
      } catch (e) {
        if (!e.message?.includes("InvocationDoesNotExist")) {
          return { success: false, instanceCount: 0, perInstance: [], error: e.message };
        }
      }
    }
    return {
      success: false,
      instanceCount: lastInvocations.length,
      perInstance: lastInvocations.map((inv) => ({
        instanceId: inv.InstanceId,
        status: inv.Status || "Unknown"
      })),
      error: "Command timed out waiting for all instances to complete"
    };
  }
}

export { EC2Client, SSMClient };
