// src/aws/client.ts
import * as crypto from "node:crypto";
import { existsSync, readFileSync } from "node:fs";
import { homedir } from "node:os";
import { join } from "node:path";

// ../../node_modules/@stacksjs/ts-xml/dist/index.js
var xmlEntities = {
  amp: "&",
  lt: "<",
  gt: ">",
  quot: '"',
  apos: "'"
};
var htmlEntities = {
  nbsp: " ",
  copy: "©",
  reg: "®",
  trade: "™",
  euro: "€",
  pound: "£",
  yen: "¥",
  cent: "¢",
  deg: "°",
  micro: "µ",
  middot: "·",
  bull: "•",
  hellip: "…",
  prime: "′",
  Prime: "″",
  laquo: "«",
  raquo: "»",
  lsquo: "‘",
  rsquo: "’",
  ldquo: "“",
  rdquo: "”",
  ndash: "–",
  mdash: "—",
  iexcl: "¡",
  iquest: "¿",
  sect: "§",
  para: "¶",
  dagger: "†",
  Dagger: "‡",
  lsaquo: "‹",
  rsaquo: "›",
  oline: "‾",
  frasl: "⁄",
  ensp: " ",
  emsp: " ",
  thinsp: " ",
  zwnj: "‌",
  zwj: "‍",
  lrm: "‎",
  rlm: "‏",
  times: "×",
  divide: "÷",
  plusmn: "±",
  ne: "≠",
  le: "≤",
  ge: "≥",
  infin: "∞",
  fnof: "ƒ",
  Alpha: "Α",
  Beta: "Β",
  Gamma: "Γ",
  Delta: "Δ",
  Epsilon: "Ε",
  Zeta: "Ζ",
  Eta: "Η",
  Theta: "Θ",
  Iota: "Ι",
  Kappa: "Κ",
  Lambda: "Λ",
  Mu: "Μ",
  Nu: "Ν",
  Xi: "Ξ",
  Omicron: "Ο",
  Pi: "Π",
  Rho: "Ρ",
  Sigma: "Σ",
  Tau: "Τ",
  Upsilon: "Υ",
  Phi: "Φ",
  Chi: "Χ",
  Psi: "Ψ",
  Omega: "Ω",
  alpha: "α",
  beta: "β",
  gamma: "γ",
  delta: "δ",
  epsilon: "ε",
  zeta: "ζ",
  eta: "η",
  theta: "θ",
  iota: "ι",
  kappa: "κ",
  lambda: "λ",
  mu: "μ",
  nu: "ν",
  xi: "ξ",
  omicron: "ο",
  pi: "π",
  rho: "ρ",
  sigmaf: "ς",
  sigma: "σ",
  tau: "τ",
  upsilon: "υ",
  phi: "φ",
  chi: "χ",
  psi: "ψ",
  omega: "ω",
  thetasym: "ϑ",
  upsih: "ϒ",
  piv: "ϖ",
  larr: "←",
  uarr: "↑",
  rarr: "→",
  darr: "↓",
  harr: "↔",
  crarr: "↵",
  lArr: "⇐",
  uArr: "⇑",
  rArr: "⇒",
  dArr: "⇓",
  hArr: "⇔",
  forall: "∀",
  part: "∂",
  exist: "∃",
  empty: "∅",
  nabla: "∇",
  isin: "∈",
  notin: "∉",
  ni: "∋",
  prod: "∏",
  sum: "∑",
  minus: "−",
  lowast: "∗",
  radic: "√",
  prop: "∝",
  ang: "∠",
  and: "∧",
  or: "∨",
  cap: "∩",
  cup: "∪",
  int: "∫",
  there4: "∴",
  sim: "∼",
  cong: "≅",
  asymp: "≈",
  equiv: "≡",
  sub: "⊂",
  sup: "⊃",
  nsub: "⊄",
  sube: "⊆",
  supe: "⊇",
  oplus: "⊕",
  otimes: "⊗",
  perp: "⊥",
  sdot: "⋅",
  lceil: "⌈",
  rceil: "⌉",
  lfloor: "⌊",
  rfloor: "⌋",
  lang: "〈",
  rang: "〉",
  loz: "◊",
  spades: "♠",
  clubs: "♣",
  hearts: "♥",
  diams: "♦",
  Agrave: "À",
  Aacute: "Á",
  Acirc: "Â",
  Atilde: "Ã",
  Auml: "Ä",
  Aring: "Å",
  AElig: "Æ",
  Ccedil: "Ç",
  Egrave: "È",
  Eacute: "É",
  Ecirc: "Ê",
  Euml: "Ë",
  Igrave: "Ì",
  Iacute: "Í",
  Icirc: "Î",
  Iuml: "Ï",
  ETH: "Ð",
  Ntilde: "Ñ",
  Ograve: "Ò",
  Oacute: "Ó",
  Ocirc: "Ô",
  Otilde: "Õ",
  Ouml: "Ö",
  Oslash: "Ø",
  Ugrave: "Ù",
  Uacute: "Ú",
  Ucirc: "Û",
  Uuml: "Ü",
  Yacute: "Ý",
  THORN: "Þ",
  szlig: "ß",
  agrave: "à",
  aacute: "á",
  acirc: "â",
  atilde: "ã",
  auml: "ä",
  aring: "å",
  aelig: "æ",
  ccedil: "ç",
  egrave: "è",
  eacute: "é",
  ecirc: "ê",
  euml: "ë",
  igrave: "ì",
  iacute: "í",
  icirc: "î",
  iuml: "ï",
  eth: "ð",
  ntilde: "ñ",
  ograve: "ò",
  oacute: "ó",
  ocirc: "ô",
  otilde: "õ",
  ouml: "ö",
  oslash: "ø",
  ugrave: "ù",
  uacute: "ú",
  ucirc: "û",
  uuml: "ü",
  yacute: "ý",
  thorn: "þ",
  yuml: "ÿ",
  OElig: "Œ",
  oelig: "œ",
  Scaron: "Š",
  scaron: "š",
  Yuml: "Ÿ",
  circ: "ˆ",
  tilde: "˜"
};

class EntityDecoder {
  customEntities = {};
  useHtmlEntities;
  constructor(useHtmlEntities = false) {
    this.useHtmlEntities = useHtmlEntities;
  }
  addEntity(name, value) {
    this.customEntities[name] = value;
  }
  decodeEntities(text) {
    let ampIdx = text.indexOf("&");
    if (ampIdx === -1)
      return text;
    const len = text.length;
    let result = "";
    let lastPos = 0;
    while (ampIdx !== -1 && ampIdx < len) {
      if (ampIdx > lastPos)
        result += text.substring(lastPos, ampIdx);
      const semiIdx = text.indexOf(";", ampIdx + 1);
      if (semiIdx === -1 || semiIdx - ampIdx > 32) {
        result += "&";
        lastPos = ampIdx + 1;
        ampIdx = text.indexOf("&", lastPos);
        continue;
      }
      const entity = text.substring(ampIdx + 1, semiIdx);
      if (entity.charCodeAt(0) === 35) {
        const elen = entity.length;
        let code = 0;
        let valid = false;
        if (elen > 2 && (entity.charCodeAt(1) === 120 || entity.charCodeAt(1) === 88)) {
          valid = true;
          for (let k = 2;k < elen; k++) {
            const d = entity.charCodeAt(k);
            if (d >= 48 && d <= 57)
              code = code << 4 | d - 48;
            else if (d >= 65 && d <= 70)
              code = code << 4 | d - 55;
            else if (d >= 97 && d <= 102)
              code = code << 4 | d - 87;
            else {
              valid = false;
              break;
            }
          }
        } else if (elen > 1) {
          valid = true;
          for (let k = 1;k < elen; k++) {
            const d = entity.charCodeAt(k);
            if (d >= 48 && d <= 57)
              code = code * 10 + (d - 48);
            else {
              valid = false;
              break;
            }
          }
        }
        if (valid) {
          result += String.fromCodePoint(code);
        } else {
          result += text.substring(ampIdx, semiIdx + 1);
        }
      } else {
        let replacement = this.customEntities[entity];
        if (replacement === undefined) {
          const elen = entity.length;
          if (elen >= 2 && elen <= 4) {
            const e0 = entity.charCodeAt(0);
            if (e0 === 108 && elen === 2 && entity.charCodeAt(1) === 116)
              replacement = "<";
            else if (e0 === 103 && elen === 2 && entity.charCodeAt(1) === 116)
              replacement = ">";
            else if (e0 === 97 && elen === 3 && entity.charCodeAt(1) === 109 && entity.charCodeAt(2) === 112)
              replacement = "&";
            else if (e0 === 113 && elen === 4)
              replacement = entity === "quot" ? '"' : undefined;
            else if (e0 === 97 && elen === 4)
              replacement = entity === "apos" ? "'" : undefined;
          }
          if (replacement === undefined)
            replacement = xmlEntities[entity] ?? (this.useHtmlEntities ? htmlEntities[entity] : undefined);
        }
        if (replacement !== undefined) {
          result += replacement;
        } else {
          result += text.substring(ampIdx, semiIdx + 1);
        }
      }
      lastPos = semiIdx + 1;
      ampIdx = text.indexOf("&", lastPos);
    }
    if (lastPos < len)
      result += text.substring(lastPos);
    return result;
  }
}
var _encodeMap = [];
_encodeMap[38] = "&amp;";
_encodeMap[60] = "&lt;";
_encodeMap[62] = "&gt;";
_encodeMap[34] = "&quot;";
_encodeMap[39] = "&apos;";
var defaultParserOptions = {
  attributeNamePrefix: "@_",
  attributesGroupName: false,
  textNodeName: "#text",
  ignoreAttributes: true,
  removeNSPrefix: false,
  allowBooleanAttributes: false,
  alwaysCreateTextNode: false,
  trimValues: true,
  parseTagValue: true,
  parseAttributeValue: false,
  processEntities: true,
  htmlEntities: false,
  commentPropName: false,
  cdataPropName: false,
  piPropName: false,
  preserveOrder: false,
  stopNodes: [],
  unpairedTags: [],
  isArray: undefined,
  tagValueProcessor: undefined,
  attributeValueProcessor: undefined,
  updateTag: undefined,
  numberParseOptions: {
    hex: true,
    leadingZeros: true,
    scientific: true
  },
  ignorePiTags: false,
  transformAttributeName: undefined,
  transformTagName: undefined,
  ignoreDeclaration: false
};
function isWhitespace(ch) {
  return ch === 32 || ch === 9 || ch === 10 || ch === 13;
}
function isNameStartChar(ch) {
  return ch >= 65 && ch <= 90 || ch >= 97 && ch <= 122 || ch === 95 || ch === 58 || ch >= 192 && ch <= 214 || ch >= 216 && ch <= 246 || ch >= 248 && ch <= 767 || ch >= 880 && ch <= 893 || ch >= 895 && ch <= 8191 || ch >= 8204 && ch <= 8205 || ch >= 8304 && ch <= 8591 || ch >= 11264 && ch <= 12271 || ch >= 12289 && ch <= 55295 || ch >= 63744 && ch <= 64975 || ch >= 65008 && ch <= 65533;
}
function isNameChar(ch) {
  return isNameStartChar(ch) || ch === 45 || ch === 46 || ch >= 48 && ch <= 57 || ch === 183 || ch >= 768 && ch <= 879 || ch >= 8255 && ch <= 8256;
}
var RE_HEX = /^0x[\da-fA-F]+$/;
var RE_SCIENTIFIC = /^[+-]?\d+(\.\d+)?[eE][+-]?\d+$/;
var RE_NUMBER = /^[+-]?(\d+\.?\d*|\.\d+)$/;
function isCDATA(xml, pos) {
  return xml.charCodeAt(pos) === 91 && xml.charCodeAt(pos + 1) === 67 && xml.charCodeAt(pos + 2) === 68 && xml.charCodeAt(pos + 3) === 65 && xml.charCodeAt(pos + 4) === 84 && xml.charCodeAt(pos + 5) === 65 && xml.charCodeAt(pos + 6) === 91;
}
function isDOCTYPE(xml, pos) {
  const c0 = xml.charCodeAt(pos);
  const c1 = xml.charCodeAt(pos + 1);
  const c2 = xml.charCodeAt(pos + 2);
  const c3 = xml.charCodeAt(pos + 3);
  const c4 = xml.charCodeAt(pos + 4);
  const c5 = xml.charCodeAt(pos + 5);
  const c6 = xml.charCodeAt(pos + 6);
  return (c0 === 68 || c0 === 100) && (c1 === 79 || c1 === 111) && (c2 === 67 || c2 === 99) && (c3 === 84 || c3 === 116) && (c4 === 89 || c4 === 121) && (c5 === 80 || c5 === 112) && (c6 === 69 || c6 === 101);
}
function readNameEnd(xml, pos, len) {
  if (pos >= len || !isNameStartChar(xml.charCodeAt(pos)))
    return pos;
  pos++;
  while (pos < len && isNameChar(xml.charCodeAt(pos)))
    pos++;
  return pos;
}

class XMLParser {
  options;
  entityDecoder;
  unpairedSet;
  exactStopNodes;
  wildcardStopSuffixes;
  needsJPath;
  _ignoreAttributes = true;
  _textNodeName = "#text";
  _commentPropName = false;
  _cdataPropName = false;
  _piPropName = false;
  _alwaysCreateTextNode = false;
  _trimValues = true;
  _processEntities = true;
  _parseTagValue = true;
  _parseAttributeValue = false;
  _removeNSPrefix = false;
  _attrPrefix = "@_";
  _attrsGroupName = false;
  _ignorePiTags = false;
  _ignoreDeclaration = false;
  constructor(options) {
    this.options = { ...defaultParserOptions, ...options };
    this.entityDecoder = new EntityDecoder(this.options.htmlEntities);
    this.unpairedSet = new Set(this.options.unpairedTags);
    this.exactStopNodes = new Set;
    this.wildcardStopSuffixes = [];
    for (const stopNode of this.options.stopNodes) {
      if (stopNode.startsWith("*.")) {
        this.wildcardStopSuffixes.push(stopNode.substring(2));
      } else {
        this.exactStopNodes.add(stopNode);
      }
    }
    this._ignoreAttributes = this.options.ignoreAttributes;
    this._textNodeName = this.options.textNodeName;
    this._commentPropName = this.options.commentPropName;
    this._cdataPropName = this.options.cdataPropName;
    this._piPropName = this.options.piPropName;
    this._alwaysCreateTextNode = this.options.alwaysCreateTextNode;
    this._trimValues = this.options.trimValues;
    this._processEntities = this.options.processEntities;
    this._parseTagValue = this.options.parseTagValue;
    this._parseAttributeValue = this.options.parseAttributeValue;
    this._removeNSPrefix = this.options.removeNSPrefix;
    this._attrPrefix = this.options.attributeNamePrefix;
    this._attrsGroupName = this.options.attributesGroupName;
    this._ignorePiTags = this.options.ignorePiTags;
    this._ignoreDeclaration = this.options.ignoreDeclaration;
    this.needsJPath = this.options.stopNodes.length > 0 || this.options.isArray !== undefined || this.options.updateTag !== undefined || this.options.tagValueProcessor !== undefined || this.options.attributeValueProcessor !== undefined;
  }
  addEntity(name, value) {
    this.entityDecoder.addEntity(name, value);
  }
  parse(xmlData) {
    if (xmlData instanceof Uint8Array) {
      xmlData = new TextDecoder().decode(xmlData);
    }
    const xml = xmlData;
    if (this.options.preserveOrder) {
      return this.parseOrdered(xml);
    }
    return this.parseUnordered(xml);
  }
  parseUnordered(xml) {
    const result = {};
    const len = xml.length;
    let i = 0;
    if (len > 0 && xml.charCodeAt(0) === 65279)
      i = 1;
    i = this.parseChildren(xml, i, len, result, "", []);
    return result;
  }
  parseOrdered(xml) {
    const len = xml.length;
    let i = 0;
    if (len > 0 && xml.charCodeAt(0) === 65279)
      i = 1;
    const result = [];
    this.parseChildrenOrdered(xml, i, len, result, "");
    return result;
  }
  readTagName(xml, pos, len) {
    const end = readNameEnd(xml, pos, len);
    return end;
  }
  extractTagName(xml, start, end) {
    let name = xml.substring(start, end);
    if (this._removeNSPrefix) {
      const colonIdx = name.indexOf(":");
      if (colonIdx !== -1)
        name = name.substring(colonIdx + 1);
    }
    if (this.options.transformTagName)
      name = this.options.transformTagName(name);
    return name;
  }
  parseAttributes(xml, pos, len, jPath, outAttrs) {
    let count = 0;
    const removeNS = this._removeNSPrefix;
    const transformAttr = this.options.transformAttributeName;
    const trimVals = this._trimValues;
    const processEnts = this._processEntities;
    const attrProcessor = this.options.attributeValueProcessor;
    while (pos < len) {
      let ch = xml.charCodeAt(pos);
      while (ch === 32 || ch === 9 || ch === 10 || ch === 13) {
        pos++;
        if (pos >= len)
          break;
        ch = xml.charCodeAt(pos);
      }
      if (pos >= len)
        break;
      if (ch === 62 || ch === 47)
        break;
      const attrStart = pos;
      if (!isNameStartChar(ch))
        break;
      pos++;
      while (pos < len && isNameChar(xml.charCodeAt(pos)))
        pos++;
      let attrName = xml.substring(attrStart, pos);
      if (removeNS) {
        const colonIdx = attrName.indexOf(":");
        if (colonIdx !== -1)
          attrName = attrName.substring(colonIdx + 1);
      }
      if (transformAttr)
        attrName = transformAttr(attrName);
      while (pos < len && isWhitespace(xml.charCodeAt(pos)))
        pos++;
      if (pos < len && xml.charCodeAt(pos) === 61) {
        pos++;
        while (pos < len && isWhitespace(xml.charCodeAt(pos)))
          pos++;
        if (pos >= len)
          break;
        const quoteChar = xml.charCodeAt(pos);
        if (quoteChar === 34 || quoteChar === 39) {
          pos++;
          const valueStart = pos;
          while (pos < len && xml.charCodeAt(pos) !== quoteChar)
            pos++;
          let value = xml.substring(valueStart, pos);
          if (trimVals && value.length > 0) {
            const f = value.charCodeAt(0);
            const l = value.charCodeAt(value.length - 1);
            if (f <= 32 || l <= 32)
              value = value.trim();
          }
          if (processEnts)
            value = this.entityDecoder.decodeEntities(value);
          if (attrProcessor)
            value = attrProcessor(attrName, value, jPath) ?? value;
          outAttrs[attrName] = value;
          count++;
          if (pos < len)
            pos++;
        }
      } else {
        outAttrs[attrName] = "true";
        count++;
      }
    }
    return count > 0 ? -pos - 1 : pos;
  }
  processTagValue(value, tagName, jPath, hasAttributes, isLeaf) {
    if (this._trimValues) {
      const vlen = value.length;
      if (vlen > 0) {
        const first = value.charCodeAt(0);
        const last = value.charCodeAt(vlen - 1);
        if (first <= 32 || last <= 32)
          value = value.trim();
      }
    }
    if (this._processEntities)
      value = this.entityDecoder.decodeEntities(value);
    if (this.options.tagValueProcessor) {
      const processed = this.options.tagValueProcessor(tagName, value, jPath, hasAttributes, isLeaf);
      if (processed !== undefined)
        value = processed;
    }
    if (!value)
      return value;
    if (this._parseTagValue)
      return this.parseValue(value);
    return value;
  }
  parseValue(val) {
    if (val === "true")
      return true;
    if (val === "false")
      return false;
    if (!val)
      return val;
    const firstCh = val.charCodeAt(0);
    const isNumStart = firstCh >= 48 && firstCh <= 57 || firstCh === 43 || firstCh === 45 || firstCh === 46;
    if (!isNumStart)
      return val;
    const opts = this.options.numberParseOptions;
    if (opts.skipLike?.test(val))
      return val;
    if (opts.hex && firstCh === 48 && RE_HEX.test(val))
      return Number.parseInt(val, 16);
    if (val.length > 1 && firstCh === 48 && val.charCodeAt(1) !== 46) {
      if (opts.leadingZeros)
        return val;
    }
    if (opts.scientific && RE_SCIENTIFIC.test(val)) {
      const num = Number(val);
      if (!Number.isNaN(num))
        return num;
    }
    if (RE_NUMBER.test(val)) {
      const num = Number(val);
      if (!Number.isNaN(num))
        return num;
    }
    return val;
  }
  parseAttributeValue(val) {
    if (!this._parseAttributeValue)
      return val;
    return this.parseValue(val);
  }
  isStopNode(jPath) {
    if (this.exactStopNodes.has(jPath))
      return true;
    for (const suffix of this.wildcardStopSuffixes) {
      if (jPath.endsWith(`.${suffix}`) || jPath === suffix)
        return true;
    }
    return false;
  }
  readStopNodeContent(xml, pos, len, tagName) {
    const closingTag = `</${tagName}`;
    const idx = xml.indexOf(closingTag, pos);
    if (idx === -1) {
      return { content: xml.substring(pos), pos: len };
    }
    const content = xml.substring(pos, idx);
    let endPos = idx + closingTag.length;
    while (endPos < len && xml.charCodeAt(endPos) !== 62)
      endPos++;
    if (endPos < len)
      endPos++;
    return { content, pos: endPos };
  }
  addToObj(obj, key, value, jPath, isLeaf, isAttribute) {
    const existing = obj[key];
    if (existing !== undefined) {
      if (!Array.isArray(existing)) {
        obj[key] = [existing, value];
      } else {
        existing.push(value);
      }
    } else if (this.options.isArray?.(key, jPath, isLeaf, isAttribute)) {
      obj[key] = [value];
    } else {
      obj[key] = value;
    }
  }
  flushText(textContent, cdataBuffer, jPath, parent) {
    if (!textContent && !cdataBuffer)
      return;
    const textNodeName = this._textNodeName;
    if (cdataBuffer) {
      let decodedText = textContent;
      if (decodedText && this._processEntities)
        decodedText = this.entityDecoder.decodeEntities(decodedText);
      let value = cdataBuffer + decodedText;
      if (this._trimValues && value.length > 0) {
        const f = value.charCodeAt(0);
        const l = value.charCodeAt(value.length - 1);
        if (f <= 32 || l <= 32)
          value = value.trim();
      }
      if (this.options.tagValueProcessor) {
        const processed = this.options.tagValueProcessor(textNodeName, value, jPath, false, true);
        if (processed !== undefined)
          value = processed;
      }
      if (value) {
        const parsed = this._parseTagValue ? this.parseValue(value) : value;
        if (parsed !== undefined && parsed !== "")
          this.addToObj(parent, textNodeName, parsed, jPath, true, false);
      }
    } else {
      const processed = this.processTagValue(textContent, textNodeName, jPath, false, true);
      if (processed !== undefined && processed !== "")
        this.addToObj(parent, textNodeName, processed, jPath, true, false);
    }
  }
  parseChildren(xml, pos, len, parent, jPath, unpairedStack) {
    let textContent = "";
    let cdataBuffer = "";
    const needsJPath = this.needsJPath;
    while (pos < len) {
      const ch = xml.charCodeAt(pos);
      if (ch !== 60) {
        const textStart = pos;
        pos++;
        while (pos < len && xml.charCodeAt(pos) !== 60)
          pos++;
        if (!textContent)
          textContent = xml.substring(textStart, pos);
        else
          textContent += xml.substring(textStart, pos);
        continue;
      }
      if (pos + 1 >= len)
        break;
      const nextCh = xml.charCodeAt(pos + 1);
      if (nextCh === 33 && pos + 3 < len && xml.charCodeAt(pos + 2) === 45 && xml.charCodeAt(pos + 3) === 45) {
        if (textContent || cdataBuffer) {
          this.flushText(textContent, cdataBuffer, jPath, parent);
          textContent = "";
          cdataBuffer = "";
        }
        pos += 4;
        const endComment = xml.indexOf("-->", pos);
        if (endComment === -1) {
          pos = len;
          break;
        }
        if (this._commentPropName) {
          const comment = xml.substring(pos, endComment);
          this.addToObj(parent, this._commentPropName, comment, jPath, true, false);
        }
        pos = endComment + 3;
        continue;
      }
      if (nextCh === 33 && pos + 8 < len && isCDATA(xml, pos + 2)) {
        pos += 9;
        const endCdata = xml.indexOf("]]>", pos);
        if (endCdata === -1) {
          pos = len;
          break;
        }
        const cdataContent = xml.substring(pos, endCdata);
        if (this._cdataPropName) {
          this.flushText(textContent, cdataBuffer, jPath, parent);
          textContent = "";
          cdataBuffer = "";
          this.addToObj(parent, this._cdataPropName, cdataContent, jPath, true, false);
        } else {
          if (textContent) {
            let decoded = textContent;
            if (this._processEntities)
              decoded = this.entityDecoder.decodeEntities(decoded);
            cdataBuffer += decoded;
            textContent = "";
          }
          cdataBuffer += cdataContent;
        }
        pos = endCdata + 3;
        continue;
      }
      if (nextCh === 33 && pos + 9 < len && isDOCTYPE(xml, pos + 2)) {
        pos += 9;
        let depth = 1;
        while (pos < len && depth > 0) {
          const c = xml.charCodeAt(pos);
          if (c === 60)
            depth++;
          else if (c === 62)
            depth--;
          pos++;
        }
        continue;
      }
      if (nextCh === 63) {
        pos += 2;
        const piNameStart = pos;
        while (pos < len && !isWhitespace(xml.charCodeAt(pos)) && xml.charCodeAt(pos) !== 63)
          pos++;
        const piTarget = xml.substring(piNameStart, pos);
        const endPi = xml.indexOf("?>", pos);
        if (endPi === -1) {
          pos = len;
          break;
        }
        const piContent = xml.substring(pos, endPi).trim();
        if (piTarget === "xml") {
          if (!this._ignoreDeclaration && this._piPropName) {
            const declAttrs = this.parsePiAttributes(piContent);
            this.addToObj(parent, this._piPropName, { [piTarget]: declAttrs }, jPath, true, false);
          }
        } else if (!this._ignorePiTags && this._piPropName) {
          this.addToObj(parent, this._piPropName, { [piTarget]: piContent }, jPath, true, false);
        }
        pos = endPi + 2;
        continue;
      }
      if (nextCh === 47) {
        if (textContent || cdataBuffer) {
          this.flushText(textContent, cdataBuffer, jPath, parent);
          textContent = "";
          cdataBuffer = "";
        }
        pos += 2;
        const nameStart2 = pos;
        const nameEnd2 = this.readTagName(xml, pos, len);
        const closingName = this.extractTagName(xml, nameStart2, nameEnd2);
        pos = nameEnd2;
        while (pos < len && isWhitespace(xml.charCodeAt(pos)))
          pos++;
        if (pos < len && xml.charCodeAt(pos) === 62)
          pos++;
        if (unpairedStack.length > 0 && unpairedStack[unpairedStack.length - 1] === closingName) {
          unpairedStack.pop();
          continue;
        }
        return pos;
      }
      if (textContent || cdataBuffer) {
        this.flushText(textContent, cdataBuffer, jPath, parent);
        textContent = "";
        cdataBuffer = "";
      }
      pos++;
      const nameStart = pos;
      const nameEnd = this.readTagName(xml, pos, len);
      const tagName = this.extractTagName(xml, nameStart, nameEnd);
      if (!tagName) {
        pos = nameEnd + 1;
        continue;
      }
      pos = nameEnd;
      const childJPath = needsJPath ? jPath ? `${jPath}.${tagName}` : tagName : "";
      let attrs = {};
      let hasAttrs = false;
      if (!this._ignoreAttributes) {
        const attrResult = this.parseAttributes(xml, pos, len, childJPath, attrs);
        if (attrResult < 0) {
          pos = -attrResult - 1;
          hasAttrs = true;
        } else {
          pos = attrResult;
        }
      } else {
        while (pos < len) {
          const c = xml.charCodeAt(pos);
          if (c === 62 || c === 47)
            break;
          if (c === 34 || c === 39) {
            pos++;
            while (pos < len && xml.charCodeAt(pos) !== c)
              pos++;
          }
          pos++;
        }
      }
      if (this.options.updateTag) {
        const newName = this.options.updateTag(tagName, childJPath, attrs);
        if (newName === false) {
          while (pos < len && isWhitespace(xml.charCodeAt(pos)))
            pos++;
          if (pos < len && xml.charCodeAt(pos) === 47) {
            pos += 2;
          } else if (pos < len && xml.charCodeAt(pos) === 62) {
            pos++;
            const closingTag = `</${tagName}>`;
            const closeIdx = xml.indexOf(closingTag, pos);
            if (closeIdx !== -1)
              pos = closeIdx + closingTag.length;
          }
          continue;
        }
      }
      while (pos < len && isWhitespace(xml.charCodeAt(pos)))
        pos++;
      if (pos < len && xml.charCodeAt(pos) === 47) {
        pos += 2;
        if (hasAttrs || this._alwaysCreateTextNode) {
          const childObj = {};
          if (hasAttrs)
            this.applyAttributes(childObj, attrs, childJPath);
          if (this._alwaysCreateTextNode)
            childObj[this._textNodeName] = "";
          this.addToObj(parent, tagName, childObj, childJPath, true, false);
        } else {
          this.addToObj(parent, tagName, "", childJPath, true, false);
        }
        continue;
      }
      if (pos < len && xml.charCodeAt(pos) === 62) {
        pos++;
        if (this.unpairedSet.has(tagName)) {
          if (hasAttrs) {
            const childObj2 = {};
            this.applyAttributes(childObj2, attrs, childJPath);
            this.addToObj(parent, tagName, childObj2, childJPath, true, false);
          } else {
            this.addToObj(parent, tagName, "", childJPath, true, false);
          }
          unpairedStack.push(tagName);
          continue;
        }
        if (this.exactStopNodes.size > 0 || this.wildcardStopSuffixes.length > 0) {
          if (this.isStopNode(childJPath)) {
            const stopResult = this.readStopNodeContent(xml, pos, len, tagName);
            const childObj2 = {};
            if (hasAttrs)
              this.applyAttributes(childObj2, attrs, childJPath);
            if (stopResult.content)
              childObj2[this._textNodeName] = stopResult.content;
            this.addToObj(parent, tagName, childObj2, childJPath, true, false);
            pos = stopResult.pos;
            continue;
          }
        }
        const childObj = {};
        if (hasAttrs)
          this.applyAttributes(childObj, attrs, childJPath);
        pos = this.parseChildren(xml, pos, len, childObj, childJPath, unpairedStack);
        const textNodeName = this._textNodeName;
        const textValue = childObj[textNodeName];
        if (!hasAttrs && !this._alwaysCreateTextNode) {
          let isLeaf = true;
          for (const k in childObj) {
            if (k !== textNodeName) {
              isLeaf = false;
              break;
            }
          }
          if (isLeaf) {
            this.addToObj(parent, tagName, textValue !== undefined ? textValue : "", childJPath, true, false);
            continue;
          }
        }
        this.addToObj(parent, tagName, childObj, childJPath, false, false);
      }
    }
    if (textContent || cdataBuffer) {
      this.flushText(textContent, cdataBuffer, jPath, parent);
    }
    return pos;
  }
  parseChildrenOrdered(xml, pos, len, parent, jPath) {
    let textContent = "";
    const needsJPath = this.needsJPath;
    while (pos < len) {
      const ch = xml.charCodeAt(pos);
      if (ch !== 60) {
        const textStart = pos;
        pos++;
        while (pos < len && xml.charCodeAt(pos) !== 60)
          pos++;
        if (!textContent)
          textContent = xml.substring(textStart, pos);
        else
          textContent += xml.substring(textStart, pos);
        continue;
      }
      if (pos + 1 >= len)
        break;
      const nextCh = xml.charCodeAt(pos + 1);
      if (nextCh === 33 && pos + 3 < len && xml.charCodeAt(pos + 2) === 45 && xml.charCodeAt(pos + 3) === 45) {
        if (textContent) {
          const processed = this.processTagValue(textContent, this._textNodeName, jPath, false, true);
          if (processed !== undefined && processed !== "")
            parent.push({ [this._textNodeName]: processed });
          textContent = "";
        }
        pos += 4;
        const endComment = xml.indexOf("-->", pos);
        if (endComment === -1) {
          pos = len;
          break;
        }
        if (this._commentPropName) {
          parent.push({ [this._commentPropName]: [{ [this._textNodeName]: xml.substring(pos, endComment) }] });
        }
        pos = endComment + 3;
        continue;
      }
      if (nextCh === 33 && pos + 8 < len && isCDATA(xml, pos + 2)) {
        pos += 9;
        const endCdata = xml.indexOf("]]>", pos);
        if (endCdata === -1) {
          pos = len;
          break;
        }
        const cdataContent = xml.substring(pos, endCdata);
        if (this._cdataPropName) {
          if (textContent) {
            const processed = this.processTagValue(textContent, this._textNodeName, jPath, false, true);
            if (processed !== undefined && processed !== "")
              parent.push({ [this._textNodeName]: processed });
            textContent = "";
          }
          parent.push({ [this._cdataPropName]: [{ [this._textNodeName]: cdataContent }] });
        } else {
          textContent += cdataContent;
        }
        pos = endCdata + 3;
        continue;
      }
      if (nextCh === 33 && pos + 9 < len && isDOCTYPE(xml, pos + 2)) {
        pos += 9;
        let depth = 1;
        while (pos < len && depth > 0) {
          const c = xml.charCodeAt(pos);
          if (c === 60)
            depth++;
          else if (c === 62)
            depth--;
          pos++;
        }
        continue;
      }
      if (nextCh === 63) {
        pos += 2;
        const endPi = xml.indexOf("?>", pos);
        if (endPi === -1) {
          pos = len;
          break;
        }
        pos = endPi + 2;
        continue;
      }
      if (nextCh === 47) {
        if (textContent) {
          const processed = this.processTagValue(textContent, this._textNodeName, jPath, false, true);
          if (processed !== undefined && processed !== "")
            parent.push({ [this._textNodeName]: processed });
          textContent = "";
        }
        pos += 2;
        const nameEnd2 = this.readTagName(xml, pos, len);
        pos = nameEnd2;
        while (pos < len && isWhitespace(xml.charCodeAt(pos)))
          pos++;
        if (pos < len && xml.charCodeAt(pos) === 62)
          pos++;
        return pos;
      }
      if (textContent) {
        const processed = this.processTagValue(textContent, this._textNodeName, jPath, false, true);
        if (processed !== undefined && processed !== "")
          parent.push({ [this._textNodeName]: processed });
        textContent = "";
      }
      pos++;
      const nameStart = pos;
      const nameEnd = this.readTagName(xml, pos, len);
      const tagName = this.extractTagName(xml, nameStart, nameEnd);
      if (!tagName) {
        pos = nameEnd + 1;
        continue;
      }
      pos = nameEnd;
      const childJPath = needsJPath ? jPath ? `${jPath}.${tagName}` : tagName : "";
      let attrs = {};
      let hasAttrs = false;
      if (!this._ignoreAttributes) {
        const attrResult = this.parseAttributes(xml, pos, len, childJPath, attrs);
        if (attrResult < 0) {
          pos = -attrResult - 1;
          hasAttrs = true;
        } else {
          pos = attrResult;
        }
      } else {
        while (pos < len) {
          const c = xml.charCodeAt(pos);
          if (c === 62 || c === 47)
            break;
          if (c === 34 || c === 39) {
            pos++;
            while (pos < len && xml.charCodeAt(pos) !== c)
              pos++;
          }
          pos++;
        }
      }
      while (pos < len && isWhitespace(xml.charCodeAt(pos)))
        pos++;
      const orderedNode = { [tagName]: [] };
      if (hasAttrs) {
        const attrKey = ":@";
        orderedNode[attrKey] = {};
        for (const name in attrs) {
          orderedNode[attrKey][this._attrPrefix + name] = this.parseAttributeValue(attrs[name]);
        }
      }
      if (pos < len && xml.charCodeAt(pos) === 47) {
        pos += 2;
        parent.push(orderedNode);
        continue;
      }
      if (pos < len && xml.charCodeAt(pos) === 62) {
        pos++;
        pos = this.parseChildrenOrdered(xml, pos, len, orderedNode[tagName], childJPath);
        parent.push(orderedNode);
      }
    }
    if (textContent) {
      const processed = this.processTagValue(textContent, this._textNodeName, jPath, false, true);
      if (processed !== undefined && processed !== "")
        parent.push({ [this._textNodeName]: processed });
    }
    return pos;
  }
  applyAttributes(obj, attrs, jPath) {
    const prefix = this._attrPrefix;
    if (this._attrsGroupName) {
      const group = {};
      for (const name in attrs) {
        group[prefix + name] = this.parseAttributeValue(attrs[name]);
      }
      obj[this._attrsGroupName] = group;
    } else {
      for (const name in attrs) {
        obj[prefix + name] = this.parseAttributeValue(attrs[name]);
      }
    }
  }
  parsePiAttributes(content) {
    const attrs = {};
    let i = 0;
    const len = content.length;
    while (i < len) {
      while (i < len && isWhitespace(content.charCodeAt(i)))
        i++;
      if (i >= len)
        break;
      const nameStart = i;
      while (i < len && !isWhitespace(content.charCodeAt(i)) && content.charCodeAt(i) !== 61)
        i++;
      const name = content.substring(nameStart, i);
      if (!name)
        break;
      while (i < len && isWhitespace(content.charCodeAt(i)))
        i++;
      if (i < len && content.charCodeAt(i) === 61) {
        i++;
        while (i < len && isWhitespace(content.charCodeAt(i)))
          i++;
        if (i < len) {
          const quote = content.charCodeAt(i);
          if (quote === 34 || quote === 39) {
            i++;
            const valueStart = i;
            while (i < len && content.charCodeAt(i) !== quote)
              i++;
            attrs[name] = content.substring(valueStart, i);
            if (i < len)
              i++;
          }
        }
      }
    }
    return attrs;
  }
}

// src/aws/client.ts
function resolveS3Endpoint(options) {
  const rawEndpoint = options.endpoint || `s3.${options.region}.amazonaws.com`;
  const parsed = new URL(/^https?:\/\//i.test(rawEndpoint) ? rawEndpoint : `https://${rawEndpoint}`);
  if (parsed.protocol !== "http:" && parsed.protocol !== "https:" || parsed.pathname !== "/" && parsed.pathname !== "" || parsed.search || parsed.hash || parsed.username || parsed.password) {
    throw new Error("S3 endpoint must be an HTTP(S) origin without a path");
  }
  const protocol = parsed.protocol.slice(0, -1);
  const base = parsed.host;
  if (!options.bucket) {
    return { protocol, host: base, path: options.path };
  }
  if (options.forcePathStyle) {
    const path = options.path === "/" ? `/${options.bucket}` : `/${options.bucket}${options.path}`;
    return { protocol, host: base, path };
  }
  return { protocol, host: `${options.bucket}.${base}`, path: options.path };
}

class AWSClient {
  credentials;
  config;
  cache;
  xmlParser;
  constructor(credentials, config) {
    this.config = {
      maxRetries: 3,
      retryDelay: 1000,
      cacheEnabled: true,
      defaultCacheTTL: 60000,
      ...config
    };
    this.credentials = credentials || this.loadCredentials();
    this.cache = new Map;
    this.xmlParser = new XMLParser({
      ignoreAttributes: false,
      attributeNamePrefix: "@_",
      textNodeName: "#text",
      parseAttributeValue: true,
      trimValues: true
    });
  }
  loadCredentials() {
    const accessKeyId = process.env.AWS_ACCESS_KEY_ID;
    const secretAccessKey = process.env.AWS_SECRET_ACCESS_KEY;
    const sessionToken = process.env.AWS_SESSION_TOKEN;
    if (accessKeyId && secretAccessKey) {
      return {
        accessKeyId,
        secretAccessKey,
        sessionToken
      };
    }
    const fileCredentials = this.loadCredentialsFromFile();
    if (fileCredentials) {
      return fileCredentials;
    }
    return {
      accessKeyId: "",
      secretAccessKey: ""
    };
  }
  loadCredentialsFromFile() {
    const profile = this.config?.profile || process.env.AWS_PROFILE || "default";
    const credentialsPath = process.env.AWS_SHARED_CREDENTIALS_FILE || join(homedir(), ".aws", "credentials");
    if (!existsSync(credentialsPath)) {
      return null;
    }
    try {
      const content = readFileSync(credentialsPath, "utf-8");
      const credentials = this.parseCredentialsFile(content, profile);
      if (credentials.accessKeyId && credentials.secretAccessKey) {
        return credentials;
      }
    } catch {}
    return null;
  }
  parseCredentialsFile(content, profile) {
    const lines = content.split(`
`);
    let currentProfile = "";
    let accessKeyId = "";
    let secretAccessKey = "";
    let sessionToken;
    for (const line of lines) {
      const trimmed = line.trim();
      if (!trimmed || trimmed.startsWith("#")) {
        continue;
      }
      const profileMatch = trimmed.match(/^\[([^\]]+)\]$/);
      if (profileMatch) {
        currentProfile = profileMatch[1];
        continue;
      }
      if (currentProfile === profile) {
        const [key, ...valueParts] = trimmed.split("=");
        const value = valueParts.join("=").trim();
        if (key.trim() === "aws_access_key_id") {
          accessKeyId = value;
        } else if (key.trim() === "aws_secret_access_key") {
          secretAccessKey = value;
        } else if (key.trim() === "aws_session_token") {
          sessionToken = value;
        }
      }
    }
    return { accessKeyId, secretAccessKey, sessionToken };
  }
  ec2CredentialsCache;
  async getCredentials() {
    const accessKeyId = process.env.AWS_ACCESS_KEY_ID;
    const secretAccessKey = process.env.AWS_SECRET_ACCESS_KEY;
    if (accessKeyId && secretAccessKey) {
      return {
        accessKeyId,
        secretAccessKey,
        sessionToken: process.env.AWS_SESSION_TOKEN
      };
    }
    const fileCredentials = this.loadCredentialsFromFile();
    if (fileCredentials) {
      return fileCredentials;
    }
    if (this.ec2CredentialsCache) {
      const now = Date.now();
      if (this.ec2CredentialsCache.expiration > now + 5 * 60 * 1000) {
        return this.ec2CredentialsCache.credentials;
      }
    }
    try {
      const tokenResponse = await fetch("http://169.254.169.254/latest/api/token", {
        method: "PUT",
        headers: {
          "X-aws-ec2-metadata-token-ttl-seconds": "21600"
        }
      });
      const token = await tokenResponse.text();
      const roleResponse = await fetch("http://169.254.169.254/latest/meta-data/iam/security-credentials/", {
        headers: { "X-aws-ec2-metadata-token": token }
      });
      const roleName = await roleResponse.text();
      const credsResponse = await fetch(`http://169.254.169.254/latest/meta-data/iam/security-credentials/${roleName}`, {
        headers: { "X-aws-ec2-metadata-token": token }
      });
      const credsData = await credsResponse.json();
      const credentials = {
        accessKeyId: credsData.AccessKeyId,
        secretAccessKey: credsData.SecretAccessKey,
        sessionToken: credsData.Token
      };
      this.ec2CredentialsCache = {
        credentials,
        expiration: new Date(credsData.Expiration).getTime()
      };
      return credentials;
    } catch (error) {
      throw new Error("AWS credentials not found. Set AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY environment variables, or run on an EC2 instance with an IAM role.");
    }
  }
  async request(options) {
    if (options.method === "GET" && this.config.cacheEnabled && options.cacheKey) {
      const cached = this.getFromCache(options.cacheKey);
      if (cached !== null) {
        return cached;
      }
    }
    const maxRetries = options.retries ?? this.config.maxRetries ?? 3;
    let lastError = null;
    for (let attempt = 0;attempt <= maxRetries; attempt++) {
      try {
        const result = await this.makeRequest(options);
        if (options.method === "GET" && this.config.cacheEnabled && options.cacheKey) {
          this.setInCache(options.cacheKey, result, options.cacheTTL ?? this.config.defaultCacheTTL ?? 60000);
        }
        return result;
      } catch (error) {
        lastError = error;
        const isRetryable = this.shouldRetry(error);
        if (!isRetryable) {
          throw error;
        }
        if (attempt === maxRetries) {
          throw error;
        }
        const delay = this.calculateRetryDelay(attempt);
        await this.sleep(delay);
      }
    }
    throw lastError || new Error("Request failed after retries");
  }
  async makeRequest(options) {
    const credentials = options.credentials || (this.credentials?.accessKeyId ? this.credentials : await this.getCredentials());
    if (!credentials || !credentials.accessKeyId || !credentials.secretAccessKey) {
      throw new Error("AWS credentials not provided");
    }
    const url = this.buildUrl(options);
    const headers = this.signRequest(options, credentials);
    const response = await fetch(url, {
      method: options.method,
      headers,
      body: options.body
    });
    const responseText = await response.text();
    if (!response.ok) {
      throw this.parseError(responseText, response.status, response.headers);
    }
    if (!responseText || responseText.trim() === "") {
      if (options.returnHeaders) {
        return { body: null, headers: this.headersToObject(response.headers) };
      }
      return null;
    }
    if (options.rawResponse) {
      if (options.returnHeaders) {
        return { body: responseText, headers: this.headersToObject(response.headers) };
      }
      return responseText;
    }
    let body;
    if (responseText.startsWith("<")) {
      body = this.parseXmlResponse(responseText);
    } else {
      try {
        body = JSON.parse(responseText);
      } catch {
        body = responseText;
      }
    }
    if (options.returnHeaders) {
      return { body, headers: this.headersToObject(response.headers) };
    }
    return body;
  }
  headersToObject(headers) {
    const result = {};
    headers.forEach((value, key) => {
      result[key] = value;
    });
    return result;
  }
  buildUrl(options) {
    const { service, region, queryParams } = options;
    let { path } = options;
    let host;
    let protocol = "https";
    if (service === "s3") {
      const resolved = resolveS3Endpoint({
        region,
        path,
        bucket: options.bucket,
        endpoint: this.config.endpoint,
        forcePathStyle: this.config.forcePathStyle
      });
      protocol = resolved.protocol;
      host = resolved.host;
      path = resolved.path;
    } else if (service === "cloudfront") {
      host = "cloudfront.amazonaws.com";
    } else if (service === "iam") {
      host = "iam.amazonaws.com";
    } else if (service === "route53") {
      host = "route53.amazonaws.com";
    } else if (service === "route53domains") {
      host = "route53domains.us-east-1.amazonaws.com";
    } else if (service === "ecr") {
      host = `api.ecr.${region}.amazonaws.com`;
    } else if (service === "ses") {
      host = `email.${region}.amazonaws.com`;
    } else {
      host = `${service}.${region}.amazonaws.com`;
    }
    let url = `${protocol}://${host}${path}`;
    if (queryParams && Object.keys(queryParams).length > 0) {
      const queryString = Object.keys(queryParams).map((k) => `${this.uriEncode(k)}=${this.uriEncode(queryParams[k])}`).join("&");
      url += `?${queryString}`;
    }
    return url;
  }
  signRequest(options, credentials) {
    const { service, region, method, queryParams, body } = options;
    let { path } = options;
    const now = new Date;
    const amzDate = this.getAmzDate(now);
    const dateStamp = this.getDateStamp(now);
    let host;
    if (service === "s3") {
      const resolved = resolveS3Endpoint({
        region,
        path,
        bucket: options.bucket,
        endpoint: this.config.endpoint,
        forcePathStyle: this.config.forcePathStyle
      });
      host = resolved.host;
      path = resolved.path;
    } else if (service === "cloudfront") {
      host = "cloudfront.amazonaws.com";
    } else if (service === "iam") {
      host = "iam.amazonaws.com";
    } else if (service === "route53") {
      host = "route53.amazonaws.com";
    } else if (service === "route53domains") {
      host = "route53domains.us-east-1.amazonaws.com";
    } else if (service === "ecr") {
      host = `api.ecr.${region}.amazonaws.com`;
    } else if (service === "ses") {
      host = `email.${region}.amazonaws.com`;
    } else {
      host = `${service}.${region}.amazonaws.com`;
    }
    const headers = {
      host,
      "x-amz-date": amzDate,
      ...options.headers || {}
    };
    if (credentials.sessionToken) {
      headers["x-amz-security-token"] = credentials.sessionToken;
    }
    if (body) {
      const hasContentType = Object.keys(headers).some((k) => k.toLowerCase() === "content-type");
      if (!hasContentType) {
        headers["content-type"] = "application/x-www-form-urlencoded";
      }
      headers["content-length"] = Buffer.byteLength(body).toString();
    }
    const payloadHash = this.sha256(body || "");
    headers["x-amz-content-sha256"] = payloadHash;
    const canonicalUri = path;
    const canonicalQueryString = queryParams ? Object.keys(queryParams).sort().map((k) => `${this.uriEncode(k)}=${this.uriEncode(queryParams[k])}`).join("&") : "";
    const sortedHeaderKeys = Object.keys(headers).sort((a, b) => a.toLowerCase().localeCompare(b.toLowerCase()));
    const canonicalHeaders = sortedHeaderKeys.map((key) => `${key.toLowerCase()}:${headers[key].trim()}
`).join("");
    const signedHeaders = sortedHeaderKeys.map((key) => key.toLowerCase()).join(";");
    const canonicalRequest = [
      method,
      canonicalUri,
      canonicalQueryString,
      canonicalHeaders,
      signedHeaders,
      payloadHash
    ].join(`
`);
    const algorithm = "AWS4-HMAC-SHA256";
    let signingService = service;
    if (service === "email")
      signingService = "ses";
    const credentialScope = `${dateStamp}/${region}/${signingService}/aws4_request`;
    const stringToSign = [algorithm, amzDate, credentialScope, this.sha256(canonicalRequest)].join(`
`);
    const signingKey = this.getSignatureKey(credentials.secretAccessKey, dateStamp, region, signingService);
    const signature = this.hmac(signingKey, stringToSign);
    const authorizationHeader = `${algorithm} Credential=${credentials.accessKeyId}/${credentialScope}, SignedHeaders=${signedHeaders}, Signature=${signature}`;
    return {
      ...headers,
      Authorization: authorizationHeader
    };
  }
  getAmzDate(date) {
    return date.toISOString().replace(/[:-]|\.\d{3}/g, "");
  }
  getDateStamp(date) {
    return date.toISOString().slice(0, 10).replace(/-/g, "");
  }
  uriEncode(str) {
    return encodeURIComponent(str).replace(/[!'()*]/g, (c) => `%${c.charCodeAt(0).toString(16).toUpperCase()}`);
  }
  sha256(data) {
    return crypto.createHash("sha256").update(data, "utf8").digest("hex");
  }
  hmac(key, data) {
    return crypto.createHmac("sha256", key).update(data, "utf8").digest("hex");
  }
  getSignatureKey(key, dateStamp, region, service) {
    const kDate = crypto.createHmac("sha256", `AWS4${key}`).update(dateStamp).digest();
    const kRegion = crypto.createHmac("sha256", kDate).update(region).digest();
    const kService = crypto.createHmac("sha256", kRegion).update(service).digest();
    const kSigning = crypto.createHmac("sha256", kService).update("aws4_request").digest();
    return kSigning;
  }
  parseXmlResponse(xml) {
    try {
      const parsed = this.xmlParser.parse(xml);
      if (parsed.ErrorResponse) {
        throw this.createErrorFromXml(parsed.ErrorResponse.Error);
      }
      const keys = Object.keys(parsed);
      if (keys.length === 1) {
        return parsed[keys[0]];
      }
      return parsed;
    } catch (error) {
      if (error.code || error.statusCode) {
        throw error;
      }
      return {};
    }
  }
  parseError(responseText, statusCode, headers) {
    const error = new Error;
    error.statusCode = statusCode;
    error.requestId = headers.get("x-amzn-requestid") || headers.get("x-amz-request-id") || undefined;
    if (responseText.startsWith("<")) {
      try {
        const parsed = this.xmlParser.parse(responseText);
        if (parsed.ErrorResponse?.Error) {
          const awsError = parsed.ErrorResponse.Error;
          error.code = awsError.Code;
          error.message = `AWS Error [${awsError.Code}]: ${awsError.Message || "Unknown error"}`;
          error.type = awsError.Type;
          return error;
        }
        if (parsed.Error) {
          error.code = parsed.Error.Code;
          error.message = `AWS Error [${parsed.Error.Code}]: ${parsed.Error.Message || "Unknown error"}`;
          return error;
        }
      } catch {}
    }
    try {
      const json = JSON.parse(responseText);
      if (json.__type || json.code) {
        error.code = json.__type || json.code;
        error.message = `AWS Error [${error.code}]: ${json.message || json.Message || "Unknown error"}`;
        return error;
      }
    } catch {}
    error.message = `AWS API Error (${statusCode}): ${responseText}`;
    return error;
  }
  createErrorFromXml(errorData) {
    const error = new Error;
    error.code = errorData.Code;
    error.message = `AWS Error [${errorData.Code}]: ${errorData.Message || "Unknown error"}`;
    error.type = errorData.Type;
    return error;
  }
  isRetryableError(code) {
    const retryableCodes = [
      "RequestTimeout",
      "RequestTimeoutException",
      "PriorRequestNotComplete",
      "ConnectionError",
      "ThrottlingException",
      "Throttling",
      "TooManyRequestsException",
      "ProvisionedThroughputExceededException",
      "RequestLimitExceeded",
      "BandwidthLimitExceeded",
      "SlowDown",
      "ServiceUnavailable",
      "ServiceUnavailableException",
      "InternalError",
      "InternalFailure",
      "InternalServerError",
      "InternalServiceException",
      "TransientError",
      "TransientFailure",
      "IDPCommunicationErrorException",
      "NetworkError",
      "ConnectionRefusedException",
      "EC2ThrottledException",
      "503 Slow Down",
      "500 InternalError"
    ];
    return retryableCodes.includes(code);
  }
  isRetryableStatusCode(statusCode) {
    return statusCode >= 500 || statusCode === 429;
  }
  shouldRetry(error) {
    if (error.name === "FetchError" || error.name === "TypeError" || error.code === "ECONNRESET") {
      return true;
    }
    if (error.code && this.isRetryableError(error.code)) {
      return true;
    }
    if (error.statusCode && this.isRetryableStatusCode(error.statusCode)) {
      return true;
    }
    if (error.statusCode && error.statusCode >= 400 && error.statusCode < 500 && error.statusCode !== 429) {
      return false;
    }
    if (error.statusCode) {
      return false;
    }
    return true;
  }
  calculateRetryDelay(attempt) {
    const baseDelay = this.config.retryDelay ?? 1000;
    const maxDelay = 30000;
    const delay = Math.min(baseDelay * 2 ** attempt, maxDelay);
    const jitter = Math.random() * 0.3 * delay;
    return delay + jitter;
  }
  sleep(ms) {
    return new Promise((resolve) => setTimeout(resolve, ms));
  }
  getFromCache(key) {
    const entry = this.cache.get(key);
    if (!entry) {
      return null;
    }
    if (Date.now() > entry.expires) {
      this.cache.delete(key);
      return null;
    }
    return entry.data;
  }
  setInCache(key, data, ttl) {
    this.cache.set(key, {
      data,
      expires: Date.now() + ttl
    });
  }
  clearCache() {
    this.cache.clear();
  }
  clearExpiredCache() {
    const now = Date.now();
    for (const [key, entry] of this.cache.entries()) {
      if (now > entry.expires) {
        this.cache.delete(key);
      }
    }
  }
}
function buildQueryParams(params) {
  const result = {};
  for (const [key, value] of Object.entries(params)) {
    if (value === undefined || value === null) {
      continue;
    }
    if (Array.isArray(value)) {
      value.forEach((item, index) => {
        result[`${key}.${index + 1}`] = String(item);
      });
    } else if (typeof value === "object") {
      for (const [subKey, subValue] of Object.entries(value)) {
        result[`${key}.${subKey}`] = String(subValue);
      }
    } else {
      result[key] = String(value);
    }
  }
  return result;
}
function detectCredentialSource() {
  const accessKeyId = process.env.AWS_ACCESS_KEY_ID;
  const secretAccessKey = process.env.AWS_SECRET_ACCESS_KEY;
  const emptyEnvKey = accessKeyId === "";
  const emptyEnvSecret = secretAccessKey === "";
  if (accessKeyId && secretAccessKey) {
    return {
      source: "env",
      emptyEnvKey,
      emptyEnvSecret,
      description: "environment variables (AWS_ACCESS_KEY_ID + AWS_SECRET_ACCESS_KEY)"
    };
  }
  const profile = process.env.AWS_PROFILE || "default";
  const credentialsPath = process.env.AWS_SHARED_CREDENTIALS_FILE || join(homedir(), ".aws", "credentials");
  if (existsSync(credentialsPath)) {
    return {
      source: "file",
      profile,
      emptyEnvKey,
      emptyEnvSecret,
      description: `~/.aws/credentials (profile: ${profile})`
    };
  }
  return {
    source: "ec2",
    emptyEnvKey,
    emptyEnvSecret,
    description: "EC2 instance metadata service (IMDSv2)"
  };
}

export { resolveS3Endpoint, AWSClient, buildQueryParams, detectCredentialSource };
