import { spawnSync } from 'node:child_process';
import http from 'node:http';
import net from 'node:net';
import path from 'node:path';
import { builtinModules } from 'node:module';

// src/features/gen-html/infrastructure/local-server/health.ts

// src/platform/core/string-helpers.ts
function isNodeError(error) {
  return error instanceof Error && typeof error.code === "string";
}
var DEFAULT_LOCAL_SERVER_PORT = 4173;
var LOCAL_SERVER_HOST = "127.0.0.1";
var LOCAL_SERVER_HEALTH_PATH = "/index.json";
var LOCAL_SERVER_PORT_SEARCH_WINDOW = 20;
var LOCAL_SERVER_START_TIMEOUT_MS = 3e4;
var LOCAL_SERVER_STOP_TIMEOUT_MS = 5e3;
new Set(
  builtinModules.flatMap((moduleName) => [moduleName, moduleName.replace(/^node:/, ""), `node:${moduleName.replace(/^node:/, "")}`])
);
function getLocalServerPaths(paths) {
  const directoryPath = path.join(paths.baseDir, "local-server");
  const generatedDirectoryPath = path.join(directoryPath, "generated");
  const multipleDirectoryPath = path.join(directoryPath, "multiple");
  const storybookConfigDirectoryPath = path.join(directoryPath, ".storybook");
  return {
    directoryPath,
    generatedDirectoryPath,
    generatedRegistryPath: path.join(directoryPath, "generated-registry.json"),
    multipleDirectoryPath,
    logPath: path.join(directoryPath, "server.log"),
    packageJsonPath: path.join(directoryPath, "package.json"),
    packageCustomJsonPath: path.join(directoryPath, "package-custom.json"),
    previewTargetPath: path.join(directoryPath, "preview-target.json"),
    refreshLockPath: path.join(directoryPath, "refresh.lock"),
    scssModuleDeclarationPath: path.join(directoryPath, "scss.d.ts"),
    serverLockPath: path.join(directoryPath, "server.lock"),
    statePath: path.join(directoryPath, "server-state.json"),
    storybookConfigDirectoryPath,
    storybookMainConfigPath: path.join(storybookConfigDirectoryPath, "main.ts"),
    storybookManagerConfigPath: path.join(storybookConfigDirectoryPath, "manager.tsx"),
    storybookPreviewConfigPath: path.join(storybookConfigDirectoryPath, "preview.ts"),
    tsconfigPath: path.join(directoryPath, "tsconfig.json")
  };
}
function isRecord(value) {
  return typeof value === "object" && value !== null && !Array.isArray(value);
}

// src/features/gen-html/infrastructure/local-server/health.ts
function findListeningProcessId(port) {
  if (!Number.isInteger(port) || port <= 0) {
    return void 0;
  }
  if (process.platform === "win32") {
    return void 0;
  }
  const result = spawnSync("lsof", ["-nP", `-iTCP:${port}`, "-sTCP:LISTEN", "-t"], {
    encoding: "utf8",
    maxBuffer: 1024 * 1024
  });
  if (result.error || result.status !== 0) {
    return void 0;
  }
  const firstLine = result.stdout.split(/\r?\n/).map((line) => line.trim()).find(Boolean);
  const pid = firstLine ? Number(firstLine) : NaN;
  return Number.isInteger(pid) && pid > 0 ? pid : void 0;
}
function getProcessCurrentWorkingDirectory(pid) {
  if (!Number.isInteger(pid) || pid <= 0 || process.platform === "win32") {
    return void 0;
  }
  const result = spawnSync("lsof", ["-a", "-p", String(pid), "-d", "cwd", "-Fn"], {
    encoding: "utf8",
    maxBuffer: 1024 * 1024
  });
  if (result.error || result.status !== 0) {
    return void 0;
  }
  const cwdLine = result.stdout.split(/\r?\n/).map((line) => line.trim()).find((line) => line.startsWith("n"));
  return cwdLine ? cwdLine.slice(1) : void 0;
}
function getLocalServerPortCandidates(startPort = DEFAULT_LOCAL_SERVER_PORT) {
  return Array.from({ length: LOCAL_SERVER_PORT_SEARCH_WINDOW }, (_, index) => startPort + index);
}
function findLocalServerListeningProcesses(directoryPath) {
  const resolvedDirectoryPath = path.resolve(directoryPath);
  const processMap = /* @__PURE__ */ new Map();
  for (const port of getLocalServerPortCandidates()) {
    const pid = findListeningProcessId(port);
    if (!pid || processMap.has(pid)) {
      continue;
    }
    const cwd = getProcessCurrentWorkingDirectory(pid);
    if (!cwd || path.resolve(cwd) !== resolvedDirectoryPath) {
      continue;
    }
    processMap.set(pid, { pid, port });
  }
  return Array.from(processMap.values()).sort((left, right) => left.port - right.port);
}
function getHealthUrl(port) {
  return `http://${LOCAL_SERVER_HOST}:${port}${LOCAL_SERVER_HEALTH_PATH}`;
}
function requestJson(url) {
  return new Promise((resolve) => {
    const request = http.get(url, { timeout: 2e3 }, (response) => {
      let body = "";
      response.setEncoding("utf8");
      response.on("data", (chunk) => {
        body += chunk;
      });
      response.on("end", () => {
        if (response.statusCode !== 200) {
          resolve(void 0);
          return;
        }
        try {
          resolve(JSON.parse(body));
        } catch {
          resolve(void 0);
        }
      });
    });
    request.on("timeout", () => {
      request.destroy();
      resolve(void 0);
    });
    request.on("error", () => {
      resolve(void 0);
    });
  });
}
function isPortAvailable(port) {
  return new Promise((resolve) => {
    const server = net.createServer();
    server.once("error", () => {
      resolve(false);
    });
    server.once("listening", () => {
      server.close(() => {
        resolve(true);
      });
    });
    server.listen(port, LOCAL_SERVER_HOST);
  });
}
function wait(delayMs) {
  return new Promise((resolve) => {
    setTimeout(resolve, delayMs);
  });
}
async function findAvailablePort(startPort) {
  for (const nextPort of getLocalServerPortCandidates(startPort)) {
    if (await isPortAvailable(nextPort)) {
      return nextPort;
    }
  }
  throw new Error(`\uC0AC\uC6A9 \uAC00\uB2A5\uD55C \uB85C\uCEEC \uC11C\uBC84 \uD3EC\uD2B8\uB97C \uCC3E\uC9C0 \uBABB\uD588\uC2B5\uB2C8\uB2E4. startPort=${startPort}`);
}
function getLocalServerBaseUrl(port) {
  return `http://${LOCAL_SERVER_HOST}:${port}`;
}
async function waitUntilServerIsReady(options) {
  const startedAt = Date.now();
  while (Date.now() - startedAt < LOCAL_SERVER_START_TIMEOUT_MS) {
    const index = await requestJson(getHealthUrl(options.port));
    if (index && isRecord(index.entries)) {
      return;
    }
    await wait(300);
  }
  throw new Error(`\uB85C\uCEEC \uD504\uB9AC\uBDF0 \uC11C\uBC84\uAC00 ${LOCAL_SERVER_START_TIMEOUT_MS}ms \uC548\uC5D0 \uC900\uBE44\uB418\uC9C0 \uC54A\uC558\uC2B5\uB2C8\uB2E4. port=${options.port}`);
}
async function waitUntilServerStops(port) {
  const startedAt = Date.now();
  while (Date.now() - startedAt < LOCAL_SERVER_STOP_TIMEOUT_MS) {
    if (await isPortAvailable(port)) {
      return;
    }
    await wait(200);
  }
}
function terminateLocalServerProcess(pid) {
  if (process.platform === "win32") {
    const result = spawnSync("taskkill", ["/PID", String(pid), "/T", "/F"], {
      stdio: "ignore",
      windowsHide: true
    });
    if (result.error || result.status !== 0 && result.status !== 128) {
      throw new Error(`\uB85C\uCEEC \uD504\uB9AC\uBDF0 \uC11C\uBC84 \uC885\uB8CC\uC5D0 \uC2E4\uD328\uD588\uC2B5\uB2C8\uB2E4. pid=${pid}`);
    }
    return;
  }
  try {
    process.kill(pid, "SIGTERM");
  } catch (error) {
    if (!isNodeError(error) || error.code !== "ESRCH") {
      throw error;
    }
  }
}
function forceTerminateLocalServerProcess(pid) {
  try {
    process.kill(pid, "SIGKILL");
  } catch (error) {
    if (!isNodeError(error) || error.code !== "ESRCH") {
      throw error;
    }
  }
}
async function cleanupDuplicateLocalServerProcesses(options) {
  const localServerPaths = getLocalServerPaths(options.paths);
  const duplicatedProcesses = findLocalServerListeningProcesses(localServerPaths.directoryPath);
  for (const processInfo of duplicatedProcesses) {
    options.trace("local-server:cleanup:start", `pid=${processInfo.pid} port=${processInfo.port}`);
    terminateLocalServerProcess(processInfo.pid);
    await waitUntilServerStops(processInfo.port);
    const remainingPid = findListeningProcessId(processInfo.port);
    if (remainingPid) {
      options.trace("local-server:cleanup:force", `pid=${remainingPid} port=${processInfo.port}`);
      forceTerminateLocalServerProcess(remainingPid);
      await waitUntilServerStops(processInfo.port);
    }
  }
}

export { cleanupDuplicateLocalServerProcesses, findAvailablePort, findListeningProcessId, findLocalServerListeningProcesses, forceTerminateLocalServerProcess, getHealthUrl, getLocalServerBaseUrl, getLocalServerPortCandidates, getProcessCurrentWorkingDirectory, isPortAvailable, requestJson, terminateLocalServerProcess, waitUntilServerIsReady, waitUntilServerStops };
