"use strict";
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 __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 = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __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 || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
  mod
));
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
var session_exports = {};
__export(session_exports, {
  Session: () => Session,
  renderResolvedConfig: () => renderResolvedConfig
});
module.exports = __toCommonJS(session_exports);
var import_child_process = require("child_process");
var import_fs = __toESM(require("fs"));
var import_net = __toESM(require("net"));
var import_os = __toESM(require("os"));
var import_path = __toESM(require("path"));
var import_socketConnection = require("./socketConnection");
class Session {
  constructor(clientInfo, options) {
    this._nextMessageId = 1;
    this._callbacks = /* @__PURE__ */ new Map();
    this._clientInfo = clientInfo;
    this.config = options;
    this.name = options.name;
  }
  isCompatible() {
    return (0, import_socketConnection.compareSemver)(this._clientInfo.version, this.config.version) >= 0;
  }
  checkCompatible() {
    if (!this.isCompatible()) {
      throw new Error(`Client is v${this._clientInfo.version}, session '${this.name}' is v${this.config.version}. Run

  playwright-cli${this.name !== "default" ? ` -s=${this.name}` : ""} open

to restart the browser session.`);
    }
  }
  async open() {
    await this._startDaemonIfNeeded();
    this.disconnect();
  }
  async run(args, cwd) {
    this.checkCompatible();
    const result = await this._send("run", { args, cwd: cwd || process.cwd() });
    this.disconnect();
    return result;
  }
  async stop(quiet = false) {
    if (!await this.canConnect()) {
      if (!quiet)
        console.log(`Browser '${this.name}' is not open.`);
      return;
    }
    await this._stopDaemon();
    if (!quiet)
      console.log(`Browser '${this.name}' closed
`);
  }
  async _send(method, params = {}) {
    const connection = await this._startDaemonIfNeeded();
    const messageId = this._nextMessageId++;
    const message = {
      id: messageId,
      method,
      params,
      version: this.config.version
    };
    const responsePromise = new Promise((resolve, reject) => {
      this._callbacks.set(messageId, { resolve, reject, method, params });
    });
    const [result] = await Promise.all([responsePromise, connection.send(message)]);
    return result;
  }
  disconnect() {
    if (!this._connection)
      return;
    for (const callback of this._callbacks.values())
      callback.reject(new Error("Session closed"));
    this._callbacks.clear();
    this._connection.close();
    this._connection = void 0;
  }
  async deleteData() {
    await this.stop();
    const dataDirs = await import_fs.default.promises.readdir(this._clientInfo.daemonProfilesDir).catch(() => []);
    const matchingEntries = dataDirs.filter((file) => file === `${this.name}.session` || file.startsWith(`ud-${this.name}-`));
    if (matchingEntries.length === 0) {
      console.log(`No user data found for browser '${this.name}'.`);
      return;
    }
    for (const entry of matchingEntries) {
      const userDataDir = import_path.default.resolve(this._clientInfo.daemonProfilesDir, entry);
      for (let i = 0; i < 5; i++) {
        try {
          await import_fs.default.promises.rm(userDataDir, { recursive: true });
          if (entry.startsWith("ud-"))
            console.log(`Deleted user data for browser '${this.name}'.`);
          break;
        } catch (e) {
          if (e.code === "ENOENT") {
            console.log(`No user data found for browser '${this.name}'.`);
            break;
          }
          await new Promise((resolve) => setTimeout(resolve, 1e3));
          if (i === 4)
            throw e;
        }
      }
    }
  }
  async _connect() {
    return await new Promise((resolve) => {
      const socket = import_net.default.createConnection(this.config.socketPath, () => {
        resolve({ socket });
      });
      socket.on("error", (error) => {
        if (import_os.default.platform() !== "win32")
          void import_fs.default.promises.unlink(this.config.socketPath).catch(() => {
          }).then(() => resolve({ error }));
        else
          resolve({ error });
      });
    });
  }
  async canConnect() {
    const { socket } = await this._connect();
    if (socket) {
      socket.destroy();
      return true;
    }
    return false;
  }
  async _startDaemonIfNeeded() {
    if (this._connection)
      return this._connection;
    let { socket } = await this._connect();
    if (!socket)
      socket = await this._startDaemon();
    this._connection = new import_socketConnection.SocketConnection(socket, this.config.version);
    this._connection.onmessage = (message) => this._onMessage(message);
    this._connection.onclose = () => this.disconnect();
    return this._connection;
  }
  _onMessage(object) {
    if (object.id && this._callbacks.has(object.id)) {
      const callback = this._callbacks.get(object.id);
      this._callbacks.delete(object.id);
      if (object.error)
        callback.reject(new Error(object.error));
      else
        callback.resolve(object.result);
    } else if (object.id) {
      throw new Error(`Unexpected message id: ${object.id}`);
    } else {
      throw new Error(`Unexpected message without id: ${JSON.stringify(object)}`);
    }
  }
  _sessionFile(suffix) {
    return import_path.default.resolve(this._clientInfo.daemonProfilesDir, `${this.name}${suffix}`);
  }
  async _startDaemon() {
    await import_fs.default.promises.mkdir(this._clientInfo.daemonProfilesDir, { recursive: true });
    const cliPath = import_path.default.join(__dirname, "../../../cli.js");
    const sessionConfigFile = this._sessionFile(".session");
    this.config.version = this._clientInfo.version;
    this.config.timestamp = Date.now();
    await import_fs.default.promises.writeFile(sessionConfigFile, JSON.stringify(this.config, null, 2));
    const errLog = this._sessionFile(".err");
    const err = import_fs.default.openSync(errLog, "w");
    const args = [
      cliPath,
      "run-cli-server",
      `--daemon-session=${sessionConfigFile}`
    ];
    const child = (0, import_child_process.spawn)(process.execPath, args, {
      detached: true,
      stdio: ["ignore", "pipe", err],
      cwd: process.cwd()
      // Will be used as root.
    });
    let signalled = false;
    const sigintHandler = () => {
      signalled = true;
      child.kill("SIGINT");
    };
    const sigtermHandler = () => {
      signalled = true;
      child.kill("SIGTERM");
    };
    process.on("SIGINT", sigintHandler);
    process.on("SIGTERM", sigtermHandler);
    let outLog = "";
    await new Promise((resolve, reject) => {
      child.stdout.on("data", (data) => {
        outLog += data.toString();
        if (!outLog.includes("<EOF>"))
          return;
        const errorMatch = outLog.match(/### Error\n([\s\S]*)<EOF>/);
        const error = errorMatch ? errorMatch[1].trim() : void 0;
        if (error) {
          const errLogContent = import_fs.default.readFileSync(errLog, "utf-8");
          const message = error + (errLogContent ? "\n" + errLogContent : "");
          reject(new Error(message));
        }
        const successMatch = outLog.match(/### Success\nDaemon listening on (.*)\n<EOF>/);
        if (successMatch)
          resolve();
      });
      child.on("close", (code) => {
        if (!signalled)
          reject(new Error(`Daemon process exited with code ${code}`));
      });
    });
    process.off("SIGINT", sigintHandler);
    process.off("SIGTERM", sigtermHandler);
    child.stdout.destroy();
    child.unref();
    const { socket } = await this._connect();
    if (socket) {
      console.log(`### Browser \`${this.name}\` opened with pid ${child.pid}.`);
      const resolvedConfig = await parseResolvedConfig(outLog);
      if (resolvedConfig) {
        this.config.resolvedConfig = resolvedConfig;
        console.log(`- ${this.name}:`);
        console.log(renderResolvedConfig(resolvedConfig).join("\n"));
      }
      console.log(`---`);
      this.config.timestamp = Date.now();
      await import_fs.default.promises.writeFile(sessionConfigFile, JSON.stringify(this.config, null, 2));
      return socket;
    }
    console.error(`Failed to connect to daemon at ${this.config.socketPath}`);
    process.exit(1);
  }
  async _stopDaemon() {
    let error;
    await this._send("stop").catch((e) => {
      error = e;
    });
    if (import_os.default.platform() !== "win32")
      await import_fs.default.promises.unlink(this.config.socketPath).catch(() => {
      });
    this.disconnect();
    if (!this.config.cli.persistent)
      await this.deleteSessionConfig();
    if (error && !error?.message?.includes("Session closed"))
      throw error;
  }
  async deleteSessionConfig() {
    await import_fs.default.promises.rm(this._sessionFile(".session")).catch(() => {
    });
  }
}
function renderResolvedConfig(resolvedConfig) {
  const channel = resolvedConfig.browser.launchOptions.channel ?? resolvedConfig.browser.browserName;
  const lines = [];
  if (channel)
    lines.push(`  - browser-type: ${channel}`);
  if (resolvedConfig.browser.isolated)
    lines.push(`  - user-data-dir: <in-memory>`);
  else
    lines.push(`  - user-data-dir: ${resolvedConfig.browser.userDataDir}`);
  lines.push(`  - headed: ${!resolvedConfig.browser.launchOptions.headless}`);
  return lines;
}
async function parseResolvedConfig(errLog) {
  const marker = "### Config\n```json\n";
  const markerIndex = errLog.indexOf(marker);
  if (markerIndex === -1)
    return null;
  const jsonStart = markerIndex + marker.length;
  const jsonEnd = errLog.indexOf("\n```", jsonStart);
  if (jsonEnd === -1)
    throw null;
  const jsonString = errLog.substring(jsonStart, jsonEnd).trim();
  try {
    return JSON.parse(jsonString);
  } catch {
    return null;
  }
}
// Annotate the CommonJS export names for ESM import in node:
0 && (module.exports = {
  Session,
  renderResolvedConfig
});
