function log(...args) {
  console.log.apply(null, ["[webrtc-internal-exporter:override]", ...args]);
}

log("Override RTCPeerConnection.");

class WebrtcInternalExporter {
  peerConnections = new Map();

  url = "";
  enabled = false;
  updateInterval = 2000;
  enabledStats = [];

  constructor() {
    window.addEventListener("message", async (message) => {
      const { event, options } = message.data;
      if (event === "webrtc-internal-exporter:options") {
        log("options updated:", options);
        Object.assign(this, options);
      }
    });

    window.postMessage({ event: "webrtc-internal-exporter:ready" });
    setInterval(() => this.collectAndPostAllStats(), this.updateInterval);
  }

  randomId() {
    return (
      window.crypto?.randomUUID() || (2 ** 64 * Math.random()).toString(16)
    );
  }

  /**
   * @param {RTCPeerConnection} pc
   */
  add(pc) {
    const id = this.randomId();
    pc.iceCandidates = [];
    pc.iceCandidateErrors = [];
    this.peerConnections.set(id, pc);
    pc.addEventListener("connectionstatechange", () => {
      log(`connectionStateChange: ${pc.connectionState}`);
      this.collectAndPostAllStats();

      if (pc.connectionState === "closed") {
        this.peerConnections.delete(id);
      }
    });

    /**
      * @param {RTCPeerConnectionIceErrorEvent} event
      */
    pc.addEventListener("icecandidateerror", (event) => {
      this.peerConnections.get(id).iceCandidateErrors.push({
        timestamp: Date.now(),
        address: event.errorAddress,
        errorCode: event.errorCode,
        errorText: event.errorText,
        port: event.errorPort,
        url: event.url,
      });
    });

    /**
     * @param {RTCPeerConnectionIceEvent} event
     */
    pc.addEventListener("icecandidate", (event) => {
      this.peerConnections.get(id).iceCandidates.push({
        timestamp: Date.now(),
        candidate: event.candidate?.candidate,
        component: event.candidate?.component,
        foundation: event.candidate?.foundation,
        port: event.candidate?.port,
        priority: event.candidate?.priority,
        protocol: event.candidate?.protocol,
        relatedAddress: event.candidate?.relatedAddress,
        relatedPort: event.candidate?.relatedPort,
        sdpMLineIndex: event.candidate?.sdpMLineIndex,
        sdpMid: event.candidate?.sdpMid,
        tcpType: event.candidate?.tcpType,
        type: event.candidate?.type,
        usernameFragment: event.candidate?.usernameFragment,
      });
    });
  }

  async collectAndPostSingleStat(id) {
    const stats = await this.collectStats(id);
    if (Object.keys(stats).length === 0 || !stats) return;

    window.postMessage(
      {
        event: "webrtc-internal-exporter:peer-connection-stats",
        stats: [stats]
      },
      [stats]
    );

    log(`Single stat collected:`, [stats]);
  }

  async collectAndPostAllStats() {
    const stats = [];

    for (const [id] of this.peerConnections) {
      if (this.url && this.enabled) {
        const pcStats = await this.collectStats(id);
        if (Object.keys(pcStats).length === 0 || !pcStats) continue;
        stats.push(pcStats);
      }
    }

    window.postMessage(
      {
        event: "webrtc-internal-exporter:peer-connections-stats",
        data: stats
      },
      stats
    );

    log(`Stats collected:`, stats);
    
    return stats;
  }

  /**
   * @param {string} id
   */
  async collectStats(id) {
    var pc = this.peerConnections.get(id);
    if (!pc) return;

    var completeStats = {};

    if (this.url && this.enabled) {
      try {
        const stats = await pc.getStats();
        const values = [...stats.values()].filter(
          (v) =>
            ["peer-connection", ...this.enabledStats].indexOf(v.type) !== -1
        );

        completeStats = {
          url: window.location.href,
          id,
          connectionState: pc.connectionState,
          iceConnectionState: pc.iceConnectionState,
          iceGatheringState: pc.iceGatheringState,
          signalingState: pc.signalingState,
          iceCandidateErrors: pc.iceCandidateErrors,
          iceCandidates: pc.iceCandidates,
          values,
        };
      } catch (error) {
        log(`collectStats error: ${error.message}`);
      }
    }

    if (pc.connectionState === "closed") {
      this.peerConnections.delete(id);
    }

    return completeStats;
  }
}

const webrtcInternalExporter = new WebrtcInternalExporter();

window.RTCPeerConnection = new Proxy(window.RTCPeerConnection, {
  construct(target, argumentsList) {
    log(`RTCPeerConnection`, argumentsList);

    const pc = new target(...argumentsList);

    webrtcInternalExporter.add(pc);

    return pc;
  },
});
