UNPKG

peerpigeon

Version:

WebRTC-based peer-to-peer mesh networking library with intelligent routing and signaling server

13,323 lines 530 kB
// PeerPigeon Browser Bundle - includes UnSEA crypto library
// Generated automatically - do not edit directly

var PeerPigeon = (() => {
  var __create = Object.create;
  var __defProp = Object.defineProperty;
  var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
  var __getOwnPropNames = Object.getOwnPropertyNames;
  var __getProtoOf = Object.getPrototypeOf;
  var __hasOwnProp = Object.prototype.hasOwnProperty;
  var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
  var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
    get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
  }) : x)(function(x) {
    if (typeof require !== "undefined") return require.apply(this, arguments);
    throw Error('Dynamic require of "' + x + '" is not supported');
  });
  var __esm = (fn, res) => function __init() {
    return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
  };
  var __commonJS = (cb, mod2) => function __require2() {
    return mod2 || (0, cb[__getOwnPropNames(cb)[0]])((mod2 = { exports: {} }).exports, mod2), mod2.exports;
  };
  var __export = (target, all) => {
    for (var name in all)
      __defProp(target, name, { get: all[name], enumerable: true });
  };
  var __copyProps = (to, from, except, desc) => {
    if (from && typeof from === "object" || typeof from === "function") {
      for (let key of __getOwnPropNames(from))
        if (!__hasOwnProp.call(to, key) && key !== except)
          __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
    }
    return to;
  };
  var __toESM = (mod2, isNodeMode, target) => (target = mod2 != null ? __create(__getProtoOf(mod2)) : {}, __copyProps(
    // If the importer is in node compatibility mode or this is not an ESM
    // file that has been converted to a CommonJS file using a Babel-
    // compatible transform (i.e. "__esModule" has not been set), then set
    // "default" to the CommonJS "module.exports" for node compatibility.
    isNodeMode || !mod2 || !mod2.__esModule ? __defProp(target, "default", { value: mod2, enumerable: true }) : target,
    mod2
  ));
  var __toCommonJS = (mod2) => __copyProps(__defProp({}, "__esModule", { value: true }), mod2);
  var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);

  // node_modules/unsea/dist/unsea.mjs
  var unsea_exports = {};
  __export(unsea_exports, {
    SECURITY_CONFIG: () => SECURITY_CONFIG,
    clearKeys: () => clearKeys,
    decryptMessageWithMeta: () => decryptMessageWithMeta,
    encryptMessageWithMeta: () => encryptMessageWithMeta,
    exportToJWK: () => exportToJWK,
    exportToPEM: () => exportToPEM,
    generateRandomPair: () => generateRandomPair,
    generateSignedWork: () => generateSignedWork,
    generateWork: () => generateWork,
    getSecurityInfo: () => getSecurityInfo,
    importFromJWK: () => importFromJWK,
    importFromPEM: () => importFromPEM,
    loadKeys: () => loadKeys,
    saveKeys: () => saveKeys,
    signMessage: () => signMessage,
    verifyMessage: () => verifyMessage,
    verifySignedWork: () => verifySignedWork,
    verifyWork: () => verifyWork
  });
  function isBytes(a) {
    return a instanceof Uint8Array || ArrayBuffer.isView(a) && a.constructor.name === "Uint8Array";
  }
  function anumber(n) {
    if (!Number.isSafeInteger(n) || n < 0)
      throw new Error("positive integer expected, got " + n);
  }
  function abytes(b, ...lengths) {
    if (!isBytes(b))
      throw new Error("Uint8Array expected");
    if (lengths.length > 0 && !lengths.includes(b.length))
      throw new Error("Uint8Array expected of length " + lengths + ", got length=" + b.length);
  }
  function ahash(h) {
    if (typeof h !== "function" || typeof h.create !== "function")
      throw new Error("Hash should be wrapped by utils.createHasher");
    anumber(h.outputLen);
    anumber(h.blockLen);
  }
  function aexists(instance, checkFinished = true) {
    if (instance.destroyed)
      throw new Error("Hash instance has been destroyed");
    if (checkFinished && instance.finished)
      throw new Error("Hash#digest() has already been called");
  }
  function aoutput(out, instance) {
    abytes(out);
    const min = instance.outputLen;
    if (out.length < min) {
      throw new Error("digestInto() expects output buffer of length at least " + min);
    }
  }
  function clean(...arrays) {
    for (let i = 0; i < arrays.length; i++) {
      arrays[i].fill(0);
    }
  }
  function createView(arr) {
    return new DataView(arr.buffer, arr.byteOffset, arr.byteLength);
  }
  function rotr(word, shift) {
    return word << 32 - shift | word >>> shift;
  }
  function bytesToHex(bytes) {
    abytes(bytes);
    if (hasHexBuiltin)
      return bytes.toHex();
    let hex = "";
    for (let i = 0; i < bytes.length; i++) {
      hex += hexes[bytes[i]];
    }
    return hex;
  }
  function asciiToBase16(ch) {
    if (ch >= asciis._0 && ch <= asciis._9)
      return ch - asciis._0;
    if (ch >= asciis.A && ch <= asciis.F)
      return ch - (asciis.A - 10);
    if (ch >= asciis.a && ch <= asciis.f)
      return ch - (asciis.a - 10);
    return;
  }
  function hexToBytes(hex) {
    if (typeof hex !== "string")
      throw new Error("hex string expected, got " + typeof hex);
    if (hasHexBuiltin)
      return Uint8Array.fromHex(hex);
    const hl = hex.length;
    const al = hl / 2;
    if (hl % 2)
      throw new Error("hex string expected, got unpadded hex of length " + hl);
    const array = new Uint8Array(al);
    for (let ai = 0, hi = 0; ai < al; ai++, hi += 2) {
      const n1 = asciiToBase16(hex.charCodeAt(hi));
      const n2 = asciiToBase16(hex.charCodeAt(hi + 1));
      if (n1 === void 0 || n2 === void 0) {
        const char = hex[hi] + hex[hi + 1];
        throw new Error('hex string expected, got non-hex character "' + char + '" at index ' + hi);
      }
      array[ai] = n1 * 16 + n2;
    }
    return array;
  }
  function utf8ToBytes(str) {
    if (typeof str !== "string")
      throw new Error("string expected");
    return new Uint8Array(new TextEncoder().encode(str));
  }
  function toBytes(data) {
    if (typeof data === "string")
      data = utf8ToBytes(data);
    abytes(data);
    return data;
  }
  function concatBytes(...arrays) {
    let sum = 0;
    for (let i = 0; i < arrays.length; i++) {
      const a = arrays[i];
      abytes(a);
      sum += a.length;
    }
    const res = new Uint8Array(sum);
    for (let i = 0, pad = 0; i < arrays.length; i++) {
      const a = arrays[i];
      res.set(a, pad);
      pad += a.length;
    }
    return res;
  }
  function createHasher(hashCons) {
    const hashC = (msg) => hashCons().update(toBytes(msg)).digest();
    const tmp = hashCons();
    hashC.outputLen = tmp.outputLen;
    hashC.blockLen = tmp.blockLen;
    hashC.create = () => hashCons();
    return hashC;
  }
  function randomBytes(bytesLength = 32) {
    if (crypto2 && typeof crypto2.getRandomValues === "function") {
      return crypto2.getRandomValues(new Uint8Array(bytesLength));
    }
    if (crypto2 && typeof crypto2.randomBytes === "function") {
      return Uint8Array.from(crypto2.randomBytes(bytesLength));
    }
    throw new Error("crypto.getRandomValues must be defined");
  }
  function abool(title, value) {
    if (typeof value !== "boolean")
      throw new Error(title + " boolean expected, got " + value);
  }
  function numberToHexUnpadded(num) {
    const hex = num.toString(16);
    return hex.length & 1 ? "0" + hex : hex;
  }
  function hexToNumber(hex) {
    if (typeof hex !== "string")
      throw new Error("hex string expected, got " + typeof hex);
    return hex === "" ? _0n$3 : BigInt("0x" + hex);
  }
  function bytesToNumberBE(bytes) {
    return hexToNumber(bytesToHex(bytes));
  }
  function bytesToNumberLE(bytes) {
    abytes(bytes);
    return hexToNumber(bytesToHex(Uint8Array.from(bytes).reverse()));
  }
  function numberToBytesBE(n, len) {
    return hexToBytes(n.toString(16).padStart(len * 2, "0"));
  }
  function numberToBytesLE(n, len) {
    return numberToBytesBE(n, len).reverse();
  }
  function ensureBytes(title, hex, expectedLength) {
    let res;
    if (typeof hex === "string") {
      try {
        res = hexToBytes(hex);
      } catch (e) {
        throw new Error(title + " must be hex string or Uint8Array, cause: " + e);
      }
    } else if (isBytes(hex)) {
      res = Uint8Array.from(hex);
    } else {
      throw new Error(title + " must be hex string or Uint8Array");
    }
    res.length;
    return res;
  }
  function inRange(n, min, max) {
    return isPosBig(n) && isPosBig(min) && isPosBig(max) && min <= n && n < max;
  }
  function aInRange(title, n, min, max) {
    if (!inRange(n, min, max))
      throw new Error("expected valid " + title + ": " + min + " <= n < " + max + ", got " + n);
  }
  function bitLen(n) {
    let len;
    for (len = 0; n > _0n$3; n >>= _1n$3, len += 1)
      ;
    return len;
  }
  function createHmacDrbg(hashLen, qByteLen, hmacFn) {
    if (typeof hashLen !== "number" || hashLen < 2)
      throw new Error("hashLen must be a number");
    if (typeof qByteLen !== "number" || qByteLen < 2)
      throw new Error("qByteLen must be a number");
    if (typeof hmacFn !== "function")
      throw new Error("hmacFn must be a function");
    const u8n = (len) => new Uint8Array(len);
    const u8of = (byte) => Uint8Array.of(byte);
    let v = u8n(hashLen);
    let k = u8n(hashLen);
    let i = 0;
    const reset = () => {
      v.fill(1);
      k.fill(0);
      i = 0;
    };
    const h = (...b) => hmacFn(k, v, ...b);
    const reseed = (seed = u8n(0)) => {
      k = h(u8of(0), seed);
      v = h();
      if (seed.length === 0)
        return;
      k = h(u8of(1), seed);
      v = h();
    };
    const gen = () => {
      if (i++ >= 1e3)
        throw new Error("drbg: tried 1000 values");
      let len = 0;
      const out = [];
      while (len < qByteLen) {
        v = h();
        const sl = v.slice();
        out.push(sl);
        len += v.length;
      }
      return concatBytes(...out);
    };
    const genUntil = (seed, pred) => {
      reset();
      reseed(seed);
      let res = void 0;
      while (!(res = pred(gen())))
        reseed();
      reset();
      return res;
    };
    return genUntil;
  }
  function _validateObject(object, fields, optFields = {}) {
    if (!object || typeof object !== "object")
      throw new Error("expected valid options object");
    function checkField(fieldName, expectedType, isOpt) {
      const val = object[fieldName];
      if (isOpt && val === void 0)
        return;
      const current = typeof val;
      if (current !== expectedType || val === null)
        throw new Error(`param "${fieldName}" is invalid: expected ${expectedType}, got ${current}`);
    }
    Object.entries(fields).forEach(([k, v]) => checkField(k, v, false));
    Object.entries(optFields).forEach(([k, v]) => checkField(k, v, true));
  }
  function memoized(fn) {
    const map = /* @__PURE__ */ new WeakMap();
    return (arg, ...args) => {
      const val = map.get(arg);
      if (val !== void 0)
        return val;
      const computed = fn(arg, ...args);
      map.set(arg, computed);
      return computed;
    };
  }
  function mod(a, b) {
    const result = a % b;
    return result >= _0n$2 ? result : b + result;
  }
  function invert(number, modulo) {
    if (number === _0n$2)
      throw new Error("invert: expected non-zero number");
    if (modulo <= _0n$2)
      throw new Error("invert: expected positive modulus, got " + modulo);
    let a = mod(number, modulo);
    let b = modulo;
    let x = _0n$2, u = _1n$2;
    while (a !== _0n$2) {
      const q = b / a;
      const r = b % a;
      const m = x - u * q;
      b = a, a = r, x = u, u = m;
    }
    const gcd = b;
    if (gcd !== _1n$2)
      throw new Error("invert: does not exist");
    return mod(x, modulo);
  }
  function assertIsSquare(Fp, root, n) {
    if (!Fp.eql(Fp.sqr(root), n))
      throw new Error("Cannot find square root");
  }
  function sqrt3mod4(Fp, n) {
    const p1div4 = (Fp.ORDER + _1n$2) / _4n$1;
    const root = Fp.pow(n, p1div4);
    assertIsSquare(Fp, root, n);
    return root;
  }
  function sqrt5mod8(Fp, n) {
    const p5div8 = (Fp.ORDER - _5n) / _8n;
    const n2 = Fp.mul(n, _2n$1);
    const v = Fp.pow(n2, p5div8);
    const nv = Fp.mul(n, v);
    const i = Fp.mul(Fp.mul(nv, _2n$1), v);
    const root = Fp.mul(nv, Fp.sub(i, Fp.ONE));
    assertIsSquare(Fp, root, n);
    return root;
  }
  function sqrt9mod16(P) {
    const Fp_ = Field(P);
    const tn = tonelliShanks(P);
    const c1 = tn(Fp_, Fp_.neg(Fp_.ONE));
    const c2 = tn(Fp_, c1);
    const c3 = tn(Fp_, Fp_.neg(c1));
    const c4 = (P + _7n) / _16n;
    return (Fp, n) => {
      let tv1 = Fp.pow(n, c4);
      let tv2 = Fp.mul(tv1, c1);
      const tv3 = Fp.mul(tv1, c2);
      const tv4 = Fp.mul(tv1, c3);
      const e1 = Fp.eql(Fp.sqr(tv2), n);
      const e2 = Fp.eql(Fp.sqr(tv3), n);
      tv1 = Fp.cmov(tv1, tv2, e1);
      tv2 = Fp.cmov(tv4, tv3, e2);
      const e3 = Fp.eql(Fp.sqr(tv2), n);
      const root = Fp.cmov(tv1, tv2, e3);
      assertIsSquare(Fp, root, n);
      return root;
    };
  }
  function tonelliShanks(P) {
    if (P < _3n$1)
      throw new Error("sqrt is not defined for small field");
    let Q = P - _1n$2;
    let S = 0;
    while (Q % _2n$1 === _0n$2) {
      Q /= _2n$1;
      S++;
    }
    let Z = _2n$1;
    const _Fp = Field(P);
    while (FpLegendre(_Fp, Z) === 1) {
      if (Z++ > 1e3)
        throw new Error("Cannot find square root: probably non-prime P");
    }
    if (S === 1)
      return sqrt3mod4;
    let cc = _Fp.pow(Z, Q);
    const Q1div2 = (Q + _1n$2) / _2n$1;
    return function tonelliSlow(Fp, n) {
      if (Fp.is0(n))
        return n;
      if (FpLegendre(Fp, n) !== 1)
        throw new Error("Cannot find square root");
      let M = S;
      let c = Fp.mul(Fp.ONE, cc);
      let t = Fp.pow(n, Q);
      let R = Fp.pow(n, Q1div2);
      while (!Fp.eql(t, Fp.ONE)) {
        if (Fp.is0(t))
          return Fp.ZERO;
        let i = 1;
        let t_tmp = Fp.sqr(t);
        while (!Fp.eql(t_tmp, Fp.ONE)) {
          i++;
          t_tmp = Fp.sqr(t_tmp);
          if (i === M)
            throw new Error("Cannot find square root");
        }
        const exponent = _1n$2 << BigInt(M - i - 1);
        const b = Fp.pow(c, exponent);
        M = i;
        c = Fp.sqr(b);
        t = Fp.mul(t, c);
        R = Fp.mul(R, b);
      }
      return R;
    };
  }
  function FpSqrt(P) {
    if (P % _4n$1 === _3n$1)
      return sqrt3mod4;
    if (P % _8n === _5n)
      return sqrt5mod8;
    if (P % _16n === _9n)
      return sqrt9mod16(P);
    return tonelliShanks(P);
  }
  function validateField(field) {
    const initial = {
      ORDER: "bigint",
      MASK: "bigint",
      BYTES: "number",
      BITS: "number"
    };
    const opts = FIELD_FIELDS.reduce((map, val) => {
      map[val] = "function";
      return map;
    }, initial);
    _validateObject(field, opts);
    return field;
  }
  function FpPow(Fp, num, power) {
    if (power < _0n$2)
      throw new Error("invalid exponent, negatives unsupported");
    if (power === _0n$2)
      return Fp.ONE;
    if (power === _1n$2)
      return num;
    let p = Fp.ONE;
    let d = num;
    while (power > _0n$2) {
      if (power & _1n$2)
        p = Fp.mul(p, d);
      d = Fp.sqr(d);
      power >>= _1n$2;
    }
    return p;
  }
  function FpInvertBatch(Fp, nums, passZero = false) {
    const inverted = new Array(nums.length).fill(passZero ? Fp.ZERO : void 0);
    const multipliedAcc = nums.reduce((acc, num, i) => {
      if (Fp.is0(num))
        return acc;
      inverted[i] = acc;
      return Fp.mul(acc, num);
    }, Fp.ONE);
    const invertedAcc = Fp.inv(multipliedAcc);
    nums.reduceRight((acc, num, i) => {
      if (Fp.is0(num))
        return acc;
      inverted[i] = Fp.mul(acc, inverted[i]);
      return Fp.mul(acc, num);
    }, invertedAcc);
    return inverted;
  }
  function FpLegendre(Fp, n) {
    const p1mod2 = (Fp.ORDER - _1n$2) / _2n$1;
    const powered = Fp.pow(n, p1mod2);
    const yes = Fp.eql(powered, Fp.ONE);
    const zero = Fp.eql(powered, Fp.ZERO);
    const no = Fp.eql(powered, Fp.neg(Fp.ONE));
    if (!yes && !zero && !no)
      throw new Error("invalid Legendre symbol result");
    return yes ? 1 : zero ? 0 : -1;
  }
  function nLength(n, nBitLength) {
    if (nBitLength !== void 0)
      anumber(nBitLength);
    const _nBitLength = nBitLength !== void 0 ? nBitLength : n.toString(2).length;
    const nByteLength = Math.ceil(_nBitLength / 8);
    return { nBitLength: _nBitLength, nByteLength };
  }
  function Field(ORDER, bitLenOrOpts, isLE = false, opts = {}) {
    if (ORDER <= _0n$2)
      throw new Error("invalid field: expected ORDER > 0, got " + ORDER);
    let _nbitLength = void 0;
    let _sqrt = void 0;
    let modOnDecode = false;
    let allowedLengths = void 0;
    if (typeof bitLenOrOpts === "object" && bitLenOrOpts != null) {
      if (opts.sqrt || isLE)
        throw new Error("cannot specify opts in two arguments");
      const _opts = bitLenOrOpts;
      if (_opts.BITS)
        _nbitLength = _opts.BITS;
      if (_opts.sqrt)
        _sqrt = _opts.sqrt;
      if (typeof _opts.isLE === "boolean")
        isLE = _opts.isLE;
      if (typeof _opts.modOnDecode === "boolean")
        modOnDecode = _opts.modOnDecode;
      allowedLengths = _opts.allowedLengths;
    } else {
      if (typeof bitLenOrOpts === "number")
        _nbitLength = bitLenOrOpts;
      if (opts.sqrt)
        _sqrt = opts.sqrt;
    }
    const { nBitLength: BITS, nByteLength: BYTES } = nLength(ORDER, _nbitLength);
    if (BYTES > 2048)
      throw new Error("invalid field: expected ORDER of <= 2048 bytes");
    let sqrtP;
    const f = Object.freeze({
      ORDER,
      isLE,
      BITS,
      BYTES,
      MASK: bitMask(BITS),
      ZERO: _0n$2,
      ONE: _1n$2,
      allowedLengths,
      create: (num) => mod(num, ORDER),
      isValid: (num) => {
        if (typeof num !== "bigint")
          throw new Error("invalid field element: expected bigint, got " + typeof num);
        return _0n$2 <= num && num < ORDER;
      },
      is0: (num) => num === _0n$2,
      // is valid and invertible
      isValidNot0: (num) => !f.is0(num) && f.isValid(num),
      isOdd: (num) => (num & _1n$2) === _1n$2,
      neg: (num) => mod(-num, ORDER),
      eql: (lhs, rhs) => lhs === rhs,
      sqr: (num) => mod(num * num, ORDER),
      add: (lhs, rhs) => mod(lhs + rhs, ORDER),
      sub: (lhs, rhs) => mod(lhs - rhs, ORDER),
      mul: (lhs, rhs) => mod(lhs * rhs, ORDER),
      pow: (num, power) => FpPow(f, num, power),
      div: (lhs, rhs) => mod(lhs * invert(rhs, ORDER), ORDER),
      // Same as above, but doesn't normalize
      sqrN: (num) => num * num,
      addN: (lhs, rhs) => lhs + rhs,
      subN: (lhs, rhs) => lhs - rhs,
      mulN: (lhs, rhs) => lhs * rhs,
      inv: (num) => invert(num, ORDER),
      sqrt: _sqrt || ((n) => {
        if (!sqrtP)
          sqrtP = FpSqrt(ORDER);
        return sqrtP(f, n);
      }),
      toBytes: (num) => isLE ? numberToBytesLE(num, BYTES) : numberToBytesBE(num, BYTES),
      fromBytes: (bytes, skipValidation = true) => {
        if (allowedLengths) {
          if (!allowedLengths.includes(bytes.length) || bytes.length > BYTES) {
            throw new Error("Field.fromBytes: expected " + allowedLengths + " bytes, got " + bytes.length);
          }
          const padded = new Uint8Array(BYTES);
          padded.set(bytes, isLE ? 0 : padded.length - bytes.length);
          bytes = padded;
        }
        if (bytes.length !== BYTES)
          throw new Error("Field.fromBytes: expected " + BYTES + " bytes, got " + bytes.length);
        let scalar = isLE ? bytesToNumberLE(bytes) : bytesToNumberBE(bytes);
        if (modOnDecode)
          scalar = mod(scalar, ORDER);
        if (!skipValidation) {
          if (!f.isValid(scalar))
            throw new Error("invalid field element: outside of range 0..ORDER");
        }
        return scalar;
      },
      // TODO: we don't need it here, move out to separate fn
      invertBatch: (lst) => FpInvertBatch(f, lst),
      // We can't move this out because Fp6, Fp12 implement it
      // and it's unclear what to return in there.
      cmov: (a, b, c) => c ? b : a
    });
    return Object.freeze(f);
  }
  function getFieldBytesLength(fieldOrder) {
    if (typeof fieldOrder !== "bigint")
      throw new Error("field order must be bigint");
    const bitLength = fieldOrder.toString(2).length;
    return Math.ceil(bitLength / 8);
  }
  function getMinHashLength(fieldOrder) {
    const length = getFieldBytesLength(fieldOrder);
    return length + Math.ceil(length / 2);
  }
  function mapHashToField(key, fieldOrder, isLE = false) {
    const len = key.length;
    const fieldLen = getFieldBytesLength(fieldOrder);
    const minLen = getMinHashLength(fieldOrder);
    if (len < 16 || len < minLen || len > 1024)
      throw new Error("expected " + minLen + "-1024 bytes of input, got " + len);
    const num = isLE ? bytesToNumberLE(key) : bytesToNumberBE(key);
    const reduced = mod(num, fieldOrder - _1n$2) + _1n$2;
    return isLE ? numberToBytesLE(reduced, fieldLen) : numberToBytesBE(reduced, fieldLen);
  }
  function setBigUint64(view, byteOffset, value, isLE) {
    if (typeof view.setBigUint64 === "function")
      return view.setBigUint64(byteOffset, value, isLE);
    const _32n2 = BigInt(32);
    const _u32_max = BigInt(4294967295);
    const wh = Number(value >> _32n2 & _u32_max);
    const wl = Number(value & _u32_max);
    const h = isLE ? 4 : 0;
    const l = isLE ? 0 : 4;
    view.setUint32(byteOffset + h, wh, isLE);
    view.setUint32(byteOffset + l, wl, isLE);
  }
  function Chi(a, b, c) {
    return a & b ^ ~a & c;
  }
  function Maj(a, b, c) {
    return a & b ^ a & c ^ b & c;
  }
  function fromBig(n, le = false) {
    if (le)
      return { h: Number(n & U32_MASK64), l: Number(n >> _32n & U32_MASK64) };
    return { h: Number(n >> _32n & U32_MASK64) | 0, l: Number(n & U32_MASK64) | 0 };
  }
  function split(lst, le = false) {
    const len = lst.length;
    let Ah = new Uint32Array(len);
    let Al = new Uint32Array(len);
    for (let i = 0; i < len; i++) {
      const { h, l } = fromBig(lst[i], le);
      [Ah[i], Al[i]] = [h, l];
    }
    return [Ah, Al];
  }
  function add(Ah, Al, Bh, Bl) {
    const l = (Al >>> 0) + (Bl >>> 0);
    return { h: Ah + Bh + (l / 2 ** 32 | 0) | 0, l: l | 0 };
  }
  function negateCt(condition, item) {
    const neg = item.negate();
    return condition ? neg : item;
  }
  function normalizeZ(c, points) {
    const invertedZs = FpInvertBatch(c.Fp, points.map((p) => p.Z));
    return points.map((p, i) => c.fromAffine(p.toAffine(invertedZs[i])));
  }
  function validateW(W, bits) {
    if (!Number.isSafeInteger(W) || W <= 0 || W > bits)
      throw new Error("invalid window size, expected [1.." + bits + "], got W=" + W);
  }
  function calcWOpts(W, scalarBits) {
    validateW(W, scalarBits);
    const windows = Math.ceil(scalarBits / W) + 1;
    const windowSize = 2 ** (W - 1);
    const maxNumber = 2 ** W;
    const mask = bitMask(W);
    const shiftBy = BigInt(W);
    return { windows, windowSize, mask, maxNumber, shiftBy };
  }
  function calcOffsets(n, window2, wOpts) {
    const { windowSize, mask, maxNumber, shiftBy } = wOpts;
    let wbits = Number(n & mask);
    let nextN = n >> shiftBy;
    if (wbits > windowSize) {
      wbits -= maxNumber;
      nextN += _1n$1;
    }
    const offsetStart = window2 * windowSize;
    const offset = offsetStart + Math.abs(wbits) - 1;
    const isZero = wbits === 0;
    const isNeg = wbits < 0;
    const isNegF = window2 % 2 !== 0;
    const offsetF = offsetStart;
    return { nextN, offset, isZero, isNeg, isNegF, offsetF };
  }
  function validateMSMPoints(points, c) {
    if (!Array.isArray(points))
      throw new Error("array expected");
    points.forEach((p, i) => {
      if (!(p instanceof c))
        throw new Error("invalid point at index " + i);
    });
  }
  function validateMSMScalars(scalars, field) {
    if (!Array.isArray(scalars))
      throw new Error("array of scalars expected");
    scalars.forEach((s, i) => {
      if (!field.isValid(s))
        throw new Error("invalid scalar at index " + i);
    });
  }
  function getW(P) {
    return pointWindowSizes.get(P) || 1;
  }
  function assert0(n) {
    if (n !== _0n$1)
      throw new Error("invalid wNAF");
  }
  function mulEndoUnsafe(Point, point, k1, k2) {
    let acc = point;
    let p1 = Point.ZERO;
    let p2 = Point.ZERO;
    while (k1 > _0n$1 || k2 > _0n$1) {
      if (k1 & _1n$1)
        p1 = p1.add(acc);
      if (k2 & _1n$1)
        p2 = p2.add(acc);
      acc = acc.double();
      k1 >>= _1n$1;
      k2 >>= _1n$1;
    }
    return { p1, p2 };
  }
  function pippenger(c, fieldN, points, scalars) {
    validateMSMPoints(points, c);
    validateMSMScalars(scalars, fieldN);
    const plength = points.length;
    const slength = scalars.length;
    if (plength !== slength)
      throw new Error("arrays of points and scalars must have equal length");
    const zero = c.ZERO;
    const wbits = bitLen(BigInt(plength));
    let windowSize = 1;
    if (wbits > 12)
      windowSize = wbits - 3;
    else if (wbits > 4)
      windowSize = wbits - 2;
    else if (wbits > 0)
      windowSize = 2;
    const MASK = bitMask(windowSize);
    const buckets = new Array(Number(MASK) + 1).fill(zero);
    const lastBits = Math.floor((fieldN.BITS - 1) / windowSize) * windowSize;
    let sum = zero;
    for (let i = lastBits; i >= 0; i -= windowSize) {
      buckets.fill(zero);
      for (let j = 0; j < slength; j++) {
        const scalar = scalars[j];
        const wbits2 = Number(scalar >> BigInt(i) & MASK);
        buckets[wbits2] = buckets[wbits2].add(points[j]);
      }
      let resI = zero;
      for (let j = buckets.length - 1, sumI = zero; j > 0; j--) {
        sumI = sumI.add(buckets[j]);
        resI = resI.add(sumI);
      }
      sum = sum.add(resI);
      if (i !== 0)
        for (let j = 0; j < windowSize; j++)
          sum = sum.double();
    }
    return sum;
  }
  function createField(order, field) {
    if (field) {
      if (field.ORDER !== order)
        throw new Error("Field.ORDER must match order: Fp == p, Fn == n");
      validateField(field);
      return field;
    } else {
      return Field(order);
    }
  }
  function _createCurveFields(type, CURVE, curveOpts = {}) {
    if (!CURVE || typeof CURVE !== "object")
      throw new Error(`expected valid ${type} CURVE object`);
    for (const p of ["p", "n", "h"]) {
      const val = CURVE[p];
      if (!(typeof val === "bigint" && val > _0n$1))
        throw new Error(`CURVE.${p} must be positive bigint`);
    }
    const Fp = createField(CURVE.p, curveOpts.Fp);
    const Fn = createField(CURVE.n, curveOpts.Fn);
    const _b = "b";
    const params = ["Gx", "Gy", "a", _b];
    for (const p of params) {
      if (!Fp.isValid(CURVE[p]))
        throw new Error(`CURVE.${p} must be valid field element of CURVE.Fp`);
    }
    return { Fp, Fn };
  }
  function _splitEndoScalar(k, basis, n) {
    const [[a1, b1], [a2, b2]] = basis;
    const c1 = divNearest(b2 * k, n);
    const c2 = divNearest(-b1 * k, n);
    let k1 = k - c1 * a1 - c2 * a2;
    let k2 = -c1 * b1 - c2 * b2;
    const k1neg = k1 < _0n;
    const k2neg = k2 < _0n;
    if (k1neg)
      k1 = -k1;
    if (k2neg)
      k2 = -k2;
    const MAX_NUM = bitMask(Math.ceil(bitLen(n) / 2)) + _1n;
    if (k1 < _0n || k1 >= MAX_NUM || k2 < _0n || k2 >= MAX_NUM) {
      throw new Error("splitScalar (endomorphism): failed, k=" + k);
    }
    return { k1neg, k1, k2neg, k2 };
  }
  function validateSigVerOpts(opts) {
    if (opts.lowS !== void 0)
      abool("lowS", opts.lowS);
    if (opts.prehash !== void 0)
      abool("prehash", opts.prehash);
  }
  function _legacyHelperEquat(Fp, a, b) {
    function weierstrassEquation(x) {
      const x2 = Fp.sqr(x);
      const x3 = Fp.mul(x2, x);
      return Fp.add(Fp.add(x3, Fp.mul(x, a)), b);
    }
    return weierstrassEquation;
  }
  function _normFnElement(Fn, key) {
    const { BYTES: expected } = Fn;
    let num;
    if (typeof key === "bigint") {
      num = key;
    } else {
      let bytes = ensureBytes("private key", key);
      try {
        num = Fn.fromBytes(bytes);
      } catch (error) {
        throw new Error(`invalid private key: expected ui8a of size ${expected}, got ${typeof key}`);
      }
    }
    if (!Fn.isValidNot0(num))
      throw new Error("invalid private key: out of range [1..N-1]");
    return num;
  }
  function weierstrassN(CURVE, curveOpts = {}) {
    const { Fp, Fn } = _createCurveFields("weierstrass", CURVE, curveOpts);
    const { h: cofactor, n: CURVE_ORDER } = CURVE;
    _validateObject(curveOpts, {}, {
      allowInfinityPoint: "boolean",
      clearCofactor: "function",
      isTorsionFree: "function",
      fromBytes: "function",
      toBytes: "function",
      endo: "object",
      wrapPrivateKey: "boolean"
    });
    const { endo } = curveOpts;
    if (endo) {
      if (!Fp.is0(CURVE.a) || typeof endo.beta !== "bigint" || !Array.isArray(endo.basises)) {
        throw new Error('invalid endo: expected "beta": bigint and "basises": array');
      }
    }
    function assertCompressionIsSupported() {
      if (!Fp.isOdd)
        throw new Error("compression is not supported: Field does not have .isOdd()");
    }
    function pointToBytes(_c, point, isCompressed) {
      const { x, y } = point.toAffine();
      const bx = Fp.toBytes(x);
      abool("isCompressed", isCompressed);
      if (isCompressed) {
        assertCompressionIsSupported();
        const hasEvenY = !Fp.isOdd(y);
        return concatBytes(pprefix(hasEvenY), bx);
      } else {
        return concatBytes(Uint8Array.of(4), bx, Fp.toBytes(y));
      }
    }
    function pointFromBytes(bytes) {
      abytes(bytes);
      const L = Fp.BYTES;
      const LC = L + 1;
      const LU = 2 * L + 1;
      const length = bytes.length;
      const head = bytes[0];
      const tail = bytes.subarray(1);
      if (length === LC && (head === 2 || head === 3)) {
        const x = Fp.fromBytes(tail);
        if (!Fp.isValid(x))
          throw new Error("bad point: is not on curve, wrong x");
        const y2 = weierstrassEquation(x);
        let y;
        try {
          y = Fp.sqrt(y2);
        } catch (sqrtError) {
          const err = sqrtError instanceof Error ? ": " + sqrtError.message : "";
          throw new Error("bad point: is not on curve, sqrt error" + err);
        }
        assertCompressionIsSupported();
        const isYOdd = Fp.isOdd(y);
        const isHeadOdd = (head & 1) === 1;
        if (isHeadOdd !== isYOdd)
          y = Fp.neg(y);
        return { x, y };
      } else if (length === LU && head === 4) {
        const x = Fp.fromBytes(tail.subarray(L * 0, L * 1));
        const y = Fp.fromBytes(tail.subarray(L * 1, L * 2));
        if (!isValidXY(x, y))
          throw new Error("bad point: is not on curve");
        return { x, y };
      } else {
        throw new Error(`bad point: got length ${length}, expected compressed=${LC} or uncompressed=${LU}`);
      }
    }
    const toBytes2 = curveOpts.toBytes || pointToBytes;
    const fromBytes = curveOpts.fromBytes || pointFromBytes;
    const weierstrassEquation = _legacyHelperEquat(Fp, CURVE.a, CURVE.b);
    function isValidXY(x, y) {
      const left = Fp.sqr(y);
      const right = weierstrassEquation(x);
      return Fp.eql(left, right);
    }
    if (!isValidXY(CURVE.Gx, CURVE.Gy))
      throw new Error("bad curve params: generator point");
    const _4a3 = Fp.mul(Fp.pow(CURVE.a, _3n), _4n);
    const _27b2 = Fp.mul(Fp.sqr(CURVE.b), BigInt(27));
    if (Fp.is0(Fp.add(_4a3, _27b2)))
      throw new Error("bad curve params: a or b");
    function acoord(title, n, banZero = false) {
      if (!Fp.isValid(n) || banZero && Fp.is0(n))
        throw new Error(`bad point coordinate ${title}`);
      return n;
    }
    function aprjpoint(other) {
      if (!(other instanceof Point))
        throw new Error("ProjectivePoint expected");
    }
    function splitEndoScalarN(k) {
      if (!endo || !endo.basises)
        throw new Error("no endo");
      return _splitEndoScalar(k, endo.basises, Fn.ORDER);
    }
    const toAffineMemo = memoized((p, iz) => {
      const { X, Y, Z } = p;
      if (Fp.eql(Z, Fp.ONE))
        return { x: X, y: Y };
      const is0 = p.is0();
      if (iz == null)
        iz = is0 ? Fp.ONE : Fp.inv(Z);
      const x = Fp.mul(X, iz);
      const y = Fp.mul(Y, iz);
      const zz = Fp.mul(Z, iz);
      if (is0)
        return { x: Fp.ZERO, y: Fp.ZERO };
      if (!Fp.eql(zz, Fp.ONE))
        throw new Error("invZ was invalid");
      return { x, y };
    });
    const assertValidMemo = memoized((p) => {
      if (p.is0()) {
        if (curveOpts.allowInfinityPoint && !Fp.is0(p.Y))
          return;
        throw new Error("bad point: ZERO");
      }
      const { x, y } = p.toAffine();
      if (!Fp.isValid(x) || !Fp.isValid(y))
        throw new Error("bad point: x or y not field elements");
      if (!isValidXY(x, y))
        throw new Error("bad point: equation left != right");
      if (!p.isTorsionFree())
        throw new Error("bad point: not in prime-order subgroup");
      return true;
    });
    function finishEndo(endoBeta, k1p, k2p, k1neg, k2neg) {
      k2p = new Point(Fp.mul(k2p.X, endoBeta), k2p.Y, k2p.Z);
      k1p = negateCt(k1neg, k1p);
      k2p = negateCt(k2neg, k2p);
      return k1p.add(k2p);
    }
    class Point {
      /** Does NOT validate if the point is valid. Use `.assertValidity()`. */
      constructor(X, Y, Z) {
        this.X = acoord("x", X);
        this.Y = acoord("y", Y, true);
        this.Z = acoord("z", Z);
        Object.freeze(this);
      }
      /** Does NOT validate if the point is valid. Use `.assertValidity()`. */
      static fromAffine(p) {
        const { x, y } = p || {};
        if (!p || !Fp.isValid(x) || !Fp.isValid(y))
          throw new Error("invalid affine point");
        if (p instanceof Point)
          throw new Error("projective point not allowed");
        if (Fp.is0(x) && Fp.is0(y))
          return Point.ZERO;
        return new Point(x, y, Fp.ONE);
      }
      get x() {
        return this.toAffine().x;
      }
      get y() {
        return this.toAffine().y;
      }
      // TODO: remove
      get px() {
        return this.X;
      }
      get py() {
        return this.X;
      }
      get pz() {
        return this.Z;
      }
      static normalizeZ(points) {
        return normalizeZ(Point, points);
      }
      static fromBytes(bytes) {
        abytes(bytes);
        return Point.fromHex(bytes);
      }
      /** Converts hash string or Uint8Array to Point. */
      static fromHex(hex) {
        const P = Point.fromAffine(fromBytes(ensureBytes("pointHex", hex)));
        P.assertValidity();
        return P;
      }
      /** Multiplies generator point by privateKey. */
      static fromPrivateKey(privateKey) {
        return Point.BASE.multiply(_normFnElement(Fn, privateKey));
      }
      // TODO: remove
      static msm(points, scalars) {
        return pippenger(Point, Fn, points, scalars);
      }
      _setWindowSize(windowSize) {
        this.precompute(windowSize);
      }
      /**
       *
       * @param windowSize
       * @param isLazy true will defer table computation until the first multiplication
       * @returns
       */
      precompute(windowSize = 8, isLazy = true) {
        wnaf.createCache(this, windowSize);
        if (!isLazy)
          this.multiply(_3n);
        return this;
      }
      // TODO: return `this`
      /** A point on curve is valid if it conforms to equation. */
      assertValidity() {
        assertValidMemo(this);
      }
      hasEvenY() {
        const { y } = this.toAffine();
        if (!Fp.isOdd)
          throw new Error("Field doesn't support isOdd");
        return !Fp.isOdd(y);
      }
      /** Compare one point to another. */
      equals(other) {
        aprjpoint(other);
        const { X: X1, Y: Y1, Z: Z1 } = this;
        const { X: X2, Y: Y2, Z: Z2 } = other;
        const U1 = Fp.eql(Fp.mul(X1, Z2), Fp.mul(X2, Z1));
        const U2 = Fp.eql(Fp.mul(Y1, Z2), Fp.mul(Y2, Z1));
        return U1 && U2;
      }
      /** Flips point to one corresponding to (x, -y) in Affine coordinates. */
      negate() {
        return new Point(this.X, Fp.neg(this.Y), this.Z);
      }
      // Renes-Costello-Batina exception-free doubling formula.
      // There is 30% faster Jacobian formula, but it is not complete.
      // https://eprint.iacr.org/2015/1060, algorithm 3
      // Cost: 8M + 3S + 3*a + 2*b3 + 15add.
      double() {
        const { a, b } = CURVE;
        const b3 = Fp.mul(b, _3n);
        const { X: X1, Y: Y1, Z: Z1 } = this;
        let X3 = Fp.ZERO, Y3 = Fp.ZERO, Z3 = Fp.ZERO;
        let t0 = Fp.mul(X1, X1);
        let t1 = Fp.mul(Y1, Y1);
        let t2 = Fp.mul(Z1, Z1);
        let t3 = Fp.mul(X1, Y1);
        t3 = Fp.add(t3, t3);
        Z3 = Fp.mul(X1, Z1);
        Z3 = Fp.add(Z3, Z3);
        X3 = Fp.mul(a, Z3);
        Y3 = Fp.mul(b3, t2);
        Y3 = Fp.add(X3, Y3);
        X3 = Fp.sub(t1, Y3);
        Y3 = Fp.add(t1, Y3);
        Y3 = Fp.mul(X3, Y3);
        X3 = Fp.mul(t3, X3);
        Z3 = Fp.mul(b3, Z3);
        t2 = Fp.mul(a, t2);
        t3 = Fp.sub(t0, t2);
        t3 = Fp.mul(a, t3);
        t3 = Fp.add(t3, Z3);
        Z3 = Fp.add(t0, t0);
        t0 = Fp.add(Z3, t0);
        t0 = Fp.add(t0, t2);
        t0 = Fp.mul(t0, t3);
        Y3 = Fp.add(Y3, t0);
        t2 = Fp.mul(Y1, Z1);
        t2 = Fp.add(t2, t2);
        t0 = Fp.mul(t2, t3);
        X3 = Fp.sub(X3, t0);
        Z3 = Fp.mul(t2, t1);
        Z3 = Fp.add(Z3, Z3);
        Z3 = Fp.add(Z3, Z3);
        return new Point(X3, Y3, Z3);
      }
      // Renes-Costello-Batina exception-free addition formula.
      // There is 30% faster Jacobian formula, but it is not complete.
      // https://eprint.iacr.org/2015/1060, algorithm 1
      // Cost: 12M + 0S + 3*a + 3*b3 + 23add.
      add(other) {
        aprjpoint(other);
        const { X: X1, Y: Y1, Z: Z1 } = this;
        const { X: X2, Y: Y2, Z: Z2 } = other;
        let X3 = Fp.ZERO, Y3 = Fp.ZERO, Z3 = Fp.ZERO;
        const a = CURVE.a;
        const b3 = Fp.mul(CURVE.b, _3n);
        let t0 = Fp.mul(X1, X2);
        let t1 = Fp.mul(Y1, Y2);
        let t2 = Fp.mul(Z1, Z2);
        let t3 = Fp.add(X1, Y1);
        let t4 = Fp.add(X2, Y2);
        t3 = Fp.mul(t3, t4);
        t4 = Fp.add(t0, t1);
        t3 = Fp.sub(t3, t4);
        t4 = Fp.add(X1, Z1);
        let t5 = Fp.add(X2, Z2);
        t4 = Fp.mul(t4, t5);
        t5 = Fp.add(t0, t2);
        t4 = Fp.sub(t4, t5);
        t5 = Fp.add(Y1, Z1);
        X3 = Fp.add(Y2, Z2);
        t5 = Fp.mul(t5, X3);
        X3 = Fp.add(t1, t2);
        t5 = Fp.sub(t5, X3);
        Z3 = Fp.mul(a, t4);
        X3 = Fp.mul(b3, t2);
        Z3 = Fp.add(X3, Z3);
        X3 = Fp.sub(t1, Z3);
        Z3 = Fp.add(t1, Z3);
        Y3 = Fp.mul(X3, Z3);
        t1 = Fp.add(t0, t0);
        t1 = Fp.add(t1, t0);
        t2 = Fp.mul(a, t2);
        t4 = Fp.mul(b3, t4);
        t1 = Fp.add(t1, t2);
        t2 = Fp.sub(t0, t2);
        t2 = Fp.mul(a, t2);
        t4 = Fp.add(t4, t2);
        t0 = Fp.mul(t1, t4);
        Y3 = Fp.add(Y3, t0);
        t0 = Fp.mul(t5, t4);
        X3 = Fp.mul(t3, X3);
        X3 = Fp.sub(X3, t0);
        t0 = Fp.mul(t3, t1);
        Z3 = Fp.mul(t5, Z3);
        Z3 = Fp.add(Z3, t0);
        return new Point(X3, Y3, Z3);
      }
      subtract(other) {
        return this.add(other.negate());
      }
      is0() {
        return this.equals(Point.ZERO);
      }
      /**
       * Constant time multiplication.
       * Uses wNAF method. Windowed method may be 10% faster,
       * but takes 2x longer to generate and consumes 2x memory.
       * Uses precomputes when available.
       * Uses endomorphism for Koblitz curves.
       * @param scalar by which the point would be multiplied
       * @returns New point
       */
      multiply(scalar) {
        const { endo: endo2 } = curveOpts;
        if (!Fn.isValidNot0(scalar))
          throw new Error("invalid scalar: out of range");
        let point, fake;
        const mul = (n) => wnaf.cached(this, n, (p) => normalizeZ(Point, p));
        if (endo2) {
          const { k1neg, k1, k2neg, k2 } = splitEndoScalarN(scalar);
          const { p: k1p, f: k1f } = mul(k1);
          const { p: k2p, f: k2f } = mul(k2);
          fake = k1f.add(k2f);
          point = finishEndo(endo2.beta, k1p, k2p, k1neg, k2neg);
        } else {
          const { p, f } = mul(scalar);
          point = p;
          fake = f;
        }
        return normalizeZ(Point, [point, fake])[0];
      }
      /**
       * Non-constant-time multiplication. Uses double-and-add algorithm.
       * It's faster, but should only be used when you don't care about
       * an exposed secret key e.g. sig verification, which works over *public* keys.
       */
      multiplyUnsafe(sc) {
        const { endo: endo2 } = curveOpts;
        const p = this;
        if (!Fn.isValid(sc))
          throw new Error("invalid scalar: out of range");
        if (sc === _0n || p.is0())
          return Point.ZERO;
        if (sc === _1n)
          return p;
        if (wnaf.hasCache(this))
          return this.multiply(sc);
        if (endo2) {
          const { k1neg, k1, k2neg, k2 } = splitEndoScalarN(sc);
          const { p1, p2 } = mulEndoUnsafe(Point, p, k1, k2);
          return finishEndo(endo2.beta, p1, p2, k1neg, k2neg);
        } else {
          return wnaf.unsafe(p, sc);
        }
      }
      multiplyAndAddUnsafe(Q, a, b) {
        const sum = this.multiplyUnsafe(a).add(Q.multiplyUnsafe(b));
        return sum.is0() ? void 0 : sum;
      }
      /**
       * Converts Projective point to affine (x, y) coordinates.
       * @param invertedZ Z^-1 (inverted zero) - optional, precomputation is useful for invertBatch
       */
      toAffine(invertedZ) {
        return toAffineMemo(this, invertedZ);
      }
      /**
       * Checks whether Point is free of torsion elements (is in prime subgroup).
       * Always torsion-free for cofactor=1 curves.
       */
      isTorsionFree() {
        const { isTorsionFree } = curveOpts;
        if (cofactor === _1n)
          return true;
        if (isTorsionFree)
          return isTorsionFree(Point, this);
        return wnaf.unsafe(this, CURVE_ORDER).is0();
      }
      clearCofactor() {
        const { clearCofactor } = curveOpts;
        if (cofactor === _1n)
          return this;
        if (clearCofactor)
          return clearCofactor(Point, this);
        return this.multiplyUnsafe(cofactor);
      }
      isSmallOrder() {
        return this.multiplyUnsafe(cofactor).is0();
      }
      toBytes(isCompressed = true) {
        abool("isCompressed", isCompressed);
        this.assertValidity();
        return toBytes2(Point, this, isCompressed);
      }
      /** @deprecated use `toBytes` */
      toRawBytes(isCompressed = true) {
        return this.toBytes(isCompressed);
      }
      toHex(isCompressed = true) {
        return bytesToHex(this.toBytes(isCompressed));
      }
      toString() {
        return `<Point ${this.is0() ? "ZERO" : this.toHex()}>`;
      }
    }
    Point.BASE = new Point(CURVE.Gx, CURVE.Gy, Fp.ONE);
    Point.ZERO = new Point(Fp.ZERO, Fp.ONE, Fp.ZERO);
    Point.Fp = Fp;
    Point.Fn = Fn;
    const bits = Fn.BITS;
    const wnaf = new wNAF(Point, curveOpts.endo ? Math.ceil(bits / 2) : bits);
    return Point;
  }
  function pprefix(hasEvenY) {
    return Uint8Array.of(hasEvenY ? 2 : 3);
  }
  function ecdsa(Point, hash, ecdsaOpts = {}) {
    ahash(hash);
    _validateObject(ecdsaOpts, {}, {
      hmac: "function",
      lowS: "boolean",
      randomBytes: "function",
      bits2int: "function",
      bits2int_modN: "function"
    });
    const randomBytes_ = ecdsaOpts.randomBytes || randomBytes;
    const hmac_ = ecdsaOpts.hmac || ((key, ...msgs) => hmac(hash, key, concatBytes(...msgs)));
    const { Fp, Fn } = Point;
    const { ORDER: CURVE_ORDER, BITS: fnBits } = Fn;
    const seedLen = getMinHashLength(CURVE_ORDER);
    const lengths = {
      secret: Fn.BYTES,
      public: 1 + Fp.BYTES,
      publicUncompressed: 1 + 2 * Fp.BYTES,
      signature: 2 * Fn.BYTES,
      seed: seedLen
    };
    function isBiggerThanHalfOrder(number) {
      const HALF = CURVE_ORDER >> _1n;
      return number > HALF;
    }
    function normalizeS(s) {
      return isBiggerThanHalfOrder(s) ? Fn.neg(s) : s;
    }
    function aValidRS(title, num) {
      if (!Fn.isValidNot0(num))
        throw new Error(`invalid signature ${title}: out of range 1..CURVE.n`);
    }
    class Signature {
      constructor(r, s, recovery) {
        aValidRS("r", r);
        aValidRS("s", s);
        this.r = r;
        this.s = s;
        if (recovery != null)
          this.recovery = recovery;
        Object.freeze(this);
      }
      static fromBytes(bytes, format = "compact") {
        if (format === "compact") {
          const L = Fn.BYTES;
          abytes(bytes, L * 2);
          const r = bytes.subarray(0, L);
          const s = bytes.subarray(L, L * 2);
          return new Signature(Fn.fromBytes(r), Fn.fromBytes(s));
        }
        if (format === "der") {
          abytes(bytes);
          const { r, s } = DER.toSig(bytes);
          return new Signature(r, s);
        }
        throw new Error("invalid format");
      }
      static fromHex(hex, format) {
        return this.fromBytes(hexToBytes(hex), format);
      }
      addRecoveryBit(recovery) {
        return new Signature(this.r, this.s, recovery);
      }
      // ProjPointType<bigint>
      recoverPublicKey(msgHash) {
        const FIELD_ORDER = Fp.ORDER;
        const { r, s, recovery: rec } = this;
        if (rec == null || ![0, 1, 2, 3].includes(rec))
          throw new Error("recovery id invalid");
        const hasCofactor = CURVE_ORDER * _2n < FIELD_ORDER;
        if (hasCofactor && rec > 1)
          throw new Error("recovery id is ambiguous for h>1 curve");
        const radj = rec === 2 || rec === 3 ? r + CURVE_ORDER : r;
        if (!Fp.isValid(radj))
          throw new Error("recovery id 2 or 3 invalid");
        const x = Fp.toBytes(radj);
        const R = Point.fromHex(concatBytes(pprefix((rec & 1) === 0), x));
        const ir = Fn.inv(radj);
        const h = bits2int_modN(ensureBytes("msgHash", msgHash));
        const u1 = Fn.create(-h * ir);
        const u2 = Fn.create(s * ir);
        const Q = Point.BASE.multiplyUnsafe(u1).add(R.multiplyUnsafe(u2));
        if (Q.is0())
          throw new Error("point at infinify");
        Q.assertValidity();
        return Q;
      }
      // Signatures should be low-s, to prevent malleability.
      hasHighS() {
        return isBiggerThanHalfOrder(this.s);
      }
      normalizeS() {
        return this.hasHighS() ? new Signature(this.r, Fn.neg(this.s), this.recovery) : this;
      }
      toBytes(format = "compact") {
        if (format === "compact")
          return concatBytes(Fn.toBytes(this.r), Fn.toBytes(this.s));
        if (format === "der")
          return hexToBytes(DER.hexFromSig(this));
        throw new Error("invalid format");
      }
      toHex(format) {
        return bytesToHex(this.toBytes(format));
      }
      // TODO: remove
      assertValidity() {
      }
      static fromCompact(hex) {
        return Signature.fromBytes(ensureBytes("sig", hex), "compact");
      }
      static fromDER(hex) {
        return Signature.fromBytes(ensureBytes("sig", hex), "der");
      }
      toDERRawBytes() {
        return this.toBytes("der");
      }
      toDERHex() {
        return bytesToHex(this.toBytes("der"));
      }
      toCompactRawBytes() {
        return this.toBytes("compact");
      }
      toCompactHex() {
        return bytesToHex(this.toBytes("compact"));
      }
    }
    function isValidSecretKey(privateKey) {
      try {
        return !!_normFnElement(Fn, privateKey);
      } catch (error) {
        return false;
      }
    }
    function isValidPublicKey(publicKey, isCompressed) {
      try {
        const l = publicKey.length;
        if (isCompressed === true && l !== lengths.public)
          return false;
        if (isCompressed === false && l !== lengths.publicUncompressed)
          return false;
        return !!Point.fromBytes(publicKey);
      } catch (error) {
        return false;
      }
    }
    function randomSecretKey(seed = randomBytes_(seedLen)) {
      return mapHashToField(seed, CURVE_ORDER);
    }
    const utils = {
      isValidSecretKey,
      isValidPublicKey,
      randomSecretKey,
      // TODO: remove
      isValidPrivateKey: isValidSecretKey,
      randomPrivateKey: randomSecretKey,
      normPrivateKeyToScalar: (key) => _normFnElement(Fn, key),
      precompute(windowSize = 8, point = Point.BASE) {
        return point.precompute(windowSize, false);
      }
    };
    function getPublicKey(secretKey, isCompressed = true) {
      return Point.BASE.multiply(_normFnElement(Fn, secretKey)).toBytes(isCompressed);
    }
    function isProbPub(item) {
      if (typeof item === "bigint")
        return false;
      if (item instanceof Point)
        return true;
      if (Fn.allowedLengths || lengths.secret === lengths.public)
        return void 0;
      const l = ensureBytes("key", item).length;
      return l === lengths.public || l === lengths.publicUncompressed;
    }
    function getSharedSecret(secretKeyA, publicKeyB, isCompressed = true) {
      if (isProbPub(secretKeyA) === true)
        throw new Error("first arg must be private key");
      if (isProbPub(publicKeyB) === false)
        throw new Error("second arg must be public key");
      const s = _normFnElement(Fn, secretKeyA);
      const b = Point.fromHex(publicKeyB);
      return b.multiply(s).toBytes(isCompressed);
    }
    const bits2int = ecdsaOpts.bits2int || function(bytes) {
      if (bytes.length > 8192)
        throw new Error("input is too large");
      const num = bytesToNumberBE(bytes);
      const delta = bytes.length * 8 - fnBits;
      return delta > 0 ? num >> BigInt(delta) : num;
    };
    const bits2int_modN = ecdsaOpts.bits2int_modN || function(bytes) {
      return Fn.create(bits2int(bytes));
    };
    const ORDER_MASK = bitMask(fnBits);
    function int2octets(num) {
      aInRange("num < 2^" + fnBits, num, _0n, ORDER_MASK);
      return Fn.toBytes(num);
    }
    function prepSig(msgHash, privateKey, opts = defaultSigOpts) {
      if (["recovered", "canonical"].some((k) => k in opts))
        throw new Error("sign() legacy options not supported");
      let { lowS, prehash, extraEntropy: ent } = opts;
      if (lowS == null)
        lowS = true;
      msgHash = ensureBytes("msgHash", msgHash);
      validateSigVerOpts(opts);
      if (prehash)
        msgHash = ensureBytes("prehashed msgHash", hash(msgHash));
      const h1int = bits2int_modN(msgHash);
      const d = _normFnElement(Fn, privateKey);
      const seedArgs = [int2octets(d), int2octets(h1int)];
      if (ent != null && ent !== false) {
        const e = ent === true ? randomBytes_(lengths.secret) : ent;
        seedArgs.push(ensureBytes("extraEntropy", e));
      }
      const seed = concatBytes(...seedArgs);
      const m = h1int;
      function k2sig(kBytes) {
        const k = bits2int(kBytes);
        if (!Fn.isValidNot0(k))
          return;
        const ik = Fn.inv(k);
        const q = Point.BASE.multiply(k).toAffine();
        const r = Fn.create(q.x);
        if (r === _0n)
          return;
        const s = Fn.create(ik * Fn.create(m + r * d));
        if (s === _0n)
          return;
        let recovery = (q.x === r ? 0 : 2) | Number(q.y & _1n);
        let normS = s;
        if (lowS && isBiggerThanHalfOrder(s)) {
          normS = normalizeS(s);
          recovery ^= 1;
        }
        return new Signature(r, normS, recovery);
      }
      return { seed, k2sig };
    }
    const defaultSigOpts = { lowS: ecdsaOpts.lowS, prehash: false };
    const defaultVerOpts = { lowS: ecdsaOpts.lowS, prehash: false };
    function sign(msgHash, secretKey, opts = defaultSigOpts) {
      const { seed, k2sig } = prepSig(msgHash, secretKey, opts);
      const drbg = createHmacDrbg(hash.outputLen, Fn.BYTES, hmac_);
      return drbg(seed, k2sig);
    }
    Point.BASE.precompute(8);
    function verify(signature, msgHash, publicKey, opts = defaultVerOpts) {
      const sg = signature;
      msgHash = ensureBytes("msgHash", msgHash);
      publicKey = ensureBytes("publicKey", publicKey);
      validateSigVerOpts(opts);
      const { lowS, prehash, format } = opts;
      if ("strict" in opts)
        throw new Error("options.strict was renamed to lowS");
      let _sig = void 0;
      let P;
      if (format === void 0) {
        const isHex = typeof sg === "string" || isBytes(sg);
        const isObj = !isHex && sg !== null && typeof sg === "object" && typeof sg.r === "bigint" && typeof sg.s === "bigint";
        if (!isHex && !isObj)
          throw new Error("invalid signature, expected Uint8Array, hex string or Signature instance");
        if (isObj) {
          _sig = new Signature(sg.r, sg.s);
        } else if (isHex) {
          try {
            _sig = Signature.fromDER(sg);
          } catch (derError) {
            if (!(derError instanceof DER.Err))
              throw derError;
          }
          if (!_sig) {
            try {
              _sig = Signature.fromCompact(sg);
            } catch (error) {
              return false;
            }
          }
        }
      } else {
        if (format === "compact" || format === "der") {
          if (typeof sg !== "string" && !isBytes(sg))
            throw new Error('"der" / "compact" format expects Uint8Array signature');
          _sig = Signature.fromBytes(ensureBytes("sig", sg), format);
        } else if (format === "js") {
          if (!(sg instanceof Signature))
            throw new Error('"js" format expects Signature instance');
          _sig = sg;
        } else {
          throw new Error('format must be "compact", "der" or "js"');
        }
      }
      if (!_sig)
        return false;
      try {
        P = Point.fromHex(publicKey);
        if (lowS && _sig.hasHighS())
          return false;
        if (prehash)
          msgHash = hash(msgHash);
        const { r, s } = _sig;
        const h = bits2int_modN(msgHash);
        const is = Fn.inv(s);
        const u1 = Fn.create(h * is);
        const u2 = Fn.create(r * is);
        const R = Point.BASE.multiplyUnsafe(u1).add(P.multiplyUnsafe(u2));
        if (R.is0())
          return false;
        const v = Fn.create(R.x);
        return v === r;
      } catch (e) {
        return false;
      }
    }
    function keygen(seed) {
      const secretKey = utils.randomSecretKey(seed);
      return { secretKey, publicKey: getPublicKey(secretKey) };
    }
    return Object.freeze({
      keygen,
      getPublicKey,
      sign,
      verify,
      getSharedSecret,
      utils,
      Point,
      Signature,
      info: { type: "weierstrass", lengths, publicKeyHasPrefix: true }
    });
  }
  function _weierstrass_legacy_opts_to_new(c) {
    const CURVE = {
      a: c.a,
      b: c.b,
      p: c.Fp.ORDER,
      n: c.n,
      h: c.h,
      Gx: c.Gx,
      Gy: c.Gy
    };
    const Fp = c.Fp;
    let allowedLengths = c.allowedPrivateKeyLengths ? Array.from(new Set(c.allowedPrivateKeyLengths.map((l) => Math.ceil(l / 2)))) : void 0;
    const Fn = Field(CURVE.n, {
      BITS: c.nBitLength,
      allowedLengths,
      modOnDecode: c.wrapPrivateKey
    });
    const curveOpts = {
      Fp,
      Fn,
      allowInfinityPoint: c.allowInfinityPoint,
      endo: c.endo,
      isTorsionFree: c.isTorsionFree,
      clearCofactor: c.clearCofactor,
      fromBytes: c.fromBytes,
      toBytes: c.toBytes
    };
    return { CURVE, curveOpts };
  }
  function _ecdsa_legacy_opts_to_new(c) {
    const { CURVE, curveOpts } = _weierstrass_legacy_opts_to_new(c);
    const ecdsaOpts = {
      hmac: c.hmac,
      randomBytes: c.randomBytes,
      lowS: c.lowS,
      bits2int: c.bits2int,
      bits2int_modN: c.bits2int_modN
    };
    return { CURVE, curveOpts, hash: c.hash, ecdsaOpts };
  }
  function _ecdsa_new_output_to_legacy(c, ecdsa2) {
    return Object.assign({}, ecdsa2, {
      ProjectivePoint: ecdsa2.Point,
      CURVE: c
    });
  }
  function weierstrass(c) {
    const { CURVE, curveOpts, hash, ecdsaOpts } = _ecdsa_legacy_opts_to_new(c);
    const Point = weierstrassN(CURVE, curveOpts);
    const signs = ecdsa(Point, hash, ecdsaOpts);
    return _ecdsa_new_output_to_legacy(c, signs);
  }
  function createCurve(curveDef, defHash) {
    const create = (hash) => weierstrass({ ...curveDef, hash });
    return { ...create(defHash), create };
  }
  function promisifyRequest(request) {
    return new Promise((resolve, reject) => {
      request.oncomplete = request.onsuccess = () => resolve(request.result);
      request.onabort = request.onerror = () => reject(request.error);
    });
  }
  function createStore(dbName, storeName) {
    let dbp;
    const getDB = () => {
      if (dbp)
        return dbp;
      const request = indexedDB.open(dbName);
      request.onupgradeneeded = () => request.result.createObjectStore(storeName);
      dbp = promisifyRequest(request);
      dbp.then((db) => {
        db.onclose = () => dbp = void 0;
      }, () => {
      });
      return dbp;
    };
    return (txMode, callback) => getDB().then((db) => callback(db.transaction(storeName, txMode).objectStore(storeName)));
  }
  function defaultGetStore() {
    if (!defaultGetStoreFunc) {
      defaultGetStoreFunc = createStore("keyval-store", "keyval");
    }
    return defaultGetStoreFunc;
  }
  function get(key, customStore = defaultGetStore()) {
    return customStore("readonly", (store) => promisifyRequest(store.get(key)));
  }
  function set(key, value, customStore = defaultGetStore()) {
    return customStore("readwrite", (store) => {
      store.put(value, key);
      return promisifyRequest(store.transaction);
    });
  }
  function del(key, customStore = defaultGetStore()) {
    return customStore("readwrite", (store) => {
      store.delete(key);
      return promisifyRequest(store.transaction);
    });
  }
  function getSubtle() {
    if (_subtle) return _subtle;
    if (isBrowser && window.crypto?.subtle) {
      _subtle = window.crypto.subtle;
    } else if (typeof globalThis !== "undefined" && globalThis.crypto?.subtle) {
      _subtle = globalThis.crypto.subtle;
    } else {
      if (typeof __require !== "undefined") {
        try {
          const { webcrypto } = __require("crypto");
          _subtle = webcrypto.subtle;
        } catch (e) {
          throw new Error("WebCrypto not available in this environment");
        }
      } else {
        throw new Error("WebCrypto not available - ensure you are using Node.js 16+ or a modern browser");
      }
    }
    return _subtle;
  }
  function getRandomValues(array) {
    if (isBrowser && window.crypto) {
      return window.crypto.getRandomValues(array);
    } else if (typeof globalThis !== "undefined" && globalThis.crypto) {
      return globalThis.crypto.getRandomValues(array);
    } else {
      if (typeof __require !== "undefined") {
        try {
          const { webcrypto } = __require("crypto");
          return webcrypto.getRandomValues(array);
        } catch (e) {
          throw new Error("Crypto random values not available in this environment");
        }
      } else {
        throw new Error("Crypto random values not available - ensure you are using Node.js 16+ or a modern browser");
      }
    }
  }
  function normalize(s) {
    if (typeof s !== "string") {
      throw new Error("Input must be a string");
    }
    return s.normalize("NFC").trim();
  }
  function validatePrivateKey(privB64) {
    if (typeof privB64 !== "string") {
      throw new Error("Private key must be a string");
    }
    try {
      const key = b64UrlToBuf(privB64);
      if (key.length !== 32) {
        throw new Error("Invalid private key length for P-256 (expected 32 bytes)");
      }
      return key;
    } catch (error) {
      throw new Error(`Invalid private key format: ${error.message}`);
    }
  }
  function validatePublicKey(pubJwk) {
    if (typeof pubJwk !== "string") {
      throw new Error("Public key must be a string");
    }
    if (!pubJwk.includes(".")) {
      throw new Error("Public key must be in JWK format (x.y)");
    }
    try {
      const [x, y] = pubJwk.split(".");
      if (!x || !y) {
        throw new Error("Invalid JWK format: missing x or y component");
      }
      const xBuf = b64UrlToBuf(x);
      const yBuf = b64UrlToBuf(y);
      if (xBuf.length !== 32 || yBuf.length !== 32) {
        throw new Error("Invalid public key coordinates length for P-256");
      }
      return { x: xBuf, y: yBuf };
    } catch (error) {
      throw new Error(`Invalid public key format: ${error.message}`);
    }
  }
  function constantTimeEqual(a, b) {
    if (a.length !== b.length) {
      return false;
    }
    let result = 0;
    for (let i = 0; i < a.length; i++) {
      result |= a[i] ^ b[i];
    }
    return result === 0;
  }
  function bufToB64Url(buf) {
    const bin = String.fromCharCode(...new Uint8Array(buf));
    return btoa(bin).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
  }
  function b64UrlToBuf(b64url) {
    const b64 = b64url.replace(/-/g, "+").replace(/_/g, "/") + "===".slice((b64url.length + 3) % 4);
    const bin = atob(b64);
    return Uint8Array.from(bin, (c) => c.charCodeAt(0));
  }
  function keyToJWK(pubBuf) {
    if (pubBuf[0] !== 4) throw new Error("Expected uncompressed key");
    const x = pubBuf.slice(1, 33);
    const y = pubBuf.slice(33, 65);
    return `${bufToB64Url(x)}.${bufToB64Url(y)}`;
  }
  function jwkToKey(jwk) {
    const [x, y] = jwk.split(".");
    return new Uint8Array([4, ...b64UrlToBuf(x), ...b64UrlToBuf(y)]);
  }
  async function generateRandomPair() {
    const signingPriv = p256.utils.randomPrivateKey();
    const encryptionPriv = p256.utils.randomPrivateKey();
    const pub = p256.getPublicKey(signingPriv, false);
    const epub = p256.getPublicKey(encryptionPriv, false);
    return {
      pub: keyToJWK(pub),
      priv: bufToB64Url(signingPriv),
      epub: keyToJWK(epub),
      epriv: bufToB64Url(encryptionPriv)
    };
  }
  async function signMessage(msg, privB64) {
    const subtle = getSubtle();
    const msgBuf = TEXT_ENCODER.encode(normalize(msg));
    const hash = await subtle.digest("SHA-256", msgBuf);
    const priv = validatePrivateKey(privB64);
    const sig = p256.sign(new Uint8Array(hash), priv);
    return bufToB64Url(sig.toCompactRawBytes());
  }
  async function verifyMessage(msg, sigB64, pubJwk) {
    if (typeof sigB64 !== "string") {
      throw new Error("Signature must be a string");
    }
    const subtle = getSubtle();
    const msgBuf = TEXT_ENCODER.encode(normalize(msg));
    const hash = await subtle.digest("SHA-256", msgBuf);
    validatePublicKey(pubJwk);
    const pub = jwkToKey(pubJwk);
    try {
      const sig = b64UrlToBuf(sigB64);
      return p256.verify(sig, new Uint8Array(hash), pub);
    } catch (error) {
      return false;
    }
  }
  async function encryptMessageWithMeta(msg, recipient) {
    if (!recipient || typeof recipient !== "object") {
      throw new Error("Recipient must be a key object with epub property");
    }
    if (!recipient.epub) {
      throw new Error("Recipient must have an encryption public key (epub)");
    }
    const subtle = getSubtle();
    validatePublicKey(recipient.epub);
    const pub = jwkToKey(recipient.epub);
    const ephPriv = p256.utils.randomPrivateKey();
    const ephPub = p256.getPublicKey(ephPriv, false);
    const shared = p256.getSharedSecret(ephPriv, pub).slice(1);
    const keyMat = await subtle.digest("SHA-256", shared);
    const iv = getRandomValues(new Uint8Array(12));
    const key = await subtle.importKey("raw", keyMat, { name: "AES-GCM" }, false, ["encrypt"]);
    const msgBuf = TEXT_ENCODER.encode(normalize(msg));
    const ct = await subtle.encrypt({ name: "AES-GCM", iv }, key, msgBuf);
    return {
      ciphertext: bufToB64Url(ct),
      iv: bufToB64Url(iv),
      sender: keyToJWK(ephPub),
      timestamp: Date.now()
    };
  }
  async function decryptMessageWithMeta(payload, privB64) {
    if (!payload || typeof payload !== "object") {
      throw new Error("Payload must be an encrypted message object");
    }
    if (!payload.ciphertext || !payload.iv || !payload.sender) {
      throw new Error("Payload must contain ciphertext, iv, and sender");
    }
    const subtle = getSubtle();
    validatePublicKey(payload.sender);
    const ephPub = jwkToKey(payload.sender);
    const priv = validatePrivateKey(privB64);
    const shared = p256.getSharedSecret(priv, ephPub).slice(1);
    const keyMat = await subtle.digest("SHA-256", shared);
    const key = await subtle.importKey("raw", keyMat, { name: "AES-GCM" }, false, ["decrypt"]);
    try {
      const iv = b64UrlToBuf(payload.iv);
      const ct = b64UrlToBuf(payload.ciphertext);
      const pt = await subtle.decrypt({ name: "AES-GCM", iv }, key, ct);
      return TEXT_DECODER.decode(pt);
    } catch (error) {
      throw new Error(`Decryption failed: ${error.message}`);
    }
  }
  async function exportToJWK(privB64) {
    const priv = b64UrlToBuf(privB64);
    if (priv.length !== 32) {
      throw new Error("Invalid private key length for P-256");
    }
    return {
      kty: "EC",
      crv: "P-256",
      d: bufToB64Url(priv),
      use: "sig",
      key_ops: ["sign"]
    };
  }
  async function importFromJWK(jwk) {
    if (jwk.kty !== "EC") {
      throw new Error("JWK must be an EC key");
    }
    if (jwk.crv !== "P-256") {
      throw new Error("JWK must use P-256 curve");
    }
    if (!jwk.d) {
      throw new Error("JWK must contain private key component (d)");
    }
    return jwk.d;
  }
  async function exportToPEM(privB64) {
    const raw = b64UrlToBuf(privB64);
    if (raw.length !== 32) {
      throw new Error("Invalid private key length for P-256");
    }
    const pkcs8Header = new Uint8Array([
      48,
      129,
      135,
      // SEQUENCE (135 bytes)
      2,
      1,
      0,
      // INTEGER version (0)
      48,
      19,
      // SEQUENCE AlgorithmIdentifier
      6,
      7,
      42,
      134,
      72,
      206,
      61,
      2,
      1,
      // OID ecPublicKey
      6,
      8,
      42,
      134,
      72,
      206,
      61,
      3,
      1,
      7,
      // OID secp256r1
      4,
      109,
      // OCTET STRING (109 bytes)
      48,
      107,
      // SEQUENCE ECPrivateKey
      2,
      1,
      1,
      // INTEGER version (1)
      4,
      32
      // OCTET STRING privateKey (32 bytes)
    ]);
    const pkcs8Suffix = new Uint8Array([
      161,
      68,
      3,
      66,
      0,
      4
      // publicKey context tag + BIT STRING + uncompressed point indicator
    ]);
    const pubKey = p256.getPublicKey(raw, false);
    const pkcs8Data = new Uint8Array(pkcs8Header.length + raw.length + pkcs8Suffix.length + pubKey.length);
    pkcs8Data.set(pkcs8Header, 0);
    pkcs8Data.set(raw, pkcs8Header.length);
    pkcs8Data.set(pkcs8Suffix, pkcs8Header.length + raw.length);
    pkcs8Data.set(pubKey, pkcs8Header.length + raw.length + pkcs8Suffix.length);
    const b64 = btoa(String.fromCharCode(...pkcs8Data));
    return `-----BEGIN PRIVATE KEY-----
${b64.match(/.{1,64}/g).join("\n")}
-----END PRIVATE KEY-----`;
  }
  async function importFromPEM(pem) {
    if (!pem.includes("-----BEGIN PRIVATE KEY-----")) {
      throw new Error("Invalid PEM format: must contain BEGIN PRIVATE KEY header");
    }
    const b64 = pem.replace(/-----.*?-----/g, "").replace(/\s+/g, "");
    if (!b64) {
      throw new Error("Invalid PEM format: no data found");
    }
    try {
      const bin = atob(b64);
      const data = Uint8Array.from([...bin].map((c) => c.charCodeAt(0)));
      for (let i = 0; i < data.length - 34; i++) {
        if (data[i] === 4 && data[i + 1] === 32) {
          const privateKey = data.slice(i + 2, i + 34);
          if (privateKey.length === 32) {
            return bufToB64Url(privateKey);
          }
        }
      }
      throw new Error("Could not extract private key from PKCS#8 structure");
    } catch (error) {
      throw new Error(`Failed to parse PEM: ${error.message}`);
    }
  }
  async function deriveStorageKey(password, salt) {
    const subtle = getSubtle();
    const encoder = new TextEncoder();
    const keyMaterial = await subtle.importKey(
      "raw",
      encoder.encode(password),
      { name: "PBKDF2" },
      false,
      ["deriveKey"]
    );
    return subtle.deriveKey(
      {
        name: "PBKDF2",
        salt,
        iterations: 1e5,
        hash: "SHA-256"
      },
      keyMaterial,
      { name: "AES-GCM", length: 256 },
      false,
      ["encrypt", "decrypt"]
    );
  }
  async function saveKeys(name, keys, password = null) {
    if (!password) {
      console.warn("\u26A0\uFE0F WARNING: Keys are being stored unencrypted. Consider providing a password for better security.");
      return set(name, { encrypted: false, data: keys });
    }
    try {
      const subtle = getSubtle();
      const salt = getRandomValues(new Uint8Array(16));
      const iv = getRandomValues(new Uint8Array(12));
      const storageKey = await deriveStorageKey(password, salt);
      const keyData = TEXT_ENCODER.encode(JSON.stringify(keys));
      const encryptedData = await subtle.encrypt({ name: "AES-GCM", iv }, storageKey, keyData);
      return set(name, {
        encrypted: true,
        salt: bufToB64Url(salt),
        iv: bufToB64Url(iv),
        data: bufToB64Url(encryptedData)
      });
    } catch (error) {
      throw new Error(`Failed to encrypt and save keys: ${error.message}`);
    }
  }
  async function loadKeys(name, password = null) {
    const stored = await get(name);
    if (!stored) {
      return void 0;
    }
    if (!stored.encrypted) {
      return stored.data;
    }
    if (!password) {
      throw new Error("Password required to decrypt stored keys");
    }
    try {
      const subtle = getSubtle();
      const salt = b64UrlToBuf(stored.salt);
      const iv = b64UrlToBuf(stored.iv);
      const encryptedData = b64UrlToBuf(stored.data);
      const storageKey = await deriveStorageKey(password, salt);
      const decryptedData = await subtle.decrypt({ name: "AES-GCM", iv }, storageKey, encryptedData);
      const keyData = TEXT_DECODER.decode(decryptedData);
      return JSON.parse(keyData);
    } catch (error) {
      throw new Error(`Failed to decrypt keys: ${error.message}`);
    }
  }
  async function clearKeys(name) {
    return del(name);
  }
  async function generateWork(data, difficulty = 4, maxIterations = 1e6) {
    const subtle = getSubtle();
    const target = "0".repeat(difficulty);
    const dataStr = typeof data === "string" ? data : JSON.stringify(data);
    let nonce = 0;
    let hash;
    let hashHex;
    const startTime = Date.now();
    while (nonce < maxIterations) {
      const payload = `${dataStr}:${nonce}`;
      const payloadBuf = TEXT_ENCODER.encode(payload);
      const hashBuf = await subtle.digest("SHA-256", payloadBuf);
      const hashArray = new Uint8Array(hashBuf);
      hashHex = Array.from(hashArray).map((b) => b.toString(16).padStart(2, "0")).join("");
      if (hashHex.startsWith(target)) {
        hash = bufToB64Url(hashBuf);
        break;
      }
      nonce++;
    }
    const endTime = Date.now();
    const duration = endTime - startTime;
    if (nonce >= maxIterations) {
      throw new Error(`Failed to find proof of work within ${maxIterations} iterations`);
    }
    return {
      data: dataStr,
      nonce,
      hash,
      hashHex,
      difficulty,
      timestamp: endTime,
      duration,
      hashRate: Math.round(nonce / (duration / 1e3))
    };
  }
  async function verifyWork(proof) {
    if (!proof || typeof proof !== "object") {
      throw new Error("Proof must be an object");
    }
    if (typeof proof.data !== "string" || typeof proof.nonce !== "number" || typeof proof.difficulty !== "number" || !proof.hash || !proof.hashHex) {
      throw new Error("Proof must contain data, nonce, difficulty, hash, and hashHex");
    }
    const subtle = getSubtle();
    const target = "0".repeat(proof.difficulty);
    const payload = `${proof.data}:${proof.nonce}`;
    const payloadBuf = TEXT_ENCODER.encode(payload);
    const hashBuf = await subtle.digest("SHA-256", payloadBuf);
    const hashArray = new Uint8Array(hashBuf);
    const hashHex = Array.from(hashArray).map((b) => b.toString(16).padStart(2, "0")).join("");
    const hashB64 = bufToB64Url(hashBuf);
    const expectedHashB64 = TEXT_ENCODER.encode(proof.hash);
    const computedHashB64 = TEXT_ENCODER.encode(hashB64);
    const expectedHashHex = TEXT_ENCODER.encode(proof.hashHex);
    const computedHashHex = TEXT_ENCODER.encode(hashHex);
    const validHashB64 = constantTimeEqual(expectedHashB64, computedHashB64);
    const validHashHex = constantTimeEqual(expectedHashHex, computedHashHex);
    const validHash = validHashB64 && validHashHex;
    const validDifficulty = hashHex.startsWith(target);
    return {
      valid: validHash && validDifficulty,
      hashMatches: validHash,
      difficultyMatches: validDifficulty,
      computedHash: hashB64,
      computedHashHex: hashHex,
      expectedDifficulty: target
    };
  }
  async function generateSignedWork(data, privKey, difficulty = 4, maxIterations = 1e6) {
    const work = await generateWork(data, difficulty, maxIterations);
    const workPayload = JSON.stringify({
      data: work.data,
      nonce: work.nonce,
      hash: work.hash,
      difficulty: work.difficulty,
      timestamp: work.timestamp
    });
    const signature = await signMessage(workPayload, privKey);
    return {
      ...work,
      signature,
      signedPayload: workPayload
    };
  }
  async function verifySignedWork(signedWork, pubKey) {
    const workVerification = await verifyWork(signedWork);
    if (!workVerification.valid) {
      return {
        valid: false,
        workValid: false,
        signatureValid: false,
        reason: "Invalid proof of work"
      };
    }
    const signatureValid = await verifyMessage(
      signedWork.signedPayload,
      signedWork.signature,
      pubKey
    );
    return {
      valid: workVerification.valid && signatureValid,
      workValid: workVerification.valid,
      signatureValid,
      workVerification
    };
  }
  function getSecurityInfo() {
    return {
      version: "1.1.2",
      securityEnhancements: [
        "Bundled dependencies with static imports",
        "Proper PKCS#8 PEM encoding/decoding",
        "Encrypted key storage with PBKDF2",
        "Input validation and sanitization",
        "Constant-time comparisons",
        "Enhanced error handling",
        "Multiple output formats for compatibility"
      ],
      algorithms: {
        signing: "ECDSA with P-256 and SHA-256",
        encryption: "ECDH + AES-GCM",
        keyDerivation: "PBKDF2 with SHA-256",
        proofOfWork: "SHA-256 based mining"
      },
      warnings: [
        "Keys stored without password are unencrypted",
        "PEM format uses simplified PKCS#8 structure",
        "Proof of work verification uses constant-time comparison for hashes only",
        "Dependencies are bundled at build time - verify bundle integrity"
      ]
    };
  }
  var crypto2, hasHexBuiltin, hexes, asciis, Hash, _0n$3, _1n$3, isPosBig, bitMask, _0n$2, _1n$2, _2n$1, _3n$1, _4n$1, _5n, _7n, _8n, _9n, _16n, FIELD_FIELDS, HashMD, SHA256_IV, SHA384_IV, SHA512_IV, U32_MASK64, _32n, shrSH, shrSL, rotrSH, rotrSL, rotrBH, rotrBL, add3L, add3H, add4L, add4H, add5L, add5H, SHA256_K, SHA256_W, SHA256, K512, SHA512_Kh, SHA512_Kl, SHA512_W_H, SHA512_W_L, SHA512, SHA384, sha256, sha512, sha384, HMAC, hmac, _0n$1, _1n$1, pointPrecomputes, pointWindowSizes, wNAF, divNearest, DERErr, DER, _0n, _1n, _2n, _3n, _4n, p256_CURVE, p384_CURVE, p521_CURVE, Fp256, Fp384, Fp521, p256$1, p256, defaultGetStoreFunc, isBrowser, _subtle, TEXT_ENCODER, TEXT_DECODER, SECURITY_CONFIG;
  var init_unsea = __esm({
    "node_modules/unsea/dist/unsea.mjs"() {
      crypto2 = typeof globalThis === "object" && "crypto" in globalThis ? globalThis.crypto : void 0;
      hasHexBuiltin = /* @__PURE__ */ (() => (
        // @ts-ignore
        typeof Uint8Array.from([]).toHex === "function" && typeof Uint8Array.fromHex === "function"
      ))();
      hexes = /* @__PURE__ */ Array.from({ length: 256 }, (_, i) => i.toString(16).padStart(2, "0"));
      asciis = { _0: 48, _9: 57, A: 65, F: 70, a: 97, f: 102 };
      Hash = class {
      };
      _0n$3 = /* @__PURE__ */ BigInt(0);
      _1n$3 = /* @__PURE__ */ BigInt(1);
      isPosBig = (n) => typeof n === "bigint" && _0n$3 <= n;
      bitMask = (n) => (_1n$3 << BigInt(n)) - _1n$3;
      _0n$2 = BigInt(0);
      _1n$2 = BigInt(1);
      _2n$1 = /* @__PURE__ */ BigInt(2);
      _3n$1 = /* @__PURE__ */ BigInt(3);
      _4n$1 = /* @__PURE__ */ BigInt(4);
      _5n = /* @__PURE__ */ BigInt(5);
      _7n = /* @__PURE__ */ BigInt(7);
      _8n = /* @__PURE__ */ BigInt(8);
      _9n = /* @__PURE__ */ BigInt(9);
      _16n = /* @__PURE__ */ BigInt(16);
      FIELD_FIELDS = [
        "create",
        "isValid",
        "is0",
        "neg",
        "inv",
        "sqrt",
        "sqr",
        "eql",
        "add",
        "sub",
        "mul",
        "pow",
        "div",
        "addN",
        "subN",
        "mulN",
        "sqrN"
      ];
      HashMD = class extends Hash {
        constructor(blockLen, outputLen, padOffset, isLE) {
          super();
          this.finished = false;
          this.length = 0;
          this.pos = 0;
          this.destroyed = false;
          this.blockLen = blockLen;
          this.outputLen = outputLen;
          this.padOffset = padOffset;
          this.isLE = isLE;
          this.buffer = new Uint8Array(blockLen);
          this.view = createView(this.buffer);
        }
        update(data) {
          aexists(this);
          data = toBytes(data);
          abytes(data);
          const { view, buffer, blockLen } = this;
          const len = data.length;
          for (let pos = 0; pos < len; ) {
            const take = Math.min(blockLen - this.pos, len - pos);
            if (take === blockLen) {
              const dataView = createView(data);
              for (; blockLen <= len - pos; pos += blockLen)
                this.process(dataView, pos);
              continue;
            }
            buffer.set(data.subarray(pos, pos + take), this.pos);
            this.pos += take;
            pos += take;
            if (this.pos === blockLen) {
              this.process(view, 0);
              this.pos = 0;
            }
          }
          this.length += data.length;
          this.roundClean();
          return this;
        }
        digestInto(out) {
          aexists(this);
          aoutput(out, this);
          this.finished = true;
          const { buffer, view, blockLen, isLE } = this;
          let { pos } = this;
          buffer[pos++] = 128;
          clean(this.buffer.subarray(pos));
          if (this.padOffset > blockLen - pos) {
            this.process(view, 0);
            pos = 0;
          }
          for (let i = pos; i < blockLen; i++)
            buffer[i] = 0;
          setBigUint64(view, blockLen - 8, BigInt(this.length * 8), isLE);
          this.process(view, 0);
          const oview = createView(out);
          const len = this.outputLen;
          if (len % 4)
            throw new Error("_sha2: outputLen should be aligned to 32bit");
          const outLen = len / 4;
          const state = this.get();
          if (outLen > state.length)
            throw new Error("_sha2: outputLen bigger than state");
          for (let i = 0; i < outLen; i++)
            oview.setUint32(4 * i, state[i], isLE);
        }
        digest() {
          const { buffer, outputLen } = this;
          this.digestInto(buffer);
          const res = buffer.slice(0, outputLen);
          this.destroy();
          return res;
        }
        _cloneInto(to) {
          to || (to = new this.constructor());
          to.set(...this.get());
          const { blockLen, buffer, length, finished, destroyed, pos } = this;
          to.destroyed = destroyed;
          to.finished = finished;
          to.length = length;
          to.pos = pos;
          if (length % blockLen)
            to.buffer.set(buffer);
          return to;
        }
        clone() {
          return this._cloneInto();
        }
      };
      SHA256_IV = /* @__PURE__ */ Uint32Array.from([
        1779033703,
        3144134277,
        1013904242,
        2773480762,
        1359893119,
        2600822924,
        528734635,
        1541459225
      ]);
      SHA384_IV = /* @__PURE__ */ Uint32Array.from([
        3418070365,
        3238371032,
        1654270250,
        914150663,
        2438529370,
        812702999,
        355462360,
        4144912697,
        1731405415,
        4290775857,
        2394180231,
        1750603025,
        3675008525,
        1694076839,
        1203062813,
        3204075428
      ]);
      SHA512_IV = /* @__PURE__ */ Uint32Array.from([
        1779033703,
        4089235720,
        3144134277,
        2227873595,
        1013904242,
        4271175723,
        2773480762,
        1595750129,
        1359893119,
        2917565137,
        2600822924,
        725511199,
        528734635,
        4215389547,
        1541459225,
        327033209
      ]);
      U32_MASK64 = /* @__PURE__ */ BigInt(2 ** 32 - 1);
      _32n = /* @__PURE__ */ BigInt(32);
      shrSH = (h, _l, s) => h >>> s;
      shrSL = (h, l, s) => h << 32 - s | l >>> s;
      rotrSH = (h, l, s) => h >>> s | l << 32 - s;
      rotrSL = (h, l, s) => h << 32 - s | l >>> s;
      rotrBH = (h, l, s) => h << 64 - s | l >>> s - 32;
      rotrBL = (h, l, s) => h >>> s - 32 | l << 64 - s;
      add3L = (Al, Bl, Cl) => (Al >>> 0) + (Bl >>> 0) + (Cl >>> 0);
      add3H = (low, Ah, Bh, Ch) => Ah + Bh + Ch + (low / 2 ** 32 | 0) | 0;
      add4L = (Al, Bl, Cl, Dl) => (Al >>> 0) + (Bl >>> 0) + (Cl >>> 0) + (Dl >>> 0);
      add4H = (low, Ah, Bh, Ch, Dh) => Ah + Bh + Ch + Dh + (low / 2 ** 32 | 0) | 0;
      add5L = (Al, Bl, Cl, Dl, El) => (Al >>> 0) + (Bl >>> 0) + (Cl >>> 0) + (Dl >>> 0) + (El >>> 0);
      add5H = (low, Ah, Bh, Ch, Dh, Eh) => Ah + Bh + Ch + Dh + Eh + (low / 2 ** 32 | 0) | 0;
      SHA256_K = /* @__PURE__ */ Uint32Array.from([
        1116352408,
        1899447441,
        3049323471,
        3921009573,
        961987163,
        1508970993,
        2453635748,
        2870763221,
        3624381080,
        310598401,
        607225278,
        1426881987,
        1925078388,
        2162078206,
        2614888103,
        3248222580,
        3835390401,
        4022224774,
        264347078,
        604807628,
        770255983,
        1249150122,
        1555081692,
        1996064986,
        2554220882,
        2821834349,
        2952996808,
        3210313671,
        3336571891,
        3584528711,
        113926993,
        338241895,
        666307205,
        773529912,
        1294757372,
        1396182291,
        1695183700,
        1986661051,
        2177026350,
        2456956037,
        2730485921,
        2820302411,
        3259730800,
        3345764771,
        3516065817,
        3600352804,
        4094571909,
        275423344,
        430227734,
        506948616,
        659060556,
        883997877,
        958139571,
        1322822218,
        1537002063,
        1747873779,
        1955562222,
        2024104815,
        2227730452,
        2361852424,
        2428436474,
        2756734187,
        3204031479,
        3329325298
      ]);
      SHA256_W = /* @__PURE__ */ new Uint32Array(64);
      SHA256 = class extends HashMD {
        constructor(outputLen = 32) {
          super(64, outputLen, 8, false);
          this.A = SHA256_IV[0] | 0;
          this.B = SHA256_IV[1] | 0;
          this.C = SHA256_IV[2] | 0;
          this.D = SHA256_IV[3] | 0;
          this.E = SHA256_IV[4] | 0;
          this.F = SHA256_IV[5] | 0;
          this.G = SHA256_IV[6] | 0;
          this.H = SHA256_IV[7] | 0;
        }
        get() {
          const { A, B, C, D, E, F, G, H } = this;
          return [A, B, C, D, E, F, G, H];
        }
        // prettier-ignore
        set(A, B, C, D, E, F, G, H) {
          this.A = A | 0;
          this.B = B | 0;
          this.C = C | 0;
          this.D = D | 0;
          this.E = E | 0;
          this.F = F | 0;
          this.G = G | 0;
          this.H = H | 0;
        }
        process(view, offset) {
          for (let i = 0; i < 16; i++, offset += 4)
            SHA256_W[i] = view.getUint32(offset, false);
          for (let i = 16; i < 64; i++) {
            const W15 = SHA256_W[i - 15];
            const W2 = SHA256_W[i - 2];
            const s0 = rotr(W15, 7) ^ rotr(W15, 18) ^ W15 >>> 3;
            const s1 = rotr(W2, 17) ^ rotr(W2, 19) ^ W2 >>> 10;
            SHA256_W[i] = s1 + SHA256_W[i - 7] + s0 + SHA256_W[i - 16] | 0;
          }
          let { A, B, C, D, E, F, G, H } = this;
          for (let i = 0; i < 64; i++) {
            const sigma1 = rotr(E, 6) ^ rotr(E, 11) ^ rotr(E, 25);
            const T1 = H + sigma1 + Chi(E, F, G) + SHA256_K[i] + SHA256_W[i] | 0;
            const sigma0 = rotr(A, 2) ^ rotr(A, 13) ^ rotr(A, 22);
            const T2 = sigma0 + Maj(A, B, C) | 0;
            H = G;
            G = F;
            F = E;
            E = D + T1 | 0;
            D = C;
            C = B;
            B = A;
            A = T1 + T2 | 0;
          }
          A = A + this.A | 0;
          B = B + this.B | 0;
          C = C + this.C | 0;
          D = D + this.D | 0;
          E = E + this.E | 0;
          F = F + this.F | 0;
          G = G + this.G | 0;
          H = H + this.H | 0;
          this.set(A, B, C, D, E, F, G, H);
        }
        roundClean() {
          clean(SHA256_W);
        }
        destroy() {
          this.set(0, 0, 0, 0, 0, 0, 0, 0);
          clean(this.buffer);
        }
      };
      K512 = /* @__PURE__ */ (() => split([
        "0x428a2f98d728ae22",
        "0x7137449123ef65cd",
        "0xb5c0fbcfec4d3b2f",
        "0xe9b5dba58189dbbc",
        "0x3956c25bf348b538",
        "0x59f111f1b605d019",
        "0x923f82a4af194f9b",
        "0xab1c5ed5da6d8118",
        "0xd807aa98a3030242",
        "0x12835b0145706fbe",
        "0x243185be4ee4b28c",
        "0x550c7dc3d5ffb4e2",
        "0x72be5d74f27b896f",
        "0x80deb1fe3b1696b1",
        "0x9bdc06a725c71235",
        "0xc19bf174cf692694",
        "0xe49b69c19ef14ad2",
        "0xefbe4786384f25e3",
        "0x0fc19dc68b8cd5b5",
        "0x240ca1cc77ac9c65",
        "0x2de92c6f592b0275",
        "0x4a7484aa6ea6e483",
        "0x5cb0a9dcbd41fbd4",
        "0x76f988da831153b5",
        "0x983e5152ee66dfab",
        "0xa831c66d2db43210",
        "0xb00327c898fb213f",
        "0xbf597fc7beef0ee4",
        "0xc6e00bf33da88fc2",
        "0xd5a79147930aa725",
        "0x06ca6351e003826f",
        "0x142929670a0e6e70",
        "0x27b70a8546d22ffc",
        "0x2e1b21385c26c926",
        "0x4d2c6dfc5ac42aed",
        "0x53380d139d95b3df",
        "0x650a73548baf63de",
        "0x766a0abb3c77b2a8",
        "0x81c2c92e47edaee6",
        "0x92722c851482353b",
        "0xa2bfe8a14cf10364",
        "0xa81a664bbc423001",
        "0xc24b8b70d0f89791",
        "0xc76c51a30654be30",
        "0xd192e819d6ef5218",
        "0xd69906245565a910",
        "0xf40e35855771202a",
        "0x106aa07032bbd1b8",
        "0x19a4c116b8d2d0c8",
        "0x1e376c085141ab53",
        "0x2748774cdf8eeb99",
        "0x34b0bcb5e19b48a8",
        "0x391c0cb3c5c95a63",
        "0x4ed8aa4ae3418acb",
        "0x5b9cca4f7763e373",
        "0x682e6ff3d6b2b8a3",
        "0x748f82ee5defb2fc",
        "0x78a5636f43172f60",
        "0x84c87814a1f0ab72",
        "0x8cc702081a6439ec",
        "0x90befffa23631e28",
        "0xa4506cebde82bde9",
        "0xbef9a3f7b2c67915",
        "0xc67178f2e372532b",
        "0xca273eceea26619c",
        "0xd186b8c721c0c207",
        "0xeada7dd6cde0eb1e",
        "0xf57d4f7fee6ed178",
        "0x06f067aa72176fba",
        "0x0a637dc5a2c898a6",
        "0x113f9804bef90dae",
        "0x1b710b35131c471b",
        "0x28db77f523047d84",
        "0x32caab7b40c72493",
        "0x3c9ebe0a15c9bebc",
        "0x431d67c49c100d4c",
        "0x4cc5d4becb3e42b6",
        "0x597f299cfc657e2a",
        "0x5fcb6fab3ad6faec",
        "0x6c44198c4a475817"
      ].map((n) => BigInt(n))))();
      SHA512_Kh = /* @__PURE__ */ (() => K512[0])();
      SHA512_Kl = /* @__PURE__ */ (() => K512[1])();
      SHA512_W_H = /* @__PURE__ */ new Uint32Array(80);
      SHA512_W_L = /* @__PURE__ */ new Uint32Array(80);
      SHA512 = class extends HashMD {
        constructor(outputLen = 64) {
          super(128, outputLen, 16, false);
          this.Ah = SHA512_IV[0] | 0;
          this.Al = SHA512_IV[1] | 0;
          this.Bh = SHA512_IV[2] | 0;
          this.Bl = SHA512_IV[3] | 0;
          this.Ch = SHA512_IV[4] | 0;
          this.Cl = SHA512_IV[5] | 0;
          this.Dh = SHA512_IV[6] | 0;
          this.Dl = SHA512_IV[7] | 0;
          this.Eh = SHA512_IV[8] | 0;
          this.El = SHA512_IV[9] | 0;
          this.Fh = SHA512_IV[10] | 0;
          this.Fl = SHA512_IV[11] | 0;
          this.Gh = SHA512_IV[12] | 0;
          this.Gl = SHA512_IV[13] | 0;
          this.Hh = SHA512_IV[14] | 0;
          this.Hl = SHA512_IV[15] | 0;
        }
        // prettier-ignore
        get() {
          const { Ah, Al, Bh, Bl, Ch, Cl, Dh, Dl, Eh, El, Fh, Fl, Gh, Gl, Hh, Hl } = this;
          return [Ah, Al, Bh, Bl, Ch, Cl, Dh, Dl, Eh, El, Fh, Fl, Gh, Gl, Hh, Hl];
        }
        // prettier-ignore
        set(Ah, Al, Bh, Bl, Ch, Cl, Dh, Dl, Eh, El, Fh, Fl, Gh, Gl, Hh, Hl) {
          this.Ah = Ah | 0;
          this.Al = Al | 0;
          this.Bh = Bh | 0;
          this.Bl = Bl | 0;
          this.Ch = Ch | 0;
          this.Cl = Cl | 0;
          this.Dh = Dh | 0;
          this.Dl = Dl | 0;
          this.Eh = Eh | 0;
          this.El = El | 0;
          this.Fh = Fh | 0;
          this.Fl = Fl | 0;
          this.Gh = Gh | 0;
          this.Gl = Gl | 0;
          this.Hh = Hh | 0;
          this.Hl = Hl | 0;
        }
        process(view, offset) {
          for (let i = 0; i < 16; i++, offset += 4) {
            SHA512_W_H[i] = view.getUint32(offset);
            SHA512_W_L[i] = view.getUint32(offset += 4);
          }
          for (let i = 16; i < 80; i++) {
            const W15h = SHA512_W_H[i - 15] | 0;
            const W15l = SHA512_W_L[i - 15] | 0;
            const s0h = rotrSH(W15h, W15l, 1) ^ rotrSH(W15h, W15l, 8) ^ shrSH(W15h, W15l, 7);
            const s0l = rotrSL(W15h, W15l, 1) ^ rotrSL(W15h, W15l, 8) ^ shrSL(W15h, W15l, 7);
            const W2h = SHA512_W_H[i - 2] | 0;
            const W2l = SHA512_W_L[i - 2] | 0;
            const s1h = rotrSH(W2h, W2l, 19) ^ rotrBH(W2h, W2l, 61) ^ shrSH(W2h, W2l, 6);
            const s1l = rotrSL(W2h, W2l, 19) ^ rotrBL(W2h, W2l, 61) ^ shrSL(W2h, W2l, 6);
            const SUMl = add4L(s0l, s1l, SHA512_W_L[i - 7], SHA512_W_L[i - 16]);
            const SUMh = add4H(SUMl, s0h, s1h, SHA512_W_H[i - 7], SHA512_W_H[i - 16]);
            SHA512_W_H[i] = SUMh | 0;
            SHA512_W_L[i] = SUMl | 0;
          }
          let { Ah, Al, Bh, Bl, Ch, Cl, Dh, Dl, Eh, El, Fh, Fl, Gh, Gl, Hh, Hl } = this;
          for (let i = 0; i < 80; i++) {
            const sigma1h = rotrSH(Eh, El, 14) ^ rotrSH(Eh, El, 18) ^ rotrBH(Eh, El, 41);
            const sigma1l = rotrSL(Eh, El, 14) ^ rotrSL(Eh, El, 18) ^ rotrBL(Eh, El, 41);
            const CHIh = Eh & Fh ^ ~Eh & Gh;
            const CHIl = El & Fl ^ ~El & Gl;
            const T1ll = add5L(Hl, sigma1l, CHIl, SHA512_Kl[i], SHA512_W_L[i]);
            const T1h = add5H(T1ll, Hh, sigma1h, CHIh, SHA512_Kh[i], SHA512_W_H[i]);
            const T1l = T1ll | 0;
            const sigma0h = rotrSH(Ah, Al, 28) ^ rotrBH(Ah, Al, 34) ^ rotrBH(Ah, Al, 39);
            const sigma0l = rotrSL(Ah, Al, 28) ^ rotrBL(Ah, Al, 34) ^ rotrBL(Ah, Al, 39);
            const MAJh = Ah & Bh ^ Ah & Ch ^ Bh & Ch;
            const MAJl = Al & Bl ^ Al & Cl ^ Bl & Cl;
            Hh = Gh | 0;
            Hl = Gl | 0;
            Gh = Fh | 0;
            Gl = Fl | 0;
            Fh = Eh | 0;
            Fl = El | 0;
            ({ h: Eh, l: El } = add(Dh | 0, Dl | 0, T1h | 0, T1l | 0));
            Dh = Ch | 0;
            Dl = Cl | 0;
            Ch = Bh | 0;
            Cl = Bl | 0;
            Bh = Ah | 0;
            Bl = Al | 0;
            const All = add3L(T1l, sigma0l, MAJl);
            Ah = add3H(All, T1h, sigma0h, MAJh);
            Al = All | 0;
          }
          ({ h: Ah, l: Al } = add(this.Ah | 0, this.Al | 0, Ah | 0, Al | 0));
          ({ h: Bh, l: Bl } = add(this.Bh | 0, this.Bl | 0, Bh | 0, Bl | 0));
          ({ h: Ch, l: Cl } = add(this.Ch | 0, this.Cl | 0, Ch | 0, Cl | 0));
          ({ h: Dh, l: Dl } = add(this.Dh | 0, this.Dl | 0, Dh | 0, Dl | 0));
          ({ h: Eh, l: El } = add(this.Eh | 0, this.El | 0, Eh | 0, El | 0));
          ({ h: Fh, l: Fl } = add(this.Fh | 0, this.Fl | 0, Fh | 0, Fl | 0));
          ({ h: Gh, l: Gl } = add(this.Gh | 0, this.Gl | 0, Gh | 0, Gl | 0));
          ({ h: Hh, l: Hl } = add(this.Hh | 0, this.Hl | 0, Hh | 0, Hl | 0));
          this.set(Ah, Al, Bh, Bl, Ch, Cl, Dh, Dl, Eh, El, Fh, Fl, Gh, Gl, Hh, Hl);
        }
        roundClean() {
          clean(SHA512_W_H, SHA512_W_L);
        }
        destroy() {
          clean(this.buffer);
          this.set(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0);
        }
      };
      SHA384 = class extends SHA512 {
        constructor() {
          super(48);
          this.Ah = SHA384_IV[0] | 0;
          this.Al = SHA384_IV[1] | 0;
          this.Bh = SHA384_IV[2] | 0;
          this.Bl = SHA384_IV[3] | 0;
          this.Ch = SHA384_IV[4] | 0;
          this.Cl = SHA384_IV[5] | 0;
          this.Dh = SHA384_IV[6] | 0;
          this.Dl = SHA384_IV[7] | 0;
          this.Eh = SHA384_IV[8] | 0;
          this.El = SHA384_IV[9] | 0;
          this.Fh = SHA384_IV[10] | 0;
          this.Fl = SHA384_IV[11] | 0;
          this.Gh = SHA384_IV[12] | 0;
          this.Gl = SHA384_IV[13] | 0;
          this.Hh = SHA384_IV[14] | 0;
          this.Hl = SHA384_IV[15] | 0;
        }
      };
      sha256 = /* @__PURE__ */ createHasher(() => new SHA256());
      sha512 = /* @__PURE__ */ createHasher(() => new SHA512());
      sha384 = /* @__PURE__ */ createHasher(() => new SHA384());
      HMAC = class extends Hash {
        constructor(hash, _key) {
          super();
          this.finished = false;
          this.destroyed = false;
          ahash(hash);
          const key = toBytes(_key);
          this.iHash = hash.create();
          if (typeof this.iHash.update !== "function")
            throw new Error("Expected instance of class which extends utils.Hash");
          this.blockLen = this.iHash.blockLen;
          this.outputLen = this.iHash.outputLen;
          const blockLen = this.blockLen;
          const pad = new Uint8Array(blockLen);
          pad.set(key.length > blockLen ? hash.create().update(key).digest() : key);
          for (let i = 0; i < pad.length; i++)
            pad[i] ^= 54;
          this.iHash.update(pad);
          this.oHash = hash.create();
          for (let i = 0; i < pad.length; i++)
            pad[i] ^= 54 ^ 92;
          this.oHash.update(pad);
          clean(pad);
        }
        update(buf) {
          aexists(this);
          this.iHash.update(buf);
          return this;
        }
        digestInto(out) {
          aexists(this);
          abytes(out, this.outputLen);
          this.finished = true;
          this.iHash.digestInto(out);
          this.oHash.update(out);
          this.oHash.digestInto(out);
          this.destroy();
        }
        digest() {
          const out = new Uint8Array(this.oHash.outputLen);
          this.digestInto(out);
          return out;
        }
        _cloneInto(to) {
          to || (to = Object.create(Object.getPrototypeOf(this), {}));
          const { oHash, iHash, finished, destroyed, blockLen, outputLen } = this;
          to = to;
          to.finished = finished;
          to.destroyed = destroyed;
          to.blockLen = blockLen;
          to.outputLen = outputLen;
          to.oHash = oHash._cloneInto(to.oHash);
          to.iHash = iHash._cloneInto(to.iHash);
          return to;
        }
        clone() {
          return this._cloneInto();
        }
        destroy() {
          this.destroyed = true;
          this.oHash.destroy();
          this.iHash.destroy();
        }
      };
      hmac = (hash, key, message) => new HMAC(hash, key).update(message).digest();
      hmac.create = (hash, key) => new HMAC(hash, key);
      _0n$1 = BigInt(0);
      _1n$1 = BigInt(1);
      pointPrecomputes = /* @__PURE__ */ new WeakMap();
      pointWindowSizes = /* @__PURE__ */ new WeakMap();
      wNAF = class {
        // Parametrized with a given Point class (not individual point)
        constructor(Point, bits) {
          this.BASE = Point.BASE;
          this.ZERO = Point.ZERO;
          this.Fn = Point.Fn;
          this.bits = bits;
        }
        // non-const time multiplication ladder
        _unsafeLadder(elm, n, p = this.ZERO) {
          let d = elm;
          while (n > _0n$1) {
            if (n & _1n$1)
              p = p.add(d);
            d = d.double();
            n >>= _1n$1;
          }
          return p;
        }
        /**
         * Creates a wNAF precomputation window. Used for caching.
         * Default window size is set by `utils.precompute()` and is equal to 8.
         * Number of precomputed points depends on the curve size:
         * 2^(𝑊−1) * (Math.ceil(𝑛 / 𝑊) + 1), where:
         * - 𝑊 is the window size
         * - 𝑛 is the bitlength of the curve order.
         * For a 256-bit curve and window size 8, the number of precomputed points is 128 * 33 = 4224.
         * @param point Point instance
         * @param W window size
         * @returns precomputed point tables flattened to a single array
         */
        precomputeWindow(point, W) {
          const { windows, windowSize } = calcWOpts(W, this.bits);
          const points = [];
          let p = point;
          let base = p;
          for (let window2 = 0; window2 < windows; window2++) {
            base = p;
            points.push(base);
            for (let i = 1; i < windowSize; i++) {
              base = base.add(p);
              points.push(base);
            }
            p = base.double();
          }
          return points;
        }
        /**
         * Implements ec multiplication using precomputed tables and w-ary non-adjacent form.
         * More compact implementation:
         * https://github.com/paulmillr/noble-secp256k1/blob/47cb1669b6e506ad66b35fe7d76132ae97465da2/index.ts#L502-L541
         * @returns real and fake (for const-time) points
         */
        wNAF(W, precomputes, n) {
          if (!this.Fn.isValid(n))
            throw new Error("invalid scalar");
          let p = this.ZERO;
          let f = this.BASE;
          const wo = calcWOpts(W, this.bits);
          for (let window2 = 0; window2 < wo.windows; window2++) {
            const { nextN, offset, isZero, isNeg, isNegF, offsetF } = calcOffsets(n, window2, wo);
            n = nextN;
            if (isZero) {
              f = f.add(negateCt(isNegF, precomputes[offsetF]));
            } else {
              p = p.add(negateCt(isNeg, precomputes[offset]));
            }
          }
          assert0(n);
          return { p, f };
        }
        /**
         * Implements ec unsafe (non const-time) multiplication using precomputed tables and w-ary non-adjacent form.
         * @param acc accumulator point to add result of multiplication
         * @returns point
         */
        wNAFUnsafe(W, precomputes, n, acc = this.ZERO) {
          const wo = calcWOpts(W, this.bits);
          for (let window2 = 0; window2 < wo.windows; window2++) {
            if (n === _0n$1)
              break;
            const { nextN, offset, isZero, isNeg } = calcOffsets(n, window2, wo);
            n = nextN;
            if (isZero) {
              continue;
            } else {
              const item = precomputes[offset];
              acc = acc.add(isNeg ? item.negate() : item);
            }
          }
          assert0(n);
          return acc;
        }
        getPrecomputes(W, point, transform) {
          let comp = pointPrecomputes.get(point);
          if (!comp) {
            comp = this.precomputeWindow(point, W);
            if (W !== 1) {
              if (typeof transform === "function")
                comp = transform(comp);
              pointPrecomputes.set(point, comp);
            }
          }
          return comp;
        }
        cached(point, scalar, transform) {
          const W = getW(point);
          return this.wNAF(W, this.getPrecomputes(W, point, transform), scalar);
        }
        unsafe(point, scalar, transform, prev) {
          const W = getW(point);
          if (W === 1)
            return this._unsafeLadder(point, scalar, prev);
          return this.wNAFUnsafe(W, this.getPrecomputes(W, point, transform), scalar, prev);
        }
        // We calculate precomputes for elliptic curve point multiplication
        // using windowed method. This specifies window size and
        // stores precomputed values. Usually only base point would be precomputed.
        createCache(P, W) {
          validateW(W, this.bits);
          pointWindowSizes.set(P, W);
          pointPrecomputes.delete(P);
        }
        hasCache(elm) {
          return getW(elm) !== 1;
        }
      };
      divNearest = (num, den) => (num + (num >= 0 ? den : -den) / _2n) / den;
      DERErr = class extends Error {
        constructor(m = "") {
          super(m);
        }
      };
      DER = {
        // asn.1 DER encoding utils
        Err: DERErr,
        // Basic building block is TLV (Tag-Length-Value)
        _tlv: {
          encode: (tag, data) => {
            const { Err: E } = DER;
            if (tag < 0 || tag > 256)
              throw new E("tlv.encode: wrong tag");
            if (data.length & 1)
              throw new E("tlv.encode: unpadded data");
            const dataLen = data.length / 2;
            const len = numberToHexUnpadded(dataLen);
            if (len.length / 2 & 128)
              throw new E("tlv.encode: long form length too big");
            const lenLen = dataLen > 127 ? numberToHexUnpadded(len.length / 2 | 128) : "";
            const t = numberToHexUnpadded(tag);
            return t + lenLen + len + data;
          },
          // v - value, l - left bytes (unparsed)
          decode(tag, data) {
            const { Err: E } = DER;
            let pos = 0;
            if (tag < 0 || tag > 256)
              throw new E("tlv.encode: wrong tag");
            if (data.length < 2 || data[pos++] !== tag)
              throw new E("tlv.decode: wrong tlv");
            const first = data[pos++];
            const isLong = !!(first & 128);
            let length = 0;
            if (!isLong)
              length = first;
            else {
              const lenLen = first & 127;
              if (!lenLen)
                throw new E("tlv.decode(long): indefinite length not supported");
              if (lenLen > 4)
                throw new E("tlv.decode(long): byte length is too big");
              const lengthBytes = data.subarray(pos, pos + lenLen);
              if (lengthBytes.length !== lenLen)
                throw new E("tlv.decode: length bytes not complete");
              if (lengthBytes[0] === 0)
                throw new E("tlv.decode(long): zero leftmost byte");
              for (const b of lengthBytes)
                length = length << 8 | b;
              pos += lenLen;
              if (length < 128)
                throw new E("tlv.decode(long): not minimal encoding");
            }
            const v = data.subarray(pos, pos + length);
            if (v.length !== length)
              throw new E("tlv.decode: wrong value length");
            return { v, l: data.subarray(pos + length) };
          }
        },
        // https://crypto.stackexchange.com/a/57734 Leftmost bit of first byte is 'negative' flag,
        // since we always use positive integers here. It must always be empty:
        // - add zero byte if exists
        // - if next byte doesn't have a flag, leading zero is not allowed (minimal encoding)
        _int: {
          encode(num) {
            const { Err: E } = DER;
            if (num < _0n)
              throw new E("integer: negative integers are not allowed");
            let hex = numberToHexUnpadded(num);
            if (Number.parseInt(hex[0], 16) & 8)
              hex = "00" + hex;
            if (hex.length & 1)
              throw new E("unexpected DER parsing assertion: unpadded hex");
            return hex;
          },
          decode(data) {
            const { Err: E } = DER;
            if (data[0] & 128)
              throw new E("invalid signature integer: negative");
            if (data[0] === 0 && !(data[1] & 128))
              throw new E("invalid signature integer: unnecessary leading zero");
            return bytesToNumberBE(data);
          }
        },
        toSig(hex) {
          const { Err: E, _int: int, _tlv: tlv } = DER;
          const data = ensureBytes("signature", hex);
          const { v: seqBytes, l: seqLeftBytes } = tlv.decode(48, data);
          if (seqLeftBytes.length)
            throw new E("invalid signature: left bytes after parsing");
          const { v: rBytes, l: rLeftBytes } = tlv.decode(2, seqBytes);
          const { v: sBytes, l: sLeftBytes } = tlv.decode(2, rLeftBytes);
          if (sLeftBytes.length)
            throw new E("invalid signature: left bytes after parsing");
          return { r: int.decode(rBytes), s: int.decode(sBytes) };
        },
        hexFromSig(sig) {
          const { _tlv: tlv, _int: int } = DER;
          const rs = tlv.encode(2, int.encode(sig.r));
          const ss = tlv.encode(2, int.encode(sig.s));
          const seq = rs + ss;
          return tlv.encode(48, seq);
        }
      };
      _0n = BigInt(0);
      _1n = BigInt(1);
      _2n = BigInt(2);
      _3n = BigInt(3);
      _4n = BigInt(4);
      p256_CURVE = {
        p: BigInt("0xffffffff00000001000000000000000000000000ffffffffffffffffffffffff"),
        n: BigInt("0xffffffff00000000ffffffffffffffffbce6faada7179e84f3b9cac2fc632551"),
        h: BigInt(1),
        a: BigInt("0xffffffff00000001000000000000000000000000fffffffffffffffffffffffc"),
        b: BigInt("0x5ac635d8aa3a93e7b3ebbd55769886bc651d06b0cc53b0f63bce3c3e27d2604b"),
        Gx: BigInt("0x6b17d1f2e12c4247f8bce6e563a440f277037d812deb33a0f4a13945d898c296"),
        Gy: BigInt("0x4fe342e2fe1a7f9b8ee7eb4a7c0f9e162bce33576b315ececbb6406837bf51f5")
      };
      p384_CURVE = {
        p: BigInt("0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffeffffffff0000000000000000ffffffff"),
        n: BigInt("0xffffffffffffffffffffffffffffffffffffffffffffffffc7634d81f4372ddf581a0db248b0a77aecec196accc52973"),
        h: BigInt(1),
        a: BigInt("0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffeffffffff0000000000000000fffffffc"),
        b: BigInt("0xb3312fa7e23ee7e4988e056be3f82d19181d9c6efe8141120314088f5013875ac656398d8a2ed19d2a85c8edd3ec2aef"),
        Gx: BigInt("0xaa87ca22be8b05378eb1c71ef320ad746e1d3b628ba79b9859f741e082542a385502f25dbf55296c3a545e3872760ab7"),
        Gy: BigInt("0x3617de4a96262c6f5d9e98bf9292dc29f8f41dbd289a147ce9da3113b5f0b8c00a60b1ce1d7e819d7a431d7c90ea0e5f")
      };
      p521_CURVE = {
        p: BigInt("0x1ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"),
        n: BigInt("0x01fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffa51868783bf2f966b7fcc0148f709a5d03bb5c9b8899c47aebb6fb71e91386409"),
        h: BigInt(1),
        a: BigInt("0x1fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc"),
        b: BigInt("0x0051953eb9618e1c9a1f929a21a0b68540eea2da725b99b315f3b8b489918ef109e156193951ec7e937b1652c0bd3bb1bf073573df883d2c34f1ef451fd46b503f00"),
        Gx: BigInt("0x00c6858e06b70404e9cd9e3ecb662395b4429c648139053fb521f828af606b4d3dbaa14b5e77efe75928fe1dc127a2ffa8de3348b3c1856a429bf97e7e31c2e5bd66"),
        Gy: BigInt("0x011839296a789a3bc0045c8a5fb42c7d1bd998f54449579b446817afbd17273e662c97ee72995ef42640c550b9013fad0761353c7086a272c24088be94769fd16650")
      };
      Fp256 = Field(p256_CURVE.p);
      Fp384 = Field(p384_CURVE.p);
      Fp521 = Field(p521_CURVE.p);
      p256$1 = createCurve({ ...p256_CURVE, Fp: Fp256, lowS: false }, sha256);
      createCurve({ ...p384_CURVE, Fp: Fp384, lowS: false }, sha384);
      createCurve({ ...p521_CURVE, Fp: Fp521, lowS: false, allowedPrivateKeyLengths: [130, 131, 132] }, sha512);
      p256 = p256$1;
      isBrowser = typeof window !== "undefined" && typeof document !== "undefined";
      TEXT_ENCODER = new TextEncoder();
      TEXT_DECODER = new TextDecoder();
      SECURITY_CONFIG = {
        PBKDF2_ITERATIONS: 1e5,
        AES_KEY_LENGTH: 256,
        CURVE: "P-256",
        HASH_ALGORITHM: "SHA-256",
        SUPPORTED_FORMATS: ["JWK", "PEM"],
        MIN_POW_DIFFICULTY: 1,
        MAX_POW_DIFFICULTY: 8
      };
    }
  });

  // node_modules/ws/browser.js
  var require_browser = __commonJS({
    "node_modules/ws/browser.js"(exports, module) {
      "use strict";
      module.exports = function() {
        throw new Error(
          "ws does not work in the browser. Browser clients must use the native WebSocket object"
        );
      };
    }
  });

  // src/browser-entry.js
  var browser_entry_exports = {};
  __export(browser_entry_exports, {
    DebugLogger: () => DebugLogger_default,
    DistributedStorageManager: () => DistributedStorageManager,
    EnvironmentDetector: () => EnvironmentDetector,
    PeerConnection: () => PeerConnection,
    PeerPigeonMesh: () => PeerPigeonMesh,
    SignalingClient: () => SignalingClient,
    WebDHT: () => WebDHT,
    environmentDetector: () => environmentDetector,
    getEnvironmentReport: () => getEnvironmentReport,
    hasWebRTC: () => hasWebRTC,
    hasWebSocket: () => hasWebSocket,
    initWebRTCAsync: () => initWebRTCAsync,
    isBrowser: () => isBrowser2,
    isNodeJS: () => isNodeJS,
    isWorker: () => isWorker
  });
  init_unsea();

  // src/PigeonRTC-browser.js
  var RTCAdapter = class {
    getRTCPeerConnection() {
      throw new Error("getRTCPeerConnection must be implemented by adapter");
    }
    getRTCSessionDescription() {
      throw new Error("getRTCSessionDescription must be implemented by adapter");
    }
    getRTCIceCandidate() {
      throw new Error("getRTCIceCandidate must be implemented by adapter");
    }
    getMediaStream() {
      return null;
    }
    isSupported() {
      throw new Error("isSupported must be implemented by adapter");
    }
    getName() {
      throw new Error("getName must be implemented by adapter");
    }
    async initialize() {
    }
    async getUserMedia(_constraints) {
      throw new Error("getUserMedia not supported by this adapter");
    }
    async getDisplayMedia(_constraints) {
      throw new Error("getDisplayMedia not supported by this adapter");
    }
  };
  var BrowserRTCAdapter = class extends RTCAdapter {
    constructor() {
      super();
      this._checkSupport();
    }
    _checkSupport() {
      if (typeof window === "undefined" || typeof navigator === "undefined") {
        return;
      }
      this.hasRTCPeerConnection = !!(window.RTCPeerConnection || window.webkitRTCPeerConnection || window.mozRTCPeerConnection);
      this.hasGetUserMedia = !!(navigator.mediaDevices?.getUserMedia || navigator.getUserMedia || navigator.webkitGetUserMedia || navigator.mozGetUserMedia);
      this.hasGetDisplayMedia = !!navigator.mediaDevices?.getDisplayMedia;
    }
    getRTCPeerConnection() {
      if (typeof window === "undefined") {
        throw new Error("BrowserRTCAdapter requires a browser environment");
      }
      return window.RTCPeerConnection || window.webkitRTCPeerConnection || window.mozRTCPeerConnection;
    }
    getRTCSessionDescription() {
      if (typeof window === "undefined") {
        throw new Error("BrowserRTCAdapter requires a browser environment");
      }
      return window.RTCSessionDescription || window.mozRTCSessionDescription;
    }
    getRTCIceCandidate() {
      if (typeof window === "undefined") {
        throw new Error("BrowserRTCAdapter requires a browser environment");
      }
      return window.RTCIceCandidate || window.mozRTCIceCandidate;
    }
    getMediaStream() {
      if (typeof window === "undefined") {
        return null;
      }
      return window.MediaStream || window.webkitMediaStream;
    }
    isSupported() {
      return typeof window !== "undefined" && this.hasRTCPeerConnection;
    }
    getName() {
      return "BrowserRTCAdapter";
    }
    async getUserMedia(constraints) {
      if (typeof navigator === "undefined") {
        throw new Error("getUserMedia requires a browser environment");
      }
      if (navigator.mediaDevices?.getUserMedia) {
        return await navigator.mediaDevices.getUserMedia(constraints);
      }
      const getUserMedia = navigator.getUserMedia || navigator.webkitGetUserMedia || navigator.mozGetUserMedia;
      if (!getUserMedia) {
        throw new Error("getUserMedia is not supported in this browser");
      }
      return new Promise((resolve, reject) => {
        getUserMedia.call(navigator, constraints, resolve, reject);
      });
    }
    async getDisplayMedia(constraints) {
      if (typeof navigator === "undefined") {
        throw new Error("getDisplayMedia requires a browser environment");
      }
      if (!navigator.mediaDevices?.getDisplayMedia) {
        throw new Error("getDisplayMedia is not supported in this browser");
      }
      return await navigator.mediaDevices.getDisplayMedia(constraints);
    }
  };
  var PigeonRTC = class {
    constructor(options = {}) {
      this.adapter = options.adapter || null;
      this.initialized = false;
    }
    async initialize(options = {}) {
      if (this.initialized) {
        return;
      }
      if (options.adapter) {
        this.adapter = options.adapter;
      }
      if (!this.adapter) {
        this.adapter = new BrowserRTCAdapter();
      }
      await this.adapter.initialize();
      this.initialized = true;
    }
    _ensureInitialized() {
      if (!this.initialized || !this.adapter) {
        throw new Error("PigeonRTC not initialized. Call initialize() first.");
      }
    }
    getRTCPeerConnection() {
      this._ensureInitialized();
      return this.adapter.getRTCPeerConnection();
    }
    getRTCSessionDescription() {
      this._ensureInitialized();
      return this.adapter.getRTCSessionDescription();
    }
    getRTCIceCandidate() {
      this._ensureInitialized();
      return this.adapter.getRTCIceCandidate();
    }
    getMediaStream() {
      this._ensureInitialized();
      return this.adapter.getMediaStream();
    }
    createPeerConnection(config) {
      this._ensureInitialized();
      const RTCPeerConnection2 = this.adapter.getRTCPeerConnection();
      return new RTCPeerConnection2(config);
    }
    createSessionDescription(init) {
      this._ensureInitialized();
      const RTCSessionDescription = this.adapter.getRTCSessionDescription();
      return new RTCSessionDescription(init);
    }
    createIceCandidate(init) {
      this._ensureInitialized();
      const RTCIceCandidate = this.adapter.getRTCIceCandidate();
      return new RTCIceCandidate(init);
    }
    async getUserMedia(constraints) {
      this._ensureInitialized();
      return await this.adapter.getUserMedia(constraints);
    }
    async getDisplayMedia(constraints) {
      this._ensureInitialized();
      return await this.adapter.getDisplayMedia(constraints);
    }
    isSupported() {
      return this.adapter ? this.adapter.isSupported() : false;
    }
    getAdapterName() {
      return this.adapter ? this.adapter.getName() : "None";
    }
  };
  async function createPigeonRTC(options = {}) {
    const rtc = new PigeonRTC(options);
    await rtc.initialize(options);
    return rtc;
  }

  // src/EventEmitter.js
  var EventEmitter = class {
    constructor() {
      this.eventListeners = {};
    }
    addEventListener(event, callback) {
      if (!this.eventListeners[event]) {
        this.eventListeners[event] = [];
      }
      this.eventListeners[event].push(callback);
    }
    removeEventListener(event, callback) {
      if (this.eventListeners[event]) {
        const index = this.eventListeners[event].indexOf(callback);
        if (index > -1) {
          this.eventListeners[event].splice(index, 1);
        }
      }
    }
    emit(event, data) {
      if (this.eventListeners[event]) {
        this.eventListeners[event].forEach((callback) => callback(data));
      }
    }
    // Standard Node.js EventEmitter compatible methods
    /**
     * Add event listener (alias for addEventListener)
     * @param {string} event - Event name
     * @param {Function} callback - Event handler
     * @returns {EventEmitter} Returns this for chaining
     */
    on(event, callback) {
      this.addEventListener(event, callback);
      return this;
    }
    /**
     * Remove event listener (alias for removeEventListener)
     * @param {string} event - Event name
     * @param {Function} callback - Event handler to remove
     * @returns {EventEmitter} Returns this for chaining
     */
    off(event, callback) {
      this.removeEventListener(event, callback);
      return this;
    }
    /**
     * Add one-time event listener
     * @param {string} event - Event name
     * @param {Function} callback - Event handler
     * @returns {EventEmitter} Returns this for chaining
     */
    once(event, callback) {
      const onceWrapper = (data) => {
        callback(data);
        this.removeEventListener(event, onceWrapper);
      };
      this.addEventListener(event, onceWrapper);
      return this;
    }
    /**
     * Remove all listeners for an event, or all listeners if no event specified
     * @param {string} [event] - Event name (optional)
     * @returns {EventEmitter} Returns this for chaining
     */
    removeAllListeners(event) {
      if (event) {
        delete this.eventListeners[event];
      } else {
        this.eventListeners = {};
      }
      return this;
    }
    /**
     * Get array of listeners for an event
     * @param {string} event - Event name
     * @returns {Function[]} Array of listeners
     */
    listeners(event) {
      return this.eventListeners[event] ? [...this.eventListeners[event]] : [];
    }
    /**
     * Get count of listeners for an event
     * @param {string} event - Event name
     * @returns {number} Number of listeners
     */
    listenerCount(event) {
      return this.eventListeners[event] ? this.eventListeners[event].length : 0;
    }
    /**
     * Get array of event names that have listeners
     * @returns {string[]} Array of event names
     */
    eventNames() {
      return Object.keys(this.eventListeners);
    }
  };

  // src/EnvironmentDetector.js
  var EnvironmentDetector = class _EnvironmentDetector {
    constructor() {
      this._cache = /* @__PURE__ */ new Map();
      this._pigeonRTC = null;
      this._init();
    }
    _init() {
      this._cache.set("isBrowser", this._detectBrowser());
      this._cache.set("isNodeJS", this._detectNodeJS());
      this._cache.set("isWorker", this._detectWorker());
      this._cache.set("isServiceWorker", this._detectServiceWorker());
      this._cache.set("isWebWorker", this._detectWebWorker());
      this._cache.set("isSharedWorker", this._detectSharedWorker());
      this._cache.set("isDeno", this._detectDeno());
      this._cache.set("isBun", this._detectBun());
      this._cache.set("isNativeScript", this._detectNativeScript());
      this._initPigeonRTC();
    }
    /**
     * Initialize PigeonRTC for cross-platform WebRTC support
     * PigeonRTC provides a unified WebRTC interface across browser and Node.js
     * @private
     */
    _initPigeonRTC() {
      try {
        this._webrtcPolyfilled = false;
      } catch (error) {
      }
    }
    /**
     * Get the PigeonRTC instance
     * @returns {PigeonRTC|null} The PigeonRTC instance or null if not initialized
     */
    getPigeonRTC() {
      return this._pigeonRTC;
    }
    // Primary environment detection
    _detectBrowser() {
      return typeof window !== "undefined" && typeof document !== "undefined" && typeof navigator !== "undefined";
    }
    _detectNodeJS() {
      return typeof process !== "undefined" && process.versions != null && process.versions.node != null;
    }
    _detectDeno() {
      return typeof Deno !== "undefined";
    }
    _detectBun() {
      return typeof Bun !== "undefined";
    }
    _detectNativeScript() {
      return typeof globalThis !== "undefined" && (typeof globalThis.NativeScriptGlobals !== "undefined" || typeof globalThis.__ANDROID__ !== "undefined" || typeof globalThis.__IOS__ !== "undefined" || typeof globalThis.__VISIONOS__ !== "undefined" || typeof globalThis.loadModule === "function" && typeof globalThis.registerModule === "function");
    }
    _detectWorker() {
      return typeof importScripts !== "undefined" || this._detectServiceWorker() || this._detectWebWorker() || this._detectSharedWorker();
    }
    _detectServiceWorker() {
      return typeof globalThis.ServiceWorkerGlobalScope !== "undefined" && typeof self !== "undefined" && self instanceof globalThis.ServiceWorkerGlobalScope;
    }
    _detectWebWorker() {
      return typeof globalThis.DedicatedWorkerGlobalScope !== "undefined" && typeof self !== "undefined" && self instanceof globalThis.DedicatedWorkerGlobalScope;
    }
    _detectSharedWorker() {
      return typeof globalThis.SharedWorkerGlobalScope !== "undefined" && typeof self !== "undefined" && self instanceof globalThis.SharedWorkerGlobalScope;
    }
    // Public getters
    get isBrowser() {
      return this._cache.get("isBrowser");
    }
    get isNodeJS() {
      return this._cache.get("isNodeJS");
    }
    get isWorker() {
      return this._cache.get("isWorker");
    }
    get isServiceWorker() {
      return this._cache.get("isServiceWorker");
    }
    get isWebWorker() {
      return this._cache.get("isWebWorker");
    }
    get isSharedWorker() {
      return this._cache.get("isSharedWorker");
    }
    get isDeno() {
      return this._cache.get("isDeno");
    }
    get isBun() {
      return this._cache.get("isBun");
    }
    get isNativeScript() {
      return this._cache.get("isNativeScript");
    }
    get isServer() {
      return this.isNodeJS || this.isDeno || this.isBun;
    }
    get isClient() {
      return this.isBrowser || this.isWorker || this.isNativeScript;
    }
    // WebRTC capability detection
    get hasWebRTC() {
      if (this._pigeonRTC) {
        return this._pigeonRTC.isSupported();
      }
      if (this.isBrowser) {
        return typeof RTCPeerConnection !== "undefined" || typeof webkitRTCPeerConnection !== "undefined" || typeof mozRTCPeerConnection !== "undefined";
      }
      if (this.isNodeJS) {
        return typeof globalThis !== "undefined" && typeof globalThis.RTCPeerConnection !== "undefined" || typeof RTCPeerConnection !== "undefined" || this._webrtcPolyfilled === true;
      }
      if (this.isNativeScript) {
        return typeof globalThis !== "undefined" && typeof globalThis.RTCPeerConnection !== "undefined" || typeof RTCPeerConnection !== "undefined";
      }
      return false;
    }
    get hasDataChannel() {
      return this.hasWebRTC && (this.isBrowser || this.isNativeScript);
    }
    get hasGetUserMedia() {
      if (!this.isBrowser && !this.isNativeScript) return false;
      return !!(navigator && navigator.mediaDevices && navigator.mediaDevices.getUserMedia) || !!(navigator && (navigator.getUserMedia || navigator.webkitGetUserMedia || navigator.mozGetUserMedia || navigator.msGetUserMedia));
    }
    // WebSocket capability detection
    get hasWebSocket() {
      if (this.isBrowser || this.isWorker) {
        return typeof WebSocket !== "undefined";
      }
      if (this.isNodeJS) {
        if (typeof globalThis !== "undefined" && typeof globalThis.WebSocket !== "undefined") {
          return true;
        }
        if (typeof WebSocket !== "undefined") {
          return true;
        }
        try {
          if (typeof __require !== "undefined") {
            __require.resolve("ws");
            return true;
          } else {
            return false;
          }
        } catch (e) {
          return false;
        }
      }
      if (this.isNativeScript) {
        return typeof WebSocket !== "undefined" || typeof globalThis !== "undefined" && typeof globalThis.WebSocket !== "undefined";
      }
      return false;
    }
    // Storage capability detection
    get hasLocalStorage() {
      if (!this.isBrowser && !this.isNativeScript) return false;
      try {
        const test = "__storage_test__";
        localStorage.setItem(test, test);
        localStorage.removeItem(test);
        return true;
      } catch (e) {
        return false;
      }
    }
    get hasSessionStorage() {
      if (!this.isBrowser && !this.isNativeScript) return false;
      try {
        const test = "__storage_test__";
        sessionStorage.setItem(test, test);
        sessionStorage.removeItem(test);
        return true;
      } catch (e) {
        return false;
      }
    }
    get hasIndexedDB() {
      if (!this.isBrowser) return false;
      return typeof indexedDB !== "undefined";
    }
    get hasCookies() {
      if (!this.isBrowser) return false;
      return typeof document !== "undefined" && typeof document.cookie === "string";
    }
    // Network and connectivity detection
    get hasNetworkInformation() {
      if (!this.isBrowser) return false;
      return typeof navigator !== "undefined" && "connection" in navigator;
    }
    get isOnline() {
      if (this.isBrowser) {
        return typeof navigator !== "undefined" ? navigator.onLine : true;
      }
      return true;
    }
    get networkType() {
      if (this.hasNetworkInformation) {
        return navigator.connection.effectiveType || "unknown";
      }
      return "unknown";
    }
    // Crypto capabilities
    get hasCrypto() {
      if (this.isBrowser || this.isWorker) {
        return typeof crypto !== "undefined" && typeof crypto.subtle !== "undefined";
      }
      if (this.isNodeJS) {
        try {
          if (typeof __require !== "undefined") {
            __require("crypto");
            return true;
          } else {
            return typeof process !== "undefined" && process.versions.node;
          }
        } catch (e) {
          return false;
        }
      }
      if (this.isNativeScript) {
        return typeof crypto !== "undefined" && typeof crypto.subtle !== "undefined" || typeof globalThis !== "undefined" && typeof globalThis.crypto !== "undefined";
      }
      return false;
    }
    get hasRandomValues() {
      if (this.isBrowser || this.isWorker) {
        return typeof crypto !== "undefined" && typeof crypto.getRandomValues === "function";
      }
      if (this.isNodeJS) {
        try {
          if (typeof __require !== "undefined") {
            const crypto3 = __require("crypto");
            return typeof crypto3.randomBytes === "function";
          } else {
            return typeof process !== "undefined" && process.versions.node;
          }
        } catch (e) {
          return false;
        }
      }
      if (this.isNativeScript) {
        return typeof crypto !== "undefined" && typeof crypto.getRandomValues === "function" || typeof globalThis !== "undefined" && typeof globalThis.crypto !== "undefined" && typeof globalThis.crypto.getRandomValues === "function";
      }
      return false;
    }
    // Performance and timing
    get hasPerformanceNow() {
      return typeof performance !== "undefined" && typeof performance.now === "function";
    }
    get hasHighResolutionTime() {
      if (this.isNodeJS) {
        return typeof process.hrtime === "function" || typeof process.hrtime.bigint === "function";
      }
      return this.hasPerformanceNow;
    }
    // Browser-specific detection
    getBrowserInfo() {
      if (!this.isBrowser) return null;
      const userAgent = navigator.userAgent;
      const browsers = {
        chrome: /Chrome\/(\d+)/.exec(userAgent),
        firefox: /Firefox\/(\d+)/.exec(userAgent),
        safari: /Safari\/(\d+)/.exec(userAgent) && !/Chrome/.test(userAgent),
        edge: /Edge\/(\d+)/.exec(userAgent),
        ie: /MSIE (\d+)/.exec(userAgent) || /Trident.*rv:(\d+)/.exec(userAgent)
      };
      for (const [browser, match] of Object.entries(browsers)) {
        if (match) {
          return {
            name: browser,
            version: match[1] || "unknown"
          };
        }
      }
      return { name: "unknown", version: "unknown" };
    }
    // Node.js-specific detection
    getNodeInfo() {
      if (!this.isNodeJS) return null;
      return {
        version: process.version,
        platform: process.platform,
        arch: process.arch,
        versions: process.versions
      };
    }
    // NativeScript-specific detection
    getNativeScriptInfo() {
      if (!this.isNativeScript) return null;
      const info = {
        runtime: "nativescript"
      };
      if (typeof globalThis !== "undefined") {
        if (globalThis.__ANDROID__) {
          info.platform = "android";
        } else if (globalThis.__IOS__) {
          info.platform = "ios";
        } else if (globalThis.__VISIONOS__) {
          info.platform = "visionos";
        }
        if (globalThis.NativeScriptGlobals) {
          info.launched = globalThis.NativeScriptGlobals.launched;
          info.appInstanceReady = globalThis.NativeScriptGlobals.appInstanceReady;
        }
      }
      return info;
    }
    // Device and platform detection
    getPlatformInfo() {
      if (this.isBrowser) {
        return {
          userAgent: navigator.userAgent,
          platform: navigator.platform,
          language: navigator.language,
          languages: navigator.languages || [navigator.language],
          cookieEnabled: navigator.cookieEnabled,
          onLine: navigator.onLine,
          hardwareConcurrency: navigator.hardwareConcurrency || 1
        };
      }
      if (this.isNodeJS) {
        return this.getNodeInfo();
      }
      if (this.isNativeScript) {
        return this.getNativeScriptInfo();
      }
      return null;
    }
    // Feature detection for specific APIs
    hasFeature(feature) {
      const features = {
        // WebRTC
        webrtc: () => this.hasWebRTC,
        datachannel: () => this.hasDataChannel,
        getusermedia: () => this.hasGetUserMedia,
        // WebSocket
        websocket: () => this.hasWebSocket,
        // Storage
        localstorage: () => this.hasLocalStorage,
        sessionstorage: () => this.hasSessionStorage,
        indexeddb: () => this.hasIndexedDB,
        cookies: () => this.hasCookies,
        // Crypto
        crypto: () => this.hasCrypto,
        randomvalues: () => this.hasRandomValues,
        // Performance
        performance: () => this.hasPerformanceNow,
        hrtime: () => this.hasHighResolutionTime,
        // Network
        networkinfo: () => this.hasNetworkInformation,
        online: () => this.isOnline,
        // Workers
        webworker: () => typeof Worker !== "undefined",
        serviceworker: () => typeof navigator !== "undefined" && "serviceWorker" in navigator,
        sharedworker: () => typeof SharedWorker !== "undefined"
      };
      const featureCheck = features[feature.toLowerCase()];
      return featureCheck ? featureCheck() : false;
    }
    // Get comprehensive environment report
    getEnvironmentReport() {
      return {
        runtime: {
          isBrowser: this.isBrowser,
          isNodeJS: this.isNodeJS,
          isWorker: this.isWorker,
          isServiceWorker: this.isServiceWorker,
          isWebWorker: this.isWebWorker,
          isSharedWorker: this.isSharedWorker,
          isDeno: this.isDeno,
          isBun: this.isBun,
          isNativeScript: this.isNativeScript,
          isServer: this.isServer,
          isClient: this.isClient
        },
        capabilities: {
          webrtc: this.hasWebRTC,
          dataChannel: this.hasDataChannel,
          getUserMedia: this.hasGetUserMedia,
          webSocket: this.hasWebSocket,
          localStorage: this.hasLocalStorage,
          sessionStorage: this.hasSessionStorage,
          indexedDB: this.hasIndexedDB,
          cookies: this.hasCookies,
          crypto: this.hasCrypto,
          randomValues: this.hasRandomValues,
          performance: this.hasPerformanceNow,
          networkInfo: this.hasNetworkInformation
        },
        platform: this.getPlatformInfo(),
        browser: this.getBrowserInfo(),
        node: this.getNodeInfo(),
        nativescript: this.getNativeScriptInfo(),
        network: {
          online: this.isOnline,
          type: this.networkType
        }
      };
    }
    /**
     * Asynchronously initialize WebRTC using PigeonRTC
     * This method initializes PigeonRTC for cross-platform WebRTC support
     * @returns {Promise<boolean>} True if WebRTC was successfully initialized
     */
    async initWebRTCAsync() {
      try {
        if (this._pigeonRTC) {
          return true;
        }
        let createPigeonRTC2;
        if (typeof globalThis !== "undefined" && globalThis.__PEERPIGEON_PIGEONRTC__) {
          createPigeonRTC2 = globalThis.__PEERPIGEON_PIGEONRTC__.createPigeonRTC || globalThis.__PEERPIGEON_PIGEONRTC__.default;
        } else if (typeof window !== "undefined" && window.__PEERPIGEON_PIGEONRTC__) {
          createPigeonRTC2 = window.__PEERPIGEON_PIGEONRTC__.createPigeonRTC || window.__PEERPIGEON_PIGEONRTC__.default;
        }
        if (!createPigeonRTC2 && this.isNodeJS) {
          try {
            const pigeonRTCModule = await import("pigeonrtc");
            createPigeonRTC2 = pigeonRTCModule.createPigeonRTC || pigeonRTCModule.default;
          } catch (error) {
            console.error("Failed to dynamically import PigeonRTC:", error);
          }
        }
        if (!createPigeonRTC2) {
          throw new Error("PigeonRTC createPigeonRTC function not found");
        }
        this._pigeonRTC = await createPigeonRTC2();
        this._webrtcPolyfilled = true;
        return this._pigeonRTC.isSupported();
      } catch (error) {
        console.error("Failed to initialize PigeonRTC:", error);
        return false;
      }
    }
    // Static method for quick environment check
    static detect() {
      return new _EnvironmentDetector();
    }
    // Static method for single feature check
    static hasFeature(feature) {
      const detector = new _EnvironmentDetector();
      return detector.hasFeature(feature);
    }
  };
  var environmentDetector = new EnvironmentDetector();
  var isBrowser2 = () => environmentDetector.isBrowser;
  var isNodeJS = () => environmentDetector.isNodeJS;
  var isWorker = () => environmentDetector.isWorker;
  var hasWebRTC = () => environmentDetector.hasWebRTC;
  var hasWebSocket = () => environmentDetector.hasWebSocket;
  var getEnvironmentReport = () => environmentDetector.getEnvironmentReport();
  var initWebRTCAsync = () => environmentDetector.initWebRTCAsync();

  // src/DebugLogger.js
  var DebugLogger = class {
    // Default is disabled
    /**
     * Create a debug logger for a specific module
     * @param {string} moduleName - Name of the module (e.g., 'GossipManager', 'PeerConnection')
     * @returns {Object} Debug logger with log/warn/error methods
     */
    static create(moduleName) {
      if (!this.moduleStates.has(moduleName)) {
        this.moduleStates.set(moduleName, this.defaultEnabled);
      }
      return {
        /**
         * Debug log - for general debugging information
         * @param {...any} args - Arguments to log
         */
        log: (...args) => {
          if (this.isEnabled(moduleName)) {
            console.log(`[${moduleName}]`, ...args);
          }
        },
        /**
         * Debug warn - for warnings
         * @param {...any} args - Arguments to log
         */
        warn: (...args) => {
          if (this.isEnabled(moduleName)) {
            console.warn(`[${moduleName}]`, ...args);
          }
        },
        /**
         * Debug error - for errors
         * @param {...any} args - Arguments to log
         */
        error: (...args) => {
          if (this.isEnabled(moduleName)) {
            console.error(`[${moduleName}]`, ...args);
          }
        },
        /**
         * Debug info - alias for log
         * @param {...any} args - Arguments to log
         */
        info: (...args) => {
          if (this.isEnabled(moduleName)) {
            console.info(`[${moduleName}]`, ...args);
          }
        },
        /**
         * Debug debug - alias for log
         * @param {...any} args - Arguments to log
         */
        debug: (...args) => {
          if (this.isEnabled(moduleName)) {
            console.debug(`[${moduleName}]`, ...args);
          }
        }
      };
    }
    /**
     * Check if debugging is enabled for a module
     * @param {string} moduleName - Module name to check
     * @returns {boolean} True if enabled
     */
    static isEnabled(moduleName) {
      if (this.globalEnabled) return true;
      return this.moduleStates.get(moduleName) || false;
    }
    /**
     * Enable debugging for a specific module
     * @param {string} moduleName - Module name to enable
     */
    static enable(moduleName) {
      this.moduleStates.set(moduleName, true);
    }
    /**
     * Disable debugging for a specific module
     * @param {string} moduleName - Module name to disable
     */
    static disable(moduleName) {
      this.moduleStates.set(moduleName, false);
    }
    /**
     * Enable debugging for all modules
     */
    static enableAll() {
      this.globalEnabled = true;
    }
    /**
     * Disable debugging for all modules
     */
    static disableAll() {
      this.globalEnabled = false;
      for (const [moduleName] of this.moduleStates) {
        this.moduleStates.set(moduleName, false);
      }
    }
    /**
     * Enable debugging for multiple modules
     * @param {string[]} moduleNames - Array of module names to enable
     */
    static enableModules(moduleNames) {
      for (const moduleName of moduleNames) {
        this.enable(moduleName);
      }
    }
    /**
     * Disable debugging for multiple modules
     * @param {string[]} moduleNames - Array of module names to disable
     */
    static disableModules(moduleNames) {
      for (const moduleName of moduleNames) {
        this.disable(moduleName);
      }
    }
    /**
     * Get the current state of all modules
     * @returns {Object} Object with module names as keys and enabled state as values
     */
    static getState() {
      const state = {};
      for (const [moduleName, enabled] of this.moduleStates) {
        state[moduleName] = enabled;
      }
      return {
        globalEnabled: this.globalEnabled,
        modules: state
      };
    }
    /**
     * Configure debugging from an options object
     * @param {Object} options - Configuration options
     * @param {boolean} [options.enableAll] - Enable all modules
     * @param {boolean} [options.disableAll] - Disable all modules
     * @param {string[]} [options.enable] - Array of module names to enable
     * @param {string[]} [options.disable] - Array of module names to disable
     */
    static configure(options = {}) {
      if (options.disableAll) {
        this.disableAll();
      }
      if (options.enableAll) {
        this.enableAll();
      }
      if (options.enable && Array.isArray(options.enable)) {
        this.enableModules(options.enable);
      }
      if (options.disable && Array.isArray(options.disable)) {
        this.disableModules(options.disable);
      }
    }
    /**
     * Get list of all known modules
     * @returns {string[]} Array of module names
     */
    static getModules() {
      return Array.from(this.moduleStates.keys()).sort();
    }
  };
  __publicField(DebugLogger, "moduleStates", /* @__PURE__ */ new Map());
  __publicField(DebugLogger, "globalEnabled", false);
  __publicField(DebugLogger, "defaultEnabled", false);
  var DebugLogger_default = DebugLogger;

  // src/SignalingClient.js
  var SignalingClient = class extends EventEmitter {
    constructor(peerId, maxPeers = 10, mesh = null) {
      super();
      this.debug = DebugLogger_default.create("SignalingClient");
      this.peerId = peerId;
      this.maxPeers = maxPeers;
      this.mesh = mesh;
      this.signalingUrl = null;
      this.connected = false;
      this.websocket = null;
      this.reconnectDelay = 1e3;
      this.maxReconnectDelay = 3e4;
      this.reconnectAttempts = 0;
      this.maxReconnectAttempts = 10;
      this.connectionPromise = null;
      this.reconnectTimeout = null;
      this.isReconnecting = false;
      this.deliberateDisconnect = false;
    }
    setConnectionType(type) {
      this.debug.log(`WebSocket-only implementation - connection type setting ignored: ${type}`);
    }
    createWebSocket(url) {
      if (environmentDetector.isNodeJS) {
        if (typeof globalThis !== "undefined" && typeof globalThis.WebSocket !== "undefined") {
          return new globalThis.WebSocket(url);
        }
        if (typeof WebSocket !== "undefined") {
          return new WebSocket(url);
        }
        try {
          if (typeof __require !== "undefined") {
            const WebSocket2 = require_browser();
            return new WebSocket2(url);
          } else {
            this.debug.warn('WebSocket package detection not available in ES modules. Ensure "ws" is installed.');
            throw new Error('WebSocket not available in Node.js ES modules. Install the "ws" package and import it manually.');
          }
        } catch (error) {
          this.debug.warn("ws package not found in Node.js environment. Install with: npm install ws");
          throw new Error('WebSocket not available in Node.js. Install the "ws" package.');
        }
      } else if (environmentDetector.isBrowser || environmentDetector.isWorker || environmentDetector.isNativeScript) {
        return new WebSocket(url);
      } else {
        throw new Error("WebSocket not supported in this environment");
      }
    }
    async sendSignalingMessage(message) {
      if (!this.isConnected()) {
        this.debug.log("WebSocket not connected, attempting to reconnect...");
        if (!this.isReconnecting) {
          this.attemptReconnect();
        }
        throw new Error("WebSocket not connected");
      }
      const payload = {
        type: message.type,
        data: message.data,
        maxPeers: this.maxPeers,
        networkName: this.mesh ? this.mesh.networkName : "global",
        // Include network namespace
        ...message.targetPeerId && { targetPeerId: message.targetPeerId }
      };
      try {
        this.websocket.send(JSON.stringify(payload));
        this.debug.log(`Sent WebSocket message: ${payload.type} (network: ${payload.networkName})`);
        return { success: true };
      } catch (error) {
        this.debug.error("Failed to send WebSocket message:", error);
        if (!this.isReconnecting) {
          this.attemptReconnect();
        }
        throw error;
      }
    }
    isConnected() {
      return this.websocket && this.websocket.readyState === WebSocket.OPEN && this.connected;
    }
    async connect(websocketUrl) {
      if (!environmentDetector.hasWebSocket) {
        const error = new Error("WebSocket not supported in this environment");
        this.emit("statusChanged", { type: "error", message: error.message });
        throw error;
      }
      if (this.connectionPromise) {
        this.debug.log("Connection already in progress, waiting for completion...");
        return this.connectionPromise;
      }
      if (websocketUrl.startsWith("http://")) {
        websocketUrl = websocketUrl.replace("http://", "ws://");
      } else if (websocketUrl.startsWith("https://")) {
        websocketUrl = websocketUrl.replace("https://", "wss://");
      }
      if (!websocketUrl.startsWith("ws://") && !websocketUrl.startsWith("wss://")) {
        throw new Error("Invalid WebSocket URL format");
      }
      this.signalingUrl = websocketUrl;
      const url = new URL(websocketUrl);
      url.searchParams.set("peerId", this.peerId);
      this.emit("statusChanged", { type: "connecting", message: "Connecting to WebSocket..." });
      this.connectionPromise = new Promise((resolve, reject) => {
        try {
          this.websocket = this.createWebSocket(url.toString());
          const connectTimeout = setTimeout(() => {
            if (this.websocket.readyState === WebSocket.CONNECTING) {
              this.websocket.close();
              reject(new Error("WebSocket connection timeout"));
            }
          }, 1e4);
          this.websocket.onopen = () => {
            clearTimeout(connectTimeout);
            this.connected = true;
            this.reconnectAttempts = 0;
            this.reconnectDelay = 1e3;
            this.isReconnecting = false;
            this.connectionPromise = null;
            this.debug.log("WebSocket connected");
            this.emit("statusChanged", { type: "info", message: "WebSocket connected" });
            this.sendSignalingMessage({
              type: "announce",
              data: { peerId: this.peerId }
            }).then(() => {
              this.emit("connected");
              resolve();
            }).catch((error) => {
              this.debug.error("Failed to send announce message:", error);
              this.emit("connected");
              resolve();
            });
          };
          this.websocket.onmessage = (event) => {
            try {
              const message = JSON.parse(event.data);
              this.debug.log(`Received WebSocket message: ${message.type} (network: ${message.networkName || "unknown"})`);
              if (message.type === "connected") {
                this.debug.log("WebSocket connection confirmed by server");
              } else {
                const currentNetwork = this.mesh ? this.mesh.networkName : "global";
                const messageNetwork = message.networkName || "global";
                if (messageNetwork === currentNetwork) {
                  this.emit("signalingMessage", message);
                } else {
                  this.debug.log(`Filtered message from different network: ${messageNetwork} (current: ${currentNetwork})`);
                }
              }
            } catch (error) {
              this.debug.error("Failed to parse WebSocket message:", error);
            }
          };
          this.websocket.onclose = (event) => {
            clearTimeout(connectTimeout);
            const wasConnected = this.connected;
            this.connected = false;
            this.connectionPromise = null;
            this.isReconnecting = false;
            this.debug.log("WebSocket closed:", event.code, event.reason);
            if (wasConnected) {
              this.emit("disconnected");
            }
            if (!this.deliberateDisconnect) {
              this.attemptReconnect();
            } else {
              this.deliberateDisconnect = false;
            }
          };
          this.websocket.onerror = (error) => {
            clearTimeout(connectTimeout);
            this.debug.error("WebSocket error:", error);
            if (this.websocket.readyState === WebSocket.CONNECTING) {
              this.connectionPromise = null;
              reject(new Error("WebSocket connection failed"));
            } else {
              this.emit("statusChanged", { type: "error", message: "WebSocket error occurred" });
              if (!this.isReconnecting) {
                this.attemptReconnect();
              }
            }
          };
        } catch (error) {
          this.connectionPromise = null;
          reject(error);
        }
      });
      return this.connectionPromise;
    }
    attemptReconnect() {
      if (this.isReconnecting) {
        this.debug.log("Reconnection already in progress");
        return;
      }
      const hasHealthyPeers = this.mesh && this.mesh.connectionManager && this.mesh.connectionManager.getConnectedPeerCount() > 0;
      if (hasHealthyPeers) {
        this.debug.log("Have healthy peer connections, reducing reconnection urgency");
      }
      if (this.reconnectTimeout) {
        clearTimeout(this.reconnectTimeout);
        this.reconnectTimeout = null;
      }
      if (this.reconnectAttempts >= this.maxReconnectAttempts) {
        this.debug.log("Max reconnection attempts reached, using exponential backoff");
        const baseExtendedDelay = hasHealthyPeers ? 6e5 : this.maxReconnectDelay * 2;
        const extendedDelay = Math.min(baseExtendedDelay, 6e5);
        this.emit("statusChanged", {
          type: "reconnecting",
          message: `Max attempts reached. Retrying in ${Math.round(extendedDelay / 1e3)}s...`,
          delay: extendedDelay,
          attempt: this.reconnectAttempts,
          maxAttempts: this.maxReconnectAttempts,
          extendedBackoff: true
        });
        this.reconnectTimeout = setTimeout(() => {
          this.reconnectAttempts = Math.floor(this.maxReconnectAttempts / 2);
          this.attemptReconnect();
        }, extendedDelay);
        return;
      }
      this.isReconnecting = true;
      this.reconnectAttempts++;
      const baseDelay = this.reconnectDelay * Math.pow(2, this.reconnectAttempts - 1);
      const delayMultiplier = hasHealthyPeers ? 3 : 1;
      const delay = Math.min(baseDelay * delayMultiplier, hasHealthyPeers ? 3e5 : this.maxReconnectDelay);
      this.debug.log(`Reconnecting in ${delay}ms (attempt ${this.reconnectAttempts}/${this.maxReconnectAttempts}, healthy peers: ${hasHealthyPeers})`);
      this.emit("statusChanged", {
        type: "reconnecting",
        message: `Reconnecting in ${Math.round(delay / 1e3)}s (attempt ${this.reconnectAttempts}/${this.maxReconnectAttempts})`,
        delay,
        attempt: this.reconnectAttempts,
        maxAttempts: this.maxReconnectAttempts
      });
      this.reconnectTimeout = setTimeout(async () => {
        if (!this.connected && this.signalingUrl) {
          try {
            await this.connect(this.signalingUrl);
            this.emit("statusChanged", { type: "info", message: "WebSocket reconnected successfully" });
          } catch (error) {
            this.debug.error("Reconnection attempt failed:", error);
          }
        }
      }, delay);
    }
    disconnect() {
      this.deliberateDisconnect = true;
      this.isReconnecting = false;
      if (this.reconnectTimeout) {
        clearTimeout(this.reconnectTimeout);
        this.reconnectTimeout = null;
      }
      this.connected = false;
      this.connectionPromise = null;
      if (this.websocket) {
        this.websocket.onopen = null;
        this.websocket.onmessage = null;
        this.websocket.onclose = null;
        this.websocket.onerror = null;
        this.websocket.close(1e3, "Client disconnect");
        this.websocket = null;
      }
      this.emit("disconnected");
    }
    sendGoodbyeMessage() {
      if (!this.connected) return;
      try {
        this.debug.log("Sending goodbye message");
        if (this.websocket && this.websocket.readyState === WebSocket.OPEN) {
          this.websocket.send(JSON.stringify({
            type: "goodbye",
            data: {
              peerId: this.peerId,
              timestamp: Date.now(),
              reason: "peer_disconnect"
            }
          }));
        }
      } catch (error) {
        this.debug.error("Failed to send goodbye message:", error);
      }
    }
    async sendCleanupMessage(targetPeerId) {
      if (!this.connected) return;
      try {
        await this.sendSignalingMessage({
          type: "cleanup",
          data: {
            peerId: this.peerId,
            targetPeerId,
            timestamp: Date.now(),
            reason: "peer_disconnect"
          },
          targetPeerId
        });
      } catch (error) {
        this.debug.log(`Cleanup message failed for ${targetPeerId}:`, error.message);
      }
    }
    getConnectionStats() {
      return {
        connected: this.connected,
        isReconnecting: this.isReconnecting,
        reconnectAttempts: this.reconnectAttempts,
        websocketState: this.websocket ? this.websocket.readyState : "not created"
      };
    }
  };

  // src/PeerDiscovery.js
  var PeerDiscovery = class extends EventEmitter {
    constructor(peerId, options = {}) {
      super();
      this.debug = DebugLogger_default.create("PeerDiscovery");
      this.peerId = peerId;
      this.discoveredPeers = /* @__PURE__ */ new Map();
      this.connectionAttempts = /* @__PURE__ */ new Map();
      this.cleanupInterval = null;
      this.meshOptimizationTimeout = null;
      this.autoDiscovery = options.autoDiscovery ?? true;
      this.evictionStrategy = options.evictionStrategy ?? true;
      this.xorRouting = options.xorRouting ?? true;
      this.minPeers = options.minPeers ?? 0;
      this.maxPeers = options.maxPeers ?? 10;
    }
    start() {
      this.startCleanupInterval();
    }
    stop() {
      if (this.cleanupInterval) {
        clearInterval(this.cleanupInterval);
        this.cleanupInterval = null;
      }
      if (this.meshOptimizationTimeout) {
        clearTimeout(this.meshOptimizationTimeout);
        this.meshOptimizationTimeout = null;
      }
    }
    // Full cleanup method for deliberate disconnects
    cleanup() {
      this.stop();
      this.discoveredPeers.clear();
      this.connectionAttempts.clear();
    }
    addDiscoveredPeer(peerId) {
      if (this.discoveredPeers.has(peerId)) {
        this.discoveredPeers.set(peerId, Date.now());
        return;
      }
      this.discoveredPeers.set(peerId, Date.now());
      this.emit("peerDiscovered", { peerId });
      this.debug.log(`Discovered peer ${peerId.substring(0, 8)}...`);
      const shouldInitiate = this.shouldInitiateConnection(peerId);
      const alreadyAttempting = this.connectionAttempts.has(peerId);
      this.debug.log(`\u{1F50D} Connection decision for ${peerId.substring(0, 8)}...: autoDiscovery=${this.autoDiscovery}, shouldInitiate=${shouldInitiate}, alreadyAttempting=${alreadyAttempting}`);
      if (this.autoDiscovery && shouldInitiate && !alreadyAttempting) {
        this.debug.log(`Considering connection to ${peerId.substring(0, 8)}...`);
        const canAccept = this.canAcceptMorePeers();
        this.debug.log(`Can accept more peers: ${canAccept}`);
        if (canAccept) {
          this.debug.log(`\u{1F680} Connecting to ${peerId.substring(0, 8)}...`);
          this.emit("connectToPeer", { peerId });
        } else {
          this.debug.log(`\u274C Cannot accept more peers (at capacity)`);
        }
      } else {
        if (!this.autoDiscovery) {
          this.debug.log(`\u274C Auto-discovery disabled, not connecting to ${peerId.substring(0, 8)}...`);
        } else if (!shouldInitiate) {
          this.debug.log(`\u23F8\uFE0F  Not initiating to ${peerId.substring(0, 8)}... (they should initiate to us)`);
        } else if (alreadyAttempting) {
          this.debug.log(`\u23F8\uFE0F  Already attempting connection to ${peerId.substring(0, 8)}...`);
        }
      }
      this.scheduleMeshOptimization();
    }
    // Update connection attempts tracking (no complex isolation logic needed)
    onConnectionEstablished() {
      this.debug.log("Connection established");
    }
    removeDiscoveredPeer(peerId) {
      this.discoveredPeers.delete(peerId);
      this.connectionAttempts.delete(peerId);
    }
    trackConnectionAttempt(peerId) {
      this.connectionAttempts.set(peerId, Date.now());
    }
    clearConnectionAttempt(peerId) {
      this.connectionAttempts.delete(peerId);
    }
    calculateXorDistance(peerId1, peerId2) {
      let distance = 0n;
      for (let i = 0; i < Math.min(peerId1.length, peerId2.length); i += 2) {
        const byte1 = parseInt(peerId1.substr(i, 2), 16);
        const byte2 = parseInt(peerId2.substr(i, 2), 16);
        const xor = byte1 ^ byte2;
        distance = distance << 8n | BigInt(xor);
      }
      return distance;
    }
    shouldInitiateConnection(targetPeerId) {
      if (this.connectionAttempts.has(targetPeerId)) {
        return false;
      }
      this.emit("checkCapacity");
      const currentConnectionCount = this._currentConnectionCount || 0;
      const lexicographicShouldInitiate = this.peerId > targetPeerId;
      if (currentConnectionCount === 0 && this.discoveredPeers.size > 0) {
        const discoveredPeers = Array.from(this.discoveredPeers.keys());
        const naturalInitiators = discoveredPeers.filter((peerId) => this.peerId > peerId);
        if (naturalInitiators.length > 0 && naturalInitiators.includes(targetPeerId)) {
          this.debug.log(`shouldInitiateConnection: Isolation override (natural) - ${this.peerId.substring(0, 8)}... will initiate to ${targetPeerId.substring(0, 8)}...`);
          return true;
        }
        if (naturalInitiators.length === 0) {
          const sortedByDistance = discoveredPeers.sort((a, b) => {
            const distA = this.calculateXorDistance(this.peerId, a);
            const distB = this.calculateXorDistance(this.peerId, b);
            return distA < distB ? -1 : 1;
          });
          const closestPeers = sortedByDistance.slice(0, Math.min(3, sortedByDistance.length));
          if (closestPeers.includes(targetPeerId)) {
            const index = closestPeers.indexOf(targetPeerId);
            this.debug.log(`shouldInitiateConnection: Isolation override (closest ${index + 1}) - ${this.peerId.substring(0, 8)}... will initiate to ${targetPeerId.substring(0, 8)}...`);
            return true;
          }
        }
        const attemptedConnections = this.connectionAttempts.size;
        if (attemptedConnections >= 2 && discoveredPeers.includes(targetPeerId)) {
          this.debug.log(`shouldInitiateConnection: Isolation override (desperate) - ${this.peerId.substring(0, 8)}... will initiate to ${targetPeerId.substring(0, 8)}... (${attemptedConnections} attempts failed)`);
          return true;
        }
      }
      this.debug.log(`shouldInitiateConnection: ${this.peerId.substring(0, 8)}... > ${targetPeerId.substring(0, 8)}... = ${lexicographicShouldInitiate}`);
      return lexicographicShouldInitiate;
    }
    isAttemptingConnection(peerId) {
      return this.connectionAttempts.has(peerId);
    }
    shouldEvictForPeer(newPeerId) {
      if (!this.evictionStrategy || !this.xorRouting) {
        return null;
      }
      this.emit("checkEviction", { newPeerId });
      return this._shouldEvictForPeer ?? null;
    }
    canAcceptMorePeers() {
      this.emit("checkCapacity");
      return this._canAcceptMorePeers ?? true;
    }
    optimizeMeshConnections(currentPeers) {
      if (!this.autoDiscovery) return;
      this.debug.log("Optimizing mesh connections...");
      const unconnectedPeers = Array.from(this.discoveredPeers.keys()).filter((peerId) => {
        const notConnected = !currentPeers.has(peerId);
        const notConnecting = !this.connectionAttempts.has(peerId);
        const shouldInitiate = this.shouldInitiateConnection(peerId);
        return notConnected && notConnecting && shouldInitiate;
      });
      if (unconnectedPeers.length === 0) {
        this.debug.log("No unconnected peers to optimize");
        return;
      }
      if (this.xorRouting) {
        unconnectedPeers.sort((a, b) => {
          const distA = this.calculateXorDistance(this.peerId, a);
          const distB = this.calculateXorDistance(this.peerId, b);
          return distA < distB ? -1 : 1;
        });
      }
      this.emit("optimizeConnections", { unconnectedPeers });
    }
    scheduleMeshOptimization() {
      if (this.meshOptimizationTimeout) {
        clearTimeout(this.meshOptimizationTimeout);
      }
      const delay = 1e4 + Math.random() * 5e3;
      this.meshOptimizationTimeout = setTimeout(() => {
        this.emit("optimizeMesh");
      }, delay);
    }
    startCleanupInterval() {
      if (environmentDetector.isBrowser) {
        this.cleanupInterval = window.setInterval(() => {
          this.cleanupStaleDiscoveredPeers();
        }, 3e4);
      } else {
        this.cleanupInterval = setInterval(() => {
          this.cleanupStaleDiscoveredPeers();
        }, 3e4);
      }
    }
    cleanupStaleDiscoveredPeers() {
      const now = Date.now();
      const staleThreshold = 5 * 60 * 1e3;
      let removedCount = 0;
      this.discoveredPeers.forEach((timestamp, peerId) => {
        if (now - timestamp > staleThreshold) {
          this.discoveredPeers.delete(peerId);
          this.connectionAttempts.delete(peerId);
          removedCount++;
          this.debug.log("Removed stale peer:", peerId.substring(0, 8));
        }
      });
      if (removedCount > 0) {
        this.emit("peersUpdated", { removedCount });
      }
    }
    getDiscoveredPeers() {
      return Array.from(this.discoveredPeers.entries()).map(([peerId, timestamp]) => ({
        peerId,
        timestamp,
        distance: this.calculateXorDistance(this.peerId, peerId),
        isConnecting: this.connectionAttempts.has(peerId),
        isConnected: false
        // Will be set by the mesh when it has peer info
      }));
    }
    hasPeer(peerId) {
      return this.discoveredPeers.has(peerId);
    }
    setSettings(settings) {
      if (settings.autoDiscovery !== void 0) {
        this.autoDiscovery = settings.autoDiscovery;
      }
      if (settings.evictionStrategy !== void 0) {
        this.evictionStrategy = settings.evictionStrategy;
      }
      if (settings.xorRouting !== void 0) {
        this.xorRouting = settings.xorRouting;
      }
      if (settings.minPeers !== void 0) {
        this.minPeers = settings.minPeers;
      }
      if (settings.maxPeers !== void 0) {
        this.maxPeers = settings.maxPeers;
      }
    }
    updateDiscoveryTimestamp(peerId) {
      if (this.discoveredPeers.has(peerId)) {
        this.discoveredPeers.set(peerId, Date.now());
      }
    }
    debugCurrentState() {
      const discoveredPeerIds = Array.from(this.discoveredPeers.keys()).map((p) => p.substring(0, 8));
      const connectionAttempts = Array.from(this.connectionAttempts.keys()).map((p) => p.substring(0, 8));
      this.debug.log("=== PEER DISCOVERY DEBUG ===");
      this.debug.log(`Our peer ID: ${this.peerId.substring(0, 8)}...`);
      this.debug.log(`Discovered peers (${discoveredPeerIds.length}): ${discoveredPeerIds.join(", ")}`);
      this.debug.log(`Connection attempts (${connectionAttempts.length}): ${connectionAttempts.join(", ")}`);
      discoveredPeerIds.forEach((peerId) => {
        const fullPeerId = Array.from(this.discoveredPeers.keys()).find((p) => p.startsWith(peerId));
        const shouldInitiate = this.shouldInitiateConnection(fullPeerId);
        const comparison = this.peerId > fullPeerId;
        this.debug.log(`  ${peerId}...: should initiate = ${shouldInitiate} (${this.peerId.substring(0, 8)}... > ${peerId}... = ${comparison})`);
      });
      this.debug.log("=== END DEBUG ===");
    }
  };

  // src/PeerConnection.js
  var PeerConnection = class extends EventEmitter {
    constructor(peerId, isInitiator = false, options = {}) {
      super();
      this.peerId = peerId;
      this.debug = DebugLogger_default.create("PeerConnection");
      this.isInitiator = isInitiator;
      this.connection = null;
      this.dataChannel = null;
      this.remoteDescriptionSet = false;
      this.dataChannelReady = false;
      this.connectionStartTime = Date.now();
      this.pendingIceCandidates = [];
      this.isClosing = false;
      this.iceTimeoutId = null;
      this._forcedStatus = null;
      this._pendingBinaryPayloads = [];
      this._activeStreams = /* @__PURE__ */ new Map();
      this._streamChunks = /* @__PURE__ */ new Map();
      this._streamMetadata = /* @__PURE__ */ new Map();
      this.localStream = options.localStream || null;
      this.remoteStream = null;
      this.enableVideo = options.enableVideo || false;
      this.enableAudio = options.enableAudio || false;
      this.audioTransceiver = null;
      this.videoTransceiver = null;
      this.allowRemoteStreams = options.allowRemoteStreams === true;
      this.pendingRemoteStreams = [];
    }
    /**
       * Force this connection into a terminal state (e.g., failed/timeout)
       */
    markAsFailed(reason = "failed") {
      this._forcedStatus = reason;
      try {
        this.close();
      } catch (e) {
      }
    }
    async createConnection() {
      if (!environmentDetector.hasWebRTC) {
        const error = new Error("WebRTC not supported in this environment");
        this.emit("connectionFailed", { peerId: this.peerId, reason: error.message });
        throw error;
      }
      const pigeonRTC = environmentDetector.getPigeonRTC();
      if (!pigeonRTC) {
        const error = new Error("PigeonRTC not initialized - call initWebRTCAsync() first");
        this.emit("connectionFailed", { peerId: this.peerId, reason: error.message });
        throw error;
      }
      const testMode = typeof window !== "undefined" && window.AUTOMATED_TEST === true || typeof globalThis !== "undefined" && globalThis.AUTOMATED_TEST === true;
      const iceServers = testMode ? [
        // Single STUN server (or none) is sufficient for localhost; fewer servers reduces Firefox slowdown warnings
        { urls: "stun:stun.l.google.com:19302" }
      ] : [
        { urls: "stun:stun.l.google.com:19302" },
        { urls: "stun:stun1.l.google.com:19302" },
        { urls: "stun:stun2.l.google.com:19302" },
        { urls: "stun:stun3.l.google.com:19302" },
        { urls: "stun:stun4.l.google.com:19302" }
      ];
      this.debug.log(`\u{1F9EA} Automated test mode: ${testMode} (iceServers before create = ${iceServers.length})`);
      this.connection = pigeonRTC.createPeerConnection({
        iceServers,
        iceCandidatePoolSize: testMode ? 4 : 10,
        bundlePolicy: "max-bundle",
        rtcpMuxPolicy: "require"
      });
      this.setupConnectionHandlers();
      this.debug.log("\u{1F504} Using addTrack() approach for proper ontrack event firing");
      if (this.localStream) {
        this.debug.log("Adding local stream tracks using addTrack() method");
        await this.addLocalStreamWithAddTrack(this.localStream);
      }
      if (this.isInitiator) {
        this.debug.log(`\u{1F680} INITIATOR: Creating data channel for ${this.peerId.substring(0, 8)}... (WE are initiator)`);
        this.dataChannel = this.connection.createDataChannel("messages", {
          ordered: true
        });
        this.setupDataChannel();
      } else {
        this.debug.log(`\u{1F465} RECEIVER: Waiting for data channel from ${this.peerId.substring(0, 8)}... (THEY are initiator)`);
        this.connection.ondatachannel = (event) => {
          this.debug.log(`\u{1F4E8} RECEIVED: Data channel received from ${this.peerId.substring(0, 8)}...`);
          this.dataChannel = event.channel;
          this.setupDataChannel();
        };
      }
    }
    setupConnectionHandlers() {
      this.connection.onicecandidate = (event) => {
        if (event.candidate) {
          this.debug.log(`\u{1F9CA} Generated ICE candidate for ${this.peerId.substring(0, 8)}...`, {
            type: event.candidate.type,
            protocol: event.candidate.protocol,
            address: event.candidate.address?.substring(0, 10) + "..." || "unknown"
          });
          this.emit("iceCandidate", { peerId: this.peerId, candidate: event.candidate });
        } else {
          this.debug.log(`\u{1F9CA} ICE gathering complete for ${this.peerId.substring(0, 8)}...`);
        }
      };
      this.connection.ontrack = (event) => {
        this.debug.log("\u{1F3B5} Received remote media stream from", this.peerId);
        const stream = event.streams[0];
        const track = event.track;
        this.debug.log(`\u{1F3B5} Track received: kind=${track.kind}, id=${track.id}, enabled=${track.enabled}, readyState=${track.readyState}`);
        this.debug.log("\u{1F50D} ONTRACK DEBUG: Starting stream validation...");
        if (!this.validateRemoteStream(stream, track)) {
          this.debug.error("\u274C ONTRACK DEBUG: Stream validation FAILED - rejecting remote stream");
          return;
        }
        this.debug.log("\u2705 ONTRACK DEBUG: Stream validation PASSED - processing remote stream");
        if (stream) {
          this.remoteStream = stream;
          const audioTracks = stream.getAudioTracks();
          const videoTracks = stream.getVideoTracks();
          this.debug.log(`\u{1F3B5} Remote stream tracks: ${audioTracks.length} audio, ${videoTracks.length} video`);
          this.debug.log(`\u{1F3B5} Remote stream ID: ${stream.id} (vs local: ${this.localStream?.id || "none"})`);
          this.markStreamAsRemote(stream);
          audioTracks.forEach((audioTrack, index) => {
            this.debug.log(`\u{1F3B5} Audio track ${index}: enabled=${audioTrack.enabled}, readyState=${audioTrack.readyState}, muted=${audioTrack.muted}, id=${audioTrack.id}`);
            this.setupAudioDataMonitoring(audioTrack, index);
          });
          this.debug.log("\u{1F6A8} ONTRACK DEBUG: About to emit remoteStream event");
          if (this.allowRemoteStreams) {
            this.emit("remoteStream", { peerId: this.peerId, stream: this.remoteStream });
            this.debug.log("\u2705 ONTRACK DEBUG: remoteStream event emitted successfully");
          } else {
            this.debug.log("\u{1F512} ONTRACK DEBUG: Buffering remote stream until crypto verification");
            this.pendingRemoteStreams.push({ peerId: this.peerId, stream: this.remoteStream });
          }
        } else {
          this.debug.error("\u274C ONTRACK DEBUG: No stream in ontrack event - this should not happen");
        }
      };
      this.connection.onconnectionstatechange = () => {
        this.debug.log(`\u{1F517} Connection state with ${this.peerId}: ${this.connection.connectionState} (previous signaling: ${this.connection.signalingState})`);
        try {
          const transceivers = this.connection.getTransceivers();
          const audioSending = this.audioTransceiver && this.audioTransceiver.sender && this.audioTransceiver.sender.track;
          const videoSending = this.videoTransceiver && this.videoTransceiver.sender && this.videoTransceiver.sender.track;
          this.debug.log(`\u{1F517} Media context: Audio sending=${!!audioSending}, Video sending=${!!videoSending}, Transceivers=${transceivers.length}`);
        } catch (error) {
          this.debug.log(`\u{1F517} Media context: Unable to access transceiver details (${error.message})`);
        }
        if (this.connection.connectionState === "connected") {
          this.debug.log(`\u2705 Connection established with ${this.peerId}`);
          this.emit("connected", { peerId: this.peerId });
        } else if (this.connection.connectionState === "connecting") {
          this.debug.log(`\u{1F504} Connection to ${this.peerId} is connecting...`);
        } else if (this.connection.connectionState === "disconnected") {
          this.debug.log(`\u26A0\uFE0F WebRTC connection disconnected for ${this.peerId}, waiting for potential recovery...`);
          const recoveryTime = 12e3;
          setTimeout(() => {
            if (this.connection && this.connection.connectionState === "disconnected" && !this.isClosing) {
              this.debug.log(`\u274C WebRTC connection remained disconnected for ${this.peerId} after ${recoveryTime}ms, treating as failed`);
              this.emit("disconnected", { peerId: this.peerId, reason: "connection disconnected" });
            }
          }, recoveryTime);
        } else if (this.connection.connectionState === "failed") {
          if (!this.isClosing) {
            this.debug.log(`\u274C Connection failed for ${this.peerId}`);
            this.emit("disconnected", { peerId: this.peerId, reason: "connection failed" });
          }
        } else if (this.connection.connectionState === "closed") {
          if (!this.isClosing) {
            this.debug.log(`\u274C Connection closed for ${this.peerId}`);
            this.emit("disconnected", { peerId: this.peerId, reason: "connection closed" });
          }
        }
      };
      this.connection.oniceconnectionstatechange = () => {
        this.debug.log(`\u{1F9CA} ICE connection state with ${this.peerId}: ${this.connection.iceConnectionState}`);
        if (this.connection.iceConnectionState === "connected") {
          this.debug.log(`\u2705 ICE connection established with ${this.peerId}`);
          if (this.iceTimeoutId) {
            clearTimeout(this.iceTimeoutId);
            this.iceTimeoutId = null;
          }
        } else if (this.connection.iceConnectionState === "checking") {
          this.debug.log(`\u{1F504} ICE checking for ${this.peerId}...`);
          if (this.iceTimeoutId) {
            clearTimeout(this.iceTimeoutId);
          }
          this.iceTimeoutId = setTimeout(() => {
            if (this.connection && this.connection.iceConnectionState === "checking" && !this.isClosing) {
              this.debug.error(`\u274C ICE negotiation timeout for ${this.peerId} - connection stuck in checking state`);
              this.emit("disconnected", { peerId: this.peerId, reason: "ICE negotiation timeout" });
            }
          }, 3e4);
        } else if (this.connection.iceConnectionState === "failed") {
          const hasSignaling = this.mesh && this.mesh.signalingClient && this.mesh.signalingClient.isConnected();
          const hasMeshConnectivity = this.mesh && this.mesh.connected && this.mesh.connectionManager.getConnectedPeerCount() > 0;
          if (hasSignaling || hasMeshConnectivity) {
            this.debug.log(`\u274C ICE connection failed for ${this.peerId}, attempting restart (signaling: ${hasSignaling}, mesh: ${hasMeshConnectivity})`);
            try {
              this.restartIceViaSignaling().catch((error) => {
                this.debug.error("Failed to restart ICE after failure:", error);
                this.emit("disconnected", { peerId: this.peerId, reason: "ICE failed" });
              });
            } catch (error) {
              this.debug.error("Failed to restart ICE after failure:", error);
              this.emit("disconnected", { peerId: this.peerId, reason: "ICE failed" });
            }
          } else {
            this.debug.log(`\u274C ICE connection failed for ${this.peerId}, disconnecting`);
            this.emit("disconnected", { peerId: this.peerId, reason: "ICE failed" });
          }
        } else if (this.connection.iceConnectionState === "disconnected") {
          this.debug.log(`\u26A0\uFE0F ICE connection disconnected for ${this.peerId}, waiting for potential reconnection...`);
          setTimeout(() => {
            if (this.connection && this.connection.iceConnectionState === "disconnected" && !this.isClosing) {
              const hasSignaling = this.mesh && this.mesh.signalingClient && this.mesh.signalingClient.isConnected();
              const hasMeshConnectivity = this.mesh && this.mesh.connected && this.mesh.connectionManager.getConnectedPeerCount() > 0;
              if (hasSignaling || hasMeshConnectivity) {
                this.debug.log(`\u274C ICE remained disconnected for ${this.peerId}, attempting restart (signaling: ${hasSignaling}, mesh: ${hasMeshConnectivity})`);
                try {
                  this.restartIceViaSignaling().catch((error) => {
                    this.debug.error("Failed to restart ICE after disconnection:", error);
                    this.emit("disconnected", { peerId: this.peerId, reason: "ICE disconnected" });
                  });
                } catch (error) {
                  this.debug.error("Failed to restart ICE after disconnection:", error);
                  this.emit("disconnected", { peerId: this.peerId, reason: "ICE disconnected" });
                }
              } else {
                this.debug.log(`\u274C ICE remained disconnected for ${this.peerId}, disconnecting`);
                this.emit("disconnected", { peerId: this.peerId, reason: "ICE disconnected" });
              }
            }
          }, 5e3);
        }
      };
      this.connection.onnegotiationneeded = () => {
        this.debug.log(`\u{1F504} Negotiation needed for ${this.peerId} (WebRTC detected track changes)`);
        this.debug.log("\u2705 RENEGOTIATION: Track changes detected - triggering renegotiation as expected");
        try {
          const transceivers = this.connection.getTransceivers();
          this.debug.log("\u{1F504} Transceivers state during renegotiation:", transceivers.map((t) => ({
            kind: t.receiver?.track?.kind || "unknown",
            direction: t.direction,
            hasTrack: !!t.sender?.track,
            mid: t.mid
          })));
        } catch (error) {
          this.debug.log("\u{1F504} Cannot inspect transceivers (Node.js WebRTC limitation):", error.message);
        }
        this.emit("renegotiationNeeded", { peerId: this.peerId });
      };
      this.connection.onsignalingstatechange = () => {
        this.debug.log(`\u{1F504} Signaling state changed for ${this.peerId}: ${this.connection.signalingState}`);
        if (this.connection.signalingState === "stable") {
          this.debug.log("\u{1F50D} Signaling stable - checking for new remote tracks...");
          this.checkForNewRemoteTracks();
        }
      };
    }
    setupDataChannel() {
      if (!this.dataChannel) {
        this.debug.error("setupDataChannel called without data channel instance");
        return;
      }
      this.dataChannel.binaryType = "arraybuffer";
      this.dataChannel.onopen = () => {
        this.debug.log(`Data channel opened with ${this.peerId}`);
        this.dataChannelReady = true;
        this.emit("dataChannelOpen", { peerId: this.peerId });
      };
      this.dataChannel.onclose = () => {
        this.debug.log(`Data channel closed with ${this.peerId}`);
        this.dataChannelReady = false;
        this._pendingBinaryPayloads.length = 0;
        if (!this.isClosing) {
          this.emit("disconnected", { peerId: this.peerId, reason: "data channel closed" });
        }
      };
      this.dataChannel.onmessage = (event) => {
        const handleBinaryPayload = (buffer) => {
          if (!buffer) return;
          if (this._pendingBinaryPayloads.length === 0) {
            this.debug.warn("Received unexpected binary payload without metadata");
            return;
          }
          const payload = this._pendingBinaryPayloads.shift();
          const uint8Array = new Uint8Array(buffer);
          if (payload.type === "binaryMessage") {
            if (payload.size && payload.size !== uint8Array.byteLength) {
              this.debug.warn(`Binary message size mismatch: expected ${payload.size}, got ${uint8Array.byteLength}`);
            }
            this.debug.log(`\u{1F4E6} Received binary message (${uint8Array.byteLength} bytes) from ${this.peerId.substring(0, 8)}...`);
            this.emit("message", {
              peerId: this.peerId,
              message: {
                type: "binary",
                data: uint8Array,
                size: uint8Array.byteLength
              }
            });
            return;
          }
          if (payload.type === "streamChunk") {
            this._handleStreamChunkBinary(payload.header, uint8Array);
            return;
          }
          this.debug.warn(`Received binary payload with unknown handler type: ${payload.type}`);
        };
        if (event.data instanceof ArrayBuffer) {
          handleBinaryPayload(event.data);
          return;
        }
        if (typeof Blob !== "undefined" && event.data instanceof Blob) {
          event.data.arrayBuffer().then(handleBinaryPayload).catch((error) => this.debug.error("Failed to read binary payload:", error));
          return;
        }
        try {
          const message = JSON.parse(event.data);
          if (message.type === "__BINARY__") {
            this._pendingBinaryPayloads.push({ type: "binaryMessage", size: message.size });
            this.debug.log(`\u{1F4E6} Binary message header received, expecting ${message.size} bytes`);
            return;
          }
          if (message.type && message.type.startsWith("__STREAM_")) {
            this._handleStreamMessage(message);
            return;
          }
          this.emit("message", { peerId: this.peerId, message });
        } catch (error) {
          this.debug.error("Failed to parse message:", error);
          this.emit("message", { peerId: this.peerId, message: { content: event.data } });
        }
      };
      this.dataChannel.onerror = (error) => {
        this.debug.error(`Data channel error with ${this.peerId}:`, error);
        this.dataChannelReady = false;
        this._pendingBinaryPayloads.length = 0;
        if (!this.isClosing) {
          this.emit("disconnected", { peerId: this.peerId, reason: "data channel error" });
        }
      };
    }
    // CRITICAL: Check and force data channel state after answer processing
    checkDataChannelState() {
      if (this.dataChannel) {
        this.debug.log(`\u{1F50D} DATA CHANNEL CHECK: State for ${this.peerId.substring(0, 8)}... is ${this.dataChannel.readyState}`);
        if (this.dataChannel.readyState === "open" && !this.dataChannelReady) {
          this.debug.log(`\u{1F680} FORCE OPEN: Triggering data channel open for ${this.peerId.substring(0, 8)}...`);
          this.dataChannelReady = true;
          this.emit("dataChannelOpen", { peerId: this.peerId });
        } else if (this.dataChannel.readyState === "connecting") {
          this.debug.log(`\u23F3 CONNECTING: Data channel connecting for ${this.peerId.substring(0, 8)}..., setting up backup check`);
          setTimeout(() => {
            if (this.dataChannel && this.dataChannel.readyState === "open" && !this.dataChannelReady) {
              this.debug.log(`\u{1F680} BACKUP OPEN: Backup trigger for data channel open for ${this.peerId.substring(0, 8)}...`);
              this.dataChannelReady = true;
              this.emit("dataChannelOpen", { peerId: this.peerId });
            }
          }, 100);
        } else {
          this.debug.log(`\u274C DATA CHANNEL CHECK: No data channel found for ${this.peerId.substring(0, 8)}...`);
        }
      }
    }
    async createOffer() {
      try {
        const offer = await Promise.race([
          this.connection.createOffer({
            offerToReceiveAudio: true,
            offerToReceiveVideo: true,
            iceRestart: false
            // Don't restart ICE unless necessary
          }),
          new Promise(
            (resolve, reject) => setTimeout(() => reject(new Error("createOffer timeout")), 1e4)
          )
        ]);
        await Promise.race([
          this.connection.setLocalDescription(offer),
          new Promise(
            (resolve, reject) => setTimeout(() => reject(new Error("setLocalDescription timeout")), 1e4)
          )
        ]);
        return offer;
      } catch (error) {
        this.debug.error(`\u274C Failed to create offer for ${this.peerId}:`, error);
        throw error;
      }
    }
    async handleOffer(offer) {
      if (!offer || typeof offer !== "object") {
        this.debug.error(`Invalid offer from ${this.peerId} - not an object:`, offer);
        throw new Error("Invalid offer: not an object");
      }
      if (!offer.type || offer.type !== "offer") {
        this.debug.error(`Invalid offer from ${this.peerId} - wrong type:`, offer.type);
        throw new Error(`Invalid offer: expected type 'offer', got '${offer.type}'`);
      }
      if (!offer.sdp || typeof offer.sdp !== "string") {
        this.debug.error(`Invalid offer from ${this.peerId} - missing or invalid SDP:`, typeof offer.sdp);
        throw new Error("Invalid offer: missing or invalid SDP");
      }
      if (offer.sdp.length < 10 || !offer.sdp.includes("v=0")) {
        this.debug.error(`Invalid offer SDP from ${this.peerId} - malformed:`, offer.sdp.substring(0, 100) + "...");
        throw new Error("Invalid offer: malformed SDP");
      }
      this.debug.log(`\u{1F504} OFFER DEBUG: Processing offer from ${this.peerId.substring(0, 8)}...`);
      this.debug.log(`\u{1F504} OFFER DEBUG: Current signaling state: ${this.connection.signalingState}`);
      this.debug.log(`\u{1F504} OFFER DEBUG: Current connection state: ${this.connection.connectionState}`);
      this.debug.log(`\u{1F504} OFFER DEBUG: Current ICE state: ${this.connection.iceConnectionState}`);
      this.debug.log(`\u{1F504} OFFER DEBUG: Offer SDP length: ${offer.sdp.length}`);
      if (this.connection.signalingState !== "stable") {
        this.debug.log(`\u274C OFFER DEBUG: Cannot handle offer from ${this.peerId} - connection state is ${this.connection.signalingState} (expected: stable)`);
        throw new Error(`Cannot handle offer in state: ${this.connection.signalingState}`);
      }
      this.debug.log(`\u{1F504} OFFER DEBUG: State validation passed, processing offer from ${this.peerId.substring(0, 8)}... SDP length: ${offer.sdp.length}`);
      try {
        await Promise.race([
          this.connection.setRemoteDescription(offer),
          new Promise(
            (resolve, reject) => setTimeout(() => reject(new Error("setRemoteDescription timeout")), 1e4)
          )
        ]);
        this.remoteDescriptionSet = true;
        this.debug.log(`\u2705 OFFER DEBUG: Offer processed successfully from ${this.peerId.substring(0, 8)}...`);
        this.debug.log(`\u2705 OFFER DEBUG: New signaling state after offer: ${this.connection.signalingState}`);
        await this.processPendingIceCandidates();
        const answer = await Promise.race([
          this.connection.createAnswer(),
          new Promise(
            (resolve, reject) => setTimeout(() => reject(new Error("createAnswer timeout")), 1e4)
          )
        ]);
        await Promise.race([
          this.connection.setLocalDescription(answer),
          new Promise(
            (resolve, reject) => setTimeout(() => reject(new Error("setLocalDescription timeout")), 1e4)
          )
        ]);
        this.debug.log(`\u2705 OFFER DEBUG: Answer created for offer from ${this.peerId.substring(0, 8)}...`);
        this.debug.log(`\u2705 OFFER DEBUG: Final signaling state after answer: ${this.connection.signalingState}`);
        this.checkDataChannelState();
        return answer;
      } catch (error) {
        this.debug.error(`\u274C OFFER DEBUG: Failed to process offer from ${this.peerId}:`, error);
        this.debug.error("OFFER DEBUG: Offer SDP that failed:", offer.sdp);
        this.debug.error("OFFER DEBUG: Current connection state:", this.connection.signalingState);
        this.debug.error("OFFER DEBUG: Current ICE state:", this.connection.iceConnectionState);
        throw error;
      }
    }
    async handleAnswer(answer) {
      if (!answer || typeof answer !== "object") {
        this.debug.error(`Invalid answer from ${this.peerId} - not an object:`, answer);
        throw new Error("Invalid answer: not an object");
      }
      if (!answer.type || answer.type !== "answer") {
        this.debug.error(`Invalid answer from ${this.peerId} - wrong type:`, answer.type);
        throw new Error(`Invalid answer: expected type 'answer', got '${answer.type}'`);
      }
      if (!answer.sdp || typeof answer.sdp !== "string") {
        this.debug.error(`Invalid answer from ${this.peerId} - missing or invalid SDP:`, typeof answer.sdp);
        throw new Error("Invalid answer: missing or invalid SDP");
      }
      if (answer.sdp.length < 10 || !answer.sdp.includes("v=0")) {
        this.debug.error(`Invalid answer SDP from ${this.peerId} - malformed:`, answer.sdp.substring(0, 100) + "...");
        throw new Error("Invalid answer: malformed SDP");
      }
      this.debug.log(`\u{1F504} ANSWER DEBUG: Processing answer from ${this.peerId.substring(0, 8)}...`);
      this.debug.log(`\u{1F504} ANSWER DEBUG: Current signaling state: ${this.connection.signalingState}`);
      this.debug.log(`\u{1F504} ANSWER DEBUG: Current connection state: ${this.connection.connectionState}`);
      this.debug.log(`\u{1F504} ANSWER DEBUG: Current ICE state: ${this.connection.iceConnectionState}`);
      this.debug.log(`\u{1F504} ANSWER DEBUG: Answer SDP length: ${answer.sdp.length}`);
      if (this.connection.signalingState !== "have-local-offer") {
        this.debug.log(`\u274C ANSWER DEBUG: Cannot handle answer from ${this.peerId} - connection state is ${this.connection.signalingState} (expected: have-local-offer)`);
        if (this.connection.signalingState === "stable") {
          this.debug.log("\u2705 ANSWER DEBUG: Connection already stable, answer not needed");
          return;
        }
        throw new Error(`Cannot handle answer in state: ${this.connection.signalingState}`);
      }
      this.debug.log(`\u{1F504} ANSWER DEBUG: State validation passed, processing answer from ${this.peerId.substring(0, 8)}... SDP length: ${answer.sdp.length}`);
      try {
        await Promise.race([
          this.connection.setRemoteDescription(answer),
          new Promise(
            (resolve, reject) => setTimeout(() => reject(new Error("setRemoteDescription timeout")), 1e4)
          )
        ]);
        this.remoteDescriptionSet = true;
        this.debug.log(`\u2705 ANSWER DEBUG: Answer processed successfully from ${this.peerId.substring(0, 8)}...`);
        this.debug.log(`\u2705 ANSWER DEBUG: New signaling state: ${this.connection.signalingState}`);
        this.debug.log(`\u2705 ANSWER DEBUG: New connection state: ${this.connection.connectionState}`);
        await this.processPendingIceCandidates();
        this.checkDataChannelState();
      } catch (error) {
        this.debug.error(`\u274C ANSWER DEBUG: Failed to set remote description for answer from ${this.peerId}:`, error);
        this.debug.error("ANSWER DEBUG: Answer SDP that failed:", answer.sdp);
        this.debug.error("ANSWER DEBUG: Current connection state:", this.connection.signalingState);
        this.debug.error("ANSWER DEBUG: Current ICE state:", this.connection.iceConnectionState);
        throw error;
      }
    }
    async handleIceCandidate(candidate) {
      if (!candidate || typeof candidate !== "object") {
        this.debug.error(`Invalid ICE candidate from ${this.peerId} - not an object:`, candidate);
        throw new Error("Invalid ICE candidate: not an object");
      }
      if (!candidate.candidate || typeof candidate.candidate !== "string" || candidate.candidate.trim() === "") {
        this.debug.log(`\u{1F9CA} Received end-of-candidates signal for ${this.peerId.substring(0, 8)}...`);
        return;
      }
      this.debug.log(`\u{1F9CA} Received ICE candidate for ${this.peerId.substring(0, 8)}...`, {
        type: candidate.type,
        protocol: candidate.protocol,
        candidateLength: candidate.candidate?.length || 0
      });
      if (!this.remoteDescriptionSet) {
        this.debug.log(`\u{1F9CA} Buffering ICE candidate for ${this.peerId.substring(0, 8)}... (remote description not set yet)`);
        this.pendingIceCandidates.push(candidate);
        return;
      }
      try {
        await Promise.race([
          this.connection.addIceCandidate(candidate),
          new Promise(
            (resolve, reject) => setTimeout(() => reject(new Error("addIceCandidate timeout")), 5e3)
          )
        ]);
        this.debug.log(`\u{1F9CA} Successfully added ICE candidate for ${this.peerId.substring(0, 8)}...`);
      } catch (error) {
        this.debug.error(`\u{1F9CA} Failed to add ICE candidate for ${this.peerId.substring(0, 8)}...:`, error);
        this.debug.error("ICE candidate that failed:", candidate);
        this.debug.error("Current connection state:", this.connection.connectionState);
        this.debug.error("Current ICE state:", this.connection.iceConnectionState);
      }
    }
    async processPendingIceCandidates() {
      if (this.pendingIceCandidates.length > 0) {
        this.debug.log(`\u{1F9CA} Processing ${this.pendingIceCandidates.length} buffered ICE candidates for ${this.peerId.substring(0, 8)}...`);
        for (const candidate of this.pendingIceCandidates) {
          try {
            await Promise.race([
              this.connection.addIceCandidate(candidate),
              new Promise(
                (resolve, reject) => setTimeout(() => reject(new Error("addIceCandidate timeout")), 5e3)
              )
            ]);
            this.debug.log(`\u{1F9CA} Successfully added buffered ICE candidate (${candidate.type}) for ${this.peerId.substring(0, 8)}...`);
          } catch (error) {
            this.debug.error(`\u{1F9CA} Failed to add buffered ICE candidate for ${this.peerId.substring(0, 8)}...:`, error);
          }
        }
        this.pendingIceCandidates = [];
        this.debug.log(`\u{1F9CA} Finished processing buffered ICE candidates for ${this.peerId.substring(0, 8)}...`);
      }
    }
    sendMessage(message) {
      if (this.dataChannel && this.dataChannel.readyState === "open") {
        try {
          if (message instanceof ArrayBuffer || ArrayBuffer.isView(message)) {
            this.debug.log(`\u{1F4E6} Sending binary message (${message.byteLength || message.buffer.byteLength} bytes) to ${this.peerId.substring(0, 8)}...`);
            const buffer = ArrayBuffer.isView(message) ? message.buffer : message;
            const header = JSON.stringify({ type: "__BINARY__", size: buffer.byteLength });
            this.dataChannel.send(header);
            this.dataChannel.send(buffer);
            return true;
          } else {
            this.dataChannel.send(JSON.stringify(message));
            return true;
          }
        } catch (error) {
          this.debug.error(`Failed to send message to ${this.peerId}:`, error);
          return false;
        }
      }
      return false;
    }
    /**
     * Create a WritableStream for sending data to this peer
     * @param {object} options - Stream options
     * @returns {WritableStream} A writable stream for sending data
     */
    createWritableStream(options = {}) {
      const streamId = options.streamId || this._generateStreamId();
      const metadata = {
        streamId,
        type: options.type || "binary",
        filename: options.filename,
        mimeType: options.mimeType,
        totalSize: options.totalSize,
        chunkSize: options.chunkSize,
        timestamp: Date.now()
      };
      this.debug.log(`\u{1F4E4} Creating writable stream ${streamId} to ${this.peerId.substring(0, 8)}...`);
      const configuredChunkSize = typeof metadata.chunkSize === "number" && Number.isFinite(metadata.chunkSize) ? metadata.chunkSize : 16384;
      const chunkSize = Math.max(4096, Math.min(configuredChunkSize, 65536));
      metadata.chunkSize = chunkSize;
      const highWaterMark = chunkSize * 32;
      const lowWaterMark = chunkSize * 8;
      const ensureChannelOpen = () => {
        if (!this.dataChannel || this.dataChannel.readyState !== "open") {
          throw new Error("Data channel not open");
        }
      };
      const waitForBufferDrain = () => {
        if (!this.dataChannel || this.dataChannel.readyState !== "open") {
          return Promise.resolve();
        }
        if (this.dataChannel.bufferedAmount <= lowWaterMark) {
          return Promise.resolve();
        }
        return new Promise((resolve) => {
          const checkBuffer = () => {
            if (!this.dataChannel || this.dataChannel.readyState !== "open") {
              resolve();
              return;
            }
            if (this.dataChannel.bufferedAmount <= lowWaterMark) {
              resolve();
            } else {
              setTimeout(checkBuffer, 20);
            }
          };
          checkBuffer();
        });
      };
      this.sendMessage({
        type: "__STREAM_INIT__",
        streamId,
        metadata
      });
      let chunkIndex = 0;
      const self2 = this;
      return new WritableStream({
        async write(chunk) {
          ensureChannelOpen();
          const data = chunk instanceof Uint8Array ? chunk : new Uint8Array(chunk);
          let offset = 0;
          while (offset < data.byteLength) {
            ensureChannelOpen();
            const sliceEnd = Math.min(offset + chunkSize, data.byteLength);
            const slice = data.subarray(offset, sliceEnd);
            const sliceBuffer = slice.byteOffset === 0 && slice.byteLength === slice.buffer.byteLength ? slice.buffer : slice.buffer.slice(slice.byteOffset, slice.byteOffset + slice.byteLength);
            const chunkMessage = {
              type: "__STREAM_CHUNK__",
              streamId,
              chunkIndex: chunkIndex++,
              size: slice.byteLength,
              encoding: "binary"
            };
            try {
              self2.dataChannel.send(JSON.stringify(chunkMessage));
              self2.dataChannel.send(sliceBuffer);
            } catch (error) {
              throw error;
            }
            offset = sliceEnd;
            if (self2.dataChannel && self2.dataChannel.bufferedAmount > highWaterMark) {
              await waitForBufferDrain();
            }
          }
        },
        async close() {
          if (self2.dataChannel && self2.dataChannel.readyState === "open") {
            await waitForBufferDrain();
            self2.sendMessage({
              type: "__STREAM_END__",
              streamId,
              totalChunks: chunkIndex
            });
          }
          self2._activeStreams.delete(streamId);
          self2.debug.log(`\u{1F4E4} Closed writable stream ${streamId}`);
        },
        async abort(reason) {
          if (self2.dataChannel && self2.dataChannel.readyState === "open") {
            self2.sendMessage({
              type: "__STREAM_ABORT__",
              streamId,
              reason: reason?.message || "Stream aborted"
            });
          }
          self2._activeStreams.delete(streamId);
          self2.debug.log(`\u274C Aborted writable stream ${streamId}: ${reason}`);
        }
      });
    }
    /**
     * Handle incoming stream messages and create ReadableStream
     * @private
     */
    _handleStreamMessage(message) {
      const { type } = message;
      switch (type) {
        case "__STREAM_INIT__":
          this._handleStreamInit(message);
          break;
        case "__STREAM_CHUNK__":
          if (message.encoding === "binary") {
            this._pendingBinaryPayloads.push({
              type: "streamChunk",
              header: message
            });
          } else {
            this._handleStreamChunkLegacy(message);
          }
          break;
        case "__STREAM_END__":
          this._handleStreamEnd(message);
          break;
        case "__STREAM_ABORT__":
          this._handleStreamAbort(message);
          break;
      }
    }
    _handleStreamInit(message) {
      const { streamId, metadata } = message;
      this.debug.log(`\u{1F4E5} Receiving stream ${streamId} from ${this.peerId.substring(0, 8)}...`);
      this._streamMetadata.set(streamId, metadata);
      this._streamChunks.set(streamId, []);
      const chunks = this._streamChunks.get(streamId);
      let controller;
      const readable = new ReadableStream({
        start(ctrl) {
          controller = ctrl;
        },
        cancel: (reason) => {
          this.debug.log(`\u{1F4E5} Stream ${streamId} cancelled: ${reason}`);
        }
      });
      this._activeStreams.set(streamId, {
        type: "readable",
        stream: readable,
        controller,
        metadata,
        chunks
      });
      this.emit("streamReceived", {
        peerId: this.peerId,
        streamId,
        stream: readable,
        metadata
      });
    }
    _handleStreamChunkLegacy(message) {
      const { streamId, chunkIndex, data } = message;
      const streamData = this._activeStreams.get(streamId);
      if (!streamData) {
        this.debug.warn(`Received chunk for unknown stream ${streamId}`);
        return;
      }
      const chunk = new Uint8Array(data);
      if (streamData.controller) {
        streamData.controller.enqueue(chunk);
      }
      if (Array.isArray(streamData.chunks)) {
        streamData.chunks.push(chunk);
      }
      this.debug.log(`\u{1F4E5} Received chunk ${chunkIndex} for stream ${streamId} (${chunk.length} bytes)`);
    }
    _handleStreamChunkBinary(header, chunk) {
      const { streamId, chunkIndex, size } = header;
      const streamData = this._activeStreams.get(streamId);
      if (!streamData) {
        this.debug.warn(`Received binary chunk for unknown stream ${streamId}`);
        return;
      }
      if (size && size !== chunk.byteLength) {
        this.debug.warn(`Stream ${streamId} chunk ${chunkIndex} size mismatch: expected ${size}, got ${chunk.byteLength}`);
      }
      if (streamData.controller) {
        streamData.controller.enqueue(chunk);
      }
      if (Array.isArray(streamData.chunks)) {
        streamData.chunks.push(chunk);
      }
      this.debug.log(`\u{1F4E5} Received chunk ${chunkIndex} for stream ${streamId} (${chunk.byteLength} bytes)`);
    }
    _handleStreamEnd(message) {
      const { streamId, totalChunks } = message;
      const streamData = this._activeStreams.get(streamId);
      if (!streamData) {
        this.debug.warn(`Received end for unknown stream ${streamId}`);
        return;
      }
      if (streamData.controller) {
        streamData.controller.close();
      }
      this.debug.log(`\u{1F4E5} Stream ${streamId} completed (${totalChunks} chunks)`);
      this._activeStreams.delete(streamId);
      this._streamChunks.delete(streamId);
      this._streamMetadata.delete(streamId);
      this.emit("streamCompleted", {
        peerId: this.peerId,
        streamId,
        totalChunks
      });
    }
    _handleStreamAbort(message) {
      const { streamId, reason } = message;
      const streamData = this._activeStreams.get(streamId);
      if (streamData && streamData.controller) {
        streamData.controller.error(new Error(reason));
      }
      this.debug.log(`\u274C Stream ${streamId} aborted: ${reason}`);
      this._activeStreams.delete(streamId);
      this._streamChunks.delete(streamId);
      this._streamMetadata.delete(streamId);
      this.emit("streamAborted", {
        peerId: this.peerId,
        streamId,
        reason
      });
    }
    _generateStreamId() {
      return `stream-${Date.now()}-${Math.random().toString(36).substring(2, 9)}`;
    }
    /**
     * CRITICAL FIX: Manually check for new remote tracks after renegotiation
     * This is needed because replaceTrack() doesn't trigger ontrack events
     */
    checkForNewRemoteTracks() {
      this.debug.log(`\u{1F50D} TRACK CHECK: Checking transceivers for new remote tracks from ${this.peerId.substring(0, 8)}...`);
      try {
        const transceivers = this.connection.getTransceivers();
        let foundNewTracks = false;
        transceivers.forEach((transceiver, index) => {
          const track = transceiver.receiver.track;
          if (track && track.readyState === "live") {
            this.debug.log(`\u{1F50D} TRACK CHECK: Transceiver ${index} has live ${track.kind} track: ${track.id.substring(0, 8)}...`);
            const isNewTrack = !this.processedTrackIds || !this.processedTrackIds.has(track.id);
            if (isNewTrack) {
              this.debug.log(`\u{1F3B5} NEW TRACK FOUND: Processing new ${track.kind} track from ${this.peerId.substring(0, 8)}...`);
              const stream = new MediaStream([track]);
              if (this.validateRemoteStream(stream, track)) {
                this.remoteStream = stream;
                this.markStreamAsRemote(stream);
                if (!this.processedTrackIds) this.processedTrackIds = /* @__PURE__ */ new Set();
                this.processedTrackIds.add(track.id);
                this.debug.log("\u{1F6A8} TRACK CHECK: Emitting remoteStream event for new track");
                if (this.allowRemoteStreams) {
                  this.emit("remoteStream", { peerId: this.peerId, stream: this.remoteStream });
                } else {
                  this.debug.log("\u{1F512} TRACK CHECK: Buffering remote stream until crypto verification");
                  this.pendingRemoteStreams.push({ peerId: this.peerId, stream: this.remoteStream });
                }
                foundNewTracks = true;
              }
            }
          }
        });
        if (!foundNewTracks) {
          this.debug.log("\u{1F50D} TRACK CHECK: No new remote tracks found");
        }
      } catch (error) {
        this.debug.error("\u274C TRACK CHECK: Failed to check for remote tracks:", error);
      }
    }
    /**
       * Enhanced validation to ensure received stream is genuinely remote
       */
    validateRemoteStream(stream, track) {
      this.debug.log("\u{1F50D} VALIDATION: Starting remote stream validation...");
      if (!stream) {
        this.debug.error("\u274C VALIDATION: Stream is null or undefined");
        return false;
      }
      if (!track) {
        this.debug.error("\u274C VALIDATION: Track is null or undefined");
        return false;
      }
      if (this.localStream && stream.id === this.localStream.id) {
        this.debug.error("\u274C LOOPBACK DETECTED: Received our own local stream as remote!");
        this.debug.error("Local stream ID:", this.localStream.id);
        this.debug.error("Received stream ID:", stream.id);
        return false;
      }
      this.debug.log("\u2705 VALIDATION: Stream ID check passed");
      if (this.localStream) {
        const localTracks = this.localStream.getTracks();
        const isOwnTrack = localTracks.some((localTrack) => localTrack.id === track.id);
        if (isOwnTrack) {
          this.debug.error("\u274C TRACK LOOPBACK: This track is our own local track!");
          this.debug.error("Local track ID:", track.id);
          return false;
        }
      }
      this.debug.log("\u2705 VALIDATION: Track ID check passed");
      if (this.connection) {
        const transceivers = this.connection.getTransceivers();
        this.debug.log(`\u{1F50D} VALIDATION: Checking ${transceivers.length} transceivers for track ${track.id.substring(0, 8)}...`);
        const sourceTransceiver = transceivers.find((t) => t.receiver.track === track);
        if (!sourceTransceiver) {
          this.debug.warn("\u26A0\uFE0F VALIDATION: Track not found in any transceiver - may be invalid");
          this.debug.warn("Available transceivers:", transceivers.map((t) => ({
            kind: t.receiver?.track?.kind || "no-track",
            direction: t.direction,
            trackId: t.receiver?.track?.id?.substring(0, 8) || "none"
          })));
          this.debug.log("\u26A0\uFE0F VALIDATION: Allowing track despite transceiver lookup failure (temporary fix)");
        } else {
          if (sourceTransceiver.direction === "sendonly") {
            this.debug.error("\u274C Invalid direction: Receiving track from sendonly transceiver");
            return false;
          }
          this.debug.log(`\u2705 VALIDATION: Transceiver check passed (direction: ${sourceTransceiver.direction})`);
        }
      }
      if (stream && stream._peerPigeonOrigin === "local") {
        this.debug.error("\u274C Stream marked as local origin - preventing synchronization loop");
        return false;
      }
      this.debug.log("\u2705 VALIDATION: Local origin check passed");
      this.debug.log("\u2705 Remote stream validation passed for peer", this.peerId.substring(0, 8));
      return true;
    }
    /**
       * Mark a stream as genuinely remote to prevent future confusion
       */
    markStreamAsRemote(stream) {
      Object.defineProperty(stream, "_peerPigeonOrigin", {
        value: "remote",
        writable: false,
        enumerable: false,
        configurable: false
      });
      Object.defineProperty(stream, "_peerPigeonSourcePeerId", {
        value: this.peerId,
        writable: false,
        enumerable: false,
        configurable: false
      });
      this.debug.log(`\u{1F512} Stream ${stream.id} marked as remote from peer ${this.peerId.substring(0, 8)}`);
    }
    /**
       * Mark local stream to prevent it from being treated as remote
       */
    markStreamAsLocal(stream) {
      if (!stream) return;
      Object.defineProperty(stream, "_peerPigeonOrigin", {
        value: "local",
        writable: false,
        enumerable: false,
        configurable: false
      });
      this.debug.log(`\u{1F512} Stream ${stream.id} marked as local origin`);
    }
    /**
     * Add local stream using addTrack() method to trigger ontrack events
     */
    async addLocalStreamWithAddTrack(stream) {
      if (!stream || !this.connection) return;
      this.debug.log("\u{1F3A5} Adding local stream using addTrack() for proper ontrack events");
      const audioTracks = stream.getAudioTracks();
      const videoTracks = stream.getVideoTracks();
      this.markStreamAsLocal(stream);
      audioTracks.forEach((audioTrack, index) => {
        this.debug.log(`\u{1F3A4} Adding audio track ${index} using addTrack()`);
        try {
          const audioSender = this.connection.addTrack(audioTrack, stream);
          this.audioTransceiver = this.connection.getTransceivers().find((t) => t.sender === audioSender);
          this.setupAudioSendingMonitoring(audioTrack);
          this.debug.log(`\u{1F3A4} SENDING AUDIO to peer ${this.peerId.substring(0, 8)} - track enabled: ${audioTrack.enabled}`);
        } catch (error) {
          this.debug.error(`\u274C Failed to add audio track ${index}:`, error);
        }
      });
      videoTracks.forEach((videoTrack, index) => {
        this.debug.log(`\u{1F3A5} Adding video track ${index} using addTrack()`);
        try {
          const videoSender = this.connection.addTrack(videoTrack, stream);
          this.videoTransceiver = this.connection.getTransceivers().find((t) => t.sender === videoSender);
          this.debug.log(`\u{1F3A5} SENDING VIDEO to peer ${this.peerId.substring(0, 8)} - track enabled: ${videoTrack.enabled}`);
        } catch (error) {
          this.debug.error(`\u274C Failed to add video track ${index}:`, error);
        }
      });
      this.localStream = stream;
      this.debug.log("\u2705 Local stream added using addTrack() method");
      const transceivers = this.connection.getTransceivers();
      this.debug.log("\u{1F50D} Transceivers after addTrack():", transceivers.map((t) => ({
        kind: t.receiver?.track?.kind || "unknown",
        direction: t.direction,
        hasTrack: !!t.sender?.track,
        trackId: t.sender?.track?.id?.substring(0, 8) || "none",
        mid: t.mid
      })));
    }
    /**
       * Add or replace local media stream
       */
    async setLocalStream(stream) {
      if (!this.connection) {
        throw new Error("Connection not initialized");
      }
      this.debug.log(`Setting local stream for ${this.peerId}, current state: ${this.connection.connectionState}, signaling: ${this.connection.signalingState}`);
      const senders = this.connection.getSenders();
      for (const sender of senders) {
        if (sender.track) {
          this.debug.log("\uFFFD\uFE0F Removing existing track:", sender.track.kind);
          this.connection.removeTrack(sender);
        }
      }
      this.audioTransceiver = null;
      this.videoTransceiver = null;
      if (stream) {
        this.debug.log("\u{1F3A5} Adding new stream using addTrack() method");
        await this.addLocalStreamWithAddTrack(stream);
      } else {
        this.localStream = null;
        this.debug.log("\u2705 All tracks removed");
      }
      this.debug.log("Updated local media stream for", this.peerId);
      this.debug.log("\u2705 Stream updated - forcing renegotiation for media changes");
      this.debug.log(`   Current state: connectionState=${this.connection.connectionState}, signalingState=${this.connection.signalingState}`);
      if (stream) {
        setTimeout(() => {
          this.debug.log("\u{1F504} Forcing renegotiation for media stream changes");
          this.emit("renegotiationNeeded", { peerId: this.peerId });
        }, 200);
      }
    }
    /**
     * Force connection recovery for stuck connections
     */
    async forceConnectionRecovery() {
      this.debug.log(`\u{1F198} FORCE RECOVERY: Attempting emergency recovery for ${this.peerId.substring(0, 8)}...`);
      try {
        const offer = await Promise.race([
          this.connection.createOffer({ iceRestart: true }),
          new Promise(
            (resolve, reject) => setTimeout(() => reject(new Error("forceConnectionRecovery createOffer timeout")), 1e4)
          )
        ]);
        await Promise.race([
          this.connection.setLocalDescription(offer),
          new Promise(
            (resolve, reject) => setTimeout(() => reject(new Error("forceConnectionRecovery setLocalDescription timeout")), 1e4)
          )
        ]);
        if (this.mesh && this.mesh.sendSignalingMessage) {
          await this.mesh.sendSignalingMessage({
            type: "recovery-offer",
            data: offer,
            emergency: true
          }, this.peerId);
          this.debug.log(`\u2705 RECOVERY: Emergency offer sent for ${this.peerId.substring(0, 8)}...`);
        } else {
          this.debug.error(`\u274C RECOVERY: No mesh signaling available for ${this.peerId.substring(0, 8)}...`);
        }
      } catch (error) {
        this.debug.error(`\u274C RECOVERY: Emergency recovery failed for ${this.peerId.substring(0, 8)}...`, error);
        throw error;
      }
    }
    /**
       * Setup audio data monitoring for received audio tracks
       */
    setupAudioDataMonitoring(audioTrack, trackIndex) {
      this.debug.log(`\u{1F3B5} Setting up audio data monitoring for track ${trackIndex} from peer ${this.peerId.substring(0, 8)}`);
      try {
        const AudioContext = window.AudioContext || window.webkitAudioContext;
        if (!AudioContext) {
          this.debug.warn("\u{1F3B5} AudioContext not available - cannot monitor audio data");
          return;
        }
        const trackStream = new MediaStream([audioTrack]);
        const audioContext = new AudioContext();
        const source = audioContext.createMediaStreamSource(trackStream);
        const analyser = audioContext.createAnalyser();
        analyser.fftSize = 256;
        const bufferLength = analyser.frequencyBinCount;
        const dataArray = new Uint8Array(bufferLength);
        source.connect(analyser);
        let lastLogTime = 0;
        let totalSamples = 0;
        let samplesWithAudio = 0;
        let maxLevel = 0;
        const monitorAudio = () => {
          if (audioTrack.readyState === "ended") {
            this.debug.log(`\u{1F3B5} Audio track ${trackIndex} ended, stopping monitoring for peer ${this.peerId.substring(0, 8)}`);
            audioContext.close();
            return;
          }
          analyser.getByteFrequencyData(dataArray);
          const average = dataArray.reduce((sum, value) => sum + value, 0) / bufferLength;
          const currentTime = Date.now();
          totalSamples++;
          if (average > 5) {
            samplesWithAudio++;
            maxLevel = Math.max(maxLevel, average);
          }
          if (currentTime - lastLogTime > 5e3) {
            const audioActivity = totalSamples > 0 ? samplesWithAudio / totalSamples * 100 : 0;
            this.debug.log(`\u{1F3B5} Audio data from peer ${this.peerId.substring(0, 8)} track ${trackIndex}:`, {
              enabled: audioTrack.enabled,
              readyState: audioTrack.readyState,
              muted: audioTrack.muted,
              currentLevel: Math.round(average),
              maxLevel: Math.round(maxLevel),
              activityPercent: Math.round(audioActivity),
              samplesAnalyzed: totalSamples,
              hasAudioData: samplesWithAudio > 0
            });
            lastLogTime = currentTime;
            totalSamples = 0;
            samplesWithAudio = 0;
            maxLevel = 0;
          }
          if (audioTrack.readyState === "live") {
            requestAnimationFrame(monitorAudio);
          }
        };
        requestAnimationFrame(monitorAudio);
        audioTrack.addEventListener("ended", () => {
          this.debug.log(`\u{1F3B5} Audio track ${trackIndex} from peer ${this.peerId.substring(0, 8)} ended`);
          audioContext.close();
        });
        audioTrack.addEventListener("mute", () => {
          this.debug.log(`\u{1F3B5} Audio track ${trackIndex} from peer ${this.peerId.substring(0, 8)} muted`);
        });
        audioTrack.addEventListener("unmute", () => {
          this.debug.log(`\u{1F3B5} Audio track ${trackIndex} from peer ${this.peerId.substring(0, 8)} unmuted`);
        });
        this.debug.log(`\u{1F3B5} Audio monitoring started for track ${trackIndex} from peer ${this.peerId.substring(0, 8)}`);
      } catch (error) {
        this.debug.error(`\u{1F3B5} Failed to setup audio monitoring for track ${trackIndex}:`, error);
      }
    }
    /**
       * Setup audio sending monitoring for outgoing audio tracks
       */
    setupAudioSendingMonitoring(audioTrack) {
      this.debug.log(`\u{1F3A4} Setting up audio SENDING monitoring to peer ${this.peerId.substring(0, 8)}`);
      try {
        audioTrack.addEventListener("ended", () => {
          this.debug.log(`\u{1F3A4} Audio SENDING track ended to peer ${this.peerId.substring(0, 8)}`);
        });
        audioTrack.addEventListener("mute", () => {
          this.debug.log(`\u{1F3A4} Audio SENDING track muted to peer ${this.peerId.substring(0, 8)}`);
        });
        audioTrack.addEventListener("unmute", () => {
          this.debug.log(`\u{1F3A4} Audio SENDING track unmuted to peer ${this.peerId.substring(0, 8)}`);
        });
        const AudioContext = window.AudioContext || window.webkitAudioContext;
        if (!AudioContext) {
          this.debug.warn("\u{1F3A4} AudioContext not available - basic sending monitoring only");
          return;
        }
        const trackStream = new MediaStream([audioTrack]);
        const audioContext = new AudioContext();
        const source = audioContext.createMediaStreamSource(trackStream);
        const analyser = audioContext.createAnalyser();
        analyser.fftSize = 256;
        const bufferLength = analyser.frequencyBinCount;
        const dataArray = new Uint8Array(bufferLength);
        source.connect(analyser);
        let lastLogTime = 0;
        let totalSamples = 0;
        let activeSamples = 0;
        let maxSendLevel = 0;
        const monitorSending = () => {
          if (audioTrack.readyState === "ended") {
            this.debug.log(`\u{1F3A4} Audio sending track ended, stopping monitoring to peer ${this.peerId.substring(0, 8)}`);
            audioContext.close();
            return;
          }
          analyser.getByteFrequencyData(dataArray);
          const average = dataArray.reduce((sum, value) => sum + value, 0) / bufferLength;
          const currentTime = Date.now();
          totalSamples++;
          if (average > 5) {
            activeSamples++;
            maxSendLevel = Math.max(maxSendLevel, average);
          }
          if (currentTime - lastLogTime > 5e3) {
            const sendingActivity = totalSamples > 0 ? activeSamples / totalSamples * 100 : 0;
            this.debug.log(`\u{1F3A4} Audio SENDING to peer ${this.peerId.substring(0, 8)}:`, {
              trackEnabled: audioTrack.enabled,
              trackReadyState: audioTrack.readyState,
              trackMuted: audioTrack.muted,
              currentSendLevel: Math.round(average),
              maxSendLevel: Math.round(maxSendLevel),
              sendingActivityPercent: Math.round(sendingActivity),
              samplesAnalyzed: totalSamples,
              audioBeingSent: activeSamples > 0
            });
            lastLogTime = currentTime;
            totalSamples = 0;
            activeSamples = 0;
            maxSendLevel = 0;
          }
          if (audioTrack.readyState === "live") {
            requestAnimationFrame(monitorSending);
          }
        };
        requestAnimationFrame(monitorSending);
        this.debug.log(`\u{1F3A4} Audio sending monitoring started to peer ${this.peerId.substring(0, 8)}`);
      } catch (error) {
        this.debug.error(`\u{1F3A4} Failed to setup audio sending monitoring to peer ${this.peerId.substring(0, 8)}:`, error);
      }
    }
    /**
       * Get remote media stream
       */
    getRemoteStream() {
      return this.remoteStream;
    }
    /**
       * Get local media stream
       */
    getLocalStream() {
      return this.localStream;
    }
    /**
     * Allow remote streams to be emitted (called after crypto verification)
     */
    allowRemoteStreamEmission() {
      this.debug.log(`\u{1F513} CRYPTO: Allowing remote stream emission for ${this.peerId.substring(0, 8)}...`);
      this.allowRemoteStreams = true;
      while (this.pendingRemoteStreams.length > 0) {
        const streamEvent = this.pendingRemoteStreams.shift();
        this.debug.log(`\u{1F513} CRYPTO: Emitting buffered remote stream from ${streamEvent.peerId.substring(0, 8)}...`);
        this.emit("remoteStream", streamEvent);
      }
    }
    /**
     * Block remote streams (called when crypto is required but not verified)
     */
    blockRemoteStreamEmission() {
      if (typeof window !== "undefined" && window.DISABLE_CRYPTO_BLOCKING) {
        this.debug.log(`\u{1F513} CRYPTO: Video test mode - NOT blocking remote stream emission for ${this.peerId.substring(0, 8)}...`);
        return;
      }
      this.debug.log(`\u{1F512} CRYPTO: Blocking remote stream emission for ${this.peerId.substring(0, 8)}...`);
      this.allowRemoteStreams = false;
    }
    /**
       * Check if connection has video/audio capabilities
       */
    getMediaCapabilities() {
      const capabilities = {
        hasLocalVideo: false,
        hasLocalAudio: false,
        hasRemoteVideo: false,
        hasRemoteAudio: false
      };
      if (this.localStream) {
        capabilities.hasLocalVideo = this.localStream.getVideoTracks().length > 0;
        capabilities.hasLocalAudio = this.localStream.getAudioTracks().length > 0;
      }
      if (this.remoteStream) {
        capabilities.hasRemoteVideo = this.remoteStream.getVideoTracks().length > 0;
        capabilities.hasRemoteAudio = this.remoteStream.getAudioTracks().length > 0;
      }
      return capabilities;
    }
    getStatus() {
      if (this._forcedStatus) {
        return this._forcedStatus;
      }
      if (this.dataChannel && this.dataChannel.readyState === "closed") {
        return "disconnected";
      }
      if (this.connection) {
        const connectionState = this.connection.connectionState;
        if (connectionState === "connected") {
          if (this.dataChannel && this.dataChannel.readyState === "open" && this.dataChannelReady) {
            return "connected";
          } else if (this.dataChannel && this.dataChannel.readyState === "open") {
            return "connected";
          } else if (this.dataChannel && this.dataChannel.readyState === "connecting") {
            return "channel-connecting";
          } else {
            return "connected";
          }
        } else if (connectionState === "connecting") {
          if (this.dataChannel && this.dataChannel.readyState === "connecting") {
            return "channel-connecting";
          } else {
            return "connecting";
          }
        } else if (connectionState === "new") {
          return "connecting";
        } else {
          return connectionState;
        }
      }
      if (this.dataChannel) {
        if (this.dataChannel.readyState === "connecting") {
          return "channel-connecting";
        } else if (this.dataChannel.readyState === "closed") {
          return "disconnected";
        }
      }
      return "connecting";
    }
    getDetailedStatus() {
      const status = {
        connectionState: this.connection ? this.connection.connectionState : "no-connection",
        iceConnectionState: this.connection ? this.connection.iceConnectionState : "no-connection",
        dataChannelState: this.dataChannel ? this.dataChannel.readyState : "no-channel",
        dataChannelReady: this.dataChannelReady,
        isClosing: this.isClosing,
        overallStatus: this.getStatus()
      };
      if (this.remoteStream || this.localStream) {
        status.audioTracks = {
          remote: this.remoteStream ? this.remoteStream.getAudioTracks().length : 0,
          local: this.localStream ? this.localStream.getAudioTracks().length : 0
        };
        status.videoTracks = {
          remote: this.remoteStream ? this.remoteStream.getVideoTracks().length : 0,
          local: this.localStream ? this.localStream.getVideoTracks().length : 0
        };
      }
      return status;
    }
    /**
     * Restart ICE using signaling coordination (WebSocket or mesh)
     * This allows ICE restart to work even when the signaling server is down
     * by using the mesh for coordination
     */
    async restartIceViaSignaling() {
      if (!this.connection) {
        throw new Error("No connection to restart ICE for");
      }
      this.debug.log(`\u{1F504} Restarting ICE via signaling for ${this.peerId}`);
      try {
        this.connection.restartIce();
        const offer = await Promise.race([
          this.connection.createOffer({ iceRestart: true }),
          new Promise(
            (resolve, reject) => setTimeout(() => reject(new Error("ICE restart createOffer timeout")), 1e4)
          )
        ]);
        await Promise.race([
          this.connection.setLocalDescription(offer),
          new Promise(
            (resolve, reject) => setTimeout(() => reject(new Error("ICE restart setLocalDescription timeout")), 1e4)
          )
        ]);
        if (this.mesh && this.mesh.sendSignalingMessage) {
          await this.mesh.sendSignalingMessage({
            type: "ice-restart-offer",
            data: { offer }
          }, this.peerId);
          this.debug.log(`\u2705 ICE restart offer sent for ${this.peerId}`);
        } else {
          throw new Error("No signaling method available for ICE restart");
        }
      } catch (error) {
        this.debug.error(`Failed to restart ICE for ${this.peerId}:`, error);
        throw error;
      }
    }
    /**
     * Handle incoming ICE restart offers
     */
    async handleIceRestartOffer(offer) {
      if (!this.connection) {
        this.debug.error("Cannot handle ICE restart offer - no connection");
        return;
      }
      this.debug.log(`\u{1F504} Handling ICE restart offer from ${this.peerId}`);
      try {
        await Promise.race([
          this.connection.setRemoteDescription(offer),
          new Promise(
            (resolve, reject) => setTimeout(() => reject(new Error("ICE restart setRemoteDescription timeout")), 1e4)
          )
        ]);
        const answer = await Promise.race([
          this.connection.createAnswer(),
          new Promise(
            (resolve, reject) => setTimeout(() => reject(new Error("ICE restart createAnswer timeout")), 1e4)
          )
        ]);
        await Promise.race([
          this.connection.setLocalDescription(answer),
          new Promise(
            (resolve, reject) => setTimeout(() => reject(new Error("ICE restart setLocalDescription timeout")), 1e4)
          )
        ]);
        if (this.mesh && this.mesh.sendSignalingMessage) {
          await this.mesh.sendSignalingMessage({
            type: "ice-restart-answer",
            data: { answer }
          }, this.peerId);
          this.debug.log(`\u2705 ICE restart answer sent for ${this.peerId}`);
        }
      } catch (error) {
        this.debug.error(`Failed to handle ICE restart offer from ${this.peerId}:`, error);
        throw error;
      }
    }
    /**
     * Handle incoming ICE restart answers
     */
    async handleIceRestartAnswer(answer) {
      if (!this.connection) {
        this.debug.error("Cannot handle ICE restart answer - no connection");
        return;
      }
      this.debug.log(`\u{1F504} Handling ICE restart answer from ${this.peerId}`);
      try {
        await Promise.race([
          this.connection.setRemoteDescription(answer),
          new Promise(
            (resolve, reject) => setTimeout(() => reject(new Error("ICE restart setRemoteDescription timeout")), 1e4)
          )
        ]);
        this.debug.log(`\u2705 ICE restart completed for ${this.peerId}`);
      } catch (error) {
        this.debug.error(`Failed to handle ICE restart answer from ${this.peerId}:`, error);
        throw error;
      }
    }
    close() {
      this.isClosing = true;
      if (this.iceTimeoutId) {
        clearTimeout(this.iceTimeoutId);
        this.iceTimeoutId = null;
      }
      if (this.connection) {
        this.connection.close();
      }
    }
  };

  // src/ConnectionManager.js
  var ConnectionManager = class extends EventEmitter {
    constructor(mesh) {
      super();
      this.mesh = mesh;
      this.debug = DebugLogger_default.create("ConnectionManager");
      this.peers = /* @__PURE__ */ new Map();
      this.connectionAttempts = /* @__PURE__ */ new Map();
      this.pendingIceCandidates = /* @__PURE__ */ new Map();
      this.disconnectionInProgress = /* @__PURE__ */ new Set();
      this.cleanupInProgress = /* @__PURE__ */ new Set();
      this.lastConnectionAttempt = /* @__PURE__ */ new Map();
      this.activeRenegotiations = /* @__PURE__ */ new Set();
      this.renegotiationQueue = /* @__PURE__ */ new Map();
      this.maxConcurrentRenegotiations = 1;
      this.maxConnectionAttempts = 3;
      this.retryDelay = 500;
      this.startPeriodicCleanup();
      this.startStuckConnectionMonitoring();
      this.setupMeshEventListeners();
    }
    /**
     * Set up event listeners for mesh-level events
     */
    setupMeshEventListeners() {
      this.mesh.addEventListener("peerKeyAdded", (event) => {
        this.handlePeerKeyAdded(event.peerId);
      });
    }
    /**
     * Handle when a peer's crypto key is successfully added
     * @param {string} peerId - The peer ID whose key was added
     */
    async handlePeerKeyAdded(peerId) {
      this.debug.log(`\u{1F510} Key added for ${peerId.substring(0, 8)}... - crypto verification complete`);
      this.debug.log(`\u{1F510} Crypto verified for ${peerId.substring(0, 8)}... - user must manually invoke media to enable streams`);
    }
    async connectToPeer(targetPeerId) {
      this.debug.log(`connectToPeer called for ${targetPeerId.substring(0, 8)}...`);
      if (this.peers.has(targetPeerId)) {
        this.debug.log(`Already connected to ${targetPeerId.substring(0, 8)}...`);
        return;
      }
      if (this.mesh.peerDiscovery.isAttemptingConnection(targetPeerId)) {
        this.debug.log(`Already attempting connection to ${targetPeerId.substring(0, 8)}... via PeerDiscovery`);
        return;
      }
      const shouldBeInitiator = this.mesh.peerId > targetPeerId;
      if (!shouldBeInitiator) {
        const connectedCount = this.getConnectedPeerCount();
        const floor = this.mesh.connectivityFloor || 0;
        if (connectedCount < floor) {
          this.debug.log(`\u{1F504} INITIATOR OVERRIDE: Forcing initiation to ${targetPeerId.substring(0, 8)}... (connected ${connectedCount} < floor ${floor})`);
        } else {
          this.debug.log(`\u{1F504} INITIATOR LOGIC: Not becoming initiator for ${targetPeerId.substring(0, 8)}... (our ID smaller, connected ${connectedCount} >= floor ${floor})`);
          return;
        }
      }
      this.debug.log(`\u{1F504} INITIATOR LOGIC: Becoming initiator for ${targetPeerId.substring(0, 8)}... (our ID: ${this.mesh.peerId.substring(0, 8)}... is greater)`);
      if (!this.mesh.canAcceptMorePeers()) {
        this.debug.log(`Cannot connect to ${targetPeerId.substring(0, 8)}... (max peers reached: ${this.mesh.maxPeers})`);
        return;
      }
      const now = Date.now();
      const attempts = this.connectionAttempts.get(targetPeerId) || 0;
      if (attempts > 0) {
        const lastAttempt = this.lastConnectionAttempt.get(targetPeerId) || 0;
        const connectedCount = this.getConnectedPeerCount();
        const retryDelay = connectedCount === 0 ? 200 : this.retryDelay;
        if (now - lastAttempt < retryDelay) {
          const remaining = retryDelay - (now - lastAttempt);
          this.debug.log(`Connection to ${targetPeerId.substring(0, 8)}... on cooldown (${Math.round(remaining / 1e3)}s remaining, isolated: ${connectedCount === 0})`);
          return;
        }
      }
      if (attempts >= this.maxConnectionAttempts) {
        this.mesh.emit("statusChanged", { type: "warning", message: `Max connection attempts reached for ${targetPeerId.substring(0, 8)}...` });
        this.mesh.peerDiscovery.removeDiscoveredPeer(targetPeerId);
        return;
      }
      this.debug.log(`Starting connection to ${targetPeerId.substring(0, 8)}... (attempt ${attempts + 1})`);
      this.connectionAttempts.set(targetPeerId, attempts + 1);
      this.lastConnectionAttempt.set(targetPeerId, now);
      this.mesh.peerDiscovery.trackConnectionAttempt(targetPeerId);
      try {
        this.debug.log(`Creating PeerConnection for ${targetPeerId.substring(0, 8)}...`);
        const options = {
          localStream: null,
          // Always null - media must be manually added later
          // ALWAYS enable both audio and video transceivers for maximum compatibility
          // This allows peers to receive media even if they don't have media when connecting
          enableAudio: true,
          enableVideo: true
          // allowRemoteStreams defaults to false - streams only invoked when user clicks "Start Media"
        };
        this.debug.log(`\u{1F504} INITIATOR SETUP: Creating PeerConnection(${targetPeerId.substring(0, 8)}..., isInitiator=true)`);
        const peerConnection = new PeerConnection(targetPeerId, true, options);
        this.setupPeerConnectionHandlers(peerConnection);
        this.peers.set(targetPeerId, peerConnection);
        this.debug.log(`Creating WebRTC connection for ${targetPeerId.substring(0, 8)}...`);
        await peerConnection.createConnection();
        this.debug.log(`Creating offer for ${targetPeerId.substring(0, 8)}...`);
        const offer = await peerConnection.createOffer();
        this.debug.log(`Offer created for ${targetPeerId.substring(0, 8)}...`, {
          type: offer.type,
          sdpLength: offer.sdp?.length || 0,
          hasAudio: offer.sdp?.includes("m=audio") || false,
          hasVideo: offer.sdp?.includes("m=video") || false
        });
        this.debug.log(`Sending offer to ${targetPeerId.substring(0, 8)}...`);
        await this.mesh.sendSignalingMessage({
          type: "offer",
          data: offer
        }, targetPeerId);
        this.mesh.emit("statusChanged", { type: "info", message: `Offer sent to ${targetPeerId.substring(0, 8)}...` });
      } catch (error) {
        this.debug.error("Failed to connect to peer:", error);
        const isConnectionTimeout = error.message?.includes("timeout");
        if (!isConnectionTimeout) {
          this.mesh.emit("statusChanged", {
            type: "error",
            message: `Failed to connect to ${targetPeerId.substring(0, 8)}...: ${error.message}`
          });
        }
        this.cleanupFailedConnection(targetPeerId);
      }
    }
    /**
     * Force connection attempt ignoring standard initiator lexicographic rule (used for connectivity floor).
     */
    async connectToPeerOverride(targetPeerId) {
      if (this.peers.has(targetPeerId)) return;
      const connectedCount = this.getConnectedPeerCount();
      const floor = this.mesh.connectivityFloor || 0;
      if (connectedCount >= floor) return;
      this.debug.log(`\u26A1 OVERRIDE CONNECT: initiating to ${targetPeerId.substring(0, 8)}... (connected ${connectedCount} < floor ${floor})`);
      this.mesh.peerDiscovery.clearConnectionAttempt(targetPeerId);
      const options = {
        localStream: null,
        enableAudio: true,
        enableVideo: true
      };
      const peerConnection = new PeerConnection(targetPeerId, true, options);
      this.setupPeerConnectionHandlers(peerConnection);
      this.peers.set(targetPeerId, peerConnection);
      try {
        await peerConnection.createConnection();
        const offer = await peerConnection.createOffer();
        await this.mesh.sendSignalingMessage({ type: "offer", data: offer }, targetPeerId);
      } catch (e) {
        this.debug.warn(`Override connection failed to ${targetPeerId.substring(0, 8)}...: ${e.message}`);
        this.mesh.peerDiscovery.removeDiscoveredPeer(targetPeerId);
        this.peers.delete(targetPeerId);
      }
    }
    cleanupFailedConnection(peerId) {
      this.debug.log(`Cleaning up failed connection for ${peerId.substring(0, 8)}...`);
      let peerRemoved = false;
      if (this.peers.has(peerId)) {
        const peer = this.peers.get(peerId);
        const status = peer.getStatus();
        this.debug.log(`Removing peer ${peerId.substring(0, 8)}... with status: ${status}`);
        try {
          if (typeof peer.markAsFailed === "function") {
            peer.markAsFailed("failed");
          }
          peer.close();
        } catch (error) {
          this.debug.error("Error closing failed connection:", error);
        }
        this.peers.delete(peerId);
        peerRemoved = true;
        this.debug.log(`Successfully removed peer ${peerId.substring(0, 8)}... from peers Map`);
      } else {
        this.debug.log(`Peer ${peerId.substring(0, 8)}... was not in peers Map`);
      }
      this.mesh.peerDiscovery.clearConnectionAttempt(peerId);
      this.pendingIceCandidates.delete(peerId);
      if (peerRemoved) {
        this.debug.log(`Emitting peersUpdated after removing ${peerId.substring(0, 8)}...`);
        this.emit("peersUpdated");
      }
    }
    cleanupRaceCondition(peerId) {
      if (this.peers.has(peerId)) {
        const peer = this.peers.get(peerId);
        try {
          peer.close();
        } catch (error) {
          this.debug.error("Error closing race condition connection:", error);
        }
        this.peers.delete(peerId);
      }
      this.pendingIceCandidates.delete(peerId);
      this.emit("peersUpdated");
    }
    setupPeerConnectionHandlers(peerConnection) {
      peerConnection.addEventListener("iceCandidate", async (event) => {
        try {
          this.debug.log("Sending ICE candidate to", event.peerId);
          await this.mesh.sendSignalingMessage({
            type: "ice-candidate",
            data: event.candidate
          }, event.peerId);
        } catch (error) {
          this.debug.error("Failed to send ICE candidate:", error);
        }
      });
      peerConnection.addEventListener("connected", (event) => {
        this.debug.log(`[EVENT] Connected event received from ${event.peerId.substring(0, 8)}...`);
        this.connectionAttempts.delete(event.peerId);
        this.mesh.emit("statusChanged", { type: "info", message: `WebRTC connected to ${event.peerId.substring(0, 8)}...` });
        this.mesh.peerDiscovery.clearConnectionAttempt(event.peerId);
        this.mesh.peerDiscovery.updateDiscoveryTimestamp(event.peerId);
        this.emit("peersUpdated");
      });
      peerConnection.addEventListener("disconnected", (event) => {
        this.mesh.emit("statusChanged", { type: "info", message: `Disconnected from ${event.peerId.substring(0, 8)}...` });
        this.handlePeerDisconnection(event.peerId, event.reason);
      });
      peerConnection.addEventListener("dataChannelOpen", (event) => {
        this.debug.log(`[EVENT] DataChannelOpen event received from ${event.peerId.substring(0, 8)}...`);
        this.mesh.emit("statusChanged", { type: "info", message: `Data channel ready with ${event.peerId.substring(0, 8)}...` });
        this.emit("peersUpdated");
        if (this.mesh.peerDiscovery) {
          this.mesh.peerDiscovery.onConnectionEstablished();
        }
        if (this.mesh.cryptoManager) {
          const hasExistingKey = this.mesh.cryptoManager.peerKeys.has(event.peerId);
          if (!hasExistingKey) {
            this.debug.log(`\u{1F510} Automatically exchanging keys with newly connected peer ${event.peerId.substring(0, 8)}...`);
            setTimeout(() => {
              this.mesh.exchangeKeysWithPeer(event.peerId).catch((error) => {
                this.debug.error(`\u{1F510} Failed to exchange keys with ${event.peerId.substring(0, 8)}:`, error);
              });
            }, 0);
          } else {
            this.debug.log(`\u{1F510} Skipping key exchange with ${event.peerId.substring(0, 8)}... - key already exists`);
          }
        }
        this.mesh.emit("peerConnected", { peerId: event.peerId });
      });
      peerConnection.addEventListener("message", (event) => {
        this.handleIncomingMessage(event.message, event.peerId);
      });
      peerConnection.addEventListener("remoteStream", (event) => {
        this.debug.log(`[EVENT] Remote stream received from ${event.peerId.substring(0, 8)}...`);
        this.emit("remoteStream", event);
        this.debug.log("\u{1F504} MEDIA FORWARDING: Disabled to prevent renegotiation conflicts with 3+ peers");
      });
      peerConnection.addEventListener("streamReceived", (event) => {
        this.debug.log(`[EVENT] Data stream received from ${event.peerId.substring(0, 8)}...`);
        this.mesh.emit("streamReceived", event);
      });
      peerConnection.addEventListener("streamCompleted", (event) => {
        this.debug.log(`[EVENT] Data stream completed from ${event.peerId.substring(0, 8)}...`);
        this.mesh.emit("streamCompleted", event);
      });
      peerConnection.addEventListener("streamAborted", (event) => {
        this.debug.log(`[EVENT] Data stream aborted from ${event.peerId.substring(0, 8)}...`);
        this.mesh.emit("streamAborted", event);
      });
      peerConnection.addEventListener("renegotiationNeeded", async (event) => {
        this.debug.log(`\u{1F504} Renegotiation needed for ${event.peerId.substring(0, 8)}...`);
        if (this.activeRenegotiations.size >= this.maxConcurrentRenegotiations) {
          this.debug.log(`\u{1F504} QUEUE: Renegotiation for ${event.peerId.substring(0, 8)}... queued (${this.activeRenegotiations.size} active)`);
          this.renegotiationQueue.set(event.peerId, event);
          return;
        }
        await this._performRenegotiation(peerConnection, event);
      });
    }
    handlePeerDisconnection(peerId, reason) {
      if (this.disconnectionInProgress.has(peerId)) {
        this.debug.log(`Disconnection already in progress for ${peerId.substring(0, 8)}..., skipping duplicate`);
        return;
      }
      this.debug.log(`Handling peer disconnection: ${peerId.substring(0, 8)}... (${reason})`);
      this.disconnectionInProgress.add(peerId);
      try {
        if (this.peers.has(peerId)) {
          const peerConnection = this.peers.get(peerId);
          try {
            peerConnection.close();
          } catch (error) {
            this.debug.error("Error closing peer connection:", error);
          }
          this.peers.delete(peerId);
        }
        this.mesh.peerDiscovery.clearConnectionAttempt(peerId);
        this.pendingIceCandidates.delete(peerId);
        this.mesh.evictionManager.clearEvictionTracking(peerId);
        if (reason === "left network" || reason === "manually removed") {
          this.mesh.peerDiscovery.removeDiscoveredPeer(peerId);
          this.connectionAttempts.delete(peerId);
        } else if (reason === "connection failed" || reason === "connection disconnected" || reason === "ICE connection closed") {
          this.connectionAttempts.delete(peerId);
          this.debug.log(`Cleared connection attempt for ${peerId.substring(0, 8)}... due to ${reason} - will retry later`);
        }
        this.mesh.emit("peerDisconnected", { peerId, reason });
        this.emit("peersUpdated");
        const connectedCount = this.getConnectedPeerCount();
        const needsOptimization = connectedCount === 0;
        if (needsOptimization && this.mesh.autoDiscovery && this.mesh.peerDiscovery.getDiscoveredPeers().length > 0) {
          this.debug.log(`Completely disconnected (${connectedCount}/${this.mesh.maxPeers}), scheduling mesh optimization`);
          setTimeout(() => {
            const currentCount = this.getConnectedPeerCount();
            if (currentCount === 0) {
              this.debug.log(`Still completely disconnected (${currentCount}/${this.mesh.maxPeers}), attempting optimization`);
              this.mesh.peerDiscovery.optimizeMeshConnections(this.peers);
            } else {
              this.debug.log(`Connection recovered (${currentCount}/${this.mesh.maxPeers}), skipping optimization`);
            }
          }, 500);
        } else {
          this.debug.log(`Peer count appropriate at ${connectedCount}/${this.mesh.maxPeers}, no optimization needed`);
        }
      } finally {
        this.disconnectionInProgress.delete(peerId);
      }
    }
    disconnectAllPeers() {
      this.peers.forEach((peerConnection, peerId) => {
        peerConnection.close();
        this.mesh.emit("peerDisconnected", { peerId, reason: "mesh disconnected" });
      });
    }
    disconnectPeer(peerId, reason) {
      this.handlePeerDisconnection(peerId, reason);
    }
    removePeer(peerId) {
      this.mesh.peerDiscovery.removeDiscoveredPeer(peerId);
      this.mesh.peerDiscovery.clearConnectionAttempt(peerId);
      this.connectionAttempts.delete(peerId);
      if (this.peers.has(peerId)) {
        const peer = this.peers.get(peerId);
        if (peer.connection) {
          peer.connection.close();
        }
        this.peers.delete(peerId);
        this.mesh.emit("peerDisconnected", { peerId, reason: "manually removed" });
      }
      this.emit("peersUpdated");
    }
    canAcceptMorePeers() {
      const connectedCount = this.getConnectedPeerCount();
      const totalPeerCount = this.peers.size;
      if (connectedCount >= this.mesh.maxPeers) {
        this.debug.log(`At or over capacity: ${connectedCount}/${this.mesh.maxPeers} connected, rejecting new connections`);
        return false;
      }
      if (totalPeerCount >= this.mesh.maxPeers) {
        this.debug.log(`Total peer count at capacity: ${totalPeerCount}/${this.mesh.maxPeers}, rejecting new connections`);
        return false;
      }
      return true;
      this.debug.log(`Cannot accept more peers: ${connectedCount}/${this.mesh.maxPeers} connected, ${totalPeerCount} total peers in Map`);
      return false;
    }
    /**
       * Count peers that are in stale/non-viable states
       */
    getStalePeerCount() {
      const now = Date.now();
      const STALE_THRESHOLD = 45e3;
      return Array.from(this.peers.values()).filter((peerConnection) => {
        const status = peerConnection.getStatus();
        const connectionAge = now - peerConnection.connectionStartTime;
        return connectionAge > STALE_THRESHOLD && (status === "failed" || status === "disconnected" || status === "closed");
      }).length;
    }
    getConnectedPeerCount() {
      return Array.from(this.peers.values()).filter(
        (peerConnection) => peerConnection.getStatus() === "connected"
      ).length;
    }
    getConnectedPeers() {
      return Array.from(this.peers.values()).filter(
        (peerConnection) => peerConnection.getStatus() === "connected"
      );
    }
    getPeers() {
      return Array.from(this.peers.entries()).map(([peerId, peerConnection]) => ({
        peerId,
        status: peerConnection.getStatus(),
        isInitiator: peerConnection.isInitiator,
        connectionStartTime: peerConnection.connectionStartTime
      }));
    }
    hasPeer(peerId) {
      return this.peers.has(peerId);
    }
    getPeer(peerId) {
      return this.peers.get(peerId);
    }
    sendMessage(content) {
      if (!content || typeof content !== "string") {
        this.debug.error("Invalid message content:", content);
        return 0;
      }
      this.debug.log(`Broadcasting message via gossip protocol: "${content}"`);
      const messageId = this.mesh.gossipManager.broadcastMessage(content, "chat");
      if (messageId) {
        const connectedCount = this.getConnectedPeerCount();
        this.debug.log(`Message broadcasted via gossip to ${connectedCount} directly connected peer(s), will propagate to entire network`);
        return connectedCount;
      } else {
        this.debug.error("Failed to broadcast message via gossip protocol");
        return 0;
      }
    }
    /**
       * Send a message directly to a specific peer via data channel
       * @param {string} peerId - The peer ID to send to
       * @param {Object} message - The message object to send
       * @returns {boolean} - True if message was sent successfully
       */
    sendDirectMessage(peerId, message) {
      const peerConnection = this.peers.get(peerId);
      if (!peerConnection) {
        this.debug.warn(`Cannot send direct message to ${peerId?.substring(0, 8)}: peer not connected`);
        return false;
      }
      try {
        this.debug.log(`\u{1F4E4} Sending direct message to ${peerId?.substring(0, 8)}:`, message);
        peerConnection.sendMessage(message);
        return true;
      } catch (error) {
        this.debug.error(`Failed to send direct message to ${peerId?.substring(0, 8)}:`, error);
        return false;
      }
    }
    async handleIceCandidate(candidate, fromPeerId) {
      this.debug.log("Handling ICE candidate from", fromPeerId);
      const peerConnection = this.peers.get(fromPeerId);
      if (peerConnection) {
        try {
          await peerConnection.handleIceCandidate(candidate);
        } catch (error) {
          this.debug.error("Failed to add ICE candidate:", error);
        }
      } else {
        this.debug.log("Buffering ICE candidate for", fromPeerId, "(no peer connection yet)");
        if (!this.pendingIceCandidates.has(fromPeerId)) {
          this.pendingIceCandidates.set(fromPeerId, []);
        }
        this.pendingIceCandidates.get(fromPeerId).push(candidate);
      }
    }
    async processPendingIceCandidates(peerId) {
      const candidates = this.pendingIceCandidates.get(peerId);
      if (candidates && candidates.length > 0) {
        this.debug.log(`Processing ${candidates.length} buffered ICE candidates for`, peerId);
        const peerConnection = this.peers.get(peerId);
        if (peerConnection) {
          for (const candidate of candidates) {
            try {
              await peerConnection.handleIceCandidate(candidate);
            } catch (error) {
              this.debug.error("Failed to add buffered ICE candidate:", error);
            }
          }
          this.pendingIceCandidates.delete(peerId);
        }
      }
    }
    cleanup() {
      this.stopPeriodicCleanup();
      this.peers.clear();
      this.connectionAttempts.clear();
      this.pendingIceCandidates.clear();
      this.disconnectionInProgress.clear();
      this.cleanupInProgress.clear();
      this.lastConnectionAttempt.clear();
    }
    /**
       * Start periodic cleanup of stale peer connections
       */
    startPeriodicCleanup() {
      if (this.cleanupInterval) {
        clearInterval(this.cleanupInterval);
      }
      if (environmentDetector.isBrowser) {
        this.cleanupInterval = window.setInterval(() => {
          this.cleanupStalePeers();
        }, 3e4);
      } else {
        this.cleanupInterval = setInterval(() => {
          this.cleanupStalePeers();
        }, 3e4);
      }
    }
    /**
       * Stop periodic cleanup
       */
    stopPeriodicCleanup() {
      if (this.cleanupInterval) {
        clearInterval(this.cleanupInterval);
        this.cleanupInterval = null;
      }
    }
    /**
       * Clean up peers that are in non-viable states for too long
       */
    cleanupStalePeers() {
      const now = Date.now();
      const STALE_THRESHOLD = 6e4;
      const DISCONNECTED_THRESHOLD = 5e3;
      const peersToCleanup = [];
      this.peers.forEach((peerConnection, peerId) => {
        const status = peerConnection.getStatus();
        const connectionAge = now - peerConnection.connectionStartTime;
        if (status === "disconnected" && connectionAge > DISCONNECTED_THRESHOLD) {
          this.debug.log(`Disconnected peer detected: ${peerId.substring(0, 8)}... (status: ${status}, age: ${Math.round(connectionAge / 1e3)}s)`);
          peersToCleanup.push(peerId);
        } else if (connectionAge > STALE_THRESHOLD) {
          if (status === "connecting" || status === "channel-connecting" || status === "failed" || status === "closed") {
            this.debug.log(`Stale peer detected: ${peerId.substring(0, 8)}... (status: ${status}, age: ${Math.round(connectionAge / 1e3)}s)`);
            peersToCleanup.push(peerId);
          }
        }
      });
      if (peersToCleanup.length > 0) {
        this.debug.log(`Cleaning up ${peersToCleanup.length} stale peer(s)`);
        peersToCleanup.forEach((peerId) => {
          this.cleanupFailedConnection(peerId);
        });
      }
    }
    /**
       * Force cleanup of peers that are not in connected state (for debugging)
       */
    forceCleanupInvalidPeers() {
      this.debug.log("Force cleaning up peers not in connected state...");
      const peersToRemove = [];
      this.peers.forEach((peerConnection, peerId) => {
        const status = peerConnection.getStatus();
        if (status !== "connected") {
          this.debug.log(`Found peer ${peerId.substring(0, 8)}... in invalid state: ${status}`);
          peersToRemove.push(peerId);
        }
      });
      peersToRemove.forEach((peerId) => {
        this.debug.log(`Force removing peer ${peerId.substring(0, 8)}...`);
        this.cleanupFailedConnection(peerId);
      });
      if (peersToRemove.length > 0) {
        this.debug.log(`Force cleaned up ${peersToRemove.length} invalid peers`);
        this.emit("peersUpdated");
      }
      return peersToRemove.length;
    }
    /**
       * Get a summary of all peer states for debugging
       */
    getPeerStateSummary() {
      const summary = {
        total: this.peers.size,
        connected: 0,
        connecting: 0,
        channelConnecting: 0,
        failed: 0,
        disconnected: 0,
        closed: 0,
        other: 0,
        stale: this.getStalePeerCount()
      };
      this.peers.forEach((peerConnection) => {
        const status = peerConnection.getStatus();
        switch (status) {
          case "connected":
            summary.connected++;
            break;
          case "connecting":
            summary.connecting++;
            break;
          case "channel-connecting":
            summary.channelConnecting++;
            break;
          case "failed":
            summary.failed++;
            break;
          case "disconnected":
            summary.disconnected++;
            break;
          case "closed":
            summary.closed++;
            break;
          default:
            summary.other++;
        }
      });
      return summary;
    }
    getDetailedPeerStatus() {
      const peerStatuses = {};
      this.peers.forEach((peerConnection, peerId) => {
        peerStatuses[peerId.substring(0, 8) + "..."] = {
          status: peerConnection.getStatus(),
          isInitiator: peerConnection.isInitiator,
          dataChannelReady: peerConnection.dataChannelReady,
          connectionStartTime: peerConnection.connectionStartTime,
          connectionState: peerConnection.connection?.connectionState,
          iceConnectionState: peerConnection.connection?.iceConnectionState
        };
      });
      return peerStatuses;
    }
    // Get all peer connections
    getAllConnections() {
      return Array.from(this.peers.values());
    }
    /**
       * Route incoming messages based on their type
       */
    handleIncomingMessage(message, fromPeerId) {
      if (!message || typeof message !== "object") {
        this.debug.warn("Received invalid message from", fromPeerId?.substring(0, 8));
        return;
      }
      if (message.type === "binary" && message.data instanceof Uint8Array) {
        this.debug.log(`\u{1F4E6} Received binary message (${message.size} bytes) from ${fromPeerId.substring(0, 8)}...`);
        this.mesh.emit("binaryMessageReceived", {
          from: fromPeerId,
          data: message.data,
          size: message.size,
          timestamp: Date.now()
        });
        return;
      }
      const filteredMessageTypes = /* @__PURE__ */ new Set([
        "signaling-relay",
        "peer-announce-relay",
        "bootstrap-keepalive",
        "client-peer-announcement",
        "cross-bootstrap-signaling"
      ]);
      const isFilteredMessage = filteredMessageTypes.has(message.type);
      if (isFilteredMessage) {
        this.debug.log(`\u{1F507} FILTER: Processing filtered message type '${message.type}' from ${fromPeerId?.substring(0, 8)} (not emitted to UI)`);
      }
      switch (message.type) {
        case "gossip":
          this.mesh.gossipManager.handleGossipMessage(message, fromPeerId).catch((error) => {
            this.debug.error("Error handling gossip message:", error);
          });
          break;
        case "eviction":
          this.handleEvictionMessage(message, fromPeerId);
          break;
        case "dht":
          if (this.mesh.webDHT) {
            this.mesh.webDHT.handleMessage(message, fromPeerId);
          }
          break;
        case "renegotiation-offer":
          this.handleRenegotiationOffer(message, fromPeerId);
          break;
        case "renegotiation-answer":
          this.handleRenegotiationAnswer(message, fromPeerId);
          break;
        case "signaling":
          this.debug.log(`\u{1F504} MESH SIGNALING: Received ${message.data?.type} from ${fromPeerId?.substring(0, 8)}...`);
          if (message.data && message.data.type) {
            const signalingMessage = {
              type: message.data.type,
              data: message.data.data,
              fromPeerId: message.fromPeerId || fromPeerId,
              targetPeerId: this.mesh.peerId,
              timestamp: message.timestamp
            };
            this.mesh.signalingHandler.handleSignalingMessage(signalingMessage);
          }
          break;
        case "signaling-relay":
          this.debug.log(`\u{1F507} FILTER: Processing signaling-relay from ${fromPeerId?.substring(0, 8)} (filtered from UI)`);
          if (message.data && message.targetPeerId === this.mesh.peerId) {
            this.mesh.signalingHandler.handleSignalingMessage({
              type: message.data.type,
              data: message.data.data,
              fromPeerId: message.fromPeerId || fromPeerId,
              targetPeerId: message.targetPeerId,
              timestamp: message.timestamp
            });
          }
          return;
        // Early return to prevent fallback to gossip handler
        case "peer-announce-relay":
          this.debug.log(`\u{1F507} FILTER: Processing peer-announce-relay from ${fromPeerId?.substring(0, 8)} (filtered from UI)`);
          if (message.data && this.mesh.signalingHandler) {
            this.mesh.signalingHandler.handlePeerAnnouncement(message.data, fromPeerId);
          }
          return;
        // Early return to prevent fallback to gossip handler
        case "bootstrap-keepalive":
          this.debug.log(`\u{1F507} FILTER: Processing bootstrap-keepalive from ${fromPeerId?.substring(0, 8)} (filtered from UI)`);
          if (this.mesh.peerDiscovery) {
            this.mesh.peerDiscovery.updateDiscoveryTimestamp(fromPeerId);
          }
          return;
        // Early return to prevent fallback to gossip handler
        case "client-peer-announcement":
          this.debug.log(`\u{1F507} FILTER: Processing client-peer-announcement from ${fromPeerId?.substring(0, 8)} (filtered from UI)`);
          if (message.clientPeerId && this.mesh.signalingHandler) {
            this.mesh.signalingHandler.handlePeerAnnouncement(message.clientPeerId);
          }
          return;
        // Early return to prevent fallback to gossip handler
        case "cross-bootstrap-signaling":
          this.debug.log(`\u{1F507} FILTER: Processing cross-bootstrap-signaling from ${fromPeerId?.substring(0, 8)} (filtered from UI)`);
          if (message.originalMessage && message.targetPeerId === this.mesh.peerId && this.mesh.signalingHandler) {
            this.mesh.signalingHandler.handleSignalingMessage({
              type: message.originalMessage.type,
              data: message.originalMessage.data,
              fromPeerId: message.originalMessage.fromPeerId || fromPeerId,
              targetPeerId: message.targetPeerId,
              timestamp: message.originalMessage.timestamp || message.timestamp
            });
          }
          return;
        // Early return to prevent fallback to gossip handler
        default:
          if (!isFilteredMessage) {
            this.debug.warn(`Unknown message type '${message.type}' from ${fromPeerId?.substring(0, 8)}, trying gossip handler`);
            this.mesh.gossipManager.handleGossipMessage(message, fromPeerId).catch((error) => {
              this.debug.error("Error handling unknown message as gossip:", error);
            });
          } else {
            this.debug.log(`\u{1F507} FILTER: Filtered message type '${message.type}' processed but not emitted`);
          }
          break;
      }
    }
    /**
       * Handle eviction messages
       */
    handleEvictionMessage(message, fromPeerId) {
      this.debug.log(`Received eviction notice from ${fromPeerId?.substring(0, 8)}: ${message.reason}`);
      this.mesh.emit("peerEvicted", {
        peerId: fromPeerId,
        reason: message.reason,
        initiatedByPeer: true
      });
      const peerConnection = this.peers.get(fromPeerId);
      if (peerConnection) {
        peerConnection.close();
        this.peers.delete(fromPeerId);
      }
    }
    /**
     * Perform a single renegotiation with conflict prevention
     * @private
     */
    async _performRenegotiation(peerConnection, event) {
      const peerId = event.peerId;
      this.activeRenegotiations.add(peerId);
      try {
        this.debug.log(`\u{1F504} ACTIVE: Starting renegotiation for ${peerId.substring(0, 8)}... (${this.activeRenegotiations.size} active)`);
        const signalingState = peerConnection.connection.signalingState;
        if (signalingState !== "stable" && signalingState !== "have-local-offer") {
          this.debug.log(`Skipping renegotiation for ${peerId.substring(0, 8)}... - connection in unsupported state (${signalingState})`);
          return;
        }
        if (peerConnection.connection.connectionState !== "connected") {
          this.debug.log(`Skipping renegotiation for ${peerId.substring(0, 8)}... - not connected (${peerConnection.connection.connectionState})`);
          return;
        }
        this.debug.log(`\u{1F504} Creating renegotiation offer for ${peerId.substring(0, 8)}... (signaling state: ${signalingState})`);
        const offer = await peerConnection.connection.createOffer();
        this.debug.log("\u{1F50D} RENEGOTIATION OFFER SDP DEBUG:");
        this.debug.log(`   SDP length: ${offer.sdp.length}`);
        this.debug.log(`   Contains video: ${offer.sdp.includes("m=video")}`);
        this.debug.log(`   Contains audio: ${offer.sdp.includes("m=audio")}`);
        await peerConnection.connection.setLocalDescription(offer);
        await this.mesh.sendSignalingMessage({
          type: "renegotiation-offer",
          data: offer
        }, peerId);
        this.debug.log(`\u2705 ACTIVE: Sent renegotiation offer to ${peerId.substring(0, 8)}...`);
      } catch (error) {
        this.debug.error(`\u274C ACTIVE: Failed to renegotiate with ${peerId.substring(0, 8)}...`, error);
      } finally {
        this.activeRenegotiations.delete(peerId);
        this.debug.log(`\u{1F504} ACTIVE: Completed renegotiation for ${peerId.substring(0, 8)}... (${this.activeRenegotiations.size} active)`);
        this._processRenegotiationQueue();
      }
    }
    /**
     * Process the next renegotiation in the queue
     * @private
     */
    _processRenegotiationQueue() {
      if (this.activeRenegotiations.size >= this.maxConcurrentRenegotiations || this.renegotiationQueue.size === 0) {
        return;
      }
      const [nextPeerId, nextEvent] = this.renegotiationQueue.entries().next().value;
      this.renegotiationQueue.delete(nextPeerId);
      const peerConnection = this.peers.get(nextPeerId);
      if (peerConnection) {
        this.debug.log(`\u{1F504} QUEUE: Processing queued renegotiation for ${nextPeerId.substring(0, 8)}...`);
        this._performRenegotiation(peerConnection, nextEvent);
      }
    }
    /**
     * Handle renegotiation offers from peers
     */
    async handleRenegotiationOffer(message, fromPeerId) {
      this.debug.log(`\u{1F504} Handling renegotiation offer via mesh from ${fromPeerId.substring(0, 8)}...`);
      const peerConnection = this.peers.get(fromPeerId);
      if (!peerConnection) {
        this.debug.error(`No peer connection found for renegotiation from ${fromPeerId.substring(0, 8)}...`);
        return;
      }
      try {
        const answer = await peerConnection.handleOffer(message.data);
        this.debug.log(`\u2705 Renegotiation offer processed, sending answer to ${fromPeerId.substring(0, 8)}...`);
        await this.mesh.sendSignalingMessage({
          type: "renegotiation-answer",
          data: answer
        }, fromPeerId);
        this.debug.log(`\u2705 Renegotiation completed via mesh with ${fromPeerId.substring(0, 8)}...`);
      } catch (error) {
        this.debug.error(`\u274C Failed to handle renegotiation offer via mesh from ${fromPeerId.substring(0, 8)}...`, error);
      }
    }
    async handleRenegotiationAnswer(message, fromPeerId) {
      this.debug.log(`\u{1F504} Handling renegotiation answer via mesh from ${fromPeerId.substring(0, 8)}...`);
      const peerConnection = this.peers.get(fromPeerId);
      if (!peerConnection) {
        this.debug.error(`No peer connection found for renegotiation answer from ${fromPeerId.substring(0, 8)}...`);
        return;
      }
      try {
        await peerConnection.handleAnswer(message.data);
        this.debug.log(`\u2705 Renegotiation answer processed from ${fromPeerId.substring(0, 8)}... - renegotiation complete`);
      } catch (error) {
        this.debug.error(`\u274C Failed to handle renegotiation answer via mesh from ${fromPeerId.substring(0, 8)}...`, error);
      }
    }
    /**
     * Monitor and fix stuck connections that remain in "have-local-offer" state
     * This is called periodically to detect and fix connections that get stuck
     */
    monitorAndFixStuckConnections() {
      if (!this.mesh.connected) return;
      const stuckConnections = [];
      for (const [peerId, peerConnection] of this.peers) {
        if (peerConnection.connection?.signalingState === "have-local-offer") {
          const connectionAge = Date.now() - peerConnection.connectionStartTime;
          if (connectionAge > 1e4) {
            stuckConnections.push(peerId);
          }
        }
      }
      if (stuckConnections.length > 0) {
        this.debug.log(`\u{1F6A8} STUCK MONITOR: Found ${stuckConnections.length} stuck connections - forcing recovery`);
        if (typeof window !== "undefined" && window.location?.hostname === "localhost") {
          console.warn("\u26A0\uFE0F LOCAL TESTING: WebRTC connections on localhost require media permissions!");
          console.warn('   Go to the Media tab and click "Start Media" to grant permissions.');
          console.warn("   See docs/LOCAL_TESTING.md for details.");
        }
        for (const peerId of stuckConnections) {
          this.forceConnectionRecovery(peerId).catch((error) => {
            this.debug.error(`Failed to recover stuck connection for ${peerId}:`, error);
          });
        }
      }
    }
    /**
     * Force recovery of a stuck connection by completely recreating it
     */
    async forceConnectionRecovery(peerId) {
      const peerConnection = this.getPeer(peerId);
      if (!peerConnection) {
        this.debug.error(`Cannot recover - peer ${peerId} not found`);
        return null;
      }
      this.debug.log(`\u{1F504} FORCE RECOVERY: Completely recreating connection for ${peerId.substring(0, 8)}...`);
      try {
        const currentLocalStream = peerConnection.getLocalStream();
        peerConnection.close();
        this.peers.delete(peerId);
        const freshConnection = await this.connectToPeer(peerId, false, {
          localStream: currentLocalStream
        });
        if (freshConnection && currentLocalStream) {
          await freshConnection.setLocalStream(currentLocalStream);
          this.debug.log(`\u2705 FORCE RECOVERY: Fresh connection created with media for ${peerId.substring(0, 8)}...`);
        }
        return freshConnection;
      } catch (error) {
        this.debug.error(`\u274C FORCE RECOVERY: Failed to recreate connection for ${peerId.substring(0, 8)}...`, error);
        throw error;
      }
    }
    /**
     * Start monitoring for stuck connections
     */
    startStuckConnectionMonitoring() {
      setInterval(() => {
        this.monitorAndFixStuckConnections();
      }, 2e3);
      this.debug.log("\u{1F50D} Started stuck connection monitoring");
    }
    /**
     * Forward a received stream to all other connected peers (except the sender)
     * This implements media forwarding through the mesh topology
     * @param {MediaStream} stream - The stream to forward
     * @param {string} sourcePeerId - The peer ID that sent the stream (don't forward back to them)
     * @private
     */
    async _forwardStreamToOtherPeers(stream, sourcePeerId) {
      if (!stream || !sourcePeerId) {
        this.debug.warn("Cannot forward stream - invalid parameters");
        return;
      }
      this.debug.log(`\u{1F504} FORWARD STREAM: Forwarding stream from ${sourcePeerId.substring(0, 8)}... to other connected peers`);
      const originalSourcePeerId = stream._peerPigeonSourcePeerId || sourcePeerId;
      let forwardCount = 0;
      for (const [peerId, connection] of this.peers) {
        if (peerId === sourcePeerId) {
          this.debug.log(`\u{1F504} FORWARD STREAM: Skipping source peer ${peerId.substring(0, 8)}...`);
          continue;
        }
        if (peerId === originalSourcePeerId) {
          this.debug.log(`\u{1F504} FORWARD STREAM: Skipping original stream creator ${peerId.substring(0, 8)}...`);
          continue;
        }
        if (connection.getStatus() !== "connected") {
          this.debug.log(`\u{1F504} FORWARD STREAM: Skipping disconnected peer ${peerId.substring(0, 8)}...`);
          continue;
        }
        try {
          this.debug.log(`\u{1F504} FORWARD STREAM: Setting forwarded stream for peer ${peerId.substring(0, 8)}...`);
          const forwardedStream = stream.clone();
          Object.defineProperty(forwardedStream, "_peerPigeonSourcePeerId", {
            value: originalSourcePeerId,
            writable: false,
            enumerable: false,
            configurable: false
          });
          Object.defineProperty(forwardedStream, "_peerPigeonOrigin", {
            value: "forwarded",
            writable: false,
            enumerable: false,
            configurable: false
          });
          await connection.setLocalStream(forwardedStream);
          forwardCount++;
          this.debug.log(`\u2705 FORWARD STREAM: Successfully forwarded stream to peer ${peerId.substring(0, 8)}...`);
        } catch (error) {
          this.debug.error(`\u274C FORWARD STREAM: Failed to forward stream to peer ${peerId.substring(0, 8)}...`, error);
        }
      }
      this.debug.log(`\u{1F504} FORWARD STREAM: Forwarded stream from ${sourcePeerId.substring(0, 8)}... to ${forwardCount} peer(s)`);
    }
  };

  // src/SignalingHandler.js
  var SignalingHandler = class extends EventEmitter {
    constructor(mesh, connectionManager) {
      super();
      this.debug = DebugLogger_default.create("SignalingHandler");
      this.mesh = mesh;
      this.connectionManager = connectionManager;
    }
    async handleSignalingMessage(message) {
      const { type, data, fromPeerId, targetPeerId } = message;
      if (type === "answer" || type === "renegotiation-answer") {
        console.log("\u{1F6A8} SIGNALING CRITICAL:", type, "from", fromPeerId?.substring(0, 8), "to", this.mesh.peerId?.substring(0, 8));
        this.debug.log("\u{1F50D} SIGNALING DEBUG: Received answer message:", {
          type,
          fromPeerId: fromPeerId?.substring(0, 8) + "...",
          targetPeerId: targetPeerId?.substring(0, 8) + "...",
          ourPeerId: this.mesh.peerId?.substring(0, 8) + "...",
          hasData: !!data,
          dataType: data?.type
        });
      }
      if (fromPeerId === this.mesh.peerId) {
        if (type === "answer" || type === "renegotiation-answer") {
          console.log("\u{1F6A8} REJECTING:", type, "- from our own peer ID");
          this.debug.log("\u{1F50D} SIGNALING DEBUG: Rejecting answer - from our own peer ID");
        }
        return;
      }
      if (targetPeerId && targetPeerId !== this.mesh.peerId) {
        if (type === "answer" || type === "renegotiation-answer") {
          console.log("\u{1F6A8} REJECTING:", type, "- target mismatch, target:", targetPeerId?.substring(0, 8), "us:", this.mesh.peerId?.substring(0, 8));
          this.debug.log("\u{1F50D} SIGNALING DEBUG: Rejecting answer - target mismatch:", {
            targetPeerId: targetPeerId?.substring(0, 8) + "...",
            ourPeerId: this.mesh.peerId?.substring(0, 8) + "..."
          });
        }
        return;
      }
      if (type === "cleanup" || type === "cleanup-all") {
        this.debug.log("Ignoring cleanup message:", { type, fromPeerId });
        return;
      }
      this.debug.log("Processing signaling message:", { type, fromPeerId, targetPeerId });
      switch (type) {
        case "announce":
          this.handlePeerAnnouncement(fromPeerId);
          break;
        case "peer-discovered":
          if (data && data.peerId) {
            this.handlePeerAnnouncement(data.peerId);
          }
          break;
        case "goodbye":
          this.handlePeerGoodbye(fromPeerId);
          break;
        case "offer":
          await this.handleOffer(data, fromPeerId);
          break;
        case "renegotiation-offer":
          await this.handleRenegotiationOffer(data, fromPeerId);
          break;
        case "answer":
          console.log("\u{1F6A8} SWITCH: Reached answer case for", fromPeerId?.substring(0, 8));
          this.debug.log("\u{1F50D} SIGNALING DEBUG: Reached answer case in switch statement");
          await this.handleAnswer(data, fromPeerId);
          break;
        case "renegotiation-answer":
          console.log("\u{1F6A8} SWITCH: Reached renegotiation-answer case for", fromPeerId?.substring(0, 8));
          await this.handleRenegotiationAnswer(data, fromPeerId);
          break;
        case "ice-restart-offer":
          await this.handleIceRestartOffer(data, fromPeerId);
          break;
        case "ice-restart-answer":
          await this.handleIceRestartAnswer(data, fromPeerId);
          break;
        case "ice-candidate":
          await this.connectionManager.handleIceCandidate(data, fromPeerId);
          break;
        case "connection-rejected":
          this.handleConnectionRejected(data, fromPeerId);
          break;
      }
    }
    handlePeerAnnouncement(fromPeerId) {
      if (this.mesh.peerDiscovery.hasPeer(fromPeerId)) {
        this.debug.log(`Peer ${fromPeerId.substring(0, 8)}... already known, skipping announcement`);
        return;
      }
      this.mesh.emit("statusChanged", { type: "info", message: `Peer ${fromPeerId.substring(0, 8)}... announced` });
      this.debug.log(`Adding discovered peer: ${fromPeerId.substring(0, 8)}... to PeerDiscovery`);
      this.debug.log(`Current peer count: ${this.connectionManager.getConnectedPeerCount()}/${this.mesh.maxPeers}`);
      this.debug.log(`All discovered peers: ${Array.from(this.mesh.peerDiscovery.discoveredPeers.keys()).map((p) => p.substring(0, 8)).join(", ")}`);
      this.mesh.peerDiscovery.addDiscoveredPeer(fromPeerId);
    }
    handlePeerGoodbye(fromPeerId) {
      this.mesh.emit("statusChanged", { type: "info", message: `Peer ${fromPeerId.substring(0, 8)}... left the network` });
      this.mesh.peerDiscovery.removeDiscoveredPeer(fromPeerId);
      this.connectionManager.disconnectPeer(fromPeerId, "left network");
    }
    async handleOffer(offer, fromPeerId) {
      this.debug.log("Handling offer from", fromPeerId);
      if (!offer || typeof offer !== "object") {
        this.debug.error(`Invalid offer data from ${fromPeerId}:`, offer);
        this.mesh.emit("statusChanged", { type: "error", message: `Invalid offer data from ${fromPeerId.substring(0, 8)}...` });
        return;
      }
      if (!offer.type || offer.type !== "offer") {
        this.debug.error(`Invalid offer type from ${fromPeerId}:`, offer.type);
        this.mesh.emit("statusChanged", { type: "error", message: `Invalid offer type from ${fromPeerId.substring(0, 8)}...` });
        return;
      }
      if (!offer.sdp || typeof offer.sdp !== "string" || offer.sdp.length < 10) {
        this.debug.error(`Invalid offer SDP from ${fromPeerId}:`, offer.sdp?.substring(0, 100) || "undefined");
        this.mesh.emit("statusChanged", { type: "error", message: `Invalid offer SDP from ${fromPeerId.substring(0, 8)}...` });
        return;
      }
      const shouldWeInitiate = this.mesh.peerId > fromPeerId;
      if (shouldWeInitiate) {
        this.debug.log(`\u{1F504} INITIATOR LOGIC: Rejecting offer from ${fromPeerId.substring(0, 8)}... because we should be the initiator (our ID: ${this.mesh.peerId.substring(0, 8)}... > their ID)`);
        try {
          await this.mesh.sendSignalingMessage({
            type: "connection-rejected",
            data: {
              reason: "initiator_role_conflict",
              details: "Peer should wait for us to initiate based on peer ID comparison",
              shouldInitiate: false
            }
          }, fromPeerId);
        } catch (error) {
          this.debug.warn("Failed to send initiator rejection:", error);
        }
        return;
      }
      this.debug.log(`\u{1F504} INITIATOR LOGIC: Accepting offer from ${fromPeerId.substring(0, 8)}... because they should be the initiator (their ID: ${fromPeerId.substring(0, 8)}... > our ID: ${this.mesh.peerId.substring(0, 8)}...)`);
      if (this.connectionManager.hasPeer(fromPeerId)) {
        const existingPeer = this.connectionManager.getPeer(fromPeerId);
        const existingStatus = existingPeer.getStatus();
        if (existingStatus === "connected") {
          this.debug.log(`Connection already established with ${fromPeerId}, ignoring duplicate offer`);
          return;
        }
        const weShouldInitiate = this.mesh.peerId > fromPeerId;
        this.debug.log(`\u{1F504} INITIATOR LOGIC: ${this.mesh.peerId.substring(0, 8)}... vs ${fromPeerId.substring(0, 8)}... \u2192 weShouldInitiate: ${weShouldInitiate}`);
        this.debug.log(`\u{1F504} EXISTING PEER: isInitiator: ${existingPeer.isInitiator}, status: ${existingStatus}`);
        if (weShouldInitiate && existingPeer.isInitiator) {
          this.debug.log(`\u{1F504} RACE CONDITION: Both peers trying to initiate! We should initiate (${this.mesh.peerId.substring(0, 8)}... > ${fromPeerId.substring(0, 8)}...) but received offer.`);
          const ourSignalingState = existingPeer.connection?.signalingState;
          if (ourSignalingState === "have-local-offer") {
            this.debug.log(`\u2705 RACE RESOLUTION: Our connection stuck in "${ourSignalingState}" - accepting their offer to complete handshake`);
            this.connectionManager.cleanupRaceCondition(fromPeerId);
          } else {
            this.debug.log(`\u274C RACE RESOLUTION: Our connection in "${ourSignalingState}" - ignoring their offer as we should initiate`);
            return;
          }
        } else if (!weShouldInitiate && existingPeer.isInitiator) {
          this.debug.log(`\u{1F504} INITIATOR CORRECTION: We incorrectly initiated to ${fromPeerId.substring(0, 8)}..., backing down for their offer`);
          if (existingStatus === "connecting" || existingStatus === "new") {
            this.debug.log(`\u2705 Cleaning up our incorrect connection attempt to ${fromPeerId.substring(0, 8)}...`);
            this.connectionManager.cleanupRaceCondition(fromPeerId);
          } else {
            this.debug.log("Connection in progress, not cleaning up");
            return;
          }
        } else {
          this.debug.log("Already processing incoming connection from", fromPeerId);
          return;
        }
      }
      if (this.connectionManager.connectionAttempts.has(fromPeerId)) {
        this.debug.log("Already attempting connection to", fromPeerId, "accepting incoming offer");
        this.connectionManager.connectionAttempts.delete(fromPeerId);
      }
      const currentCount = this.connectionManager.getConnectedPeerCount();
      const totalPeerCount = this.connectionManager.peers.size;
      let canAccept = this.mesh.canAcceptMorePeers();
      let evictPeerId = null;
      this.debug.log(`Capacity check for ${fromPeerId.substring(0, 8)}...: ${currentCount}/${this.mesh.maxPeers} connected, ${totalPeerCount} total peers, canAccept: ${canAccept}`);
      if (!canAccept) {
        this.debug.log(`At capacity (${currentCount}/${this.mesh.maxPeers} connected, ${totalPeerCount} total) - checking eviction and cleanup options`);
        if (this.mesh.evictionStrategy) {
          evictPeerId = this.mesh.evictionManager.shouldEvictForPeer(fromPeerId);
          if (evictPeerId) {
            this.debug.log(`Will evict ${evictPeerId.substring(0, 8)}... for incoming connection from ${fromPeerId.substring(0, 8)}...`);
            canAccept = true;
          } else {
            this.debug.log(`No suitable peer found for eviction for ${fromPeerId.substring(0, 8)}...`);
          }
        } else {
          this.debug.log(`Eviction strategy disabled - cannot evict for ${fromPeerId.substring(0, 8)}...`);
        }
        if (!canAccept) {
          const stalePeerCount = this.connectionManager.getStalePeerCount();
          if (stalePeerCount > 0) {
            this.debug.log(`No eviction candidate, attempting to clean up ${stalePeerCount} stale peer(s) to make room for ${fromPeerId.substring(0, 8)}...`);
            this.connectionManager.cleanupStalePeers();
            canAccept = this.mesh.canAcceptMorePeers();
            if (canAccept) {
              this.debug.log(`Successfully made room after cleanup for ${fromPeerId.substring(0, 8)}...`);
            }
          }
        }
        if (!canAccept) {
          const reason = `max_peers_reached (${currentCount}/${this.mesh.maxPeers} connected, ${this.connectionManager.peers.size} total, no eviction candidate)`;
          this.debug.log(`Rejecting offer from ${fromPeerId.substring(0, 8)}...: ${reason}`);
          try {
            await this.mesh.sendSignalingMessage({
              type: "connection-rejected",
              data: {
                reason: "max_peers_reached",
                details: reason,
                currentCount,
                maxPeers: this.mesh.maxPeers
              }
            }, fromPeerId);
            this.debug.log(`Sent connection rejection to ${fromPeerId.substring(0, 8)}...`);
          } catch (error) {
            this.debug.error("Failed to send connection rejection:", error);
          }
          return;
        }
      }
      if (evictPeerId) {
        this.mesh.emit("statusChanged", { type: "info", message: `Evicting ${evictPeerId.substring(0, 8)}... for incoming connection from ${fromPeerId.substring(0, 8)}...` });
        await this.mesh.evictionManager.evictPeer(evictPeerId, "incoming closer peer");
      }
      try {
        const options = {
          localStream: null,
          // Always null - media must be manually added later
          // ALWAYS enable both audio and video transceivers for maximum compatibility
          // This allows peers to receive media even if they don't have media when connecting
          enableAudio: true,
          enableVideo: true
          // allowRemoteStreams defaults to false - streams only invoked when user explicitly enables them
        };
        this.debug.log(`Creating answer connection for ${fromPeerId.substring(0, 8)}... (no automatic media sharing)`);
        this.debug.log(`\u{1F504} RECEIVER SETUP: Creating PeerConnection(${fromPeerId.substring(0, 8)}..., isInitiator=false)`);
        const peerConnection = new PeerConnection(fromPeerId, false, options);
        this.connectionManager.setupPeerConnectionHandlers(peerConnection);
        this.connectionManager.peers.set(fromPeerId, peerConnection);
        await peerConnection.createConnection();
        this.debug.log(`Processing offer from ${fromPeerId.substring(0, 8)}...`, {
          type: offer.type,
          sdpLength: offer.sdp?.length || 0,
          hasAudio: offer.sdp?.includes("m=audio") || false,
          hasVideo: offer.sdp?.includes("m=video") || false
        });
        const answer = await peerConnection.handleOffer(offer);
        this.debug.log(`Answer created for ${fromPeerId.substring(0, 8)}...`, {
          type: answer.type,
          sdpLength: answer.sdp?.length || 0,
          hasAudio: answer.sdp?.includes("m=audio") || false,
          hasVideo: answer.sdp?.includes("m=video") || false
        });
        this.debug.log("Sending answer to", fromPeerId);
        this.debug.log(`\u{1F504} ANSWER SEND DEBUG: Sending answer to ${fromPeerId.substring(0, 8)}...`);
        this.debug.log(`\u{1F504} ANSWER SEND DEBUG: Answer type: ${answer.type}`);
        this.debug.log(`\u{1F504} ANSWER SEND DEBUG: Answer SDP length: ${answer.sdp?.length}`);
        this.debug.log(`\u{1F504} ANSWER SEND DEBUG: Target peer ID: ${fromPeerId}`);
        await this.mesh.sendSignalingMessage({
          type: "answer",
          data: answer,
          timestamp: Date.now()
        }, fromPeerId);
        this.debug.log(`\u2705 ANSWER SEND DEBUG: Answer successfully sent to ${fromPeerId.substring(0, 8)}...`);
        this.mesh.emit("statusChanged", { type: "info", message: `Answer sent to ${fromPeerId.substring(0, 8)}...` });
        this.mesh.peerDiscovery.clearConnectionAttempt(fromPeerId);
      } catch (error) {
        this.debug.error("Failed to handle offer from", fromPeerId, ":", error);
        if (error.message.includes("state:")) {
          this.debug.log(`Offer ignored for ${fromPeerId.substring(0, 8)}... - connection state issue (likely race condition)`);
          this.mesh.emit("statusChanged", { type: "info", message: `Offer skipped for ${fromPeerId.substring(0, 8)}... (connection state conflict)` });
        } else {
          this.mesh.emit("statusChanged", { type: "error", message: `Failed to handle offer from ${fromPeerId.substring(0, 8)}...: ${error.message}` });
          this.connectionManager.cleanupFailedConnection(fromPeerId);
        }
      }
    }
    async handleRenegotiationOffer(offer, fromPeerId) {
      this.debug.log(`\u{1F504} Handling renegotiation offer via signaling from ${fromPeerId.substring(0, 8)}...`);
      const peerConnection = this.connectionManager.getPeer(fromPeerId);
      if (!peerConnection) {
        this.debug.error(`No peer connection found for renegotiation from ${fromPeerId.substring(0, 8)}...`);
        return;
      }
      try {
        const answer = await peerConnection.handleOffer(offer);
        await this.mesh.sendSignalingMessage({
          type: "renegotiation-answer",
          data: answer,
          timestamp: Date.now()
        }, fromPeerId);
        this.debug.log(`\u2705 Renegotiation completed via signaling with ${fromPeerId.substring(0, 8)}...`);
      } catch (error) {
        this.debug.error(`\u274C Failed to handle renegotiation offer via signaling from ${fromPeerId.substring(0, 8)}...`, error);
      }
    }
    async handleAnswer(answer, fromPeerId) {
      this.debug.log("\u{1F504} ANSWER RECEIVE DEBUG: Handling answer from", fromPeerId);
      this.debug.log(`\u{1F504} ANSWER RECEIVE DEBUG: Our peer ID: ${this.mesh.peerId.substring(0, 8)}...`);
      this.debug.log(`\u{1F504} ANSWER RECEIVE DEBUG: Answer from: ${fromPeerId.substring(0, 8)}...`);
      if (!answer || typeof answer !== "object") {
        this.debug.error(`Invalid answer data from ${fromPeerId}:`, answer);
        this.mesh.emit("statusChanged", { type: "error", message: `Invalid answer data from ${fromPeerId.substring(0, 8)}...` });
        return;
      }
      if (!answer.type || answer.type !== "answer") {
        this.debug.error(`Invalid answer type from ${fromPeerId}:`, answer.type);
        this.mesh.emit("statusChanged", { type: "error", message: `Invalid answer type from ${fromPeerId.substring(0, 8)}...` });
        return;
      }
      if (!answer.sdp || typeof answer.sdp !== "string" || answer.sdp.length < 10) {
        this.debug.error(`Invalid answer SDP from ${fromPeerId}:`, answer.sdp?.substring(0, 100) || "undefined");
        this.mesh.emit("statusChanged", { type: "error", message: `Invalid answer SDP from ${fromPeerId.substring(0, 8)}...` });
        return;
      }
      const peerConnection = this.connectionManager.getPeer(fromPeerId);
      this.debug.log(`\u{1F504} ANSWER RECEIVE DEBUG: Found peer connection: ${!!peerConnection}`);
      const allPeerIds = Array.from(this.connectionManager.peers.keys());
      this.debug.log(`\u{1F504} ANSWER RECEIVE DEBUG: All current peers (${allPeerIds.length}): ${allPeerIds.map((p) => p.substring(0, 8)).join(", ")}`);
      if (peerConnection) {
        this.debug.log(`\u{1F504} ANSWER RECEIVE DEBUG: Peer connection state: ${peerConnection.connection?.signalingState}`);
        this.debug.log(`\u{1F504} ANSWER RECEIVE DEBUG: Is initiator: ${peerConnection.isInitiator}`);
        this.debug.log(`\u{1F504} ANSWER RECEIVE DEBUG: Connection age: ${Date.now() - peerConnection.connectionStartTime}ms`);
      } else {
        this.debug.error(`\u{1F504} ANSWER RECEIVE DEBUG: NO PEER CONNECTION - Was expecting peer ${fromPeerId.substring(0, 8)}...`);
        this.debug.error(`\u{1F504} ANSWER RECEIVE DEBUG: This means the peer connection was cleaned up or never created!`);
      }
      if (peerConnection) {
        try {
          this.debug.log(`\u{1F504} ANSWER RECEIVE DEBUG: Processing answer from ${fromPeerId.substring(0, 8)}...`);
          await peerConnection.handleAnswer(answer);
          this.debug.log(`\u2705 ANSWER RECEIVE DEBUG: Answer processed successfully from ${fromPeerId.substring(0, 8)}...`);
          this.mesh.emit("statusChanged", { type: "info", message: `Answer processed from ${fromPeerId.substring(0, 8)}...` });
        } catch (error) {
          this.debug.error("\u274C ANSWER RECEIVE DEBUG: Failed to handle answer:", error);
          if (error.message.includes("state:") || error.message.includes("stable")) {
            this.debug.log(`Answer ignored for ${fromPeerId.substring(0, 8)}... - connection state issue (likely race condition resolved)`);
            this.mesh.emit("statusChanged", { type: "info", message: `Answer skipped for ${fromPeerId.substring(0, 8)}... (connection already stable)` });
          } else {
            this.mesh.emit("statusChanged", { type: "error", message: `Failed to handle answer from ${fromPeerId.substring(0, 8)}...: ${error.message}` });
          }
        }
      } else {
        this.debug.error("\u274C ANSWER RECEIVE DEBUG: No peer connection found for answer from", fromPeerId);
        this.debug.error("\u274C ANSWER RECEIVE DEBUG: Peer connection may have been cleaned up prematurely - check connection timeout/cleanup logic");
      }
    }
    handleConnectionRejected(data, fromPeerId) {
      this.debug.log(`Connection rejected by ${fromPeerId.substring(0, 8)}...: ${data.reason} (${data.details})`);
      this.mesh.emit("statusChanged", {
        type: "info",
        message: `Connection rejected by ${fromPeerId.substring(0, 8)}... (${data.reason})`
      });
      this.connectionManager.connectionAttempts.delete(fromPeerId);
      const peerConnection = this.connectionManager.getPeer(fromPeerId);
      if (peerConnection && peerConnection.getStatus() !== "connected") {
        peerConnection.close();
        this.connectionManager.peers.delete(fromPeerId);
      }
      this.mesh.peerDiscovery.clearConnectionAttempt(fromPeerId);
      const connectedCount = this.connectionManager.getConnectedPeerCount();
      if (connectedCount === 0) {
        this.debug.log(`Connection rejected and peer is isolated (${connectedCount} connections) - trying alternative peers immediately`);
        const discoveredPeers = Array.from(this.mesh.peerDiscovery.getDiscoveredPeers());
        const availablePeers = discoveredPeers.filter(
          (peer) => peer.peerId !== fromPeerId && !this.connectionManager.hasPeer(peer.peerId) && !this.mesh.peerDiscovery.isAttemptingConnection(peer.peerId)
        );
        if (availablePeers.length > 0) {
          const sortedByDistance = availablePeers.sort((a, b) => {
            const distA = this.mesh.peerDiscovery.calculateXorDistance(this.mesh.peerId, a.peerId);
            const distB = this.mesh.peerDiscovery.calculateXorDistance(this.mesh.peerId, b.peerId);
            return distA < distB ? -1 : 1;
          });
          const nextPeer = sortedByDistance[0];
          this.debug.log(`Immediately attempting connection to next closest peer: ${nextPeer.peerId.substring(0, 8)}...`);
          this.connectionManager.connectToPeer(nextPeer.peerId);
        }
      } else {
        setTimeout(() => {
          this.mesh.peerDiscovery.optimizeMeshConnections(this.connectionManager.peers);
        }, 1e3);
      }
    }
    async handleRenegotiationAnswer(answer, fromPeerId) {
      console.log("\u{1F6A8} RENEGOTIATION ANSWER: IMMEDIATE PROCESSING from", fromPeerId?.substring(0, 8), "to", this.mesh.peerId?.substring(0, 8));
      this.debug.log(`\u{1F504} CRITICAL FIX: IMMEDIATE renegotiation answer from ${fromPeerId.substring(0, 8)}...`);
      const peerConnection = this.connectionManager.getPeer(fromPeerId);
      if (!peerConnection) {
        this.debug.error(`\u274C CRITICAL: No peer connection for answer from ${fromPeerId.substring(0, 8)}... - CONNECTION LOST`);
        return;
      }
      const currentState = peerConnection.connection?.signalingState;
      this.debug.log(`\uFFFD CRITICAL: Connection state before answer: ${currentState}`);
      if (currentState !== "have-local-offer") {
        this.debug.log(`\u26A0\uFE0F CRITICAL: Connection not waiting for answer (state: ${currentState}) - applying anyway`);
      }
      try {
        this.debug.log(`\u{1F504} CRITICAL: APPLYING ANSWER IMMEDIATELY from ${fromPeerId.substring(0, 8)}...`);
        await peerConnection.handleAnswer(answer);
        const newState = peerConnection.connection?.signalingState;
        this.debug.log(`\u2705 CRITICAL: Answer applied - state changed from ${currentState} to ${newState}`);
        if (newState === "stable") {
          this.debug.log(`\u{1F389} SUCCESS: Connection with ${fromPeerId.substring(0, 8)}... is now STABLE - media should work!`);
        } else {
          this.debug.error(`\u274C CRITICAL: Connection still not stable after answer - state: ${newState}`);
        }
      } catch (error) {
        this.debug.error(`\u274C CRITICAL: FAILED to apply renegotiation answer from ${fromPeerId.substring(0, 8)}...`, error);
        this.debug.log(`\u{1F198} EMERGENCY: Forcing connection recovery for ${fromPeerId.substring(0, 8)}...`);
        try {
          await this.connectionManager.connectToPeer(fromPeerId, false, { emergency: true });
        } catch (recoveryError) {
          this.debug.error(`\u274C EMERGENCY: Recovery failed for ${fromPeerId.substring(0, 8)}...`, recoveryError);
        }
      }
    }
    /**
     * Handle ICE restart offers sent over the mesh
     */
    async handleIceRestartOffer(data, fromPeerId) {
      this.debug.log(`Received ICE restart offer from ${fromPeerId.substring(0, 8)}...`);
      const peerConnection = this.connectionManager.peers.get(fromPeerId);
      if (!peerConnection) {
        this.debug.warn(`No peer connection found for ICE restart offer from ${fromPeerId.substring(0, 8)}...`);
        return;
      }
      try {
        await peerConnection.handleIceRestartOffer(data.offer);
        this.debug.log(`Successfully handled ICE restart offer from ${fromPeerId.substring(0, 8)}...`);
      } catch (error) {
        this.debug.error(`Failed to handle ICE restart offer from ${fromPeerId.substring(0, 8)}...:`, error);
      }
    }
    /**
     * Handle ICE restart answers sent over the mesh
     */
    async handleIceRestartAnswer(data, fromPeerId) {
      this.debug.log(`Received ICE restart answer from ${fromPeerId.substring(0, 8)}...`);
      const peerConnection = this.connectionManager.peers.get(fromPeerId);
      if (!peerConnection) {
        this.debug.warn(`No peer connection found for ICE restart answer from ${fromPeerId.substring(0, 8)}...`);
        return;
      }
      try {
        await peerConnection.handleIceRestartAnswer(data.answer);
        this.debug.log(`Successfully handled ICE restart answer from ${fromPeerId.substring(0, 8)}...`);
      } catch (error) {
        this.debug.error(`Failed to handle ICE restart answer from ${fromPeerId.substring(0, 8)}...:`, error);
      }
    }
  };

  // src/EvictionManager.js
  var EvictionManager = class extends EventEmitter {
    constructor(mesh, connectionManager) {
      super();
      this.debug = DebugLogger_default.create("EvictionManager");
      this.mesh = mesh;
      this.connectionManager = connectionManager;
    }
    shouldEvictForPeer(newPeerId) {
      if (!this.mesh.evictionStrategy) {
        this.debug.log("Eviction disabled - evictionStrategy is false");
        return null;
      }
      const totalPeerCount = this.connectionManager.peers.size;
      const connectedCount = this.connectionManager.getConnectedPeerCount();
      if (totalPeerCount < this.mesh.maxPeers) {
        this.debug.log(`No eviction needed: ${totalPeerCount}/${this.mesh.maxPeers} peers (connected: ${connectedCount})`);
        return null;
      }
      if (connectedCount === 0) {
        this.debug.log(`Isolated peer scenario: finding any peer to evict for ${newPeerId.substring(0, 8)}...`);
        const anyPeerId = Array.from(this.connectionManager.peers.keys())[0];
        if (anyPeerId) {
          this.debug.log(`Will evict any peer ${anyPeerId.substring(0, 8)}... to escape isolation for ${newPeerId.substring(0, 8)}...`);
          return anyPeerId;
        }
      }
      if (this.mesh.xorRouting) {
        const newPeerDistance = this.calculateXorDistance(this.mesh.peerId, newPeerId);
        const farthestPeerId = this.findFarthestPeer();
        if (!farthestPeerId) {
          this.debug.log("No eviction candidate found (no peers to evict)");
          return null;
        }
        const farthestDistance = this.calculateXorDistance(this.mesh.peerId, farthestPeerId);
        this.debug.log(`Eviction check: new peer ${newPeerId.substring(0, 8)}... (distance: ${newPeerDistance.toString(16).substring(0, 8)}) vs farthest ${farthestPeerId.substring(0, 8)}... (distance: ${farthestDistance.toString(16).substring(0, 8)})`);
        const shouldEvict = newPeerDistance < farthestDistance;
        if (shouldEvict) {
          this.debug.log(`Will evict ${farthestPeerId.substring(0, 8)}... for closer peer ${newPeerId.substring(0, 8)}... (${totalPeerCount}/${this.mesh.maxPeers} peers)`);
          return farthestPeerId;
        }
        this.debug.log(`Not evicting for ${newPeerId.substring(0, 8)}... - new peer is not closer (${totalPeerCount}/${this.mesh.maxPeers} peers)`);
        return null;
      } else {
        this.debug.log(`XOR routing disabled - using FIFO eviction for ${newPeerId.substring(0, 8)}...`);
        const oldestPeerId = this.findOldestPeer();
        if (oldestPeerId) {
          this.debug.log(`Will evict oldest peer ${oldestPeerId.substring(0, 8)}... for new peer ${newPeerId.substring(0, 8)}... (${totalPeerCount}/${this.mesh.maxPeers} peers)`);
          return oldestPeerId;
        }
        this.debug.log("No eviction candidate found (no peers to evict)");
        return null;
      }
    }
    async evictPeer(peerId, reason = "topology optimization") {
      const peerConnection = this.connectionManager.getPeer(peerId);
      if (!peerConnection) {
        this.debug.log(`Cannot evict ${peerId.substring(0, 8)}... - peer not found`);
        return;
      }
      this.debug.log(`Evicting ${peerId.substring(0, 8)}... (${reason})`);
      try {
        peerConnection.sendMessage({
          type: "eviction",
          reason,
          from: this.mesh.peerId
        });
      } catch (error) {
        this.debug.log("Failed to send eviction notice:", error.message);
      }
      peerConnection.close();
      this.connectionManager.peers.delete(peerId);
      this.mesh.peerDiscovery.clearConnectionAttempt(peerId);
      this.mesh.emit("peerDisconnected", { peerId, reason: `evicted: ${reason}` });
      this.connectionManager.emit("peersUpdated");
    }
    handleEvictionNotice(message, fromPeerId) {
      this.debug.log(`Evicted by ${fromPeerId.substring(0, 8)}... (${message.reason})`);
      this.connectionManager.peers.delete(fromPeerId);
      this.mesh.peerDiscovery.clearConnectionAttempt(fromPeerId);
      this.mesh.emit("peerEvicted", { fromPeerId, reason: message.reason });
      this.connectionManager.emit("peersUpdated");
    }
    calculateXorDistance(peerId1, peerId2) {
      let distance = 0n;
      for (let i = 0; i < Math.min(peerId1.length, peerId2.length); i += 2) {
        const byte1 = parseInt(peerId1.substr(i, 2), 16);
        const byte2 = parseInt(peerId2.substr(i, 2), 16);
        const xor = byte1 ^ byte2;
        distance = distance << 8n | BigInt(xor);
      }
      return distance;
    }
    findFarthestPeer() {
      if (this.connectionManager.peers.size === 0) {
        this.debug.log("No peers available for eviction");
        return null;
      }
      let farthestPeer = null;
      let maxDistance = 0n;
      let candidateCount = 0;
      this.connectionManager.peers.forEach((peerConnection, peerId) => {
        const status = peerConnection.getStatus();
        if (status === "connected" || status === "channel-connecting" || status === "connecting" || status === "channel-open") {
          candidateCount++;
          const distance = this.calculateXorDistance(this.mesh.peerId, peerId);
          if (distance > maxDistance) {
            maxDistance = distance;
            farthestPeer = peerId;
          }
        }
      });
      if (!farthestPeer && this.connectionManager.peers.size > 0) {
        this.debug.log("No eviction candidates with connected/connecting status, trying any peer...");
        this.connectionManager.peers.forEach((peerConnection, peerId) => {
          candidateCount++;
          const distance = this.calculateXorDistance(this.mesh.peerId, peerId);
          if (distance > maxDistance) {
            maxDistance = distance;
            farthestPeer = peerId;
          }
        });
      }
      if (farthestPeer) {
        this.debug.log(`Found farthest peer for eviction: ${farthestPeer.substring(0, 8)}... (distance: ${maxDistance.toString(16).substring(0, 8)}, ${candidateCount} candidate peers)`);
      } else {
        this.debug.log(`No peers available for eviction (${candidateCount} candidate peers)`);
      }
      return farthestPeer;
    }
    findOldestPeer() {
      if (this.connectionManager.peers.size === 0) {
        this.debug.log("No peers available for FIFO eviction");
        return null;
      }
      let oldestPeer = null;
      let oldestTime = Date.now();
      let candidateCount = 0;
      this.connectionManager.peers.forEach((peerConnection, peerId) => {
        const status = peerConnection.getStatus();
        if (status === "connected" || status === "channel-connecting" || status === "connecting" || status === "channel-open") {
          candidateCount++;
          const connectionTime = peerConnection.connectionStartTime || Date.now();
          if (connectionTime < oldestTime) {
            oldestTime = connectionTime;
            oldestPeer = peerId;
          }
        }
      });
      if (!oldestPeer && this.connectionManager.peers.size > 0) {
        this.debug.log("No FIFO eviction candidates with connected/connecting status, trying any peer...");
        this.connectionManager.peers.forEach((peerConnection, peerId) => {
          candidateCount++;
          const connectionTime = peerConnection.connectionStartTime || Date.now();
          if (connectionTime < oldestTime) {
            oldestTime = connectionTime;
            oldestPeer = peerId;
          }
        });
      }
      if (oldestPeer) {
        this.debug.log(`Found oldest peer for eviction: ${oldestPeer.substring(0, 8)}... (connected at: ${new Date(oldestTime).toLocaleTimeString()}, ${candidateCount} candidates)`);
      } else {
        this.debug.log(`No peers available for FIFO eviction (${candidateCount} candidates)`);
      }
      return oldestPeer;
    }
    disconnectExcessPeers() {
      if (this.connectionManager.peers.size <= this.mesh.maxPeers) return;
      const peerEntries = Array.from(this.connectionManager.peers.entries()).filter(([_, peerConnection]) => peerConnection.connectionStartTime).sort((a, b) => a[1].connectionStartTime - b[1].connectionStartTime);
      const toDisconnect = peerEntries.slice(0, this.connectionManager.peers.size - this.mesh.maxPeers);
      toDisconnect.forEach(([peerId, peerConnection]) => {
        this.debug.log(`Disconnecting ${peerId.substring(0, 8)}... (over max peers limit)`);
        peerConnection.close();
        this.connectionManager.peers.delete(peerId);
        this.mesh.peerDiscovery.clearConnectionAttempt(peerId);
        this.mesh.emit("peerDisconnected", { peerId, reason: "over max peers limit" });
      });
      this.connectionManager.emit("peersUpdated");
    }
    clearEvictionTracking(_peerId) {
    }
    cleanup() {
    }
  };

  // src/MeshOptimizer.js
  var MeshOptimizer = class extends EventEmitter {
    constructor(mesh, connectionManager, evictionManager) {
      super();
      this.debug = DebugLogger_default.create("MeshOptimizer");
      this.mesh = mesh;
      this.connectionManager = connectionManager;
      this.evictionManager = evictionManager;
    }
    handleOptimizeConnections(unconnectedPeers) {
      if (!this.mesh.autoDiscovery) return;
      const currentConnected = this.connectionManager.getConnectedPeerCount();
      const hasNoConnections = currentConnected === 0;
      const belowMinimum = currentConnected < this.mesh.minPeers;
      if (hasNoConnections && unconnectedPeers.length > 0) {
        this.debug.log(`Peer has no connections but found ${unconnectedPeers.length} unconnected peers - forcing connection attempt`);
        const sortedByDistance = unconnectedPeers.sort((a, b) => {
          const distA = this.calculateXorDistance(this.mesh.peerId, a);
          const distB = this.calculateXorDistance(this.mesh.peerId, b);
          return distA < distB ? -1 : 1;
        });
        const targetPeer = sortedByDistance[0];
        this.debug.log(`Forcing connection to closest peer: ${targetPeer.substring(0, 8)}... (no connections, mesh connectivity required)`);
        this.connectionManager.connectToPeer(targetPeer);
        return;
      }
      if (belowMinimum && unconnectedPeers.length > 0) {
        this.debug.log(`Below minimum peers (${currentConnected}/${this.mesh.minPeers}) - connecting to additional peers`);
        const needed = Math.min(this.mesh.minPeers - currentConnected, unconnectedPeers.length);
        const sortedByDistance = unconnectedPeers.sort((a, b) => {
          const distA = this.calculateXorDistance(this.mesh.peerId, a);
          const distB = this.calculateXorDistance(this.mesh.peerId, b);
          return distA < distB ? -1 : 1;
        });
        for (let i = 0; i < needed; i++) {
          const targetPeer = sortedByDistance[i];
          this.debug.log(`Connecting to reach minimum: ${targetPeer.substring(0, 8)}...`);
          this.connectionManager.connectToPeer(targetPeer);
        }
        return;
      }
      if (this.mesh.maxPeers <= 3) {
        if (currentConnected >= this.mesh.maxPeers) {
          this.debug.log(`Skipping optimization - already at max capacity ${currentConnected}/${this.mesh.maxPeers}`);
          return;
        }
      } else {
        const targetThreshold = Math.floor(this.mesh.maxPeers * 0.7);
        if (currentConnected >= targetThreshold) {
          this.debug.log(`Skipping optimization - ${currentConnected}/${this.mesh.maxPeers} peers connected (threshold: ${targetThreshold})`);
          return;
        }
      }
      const availableSlots = this.mesh.maxPeers - currentConnected;
      const peersToConnect = unconnectedPeers.slice(0, Math.min(availableSlots, 1));
      this.debug.log(`Optimizing connections carefully: ${availableSlots} slots available, connecting to ${peersToConnect.length} peer(s)`);
      peersToConnect.forEach((peerId, _index) => {
        if (this.mesh.peerDiscovery.shouldInitiateConnection(peerId)) {
          this.debug.log(`Initiating immediate connection to ${peerId.substring(0, 8)}... in optimization`);
          if (this.connectionManager.canAcceptMorePeers() && !this.connectionManager.hasPeer(peerId) && !this.mesh.peerDiscovery.isAttemptingConnection(peerId)) {
            this.connectionManager.connectToPeer(peerId);
          } else {
            this.debug.log(`Skipping connection to ${peerId.substring(0, 8)}... - conditions changed`);
          }
        } else {
          this.debug.log(`Not initiating connection to ${peerId.substring(0, 8)}... (should not initiate)`);
        }
      });
    }
    calculateXorDistance(peerId1, peerId2) {
      let distance = 0n;
      for (let i = 0; i < Math.min(peerId1.length, peerId2.length); i += 2) {
        const byte1 = parseInt(peerId1.substr(i, 2), 16);
        const byte2 = parseInt(peerId2.substr(i, 2), 16);
        const xor = byte1 ^ byte2;
        distance = distance << 8n | BigInt(xor);
      }
      return distance;
    }
    // Method to force connection attempts to all discovered peers
    forceConnectToAllPeers() {
      const discoveredPeers = this.mesh.getDiscoveredPeers();
      let connectionAttempts = 0;
      this.debug.log(`Forcing connections to ${discoveredPeers.length} discovered peers...`);
      discoveredPeers.forEach((peer) => {
        if (!this.connectionManager.hasPeer(peer.peerId) && this.connectionManager.canAcceptMorePeers()) {
          this.debug.log(`Force connecting to ${peer.peerId.substring(0, 8)}...`);
          this.connectionManager.connectToPeer(peer.peerId);
          connectionAttempts++;
        }
      });
      this.debug.log(`Initiated ${connectionAttempts} forced connection attempts`);
      return connectionAttempts;
    }
    // Debug method to help diagnose connectivity issues
    debugConnectivity() {
      const connectedPeers = this.connectionManager.getConnectedPeerCount();
      const discoveredPeers = this.mesh.getDiscoveredPeers();
      const totalPeers = this.connectionManager.peers.size;
      this.debug.log("=== CONNECTIVITY DEBUG ===");
      this.debug.log(`My Peer ID: ${this.mesh.peerId}`);
      this.debug.log(`Connected Peers: ${connectedPeers}/${this.mesh.maxPeers}`);
      this.debug.log(`Total Peer Objects: ${totalPeers}`);
      this.debug.log(`Discovered Peers: ${discoveredPeers.length}`);
      this.debug.log("\nPeer Details:");
      this.connectionManager.peers.forEach((peerConnection, peerId) => {
        const status = peerConnection.getStatus();
        const dataChannelReady = peerConnection.dataChannelReady;
        const connectionState = peerConnection.connection?.connectionState || "unknown";
        const dataChannelState = peerConnection.dataChannel?.readyState || "unknown";
        this.debug.log(`  ${peerId.substring(0, 8)}... - Status: ${status}, WebRTC: ${connectionState}, DataChannel: ${dataChannelState}, Ready: ${dataChannelReady}`);
      });
      this.debug.log("\nDiscovered Peers:");
      discoveredPeers.forEach((peer) => {
        const shouldInitiate = this.mesh.peerId < peer.peerId;
        const isConnected = this.connectionManager.hasPeer(peer.peerId);
        this.debug.log(`  ${peer.peerId.substring(0, 8)}... - ShouldInitiate: ${shouldInitiate}, IsConnected: ${isConnected}`);
      });
      this.debug.log("\nConnection Attempts:");
      this.connectionManager.connectionAttempts.forEach((attempts, peerId) => {
        this.debug.log(`  ${peerId.substring(0, 8)}... - Attempts: ${attempts}/${this.connectionManager.maxConnectionAttempts}`);
      });
      this.debug.log("\nEviction Status:");
      this.debug.log(`  Eviction Strategy: ${this.mesh.evictionStrategy ? "enabled" : "disabled"}`);
      this.debug.log(`  XOR Routing: ${this.mesh.xorRouting ? "enabled" : "disabled"}`);
      this.debug.log("=== END DEBUG ===\n");
      return {
        connectedPeers,
        totalPeers,
        discoveredPeers: discoveredPeers.length,
        evictionEnabled: this.mesh.evictionStrategy,
        xorRouting: this.mesh.xorRouting,
        peerStatuses: Array.from(this.connectionManager.peers.entries()).map(([peerId, conn]) => ({
          peerId: peerId.substring(0, 8),
          status: conn.getStatus(),
          dataChannelReady: conn.dataChannelReady
        }))
      };
    }
  };

  // src/CleanupManager.js
  var CleanupManager = class extends EventEmitter {
    constructor(mesh) {
      super();
      this.debug = DebugLogger_default.create("CleanupManager");
      this.mesh = mesh;
      this.cleanupInProgress = /* @__PURE__ */ new Set();
    }
    async cleanupSignalingData(peerId) {
      if (this.cleanupInProgress.has(peerId)) {
        this.debug.log("Cleanup already in progress for", peerId);
        return;
      }
      this.cleanupInProgress.add(peerId);
      try {
        this.debug.log("Cleaning up signaling data for", peerId);
        const response = await this.mesh.signalingClient.sendSignalingMessage({
          type: "cleanup",
          data: {
            peerId: this.mesh.peerId,
            targetPeerId: peerId,
            timestamp: Date.now(),
            reason: "connection_established"
          },
          targetPeerId: peerId
        });
        if (response.cleaned && (response.cleaned.signaling > 0 || response.cleaned.discovery > 0)) {
          this.debug.log("Signaling cleanup completed for", peerId, response);
          this.mesh.emit("statusChanged", { type: "info", message: `Cleaned up signaling data with ${peerId.substring(0, 8)}...` });
        }
      } catch (error) {
        this.debug.error("Failed to cleanup signaling data for", peerId, error);
      } finally {
        this.cleanupInProgress.delete(peerId);
      }
    }
    async cleanupAllSignalingData() {
      if (this.mesh.signalingClient && this.mesh.peerId) {
        try {
          const response = await this.mesh.signalingClient.sendSignalingMessage({
            type: "cleanup-all",
            data: {
              peerId: this.mesh.peerId,
              timestamp: Date.now(),
              reason: "peer_cleanup"
            }
          });
          if (response.cleaned > 0) {
            this.debug.log(`Cleaned up ${response.cleaned} stale signaling items`);
          }
        } catch (error) {
          this.debug.log("Failed to cleanup all signaling data:", error.message);
        }
      }
    }
    cleanupAllSignalingDataSync() {
      if (this.mesh.signalingClient && this.mesh.connected && this.mesh.peerId) {
        try {
          this.mesh.signalingClient.sendSignalingMessage({
            type: "cleanup-all",
            data: {
              peerId: this.mesh.peerId,
              timestamp: Date.now(),
              reason: "browser_unload"
            }
          }).catch((error) => {
            this.debug.log("Failed to send cleanup-all message:", error.message);
          });
          this.debug.log("Cleanup-all message sent via WebSocket");
        } catch (error) {
          this.debug.log("Failed to send cleanup-all message:", error.message);
        }
      }
    }
    // Manual cleanup method for already-connected mesh networks
    async cleanupStaleSignalingData() {
      if (!this.mesh.signalingClient || !this.mesh.connected) {
        this.debug.log("Cannot cleanup - not connected to signaling server");
        return;
      }
      this.debug.log("Manually cleaning up stale signaling data for all connected peers...");
      try {
        const cleanupPromises = [];
        if (this.mesh.connectionManager && this.mesh.connectionManager.peers) {
          const peerEntries = Array.from(this.mesh.connectionManager.peers.entries());
          for (const [peerId, peerConnection] of peerEntries) {
            try {
              if (peerConnection && peerConnection.getStatus && peerConnection.getStatus() === "connected") {
                cleanupPromises.push(this.cleanupSignalingData(peerId));
              }
            } catch (error) {
              this.debug.log(`Error checking peer ${peerId} status during cleanup:`, error.message);
            }
          }
        }
        if (cleanupPromises.length > 0) {
          await Promise.allSettled(cleanupPromises);
        }
        await this.cleanupAllSignalingData();
        this.debug.log("Manual cleanup completed for all connected peers");
        this.mesh.emit("statusChanged", { type: "info", message: "Cleaned up stale signaling data" });
      } catch (error) {
        this.debug.error("Manual cleanup failed:", error);
        this.mesh.emit("statusChanged", { type: "error", message: "Failed to cleanup signaling data" });
      }
    }
    sendGoodbyeMessageSync() {
      if (this.mesh.signalingClient && this.mesh.connected) {
        try {
          this.mesh.signalingClient.sendGoodbyeMessage();
          this.debug.log("Goodbye message sent via WebSocket");
        } catch (error) {
          this.debug.log("Failed to send goodbye message:", error.message);
        }
      }
    }
    async sendGoodbyeMessage() {
      if (this.mesh.signalingClient && this.mesh.connected) {
        try {
          await this.mesh.signalingClient.sendSignalingMessage({
            type: "goodbye",
            data: { peerId: this.mesh.peerId, timestamp: Date.now() }
          });
        } catch (error) {
          this.debug.log("Failed to send goodbye message:", error.message);
        }
      }
    }
    setupUnloadHandlers() {
      if (typeof window !== "undefined") {
        const handleUnload = () => {
          this.debug.log("Page unloading - cleaning up ALL peer data");
          this.sendGoodbyeMessageSync();
          this.cleanupAllSignalingDataSync();
        };
        window.addEventListener("beforeunload", handleUnload);
        window.addEventListener("unload", handleUnload);
        window.addEventListener("pagehide", handleUnload);
      }
    }
    cleanup() {
      this.cleanupInProgress.clear();
    }
  };

  // src/StorageManager.js
  var StorageManager = class extends EventEmitter {
    constructor(mesh) {
      super();
      this.debug = DebugLogger_default.create("StorageManager");
      this.mesh = mesh;
    }
    loadSignalingUrlFromStorage() {
      if (environmentDetector.hasLocalStorage) {
        const savedUrl = localStorage.getItem("pigon-signaling-url");
        if (savedUrl) {
          this.mesh.signalingUrl = savedUrl;
          this.mesh.emit("statusChanged", { type: "urlLoaded", signalingUrl: savedUrl });
          return savedUrl;
        }
      } else if (environmentDetector.isNodeJS) {
        this.debug.log("Local storage not available in Node.js environment");
      }
      return null;
    }
    saveSignalingUrlToStorage(url) {
      if (environmentDetector.hasLocalStorage && url) {
        localStorage.setItem("pigon-signaling-url", url);
      } else if (environmentDetector.isNodeJS) {
        this.debug.log("Storage not implemented for Node.js environment");
      }
    }
    loadSignalingUrlFromQuery() {
      if (!environmentDetector.isBrowser && !environmentDetector.isNativeScript) return this.loadSignalingUrlFromStorage();
      if (typeof URLSearchParams === "undefined" || environmentDetector.isBrowser && typeof window === "undefined" || environmentDetector.isBrowser && typeof window.location === "undefined") {
        return this.loadSignalingUrlFromStorage();
      }
      let searchParams;
      if (environmentDetector.isBrowser) {
        searchParams = new URLSearchParams(window.location.search);
      } else if (environmentDetector.isNativeScript) {
        return this.loadSignalingUrlFromStorage();
      }
      const signalingUrl = searchParams?.get("api") || searchParams?.get("url") || searchParams?.get("signaling");
      if (signalingUrl) {
        const currentUrl = this.mesh.signalingUrl;
        this.mesh.signalingUrl = signalingUrl;
        this.saveSignalingUrlToStorage(signalingUrl);
        if (currentUrl !== signalingUrl) {
          this.mesh.emit("statusChanged", { type: "urlLoaded", signalingUrl });
        }
        return signalingUrl;
      }
      return this.loadSignalingUrlFromStorage();
    }
    validatePeerId(peerId) {
      return typeof peerId === "string" && /^[a-fA-F0-9]{40}$/.test(peerId);
    }
    saveSettings(settings) {
      if (environmentDetector.hasLocalStorage) {
        localStorage.setItem("pigon-settings", JSON.stringify(settings));
      }
    }
    loadSettings() {
      if (environmentDetector.hasLocalStorage) {
        const saved = localStorage.getItem("pigon-settings");
        if (saved) {
          try {
            return JSON.parse(saved);
          } catch (error) {
            this.debug.error("Failed to parse saved settings:", error);
          }
        }
      }
      return {};
    }
    clearStorage() {
      if (typeof localStorage !== "undefined") {
        localStorage.removeItem("pigon-signaling-url");
        localStorage.removeItem("pigon-settings");
      }
    }
  };

  // src/GossipManager.js
  var GossipManager = class extends EventEmitter {
    constructor(mesh, connectionManager) {
      super();
      this.mesh = mesh;
      this.connectionManager = connectionManager;
      this.debug = DebugLogger_default.create("GossipManager");
      this.seenMessages = /* @__PURE__ */ new Map();
      this.messageHistory = /* @__PURE__ */ new Map();
      this.processedKeyExchanges = /* @__PURE__ */ new Map();
      this.maxTTL = 40;
      this.messageExpiryTime = 5 * 60 * 1e3;
      this.cleanupInterval = 60 * 1e3;
      this.cleanupTimer = null;
      this.startCleanupTimer();
    }
    /**
       * Broadcast a message to all peers in the network using gossip protocol
       */
    async broadcastMessage(content, messageType = "chat") {
      if (content === void 0 || content === null) {
        this.debug.error("Cannot broadcast message with undefined/null content");
        return null;
      }
      if (messageType === "chat" && (typeof content !== "string" || content.trim().length === 0)) {
        this.debug.error("Cannot broadcast empty chat message");
        return null;
      }
      if (messageType === "encrypted" && (typeof content !== "object" || !content.encrypted)) {
        this.debug.error("Cannot broadcast invalid encrypted message");
        return null;
      }
      const messageId = await this.generateMessageId();
      const message = {
        id: messageId,
        type: "gossip",
        subtype: messageType,
        content,
        from: this.mesh.peerId,
        networkName: this.mesh.networkName,
        // Include network namespace
        timestamp: Date.now(),
        ttl: this.maxTTL,
        path: [this.mesh.peerId]
        // Track propagation path
      };
      this.debug.log(`Broadcasting ${messageType} message: ${messageId.substring(0, 8)}... content: "${content}"`);
      this.seenMessages.set(messageId, {
        timestamp: Date.now(),
        ttl: this.maxTTL
      });
      this.messageHistory.set(messageId, message);
      this.propagateMessage(message);
      if (messageType === "chat" || messageType === "encrypted") {
        this.emit("messageReceived", {
          from: this.mesh.peerId,
          content,
          timestamp: message.timestamp,
          messageId,
          encrypted: messageType === "encrypted"
        });
      }
      return messageId;
    }
    /**
     * Attempt adaptive resend of recent broadcasts to peers that connected
     * after initial propagation window.
     */
    // (Rollback) attemptAdaptiveResends removed
    /**
       * Send a direct message to a specific peer using gossip routing (DM)
       * @param {string} targetPeerId - The destination peer's ID
       * @param {string|object} content - The message content
       * @param {string} subtype - Message subtype (default: 'dm')
       * @returns {string|null} The message ID if sent, or null on error
       */
    async sendDirectMessage(targetPeerId, content, subtype = "dm") {
      if (!targetPeerId || typeof targetPeerId !== "string") {
        this.debug.error("Invalid targetPeerId for direct message");
        return null;
      }
      if (!/^[a-fA-F0-9]{40}$/.test(targetPeerId)) {
        this.debug.error("Invalid peer ID format for direct message:", targetPeerId);
        return null;
      }
      const messageId = await this.generateMessageId();
      const message = {
        id: messageId,
        type: "gossip",
        subtype,
        content,
        from: this.mesh.peerId,
        to: targetPeerId,
        networkName: this.mesh.networkName,
        // Include network namespace
        timestamp: Date.now(),
        ttl: this.maxTTL,
        path: [this.mesh.peerId]
      };
      this.seenMessages.set(messageId, {
        timestamp: Date.now(),
        ttl: this.maxTTL
      });
      this.messageHistory.set(messageId, message);
      this.propagateMessage(message);
      return messageId;
    }
    /**
       * Handle incoming gossip message from a peer
       */
    async handleGossipMessage(message, fromPeerId) {
      this.debug.log(`\u{1F525}\u{1F525}\u{1F525} GOSSIP MESSAGE RECEIVED! From: ${fromPeerId?.substring(0, 8)}...`);
      this.debug.log("\u{1F525}\u{1F525}\u{1F525} Message:", message);
      const { id: messageId, ttl, from: originPeerId, subtype, content, timestamp, path, to, networkName } = message;
      if (!messageId || !originPeerId || !subtype || content === void 0) {
        this.debug.error("Invalid gossip message structure:", message);
        return;
      }
      const messageNetwork = networkName || "global";
      const currentNetwork = this.mesh.networkName;
      if (messageNetwork !== currentNetwork) {
        this.debug.log(`Filtering gossip message from different network: ${messageNetwork} (current: ${currentNetwork})`);
        return;
      }
      if (this.seenMessages.has(messageId)) {
        this.debug.log(`Ignoring duplicate message: ${messageId.substring(0, 8)}...`);
        return;
      }
      if (ttl <= 0) {
        this.debug.log(`Message expired: ${messageId.substring(0, 8)}...`);
        return;
      }
      if (path && path.includes(this.mesh.peerId)) {
        this.debug.log(`Preventing message loop: ${messageId.substring(0, 8)}...`);
        return;
      }
      this.debug.log(`Received gossip message: ${messageId.substring(0, 8)}... from ${fromPeerId.substring(0, 8)}... (TTL: ${ttl}, content: "${content}")`);
      this.seenMessages.set(messageId, {
        timestamp: Date.now(),
        ttl
      });
      this.messageHistory.set(messageId, message);
      if (this.mesh.enableCrypto && this.mesh.cryptoManager) {
        const handled = await this._handleCryptoMessage(message, fromPeerId, originPeerId);
        if (handled) {
          return;
        }
      }
      let processedContent = content;
      let isEncrypted = false;
      if (this.mesh.enableCrypto && this.mesh.cryptoManager && content && typeof content === "object" && content.encrypted) {
        try {
          processedContent = await this.mesh.decryptMessage(content);
          isEncrypted = true;
          this.debug.log(`\u{1F510} Decrypted message from ${originPeerId.substring(0, 8)}...`);
        } catch (error) {
          this.debug.error("Failed to decrypt message:", error);
          processedContent = content;
        }
      }
      if (subtype === "chat") {
        if (isEncrypted || typeof processedContent === "string" && processedContent.trim().length > 0) {
          this.emit("messageReceived", {
            from: originPeerId,
            content: processedContent,
            timestamp,
            messageId,
            hops: this.maxTTL - ttl,
            direct: false,
            // Flag to indicate this was a gossip message
            encrypted: isEncrypted
          });
        } else {
          this.debug.warn("Ignoring gossip chat message with invalid content:", processedContent);
          return;
        }
      } else if (subtype === "encrypted") {
        this.emit("messageReceived", {
          from: originPeerId,
          content: processedContent,
          timestamp,
          messageId,
          hops: this.maxTTL - ttl,
          direct: false,
          encrypted: true
        });
      } else if (subtype === "peer-announcement") {
        this.handlePeerAnnouncement(content, originPeerId);
      } else if (subtype === "mediaEvent") {
        this.handleMediaEvent(content, originPeerId);
      } else if (subtype === "stream-chunk") {
        this.emit("messageReceived", {
          from: originPeerId,
          message: {
            type: "stream-chunk",
            ...content
          },
          timestamp,
          messageId
        });
      } else if (subtype === "stream-control") {
        this.emit("messageReceived", {
          from: originPeerId,
          message: {
            type: "stream-control",
            ...content
          },
          timestamp,
          messageId
        });
      } else if (subtype === "dm") {
        if (typeof to === "string" && typeof this.mesh.peerId === "string" && to.trim().toLowerCase() === this.mesh.peerId.trim().toLowerCase()) {
          this.debug.log(`\u{1F50D} DM DEBUG: Received DM from ${originPeerId?.substring(0, 8)}, content type: ${typeof processedContent}`);
          if (typeof processedContent === "object" && processedContent) {
            this.debug.log(`\u{1F50D} DM DEBUG: Content object has type: ${processedContent.type}`);
          }
          const filteredMessageTypes = /* @__PURE__ */ new Set([
            "signaling-relay",
            "peer-announce-relay",
            "bootstrap-keepalive"
          ]);
          let messageType = null;
          let shouldFilter = false;
          if (typeof processedContent === "object" && processedContent && processedContent.type) {
            messageType = processedContent.type;
            shouldFilter = filteredMessageTypes.has(messageType);
          } else if (typeof processedContent === "string") {
            try {
              const parsedContent = JSON.parse(processedContent);
              messageType = parsedContent.type;
              shouldFilter = filteredMessageTypes.has(messageType);
            } catch (e) {
            }
          }
          this.debug.log(`\u{1F50D} DM DEBUG: messageType=${messageType}, shouldFilter=${shouldFilter}`);
          if (shouldFilter) {
            this.debug.log(`\u{1F507} FILTER: Processing filtered DM type '${messageType}' from ${originPeerId?.substring(0, 8)} (not emitted to UI)`);
            if (messageType === "signaling-relay" && typeof processedContent === "object") {
              if (processedContent.signalingMessage && this.mesh.signalingHandler) {
                this.mesh.signalingHandler.handleSignalingMessage({
                  type: processedContent.signalingMessage.type,
                  data: processedContent.signalingMessage.data,
                  fromPeerId: processedContent.signalingMessage.fromPeerId || originPeerId,
                  targetPeerId: processedContent.targetPeerId,
                  timestamp: processedContent.timestamp
                });
              }
            } else if (messageType === "peer-announce-relay" && typeof processedContent === "object") {
              if (processedContent.data && this.mesh.signalingHandler) {
                this.mesh.signalingHandler.handlePeerAnnouncement(processedContent.data, originPeerId);
              }
            } else if (messageType === "bootstrap-keepalive") {
              if (this.mesh.peerDiscovery) {
                const keepalivePeerId = typeof processedContent === "object" && processedContent.from ? processedContent.from : originPeerId;
                this.mesh.peerDiscovery.updateDiscoveryTimestamp(keepalivePeerId);
              }
            }
            return;
          }
          this.emit("messageReceived", {
            from: originPeerId,
            content: processedContent,
            timestamp,
            messageId,
            hops: this.maxTTL - ttl,
            direct: true,
            // Flag to indicate this was a direct message
            encrypted: isEncrypted
          });
          return;
        } else {
        }
      } else if (subtype === "dht-routing") {
        if (typeof to === "string" && typeof this.mesh.peerId === "string" && to.trim().toLowerCase() === this.mesh.peerId.trim().toLowerCase()) {
          this.debug.log(`DHT: Received routed message for us from ${originPeerId.substring(0, 8)}`);
          if (this.mesh.webDHT && content) {
            this.mesh.webDHT.handleMessage(content, originPeerId);
          }
          return;
        } else {
          this.debug.log(`DHT: Routing message for ${to?.substring(0, 8)} (not us)`);
        }
      }
      const updatedMessage = {
        ...message,
        ttl: ttl - 1,
        path: [...path || [], this.mesh.peerId]
      };
      this.propagateMessage(updatedMessage, fromPeerId);
    }
    /**
       * Handle peer announcements received via gossip
       */
    handlePeerAnnouncement(announcementData, originPeerId) {
      const { peerId: announcedPeerId } = announcementData;
      this.debug.log(`Gossip peer announcement: ${announcedPeerId.substring(0, 8)}... via ${originPeerId.substring(0, 8)}...`);
      if (!this.mesh.peerDiscovery.hasPeer(announcedPeerId) && announcedPeerId !== this.mesh.peerId) {
        this.mesh.emit("statusChanged", {
          type: "info",
          message: `Discovered peer ${announcedPeerId.substring(0, 8)}... via gossip`
        });
        this.mesh.peerDiscovery.addDiscoveredPeer(announcedPeerId);
      }
    }
    /**
     * Handle media streaming events received via gossip
     */
    handleMediaEvent(eventData, originPeerId) {
      const { event, peerId, hasVideo, hasAudio, timestamp } = eventData;
      this.debug.log(`Media event gossip: ${event} from ${peerId.substring(0, 8)}... via ${originPeerId.substring(0, 8)}...`);
      if (peerId === this.mesh.peerId) {
        return;
      }
      if (event === "streamStarted") {
        this.mesh.emit("remoteStreamAnnouncement", {
          peerId,
          hasVideo,
          hasAudio,
          timestamp,
          event: "started"
        });
      } else if (event === "streamStopped") {
        this.mesh.emit("remoteStreamAnnouncement", {
          peerId,
          timestamp,
          event: "stopped"
        });
      }
    }
    /**
       * Broadcast peer announcement via gossip when we connect
       */
    async announcePeer(peerId = this.mesh.peerId) {
      const announcementData = {
        peerId,
        timestamp: Date.now()
      };
      this.debug.log(`Gossiping peer announcement for: ${peerId.substring(0, 8)}...`);
      await this.broadcastMessage(announcementData, "peer-announcement");
    }
    /**
       * Propagate message to ALL peers that can receive messages - ignore connection states!
       */
    propagateMessage(message, excludePeerId = null) {
      if ((message.subtype === "dm" || message.subtype === "dht-routing") && message.to) {
        let xorDistance = function(a, b) {
          if (!a || !b) return Number.MAX_SAFE_INTEGER;
          let dist = 0n;
          for (let i = 0; i < 40; i += 8) {
            const aChunk = a.substring(i, i + 8);
            const bChunk = b.substring(i, i + 8);
            dist = (dist << 32n) + (BigInt("0x" + aChunk) ^ BigInt("0x" + bChunk));
          }
          return dist;
        };
        const targetId = message.to;
        const allPeers2 = Array.from(this.connectionManager.peers.values());
        const capablePeers2 = allPeers2.filter((peerConnection) => {
          return peerConnection.dataChannel && peerConnection.dataChannel.readyState === "open";
        });
        let minDist = null;
        let closestPeers = [];
        capablePeers2.forEach((peerConnection) => {
          if (peerConnection.peerId === excludePeerId || message.ttl <= 0) return;
          const dist = xorDistance(peerConnection.peerId, targetId);
          if (minDist === null || dist < minDist) {
            minDist = dist;
            closestPeers = [peerConnection];
          } else if (dist === minDist) {
            closestPeers.push(peerConnection);
          }
        });
        if (closestPeers.length > 0) {
          closestPeers.forEach((peer) => {
            try {
              peer.sendMessage(message);
              const routingType = message.subtype === "dht-routing" ? "DHT" : "DM";
              this.debug.log(`${routingType} routed to closest peer: ${peer.peerId.substring(0, 8)}...`);
            } catch (error) {
              this.debug.error(`${message.subtype} routing failed:`, error);
            }
          });
        } else {
          capablePeers2.forEach((peerConnection) => {
            if (peerConnection.peerId === excludePeerId || message.ttl <= 0) return;
            try {
              peerConnection.sendMessage(message);
              const routingType = message.subtype === "dht-routing" ? "DHT" : "DM";
              this.debug.log(`${routingType} fallback relay to: ${peerConnection.peerId.substring(0, 8)}...`);
            } catch (error) {
              this.debug.error(`${message.subtype} fallback relay failed:`, error);
            }
          });
        }
        return;
      }
      const allPeers = Array.from(this.connectionManager.peers.values());
      const capablePeers = allPeers.filter((peerConnection) => {
        return peerConnection.dataChannel && peerConnection.dataChannel.readyState === "open";
      });
      let propagatedTo = 0;
      this.debug.log(`\u{1F680} AGGRESSIVE GOSSIP: Found ${capablePeers.length}/${allPeers.length} peers with open data channels`);
      this.debug.log(`Message: ${message.id.substring(0, 8)}..., TTL: ${message.ttl}, Exclude: ${excludePeerId?.substring(0, 8) || "none"}`);
      allPeers.forEach((peerConnection) => {
        const status = peerConnection.getStatus();
        const dataChannelState = peerConnection.dataChannel?.readyState || "none";
        const canSend = peerConnection.dataChannel && peerConnection.dataChannel.readyState === "open";
        const isExcluded = peerConnection.peerId === excludePeerId;
        this.debug.log(`  ${peerConnection.peerId.substring(0, 8)}... - Status: ${status}, DataChannel: ${dataChannelState}, CanSend: ${canSend}, Excluded: ${isExcluded}`);
      });
      capablePeers.forEach((peerConnection) => {
        const peerId = peerConnection.peerId;
        if (peerId === excludePeerId || message.ttl <= 0) {
          return;
        }
        try {
          peerConnection.sendMessage(message);
          propagatedTo++;
          this.debug.log(`\u2705 GOSSIP SUCCESS: Sent to ${peerId.substring(0, 8)}...`);
        } catch (error) {
          this.debug.error(`\u274C GOSSIP FAILED: Could not send to ${peerId.substring(0, 8)}...`, error);
        }
      });
      this.debug.log(`\u{1F3AF} GOSSIP RESULT: Propagated to ${propagatedTo}/${capablePeers.length} capable peers`);
      if (propagatedTo === 0 && allPeers.length > 0) {
        this.debug.error(`\u{1F6A8} GOSSIP FAILURE: NO PROPAGATION! ${allPeers.length} total peers, ${capablePeers.length} with data channels`);
      }
    }
    /**
       * Get message statistics
       */
    getStats() {
      return {
        seenMessages: this.seenMessages.size,
        storedMessages: this.messageHistory.size,
        maxTTL: this.maxTTL,
        messageExpiryTime: this.messageExpiryTime
      };
    }
    /**
       * Generate unique message ID
       */
    async generateMessageId() {
      const array = new Uint8Array(16);
      if (typeof crypto !== "undefined" && crypto.getRandomValues) {
        crypto.getRandomValues(array);
      } else if (typeof process !== "undefined" && process.versions && process.versions.node) {
        try {
          const crypto3 = await import("crypto");
          const randomBytes2 = crypto3.randomBytes(16);
          array.set(randomBytes2);
        } catch (e) {
          console.warn("Could not use Node.js crypto, falling back to Math.random");
          for (let i = 0; i < array.length; i++) {
            array[i] = Math.floor(Math.random() * 256);
          }
        }
      } else {
        for (let i = 0; i < array.length; i++) {
          array[i] = Math.floor(Math.random() * 256);
        }
      }
      return Array.from(array, (byte) => byte.toString(16).padStart(2, "0")).join("");
    }
    /**
       * Clean up expired messages
       */
    startCleanupTimer() {
      if (this.cleanupTimer) {
        clearInterval(this.cleanupTimer);
      }
      if (environmentDetector.isBrowser) {
        this.cleanupTimer = window.setInterval(() => {
          this.cleanupExpiredMessages();
        }, this.cleanupInterval);
      } else {
        this.cleanupTimer = setInterval(() => {
          this.cleanupExpiredMessages();
        }, this.cleanupInterval);
      }
    }
    /**
       * Stop cleanup timer
       */
    stopCleanupTimer() {
      if (this.cleanupTimer) {
        clearInterval(this.cleanupTimer);
        this.cleanupTimer = null;
      }
    }
    cleanupExpiredMessages() {
      const now = Date.now();
      let cleaned = 0;
      let keyExchangesCleaned = 0;
      this.seenMessages.forEach((data, messageId) => {
        if (now - data.timestamp > this.messageExpiryTime) {
          this.seenMessages.delete(messageId);
          this.messageHistory.delete(messageId);
          cleaned++;
        }
      });
      this.processedKeyExchanges.forEach((timestamp, keyExchangeId) => {
        if (now - timestamp > 6e4) {
          this.processedKeyExchanges.delete(keyExchangeId);
          keyExchangesCleaned++;
        }
      });
      if (cleaned > 0) {
        this.debug.log(`Cleaned up ${cleaned} expired gossip messages`);
      }
      if (keyExchangesCleaned > 0) {
        this.debug.log(`Cleaned up ${keyExchangesCleaned} old key exchange tracking entries`);
      }
    }
    /**
       * Cleanup method
       */
    cleanup() {
      this.stopCleanupTimer();
      this.seenMessages.clear();
      this.messageHistory.clear();
      this.processedKeyExchanges.clear();
    }
    /**
       * Handle crypto-related messages (key exchange, etc.)
       * @private
       */
    async _handleCryptoMessage(message, fromPeerId, originPeerId) {
      const { subtype, content } = message;
      if (subtype === "key_exchange" || subtype === "key_exchange_response") {
        if (content && (content.type === "key_exchange" || content.type === "key_exchange_response") && content.publicKey) {
          const keyExchangeId = `${originPeerId}:${content.type}:${content.timestamp || Date.now()}`;
          if (this.processedKeyExchanges.has(keyExchangeId)) {
            this.debug.log(`\u{1F510} Ignoring duplicate ${content.type} from peer ${originPeerId.substring(0, 8)}... (already processed)`);
            return true;
          }
          const recentKeyExchangePattern = `${originPeerId}:${content.type}:`;
          const now = Date.now();
          let foundRecent = false;
          for (const [existingId, timestamp] of this.processedKeyExchanges.entries()) {
            if (existingId.startsWith(recentKeyExchangePattern) && now - timestamp < 5e3) {
              foundRecent = true;
              break;
            }
          }
          if (foundRecent) {
            this.debug.log(`\u{1F510} Ignoring recent duplicate ${content.type} from peer ${originPeerId.substring(0, 8)}... (processed recently)`);
            return true;
          }
          this.debug.log(`\u{1F510} Processing ${content.type} from peer ${originPeerId.substring(0, 8)}...`);
          this.processedKeyExchanges.set(keyExchangeId, now);
          this.mesh._handleKeyExchange(content, originPeerId);
          return true;
        }
      }
      return false;
    }
  };

  // src/MediaManager.js
  var MediaManager = class extends EventEmitter {
    constructor() {
      super();
      this.debug = DebugLogger_default.create("MediaManager");
      this.localStream = null;
      this.isVideoEnabled = false;
      this.isAudioEnabled = false;
      this.devices = {
        cameras: [],
        microphones: [],
        speakers: []
      };
      this.constraints = {
        video: {
          width: { ideal: 640 },
          height: { ideal: 480 },
          frameRate: { ideal: 30 }
        },
        audio: {
          echoCancellation: true,
          noiseSuppression: true,
          autoGainControl: true
        }
      };
    }
    /**
       * Initialize media devices and permissions
       */
    async init() {
      if (!environmentDetector.hasGetUserMedia) {
        this.debug.warn("getUserMedia not available in this environment");
        this.emit("error", { type: "init", error: new Error("getUserMedia not supported") });
        return false;
      }
      try {
        await this.enumerateDevices();
        return true;
      } catch (error) {
        this.debug.error("Failed to initialize media manager:", error);
        this.emit("error", { type: "init", error });
        return false;
      }
    }
    /**
       * Get available media devices
       */
    async enumerateDevices() {
      if (!environmentDetector.isBrowser && !environmentDetector.isNativeScript || typeof navigator === "undefined" || typeof navigator.mediaDevices === "undefined") {
        this.debug.warn("Media device enumeration not available in this environment");
        return;
      }
      try {
        const devices = await navigator.mediaDevices.enumerateDevices();
        this.devices.cameras = devices.filter((device) => device.kind === "videoinput");
        this.devices.microphones = devices.filter((device) => device.kind === "audioinput");
        this.devices.speakers = devices.filter((device) => device.kind === "audiooutput");
        this.emit("devicesUpdated", this.devices);
        return this.devices;
      } catch (error) {
        this.debug.error("Failed to enumerate devices:", error);
        this.emit("error", { type: "enumerate", error });
        throw error;
      }
    }
    /**
       * Start local media stream with specified constraints
       */
    async startLocalStream(options = {}) {
      const { video = false, audio = false, deviceIds = {} } = options;
      try {
        if (this.localStream) {
          this.stopLocalStream();
        }
        const constraints = {};
        if (video) {
          constraints.video = { ...this.constraints.video };
          if (deviceIds.camera) {
            constraints.video.deviceId = { exact: deviceIds.camera };
          }
        }
        if (audio) {
          constraints.audio = { ...this.constraints.audio };
          if (deviceIds.microphone) {
            constraints.audio.deviceId = { exact: deviceIds.microphone };
          }
        }
        if (!video && !audio) {
          throw new Error("At least one of video or audio must be enabled");
        }
        const pigeonRTC = environmentDetector.getPigeonRTC();
        if (pigeonRTC) {
          this.localStream = await pigeonRTC.getUserMedia(constraints);
        } else {
          this.localStream = await navigator.mediaDevices.getUserMedia(constraints);
        }
        this.isVideoEnabled = video;
        this.isAudioEnabled = audio;
        this.markStreamAsLocal(this.localStream);
        this.debug.log("Local media stream started:", {
          video: this.isVideoEnabled,
          audio: this.isAudioEnabled,
          tracks: this.localStream.getTracks().map((track) => ({
            kind: track.kind,
            enabled: track.enabled,
            label: track.label
          }))
        });
        this.emit("localStreamStarted", {
          stream: this.localStream,
          video: this.isVideoEnabled,
          audio: this.isAudioEnabled
        });
        return this.localStream;
      } catch (error) {
        this.debug.error("Failed to start local media stream:", error);
        this.emit("error", { type: "getUserMedia", error });
        throw error;
      }
    }
    /**
       * Stop local media stream
       */
    stopLocalStream() {
      if (this.localStream) {
        this.debug.log("Stopping local media stream");
        this.localStream.getTracks().forEach((track) => {
          track.stop();
        });
        this.localStream = null;
        this.isVideoEnabled = false;
        this.isAudioEnabled = false;
        this.emit("localStreamStopped");
      }
    }
    /**
       * Toggle video track on/off
       */
    toggleVideo() {
      if (this.localStream) {
        const videoTrack = this.localStream.getVideoTracks()[0];
        if (videoTrack) {
          videoTrack.enabled = !videoTrack.enabled;
          this.emit("videoToggled", { enabled: videoTrack.enabled });
          return videoTrack.enabled;
        }
      }
      return false;
    }
    /**
       * Toggle audio track on/off
       */
    toggleAudio() {
      if (this.localStream) {
        const audioTrack = this.localStream.getAudioTracks()[0];
        if (audioTrack) {
          audioTrack.enabled = !audioTrack.enabled;
          this.emit("audioToggled", { enabled: audioTrack.enabled });
          return audioTrack.enabled;
        }
      }
      return false;
    }
    /**
       * Get current media state
       */
    getMediaState() {
      const state = {
        hasLocalStream: !!this.localStream,
        videoEnabled: false,
        audioEnabled: false,
        devices: this.devices
      };
      if (this.localStream) {
        const videoTrack = this.localStream.getVideoTracks()[0];
        const audioTrack = this.localStream.getAudioTracks()[0];
        state.videoEnabled = videoTrack ? videoTrack.enabled : false;
        state.audioEnabled = audioTrack ? audioTrack.enabled : false;
      }
      return state;
    }
    /**
       * Check if browser supports required APIs
       */
    static checkSupport() {
      const pigeonRTC = environmentDetector.getPigeonRTC();
      const support = {
        getUserMedia: !!(navigator.mediaDevices && navigator.mediaDevices.getUserMedia),
        enumerateDevices: !!(navigator.mediaDevices && navigator.mediaDevices.enumerateDevices),
        webRTC: pigeonRTC ? pigeonRTC.isSupported() : !!window.RTCPeerConnection
      };
      support.fullSupport = support.getUserMedia && support.enumerateDevices && support.webRTC;
      return support;
    }
    /**
       * Get media permissions status
       */
    async getPermissions() {
      try {
        const permissions = {};
        if (navigator.permissions) {
          permissions.camera = await navigator.permissions.query({ name: "camera" });
          permissions.microphone = await navigator.permissions.query({ name: "microphone" });
        }
        return permissions;
      } catch (error) {
        this.debug.warn("Could not check media permissions:", error);
        return {};
      }
    }
    /**
       * Mark stream as local origin to prevent confusion with remote streams
       */
    markStreamAsLocal(stream) {
      if (!stream) return;
      try {
        Object.defineProperty(stream, "_peerPigeonOrigin", {
          value: "local",
          writable: false,
          enumerable: false,
          configurable: false
        });
        this.debug.log(`\u{1F512} Stream ${stream.id} marked as local origin in MediaManager`);
      } catch (error) {
        this.debug.warn("Could not mark stream as local origin:", error);
      }
    }
  };

  // src/WebDHT.js
  var WebDHT = class extends EventEmitter {
    constructor(mesh) {
      super();
      this.debug = DebugLogger_default.create("SimpleWebDHT");
      this.mesh = mesh;
      this.peerId = mesh.peerId;
      this.storage = /* @__PURE__ */ new Map();
      this.closestPeers = /* @__PURE__ */ new Set();
      this.replicationFactor = 3;
      this.hashPosition = this.hashPeerId(this.peerId);
      this.debug.log(`SimpleWebDHT initialized for peer ${this.peerId.substring(0, 8)} at position ${this.hashPosition.toString(16).substring(0, 8)}`);
      this.setupMessageHandling();
      this.startMaintenance();
    }
    /**
     * Calculate replication factor based on storage space and network size
     * @param {string} space - Storage space type (private, public, frozen)
     * @param {number} totalPeers - Total number of connected peers
     * @returns {number} Appropriate replication factor
     */
    getReplicationFactor(space, totalPeers = null) {
      const peerCount = totalPeers || Array.from(this.mesh.connectionManager.peers.keys()).filter((peerId) => this.mesh.connectionManager.peers.get(peerId).getStatus() === "connected").length;
      if (peerCount === 0) return 0;
      switch (space) {
        case "private":
          return Math.min(3, peerCount);
        case "public":
          return Math.max(3, Math.min(Math.ceil(peerCount * 0.3), 7));
        case "frozen":
          return Math.max(5, Math.min(Math.ceil(peerCount * 0.5), 10));
        default:
          return Math.min(this.replicationFactor, peerCount);
      }
    }
    /**
     * Simple hash function for consistent hashing
     */
    async hash(data) {
      const encoder = new TextEncoder();
      const dataBuffer = encoder.encode(typeof data === "string" ? data : JSON.stringify(data));
      const hashBuffer = await crypto.subtle.digest("SHA-256", dataBuffer);
      const hashArray = new Uint8Array(hashBuffer);
      let hash = 0;
      for (let i = 0; i < 4; i++) {
        hash = hash * 256 + hashArray[i] >>> 0;
      }
      return hash;
    }
    /**
     * Hash peer ID to position on ring
     */
    hashPeerId(peerId) {
      let hash = 0;
      for (let i = 0; i < peerId.length; i++) {
        hash = (hash << 5) - hash + peerId.charCodeAt(i) >>> 0;
      }
      return hash;
    }
    /**
     * Calculate distance between two positions on hash ring
     */
    ringDistance(pos1, pos2) {
      const diff = Math.abs(pos1 - pos2);
      const maxUint32 = 4294967295;
      return Math.min(diff, maxUint32 - diff);
    }
    /**
     * Find closest peers to a hash position
     */
    findClosestPeers(targetHash, count = this.replicationFactor) {
      const connectedPeers = Array.from(this.mesh.connectionManager.peers.keys()).filter((peerId) => this.mesh.connectionManager.peers.get(peerId).getStatus() === "connected");
      if (connectedPeers.length === 0) {
        return [];
      }
      const peersWithDistance = connectedPeers.map((peerId) => ({
        peerId,
        distance: this.ringDistance(targetHash, this.hashPeerId(peerId))
      }));
      peersWithDistance.sort((a, b) => a.distance - b.distance);
      return peersWithDistance.slice(0, count).map((p) => p.peerId);
    }
    /**
     * Store key-value pair with network namespace support
     * @param {string} key - Storage key
     * @param {*} value - Value to store
     * @param {Object} options - Storage options
     * @param {string} options.space - Storage space (private, public, frozen) for replication strategy
     */
    async put(key, value, options = {}) {
      const namespacedKey = `${this.mesh.networkName}:${key}`;
      const keyHash = await this.hash(namespacedKey);
      const storeData = {
        key: namespacedKey,
        originalKey: key,
        // Store original key for retrieval
        value,
        networkName: this.mesh.networkName,
        timestamp: Date.now(),
        publisher: this.peerId,
        space: options.space
        // Track space for replication info
      };
      this.storage.set(namespacedKey, storeData);
      this.debug.log(`PUT: Stored ${key} locally in network ${this.mesh.networkName}`);
      const replicationFactor = options.space ? this.getReplicationFactor(options.space) : this.replicationFactor;
      const targetPeers = this.findClosestPeers(keyHash, replicationFactor);
      this.debug.log(`PUT: Replicating ${key} to ${targetPeers.length} peers`);
      const replicationPromises = targetPeers.map(async (peerId) => {
        if (peerId !== this.peerId) {
          try {
            this.sendMessage(peerId, "dht_store", storeData);
            await new Promise((resolve) => setTimeout(resolve, 50));
            return { peerId, success: true };
          } catch (error) {
            this.debug.warn(`Replication to ${peerId.substring(0, 8)} failed:`, error.message);
            return { peerId, success: false, error: error.message };
          }
        }
        return { peerId, success: true };
      });
      await Promise.allSettled(replicationPromises);
      const spaceInfo = options.space ? ` (${options.space} space, RF=${replicationFactor})` : "";
      this.debug.log(`PUT: ${key} replicated to ${targetPeers.length} peers in network ${this.mesh.networkName}${spaceInfo}`);
      return true;
    }
    /**
     * Retrieve value by key with network namespace support
     * @param {string} key - Storage key
     * @param {Object} options - Retrieval options
     * @param {boolean} options.forceRefresh - Force refresh from network
     * @param {string} options.space - Storage space for space-aware replication
     */
    async get(key, options = {}) {
      const forceRefresh = options.forceRefresh || false;
      const namespacedKey = `${this.mesh.networkName}:${key}`;
      if (!forceRefresh && this.storage.has(namespacedKey)) {
        const data = this.storage.get(namespacedKey);
        this.debug.log(`GET: Found ${key} locally in network ${this.mesh.networkName}`);
        return data.value;
      }
      const keyHash = await this.hash(namespacedKey);
      const replicationFactor = options.space ? this.getReplicationFactor(options.space) : this.replicationFactor;
      const targetPeers = this.findClosestPeers(keyHash, replicationFactor);
      this.debug.log(`GET: Querying ${targetPeers.length} peers for ${key} in network ${this.mesh.networkName}`);
      const queryPromises = targetPeers.map(async (peerId) => {
        if (peerId === this.peerId) return null;
        try {
          this.debug.log(`GET: Querying peer ${peerId.substring(0, 8)} for ${key}`);
          const result = await this.queryPeer(peerId, namespacedKey);
          this.debug.log(`GET: Peer ${peerId.substring(0, 8)} response for ${key}:`, result ? "found" : "not found");
          return result;
        } catch (error) {
          this.debug.warn(`Query to ${peerId.substring(0, 8)} failed:`, error.message);
          return null;
        }
      });
      const results = await Promise.allSettled(queryPromises);
      for (const result of results) {
        if (result.status === "fulfilled" && result.value) {
          const data = result.value;
          this.storage.set(namespacedKey, data);
          this.debug.log(`GET: Found ${key} from network ${this.mesh.networkName}`);
          return data.value;
        }
      }
      this.debug.log(`GET: ${key} not found after querying ${targetPeers.length} peers`);
      return null;
    }
    /**
     * Query a specific peer for a key
     */
    async queryPeer(peerId, key) {
      return new Promise((resolve, reject) => {
        const requestId = Math.random().toString(36).substring(7);
        const timeout = setTimeout(() => {
          this.responseHandlers.delete(requestId);
          reject(new Error("Query timeout"));
        }, 5e3);
        this.responseHandlers.set(requestId, (response) => {
          clearTimeout(timeout);
          this.responseHandlers.delete(requestId);
          if (response.found) {
            resolve(response.data);
          } else {
            resolve(null);
          }
        });
        this.sendMessage(peerId, "dht_query", { key, requestId });
      });
    }
    /**
     * Send message to peer through connection manager
     */
    sendMessage(peerId, type, data) {
      const message = {
        type: "dht",
        messageType: type,
        data,
        from: this.peerId,
        timestamp: Date.now()
      };
      const peer = this.mesh.connectionManager.peers.get(peerId);
      if (peer && peer.getStatus() === "connected") {
        peer.sendMessage(message);
      } else {
        throw new Error(`Peer ${peerId} not connected`);
      }
    }
    /**
     * Setup message handling
     */
    setupMessageHandling() {
      this.responseHandlers = /* @__PURE__ */ new Map();
    }
    /**
     * Handle incoming DHT message
     */
    async handleMessage(message, fromPeerId) {
      const { messageType, data } = message;
      switch (messageType) {
        case "dht_store":
          this.handleStore(data, fromPeerId);
          break;
        case "dht_query":
          await this.handleQuery(data, fromPeerId);
          break;
        case "dht_query_response":
          this.handleQueryResponse(data);
          break;
        default:
          this.debug.warn(`Unknown DHT message type: ${messageType}`);
      }
    }
    /**
     * Handle store request from peer with network filtering
     */
    handleStore(data, fromPeerId) {
      const { key, value, timestamp, publisher, networkName, space } = data;
      const messageNetwork = networkName || "global";
      if (messageNetwork !== this.mesh.networkName) {
        this.debug.log(`Filtering DHT store from different network: ${messageNetwork} (current: ${this.mesh.networkName})`);
        return;
      }
      try {
        if (this.storage.has(key)) {
          const existing = this.storage.get(key);
          if (existing.timestamp >= timestamp) {
            return;
          }
        }
        this.storage.set(key, { key, value, timestamp, publisher, space, networkName });
        this.debug.log(`STORE: Received ${key} from ${fromPeerId.substring(0, 8)}`);
      } catch (error) {
        this.debug.warn(`Store failed for ${key}:`, error.message);
      }
    }
    /**
     * Handle query request from peer
     */
    async handleQuery(data, fromPeerId) {
      const { key, requestId } = data;
      const response = {
        requestId,
        found: false,
        data: null
      };
      if (this.storage.has(key)) {
        response.found = true;
        response.data = this.storage.get(key);
      }
      this.sendMessage(fromPeerId, "dht_query_response", response);
    }
    /**
     * Handle query response
     */
    handleQueryResponse(data) {
      const { requestId } = data;
      const handler = this.responseHandlers.get(requestId);
      if (handler) {
        handler(data);
      }
    }
    /**
     * Update closest peers for efficient routing
     */
    updateClosestPeers() {
      const connectedPeers = Array.from(this.mesh.connectionManager.peers.keys()).filter((peerId) => this.mesh.connectionManager.peers.get(peerId).getStatus() === "connected");
      const peersWithDistance = connectedPeers.map((peerId) => ({
        peerId,
        distance: this.ringDistance(this.hashPosition, this.hashPeerId(peerId))
      }));
      peersWithDistance.sort((a, b) => a.distance - b.distance);
      this.closestPeers = new Set(
        peersWithDistance.slice(0, 10).map((p) => p.peerId)
      );
    }
    /**
     * Periodic maintenance
     */
    startMaintenance() {
      setInterval(() => {
        this.updateClosestPeers();
      }, 3e4);
      setInterval(() => {
        this.cleanupOldData();
      }, 3e5);
    }
    /**
     * Clean up old data (optional TTL support)
     */
    cleanupOldData() {
      const now = Date.now();
      const maxAge = 24 * 60 * 60 * 1e3;
      for (const [key, data] of this.storage) {
        if (now - data.timestamp > maxAge) {
          this.storage.delete(key);
          this.debug.log(`Cleaned up old data: ${key}`);
        }
      }
    }
    /**
     * Get DHT statistics
     */
    getStats() {
      return {
        localKeys: this.storage.size,
        connectedPeers: this.mesh.connectionManager.getConnectedPeerCount(),
        closestPeers: this.closestPeers.size,
        hashPosition: this.hashPosition.toString(16).substring(0, 8)
      };
    }
  };

  // src/CryptoManager.js
  var unsea = null;
  async function initializeUnsea() {
    if (unsea) return unsea;
    try {
      const isNode = typeof process !== "undefined" && process.versions && process.versions.node;
      const isBrowser3 = !isNode && typeof window !== "undefined" && typeof document !== "undefined";
      if (isNode) {
        unsea = await Promise.resolve().then(() => (init_unsea(), unsea_exports));
        console.log("\u2705 Loaded unsea from npm package (Node.js)");
      } else if (isBrowser3) {
        if (typeof globalThis !== "undefined" && globalThis.__PEERPIGEON_UNSEA__ || typeof window !== "undefined" && window.__PEERPIGEON_UNSEA__) {
          unsea = globalThis.__PEERPIGEON_UNSEA__ || window.__PEERPIGEON_UNSEA__;
          console.log("\u2705 Using bundled unsea (self-contained)");
        } else {
          try {
            unsea = await import("https://cdn.jsdelivr.net/npm/unsea@latest/+esm");
            console.log("\u2705 Loaded unsea from jsDelivr CDN");
          } catch (jsDelivrError) {
            console.warn("Failed to load from jsDelivr, trying unpkg:", jsDelivrError);
            try {
              unsea = await import("https://unpkg.com/unsea@latest/dist/unsea.esm.js");
              console.log("\u2705 Loaded unsea from unpkg CDN");
            } catch (unpkgError) {
              console.warn("Failed to load from unpkg, trying Skypack:", unpkgError);
              unsea = await import("https://cdn.skypack.dev/unsea");
              console.log("\u2705 Loaded unsea from Skypack CDN");
            }
          }
        }
      } else {
        throw new Error("Unknown environment - cannot load unsea");
      }
      if (!unsea) {
        throw new Error("Unsea not found after loading");
      }
      return unsea;
    } catch (error) {
      console.error("Failed to load unsea:", error);
      throw error;
    }
  }
  var CryptoManager = class extends EventEmitter {
    constructor() {
      super();
      this.debug = DebugLogger_default.create("CryptoManager");
      this.unsea = null;
      this.keypair = null;
      this.peerKeys = /* @__PURE__ */ new Map();
      this.encryptionEnabled = false;
      this.initialized = false;
      this.groupKeys = /* @__PURE__ */ new Map();
      this.messageNonces = /* @__PURE__ */ new Set();
      this.maxNonceAge = 3e5;
      this.nonceCleanupInterval = null;
      this.stats = {
        messagesEncrypted: 0,
        messagesDecrypted: 0,
        encryptionTime: 0,
        decryptionTime: 0,
        keyExchanges: 0
      };
    }
    /**
       * Initialize the crypto manager
       * @param {Object} options - Configuration options
       * @param {string} options.alias - Optional user alias for persistent identity
       * @param {string} options.password - Optional password for user account
       * @param {boolean} options.generateKeypair - Whether to generate a new keypair if no credentials
       * @param {string} options.peerId - Peer ID to use for automatic key storage
       * @returns {Promise<Object>} The generated or loaded keypair
       */
    async init(options = {}) {
      try {
        this.unsea = await initializeUnsea();
        if (options.alias && options.password) {
          await this.createOrAuthenticateUser(options.alias, options.password);
        } else if (options.peerId) {
          await this.initWithPeerId(options.peerId);
        } else if (options.generateKeypair !== false) {
          this.keypair = await this.unsea.generateRandomPair();
        }
        if (this.keypair) {
          this.encryptionEnabled = true;
          this.initialized = true;
          this.emit("cryptoReady", { publicKey: this.getPublicKey() });
          this.startNonceCleanup();
        }
        return this.keypair;
      } catch (error) {
        this.debug.error("CryptoManager initialization failed:", error);
        this.emit("cryptoError", { error: error.message });
        throw error;
      }
    }
    /**
       * Create or authenticate a persistent user account
       */
    async createOrAuthenticateUser(alias, password) {
      try {
        if (typeof window !== "undefined") {
          try {
            const existingKeys = await this.unsea.loadKeys(alias, password);
            if (existingKeys) {
              this.keypair = existingKeys;
            } else {
              this.keypair = await this.unsea.generateRandomPair();
              setTimeout(async () => {
                try {
                  await this.unsea.saveKeys(alias, this.keypair, password);
                } catch (saveError) {
                  this.debug.warn("Failed to save persistent keys:", saveError);
                }
              }, 0);
            }
          } catch (error) {
            this.debug.warn("Failed to use persistent storage, generating ephemeral keys:", error);
            this.keypair = await this.unsea.generateRandomPair();
          }
        } else {
          this.keypair = await this.unsea.generateRandomPair();
        }
        this.emit("userAuthenticated", { alias, publicKey: this.getPublicKey() });
      } catch (error) {
        this.debug.error("User authentication failed:", error);
        throw error;
      }
    }
    /**
       * Initialize with automatic key persistence using peer ID
       * @param {string} peerId - The peer ID to use as storage alias
       */
    async initWithPeerId(peerId) {
      try {
        if (!this.unsea) {
          this.unsea = await initializeUnsea();
        }
        const keyAlias = `peerpigeon-${peerId}`;
        this.debug.log(`\u{1F510} Initializing crypto with automatic key persistence for peer ${peerId.substring(0, 8)}...`);
        try {
          const loadKeysPromise = this.unsea.loadKeys(keyAlias);
          const timeoutPromise = new Promise((resolve, reject) => {
            setTimeout(() => reject(new Error("LoadKeys timeout")), 5e3);
          });
          const existingKeys = await Promise.race([loadKeysPromise, timeoutPromise]);
          if (existingKeys && existingKeys.pub && existingKeys.priv) {
            this.keypair = existingKeys;
            this.debug.log(`\u{1F510} Loaded existing keypair for peer ${peerId.substring(0, 8)}...`);
            this.initialized = true;
            this.encryptionEnabled = true;
            this.emit("cryptoReady", {
              hasKeypair: !!this.keypair,
              publicKey: this.getPublicKey()
            });
            return;
          }
        } catch (error) {
          this.debug.log(`\u{1F510} No existing keys found for peer ${peerId.substring(0, 8)}..., will generate new ones`);
        }
        const generateKeysPromise = this.unsea.generateRandomPair();
        const generateTimeoutPromise = new Promise((resolve, reject) => {
          setTimeout(() => reject(new Error("GenerateKeys timeout")), 5e3);
        });
        this.keypair = await Promise.race([generateKeysPromise, generateTimeoutPromise]);
        setTimeout(async () => {
          try {
            await this.unsea.saveKeys(keyAlias, this.keypair);
            this.debug.log(`\u{1F510} Generated and saved new keypair for peer ${peerId.substring(0, 8)}...`);
          } catch (saveError) {
            this.debug.warn(`\u{1F510} Failed to save keypair for peer ${peerId.substring(0, 8)}..., using ephemeral keys:`, saveError.message);
          }
        }, 0);
        this.initialized = true;
        this.encryptionEnabled = true;
        this.emit("cryptoReady", {
          hasKeypair: !!this.keypair,
          publicKey: this.getPublicKey()
        });
      } catch (error) {
        this.debug.error("Failed to initialize crypto with peer ID:", error);
        if (this.unsea) {
          this.keypair = await this.unsea.generateRandomPair();
          this.debug.log("\u{1F510} Using ephemeral keypair as fallback");
        } else {
          throw error;
        }
      }
      this.initialized = true;
      this.encryptionEnabled = true;
      this.emit("cryptoReady", {
        hasKeypair: !!this.keypair,
        publicKey: this.getPublicKey()
      });
    }
    /**
       * Get the public key for sharing
       * @returns {string} The public key
       */
    getPublicKey() {
      return this.keypair?.pub || this.keypair?.publicKey;
    }
    /**
       * Get crypto status information
       * @returns {Object} Status information
       */
    getStatus() {
      const groups = {};
      this.groupKeys.forEach((groupKey, groupId) => {
        groups[groupId] = {
          publicKey: groupKey.pub,
          created: groupKey.created || Date.now()
          // Use stored creation time or fallback to current time
        };
      });
      return {
        initialized: this.initialized,
        enabled: this.encryptionEnabled,
        hasKeypair: !!this.keypair,
        publicKey: this.getPublicKey(),
        peerCount: this.peerKeys.size,
        groupCount: this.groupKeys.size,
        groups,
        stats: { ...this.stats }
      };
    }
    /**
       * Store a peer's public keys (both pub and epub)
       * @param {string} peerId - The peer ID
       * @param {string|Object} publicKey - The peer's public key(s) - can be string (pub) or object with {pub, epub}
       */
    addPeerKey(peerId, publicKey) {
      if (!publicKey) return false;
      let keyData;
      if (typeof publicKey === "string") {
        keyData = { pub: publicKey, epub: null };
      } else if (typeof publicKey === "object" && (publicKey.pub || publicKey.epub)) {
        keyData = publicKey;
      } else {
        return false;
      }
      const existingKey = this.peerKeys.get(peerId);
      if (existingKey) {
        const pubMatches = existingKey.pub === keyData.pub;
        const epubMatches = existingKey.epub === keyData.epub;
        if (pubMatches && epubMatches) {
          return false;
        }
      }
      this.peerKeys.set(peerId, keyData);
      this.stats.keyExchanges++;
      this.emit("peerKeyAdded", { peerId, publicKey: keyData });
      return true;
    }
    /**
       * Remove a peer's public key
       * @param {string} peerId - The peer ID
       */
    removePeerKey(peerId) {
      const removed = this.peerKeys.delete(peerId);
      if (removed) {
        this.emit("peerKeyRemoved", { peerId });
      }
      return removed;
    }
    /**
       * Encrypt a message for a specific peer
       * @param {any} message - The message to encrypt
       * @param {string} peerId - The target peer ID
       * @returns {Promise<Object>} Encrypted message object
       */
    async encryptForPeer(message, peerId) {
      if (!this.encryptionEnabled) {
        return { encrypted: false, data: message };
      }
      const peerKeyData = this.peerKeys.get(peerId);
      if (!peerKeyData) {
        throw new Error(`No public key found for peer ${peerId}`);
      }
      if (!peerKeyData.epub) {
        throw new Error(`No encryption public key (epub) found for peer ${peerId}. Only regular public key (pub) available.`);
      }
      const startTime = Date.now();
      try {
        const nonce = await this.generateNonce();
        const serialized = JSON.stringify(message);
        const peerKeypair = {
          pub: peerKeyData.pub,
          epub: peerKeyData.epub
        };
        const encrypted = await this.unsea.encryptMessageWithMeta(serialized, peerKeypair);
        const result = {
          encrypted: true,
          data: encrypted,
          from: this.getPublicKey(),
          nonce,
          timestamp: Date.now()
        };
        this.stats.messagesEncrypted++;
        this.stats.encryptionTime += Date.now() - startTime;
        return result;
      } catch (error) {
        this.debug.error("Peer encryption failed:", error);
        throw error;
      }
    }
    /**
       * Decrypt a message from a peer
       * @param {Object} encryptedData - The encrypted message object
       * @returns {Promise<any>} The decrypted message
       */
    async decryptFromPeer(encryptedData) {
      if (!this.encryptionEnabled || !encryptedData.encrypted) {
        return encryptedData.data || encryptedData;
      }
      if (encryptedData.nonce && this.messageNonces.has(encryptedData.nonce)) {
        throw new Error("Replay attack detected: duplicate nonce");
      }
      const startTime = Date.now();
      try {
        const decrypted = await this.unsea.decryptMessageWithMeta(encryptedData.data, this.keypair.epriv);
        const parsed = JSON.parse(decrypted);
        if (encryptedData.nonce) {
          this.messageNonces.add(encryptedData.nonce);
        }
        this.stats.messagesDecrypted++;
        this.stats.decryptionTime += Date.now() - startTime;
        return parsed;
      } catch (error) {
        this.debug.error("Peer decryption failed:", error);
        throw error;
      }
    }
    /**
       * Sign data with our private key
       * @param {any} data - The data to sign
       * @returns {Promise<string>} The signature
       */
    async sign(data) {
      if (!this.encryptionEnabled) return null;
      try {
        const serialized = typeof data === "string" ? data : JSON.stringify(data);
        return await this.unsea.signMessage(serialized, this.keypair.priv);
      } catch (error) {
        this.debug.error("Signing failed:", error);
        throw error;
      }
    }
    /**
       * Verify a signature
       * @param {string} signature - The signature to verify
       * @param {any} data - The original data
       * @param {string} publicKey - The signer's public key
       * @returns {Promise<boolean>} Whether the signature is valid
       */
    async verify(signature, data, publicKey) {
      if (!this.encryptionEnabled) return true;
      try {
        const serialized = typeof data === "string" ? data : JSON.stringify(data);
        return await this.unsea.verifyMessage(serialized, signature, publicKey);
      } catch (error) {
        this.debug.error("Signature verification failed:", error);
        return false;
      }
    }
    /**
       * Generate a shared group key
       * @param {string} groupId - The group identifier
       * @returns {Promise<Object>} The group key pair
       */
    async generateGroupKey(groupId) {
      try {
        const groupKey = await this.unsea.generateRandomPair();
        const groupKeyWithMeta = {
          ...groupKey,
          created: Date.now(),
          groupId
        };
        this.groupKeys.set(groupId, groupKeyWithMeta);
        this.emit("groupKeyGenerated", { groupId, publicKey: groupKey.pub });
        return groupKeyWithMeta;
      } catch (error) {
        this.debug.error("Group key generation failed:", error);
        throw error;
      }
    }
    /**
       * Add an existing group key
       * @param {string} groupId - The group identifier
       * @param {Object} groupKey - The group key pair
       */
    addGroupKey(groupId, groupKey) {
      this.groupKeys.set(groupId, groupKey);
      this.emit("groupKeyAdded", { groupId, publicKey: groupKey.pub });
    }
    /**
       * Encrypt a message for a group
       * @param {any} message - The message to encrypt
       * @param {string} groupId - The group identifier
       * @returns {Promise<Object>} Encrypted message object
       */
    async encryptForGroup(message, groupId) {
      if (!this.encryptionEnabled) {
        return { encrypted: false, data: message };
      }
      const groupKey = this.groupKeys.get(groupId);
      if (!groupKey) {
        throw new Error(`No group key found for group ${groupId}`);
      }
      const startTime = Date.now();
      try {
        const nonce = await this.generateNonce();
        const serialized = JSON.stringify(message);
        const encrypted = await this.unsea.encryptMessageWithMeta(serialized, groupKey);
        const result = {
          encrypted: true,
          group: true,
          groupId,
          data: encrypted,
          from: this.getPublicKey(),
          nonce,
          timestamp: Date.now()
        };
        this.stats.messagesEncrypted++;
        this.stats.encryptionTime += Date.now() - startTime;
        return result;
      } catch (error) {
        this.debug.error("Group encryption failed:", error);
        throw error;
      }
    }
    /**
       * Decrypt a group message
       * @param {Object} encryptedData - The encrypted message object
       * @returns {Promise<any>} The decrypted message
       */
    async decryptFromGroup(encryptedData) {
      if (!this.encryptionEnabled || !encryptedData.encrypted || !encryptedData.group) {
        return encryptedData.data || encryptedData;
      }
      const groupKey = this.groupKeys.get(encryptedData.groupId);
      if (!groupKey) {
        throw new Error(`No group key found for group ${encryptedData.groupId}`);
      }
      if (encryptedData.nonce && this.messageNonces.has(encryptedData.nonce)) {
        throw new Error("Replay attack detected: duplicate nonce");
      }
      const startTime = Date.now();
      try {
        const decrypted = await this.unsea.decryptMessageWithMeta(encryptedData.data, groupKey.epriv);
        const parsed = JSON.parse(decrypted);
        if (encryptedData.nonce) {
          this.messageNonces.add(encryptedData.nonce);
        }
        this.stats.messagesDecrypted++;
        this.stats.decryptionTime += Date.now() - startTime;
        return parsed;
      } catch (error) {
        this.debug.error("Group decryption failed:", error);
        throw error;
      }
    }
    /**
       * Generate a cryptographically secure nonce
       * @returns {Promise<string>} A unique nonce
       */
    async generateNonce() {
      const timestamp = Date.now().toString();
      const random = Math.random().toString(36).substring(2);
      const combined = `${timestamp}-${random}-${Math.floor(Math.random() * 1e6)}`;
      if (typeof crypto !== "undefined" && crypto.subtle) {
        try {
          const encoder = new TextEncoder();
          const data = encoder.encode(combined);
          const hashBuffer = await crypto.subtle.digest("SHA-256", data);
          const hashArray = Array.from(new Uint8Array(hashBuffer));
          return hashArray.map((b) => b.toString(16).padStart(2, "0")).join("").substring(0, 16);
        } catch (error) {
          this.debug.warn("Could not use crypto.subtle for nonce generation:", error);
        }
      }
      return combined;
    }
    /**
       * Start periodic cleanup of old nonces
       */
    startNonceCleanup() {
      this.nonceCleanupInterval = setInterval(() => {
        try {
          if (this.messageNonces.size > 1e3) {
            this.messageNonces.clear();
          }
        } catch (error) {
          console.error("Error during nonce cleanup:", error);
        }
      }, 6e4);
    }
    /**
       * Export public key for sharing
       * @returns {Object} Export data
       */
    exportPublicKey() {
      if (!this.keypair) return null;
      return {
        pub: this.keypair.pub,
        epub: this.keypair.epub,
        algorithm: "ECDSA",
        created: Date.now()
      };
    }
    /**
       * Clear all stored keys and reset state
       */
    reset() {
      if (this.nonceCleanupInterval) {
        clearInterval(this.nonceCleanupInterval);
        this.nonceCleanupInterval = null;
      }
      this.keypair = null;
      this.peerKeys.clear();
      this.groupKeys.clear();
      this.messageNonces.clear();
      this.encryptionEnabled = false;
      this.initialized = false;
      this.stats = {
        messagesEncrypted: 0,
        messagesDecrypted: 0,
        encryptionTime: 0,
        decryptionTime: 0,
        keyExchanges: 0
      };
      this.emit("cryptoReset");
    }
    /**
       * Test crypto functionality
       * @returns {Promise<Object>} Test results
       */
    async runSelfTest() {
      const results = {
        keypairGeneration: false,
        encryption: false,
        decryption: false,
        signing: false,
        verification: false,
        groupEncryption: false,
        errors: []
      };
      if (!this.unsea) {
        results.errors.push("Unsea library not loaded");
        return results;
      }
      if (!this.initialized) {
        results.errors.push("CryptoManager not initialized");
        return results;
      }
      this.debug.log("\u{1F50D} Debug: unsea object:", this.unsea);
      this.debug.log("\u{1F50D} Debug: available methods:", Object.keys(this.unsea));
      try {
        this.debug.log("\u{1F9EA} Testing keypair generation...");
        const testKeypair = await this.unsea.generateRandomPair();
        this.debug.log("\u{1F50D} Generated keypair:", testKeypair);
        results.keypairGeneration = !!(testKeypair && (testKeypair.pub || testKeypair.publicKey));
        this.debug.log("\u{1F9EA} Testing encryption...");
        const testMessage = "Hello, crypto world!";
        const peerKeypair = await this.unsea.generateRandomPair();
        const encrypted = await this.unsea.encryptMessageWithMeta(testMessage, peerKeypair);
        this.debug.log("\u{1F50D} Encrypted result:", encrypted);
        results.encryption = !!encrypted;
        this.debug.log("\u{1F9EA} Testing decryption...");
        const decrypted = await this.unsea.decryptMessageWithMeta(encrypted, peerKeypair.epriv);
        this.debug.log("\u{1F50D} Decrypted result:", decrypted);
        results.decryption = decrypted === testMessage;
        this.debug.log("\u{1F9EA} Testing signing...");
        const signature = await this.unsea.signMessage(testMessage, this.keypair.priv);
        this.debug.log("\u{1F50D} Signature:", signature);
        results.signing = !!signature;
        this.debug.log("\u{1F9EA} Testing verification...");
        const verified = await this.unsea.verifyMessage(testMessage, signature, this.keypair.pub);
        this.debug.log("\u{1F50D} Verification result:", verified);
        results.verification = verified === true;
        this.debug.log("\u{1F9EA} Testing group encryption...");
        const groupKey = await this.unsea.generateRandomPair();
        const groupEncrypted = await this.unsea.encryptMessageWithMeta(testMessage, groupKey);
        const groupDecrypted = await this.unsea.decryptMessageWithMeta(groupEncrypted, groupKey.epriv);
        results.groupEncryption = groupDecrypted === testMessage;
      } catch (error) {
        this.debug.error("\u{1F50D} Self-test error:", error);
        results.errors.push(error.message);
      }
      this.debug.log("\u{1F50D} Final test results:", results);
      return results;
    }
  };

  // src/LexicalStorageInterface.js
  var LexicalStorageInterface = class _LexicalStorageInterface extends EventEmitter {
    constructor(distributedStorage, path = []) {
      super();
      this.debug = DebugLogger_default.create("LexicalStorageInterface");
      this.storage = distributedStorage;
      this.path = path.slice();
      this.subscriptions = /* @__PURE__ */ new Map();
      this._isMap = false;
    }
    /**
     * Navigate to a key (creates a new interface instance)
     * @param {string} key - The key to navigate to
     * @returns {LexicalStorageInterface} New interface for the key
     */
    get(key) {
      const newInterface = new _LexicalStorageInterface(this.storage, [...this.path, key]);
      return newInterface;
    }
    /**
     * Store data at the current path
     * @param {any} value - Value to store
     * @param {Object} options - Storage options
     * @returns {Promise<LexicalStorageInterface>} This interface for chaining
     */
    async put(value, options = {}) {
      const fullKey = this.path.join(":");
      if (typeof value === "object" && value !== null && !Array.isArray(value)) {
        const promises = Object.entries(value).map(([prop, val]) => {
          const propKey = `${fullKey}:${prop}`;
          return this.storage.store(propKey, val, options);
        });
        await Promise.all(promises);
        await this.storage.store(fullKey, { _keys: Object.keys(value), _type: "object" }, options);
      } else {
        await this.storage.store(fullKey, value, options);
      }
      this.emit("put", { key: fullKey, value });
      return this;
    }
    /**
     * Retrieve the current value
     * @param {Object} options - Retrieval options
     * @returns {Promise<any>} The stored value
     */
    async val(options = {}) {
      const fullKey = this.path.join(":");
      const value = await this.storage.retrieve(fullKey, options);
      if (value && typeof value === "object" && value._keys && value._type === "object") {
        const reconstructed = {};
        const promises = value._keys.map(async (key) => {
          const keyValue = await this.storage.retrieve(`${fullKey}:${key}`, options);
          reconstructed[key] = keyValue;
        });
        await Promise.all(promises);
        return reconstructed;
      }
      return value;
    }
    /**
     * Set multiple key-value pairs (like GUN's set)
     * @param {Object} obj - Object with key-value pairs to set
     * @param {Object} options - Storage options
     * @returns {Promise<LexicalStorageInterface>} This interface for chaining
     */
    async set(obj, options = {}) {
      const fullKey = this.path.join(":");
      const promises = Object.entries(obj).map(([key, value]) => {
        const setKey = `${fullKey}:${key}`;
        return this.storage.store(setKey, value, options);
      });
      await Promise.all(promises);
      const existingSet = await this.storage.retrieve(`${fullKey}:_set`) || {};
      const updatedSet = { ...existingSet, ...obj };
      await this.storage.store(`${fullKey}:_set`, updatedSet, options);
      this.emit("set", { key: fullKey, values: obj });
      return this;
    }
    /**
     * Map over a set (like GUN's map)
     * @returns {LexicalStorageInterface} New interface for mapping
     */
    map() {
      const mapInterface = new _LexicalStorageInterface(this.storage, this.path);
      mapInterface._isMap = true;
      return mapInterface;
    }
    /**
     * Subscribe to changes (like GUN's on)
     * @param {Function} callback - Callback for changes
     * @returns {Function} Unsubscribe function
     */
    on(callback) {
      const fullKey = this.path.join(":");
      if (this._isMap) {
        const setKey = `${fullKey}:_set`;
        const handleChange = async () => {
          const setData = await this.storage.retrieve(setKey);
          if (setData) {
            Object.entries(setData).forEach(([key, value]) => {
              callback(value, key);
            });
          }
        };
        if (this.storage.subscribe) {
          this.storage.subscribe(setKey).then(() => {
            this.storage.on("dataUpdated", (event) => {
              if (event.key === setKey) {
                handleChange();
              }
            });
          });
        }
        handleChange();
        return () => {
          if (this.storage.unsubscribe) {
            this.storage.unsubscribe(setKey);
          }
        };
      } else {
        if (this.storage.subscribe) {
          this.storage.subscribe(fullKey).then((currentValue) => {
            callback(currentValue, fullKey);
          });
          this.storage.on("dataUpdated", (event) => {
            if (event.key === fullKey) {
              this.storage.retrieve(fullKey).then((value) => {
                callback(value, fullKey);
              });
            }
          });
        }
        return () => {
          if (this.storage.unsubscribe) {
            this.storage.unsubscribe(fullKey);
          }
        };
      }
    }
    /**
     * Once - listen for a single change
     * @param {Function} callback - Callback for the change
     * @returns {Promise} Promise that resolves with the value
     */
    async once(callback) {
      return new Promise((resolve) => {
        const unsubscribe = this.on((value, key) => {
          if (callback) callback(value, key);
          unsubscribe();
          resolve(value);
        });
      });
    }
    /**
     * Delete the current path
     * @returns {Promise<boolean>} Success status
     */
    async delete() {
      const fullKey = this.path.join(":");
      const value = await this.storage.retrieve(fullKey);
      if (value && typeof value === "object" && value._keys && value._type === "object") {
        const deletePromises = value._keys.map((key) => {
          const propKey = `${fullKey}:${key}`;
          return this.storage.delete(propKey);
        });
        await Promise.all(deletePromises);
      }
      const result = await this.storage.delete(fullKey);
      this.emit("delete", { key: fullKey });
      return result;
    }
    /**
     * Update with merge semantics
     * @param {any} value - Value to merge
     * @param {Object} options - Update options
     * @returns {Promise<LexicalStorageInterface>} This interface for chaining
     */
    async update(value, options = {}) {
      const fullKey = this.path.join(":");
      const currentValue = await this.storage.retrieve(fullKey);
      let mergedValue;
      if (typeof value === "object" && value !== null && !Array.isArray(value) && typeof currentValue === "object" && currentValue !== null && !Array.isArray(currentValue)) {
        if (currentValue._keys && currentValue._type === "object") {
          const reconstructed = {};
          const promises = currentValue._keys.map(async (key) => {
            const keyValue = await this.storage.retrieve(`${fullKey}:${key}`, options);
            reconstructed[key] = keyValue;
          });
          await Promise.all(promises);
          mergedValue = { ...reconstructed, ...value };
        } else {
          mergedValue = { ...currentValue, ...value };
        }
      } else {
        mergedValue = value;
      }
      await this.put(mergedValue, options);
      this.emit("update", { key: fullKey, value: mergedValue });
      return this;
    }
    /**
     * Create a proxied interface that allows property access
     * @returns {Proxy} Proxied interface
     */
    proxy() {
      return new Proxy(this, {
        get(target, prop) {
          if (typeof target[prop] === "function") {
            return target[prop].bind(target);
          }
          if (typeof prop === "string" && !prop.startsWith("_") && prop !== "constructor") {
            return target.get(prop).proxy();
          }
          return target[prop];
        }
      });
    }
    /**
     * Get the current path as a string
     * @returns {string} The path
     */
    getPath() {
      return this.path.join(":");
    }
    /**
     * Check if a value exists at the current path
     * @returns {Promise<boolean>} Whether the value exists
     */
    async exists() {
      const fullKey = this.path.join(":");
      const value = await this.storage.retrieve(fullKey);
      return value !== null && value !== void 0;
    }
    /**
     * Get all keys under the current path (for object-like structures)
     * @returns {Promise<string[]>} Array of keys
     */
    async keys() {
      const fullKey = this.path.join(":");
      const value = await this.storage.retrieve(fullKey);
      if (value && typeof value === "object" && value._keys && value._type === "object") {
        return value._keys;
      }
      return [];
    }
  };
  function createLexicalInterface(distributedStorage) {
    const lexicalInterface = new LexicalStorageInterface(distributedStorage);
    return lexicalInterface.proxy();
  }

  // src/DistributedStorageManager.js
  var unsea2 = null;
  async function initializeUnsea2() {
    if (unsea2) return unsea2;
    try {
      if (typeof window !== "undefined" && window.__PEERPIGEON_UNSEA__) {
        unsea2 = window.__PEERPIGEON_UNSEA__;
        console.log("\u2705 Using bundled UnSEA crypto for storage");
        return unsea2;
      }
      if (typeof globalThis !== "undefined" && globalThis.__PEERPIGEON_UNSEA__) {
        unsea2 = globalThis.__PEERPIGEON_UNSEA__;
        console.log("\u2705 Using bundled UnSEA crypto for storage");
        return unsea2;
      }
      const isNode = typeof process !== "undefined" && process.versions && process.versions.node;
      if (isNode) {
        unsea2 = await Promise.resolve().then(() => (init_unsea(), unsea_exports));
        console.log("\u2705 Loaded unsea from npm package (Node.js) for storage");
      } else {
        try {
          unsea2 = await import("https://cdn.jsdelivr.net/npm/unsea@latest/+esm");
          console.log("\u2705 Loaded unsea from jsDelivr CDN for storage");
        } catch (jsDelivrError) {
          console.warn("Failed to load from jsDelivr, trying unpkg:", jsDelivrError);
          try {
            unsea2 = await import("https://unpkg.com/unsea@latest/dist/unsea.esm.js");
            console.log("\u2705 Loaded unsea from unpkg CDN for storage");
          } catch (unpkgError) {
            console.warn("Failed to load from unpkg, trying Skypack:", unpkgError);
            unsea2 = await import("https://cdn.skypack.dev/unsea");
            console.log("\u2705 Loaded unsea from Skypack CDN for storage");
          }
        }
      }
      if (!unsea2) {
        throw new Error("Unsea not found after loading");
      }
      return unsea2;
    } catch (error) {
      console.error("Failed to load unsea for storage:", error);
      throw error;
    }
  }
  var DistributedStorageManager = class extends EventEmitter {
    constructor(mesh) {
      super();
      this.debug = DebugLogger_default.create("DistributedStorageManager");
      this.mesh = mesh;
      this.webDHT = mesh.webDHT;
      this.cryptoManager = mesh.cryptoManager;
      this.unsea = null;
      this.storageKeypair = null;
      this.initializeCrypto();
      this.config = {
        encryptionEnabled: true,
        defaultTTL: null,
        // No expiration by default
        maxValueSize: 1024 * 1024,
        // 1MB default max size
        enableCRDT: true,
        conflictResolution: "last-write-wins",
        // or 'crdt-merge'
        spaceEnforcement: true
        // Enable space-based access control
      };
      this.spaces = {
        PRIVATE: "private",
        // Only owner can read/write, encrypted
        PUBLIC: "public",
        // Anyone can read, only owner can write
        FROZEN: "frozen"
        // Immutable once set, anyone can read
      };
      this.storageMetadata = /* @__PURE__ */ new Map();
      this.accessControl = /* @__PURE__ */ new Map();
      this.crdtStates = /* @__PURE__ */ new Map();
      this.ownedKeys = /* @__PURE__ */ new Set();
      this.spaceOwnership = /* @__PURE__ */ new Map();
      this.keyToSpaceMapping = /* @__PURE__ */ new Map();
      this.enabled = true;
      this.debug.log(`DistributedStorageManager initialized for peer ${this.mesh.peerId?.substring(0, 8)}...`);
    }
    /**
     * Initialize unsea encryption for storage
     * @private
     */
    async initializeCrypto() {
      try {
        this.unsea = await initializeUnsea2();
        if (this.cryptoManager && this.cryptoManager.keypair) {
          this.storageKeypair = this.cryptoManager.keypair;
        } else {
          this.storageKeypair = await this.unsea.generateRandomPair();
        }
        this.debug.log("\u{1F4E6} Storage encryption initialized with unsea");
      } catch (error) {
        this.debug.warn("\u{1F4E6} Failed to initialize storage encryption:", error);
        this.config.encryptionEnabled = false;
      }
    }
    /**
     * Wait for crypto initialization to complete
     * @returns {Promise<void>}
     */
    async waitForCrypto() {
      if (this.unsea && this.storageKeypair) {
        return;
      }
      const timeout = 5e3;
      const start = Date.now();
      while ((!this.unsea || !this.storageKeypair) && Date.now() - start < timeout) {
        await new Promise((resolve) => setTimeout(resolve, 100));
      }
    }
    /**
     * Resolve a key to its actual storage location across spaces
     * @param {string} key - The storage key (base key only)
     * @returns {Object} - {space, baseKey, keyId}
     */
    async resolveKey(key) {
      const baseKey = key;
      const keyId = await this.webDHT.hash(baseKey);
      const mapping = this.keyToSpaceMapping.get(baseKey);
      if (mapping) {
        return {
          space: mapping.space,
          baseKey,
          keyId
        };
      }
      if (this.storageMetadata.has(keyId)) {
        const metadata = this.storageMetadata.get(keyId);
        const space = metadata.space || this.spaces.PRIVATE;
        this.keyToSpaceMapping.set(baseKey, { space, storageKey: baseKey });
        return {
          space,
          baseKey,
          keyId
        };
      }
      const defaultSpace = this.spaces.PRIVATE;
      this.keyToSpaceMapping.set(baseKey, { space: defaultSpace, storageKey: baseKey });
      return {
        space: defaultSpace,
        baseKey,
        keyId
      };
    }
    /**
     * Check if a peer can access a key in a specific space
     * @param {string} space - The storage space
     * @param {string} key - The storage key
     * @param {string} peerId - The peer ID requesting access
     * @param {string} operation - The operation (read, write)
     * @returns {boolean} - Whether access is allowed
     */
    canAccessSpace(space, key, peerId, operation = "read") {
      if (!this.config.spaceEnforcement) {
        return true;
      }
      const spaceKey = `${space}:${key}`;
      const owner = this.spaceOwnership.get(spaceKey);
      switch (space) {
        case this.spaces.PRIVATE:
          return !owner || owner === peerId;
        case this.spaces.PUBLIC:
          if (operation === "read") {
            return true;
          } else {
            return !owner || owner === peerId;
          }
        case this.spaces.FROZEN:
          if (operation === "read") {
            return true;
          } else {
            return !owner || owner === peerId;
          }
        default:
          return false;
      }
    }
    /**
     * Determine appropriate space and access control from options
     * @param {Object} options - Storage options
     * @returns {Object} - {space, isPublic, isImmutable}
     */
    determineSpaceFromOptions(options = {}) {
      if (options.space && Object.values(this.spaces).includes(options.space)) {
        const space = options.space;
        return {
          space,
          isPublic: space !== this.spaces.PRIVATE,
          isImmutable: space === this.spaces.FROZEN
        };
      }
      if (options.isImmutable) {
        return {
          space: this.spaces.FROZEN,
          isPublic: true,
          isImmutable: true
        };
      } else if (options.isPublic) {
        return {
          space: this.spaces.PUBLIC,
          isPublic: true,
          isImmutable: false
        };
      } else {
        return {
          space: this.spaces.PRIVATE,
          isPublic: false,
          isImmutable: false
        };
      }
    }
    /**
     * Find if a base key exists in a different space
     * @param {string} baseKey - The base key to search for
     * @param {string} excludeSpace - The space to exclude from search
     * @returns {string|null} - The space where the key exists, or null if not found
     */
    findKeyInDifferentSpace(baseKey, excludeSpace) {
      for (const metadata of this.storageMetadata.values()) {
        if (metadata.baseKey === baseKey && metadata.space !== excludeSpace) {
          return metadata.space;
        }
      }
      if (this.spaceOwnership.has(baseKey)) {
        const mapping = this.keyToSpaceMapping.get(baseKey);
        if (mapping && mapping.space !== excludeSpace) {
          return mapping.space;
        }
      }
      return null;
    }
    /**
     * Check if the current peer has read access to a key with space awareness
     * @param {string} keyId - The key ID
     * @param {Object} metadata - The metadata object
     * @param {string} space - The storage space
     * @returns {boolean} - Whether access is allowed
     */
    hasReadAccessWithSpace(keyId, metadata, space) {
      const accessControl = this.accessControl.get(keyId);
      if (!accessControl && metadata) {
        this.accessControl.set(keyId, {
          isPublic: metadata.isPublic,
          owner: metadata.owner,
          allowedPeers: new Set(metadata.allowedPeers || []),
          isImmutable: metadata.isImmutable,
          space: metadata.space || space
        });
        return this.hasReadAccessWithSpace(keyId, metadata, space);
      }
      if (!accessControl) {
        return false;
      }
      if (accessControl.owner === this.mesh.peerId) {
        return true;
      }
      switch (space) {
        case this.spaces.PRIVATE:
          return accessControl.allowedPeers.has(this.mesh.peerId);
        case this.spaces.PUBLIC:
        case this.spaces.FROZEN:
          return true;
        default:
          return false;
      }
    }
    /**
     * Store data with encryption and access control
     * @param {string} key - The storage key
     * @param {any} value - The value to store
     * @param {Object} options - Storage options
     * @param {string} options.space - Storage space ('private', 'public', 'frozen')
     * @param {boolean} options.isPublic - Whether data is publicly readable (legacy, use space instead)
     * @param {boolean} options.isImmutable - Whether data is immutable (legacy, use frozen space instead)
     * @param {boolean} options.enableCRDT - Whether to enable CRDT for collaborative editing (default: false)
     * @param {number} options.ttl - Time to live in milliseconds
     * @param {Array<string>} options.allowedPeers - Specific peers allowed to read private data
     * @returns {Promise<boolean>} Success status
     */
    async store(key, value, options = {}) {
      if (!this.enabled) {
        return false;
      }
      if (!this.webDHT) {
        throw new Error("WebDHT not available - ensure it is enabled in mesh configuration");
      }
      if (this.config.encryptionEnabled) {
        await this.waitForCrypto();
      }
      const spaceConfig = this.determineSpaceFromOptions(options);
      const baseKey = key;
      const space = options.space || spaceConfig.space;
      const storageKey = baseKey;
      if (!this.canAccessSpace(space, baseKey, this.mesh.peerId, "write")) {
        throw new Error(`Write access denied for space "${space}" and key "${baseKey}"`);
      }
      const keyId = await this.webDHT.hash(storageKey);
      const timestamp = Date.now();
      this.debug.log(`\u{1F4E6} Storing key: ${storageKey} in ${space} space, keyId: ${keyId.toString(16).substring(0, 8)}...`);
      const serializedValue = JSON.stringify(value);
      if (serializedValue.length > this.config.maxValueSize) {
        throw new Error(`Value size exceeds maximum allowed size of ${this.config.maxValueSize} bytes`);
      }
      const metadata = {
        key: baseKey,
        // Just the base key, no prefixes - space is its own attribute
        baseKey,
        space,
        keyId,
        owner: this.mesh.peerId,
        isPublic: spaceConfig.isPublic,
        isImmutable: spaceConfig.isImmutable,
        enableCRDT: options.enableCRDT || false,
        allowedPeers: options.allowedPeers || [],
        createdAt: timestamp,
        updatedAt: timestamp,
        version: 1,
        ttl: options.ttl || this.config.defaultTTL,
        type: "storage",
        // Mark this as storage data type
        dataType: "distributed-storage"
        // Specific storage system identifier
      };
      this.storageMetadata.set(keyId, metadata);
      this.ownedKeys.add(keyId);
      this.spaceOwnership.set(baseKey, this.mesh.peerId);
      this.keyToSpaceMapping.set(baseKey, { space, storageKey });
      this.accessControl.set(keyId, {
        isPublic: metadata.isPublic,
        owner: metadata.owner,
        allowedPeers: new Set(metadata.allowedPeers),
        isImmutable: metadata.isImmutable,
        space
      });
      if (metadata.enableCRDT) {
        this.crdtStates.set(keyId, {
          vectorClock: { [this.mesh.peerId]: 1 },
          operations: [],
          lastMerged: timestamp
        });
      }
      let storagePayload = {
        value,
        metadata,
        encrypted: false
      };
      if (space === this.spaces.PRIVATE && this.config.encryptionEnabled && this.unsea && this.storageKeypair) {
        try {
          const encryptedValue = await this.unsea.encryptMessageWithMeta(serializedValue, this.storageKeypair);
          storagePayload = {
            value: encryptedValue,
            metadata,
            encrypted: true,
            encryptedBy: this.mesh.peerId
            // Track who encrypted it
          };
          this.debug.log(`\u{1F4E6} Encrypted private space storage data for key: ${storageKey} (owner-only access)`);
        } catch (error) {
          this.debug.warn(`Failed to encrypt private space storage data for key ${storageKey}:`, error);
        }
      } else {
        this.debug.log(`\u{1F4E6} Storing ${space} space data for key: ${storageKey}`);
      }
      this.debug.log(`\u{1F4E6} STORING PAYLOAD STRUCTURE for key ${storageKey}:`, {
        hasValue: !!storagePayload.value,
        hasMetadata: !!storagePayload.metadata,
        valueType: typeof storagePayload.value,
        metadataType: typeof storagePayload.metadata,
        metadataKeys: storagePayload.metadata ? Object.keys(storagePayload.metadata) : "none",
        encrypted: storagePayload.encrypted,
        payloadKeys: Object.keys(storagePayload)
      });
      try {
        this.debug.log(`\u{1F4E6} Storing payload for key ${storageKey}:`, {
          hasValue: !!storagePayload.value,
          hasMetadata: !!storagePayload.metadata,
          encrypted: storagePayload.encrypted,
          space
        });
        await this.webDHT.put(storageKey, storagePayload, {
          ttl: metadata.ttl,
          space
          // Pass space for space-aware replication
        });
        this.debug.log(`\u{1F4E6} Stored ${space} space data for key: ${storageKey}`);
        this.emit("dataStored", {
          key: storageKey,
          baseKey,
          space,
          keyId,
          isPublic: metadata.isPublic,
          isImmutable: metadata.isImmutable,
          enableCRDT: metadata.enableCRDT
        });
        return true;
      } catch (error) {
        this.debug.error(`Failed to store data for key ${storageKey}:`, error);
        this.storageMetadata.delete(keyId);
        this.ownedKeys.delete(keyId);
        this.accessControl.delete(keyId);
        this.crdtStates.delete(keyId);
        this.spaceOwnership.delete(baseKey);
        this.keyToSpaceMapping.delete(baseKey);
        throw error;
      }
    }
    /**
     * Retrieve data with access control and decryption
     * @param {string} key - The storage key (base key only, no prefixes)
     * @param {Object} options - Retrieval options
     * @param {string} options.space - Specific space to look in (optional)
     * @param {boolean} options.forceRefresh - Force refresh from network
     * @returns {Promise<any>} The stored value or null if not accessible
     */
    async retrieve(key, options = {}) {
      if (!this.webDHT) {
        throw new Error("WebDHT not available - ensure it is enabled in mesh configuration");
      }
      if (this.config.encryptionEnabled) {
        await this.waitForCrypto();
      }
      const baseKey = key;
      this.debug.log(`\u{1F4E6} Retrieving data for base key: ${baseKey}`);
      try {
        const webDHTPayload = await this.webDHT.get(baseKey, {
          forceRefresh: options.forceRefresh,
          space: options.space
          // Pass space for space-aware replication
        });
        this.debug.log(`\u{1F4E6} Retrieved WebDHT payload for key ${baseKey}:`, {
          payloadExists: !!webDHTPayload,
          payloadType: typeof webDHTPayload,
          keys: webDHTPayload ? Object.keys(webDHTPayload) : "none"
        });
        if (!webDHTPayload || typeof webDHTPayload !== "object") {
          this.debug.log(`\u{1F4E6} No data found for key: ${baseKey}`);
          return null;
        }
        return await this.processRetrievedData(baseKey, webDHTPayload);
      } catch (error) {
        this.debug.error(`Failed to retrieve data for key ${baseKey}:`, error);
        return null;
      }
    }
    /**
     * Process retrieved data payload with decryption and access control
     * @private
     */
    async processRetrievedData(baseKey, webDHTPayload) {
      try {
        const keyId = await this.webDHT.hash(baseKey);
        const storagePayload = webDHTPayload;
        console.log(`\u{1F4E6} CRITICAL DEBUG: WebDHT payload structure for ${baseKey}:`, {
          webDHTKeys: Object.keys(webDHTPayload),
          payloadType: typeof webDHTPayload,
          directValue: !!webDHTPayload.value,
          directMetadata: !!webDHTPayload.metadata,
          directEncrypted: webDHTPayload.encrypted
        });
        if (!storagePayload || typeof storagePayload !== "object") {
          console.log(`\u{1F4E6} CRITICAL: Invalid storage payload for key: ${baseKey}`);
          this.debug.log(`\u{1F4E6} Invalid storage payload for key: ${baseKey}`);
          return null;
        }
        const value = storagePayload.value;
        const metadata = storagePayload.metadata;
        const encrypted = storagePayload.encrypted || false;
        const encryptedBy = storagePayload.encryptedBy;
        if (!metadata) {
          this.debug.warn(`\u{1F4E6} Invalid storage payload for key ${baseKey}: missing metadata`);
          return null;
        }
        if (!metadata.type || metadata.type !== "storage") {
          this.debug.log(`\u{1F4E6} Data for key ${baseKey} is not storage data (type: ${metadata.type || "unknown"}) - ignoring`);
          return null;
        }
        const space = metadata.space;
        this.debug.log("\u{1F4E6} Payload components:", {
          hasValue: value !== void 0,
          valueType: typeof value,
          hasMetadata: metadata !== void 0,
          metadataType: typeof metadata,
          encrypted,
          space: space || "unknown",
          encryptedBy: encryptedBy?.substring(0, 8) + "..." || "unknown"
        });
        if (!this.canAccessSpace(space, baseKey, this.mesh.peerId, "read")) {
          this.debug.warn(`\u{1F4E6} Access denied for key: ${baseKey} in space: ${space}`);
          return null;
        }
        if (!this.hasReadAccessWithSpace(keyId, metadata, space)) {
          this.debug.warn(`\u{1F4E6} Access denied for key: ${baseKey} in space: ${space}`);
          return null;
        }
        let finalValue = value;
        if (encrypted && this.unsea && this.storageKeypair) {
          if (encryptedBy && encryptedBy !== this.mesh.peerId) {
            this.debug.warn(`\u{1F4E6} Cannot decrypt data for key ${baseKey}: encrypted by different peer (${encryptedBy.substring(0, 8)}...), current peer: ${this.mesh.peerId.substring(0, 8)}...`);
            if (!metadata.isPublic) {
              this.debug.warn(`Access denied for key: ${baseKey} - private data encrypted by different peer`);
              return null;
            }
          }
          try {
            const decryptedValue = await this.unsea.decryptMessageWithMeta(value, this.storageKeypair.epriv);
            finalValue = JSON.parse(decryptedValue);
            this.debug.log(`\u{1F513} Decrypted storage data for key: ${baseKey}`);
          } catch (error) {
            this.debug.error(`Failed to decrypt data for key ${baseKey}:`, error);
            if (!metadata.isPublic) {
              return null;
            }
            this.debug.warn(`\u{1F4E6} Using raw value for public key ${baseKey} due to decryption failure`);
            finalValue = value;
          }
        }
        if (!this.storageMetadata.has(keyId)) {
          this.storageMetadata.set(keyId, metadata);
          this.accessControl.set(keyId, {
            isPublic: metadata.isPublic,
            owner: metadata.owner,
            allowedPeers: new Set(metadata.allowedPeers),
            isImmutable: metadata.isImmutable,
            space: metadata.space || space
          });
          if (metadata.owner && !this.spaceOwnership.has(baseKey)) {
            this.spaceOwnership.set(baseKey, metadata.owner);
          }
          this.keyToSpaceMapping.set(baseKey, { space, storageKey: baseKey });
        }
        this.debug.log(`\u{1F4E6} Retrieved ${space} space data for key: ${baseKey}`);
        this.emit("dataRetrieved", {
          key: baseKey,
          baseKey,
          space,
          keyId,
          isPublic: metadata.isPublic,
          owner: metadata.owner
        });
        return finalValue;
      } catch (error) {
        this.debug.error(`Failed to retrieve data for key ${baseKey}:`, error);
        return null;
      }
    }
    /**
     * Update existing data (only allowed for owners or if mutable)
     * @param {string} key - The storage key
     * @param {any} newValue - The new value
     * @param {Object} options - Update options
     * @param {boolean} options.forceCRDTMerge - Force CRDT merge even if not owner
     * @returns {Promise<boolean>} Success status
     */
    async update(key, newValue, options = {}) {
      if (!this.webDHT) {
        throw new Error("WebDHT not available - ensure it is enabled in mesh configuration");
      }
      const resolved = await this.resolveKey(key);
      const { baseKey, keyId } = resolved;
      const timestamp = Date.now();
      let accessControl = this.accessControl.get(keyId);
      let metadata = this.storageMetadata.get(keyId);
      if (!accessControl || !metadata) {
        const existingData = await this.retrieve(baseKey);
        if (!existingData) {
          throw new Error(`Key ${key} does not exist or is not accessible`);
        }
        const updatedAccessControl = this.accessControl.get(keyId);
        const updatedMetadata = this.storageMetadata.get(keyId);
        if (!updatedAccessControl || !updatedMetadata) {
          throw new Error(`Key ${key} metadata could not be loaded - unable to update`);
        }
        accessControl = updatedAccessControl;
        metadata = updatedMetadata;
      }
      const isOwner = accessControl.owner === this.mesh.peerId;
      const canUpdate = isOwner || !accessControl.isImmutable || metadata.enableCRDT && options.forceCRDTMerge;
      if (!canUpdate) {
        throw new Error(`Update not allowed for key ${key}: immutable data and not owner`);
      }
      if (metadata.enableCRDT && !isOwner) {
        return this.applyCRDTUpdate(key, keyId, newValue, options);
      }
      metadata.updatedAt = timestamp;
      metadata.version += 1;
      let storagePayload = {
        value: newValue,
        metadata,
        encrypted: false
      };
      if (!metadata.isPublic && this.config.encryptionEnabled && this.unsea && this.storageKeypair) {
        try {
          const serializedValue = JSON.stringify(newValue);
          const encryptedValue = await this.unsea.encryptMessageWithMeta(serializedValue, this.storageKeypair);
          storagePayload = {
            value: encryptedValue,
            metadata,
            encrypted: true,
            encryptedBy: this.mesh.peerId
            // Track who encrypted it
          };
          this.debug.log(`\u{1F4E6} Encrypted updated storage data for key: ${key}`);
        } catch (error) {
          this.debug.warn(`Failed to encrypt updated storage data for key ${key}:`, error);
        }
      }
      try {
        await this.webDHT.update(baseKey, storagePayload, {
          ttl: metadata.ttl
        });
        this.storageMetadata.set(keyId, metadata);
        this.debug.log(`\u{1F4E6} Updated ${metadata.isPublic ? "public" : "private"} data for key: ${baseKey}`);
        this.emit("dataUpdated", {
          key: baseKey,
          keyId,
          isPublic: metadata.isPublic,
          version: metadata.version,
          isOwner
        });
        return true;
      } catch (error) {
        this.debug.error(`Failed to update data for key ${baseKey}:`, error);
        throw error;
      }
    }
    /**
     * Apply CRDT-based update for collaborative editing
     * @private
     */
    async applyCRDTUpdate(key, keyId, operation, options = {}) {
      const crdtState = this.crdtStates.get(keyId);
      const metadata = this.storageMetadata.get(keyId);
      if (!crdtState || !metadata) {
        throw new Error(`CRDT state not found for key ${key}`);
      }
      const currentClock = crdtState.vectorClock[this.mesh.peerId] || 0;
      crdtState.vectorClock[this.mesh.peerId] = currentClock + 1;
      const crdtOperation = {
        peerId: this.mesh.peerId,
        timestamp: Date.now(),
        vectorClock: { ...crdtState.vectorClock },
        operation,
        type: options.operationType || "replace"
      };
      crdtState.operations.push(crdtOperation);
      crdtState.lastMerged = Date.now();
      const mergedValue = this.mergeCRDTOperations(crdtState.operations);
      return this.update(key, mergedValue, { ...options, forceCRDTMerge: false });
    }
    /**
     * Simple CRDT merge implementation (can be extended for more sophisticated CRDTs)
     * @private
     */
    mergeCRDTOperations(operations) {
      const sortedOps = operations.sort((a, b) => a.timestamp - b.timestamp);
      let result = null;
      for (const op of sortedOps) {
        switch (op.type) {
          case "replace":
            result = op.operation;
            break;
          case "merge":
            if (result && typeof result === "object" && typeof op.operation === "object") {
              result = { ...result, ...op.operation };
            } else {
              result = op.operation;
            }
            break;
          default:
            result = op.operation;
        }
      }
      return result;
    }
    /**
     * Delete data (only allowed for owners)
     * @param {string} key - The storage key
     * @returns {Promise<boolean>} Success status
     */
    async delete(key) {
      if (!this.webDHT) {
        throw new Error("WebDHT not available - ensure it is enabled in mesh configuration");
      }
      const resolved = await this.resolveKey(key);
      const { baseKey, keyId } = resolved;
      const accessControl = this.accessControl.get(keyId);
      if (!accessControl || accessControl.owner !== this.mesh.peerId) {
        throw new Error(`Delete not allowed for key ${key}: not owner`);
      }
      try {
        const tombstone = {
          deleted: true,
          deletedAt: Date.now(),
          deletedBy: this.mesh.peerId
        };
        await this.webDHT.update(baseKey, tombstone);
        this.storageMetadata.delete(keyId);
        this.ownedKeys.delete(keyId);
        this.accessControl.delete(keyId);
        this.crdtStates.delete(keyId);
        this.spaceOwnership.delete(baseKey);
        this.keyToSpaceMapping.delete(baseKey);
        this.debug.log(`\u{1F4E6} Deleted data for key: ${baseKey}`);
        this.emit("dataDeleted", { key: baseKey, keyId });
        return true;
      } catch (error) {
        this.debug.error(`Failed to delete data for key ${baseKey}:`, error);
        throw error;
      }
    }
    /**
     * Subscribe to changes for a storage key
     * @param {string} key - The storage key
     * @returns {Promise<any>} Current value or null
     */
    async subscribe(key) {
      if (!this.webDHT) {
        throw new Error("WebDHT not available - ensure it is enabled in mesh configuration");
      }
      const currentValue = await this.webDHT.subscribe(key);
      this.debug.log(`\u{1F4E6} Subscribed to storage key: ${key}`);
      return currentValue;
    }
    /**
     * Unsubscribe from changes for a storage key
     * @param {string} key - The storage key
     */
    async unsubscribe(key) {
      if (!this.webDHT) {
        return;
      }
      await this.webDHT.unsubscribe(key);
      this.debug.log(`\u{1F4E6} Unsubscribed from storage key: ${key}`);
    }
    /**
     * Check if the current peer has read access to a key
     * @private
     */
    hasReadAccess(keyId, metadata) {
      const accessControl = this.accessControl.get(keyId);
      if (!accessControl && metadata) {
        this.accessControl.set(keyId, {
          isPublic: metadata.isPublic,
          owner: metadata.owner,
          allowedPeers: new Set(metadata.allowedPeers || []),
          isImmutable: metadata.isImmutable
        });
        return this.hasReadAccess(keyId, metadata);
      }
      if (!accessControl) {
        return false;
      }
      if (accessControl.owner === this.mesh.peerId) {
        return true;
      }
      if (accessControl.isPublic) {
        return true;
      }
      return accessControl.allowedPeers.has(this.mesh.peerId);
    }
    /**
     * Grant access to a peer for a private key (only owner can do this)
     * @param {string} key - The storage key
     * @param {string} peerId - The peer to grant access to
     * @returns {Promise<boolean>} Success status
     */
    async grantAccess(key, peerId) {
      const resolved = await this.resolveKey(key);
      const { baseKey, keyId } = resolved;
      const accessControl = this.accessControl.get(keyId);
      const metadata = this.storageMetadata.get(keyId);
      if (!accessControl || !metadata || accessControl.owner !== this.mesh.peerId) {
        throw new Error(`Cannot grant access for key ${key}: not owner`);
      }
      if (accessControl.isPublic) {
        throw new Error(`Cannot grant access for key ${key}: already public`);
      }
      accessControl.allowedPeers.add(peerId);
      metadata.allowedPeers.push(peerId);
      metadata.updatedAt = Date.now();
      try {
        const currentPayload = await this.webDHT.get(baseKey);
        if (currentPayload) {
          currentPayload.metadata = metadata;
          await this.webDHT.update(baseKey, currentPayload);
        }
        this.debug.log(`\u{1F4E6} Granted access to peer ${peerId.substring(0, 8)}... for key: ${baseKey}`);
        this.emit("accessGranted", { key: baseKey, keyId, peerId });
        return true;
      } catch (error) {
        accessControl.allowedPeers.delete(peerId);
        metadata.allowedPeers = metadata.allowedPeers.filter((p) => p !== peerId);
        throw error;
      }
    }
    /**
     * Revoke access from a peer for a private key (only owner can do this)
     * @param {string} key - The storage key
     * @param {string} peerId - The peer to revoke access from
     * @returns {Promise<boolean>} Success status
     */
    async revokeAccess(key, peerId) {
      const resolved = await this.resolveKey(key);
      const { baseKey, keyId } = resolved;
      const accessControl = this.accessControl.get(keyId);
      const metadata = this.storageMetadata.get(keyId);
      if (!accessControl || !metadata || accessControl.owner !== this.mesh.peerId) {
        throw new Error(`Cannot revoke access for key ${key}: not owner`);
      }
      accessControl.allowedPeers.delete(peerId);
      metadata.allowedPeers = metadata.allowedPeers.filter((p) => p !== peerId);
      metadata.updatedAt = Date.now();
      try {
        const currentPayload = await this.webDHT.get(baseKey);
        if (currentPayload) {
          currentPayload.metadata = metadata;
          await this.webDHT.update(baseKey, currentPayload);
        }
        this.debug.log(`\u{1F4E6} Revoked access from peer ${peerId.substring(0, 8)}... for key: ${baseKey}`);
        this.emit("accessRevoked", { key: baseKey, keyId, peerId });
        return true;
      } catch (error) {
        accessControl.allowedPeers.add(peerId);
        metadata.allowedPeers.push(peerId);
        throw error;
      }
    }
    /**
     * List all keys owned by this peer
     * @returns {Array<Object>} Array of owned key metadata
     */
    getOwnedKeys() {
      const ownedKeys = [];
      for (const keyId of this.ownedKeys) {
        const metadata = this.storageMetadata.get(keyId);
        if (metadata) {
          ownedKeys.push({
            key: metadata.key,
            keyId,
            isPublic: metadata.isPublic,
            isImmutable: metadata.isImmutable,
            enableCRDT: metadata.enableCRDT,
            createdAt: metadata.createdAt,
            updatedAt: metadata.updatedAt,
            version: metadata.version
          });
        }
      }
      return ownedKeys;
    }
    /**
     * Get statistics about the storage manager
     * @returns {Promise<Object>} Storage statistics
     */
    async getStats() {
      let totalSize = 0;
      let itemCount = 0;
      for (const keyId of this.ownedKeys) {
        const metadata = this.storageMetadata.get(keyId);
        if (metadata) {
          try {
            const value = await this.retrieve(metadata.key);
            if (value !== null) {
              const serializedSize = JSON.stringify(value).length;
              totalSize += serializedSize;
              itemCount++;
            }
          } catch (error) {
          }
        }
      }
      return {
        enabled: this.enabled,
        itemCount,
        totalSize,
        ownedKeys: this.ownedKeys.size,
        totalKeys: this.storageMetadata.size,
        crdtKeys: this.crdtStates.size,
        encryptionEnabled: this.config.encryptionEnabled && !!this.unsea && !!this.storageKeypair,
        maxValueSize: this.config.maxValueSize
      };
    }
    /**
     * Clean up expired data and old CRDT operations
     */
    cleanup() {
      const now = Date.now();
      for (const [keyId, metadata] of this.storageMetadata.entries()) {
        if (metadata.ttl && metadata.createdAt + metadata.ttl < now) {
          this.storageMetadata.delete(keyId);
          this.accessControl.delete(keyId);
          this.ownedKeys.delete(keyId);
          this.debug.log(`\u{1F4E6} Cleaned up expired metadata for key: ${metadata.key}`);
        }
      }
      for (const [keyId, crdtState] of this.crdtStates.entries()) {
        if (crdtState.operations.length > 100) {
          crdtState.operations = crdtState.operations.slice(-100);
          this.debug.log(`\u{1F4E6} Cleaned up old CRDT operations for key: ${keyId.substring(0, 8)}...`);
        }
      }
    }
    /**
     * Backup all owned data to a serializable format
     * @returns {Object} Backup data that can be restored later
     */
    async backup() {
      const backupData = {
        version: "1.0.0",
        timestamp: Date.now(),
        peerId: this.mesh.peerId,
        keys: []
      };
      for (const keyId of this.ownedKeys) {
        const metadata = this.storageMetadata.get(keyId);
        if (metadata) {
          try {
            const value = await this.retrieve(metadata.key);
            if (value !== null) {
              backupData.keys.push({
                key: metadata.key,
                value,
                metadata: {
                  isPublic: metadata.isPublic,
                  isImmutable: metadata.isImmutable,
                  enableCRDT: metadata.enableCRDT,
                  allowedPeers: metadata.allowedPeers,
                  ttl: metadata.ttl
                }
              });
            }
          } catch (error) {
            this.debug.warn(`Failed to backup key ${metadata.key}:`, error);
          }
        }
      }
      this.debug.log(`\u{1F4E6} Created backup with ${backupData.keys.length} keys`);
      return backupData;
    }
    /**
     * Restore data from a backup
     * @param {Object} backupData - Backup data created by backup()
     * @param {Object} options - Restore options
     * @param {boolean} options.overwrite - Whether to overwrite existing keys (default: false)
     * @returns {Promise<Object>} Restore results
     */
    async restore(backupData, options = {}) {
      if (!backupData || !backupData.keys || !Array.isArray(backupData.keys)) {
        throw new Error("Invalid backup data format");
      }
      const results = {
        restored: 0,
        skipped: 0,
        failed: 0,
        errors: []
      };
      for (const keyData of backupData.keys) {
        try {
          const { key, value, metadata } = keyData;
          const existing = await this.retrieve(key);
          if (existing !== null && !options.overwrite) {
            results.skipped++;
            continue;
          }
          await this.store(key, value, metadata);
          results.restored++;
        } catch (error) {
          results.failed++;
          results.errors.push({
            key: keyData.key,
            error: error.message
          });
          this.debug.warn(`Failed to restore key ${keyData.key}:`, error);
        }
      }
      this.debug.log(`\u{1F4E6} Restore complete: ${results.restored} restored, ${results.skipped} skipped, ${results.failed} failed`);
      return results;
    }
    /**
     * List all accessible keys (owned + granted access)
     * @returns {Array<Object>} Array of accessible key metadata
     */
    async listAccessibleKeys() {
      const accessibleKeys = [];
      for (const keyId of this.ownedKeys) {
        const metadata = this.storageMetadata.get(keyId);
        if (metadata) {
          accessibleKeys.push({
            key: metadata.key,
            keyId,
            isPublic: metadata.isPublic,
            isImmutable: metadata.isImmutable,
            enableCRDT: metadata.enableCRDT,
            createdAt: metadata.createdAt,
            updatedAt: metadata.updatedAt,
            version: metadata.version,
            owned: true,
            accessible: true
          });
        }
      }
      for (const [keyId] of this.accessControl.entries()) {
        if (!this.ownedKeys.has(keyId) && this.hasReadAccess(keyId)) {
          const metadata = this.storageMetadata.get(keyId);
          if (metadata) {
            accessibleKeys.push({
              key: metadata.key,
              keyId,
              isPublic: metadata.isPublic,
              isImmutable: metadata.isImmutable,
              enableCRDT: metadata.enableCRDT,
              createdAt: metadata.createdAt,
              updatedAt: metadata.updatedAt,
              version: metadata.version,
              owned: false,
              accessible: true
            });
          }
        }
      }
      return accessibleKeys;
    }
    /**
     * Bulk store multiple key-value pairs
     * @param {Array<Object>} items - Array of {key, value, options} objects
     * @param {Object} globalOptions - Options to apply to all items
     * @returns {Promise<Object>} Results summary
     */
    async bulkStore(items, globalOptions = {}) {
      const results = {
        stored: 0,
        failed: 0,
        errors: []
      };
      const storePromises = items.map(async (item) => {
        try {
          const options = { ...globalOptions, ...item.options };
          await this.store(item.key, item.value, options);
          results.stored++;
        } catch (error) {
          results.failed++;
          results.errors.push({
            key: item.key,
            error: error.message
          });
        }
      });
      await Promise.allSettled(storePromises);
      this.debug.log(`\u{1F4E6} Bulk store complete: ${results.stored} stored, ${results.failed} failed`);
      return results;
    }
    /**
     * Bulk retrieve multiple keys
     * @param {Array<string>} keys - Array of keys to retrieve
     * @param {Object} options - Retrieval options
     * @returns {Promise<Object>} Map of key -> value (null for inaccessible keys)
     */
    async bulkRetrieve(keys, options = {}) {
      const results = {};
      const retrievePromises = keys.map(async (key) => {
        try {
          const value = await this.retrieve(key, options);
          results[key] = value;
        } catch (error) {
          this.debug.warn(`Failed to retrieve key ${key}:`, error);
          results[key] = null;
        }
      });
      await Promise.allSettled(retrievePromises);
      return results;
    }
    /**
     * Search for keys by pattern or metadata
     * @param {Object} criteria - Search criteria
     * @param {string} criteria.keyPattern - Regex pattern to match keys
     * @param {boolean} criteria.isPublic - Filter by public/private
     * @param {boolean} criteria.owned - Filter by ownership
     * @param {string} criteria.owner - Filter by specific owner
     * @returns {Array<Object>} Matching keys
     */
    searchKeys(criteria = {}) {
      const matches = [];
      for (const [keyId, metadata] of this.storageMetadata.entries()) {
        let match = true;
        if (criteria.keyPattern) {
          const regex = new RegExp(criteria.keyPattern);
          if (!regex.test(metadata.key)) {
            match = false;
          }
        }
        if (criteria.isPublic !== void 0 && metadata.isPublic !== criteria.isPublic) {
          match = false;
        }
        if (criteria.owned !== void 0) {
          const isOwned = this.ownedKeys.has(keyId);
          if (isOwned !== criteria.owned) {
            match = false;
          }
        }
        if (criteria.owner && metadata.owner !== criteria.owner) {
          match = false;
        }
        if (match) {
          matches.push({
            key: metadata.key,
            keyId,
            isPublic: metadata.isPublic,
            isImmutable: metadata.isImmutable,
            enableCRDT: metadata.enableCRDT,
            owner: metadata.owner,
            createdAt: metadata.createdAt,
            updatedAt: metadata.updatedAt,
            version: metadata.version,
            owned: this.ownedKeys.has(keyId)
          });
        }
      }
      return matches;
    }
    /**
     * Watch for changes to multiple keys
     * @param {Array<string>} keys - Keys to watch
     * @param {Function} callback - Callback function for changes
     * @returns {Function} Unwatch function
     */
    async watchKeys(keys, callback) {
      const subscriptions = /* @__PURE__ */ new Set();
      for (const key of keys) {
        try {
          await this.subscribe(key);
          subscriptions.add(key);
        } catch (error) {
          this.debug.warn(`Failed to subscribe to key ${key}:`, error);
        }
      }
      const eventHandler = (event) => {
        if (keys.includes(event.key)) {
          callback(event);
        }
      };
      this.addEventListener("dataUpdated", eventHandler);
      this.addEventListener("dataDeleted", eventHandler);
      return async () => {
        for (const key of subscriptions) {
          try {
            await this.unsubscribe(key);
          } catch (error) {
            this.debug.warn(`Failed to unsubscribe from key ${key}:`, error);
          }
        }
        this.removeEventListener("dataUpdated", eventHandler);
        this.removeEventListener("dataDeleted", eventHandler);
      };
    }
    /**
     * Get detailed information about a key including access control
     * @param {string} key - The storage key
     * @returns {Promise<Object|null>} Key information or null if not found
     */
    async getKeyInfo(key) {
      const resolved = await this.resolveKey(key);
      const { baseKey, keyId } = resolved;
      let metadata = this.storageMetadata.get(keyId);
      if (!metadata) {
        try {
          const existingData = await this.retrieve(baseKey);
          if (existingData !== null) {
            metadata = this.storageMetadata.get(keyId);
          }
        } catch (error) {
          this.debug.warn(`Failed to retrieve metadata for key ${key}:`, error);
        }
      }
      if (!metadata) {
        return null;
      }
      return {
        key: metadata.key,
        keyId,
        owner: metadata.owner,
        isPublic: metadata.isPublic,
        isImmutable: metadata.isImmutable,
        enableCRDT: metadata.enableCRDT,
        allowedPeers: metadata.allowedPeers,
        createdAt: metadata.createdAt,
        updatedAt: metadata.updatedAt,
        version: metadata.version,
        ttl: metadata.ttl,
        owned: this.ownedKeys.has(keyId),
        accessible: this.hasReadAccess(keyId, metadata),
        crdtEnabled: this.crdtStates.has(keyId)
      };
    }
    /**
     * Enable the distributed storage layer
     * @returns {Promise<void>}
     */
    async enable() {
      if (!this.webDHT) {
        throw new Error("WebDHT not available - ensure it is enabled in mesh configuration");
      }
      this.enabled = true;
      this.debug.log("\u{1F4E6} Distributed storage enabled");
      this.emit("storageEnabled");
    }
    /**
     * Disable the distributed storage layer
     * @returns {Promise<void>}
     */
    async disable() {
      this.enabled = false;
      this.debug.log("\u{1F4E6} Distributed storage disabled");
      this.emit("storageDisabled");
    }
    /**
     * Check if the distributed storage layer is enabled
     * @returns {boolean} Whether storage is enabled
     */
    isEnabled() {
      return this.enabled;
    }
    /**
     * Clear all stored data owned by this peer
     * @returns {Promise<void>}
     */
    async clear() {
      const ownedKeys = Array.from(this.ownedKeys);
      for (const keyId of ownedKeys) {
        const metadata = this.storageMetadata.get(keyId);
        if (metadata) {
          try {
            await this.delete(metadata.key);
          } catch (error) {
            this.debug.warn(`Failed to delete key ${metadata.key} during clear:`, error);
          }
        }
      }
      this.storageMetadata.clear();
      this.accessControl.clear();
      this.crdtStates.clear();
      this.ownedKeys.clear();
      this.spaceOwnership.clear();
      this.keyToSpaceMapping.clear();
      this.debug.log("\u{1F4E6} All stored data cleared");
      this.emit("storageCleared");
    }
    /**
     * List keys with optional prefix filter
     * @param {string} prefix - Optional prefix to filter keys
     * @returns {Promise<Array<string>>} Array of matching keys
     */
    async listKeys(prefix = "") {
      const keys = [];
      for (const keyId of this.ownedKeys) {
        const metadata = this.storageMetadata.get(keyId);
        if (metadata) {
          const storedKey = metadata.key;
          const baseKey = metadata.baseKey;
          if (storedKey.startsWith(prefix) || baseKey.startsWith(prefix)) {
            keys.push(baseKey);
          }
        }
      }
      return keys.sort();
    }
    /**
     * Bulk delete keys with a given prefix
     * @param {string} prefix - Prefix of keys to delete
     * @returns {Promise<number>} Number of keys deleted
     */
    async bulkDelete(prefix) {
      const keysToDelete = await this.listKeys(prefix);
      let deletedCount = 0;
      for (const key of keysToDelete) {
        try {
          await this.delete(key);
          deletedCount++;
        } catch (error) {
          this.debug.warn(`Failed to delete key ${key} during bulk delete:`, error);
        }
      }
      return deletedCount;
    }
    /**
     * Search for data by key, value, or metadata
     * @param {string} query - Search query
     * @param {string} type - Search type: 'key', 'value', or 'metadata'
     * @returns {Promise<Array<Object>>} Search results
     */
    async search(query, type = "key") {
      const results = [];
      const searchRegex = new RegExp(query, "i");
      for (const keyId of this.ownedKeys) {
        const metadata = this.storageMetadata.get(keyId);
        if (!metadata) continue;
        let match = false;
        if (type === "key" && searchRegex.test(metadata.key)) {
          match = true;
        } else if (type === "value") {
          try {
            const value = await this.retrieve(metadata.key);
            if (value && searchRegex.test(JSON.stringify(value))) {
              match = true;
            }
          } catch (error) {
          }
        } else if (type === "metadata") {
          const metadataStr = JSON.stringify({
            owner: metadata.owner,
            isPublic: metadata.isPublic,
            isImmutable: metadata.isImmutable,
            enableCRDT: metadata.enableCRDT
          });
          if (searchRegex.test(metadataStr)) {
            match = true;
          }
        }
        if (match) {
          try {
            const value = await this.retrieve(metadata.key);
            results.push({
              key: metadata.key,
              value,
              metadata: {
                owner: metadata.owner,
                isPublic: metadata.isPublic,
                isImmutable: metadata.isImmutable,
                enableCRDT: metadata.enableCRDT,
                createdAt: metadata.createdAt,
                updatedAt: metadata.updatedAt
              }
            });
          } catch (error) {
          }
        }
      }
      return results;
    }
    /**
     * Get a lexical interface for GUN-like chaining
     * @returns {Proxy} Lexical interface
     */
    lexical() {
      return createLexicalInterface(this);
    }
    /**
     * Alias for lexical() - more GUN-like naming
     * @returns {Proxy} Lexical interface
     */
    gun() {
      return this.lexical();
    }
  };

  // src/PeerPigeonMesh.js
  var PeerPigeonMesh = class _PeerPigeonMesh extends EventEmitter {
    constructor(options = {}) {
      super();
      this.debug = DebugLogger_default.create("PeerPigeonMesh");
      this.environmentReport = this.validateEnvironment(options);
      this.peerId = null;
      this.providedPeerId = options.peerId || null;
      this.signalingClient = null;
      this.peerDiscovery = null;
      this.networkName = options.networkName || "global";
      this.allowGlobalFallback = options.allowGlobalFallback !== false;
      this.isInFallbackMode = false;
      this.originalNetworkName = this.networkName;
      this.maxPeers = options.maxPeers !== void 0 ? options.maxPeers : 3;
      this.minPeers = options.minPeers !== void 0 ? options.minPeers : 2;
      const defaultFloor = Math.min(3, this.maxPeers ?? 3);
      this.connectivityFloor = options.connectivityFloor !== void 0 ? options.connectivityFloor : defaultFloor;
      this.autoConnect = options.autoConnect !== false;
      this.autoDiscovery = options.autoDiscovery !== false;
      this.evictionStrategy = options.evictionStrategy !== false;
      this.xorRouting = options.xorRouting !== false;
      this.enableWebDHT = options.enableWebDHT !== false;
      this.enableCrypto = options.enableCrypto !== false;
      this.connected = false;
      this.polling = false;
      this.signalingUrl = null;
      this.discoveredPeers = /* @__PURE__ */ new Map();
      this.ongoingKeyExchanges = /* @__PURE__ */ new Set();
      this.emittedPeerKeyEvents = /* @__PURE__ */ new Set();
      this.gossipStreams = /* @__PURE__ */ new Map();
      this.storageManager = new StorageManager(this);
      this.mediaManager = new MediaManager();
      this.connectionManager = new ConnectionManager(this);
      this.evictionManager = new EvictionManager(this, this.connectionManager);
      this.meshOptimizer = new MeshOptimizer(this, this.connectionManager, this.evictionManager);
      this.cleanupManager = new CleanupManager(this);
      this.signalingHandler = new SignalingHandler(this, this.connectionManager);
      this.gossipManager = new GossipManager(this, this.connectionManager);
      this.webDHT = null;
      this.distributedStorage = null;
      this.cryptoManager = null;
      if (this.enableCrypto) {
        this.cryptoManager = new CryptoManager();
      }
      this.setupManagerEventHandlers();
      this.cleanupManager.setupUnloadHandlers();
      this.storageManager.loadSignalingUrlFromStorage();
      this._connectivityEnforcementTimer = null;
    }
    setupManagerEventHandlers() {
      this.connectionManager.addEventListener("peersUpdated", () => {
        this.emit("peersUpdated");
      });
      this.addEventListener("peerDisconnected", (data) => {
        this.debug.log(`Peer ${data.peerId.substring(0, 8)}... disconnected: ${data.reason}`);
        this.emittedPeerKeyEvents.delete(data.peerId);
        this.ongoingKeyExchanges.delete(data.peerId);
      });
      this.gossipManager.addEventListener("messageReceived", (data) => {
        if (data.message && data.message.type === "mesh_signaling") {
          this._handleMeshSignalingMessage(data.message, data.from);
          return;
        }
        if (data.message && (data.message.type === "key_exchange" || data.message.type === "key_exchange_response")) {
          this._handleKeyExchange(data.message, data.from).catch((err) => {
            this.debug.error("Key exchange handling failed:", err);
          });
          return;
        }
        if (data.message && data.message.type === "dht" && this.webDHT) {
          this.webDHT.handleMessage(data.message, data.from);
          return;
        }
        if (data.message && data.message.type === "stream-chunk") {
          this._handleGossipStreamChunk(data.message, data.from);
          return;
        }
        if (data.message && data.message.type === "stream-control") {
          this._handleGossipStreamControl(data.message, data.from);
          return;
        }
        if (data.content && typeof data.content === "string") {
          try {
            const parsedContent = JSON.parse(data.content);
            const filteredTypes = ["signaling-relay", "peer-announce-relay", "bootstrap-keepalive", "client-peer-announcement", "cross-bootstrap-signaling"];
            if (filteredTypes.includes(parsedContent.type)) {
              console.debug(`\u{1F507} MESH FILTER: Blocked filtered message type '${parsedContent.type}' from UI emission`);
              return;
            }
          } catch (e) {
          }
        }
        this.emit("messageReceived", data);
      });
      this.addEventListener("remoteStreamAnnouncement", (data) => {
        this._handleRemoteStreamAnnouncement(data);
      });
      this.mediaManager.addEventListener("localStreamStarted", (data) => {
        this.emit("localStreamStarted", data);
        this.gossipManager.broadcastMessage({
          event: "streamStarted",
          peerId: this.peerId,
          hasVideo: data.hasVideo,
          hasAudio: data.hasAudio,
          timestamp: Date.now()
        }, "mediaEvent").catch((err) => {
          this.debug.error("Failed to broadcast stream started event:", err);
        });
      });
      this.mediaManager.addEventListener("localStreamStopped", () => {
        this.emit("localStreamStopped");
        this.gossipManager.broadcastMessage({
          event: "streamStopped",
          peerId: this.peerId,
          timestamp: Date.now()
        }, "mediaEvent").catch((err) => {
          this.debug.error("Failed to broadcast stream stopped event:", err);
        });
      });
      this.mediaManager.addEventListener("error", (data) => {
        this.emit("mediaError", data);
      });
      this.connectionManager.addEventListener("remoteStream", (data) => {
        this.emit("remoteStream", data);
      });
      if (this.cryptoManager) {
        this.cryptoManager.addEventListener("cryptoReady", (data) => {
          this.emit("cryptoReady", data);
        });
        this.cryptoManager.addEventListener("cryptoError", (data) => {
          this.emit("cryptoError", data);
        });
        this.cryptoManager.addEventListener("peerKeyAdded", (data) => {
          if (!this.emittedPeerKeyEvents.has(data.peerId)) {
            this.emittedPeerKeyEvents.add(data.peerId);
            this.emit("peerKeyAdded", data);
          }
        });
        this.cryptoManager.addEventListener("userAuthenticated", (data) => {
          this.emit("userAuthenticated", data);
        });
      }
    }
    validateEnvironment(options = {}) {
      const report = environmentDetector.getEnvironmentReport();
      const warnings = [];
      const errors = [];
      this.debug.log("\u{1F50D} PeerPigeon Environment Detection:", {
        runtime: `${report.runtime.isBrowser ? "Browser" : ""}${report.runtime.isNodeJS ? "Node.js" : ""}${report.runtime.isWorker ? "Worker" : ""}${report.runtime.isNativeScript ? "NativeScript" : ""}`,
        webrtc: report.capabilities.webrtc,
        websocket: report.capabilities.webSocket,
        browser: report.browser?.name || "N/A",
        nativescript: report.nativescript?.platform || "N/A"
      });
      if (!report.capabilities.webrtc) {
        if (report.runtime.isBrowser) {
          errors.push("WebRTC is not supported in this browser. PeerPigeon requires WebRTC for peer-to-peer connections.");
        } else if (report.runtime.isNodeJS) {
          warnings.push("WebRTC support not detected in Node.js environment. PeerPigeon includes @koush/wrtc for automatic WebRTC support - ensure it is properly installed.");
        } else if (report.runtime.isNativeScript) {
          warnings.push("WebRTC support not detected in NativeScript environment. Consider using a native WebRTC plugin.");
        }
      }
      if (!report.capabilities.webSocket) {
        if (report.runtime.isBrowser) {
          errors.push("WebSocket is not supported in this browser. PeerPigeon requires WebSocket for signaling.");
        } else if (report.runtime.isNodeJS) {
          warnings.push('WebSocket support not detected. Install the "ws" package for WebSocket support in Node.js.');
        } else if (report.runtime.isNativeScript) {
          warnings.push("WebSocket support not detected. Consider using a native WebSocket plugin or polyfill.");
        }
      }
      if ((report.runtime.isBrowser || report.runtime.isNativeScript) && !report.capabilities.localStorage && !report.capabilities.sessionStorage) {
        warnings.push("No storage mechanism available. Peer ID will not persist between sessions.");
      }
      if (!report.capabilities.randomValues) {
        warnings.push("Crypto random values not available. Peer ID generation may be less secure.");
      }
      if (report.runtime.isBrowser && !report.network.online) {
        warnings.push("Browser reports offline status. Mesh networking may not function properly.");
      }
      if (report.runtime.isBrowser) {
        const browser = report.browser;
        if (browser && browser.name === "ie") {
          errors.push("Internet Explorer is not supported. Please use a modern browser.");
        }
        if (typeof location !== "undefined" && location.protocol === "http:" && location.hostname !== "localhost") {
          warnings.push("Running on HTTP in production. Some WebRTC features may be limited. Consider using HTTPS.");
        }
      }
      if (report.runtime.isNativeScript) {
        const nativeScript = report.nativescript;
        if (nativeScript && nativeScript.platform) {
          this.debug.log(`\u{1F52E} Running on NativeScript ${nativeScript.platform} platform`);
          if (nativeScript.platform === "android") {
            warnings.push("Android WebRTC may require network permissions and appropriate security configurations.");
          } else if (nativeScript.platform === "ios" || nativeScript.platform === "visionos") {
            warnings.push("iOS/visionOS WebRTC may require camera/microphone permissions for media features.");
          }
        }
      }
      if (errors.length > 0) {
        const errorMessage = "PeerPigeon environment validation failed:\n" + errors.join("\n");
        this.debug.error(errorMessage);
        if (!options.ignoreEnvironmentErrors) {
          throw new Error(errorMessage);
        }
      }
      if (warnings.length > 0) {
        this.debug.warn("PeerPigeon environment warnings:\n" + warnings.join("\n"));
      }
      this.capabilities = report.capabilities;
      this.runtimeInfo = report.runtime;
      return report;
    }
    async init() {
      try {
        try {
          const webrtcInitialized = await environmentDetector.initWebRTCAsync();
          if (webrtcInitialized) {
            const adapterName = environmentDetector.getPigeonRTC()?.getAdapterName();
            this.debug.log(`\u{1F310} PigeonRTC initialized successfully (${adapterName})`);
          }
        } catch (error) {
          this.debug.warn("PigeonRTC initialization failed:", error.message);
        }
        if (environmentDetector.isBrowser) {
          try {
            const disableLocalhostMedia = typeof window !== "undefined" && window.DISABLE_LOCALHOST_MEDIA_REQUEST;
            const isLocalhost = window.location.hostname === "localhost" || window.location.hostname === "127.0.0.1" || window.location.hostname === "";
            if (!disableLocalhostMedia && isLocalhost && navigator.mediaDevices && navigator.mediaDevices.getUserMedia) {
              this.debug.log("\u{1F3A4} Requesting media permissions for localhost WebRTC (not disabled)...");
              const stream = await navigator.mediaDevices.getUserMedia({
                audio: true,
                video: false
              }).catch((err) => {
                this.debug.warn("Media permission denied - connections may fail on localhost:", err.message);
                return null;
              });
              if (stream) {
                stream.getTracks().forEach((track) => track.stop());
                this.debug.log("\u2705 Media permissions granted - localhost connections will work");
              }
            } else if (disableLocalhostMedia) {
              this.debug.log("\u{1F6AB} Skipping localhost media permission request (DISABLE_LOCALHOST_MEDIA_REQUEST flag set)");
            }
          } catch (error) {
            this.debug.warn("Could not request media permissions:", error.message);
          }
        }
        if (this.providedPeerId) {
          if (_PeerPigeonMesh.validatePeerId(this.providedPeerId)) {
            this.peerId = this.providedPeerId;
            this.debug.log(`Using provided peer ID: ${this.peerId}`);
          } else {
            this.debug.warn(`Invalid peer ID provided: ${this.providedPeerId}. Must be 40-character SHA-1 hex string. Generating new one.`);
            this.peerId = await _PeerPigeonMesh.generatePeerId();
          }
        } else {
          this.peerId = await _PeerPigeonMesh.generatePeerId();
        }
        if (this.enableWebDHT) {
          this.webDHT = new WebDHT(this);
          this.debug.log("WebDHT (low-level DHT) initialized and enabled");
          this.setupWebDHTEventHandlers();
          this.distributedStorage = new DistributedStorageManager(this);
          this.debug.log("DistributedStorageManager (high-level encrypted storage) initialized");
          this.setupDistributedStorageEventHandlers();
        } else {
          this.debug.log("WebDHT disabled by configuration");
        }
        const savedUrl = this.storageManager.loadSignalingUrlFromQuery();
        if (savedUrl) {
          this.signalingUrl = savedUrl;
        }
        this.signalingClient = new SignalingClient(this.peerId, this.maxPeers, this);
        this.setupSignalingHandlers();
        this.peerDiscovery = new PeerDiscovery(this.peerId, {
          autoDiscovery: this.autoDiscovery,
          evictionStrategy: this.evictionStrategy,
          xorRouting: this.xorRouting,
          minPeers: this.minPeers,
          maxPeers: this.maxPeers
        });
        this.setupDiscoveryHandlers();
        this.startConnectivityEnforcement();
        if (this.cryptoManager) {
          try {
            this.debug.log("\u{1F510} Initializing crypto manager with automatic key persistence...");
            const cryptoInitPromise = this.cryptoManager.initWithPeerId(this.peerId);
            const timeoutPromise = new Promise((resolve, reject) => {
              setTimeout(() => reject(new Error("Crypto initialization timeout")), 1e4);
            });
            await Promise.race([cryptoInitPromise, timeoutPromise]);
            this.debug.log("\u{1F510} Crypto manager initialized successfully with persistent keys");
          } catch (error) {
            this.debug.error("Failed to initialize crypto manager:", error);
            this.enableCrypto = false;
            this.cryptoManager = null;
          }
        }
        this.emit("statusChanged", { type: "initialized", peerId: this.peerId });
      } catch (error) {
        this.debug.error("Failed to initialize mesh:", error);
        this.emit("statusChanged", { type: "error", message: `Initialization failed: ${error.message}` });
        throw error;
      }
    }
    setupSignalingHandlers() {
      this.signalingClient.addEventListener("connected", () => {
        this.connected = true;
        this.polling = false;
        this.peerDiscovery.start();
        this.emit("statusChanged", { type: "connected" });
      });
      this.signalingClient.addEventListener("disconnected", () => {
        this.connected = false;
        this.polling = false;
        this.peerDiscovery.stop();
        this.emit("statusChanged", { type: "disconnected" });
      });
      this.signalingClient.addEventListener("signalingMessage", (message) => {
        this.signalingHandler.handleSignalingMessage(message);
      });
      this.signalingClient.addEventListener("statusChanged", (data) => {
        this.emit("statusChanged", data);
      });
    }
    setupDiscoveryHandlers() {
      this.peerDiscovery.addEventListener("peerDiscovered", (data) => {
        this.emit("peerDiscovered", data);
        if (this.isInFallbackMode && this.originalNetworkName !== "global") {
          this._tryReturnToOriginalNetwork();
        }
      });
      this.peerDiscovery.addEventListener("connectToPeer", (data) => {
        this.debug.log(`PeerDiscovery requested connection to: ${data.peerId.substring(0, 8)}...`);
        this.connectionManager.connectToPeer(data.peerId);
      });
      this.peerDiscovery.addEventListener("evictPeer", (data) => {
        this.evictionManager.evictPeer(data.peerId, data.reason);
      });
      this.peerDiscovery.addEventListener("optimizeMesh", () => {
        this.peerDiscovery.optimizeMeshConnections(this.connectionManager.peers);
      });
      this.peerDiscovery.addEventListener("optimizeConnections", (data) => {
        this.meshOptimizer.handleOptimizeConnections(data.unconnectedPeers);
      });
      this.addEventListener("peersUpdated", () => {
        this._checkNetworkHealth();
      });
      this.peerDiscovery.addEventListener("peersUpdated", (data) => {
        this.emit("statusChanged", { type: "info", message: `Cleaned up ${data.removedCount} stale peer(s)` });
        this.emit("peersUpdated");
      });
      this.peerDiscovery.addEventListener("checkCapacity", () => {
        const canAccept = this.connectionManager.canAcceptMorePeers();
        const currentConnectionCount = this.connectionManager.getConnectedPeerCount();
        this.debug.log(`Capacity check: ${canAccept} (${currentConnectionCount}/${this.maxPeers} peers)`);
        this.peerDiscovery._canAcceptMorePeers = canAccept;
        this.peerDiscovery._currentConnectionCount = currentConnectionCount;
      });
      this.peerDiscovery.addEventListener("checkEviction", (data) => {
        const evictPeerId = this.evictionManager.shouldEvictForPeer(data.newPeerId);
        this.debug.log(`Eviction check for ${data.newPeerId.substring(0, 8)}...: ${evictPeerId ? evictPeerId.substring(0, 8) + "..." : "none"}`);
        this.peerDiscovery._shouldEvictForPeer = evictPeerId;
      });
    }
    async connect(signalingUrl) {
      this.signalingUrl = signalingUrl;
      this.storageManager.saveSignalingUrlToStorage(signalingUrl);
      this.polling = false;
      try {
        await this.signalingClient.connect(signalingUrl);
      } catch (error) {
        this.debug.error("Connection failed:", error);
        this.polling = false;
        this.emit("statusChanged", { type: "error", message: `Connection failed: ${error.message}` });
        throw error;
      }
    }
    disconnect() {
      if (this.connected) {
        this.cleanupManager.sendGoodbyeMessage();
      }
      this.connected = false;
      this.polling = false;
      if (this.signalingClient) {
        this.signalingClient.disconnect();
      }
      if (this.peerDiscovery) {
        this.peerDiscovery.cleanup();
      }
      if (this._connectivityEnforcementTimer) {
        clearInterval(this._connectivityEnforcementTimer);
        this._connectivityEnforcementTimer = null;
      }
      this.connectionManager.disconnectAllPeers();
      this.connectionManager.cleanup();
      this.evictionManager.cleanup();
      this.cleanupManager.cleanup();
      this.gossipManager.cleanup();
      this.emit("statusChanged", { type: "disconnected" });
    }
    /**
     * Periodically attempt extra connections for under-connected peers until reaching connectivityFloor.
     */
    startConnectivityEnforcement() {
      if (this._connectivityEnforcementTimer) return;
      const intervalMs = 4e3;
      this._connectivityEnforcementTimer = setInterval(() => {
        if (!this.connected || !this.peerDiscovery) return;
        const connectedCount = this.connectionManager.getConnectedPeerCount();
        if (connectedCount >= this.connectivityFloor) return;
        const discovered = this.peerDiscovery.getDiscoveredPeers().map((p) => p.peerId);
        const notConnected = discovered.filter(
          (pid) => !this.connectionManager.hasPeer(pid) && !this.peerDiscovery.isAttemptingConnection(pid)
        );
        if (notConnected.length === 0) return;
        const prioritized = notConnected.sort((a, b) => {
          const distA = this.peerDiscovery.calculateXorDistance(this.peerId, a);
          const distB = this.peerDiscovery.calculateXorDistance(this.peerId, b);
          return distA < distB ? -1 : 1;
        });
        const needed = this.connectivityFloor - connectedCount;
        const attemptLimit = Math.min(prioritized.length, Math.max(1, needed));
        const batch = prioritized.slice(0, attemptLimit);
        batch.forEach((pid) => {
          this.debug.log(`\u{1F527} CONNECTIVITY FLOOR (${connectedCount}/${this.connectivityFloor}) attempting extra connection to ${pid.substring(0, 8)}...`);
          this.connectionManager.connectToPeer(pid);
        });
      }, intervalMs);
    }
    // Configuration methods
    setMaxPeers(maxPeers) {
      this.maxPeers = Math.max(1, Math.min(50, maxPeers));
      if (this.connectionManager.peers.size > this.maxPeers) {
        this.evictionManager.disconnectExcessPeers();
      }
      return this.maxPeers;
    }
    setMinPeers(minPeers) {
      this.minPeers = Math.max(0, Math.min(49, minPeers));
      if (this.connectionManager.getConnectedPeerCount() < this.minPeers && this.autoDiscovery && this.connected) {
        this.peerDiscovery.optimizeMeshConnections(this.connectionManager.peers);
      }
      return this.minPeers;
    }
    setXorRouting(enabled) {
      this.xorRouting = enabled;
      this.emit("statusChanged", { type: "setting", setting: "xorRouting", value: enabled });
      if (!enabled && this.evictionStrategy) {
        this.emit("statusChanged", { type: "warning", message: "XOR routing disabled - eviction strategy effectiveness reduced" });
      }
    }
    setAutoDiscovery(enabled) {
      this.autoDiscovery = enabled;
      this.emit("statusChanged", { type: "setting", setting: "autoDiscovery", value: enabled });
    }
    setAutoConnect(enabled) {
      this.autoConnect = enabled;
      this.emit("statusChanged", { type: "setting", setting: "autoConnect", value: enabled });
    }
    setEvictionStrategy(enabled) {
      this.evictionStrategy = enabled;
      this.emit("statusChanged", { type: "setting", setting: "evictionStrategy", value: enabled });
    }
    // Network namespace management methods
    setNetworkName(networkName) {
      if (this.connected) {
        throw new Error("Cannot change network name while connected. Disconnect first.");
      }
      this.networkName = networkName || "global";
      this.originalNetworkName = this.networkName;
      this.isInFallbackMode = false;
      this.emit("statusChanged", {
        type: "setting",
        setting: "networkName",
        value: this.networkName
      });
      return this.networkName;
    }
    getNetworkName() {
      return this.networkName;
    }
    getOriginalNetworkName() {
      return this.originalNetworkName;
    }
    isUsingGlobalFallback() {
      return this.isInFallbackMode;
    }
    setAllowGlobalFallback(allow) {
      this.allowGlobalFallback = allow;
      this.emit("statusChanged", {
        type: "setting",
        setting: "allowGlobalFallback",
        value: allow
      });
      if (!allow && this.isInFallbackMode) {
        this._tryReturnToOriginalNetwork();
      }
      return this.allowGlobalFallback;
    }
    async _tryReturnToOriginalNetwork() {
      if (!this.isInFallbackMode || this.originalNetworkName === "global") {
        return;
      }
      const originalNetworkPeerCount = await this._getNetworkPeerCount(this.originalNetworkName);
      if (originalNetworkPeerCount > 0) {
        this.debug.log(`Returning from global fallback to original network: ${this.originalNetworkName}`);
        this.networkName = this.originalNetworkName;
        this.isInFallbackMode = false;
        this.emit("statusChanged", {
          type: "network",
          message: `Returned to network: ${this.networkName}`,
          networkName: this.networkName,
          fallbackMode: false
        });
        if (this.connected) {
          this.disconnect();
          setTimeout(() => {
            if (this.signalingUrl) {
              this.connect(this.signalingUrl);
            }
          }, 1e3);
        }
      }
    }
    async _activateGlobalFallback() {
      if (this.originalNetworkName === "global" || this.isInFallbackMode || !this.allowGlobalFallback) {
        return false;
      }
      this.debug.log(`Activating global fallback from network: ${this.originalNetworkName}`);
      this.networkName = "global";
      this.isInFallbackMode = true;
      this.emit("statusChanged", {
        type: "network",
        message: `Fallback to global network from: ${this.originalNetworkName}`,
        networkName: this.networkName,
        originalNetwork: this.originalNetworkName,
        fallbackMode: true
      });
      return true;
    }
    async _getNetworkPeerCount(networkName) {
      return 0;
    }
    _checkNetworkHealth() {
      return;
      if (this.originalNetworkName === "global" || !this.allowGlobalFallback) {
        return;
      }
      const connectedCount = this.connectionManager.getConnectedPeerCount();
      const discoveredCount = this.discoveredPeers.size;
      if (!this.isInFallbackMode && this.networkName === this.originalNetworkName) {
        if (connectedCount === 0 && discoveredCount === 0) {
          this.debug.log(`Network ${this.originalNetworkName} appears empty, activating global fallback`);
          this._activateGlobalFallback().then((activated) => {
            if (activated && this.connected && this.signalingUrl) {
              this.disconnect();
              setTimeout(() => {
                this.connect(this.signalingUrl);
              }, 1e3);
            }
          });
        }
      }
    }
    // Status and information methods
    getStatus() {
      const connectedCount = this.connectionManager.getConnectedPeerCount();
      const totalCount = this.connectionManager.peers.size;
      return {
        peerId: this.peerId,
        connected: this.connected,
        polling: false,
        // Only WebSocket is supported
        signalingUrl: this.signalingUrl,
        networkName: this.networkName,
        originalNetworkName: this.originalNetworkName,
        isInFallbackMode: this.isInFallbackMode,
        allowGlobalFallback: this.allowGlobalFallback,
        connectedCount,
        totalPeerCount: totalCount,
        // Include total count for debugging
        minPeers: this.minPeers,
        maxPeers: this.maxPeers,
        discoveredCount: this.discoveredPeers.size,
        autoConnect: this.autoConnect,
        autoDiscovery: this.autoDiscovery,
        evictionStrategy: this.evictionStrategy,
        xorRouting: this.xorRouting
      };
    }
    getPeers() {
      return this.connectionManager.getPeers();
    }
    getPeerStatus(peerConnection) {
      return peerConnection.getStatus();
    }
    getDiscoveredPeers() {
      if (!this.peerDiscovery) {
        return [];
      }
      const discoveredPeers = this.peerDiscovery.getDiscoveredPeers();
      return discoveredPeers.map((peer) => {
        const peerConnection = this.connectionManager.getPeer(peer.peerId);
        let isConnected = false;
        if (peerConnection) {
          const status = peerConnection.getStatus();
          isConnected = status === "connected" || status === "channel-connecting";
        }
        return {
          ...peer,
          isConnected
        };
      });
    }
    /**
       * Send a direct message to a specific peer via gossip routing
       * @param {string} targetPeerId - The destination peer's ID
       * @param {string|object} content - The message content
       * @returns {string|null} The message ID if sent, or null on error
       */
    async sendDirectMessage(targetPeerId, content) {
      if (!targetPeerId || typeof targetPeerId !== "string") {
        this.debug.error("Invalid targetPeerId for direct message");
        return null;
      }
      return await this.gossipManager.sendDirectMessage(targetPeerId, content);
    }
    /**
       * Send a broadcast (gossip) message to all peers
       * @param {string|object} content - The message content
       * @returns {string|null} The message ID if sent, or null on error
       */
    async sendMessage(content) {
      return await this.gossipManager.broadcastMessage(content, "chat");
    }
    /**
     * Send binary data to a specific peer
     * @param {string} targetPeerId - The destination peer's ID
     * @param {Uint8Array|ArrayBuffer} binaryData - The binary data to send
     * @returns {boolean} True if sent successfully
     */
    async sendBinaryData(targetPeerId, binaryData) {
      if (!targetPeerId || typeof targetPeerId !== "string") {
        this.debug.error("Invalid targetPeerId for binary message");
        return false;
      }
      const peerConnection = this.connectionManager.peers.get(targetPeerId);
      if (!peerConnection) {
        this.debug.error(`No connection to peer ${targetPeerId.substring(0, 8)}...`);
        return false;
      }
      return peerConnection.sendMessage(binaryData);
    }
    /**
     * Broadcast binary data to all connected peers
     * @param {Uint8Array|ArrayBuffer} binaryData - The binary data to broadcast
     * @returns {number} Number of peers the data was sent to
     */
    async broadcastBinaryData(binaryData) {
      const peers = this.getConnectedPeers();
      let sentCount = 0;
      for (const peer of peers) {
        if (await this.sendBinaryData(peer.peerId, binaryData)) {
          sentCount++;
        }
      }
      this.debug.log(`\u{1F4E6} Binary data broadcasted to ${sentCount}/${peers.length} peers`);
      return sentCount;
    }
    /**
     * Create a writable stream to send data to a specific peer
     * @param {string} targetPeerId - The destination peer's ID
     * @param {object} options - Stream options (filename, mimeType, totalSize, etc.)
     * @returns {WritableStream} A writable stream
     */
    createStreamToPeer(targetPeerId, options = {}) {
      if (!targetPeerId || typeof targetPeerId !== "string") {
        throw new Error("Invalid targetPeerId for stream");
      }
      const peerConnection = this.connectionManager.peers.get(targetPeerId);
      if (!peerConnection) {
        throw new Error(`No connection to peer ${targetPeerId.substring(0, 8)}...`);
      }
      return peerConnection.createWritableStream(options);
    }
    /**
     * Send a ReadableStream to a peer
     * @param {string} targetPeerId - The destination peer's ID
     * @param {ReadableStream} readableStream - The stream to send
     * @param {object} options - Stream options (filename, mimeType, etc.)
     * @returns {Promise<void>}
     */
    async sendStream(targetPeerId, readableStream, options = {}) {
      const writableStream = this.createStreamToPeer(targetPeerId, options);
      try {
        await readableStream.pipeTo(writableStream);
        this.debug.log(`\u2705 Stream sent successfully to ${targetPeerId.substring(0, 8)}...`);
      } catch (error) {
        this.debug.error(`\u274C Failed to send stream to ${targetPeerId.substring(0, 8)}...:`, error);
        throw error;
      }
    }
    /**
     * Send a File to a peer using streams
     * @param {string} targetPeerId - The destination peer's ID
     * @param {File} file - The file to send
     * @returns {Promise<void>}
     */
    async sendFile(targetPeerId, file) {
      this.debug.log(`\u{1F4C1} Sending file "${file.name}" (${file.size} bytes) to ${targetPeerId.substring(0, 8)}...`);
      const options = {
        filename: file.name,
        mimeType: file.type,
        totalSize: file.size,
        type: "file"
      };
      const stream = file.stream();
      await this.sendStream(targetPeerId, stream, options);
    }
    /**
     * Send a Blob to a peer using streams
     * @param {string} targetPeerId - The destination peer's ID
     * @param {Blob} blob - The blob to send
     * @param {object} options - Additional options
     * @returns {Promise<void>}
     */
    async sendBlob(targetPeerId, blob, options = {}) {
      this.debug.log(`\u{1F4E6} Sending blob (${blob.size} bytes) to ${targetPeerId.substring(0, 8)}...`);
      const streamOptions = {
        ...options,
        mimeType: blob.type,
        totalSize: blob.size,
        type: "blob"
      };
      const stream = blob.stream();
      await this.sendStream(targetPeerId, stream, streamOptions);
    }
    /**
     * Create a writable stream that broadcasts data to all peers in the mesh using gossip
     * @param {object} options - Stream options (filename, mimeType, totalSize, etc.)
     * @returns {WritableStream} A writable stream that broadcasts via gossip protocol
     */
    createBroadcastStream(options = {}) {
      const streamId = options.streamId || this._generateStreamId();
      const metadata = {
        streamId,
        type: options.type || "broadcast",
        filename: options.filename,
        mimeType: options.mimeType,
        totalSize: options.totalSize,
        timestamp: Date.now(),
        ...options
      };
      const chunks = [];
      let chunkIndex = 0;
      let totalBytesWritten = 0;
      const self2 = this;
      this.debug.log(`\u{1F4E1} Creating gossip broadcast stream: ${streamId}`);
      return new WritableStream({
        async write(chunk) {
          const chunkData = chunk instanceof Uint8Array ? chunk : new Uint8Array(chunk);
          const chunkId = chunkIndex++;
          chunks.push({
            index: chunkId,
            data: chunkData
          });
          totalBytesWritten += chunkData.length;
          try {
            await self2.gossipManager.broadcastMessage({
              streamId,
              chunkIndex: chunkId,
              data: Array.from(chunkData),
              // Convert to regular array for JSON
              totalSize: metadata.totalSize,
              metadata: chunkId === 0 ? metadata : void 0
              // Include metadata with first chunk
            }, "stream-chunk");
            self2.debug.log(`\u{1F4E1} Gossiped chunk ${chunkId} (${chunkData.length} bytes) for stream ${streamId.substring(0, 8)}...`);
          } catch (error) {
            self2.debug.error(`Failed to gossip chunk ${chunkId}:`, error);
            throw error;
          }
        },
        async close() {
          try {
            await self2.gossipManager.broadcastMessage({
              streamId,
              action: "end",
              totalChunks: chunkIndex,
              totalBytes: totalBytesWritten,
              metadata
            }, "stream-control");
            self2.debug.log(`\u{1F4E1} Broadcast stream completed via gossip: ${totalBytesWritten} bytes in ${chunkIndex} chunks`);
            self2.emit("broadcastStreamComplete", {
              streamId,
              totalBytes: totalBytesWritten,
              totalChunks: chunkIndex,
              metadata
            });
          } catch (error) {
            self2.debug.error("Failed to broadcast stream end:", error);
            throw error;
          }
        },
        async abort(reason) {
          try {
            await self2.gossipManager.broadcastMessage({
              streamId,
              action: "abort",
              reason: reason?.message || String(reason),
              metadata
            }, "stream-control");
            self2.debug.log(`\u274C Broadcast stream aborted via gossip: ${reason}`);
            self2.emit("broadcastStreamAborted", {
              streamId,
              reason: reason?.message || String(reason),
              metadata
            });
          } catch (error) {
            self2.debug.error("Failed to broadcast stream abort:", error);
          }
        }
      });
    }
    /**
     * Broadcast a ReadableStream to all connected peers
     * @param {ReadableStream} readableStream - The stream to broadcast
     * @param {object} options - Stream options (filename, mimeType, etc.)
     * @returns {Promise<void>}
     */
    async broadcastStream(readableStream, options = {}) {
      const writableStream = this.createBroadcastStream(options);
      try {
        await readableStream.pipeTo(writableStream);
        this.debug.log(`\u2705 Stream broadcasted successfully to all peers`);
      } catch (error) {
        this.debug.error(`\u274C Failed to broadcast stream:`, error);
        throw error;
      }
    }
    /**
     * Broadcast a File to all connected peers using streams
     * @param {File} file - The file to broadcast
     * @returns {Promise<void>}
     */
    async broadcastFile(file) {
      this.debug.log(`\u{1F4C1} Broadcasting file "${file.name}" (${file.size} bytes) to all peers`);
      const options = {
        filename: file.name,
        mimeType: file.type,
        totalSize: file.size,
        type: "file"
      };
      const stream = file.stream();
      await this.broadcastStream(stream, options);
    }
    /**
     * Broadcast a Blob to all connected peers using streams
     * @param {Blob} blob - The blob to broadcast
     * @param {object} options - Additional options
     * @returns {Promise<void>}
     */
    async broadcastBlob(blob, options = {}) {
      this.debug.log(`\u{1F4E6} Broadcasting blob (${blob.size} bytes) to all peers`);
      const streamOptions = {
        ...options,
        mimeType: blob.type,
        totalSize: blob.size,
        type: "blob"
      };
      const stream = blob.stream();
      await this.broadcastStream(stream, streamOptions);
    }
    /**
     * Generate a unique stream ID
     * @private
     */
    _generateStreamId() {
      return `stream_${Date.now()}_${Math.random().toString(36).substring(2, 11)}`;
    }
    // Helper methods for backward compatibility
    canAcceptMorePeers() {
      return this.connectionManager.canAcceptMorePeers();
    }
    getConnectedPeerCount() {
      return this.connectionManager.getConnectedPeerCount();
    }
    // Expose peers Map for backward compatibility
    get peers() {
      return this.connectionManager.peers;
    }
    // Get peer status method for UI compatibility
    getPeerUIStatus(peer) {
      if (!peer) return "unknown";
      return peer.getStatus ? peer.getStatus() : "unknown";
    }
    // Get connected peer IDs as array for UI compatibility
    getConnectedPeerIds() {
      return this.connectionManager.getPeers().filter((peer) => peer.status === "connected").map((peer) => peer.peerId);
    }
    // Advanced methods
    async cleanupStaleSignalingData() {
      return this.cleanupManager.cleanupStaleSignalingData();
    }
    forceConnectToAllPeers() {
      return this.meshOptimizer.forceConnectToAllPeers();
    }
    // Debugging and maintenance methods
    forceCleanupInvalidPeers() {
      this.debug.log("Force cleaning up peers not in connected state...");
      return this.connectionManager.forceCleanupInvalidPeers();
    }
    cleanupStalePeers() {
      this.debug.log("Manually cleaning up stale peers...");
      return this.connectionManager.cleanupStalePeers();
    }
    getPeerStateSummary() {
      return this.connectionManager.getPeerStateSummary();
    }
    debugConnectivity() {
      return this.meshOptimizer.debugConnectivity();
    }
    // Media management methods
    async initializeMedia() {
      return await this.mediaManager.init();
    }
    async startMedia(options = {}) {
      const { video = false, audio = false, deviceIds = {} } = options;
      try {
        const stream = await this.mediaManager.startLocalStream({ video, audio, deviceIds });
        const connections = this.connectionManager.getAllConnections();
        this.debug.log(`\u{1F4E1} MEDIA START: Applying stream to ${connections.length} connections (with crypto verification)`);
        for (const connection of connections) {
          let shouldShareMedia = true;
          if (this.enableCrypto && this.cryptoManager) {
            const hasKeys = this.cryptoManager.peerKeys && this.cryptoManager.peerKeys.has(connection.peerId);
            if (!hasKeys) {
              this.debug.log(`\uFFFD MEDIA START: Skipping media share with ${connection.peerId.substring(0, 8)}... - no crypto keys established`);
              shouldShareMedia = false;
            } else {
              this.debug.log(`\u{1F512} MEDIA START: Crypto keys verified for ${connection.peerId.substring(0, 8)}... - sharing media`);
            }
          }
          if (shouldShareMedia) {
            this.debug.log(`\u{1F4E1} MEDIA START: Setting stream for peer ${connection.peerId.substring(0, 8)}...`);
            await connection.setLocalStream(stream);
            this.debug.log(`\u2705 MEDIA START: Stream applied to ${connection.peerId.substring(0, 8)}...`);
          }
        }
        return stream;
      } catch (error) {
        this.debug.error("Failed to start media:", error);
        throw error;
      }
    }
    async stopMedia() {
      this.mediaManager.stopLocalStream();
      const connections = this.connectionManager.getAllConnections();
      for (const connection of connections) {
        await connection.setLocalStream(null);
      }
    }
    toggleVideo() {
      return this.mediaManager.toggleVideo();
    }
    toggleAudio() {
      return this.mediaManager.toggleAudio();
    }
    getMediaState() {
      return this.mediaManager.getMediaState();
    }
    getMediaDevices() {
      return this.mediaManager.devices;
    }
    async enumerateMediaDevices() {
      return await this.mediaManager.enumerateDevices();
    }
    getLocalStream() {
      return this.mediaManager.localStream;
    }
    // Get remote streams from all connected peers
    getRemoteStreams() {
      const streams = /* @__PURE__ */ new Map();
      const connections = this.connectionManager.getAllConnections();
      for (const connection of connections) {
        const remoteStream = connection.getRemoteStream();
        if (remoteStream) {
          streams.set(connection.peerId, remoteStream);
        }
      }
      return streams;
    }
    // === SELECTIVE STREAMING CONTROL METHODS ===
    /**
     * Enable streaming to specific peers only (1:1 or 1:many patterns)
     * @param {string|string[]} peerIds - Single peer ID or array of peer IDs to stream to
     * @param {Object} options - Stream options (video, audio, deviceIds)
     * @returns {Promise<MediaStream>} The local stream
     */
    async startSelectiveStream(peerIds, options = {}) {
      const targetPeerIds = Array.isArray(peerIds) ? peerIds : [peerIds];
      this.debug.log(`\u{1F3AF} SELECTIVE STREAM: Starting stream to ${targetPeerIds.length} specific peer(s)`);
      const stream = await this.mediaManager.startLocalStream(options);
      const connections = this.connectionManager.getAllConnections();
      for (const connection of connections) {
        if (targetPeerIds.includes(connection.peerId)) {
          this.debug.log(`\u{1F4E1} SELECTIVE STREAM: Enabling stream for target peer ${connection.peerId.substring(0, 8)}...`);
          if (connection.allowRemoteStreamEmission) {
            connection.allowRemoteStreamEmission();
          }
          await connection.setLocalStream(stream);
        } else {
          this.debug.log(`\u{1F6AB} SELECTIVE STREAM: Blocking stream for non-target peer ${connection.peerId.substring(0, 8)}...`);
          await connection.setLocalStream(null);
          if (connection.blockRemoteStreamEmission) {
            connection.blockRemoteStreamEmission();
          }
        }
      }
      this.emit("selectiveStreamStarted", {
        targetPeerIds,
        stream,
        streamType: targetPeerIds.length === 1 ? "1:1" : "1:many"
      });
      return stream;
    }
    /**
     * Stop selective streaming and return to broadcast mode or stop entirely
     * @param {boolean} returnToBroadcast - If true, switch to broadcast mode; if false, stop streaming entirely
     */
    async stopSelectiveStream(returnToBroadcast = false) {
      this.debug.log(`\u{1F6D1} SELECTIVE STREAM: Stopping selective streaming (broadcast mode: ${returnToBroadcast})`);
      if (returnToBroadcast) {
        await this.enableStreamingForAllPeers();
      } else {
        await this.stopMedia();
      }
      this.emit("selectiveStreamStopped", { returnToBroadcast });
    }
    /**
     * Enable streaming for all connected peers (broadcast mode)
     */
    async enableStreamingForAllPeers() {
      this.debug.log("\u{1F4E1} BROADCAST STREAM: Enabling streaming for ALL connected peers");
      const connections = this.connectionManager.getAllConnections();
      const currentStream = this.mediaManager.localStream;
      for (const connection of connections) {
        this.debug.log(`\u{1F4E1} BROADCAST STREAM: Enabling stream for peer ${connection.peerId.substring(0, 8)}...`);
        if (connection.allowRemoteStreamEmission) {
          connection.allowRemoteStreamEmission();
        }
        if (currentStream) {
          await connection.setLocalStream(currentStream);
        }
      }
      this.emit("broadcastStreamEnabled");
    }
    /**
     * Block streaming to specific peers while maintaining streams to others
     * @param {string|string[]} peerIds - Peer ID(s) to block streaming to
     */
    async blockStreamingToPeers(peerIds) {
      const targetPeerIds = Array.isArray(peerIds) ? peerIds : [peerIds];
      this.debug.log(`\u{1F6AB} BLOCK STREAM: Blocking streaming to ${targetPeerIds.length} peer(s)`);
      for (const peerId of targetPeerIds) {
        const connection = this.connectionManager.getPeer(peerId);
        if (connection) {
          this.debug.log(`\u{1F6AB} BLOCK STREAM: Blocking stream to peer ${peerId.substring(0, 8)}...`);
          await connection.setLocalStream(null);
          if (connection.blockRemoteStreamEmission) {
            connection.blockRemoteStreamEmission();
          }
        }
      }
      this.emit("streamingBlockedToPeers", { blockedPeerIds: targetPeerIds });
    }
    /**
     * Allow streaming to specific peers (unblock them)
     * @param {string|string[]} peerIds - Peer ID(s) to allow streaming to
     */
    async allowStreamingToPeers(peerIds) {
      const targetPeerIds = Array.isArray(peerIds) ? peerIds : [peerIds];
      this.debug.log(`\u2705 ALLOW STREAM: Allowing streaming to ${targetPeerIds.length} peer(s)`);
      const currentStream = this.mediaManager.localStream;
      for (const peerId of targetPeerIds) {
        const connection = this.connectionManager.getPeer(peerId);
        if (connection) {
          this.debug.log(`\u2705 ALLOW STREAM: Allowing stream to peer ${peerId.substring(0, 8)}...`);
          if (connection.allowRemoteStreamEmission) {
            connection.allowRemoteStreamEmission();
          }
          if (currentStream) {
            await connection.setLocalStream(currentStream);
          }
        }
      }
      this.emit("streamingAllowedToPeers", { allowedPeerIds: targetPeerIds });
    }
    /**
     * Get current streaming status for all connected peers
     * @returns {Map} Map of peer IDs to streaming status
     */
    getStreamingStatus() {
      const status = /* @__PURE__ */ new Map();
      const connections = this.connectionManager.getAllConnections();
      for (const connection of connections) {
        const hasLocalStream = connection.getLocalStream() !== null;
        const allowsRemoteStreams = connection.allowRemoteStreams;
        status.set(connection.peerId, {
          sendingStream: hasLocalStream,
          receivingStreams: allowsRemoteStreams,
          streamType: hasLocalStream ? this.isStreamingToAll() ? "broadcast" : "selective" : "none"
        });
      }
      return status;
    }
    /**
     * Check if currently streaming to all connected peers
     * @returns {boolean} True if streaming to all peers
     */
    isStreamingToAll() {
      const connections = this.connectionManager.getAllConnections();
      if (connections.length === 0) return false;
      return connections.every((connection) => connection.getLocalStream() !== null);
    }
    /**
     * Get list of peers currently receiving streams
     * @returns {string[]} Array of peer IDs receiving streams
     */
    getStreamingPeers() {
      const connections = this.connectionManager.getAllConnections();
      return connections.filter((connection) => connection.getLocalStream() !== null).map((connection) => connection.peerId);
    }
    /**
     * Get list of peers currently blocked from streaming
     * @returns {string[]} Array of peer IDs blocked from streaming
     */
    getBlockedStreamingPeers() {
      const connections = this.connectionManager.getAllConnections();
      return connections.filter((connection) => connection.getLocalStream() === null && this.mediaManager.localStream !== null).map((connection) => connection.peerId);
    }
    // Static utility methods
    static validatePeerId(peerId) {
      return typeof peerId === "string" && /^[a-fA-F0-9]{40}$/.test(peerId);
    }
    static async generatePeerId() {
      const array = new Uint8Array(20);
      if (typeof crypto !== "undefined" && crypto.getRandomValues) {
        crypto.getRandomValues(array);
      } else if (typeof process !== "undefined" && process.versions && process.versions.node) {
        try {
          const crypto3 = await import("crypto");
          const randomBytes2 = crypto3.randomBytes(20);
          array.set(randomBytes2);
        } catch (e) {
          console.warn("Could not use Node.js crypto, falling back to Math.random");
          for (let i = 0; i < array.length; i++) {
            array[i] = Math.floor(Math.random() * 256);
          }
        }
      } else {
        console.warn("Secure random values not available, using fallback method");
        for (let i = 0; i < array.length; i++) {
          array[i] = Math.floor(Math.random() * 256);
        }
      }
      return Array.from(array, (byte) => byte.toString(16).padStart(2, "0")).join("");
    }
    // WebDHT methods - Low-level Distributed Hash Table for raw key-value storage
    // Note: For encrypted storage with access control, use this.distributedStorage instead
    /**
       * Store a key-value pair in the distributed hash table (low-level, no encryption)
       * @param {string} key - The key to store
       * @param {any} value - The value to store
       * @param {object} options - Storage options (ttl, etc.)
       * @returns {Promise<boolean>} True if stored successfully
       */
    async dhtPut(key, value, options = {}) {
      if (!this.enableWebDHT) {
        throw new Error("WebDHT is disabled. Enable it by setting enableWebDHT: true in constructor options.");
      }
      if (!this.webDHT) {
        throw new Error("WebDHT not initialized");
      }
      return this.webDHT.put(key, value, options);
    }
    /**
       * Retrieve a value from the distributed hash table (low-level, no encryption)
       * @param {string} key - The key to retrieve
       * @param {object} options - Retrieval options (subscribe, etc.)
       * @returns {Promise<any>} The stored value or null if not found
       */
    async dhtGet(key, options = {}) {
      if (!this.enableWebDHT) {
        throw new Error("WebDHT is disabled. Enable it by setting enableWebDHT: true in constructor options.");
      }
      if (!this.webDHT) {
        throw new Error("WebDHT not initialized");
      }
      return this.webDHT.get(key, options);
    }
    /**
       * Subscribe to changes for a key in the DHT
       * @param {string} key - The key to subscribe to
       * @returns {Promise<any>} The current value
       */
    async dhtSubscribe(key) {
      if (!this.enableWebDHT) {
        throw new Error("WebDHT is disabled. Enable it by setting enableWebDHT: true in constructor options.");
      }
      if (!this.webDHT) {
        throw new Error("WebDHT not initialized");
      }
      return this.webDHT.subscribe(key);
    }
    /**
       * Unsubscribe from changes for a key in the DHT
       * @param {string} key - The key to unsubscribe from
       */
    async dhtUnsubscribe(key) {
      if (!this.enableWebDHT) {
        throw new Error("WebDHT is disabled. Enable it by setting enableWebDHT: true in constructor options.");
      }
      if (!this.webDHT) {
        throw new Error("WebDHT not initialized");
      }
      return this.webDHT.unsubscribe(key);
    }
    /**
       * Update a key's value and notify subscribers
       * @param {string} key - The key to update
       * @param {any} newValue - The new value
       * @param {object} options - Update options
       * @returns {Promise<boolean>} True if updated successfully
       */
    async dhtUpdate(key, newValue, options = {}) {
      if (!this.enableWebDHT) {
        throw new Error("WebDHT is disabled. Enable it by setting enableWebDHT: true in constructor options.");
      }
      if (!this.webDHT) {
        throw new Error("WebDHT not initialized");
      }
      return this.webDHT.update(key, newValue, options);
    }
    /**
       * Get DHT statistics
       * @returns {object} DHT statistics
       */
    getDHTStats() {
      if (!this.enableWebDHT) {
        return { error: "WebDHT is disabled. Enable it by setting enableWebDHT: true in constructor options." };
      }
      if (!this.webDHT) {
        return { error: "WebDHT not initialized" };
      }
      return this.webDHT.getStats();
    }
    /**
       * Check if WebDHT is enabled
       * @returns {boolean} True if WebDHT is enabled
       */
    isDHTEnabled() {
      return this.enableWebDHT;
    }
    /**
       * Setup WebDHT event handlers
       */
    setupWebDHTEventHandlers() {
      if (this.webDHT) {
        this.webDHT.addEventListener("valueChanged", (data) => {
          this.emit("dhtValueChanged", data);
        });
      }
    }
    /**
     * Setup event handlers for DistributedStorageManager
     */
    setupDistributedStorageEventHandlers() {
      if (this.distributedStorage) {
        this.distributedStorage.addEventListener("dataStored", (data) => {
          this.emit("storageDataStored", data);
        });
        this.distributedStorage.addEventListener("dataRetrieved", (data) => {
          this.emit("storageDataRetrieved", data);
        });
        this.distributedStorage.addEventListener("dataUpdated", (data) => {
          this.emit("storageDataUpdated", data);
        });
        this.distributedStorage.addEventListener("dataDeleted", (data) => {
          this.emit("storageDataDeleted", data);
        });
        this.distributedStorage.addEventListener("accessGranted", (data) => {
          this.emit("storageAccessGranted", data);
        });
        this.distributedStorage.addEventListener("accessRevoked", (data) => {
          this.emit("storageAccessRevoked", data);
        });
      }
    }
    /**
       * Connect to a specific peer by ID
       */
    connectToPeer(peerId) {
      if (!peerId || typeof peerId !== "string") {
        throw new Error("Valid peer ID is required");
      }
      if (peerId === this.peerId) {
        throw new Error("Cannot connect to yourself");
      }
      return this.connectionManager.connectToPeer(peerId);
    }
    /**
       * Get the current environment report
       * @returns {object} Complete environment detection report
       */
    getEnvironmentReport() {
      return this.environmentReport;
    }
    /**
       * Get runtime capabilities
       * @returns {object} Capabilities detected during initialization
       */
    getCapabilities() {
      return this.capabilities;
    }
    /**
       * Get runtime information
       * @returns {object} Runtime environment information
       */
    getRuntimeInfo() {
      return this.runtimeInfo;
    }
    /**
       * Check if a specific feature is supported
       * @param {string} feature - The feature to check (e.g., 'webrtc', 'websocket', 'localstorage')
       * @returns {boolean} True if the feature is supported
       */
    hasFeature(feature) {
      return environmentDetector.hasFeature(feature);
    }
    /**
       * Get environment-specific recommendations
       * @returns {object} Recommendations based on current environment
       */
    getEnvironmentRecommendations() {
      const recommendations = [];
      const report = this.environmentReport;
      if (report.runtime.isBrowser) {
        if (!report.network.online) {
          recommendations.push({
            type: "warning",
            message: "Browser is offline. Enable network connectivity for mesh functionality."
          });
        }
        if (typeof location !== "undefined" && location.protocol === "http:" && location.hostname !== "localhost") {
          recommendations.push({
            type: "security",
            message: "Consider using HTTPS for better WebRTC compatibility and security."
          });
        }
        if (report.browser && report.browser.name === "safari") {
          recommendations.push({
            type: "compatibility",
            message: "Safari has some WebRTC limitations. Test thoroughly for production use."
          });
        }
      }
      if (report.runtime.isNodeJS) {
        if (!report.capabilities.webSocket) {
          recommendations.push({
            type: "dependency",
            message: 'Install the "ws" package for WebSocket support: npm install ws'
          });
        }
        if (!report.capabilities.webrtc) {
          recommendations.push({
            type: "dependency",
            message: 'Install "node-webrtc" or similar for WebRTC support in Node.js: npm install node-webrtc'
          });
        }
      }
      if (!report.capabilities.localStorage && !report.capabilities.sessionStorage) {
        recommendations.push({
          type: "feature",
          message: "No persistent storage available. Peer ID will change on restart."
        });
      }
      return {
        environment: report.runtime,
        recommendations
      };
    }
    // ============================================
    // Cryptographic Methods (unsea integration)
    // ============================================
    /**
       * Initialize crypto with user credentials
       * @param {Object} options - Crypto initialization options
       * @param {string} options.alias - User alias for persistent identity
       * @param {string} options.password - User password
       * @returns {Promise<boolean>} True if crypto was initialized successfully
       */
    async initCrypto(options = {}) {
      if (!this.enableCrypto) {
        this.enableCrypto = true;
        this.cryptoManager = new CryptoManager();
      }
      if (!this.cryptoManager) {
        throw new Error("Crypto manager not available");
      }
      try {
        await this.cryptoManager.init(options);
        return true;
      } catch (error) {
        this.debug.error("Failed to initialize crypto:", error);
        throw error;
      }
    }
    /**
       * Get crypto status and information
       * @returns {Object} Crypto status information
       */
    getCryptoStatus() {
      if (!this.cryptoManager) {
        return {
          enabled: false,
          initialized: false,
          error: "Crypto not enabled. Enable with enableCrypto: true in constructor"
        };
      }
      return this.cryptoManager.getStatus();
    }
    /**
       * Enable/disable crypto functionality
       * @param {boolean} enabled - Whether to enable crypto
       */
    setCrypto(enabled) {
      if (enabled && !this.cryptoManager) {
        this.cryptoManager = new CryptoManager();
      }
      this.enableCrypto = enabled;
    }
    /**
       * Encrypt a message without sending it
       * @param {any} content - Message content to encrypt
       * @param {string} peerId - Optional target peer ID for peer-to-peer encryption
       * @param {string} groupId - Optional group ID for group encryption
       * @returns {Promise<Object>} Encrypted message object
       */
    async encryptMessage(content, peerId = null, groupId = null) {
      if (!this.enableCrypto || !this.cryptoManager) {
        throw new Error("Crypto not enabled or initialized");
      }
      try {
        if (groupId) {
          return await this.cryptoManager.encryptForGroup(content, groupId);
        } else if (peerId) {
          return await this.cryptoManager.encryptForPeer(content, peerId);
        } else {
          const ourPeerId = this.peerId;
          const ourKeypair = this.cryptoManager.keypair;
          if (ourKeypair && ourKeypair.pub && ourKeypair.epub) {
            this.cryptoManager.addPeerKey(ourPeerId, {
              pub: ourKeypair.pub,
              epub: ourKeypair.epub
            });
            return await this.cryptoManager.encryptForPeer(content, ourPeerId);
          } else {
            throw new Error("No complete keypair available for encryption (need both pub and epub)");
          }
        }
      } catch (error) {
        this.debug.error("Failed to encrypt message:", error);
        throw error;
      }
    }
    /**
     * Send a signaling message over the mesh (peer-to-peer) instead of the signaling server
     * This allows peers to coordinate without the signaling server after initial connection
     * @param {Object} message - The signaling message
     * @param {string} targetPeerId - Target peer ID (optional, for direct signaling)
     * @returns {Promise<boolean>} Success status
     */
    async sendMeshSignalingMessage(message, targetPeerId = null) {
      if (!this.connected) {
        this.debug.warn("Cannot send mesh signaling message - mesh not connected");
        return false;
      }
      const signalingMessage = {
        type: "mesh_signaling",
        meshSignalingType: message.type,
        data: message.data,
        fromPeerId: this.peerId,
        targetPeerId,
        timestamp: Date.now(),
        messageId: this.generateMessageId()
      };
      this.debug.log(`\u{1F4E1} Sending mesh signaling message: ${message.type} ${targetPeerId ? `to ${targetPeerId.substring(0, 8)}...` : "(broadcast)"}`);
      try {
        if (targetPeerId) {
          return await this.sendDirectMessage(targetPeerId, signalingMessage);
        } else {
          return this.broadcast(signalingMessage);
        }
      } catch (error) {
        this.debug.error("Failed to send mesh signaling message:", error);
        return false;
      }
    }
    /**
     * Handle incoming mesh signaling messages
     * @param {Object} message - The mesh signaling message
     * @param {string} from - Sender peer ID
     * @private
     */
    _handleMeshSignalingMessage(message, from) {
      if (!message.meshSignalingType || !message.data) {
        this.debug.warn("Invalid mesh signaling message format");
        return;
      }
      if (message.targetPeerId && message.targetPeerId !== this.peerId) {
        this.debug.log(`Ignoring mesh signaling message not intended for us (target: ${message.targetPeerId?.substring(0, 8)}...)`);
        return;
      }
      this.debug.log(`\u{1F4E1} Received mesh signaling message: ${message.meshSignalingType} from ${from.substring(0, 8)}...`);
      const reconstitutedMessage = {
        type: message.meshSignalingType,
        data: message.data,
        fromPeerId: from,
        targetPeerId: message.targetPeerId,
        timestamp: message.timestamp,
        messageId: message.messageId,
        viaWebSocket: false,
        // Mark as coming from mesh, not WebSocket
        viaMesh: true
      };
      this.signalingHandler.handleSignalingMessage(reconstitutedMessage);
    }
    /**
     * Handle incoming gossip stream chunk
     * @param {Object} message - The stream chunk message
     * @param {string} from - Sender peer ID
     * @private
     */
    _handleGossipStreamChunk(message, from) {
      const { streamId, chunkIndex, data, metadata } = message;
      if (!streamId || chunkIndex === void 0 || !data) {
        this.debug.error("Invalid gossip stream chunk format");
        return;
      }
      this.debug.log(`\u{1F4E5} Received gossip stream chunk ${chunkIndex} for ${streamId.substring(0, 8)}... from ${from.substring(0, 8)}...`);
      if (!this.gossipStreams.has(streamId)) {
        const chunks = /* @__PURE__ */ new Map();
        let controller;
        const readable = new ReadableStream({
          start(ctrl) {
            controller = ctrl;
          },
          cancel: (reason) => {
            this.debug.log(`\u{1F4E5} Gossip stream ${streamId.substring(0, 8)}... cancelled: ${reason}`);
          }
        });
        this.gossipStreams.set(streamId, {
          chunks,
          metadata: metadata || {},
          controller,
          stream: readable,
          from,
          totalChunks: null
        });
        this.emit("streamReceived", {
          peerId: from,
          streamId,
          stream: readable,
          metadata: metadata || {}
        });
      }
      const streamData = this.gossipStreams.get(streamId);
      streamData.chunks.set(chunkIndex, new Uint8Array(data));
      this._enqueueOrderedChunks(streamId);
    }
    /**
     * Handle incoming gossip stream control message
     * @param {Object} message - The stream control message
     * @param {string} from - Sender peer ID
     * @private
     */
    _handleGossipStreamControl(message, from) {
      const { streamId, action, totalChunks, totalBytes, metadata, reason } = message;
      if (!streamId || !action) {
        this.debug.error("Invalid gossip stream control format");
        return;
      }
      const streamData = this.gossipStreams.get(streamId);
      if (!streamData) {
        this.debug.warn(`Received control message for unknown stream: ${streamId.substring(0, 8)}...`);
        return;
      }
      if (action === "end") {
        this.debug.log(`\u{1F4E5} Gossip stream ${streamId.substring(0, 8)}... ended: ${totalBytes} bytes in ${totalChunks} chunks`);
        streamData.totalChunks = totalChunks;
        this._enqueueOrderedChunks(streamId);
        if (streamData.controller) {
          streamData.controller.close();
        }
        this.emit("streamCompleted", {
          peerId: from,
          streamId,
          totalChunks,
          totalBytes
        });
        setTimeout(() => {
          this.gossipStreams.delete(streamId);
        }, 5e3);
      } else if (action === "abort") {
        this.debug.log(`\u274C Gossip stream ${streamId.substring(0, 8)}... aborted: ${reason}`);
        if (streamData.controller) {
          streamData.controller.error(new Error(reason || "Stream aborted"));
        }
        this.emit("streamAborted", {
          peerId: from,
          streamId,
          reason
        });
        this.gossipStreams.delete(streamId);
      }
    }
    /**
     * Enqueue ordered chunks to the readable stream
     * @param {string} streamId - The stream ID
     * @private
     */
    _enqueueOrderedChunks(streamId) {
      const streamData = this.gossipStreams.get(streamId);
      if (!streamData || !streamData.controller) return;
      const { chunks, controller } = streamData;
      let nextIndex = streamData.nextIndex || 0;
      while (chunks.has(nextIndex)) {
        const chunk = chunks.get(nextIndex);
        try {
          controller.enqueue(chunk);
          chunks.delete(nextIndex);
          nextIndex++;
        } catch (error) {
          this.debug.error(`Failed to enqueue chunk ${nextIndex}:`, error);
          break;
        }
      }
      streamData.nextIndex = nextIndex;
    }
    /**
     * Send a signaling message, using mesh connections for renegotiation
     * @param {Object} message - The signaling message
     * @param {string} targetPeerId - Target peer ID (optional)
     * @returns {Promise<boolean>} Success status
     */
    async sendSignalingMessage(message, targetPeerId = null) {
      const isRenegotiation = message.type === "renegotiation-offer" || message.type === "renegotiation-answer";
      if (isRenegotiation && targetPeerId) {
        this.debug.log(`\u{1F504} MESH RENEGOTIATION: Sending ${message.type} via mesh to ${targetPeerId.substring(0, 8)}...`);
        const peerConnection = this.connectionManager.getPeer(targetPeerId);
        if (peerConnection && peerConnection.sendMessage) {
          const success = peerConnection.sendMessage({
            type: "signaling",
            data: message,
            fromPeerId: this.peerId,
            timestamp: Date.now()
          });
          if (success) {
            this.debug.log(`\u2705 MESH RENEGOTIATION: ${message.type} sent via mesh to ${targetPeerId.substring(0, 8)}...`);
            return true;
          } else {
            this.debug.error(`\u274C MESH RENEGOTIATION: Failed to send ${message.type} via mesh to ${targetPeerId.substring(0, 8)}...`);
          }
        } else {
          this.debug.error(`\u274C MESH RENEGOTIATION: No mesh connection to ${targetPeerId.substring(0, 8)}... for ${message.type}`);
        }
        this.debug.log(`\u{1F504} FALLBACK: Using WebSocket for ${message.type} to ${targetPeerId.substring(0, 8)}...`);
      }
      if (this.signalingClient && this.signalingClient.isConnected()) {
        this.debug.log(`\u{1F4E1} Using WebSocket signaling for ${message.type} to ${targetPeerId?.substring(0, 8) || "broadcast"}`);
        const messageWithTarget = { ...message };
        if (targetPeerId) {
          messageWithTarget.targetPeerId = targetPeerId;
        }
        return await this.signalingClient.sendSignalingMessage(messageWithTarget);
      }
      this.debug.warn(`\u{1F4E1} Cannot send signaling message ${message.type} - WebSocket not connected and mesh failed`);
      return false;
    }
    /**
       * Send an encrypted message to a specific peer
       * @param {string} peerId - Target peer ID
       * @param {any} content - Message content
       * @param {Object} options - Message options
       * @returns {Promise<string|null>} Message ID if sent successfully
       */
    async sendEncryptedMessage(peerId, content, _options = {}) {
      if (!this.enableCrypto || !this.cryptoManager) {
        throw new Error("Crypto not enabled or initialized");
      }
      try {
        const encryptedContent = await this.cryptoManager.encryptForPeer(content, peerId);
        return await this.sendDirectMessage(peerId, encryptedContent);
      } catch (error) {
        this.debug.error("Failed to send encrypted message:", error);
        throw error;
      }
    }
    /**
       * Send an encrypted broadcast message
       * @param {any} content - Message content
       * @param {string} groupId - Optional group ID for group encryption
       * @returns {Promise<string|null>} Message ID if sent successfully
       */
    async sendEncryptedBroadcast(content, groupId = null) {
      if (!this.enableCrypto || !this.cryptoManager) {
        throw new Error("Crypto not enabled or initialized");
      }
      try {
        let encryptedContent;
        if (groupId) {
          encryptedContent = await this.cryptoManager.encryptForGroup(content, groupId);
        } else {
          encryptedContent = {
            encrypted: true,
            broadcast: true,
            data: content,
            from: this.cryptoManager.getPublicKey(),
            timestamp: Date.now()
          };
        }
        return await this.gossipManager.broadcastMessage(encryptedContent, "encrypted");
      } catch (error) {
        this.debug.error("Failed to send encrypted broadcast:", error);
        throw error;
      }
    }
    /**
       * Decrypt a received message
       * @param {Object} encryptedData - Encrypted message data
       * @returns {Promise<any>} Decrypted content
       */
    async decryptMessage(encryptedData) {
      if (!this.enableCrypto || !this.cryptoManager) {
        return encryptedData;
      }
      try {
        if (encryptedData.group) {
          return await this.cryptoManager.decryptFromGroup(encryptedData);
        } else {
          return await this.cryptoManager.decryptFromPeer(encryptedData);
        }
      } catch (error) {
        this.debug.error("Failed to decrypt message:", error);
        throw error;
      }
    }
    /**
       * Exchange public keys with a peer
       * @param {string} peerId - Peer ID to exchange keys with
       */
    async exchangeKeysWithPeer(peerId) {
      if (!this.enableCrypto || !this.cryptoManager) {
        throw new Error("Crypto not enabled or initialized");
      }
      if (this.ongoingKeyExchanges.has(peerId)) {
        this.debug.log(`\u{1F510} Skipping key exchange with ${peerId.substring(0, 8)}... - exchange already in progress`);
        return;
      }
      const hasExistingKey = this.cryptoManager.peerKeys.has(peerId);
      if (hasExistingKey) {
        this.debug.log(`\u{1F510} Skipping key exchange with ${peerId.substring(0, 8)}... - key already exists`);
        return;
      }
      const keypair = this.cryptoManager.keypair;
      if (!keypair || !keypair.pub || !keypair.epub) {
        throw new Error("No complete keypair available (need both pub and epub)");
      }
      this.debug.log(`\u{1F510} Initiating key exchange with ${peerId.substring(0, 8)}...`);
      this.ongoingKeyExchanges.add(peerId);
      try {
        const result = await this.gossipManager.sendDirectMessage(peerId, {
          type: "key_exchange",
          publicKey: {
            pub: keypair.pub,
            epub: keypair.epub
          },
          timestamp: Date.now()
        }, "key_exchange");
        setTimeout(() => {
          this.ongoingKeyExchanges.delete(peerId);
        }, 5e3);
        return result;
      } catch (error) {
        this.ongoingKeyExchanges.delete(peerId);
        throw error;
      }
    }
    /**
       * Add a peer's public key
       * @param {string} peerId - Peer ID
       * @param {string} publicKey - Peer's public key
       * @returns {boolean} True if key was added successfully
       */
    addPeerPublicKey(peerId, publicKey) {
      if (!this.enableCrypto || !this.cryptoManager) {
        return false;
      }
      return this.cryptoManager.addPeerKey(peerId, publicKey);
    }
    /**
       * Generate a group key for encrypted group communications
       * @param {string} groupId - Group identifier
       * @returns {Promise<Object>} Generated group key
       */
    async generateGroupKey(groupId) {
      if (!this.enableCrypto || !this.cryptoManager) {
        throw new Error("Crypto not enabled or initialized");
      }
      return await this.cryptoManager.generateGroupKey(groupId);
    }
    /**
       * Add a group key for encrypted group communications
       * @param {string} groupId - Group identifier
       * @param {Object} groupKey - Group key object
       */
    addGroupKey(groupId, groupKey) {
      if (!this.enableCrypto || !this.cryptoManager) {
        throw new Error("Crypto not enabled or initialized");
      }
      this.cryptoManager.addGroupKey(groupId, groupKey);
    }
    /**
       * Sign data with our private key
       * @param {any} data - Data to sign
       * @returns {Promise<string>} Digital signature
       */
    async signData(data) {
      if (!this.enableCrypto || !this.cryptoManager) {
        throw new Error("Crypto not enabled or initialized");
      }
      return await this.cryptoManager.sign(data);
    }
    /**
       * Verify a signature
       * @param {string} signature - Signature to verify
       * @param {any} data - Original data
       * @param {string} publicKey - Signer's public key
       * @returns {Promise<boolean>} True if signature is valid
       */
    async verifySignature(signature, data, publicKey) {
      if (!this.enableCrypto || !this.cryptoManager) {
        return true;
      }
      return await this.cryptoManager.verify(signature, data, publicKey);
    }
    /**
       * Export our public key for sharing
       * @returns {Object|null} Public key export data
       */
    exportPublicKey() {
      if (!this.enableCrypto || !this.cryptoManager) {
        return null;
      }
      return this.cryptoManager.exportPublicKey();
    }
    /**
       * Run crypto self-tests
       * @returns {Promise<Object>} Test results
       */
    async runCryptoTests() {
      if (!this.enableCrypto || !this.cryptoManager) {
        throw new Error("Crypto not enabled or initialized");
      }
      return await this.cryptoManager.runSelfTest();
    }
    /**
       * Reset crypto state and clear all keys
       */
    resetCrypto() {
      if (this.cryptoManager) {
        this.cryptoManager.reset();
      }
      this.ongoingKeyExchanges.clear();
      this.emittedPeerKeyEvents.clear();
    }
    /**
       * Handle incoming key exchange messages
       * @param {Object} data - Key exchange message data
       * @param {string} from - Sender's peer ID
       * @private
       */
    async _handleKeyExchange(data, from) {
      if ((data.type === "key_exchange" || data.type === "key_exchange_response") && data.publicKey && this.cryptoManager) {
        const keyAdded = this.cryptoManager.addPeerKey(from, data.publicKey);
        if (keyAdded) {
          this.debug.log(`\u{1F510} Stored public key for peer ${from.substring(0, 8)}...`);
          this.ongoingKeyExchanges.delete(from);
        } else {
          this.debug.log(`\u{1F510} Key exchange ignored for peer ${from.substring(0, 8)}... - duplicate key`);
        }
        if (data.type === "key_exchange" && keyAdded) {
          const keypair = this.cryptoManager.keypair;
          if (keypair && keypair.pub && keypair.epub) {
            await this.gossipManager.sendDirectMessage(from, {
              type: "key_exchange_response",
              publicKey: {
                pub: keypair.pub,
                epub: keypair.epub
              },
              timestamp: Date.now()
            }, "key_exchange_response");
          }
        }
      }
    }
    /**
     * Handle remote stream announcements from gossip protocol
     * The actual stream forwarding is handled by ConnectionManager
     * @param {Object} data - Stream announcement data
     * @private
     */
    async _handleRemoteStreamAnnouncement(data) {
      const { peerId, event } = data;
      if (peerId === this.peerId) {
        return;
      }
      this.debug.log(`\u{1F3B5} GOSSIP STREAM: Received stream announcement - ${event} from ${peerId.substring(0, 8)}...`);
      this.emit("remoteStreamAnnouncementReceived", data);
    }
  };

  // src/browser-entry.js
  if (typeof globalThis !== "undefined" && globalThis.AUTOMATED_TEST === void 0) {
    globalThis.AUTOMATED_TEST = typeof window !== "undefined" && window.AUTOMATED_TEST === true ? true : false;
  }
  if (typeof globalThis !== "undefined") {
    globalThis.__PEERPIGEON_UNSEA__ = unsea_exports;
    globalThis.__PEERPIGEON_PIGEONRTC__ = {
      createPigeonRTC,
      PigeonRTC,
      BrowserRTCAdapter,
      RTCAdapter,
      default: createPigeonRTC
    };
  }
  if (typeof window !== "undefined") {
    window.__PEERPIGEON_UNSEA__ = unsea_exports;
    window.__PEERPIGEON_PIGEONRTC__ = {
      createPigeonRTC,
      PigeonRTC,
      BrowserRTCAdapter,
      RTCAdapter,
      default: createPigeonRTC
    };
    window.PeerPigeonMesh = PeerPigeonMesh;
  }
  console.log("\u{1F510} PeerPigeon browser bundle loaded with embedded UnSEA crypto and PigeonRTC");
  return __toCommonJS(browser_entry_exports);
})();
/*! Bundled license information:

unsea/dist/unsea.mjs:
  (*! noble-hashes - MIT License (c) 2022 Paul Miller (paulmillr.com) *)
  (*! noble-curves - MIT License (c) 2022 Paul Miller (paulmillr.com) *)
*/
//# sourceMappingURL=peerpigeon-browser.js.map