import { createServer } from 'net';
import { networkInterfaces } from 'os';
import { getMemo, setMemo } from 'fs-memo';

const unsafePorts = /* @__PURE__ */ new Set([
  1,
  7,
  9,
  11,
  13,
  15,
  17,
  19,
  20,
  21,
  22,
  23,
  25,
  37,
  42,
  43,
  53,
  69,
  77,
  79,
  87,
  95,
  101,
  102,
  103,
  104,
  109,
  110,
  111,
  113,
  115,
  117,
  119,
  123,
  135,
  137,
  139,
  143,
  161,
  179,
  389,
  427,
  465,
  512,
  513,
  514,
  515,
  526,
  530,
  531,
  532,
  540,
  548,
  554,
  556,
  563,
  587,
  601,
  636,
  989,
  990,
  993,
  995,
  1719,
  1720,
  1723,
  2049,
  3659,
  4045,
  5060,
  5061,
  6e3,
  6566,
  6665,
  6666,
  6667,
  6668,
  6669,
  6697,
  10080
]);
function isUnsafePort(port) {
  return unsafePorts.has(port);
}
function isSafePort(port) {
  return !isUnsafePort(port);
}

function log(...args) {
  console.log("[get-port]", ...args);
}
async function getPort(config = {}) {
  if (typeof config === "number" || typeof config === "string") {
    config = { port: parseInt(config + "") || 0 };
  }
  const options = {
    name: "default",
    random: false,
    ports: [],
    portRange: [],
    alternativePortRange: [3e3, 3100],
    host: void 0,
    memoName: "port",
    verbose: false,
    ...config,
    port: parseInt(process.env.PORT || "") || config.port || 3e3
  };
  if (options.random) {
    return getRandomPort(options.host);
  }
  const portsToCheck = [
    options.port,
    ...options.ports,
    ...generateRange(...options.portRange)
  ].filter((port) => {
    if (!port) {
      return false;
    }
    if (!isSafePort(port)) {
      if (options.verbose) {
        log("Ignoring unsafe port:", port);
      }
      return false;
    }
    return true;
  });
  const memoOptions = { name: options.memoName, dir: options.memoDir };
  const memoKey = "port_" + options.name;
  const memo = await getMemo(memoOptions);
  if (memo[memoKey]) {
    portsToCheck.push(memo[memoKey]);
  }
  let availablePort = await findPort(portsToCheck, options.host, options.verbose, false);
  if (!availablePort) {
    availablePort = await findPort(generateRange(...options.alternativePortRange), options.host, options.verbose);
    if (options.verbose) {
      log(`Unable to find an available port (tried ${portsToCheck.join(", ") || "-"}). Using alternative port:`, availablePort);
    }
  }
  await setMemo({ [memoKey]: availablePort }, memoOptions);
  return availablePort;
}
async function getRandomPort(host) {
  const port = await checkPort(0, host);
  if (port === false) {
    throw new Error("Unable to obtain an available random port number!");
  }
  return port;
}
async function waitForPort(port, opts = {}) {
  const delay = opts.delay || 500;
  const retries = opts.retries || 4;
  for (let i = retries; i > 0; i--) {
    if (await checkPort(port, opts.host) === false) {
      return;
    }
    await new Promise((resolve) => setTimeout(resolve, delay));
  }
  throw new Error(`Timeout waiting for port ${port} after ${retries} retries with ${delay}ms interval.`);
}
async function checkPort(port, host = process.env.HOST, _verbose) {
  if (!host) {
    host = getLocalHosts([void 0, "0.0.0.0"]);
  }
  if (!Array.isArray(host)) {
    return _checkPort(port, host);
  }
  for (const _host of host) {
    const _port = await _checkPort(port, _host);
    if (_port === false) {
      if (port < 1024 && _verbose) {
        log("Unable to listen to priviliged port:", `${_host}:${port}`);
      }
      return false;
    }
    if (port === 0 && _port !== 0) {
      port = _port;
    }
  }
  return port;
}
function generateRange(from, to) {
  if (to < from) {
    return [];
  }
  const r = [];
  for (let i = from; i < to; i++) {
    r.push(i);
  }
  return r;
}
function _checkPort(port, host) {
  return new Promise((resolve) => {
    const server = createServer();
    server.unref();
    server.on("error", (err) => {
      if (err.code === "EINVAL" || err.code === "EADDRNOTAVAIL") {
        resolve(port !== 0 && isSafePort(port) && port);
      } else {
        resolve(false);
      }
    });
    server.listen({ port, host }, () => {
      const { port: port2 } = server.address();
      server.close(() => {
        resolve(isSafePort(port2) && port2);
      });
    });
  });
}
function getLocalHosts(additional) {
  const hosts = new Set(additional);
  for (const _interface of Object.values(networkInterfaces())) {
    for (const config of _interface) {
      hosts.add(config.address);
    }
  }
  return Array.from(hosts);
}
async function findPort(ports, host, _verbose = false, _random = true) {
  for (const port of ports) {
    const r = await checkPort(port, host, _verbose);
    if (r) {
      return r;
    }
  }
  if (_random) {
    const randomPort = await getRandomPort(host);
    if (_verbose) {
      log(`Unable to find an available port (tried ${ports.join(", ") || "-"}). Using random port:`, randomPort);
    }
    return randomPort;
  } else {
    return 0;
  }
}

export { checkPort, getPort, getRandomPort, isSafePort, isUnsafePort, waitForPort };
