import {
  setRawRequestBodyStream
} from "./chunk-GKN5RBVR.mjs";
import {
  emitAsync,
  handleRequest,
  isObject,
  isPropertyAccessible
} from "./chunk-R6T7CL5E.mjs";
import {
  FetchResponse,
  INTERNAL_REQUEST_ID_HEADER_NAME,
  Interceptor,
  RequestController,
  createRequestId
} from "./chunk-JXGB54LE.mjs";
import {
  setRawRequest
} from "./chunk-YWNGXXUQ.mjs";

// src/interceptors/ClientRequest/index.ts
import http2 from "http";
import https2 from "https";

// src/interceptors/ClientRequest/MockHttpSocket.ts
import net2 from "net";
import {
  HTTPParser
} from "_http_common";
import { STATUS_CODES, IncomingMessage, ServerResponse } from "http";
import { Readable } from "stream";
import { invariant } from "outvariant";

// src/interceptors/Socket/MockSocket.ts
import net from "net";

// src/interceptors/Socket/utils/normalizeSocketWriteArgs.ts
function normalizeSocketWriteArgs(args) {
  const normalized = [args[0], void 0, void 0];
  if (typeof args[1] === "string") {
    normalized[1] = args[1];
  } else if (typeof args[1] === "function") {
    normalized[2] = args[1];
  }
  if (typeof args[2] === "function") {
    normalized[2] = args[2];
  }
  return normalized;
}

// src/interceptors/Socket/MockSocket.ts
var MockSocket = class extends net.Socket {
  constructor(options) {
    super();
    this.options = options;
    this.connecting = false;
    this.connect();
    this._final = (callback) => {
      callback(null);
    };
  }
  connect() {
    this.connecting = true;
    return this;
  }
  write(...args) {
    const [chunk, encoding, callback] = normalizeSocketWriteArgs(
      args
    );
    this.options.write(chunk, encoding, callback);
    return true;
  }
  end(...args) {
    const [chunk, encoding, callback] = normalizeSocketWriteArgs(
      args
    );
    this.options.write(chunk, encoding, callback);
    return super.end.apply(this, args);
  }
  push(chunk, encoding) {
    this.options.read(chunk, encoding);
    return super.push(chunk, encoding);
  }
};

// src/interceptors/Socket/utils/baseUrlFromConnectionOptions.ts
function baseUrlFromConnectionOptions(options) {
  if ("href" in options) {
    return new URL(options.href);
  }
  const protocol = options.port === 443 ? "https:" : "http:";
  const host = options.host;
  const url = new URL(`${protocol}//${host}`);
  if (options.port) {
    url.port = options.port.toString();
  }
  if (options.path) {
    url.pathname = options.path;
  }
  if (options.auth) {
    const [username, password] = options.auth.split(":");
    url.username = username;
    url.password = password;
  }
  return url;
}

// src/interceptors/ClientRequest/utils/recordRawHeaders.ts
var kRawHeaders = Symbol("kRawHeaders");
var kRestorePatches = Symbol("kRestorePatches");
function recordRawHeader(headers, args, behavior) {
  ensureRawHeadersSymbol(headers, []);
  const rawHeaders = Reflect.get(headers, kRawHeaders);
  if (behavior === "set") {
    for (let index = rawHeaders.length - 1; index >= 0; index--) {
      if (rawHeaders[index][0].toLowerCase() === args[0].toLowerCase()) {
        rawHeaders.splice(index, 1);
      }
    }
  }
  rawHeaders.push(args);
}
function ensureRawHeadersSymbol(headers, rawHeaders) {
  if (Reflect.has(headers, kRawHeaders)) {
    return;
  }
  defineRawHeadersSymbol(headers, rawHeaders);
}
function defineRawHeadersSymbol(headers, rawHeaders) {
  Object.defineProperty(headers, kRawHeaders, {
    value: rawHeaders,
    enumerable: false,
    // Mark the symbol as configurable so its value can be overridden.
    // Overrides happen when merging raw headers from multiple sources.
    // E.g. new Request(new Request(url, { headers }), { headers })
    configurable: true
  });
}
function recordRawFetchHeaders() {
  if (Reflect.get(Headers, kRestorePatches)) {
    return Reflect.get(Headers, kRestorePatches);
  }
  const {
    Headers: OriginalHeaders,
    Request: OriginalRequest,
    Response: OriginalResponse
  } = globalThis;
  const { set, append, delete: headersDeleteMethod } = Headers.prototype;
  Object.defineProperty(Headers, kRestorePatches, {
    value: () => {
      Headers.prototype.set = set;
      Headers.prototype.append = append;
      Headers.prototype.delete = headersDeleteMethod;
      globalThis.Headers = OriginalHeaders;
      globalThis.Request = OriginalRequest;
      globalThis.Response = OriginalResponse;
      Reflect.deleteProperty(Headers, kRestorePatches);
    },
    enumerable: false,
    /**
     * @note Mark this property as configurable
     * so we can delete it using `Reflect.delete` during cleanup.
     */
    configurable: true
  });
  Object.defineProperty(globalThis, "Headers", {
    enumerable: true,
    writable: true,
    value: new Proxy(Headers, {
      construct(target, args, newTarget) {
        const headersInit = args[0] || [];
        if (headersInit instanceof Headers && Reflect.has(headersInit, kRawHeaders)) {
          const headers2 = Reflect.construct(
            target,
            [Reflect.get(headersInit, kRawHeaders)],
            newTarget
          );
          ensureRawHeadersSymbol(headers2, [
            /**
             * @note Spread the retrieved headers to clone them.
             * This prevents multiple Headers instances from pointing
             * at the same internal "rawHeaders" array.
             */
            ...Reflect.get(headersInit, kRawHeaders)
          ]);
          return headers2;
        }
        const headers = Reflect.construct(target, args, newTarget);
        if (!Reflect.has(headers, kRawHeaders)) {
          const rawHeadersInit = Array.isArray(headersInit) ? headersInit : Object.entries(headersInit);
          ensureRawHeadersSymbol(headers, rawHeadersInit);
        }
        return headers;
      }
    })
  });
  Headers.prototype.set = new Proxy(Headers.prototype.set, {
    apply(target, thisArg, args) {
      recordRawHeader(thisArg, args, "set");
      return Reflect.apply(target, thisArg, args);
    }
  });
  Headers.prototype.append = new Proxy(Headers.prototype.append, {
    apply(target, thisArg, args) {
      recordRawHeader(thisArg, args, "append");
      return Reflect.apply(target, thisArg, args);
    }
  });
  Headers.prototype.delete = new Proxy(Headers.prototype.delete, {
    apply(target, thisArg, args) {
      const rawHeaders = Reflect.get(thisArg, kRawHeaders);
      if (rawHeaders) {
        for (let index = rawHeaders.length - 1; index >= 0; index--) {
          if (rawHeaders[index][0].toLowerCase() === args[0].toLowerCase()) {
            rawHeaders.splice(index, 1);
          }
        }
      }
      return Reflect.apply(target, thisArg, args);
    }
  });
  Object.defineProperty(globalThis, "Request", {
    enumerable: true,
    writable: true,
    value: new Proxy(Request, {
      construct(target, args, newTarget) {
        const request = Reflect.construct(target, args, newTarget);
        const inferredRawHeaders = [];
        if (typeof args[0] === "object" && args[0].headers != null) {
          inferredRawHeaders.push(...inferRawHeaders(args[0].headers));
        }
        if (typeof args[1] === "object" && args[1].headers != null) {
          inferredRawHeaders.push(...inferRawHeaders(args[1].headers));
        }
        if (inferredRawHeaders.length > 0) {
          ensureRawHeadersSymbol(request.headers, inferredRawHeaders);
        }
        return request;
      }
    })
  });
  Object.defineProperty(globalThis, "Response", {
    enumerable: true,
    writable: true,
    value: new Proxy(Response, {
      construct(target, args, newTarget) {
        const response = Reflect.construct(target, args, newTarget);
        if (typeof args[1] === "object" && args[1].headers != null) {
          ensureRawHeadersSymbol(
            response.headers,
            inferRawHeaders(args[1].headers)
          );
        }
        return response;
      }
    })
  });
}
function restoreHeadersPrototype() {
  if (!Reflect.get(Headers, kRestorePatches)) {
    return;
  }
  Reflect.get(Headers, kRestorePatches)();
}
function getRawFetchHeaders(headers) {
  if (!Reflect.has(headers, kRawHeaders)) {
    return Array.from(headers.entries());
  }
  const rawHeaders = Reflect.get(headers, kRawHeaders);
  return rawHeaders.length > 0 ? rawHeaders : Array.from(headers.entries());
}
function inferRawHeaders(headers) {
  if (headers instanceof Headers) {
    return Reflect.get(headers, kRawHeaders) || [];
  }
  return Reflect.get(new Headers(headers), kRawHeaders);
}

// src/interceptors/ClientRequest/MockHttpSocket.ts
var kRequestId = Symbol("kRequestId");
var MockHttpSocket = class extends MockSocket {
  constructor(options) {
    super({
      write: (chunk, encoding, callback) => {
        var _a;
        if (this.socketState !== "passthrough") {
          this.writeBuffer.push([chunk, encoding, callback]);
        }
        if (chunk) {
          if (this.socketState === "passthrough") {
            (_a = this.originalSocket) == null ? void 0 : _a.write(chunk, encoding, callback);
          }
          this.requestParser.execute(
            Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk, encoding)
          );
        }
      },
      read: (chunk) => {
        if (chunk !== null) {
          this.responseParser.execute(
            Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk)
          );
        }
      }
    });
    this.requestRawHeadersBuffer = [];
    this.responseRawHeadersBuffer = [];
    this.writeBuffer = [];
    this.socketState = "unknown";
    /**
     * This callback might be called when the request is "slow":
     * - Request headers were fragmented across multiple TCP packages;
     * - Request headers were too large to be processed in a single run
     * (e.g. more than 30 request headers).
     * @note This is called before request start.
     */
    this.onRequestHeaders = (rawHeaders) => {
      this.requestRawHeadersBuffer.push(...rawHeaders);
    };
    this.onRequestStart = (versionMajor, versionMinor, rawHeaders, _, path, __, ___, ____, shouldKeepAlive) => {
      var _a;
      this.shouldKeepAlive = shouldKeepAlive;
      const url = new URL(path || "", this.baseUrl);
      const method = ((_a = this.connectionOptions.method) == null ? void 0 : _a.toUpperCase()) || "GET";
      const headers = FetchResponse.parseRawHeaders([
        ...this.requestRawHeadersBuffer,
        ...rawHeaders || []
      ]);
      this.requestRawHeadersBuffer.length = 0;
      const canHaveBody = method !== "GET" && method !== "HEAD";
      if (url.username || url.password) {
        if (!headers.has("authorization")) {
          headers.set("authorization", `Basic ${url.username}:${url.password}`);
        }
        url.username = "";
        url.password = "";
      }
      this.requestStream = new Readable({
        /**
         * @note Provide the `read()` method so a `Readable` could be
         * used as the actual request body (the stream calls "read()").
         * We control the queue in the onRequestBody/End functions.
         */
        read: () => {
          this.flushWriteBuffer();
        }
      });
      const requestId = createRequestId();
      this.request = new Request(url, {
        method,
        headers,
        credentials: "same-origin",
        // @ts-expect-error Undocumented Fetch property.
        duplex: canHaveBody ? "half" : void 0,
        body: canHaveBody ? Readable.toWeb(this.requestStream) : null
      });
      Reflect.set(this.request, kRequestId, requestId);
      setRawRequest(this.request, Reflect.get(this, "_httpMessage"));
      setRawRequestBodyStream(this.request, this.requestStream);
      if (this.request.headers.has(INTERNAL_REQUEST_ID_HEADER_NAME)) {
        this.passthrough();
        return;
      }
      this.onRequest({
        requestId,
        request: this.request,
        socket: this
      });
    };
    /**
     * This callback might be called when the response is "slow":
     * - Response headers were fragmented across multiple TCP packages;
     * - Response headers were too large to be processed in a single run
     * (e.g. more than 30 response headers).
     * @note This is called before response start.
     */
    this.onResponseHeaders = (rawHeaders) => {
      this.responseRawHeadersBuffer.push(...rawHeaders);
    };
    this.onResponseStart = (versionMajor, versionMinor, rawHeaders, method, url, status, statusText) => {
      const headers = FetchResponse.parseRawHeaders([
        ...this.responseRawHeadersBuffer,
        ...rawHeaders || []
      ]);
      this.responseRawHeadersBuffer.length = 0;
      const response = new FetchResponse(
        /**
         * @note The Fetch API response instance exposed to the consumer
         * is created over the response stream of the HTTP parser. It is NOT
         * related to the Socket instance. This way, you can read response body
         * in response listener while the Socket instance delays the emission
         * of "end" and other events until those response listeners are finished.
         */
        FetchResponse.isResponseWithBody(status) ? Readable.toWeb(
          this.responseStream = new Readable({ read() {
          } })
        ) : null,
        {
          url,
          status,
          statusText,
          headers
        }
      );
      invariant(
        this.request,
        "Failed to handle a response: request does not exist"
      );
      FetchResponse.setUrl(this.request.url, response);
      if (this.request.headers.has(INTERNAL_REQUEST_ID_HEADER_NAME)) {
        return;
      }
      this.responseListenersPromise = this.onResponse({
        response,
        isMockedResponse: this.socketState === "mock",
        requestId: Reflect.get(this.request, kRequestId),
        request: this.request,
        socket: this
      });
    };
    this.connectionOptions = options.connectionOptions;
    this.createConnection = options.createConnection;
    this.onRequest = options.onRequest;
    this.onResponse = options.onResponse;
    this.baseUrl = baseUrlFromConnectionOptions(this.connectionOptions);
    this.requestParser = new HTTPParser();
    this.requestParser.initialize(HTTPParser.REQUEST, {});
    this.requestParser[HTTPParser.kOnHeaders] = this.onRequestHeaders.bind(this);
    this.requestParser[HTTPParser.kOnHeadersComplete] = this.onRequestStart.bind(this);
    this.requestParser[HTTPParser.kOnBody] = this.onRequestBody.bind(this);
    this.requestParser[HTTPParser.kOnMessageComplete] = this.onRequestEnd.bind(this);
    this.responseParser = new HTTPParser();
    this.responseParser.initialize(HTTPParser.RESPONSE, {});
    this.responseParser[HTTPParser.kOnHeaders] = this.onResponseHeaders.bind(this);
    this.responseParser[HTTPParser.kOnHeadersComplete] = this.onResponseStart.bind(this);
    this.responseParser[HTTPParser.kOnBody] = this.onResponseBody.bind(this);
    this.responseParser[HTTPParser.kOnMessageComplete] = this.onResponseEnd.bind(this);
    this.once("finish", () => this.requestParser.free());
    if (this.baseUrl.protocol === "https:") {
      Reflect.set(this, "encrypted", true);
      Reflect.set(this, "authorized", false);
      Reflect.set(this, "getProtocol", () => "TLSv1.3");
      Reflect.set(this, "getSession", () => void 0);
      Reflect.set(this, "isSessionReused", () => false);
      Reflect.set(this, "getCipher", () => ({ name: "AES256-SHA", standardName: "TLS_RSA_WITH_AES_256_CBC_SHA", version: "TLSv1.3" }));
    }
  }
  emit(event, ...args) {
    const emitEvent = super.emit.bind(this, event, ...args);
    if (this.responseListenersPromise) {
      this.responseListenersPromise.finally(emitEvent);
      return this.listenerCount(event) > 0;
    }
    return emitEvent();
  }
  destroy(error) {
    this.responseParser.free();
    if (error) {
      this.emit("error", error);
    }
    return super.destroy(error);
  }
  /**
   * Establish this Socket connection as-is and pipe
   * its data/events through this Socket.
   */
  passthrough() {
    this.socketState = "passthrough";
    if (this.destroyed) {
      return;
    }
    const socket = this.createConnection();
    this.originalSocket = socket;
    if ("_handle" in socket) {
      Object.defineProperty(this, "_handle", {
        value: socket._handle,
        enumerable: true,
        writable: true
      });
    }
    this.once("error", (error) => {
      socket.destroy(error);
    });
    this.address = socket.address.bind(socket);
    let writeArgs;
    let headersWritten = false;
    while (writeArgs = this.writeBuffer.shift()) {
      if (writeArgs !== void 0) {
        if (!headersWritten) {
          const [chunk, encoding, callback] = writeArgs;
          const chunkString = chunk.toString();
          const chunkBeforeRequestHeaders = chunkString.slice(
            0,
            chunkString.indexOf("\r\n") + 2
          );
          const chunkAfterRequestHeaders = chunkString.slice(
            chunk.indexOf("\r\n\r\n")
          );
          const rawRequestHeaders = getRawFetchHeaders(this.request.headers);
          const requestHeadersString = rawRequestHeaders.filter(([name]) => {
            return name.toLowerCase() !== INTERNAL_REQUEST_ID_HEADER_NAME;
          }).map(([name, value]) => `${name}: ${value}`).join("\r\n");
          const headersChunk = `${chunkBeforeRequestHeaders}${requestHeadersString}${chunkAfterRequestHeaders}`;
          socket.write(headersChunk, encoding, callback);
          headersWritten = true;
          continue;
        }
        socket.write(...writeArgs);
      }
    }
    if (Reflect.get(socket, "encrypted")) {
      const tlsProperties = [
        "encrypted",
        "authorized",
        "getProtocol",
        "getSession",
        "isSessionReused",
        "getCipher"
      ];
      tlsProperties.forEach((propertyName) => {
        Object.defineProperty(this, propertyName, {
          enumerable: true,
          get: () => {
            const value = Reflect.get(socket, propertyName);
            return typeof value === "function" ? value.bind(socket) : value;
          }
        });
      });
    }
    socket.on("lookup", (...args) => this.emit("lookup", ...args)).on("connect", () => {
      this.connecting = socket.connecting;
      this.emit("connect");
    }).on("secureConnect", () => this.emit("secureConnect")).on("secure", () => this.emit("secure")).on("session", (session) => this.emit("session", session)).on("ready", () => this.emit("ready")).on("drain", () => this.emit("drain")).on("data", (chunk) => {
      this.push(chunk);
    }).on("error", (error) => {
      Reflect.set(this, "_hadError", Reflect.get(socket, "_hadError"));
      this.emit("error", error);
    }).on("resume", () => this.emit("resume")).on("timeout", () => this.emit("timeout")).on("prefinish", () => this.emit("prefinish")).on("finish", () => this.emit("finish")).on("close", (hadError) => this.emit("close", hadError)).on("end", () => this.emit("end"));
  }
  /**
   * Convert the given Fetch API `Response` instance to an
   * HTTP message and push it to the socket.
   */
  async respondWith(response) {
    var _a, _b, _c;
    if (this.destroyed) {
      return;
    }
    invariant(
      this.socketState !== "mock",
      '[MockHttpSocket] Failed to respond to the "%s %s" request with "%s %s": the request has already been handled',
      (_a = this.request) == null ? void 0 : _a.method,
      (_b = this.request) == null ? void 0 : _b.url,
      response.status,
      response.statusText
    );
    if (isPropertyAccessible(response, "type") && response.type === "error") {
      this.errorWith(new TypeError("Network error"));
      return;
    }
    this.mockConnect();
    this.socketState = "mock";
    this.flushWriteBuffer();
    const serverResponse = new ServerResponse(new IncomingMessage(this));
    serverResponse.assignSocket(
      new MockSocket({
        write: (chunk, encoding, callback) => {
          this.push(chunk, encoding);
          callback == null ? void 0 : callback();
        },
        read() {
        }
      })
    );
    serverResponse.removeHeader("connection");
    serverResponse.removeHeader("date");
    const rawResponseHeaders = getRawFetchHeaders(response.headers);
    serverResponse.writeHead(
      response.status,
      response.statusText || STATUS_CODES[response.status],
      rawResponseHeaders
    );
    this.once("error", () => {
      serverResponse.destroy();
    });
    if (response.body) {
      try {
        const reader = response.body.getReader();
        while (true) {
          const { done, value } = await reader.read();
          if (done) {
            serverResponse.end();
            break;
          }
          serverResponse.write(value);
        }
      } catch (error) {
        if (error instanceof Error) {
          serverResponse.destroy();
          this.destroy();
          return;
        }
        serverResponse.destroy();
        throw error;
      }
    } else {
      serverResponse.end();
    }
    if (!this.shouldKeepAlive) {
      this.emit("readable");
      (_c = this.responseStream) == null ? void 0 : _c.push(null);
      this.push(null);
    }
  }
  /**
   * Close this socket connection with the given error.
   */
  errorWith(error) {
    this.destroy(error);
  }
  mockConnect() {
    this.connecting = false;
    const isIPv6 = net2.isIPv6(this.connectionOptions.hostname) || this.connectionOptions.family === 6;
    const addressInfo = {
      address: isIPv6 ? "::1" : "127.0.0.1",
      family: isIPv6 ? "IPv6" : "IPv4",
      port: this.connectionOptions.port
    };
    this.address = () => addressInfo;
    this.emit(
      "lookup",
      null,
      addressInfo.address,
      addressInfo.family === "IPv6" ? 6 : 4,
      this.connectionOptions.host
    );
    this.emit("connect");
    this.emit("ready");
    if (this.baseUrl.protocol === "https:") {
      this.emit("secure");
      this.emit("secureConnect");
      this.emit(
        "session",
        this.connectionOptions.session || Buffer.from("mock-session-renegotiate")
      );
      this.emit("session", Buffer.from("mock-session-resume"));
    }
  }
  flushWriteBuffer() {
    for (const writeCall of this.writeBuffer) {
      if (typeof writeCall[2] === "function") {
        writeCall[2]();
        writeCall[2] = void 0;
      }
    }
  }
  onRequestBody(chunk) {
    invariant(
      this.requestStream,
      "Failed to write to a request stream: stream does not exist"
    );
    this.requestStream.push(chunk);
  }
  onRequestEnd() {
    if (this.requestStream) {
      this.requestStream.push(null);
    }
  }
  onResponseBody(chunk) {
    invariant(
      this.responseStream,
      "Failed to write to a response stream: stream does not exist"
    );
    this.responseStream.push(chunk);
  }
  onResponseEnd() {
    if (this.responseStream) {
      this.responseStream.push(null);
    }
  }
};

// src/interceptors/ClientRequest/agents.ts
import http from "http";
import https from "https";
var MockAgent = class extends http.Agent {
  constructor(options) {
    super();
    this.customAgent = options.customAgent;
    this.onRequest = options.onRequest;
    this.onResponse = options.onResponse;
  }
  createConnection(options, callback) {
    const createConnection = this.customAgent instanceof http.Agent ? this.customAgent.createConnection : super.createConnection;
    const createConnectionOptions = this.customAgent instanceof http.Agent ? {
      ...options,
      ...this.customAgent.options
    } : options;
    const socket = new MockHttpSocket({
      connectionOptions: options,
      createConnection: createConnection.bind(
        this.customAgent || this,
        createConnectionOptions,
        callback
      ),
      onRequest: this.onRequest.bind(this),
      onResponse: this.onResponse.bind(this)
    });
    return socket;
  }
};
var MockHttpsAgent = class extends https.Agent {
  constructor(options) {
    super();
    this.customAgent = options.customAgent;
    this.onRequest = options.onRequest;
    this.onResponse = options.onResponse;
  }
  createConnection(options, callback) {
    const createConnection = this.customAgent instanceof http.Agent ? this.customAgent.createConnection : super.createConnection;
    const createConnectionOptions = this.customAgent instanceof http.Agent ? {
      ...options,
      ...this.customAgent.options
    } : options;
    const socket = new MockHttpSocket({
      connectionOptions: options,
      createConnection: createConnection.bind(
        this.customAgent || this,
        createConnectionOptions,
        callback
      ),
      onRequest: this.onRequest.bind(this),
      onResponse: this.onResponse.bind(this)
    });
    return socket;
  }
};

// src/interceptors/ClientRequest/utils/normalizeClientRequestArgs.ts
import { urlToHttpOptions } from "url";
import {
  globalAgent as httpGlobalAgent
} from "http";
import {
  globalAgent as httpsGlobalAgent
} from "https";
import {
  URL as URL2,
  parse as parseUrl
} from "url";
import { Logger as Logger3 } from "@open-draft/logger";

// src/utils/getUrlByRequestOptions.ts
import { Agent } from "http";
import { Logger } from "@open-draft/logger";
var logger = new Logger("utils getUrlByRequestOptions");
var DEFAULT_PATH = "/";
var DEFAULT_PROTOCOL = "http:";
var DEFAULT_HOSTNAME = "localhost";
var SSL_PORT = 443;
function getAgent(options) {
  return options.agent instanceof Agent ? options.agent : void 0;
}
function getProtocolByRequestOptions(options) {
  var _a;
  if (options.protocol) {
    return options.protocol;
  }
  const agent = getAgent(options);
  const agentProtocol = agent == null ? void 0 : agent.protocol;
  if (agentProtocol) {
    return agentProtocol;
  }
  const port = getPortByRequestOptions(options);
  const isSecureRequest = options.cert || port === SSL_PORT;
  return isSecureRequest ? "https:" : ((_a = options.uri) == null ? void 0 : _a.protocol) || DEFAULT_PROTOCOL;
}
function getPortByRequestOptions(options) {
  if (options.port) {
    return Number(options.port);
  }
  const agent = getAgent(options);
  if (agent == null ? void 0 : agent.options.port) {
    return Number(agent.options.port);
  }
  if (agent == null ? void 0 : agent.defaultPort) {
    return Number(agent.defaultPort);
  }
  return void 0;
}
function getAuthByRequestOptions(options) {
  if (options.auth) {
    const [username, password] = options.auth.split(":");
    return { username, password };
  }
}
function isRawIPv6Address(host) {
  return host.includes(":") && !host.startsWith("[") && !host.endsWith("]");
}
function getHostname(options) {
  let host = options.hostname || options.host;
  if (host) {
    if (isRawIPv6Address(host)) {
      host = `[${host}]`;
    }
    return new URL(`http://${host}`).hostname;
  }
  return DEFAULT_HOSTNAME;
}
function getUrlByRequestOptions(options) {
  logger.info("request options", options);
  if (options.uri) {
    logger.info(
      'constructing url from explicitly provided "options.uri": %s',
      options.uri
    );
    return new URL(options.uri.href);
  }
  logger.info("figuring out url from request options...");
  const protocol = getProtocolByRequestOptions(options);
  logger.info("protocol", protocol);
  const port = getPortByRequestOptions(options);
  logger.info("port", port);
  const hostname = getHostname(options);
  logger.info("hostname", hostname);
  const path = options.path || DEFAULT_PATH;
  logger.info("path", path);
  const credentials = getAuthByRequestOptions(options);
  logger.info("credentials", credentials);
  const authString = credentials ? `${credentials.username}:${credentials.password}@` : "";
  logger.info("auth string:", authString);
  const portString = typeof port !== "undefined" ? `:${port}` : "";
  const url = new URL(`${protocol}//${hostname}${portString}${path}`);
  url.username = (credentials == null ? void 0 : credentials.username) || "";
  url.password = (credentials == null ? void 0 : credentials.password) || "";
  logger.info("created url:", url);
  return url;
}

// src/utils/cloneObject.ts
import { Logger as Logger2 } from "@open-draft/logger";
var logger2 = new Logger2("cloneObject");
function isPlainObject(obj) {
  var _a;
  logger2.info("is plain object?", obj);
  if (obj == null || !((_a = obj.constructor) == null ? void 0 : _a.name)) {
    logger2.info("given object is undefined, not a plain object...");
    return false;
  }
  logger2.info("checking the object constructor:", obj.constructor.name);
  return obj.constructor.name === "Object";
}
function cloneObject(obj) {
  logger2.info("cloning object:", obj);
  const enumerableProperties = Object.entries(obj).reduce(
    (acc, [key, value]) => {
      logger2.info("analyzing key-value pair:", key, value);
      acc[key] = isPlainObject(value) ? cloneObject(value) : value;
      return acc;
    },
    {}
  );
  return isPlainObject(obj) ? enumerableProperties : Object.assign(Object.getPrototypeOf(obj), enumerableProperties);
}

// src/interceptors/ClientRequest/utils/normalizeClientRequestArgs.ts
var logger3 = new Logger3("http normalizeClientRequestArgs");
function resolveRequestOptions(args, url) {
  if (typeof args[1] === "undefined" || typeof args[1] === "function") {
    logger3.info("request options not provided, deriving from the url", url);
    return urlToHttpOptions(url);
  }
  if (args[1]) {
    logger3.info("has custom RequestOptions!", args[1]);
    const requestOptionsFromUrl = urlToHttpOptions(url);
    logger3.info("derived RequestOptions from the URL:", requestOptionsFromUrl);
    logger3.info("cloning RequestOptions...");
    const clonedRequestOptions = cloneObject(args[1]);
    logger3.info("successfully cloned RequestOptions!", clonedRequestOptions);
    return {
      ...requestOptionsFromUrl,
      ...clonedRequestOptions
    };
  }
  logger3.info("using an empty object as request options");
  return {};
}
function overrideUrlByRequestOptions(url, options) {
  url.host = options.host || url.host;
  url.hostname = options.hostname || url.hostname;
  url.port = options.port ? options.port.toString() : url.port;
  if (options.path) {
    const parsedOptionsPath = parseUrl(options.path, false);
    url.pathname = parsedOptionsPath.pathname || "";
    url.search = parsedOptionsPath.search || "";
  }
  return url;
}
function resolveCallback(args) {
  return typeof args[1] === "function" ? args[1] : args[2];
}
function normalizeClientRequestArgs(defaultProtocol, args) {
  let url;
  let options;
  let callback;
  logger3.info("arguments", args);
  logger3.info("using default protocol:", defaultProtocol);
  if (args.length === 0) {
    const url2 = new URL2("http://localhost");
    const options2 = resolveRequestOptions(args, url2);
    return [url2, options2];
  }
  if (typeof args[0] === "string") {
    logger3.info("first argument is a location string:", args[0]);
    url = new URL2(args[0]);
    logger3.info("created a url:", url);
    const requestOptionsFromUrl = urlToHttpOptions(url);
    logger3.info("request options from url:", requestOptionsFromUrl);
    options = resolveRequestOptions(args, url);
    logger3.info("resolved request options:", options);
    callback = resolveCallback(args);
  } else if (args[0] instanceof URL2) {
    url = args[0];
    logger3.info("first argument is a URL:", url);
    if (typeof args[1] !== "undefined" && isObject(args[1])) {
      url = overrideUrlByRequestOptions(url, args[1]);
    }
    options = resolveRequestOptions(args, url);
    logger3.info("derived request options:", options);
    callback = resolveCallback(args);
  } else if ("hash" in args[0] && !("method" in args[0])) {
    const [legacyUrl] = args;
    logger3.info("first argument is a legacy URL:", legacyUrl);
    if (legacyUrl.hostname === null) {
      logger3.info("given legacy URL is relative (no hostname)");
      return isObject(args[1]) ? normalizeClientRequestArgs(defaultProtocol, [
        { path: legacyUrl.path, ...args[1] },
        args[2]
      ]) : normalizeClientRequestArgs(defaultProtocol, [
        { path: legacyUrl.path },
        args[1]
      ]);
    }
    logger3.info("given legacy url is absolute");
    const resolvedUrl = new URL2(legacyUrl.href);
    return args[1] === void 0 ? normalizeClientRequestArgs(defaultProtocol, [resolvedUrl]) : typeof args[1] === "function" ? normalizeClientRequestArgs(defaultProtocol, [resolvedUrl, args[1]]) : normalizeClientRequestArgs(defaultProtocol, [
      resolvedUrl,
      args[1],
      args[2]
    ]);
  } else if (isObject(args[0])) {
    options = { ...args[0] };
    logger3.info("first argument is RequestOptions:", options);
    options.protocol = options.protocol || defaultProtocol;
    logger3.info("normalized request options:", options);
    url = getUrlByRequestOptions(options);
    logger3.info("created a URL from RequestOptions:", url.href);
    callback = resolveCallback(args);
  } else {
    throw new Error(
      `Failed to construct ClientRequest with these parameters: ${args}`
    );
  }
  options.protocol = options.protocol || url.protocol;
  options.method = options.method || "GET";
  if (!options._defaultAgent) {
    logger3.info(
      'has no default agent, setting the default agent for "%s"',
      options.protocol
    );
    options._defaultAgent = options.protocol === "https:" ? httpsGlobalAgent : httpGlobalAgent;
  }
  logger3.info("successfully resolved url:", url.href);
  logger3.info("successfully resolved options:", options);
  logger3.info("successfully resolved callback:", callback);
  if (!(url instanceof URL2)) {
    url = url.toString();
  }
  return [url, options, callback];
}

// src/interceptors/ClientRequest/index.ts
var _ClientRequestInterceptor = class extends Interceptor {
  constructor() {
    super(_ClientRequestInterceptor.symbol);
    this.onRequest = async ({
      request,
      socket
    }) => {
      const controller = new RequestController(request, {
        passthrough() {
          socket.passthrough();
        },
        async respondWith(response) {
          await socket.respondWith(response);
        },
        errorWith(reason) {
          if (reason instanceof Error) {
            socket.errorWith(reason);
          }
        }
      });
      await handleRequest({
        request,
        requestId: Reflect.get(request, kRequestId),
        controller,
        emitter: this.emitter
      });
    };
    this.onResponse = async ({
      requestId,
      request,
      response,
      isMockedResponse
    }) => {
      return emitAsync(this.emitter, "response", {
        requestId,
        request,
        response,
        isMockedResponse
      });
    };
  }
  setup() {
    const {
      ClientRequest: OriginalClientRequest,
      get: originalGet,
      request: originalRequest
    } = http2;
    const { get: originalHttpsGet, request: originalHttpsRequest } = https2;
    const onRequest = this.onRequest.bind(this);
    const onResponse = this.onResponse.bind(this);
    http2.ClientRequest = new Proxy(http2.ClientRequest, {
      construct: (target, args) => {
        const [url, options, callback] = normalizeClientRequestArgs(
          "http:",
          args
        );
        const Agent2 = options.protocol === "https:" ? MockHttpsAgent : MockAgent;
        const mockAgent = new Agent2({
          customAgent: options.agent,
          onRequest,
          onResponse
        });
        options.agent = mockAgent;
        return Reflect.construct(target, [url, options, callback]);
      }
    });
    http2.request = new Proxy(http2.request, {
      apply: (target, thisArg, args) => {
        const [url, options, callback] = normalizeClientRequestArgs(
          "http:",
          args
        );
        const mockAgent = new MockAgent({
          customAgent: options.agent,
          onRequest,
          onResponse
        });
        options.agent = mockAgent;
        return Reflect.apply(target, thisArg, [url, options, callback]);
      }
    });
    http2.get = new Proxy(http2.get, {
      apply: (target, thisArg, args) => {
        const [url, options, callback] = normalizeClientRequestArgs(
          "http:",
          args
        );
        const mockAgent = new MockAgent({
          customAgent: options.agent,
          onRequest,
          onResponse
        });
        options.agent = mockAgent;
        return Reflect.apply(target, thisArg, [url, options, callback]);
      }
    });
    https2.request = new Proxy(https2.request, {
      apply: (target, thisArg, args) => {
        const [url, options, callback] = normalizeClientRequestArgs(
          "https:",
          args
        );
        const mockAgent = new MockHttpsAgent({
          customAgent: options.agent,
          onRequest,
          onResponse
        });
        options.agent = mockAgent;
        return Reflect.apply(target, thisArg, [url, options, callback]);
      }
    });
    https2.get = new Proxy(https2.get, {
      apply: (target, thisArg, args) => {
        const [url, options, callback] = normalizeClientRequestArgs(
          "https:",
          args
        );
        const mockAgent = new MockHttpsAgent({
          customAgent: options.agent,
          onRequest,
          onResponse
        });
        options.agent = mockAgent;
        return Reflect.apply(target, thisArg, [url, options, callback]);
      }
    });
    recordRawFetchHeaders();
    this.subscriptions.push(() => {
      http2.ClientRequest = OriginalClientRequest;
      http2.get = originalGet;
      http2.request = originalRequest;
      https2.get = originalHttpsGet;
      https2.request = originalHttpsRequest;
      restoreHeadersPrototype();
    });
  }
};
var ClientRequestInterceptor = _ClientRequestInterceptor;
ClientRequestInterceptor.symbol = Symbol("client-request-interceptor");

export {
  ClientRequestInterceptor
};
//# sourceMappingURL=chunk-5UGIB6OX.mjs.map