import { spawn } from "child_process";

export function normalizeServiceStartCommand(command) {
  if (typeof command !== "string") {
    throw new Error("Service start command must be a string");
  }

  const trimmed = command.trim();
  if (trimmed.length === 0) {
    throw new Error("Service start command must not be empty");
  }

  return trimmed.replace(/^exec\s+/u, "");
}

export function startDetachedCommand(command, cwd, env) {
  const normalizedCommand = normalizeServiceStartCommand(command);
  if (process.platform === "win32") {
    return spawn(normalizedCommand, {
      cwd,
      env,
      detached: true,
      shell: true,
      stdio: ["ignore", "pipe", "pipe"],
    });
  }

  const shell = process.env.SHELL || "/bin/sh";
  return spawn(shell, ["-lc", `exec ${normalizedCommand}`], {
    cwd,
    env,
    detached: true,
    stdio: ["ignore", "pipe", "pipe"],
  });
}

export function killChildProcess(child, signal) {
  if (!child?.pid) return;

  try {
    process.kill(-child.pid, signal);
    return;
  } catch (error) {
    if (error?.code !== "ESRCH") {
      // Fall back to the direct child if process-group signalling is unavailable.
    } else {
      return;
    }
  }

  try {
    child.kill(signal);
  } catch (error) {
    if (error?.code !== "ESRCH") throw error;
  }
}

export function captureOutput(stream, options = {}) {
  if (!stream) return Promise.resolve();

  const onLine = typeof options.onLine === "function" ? options.onLine : null;
  const liveWriter = typeof options.liveWriter === "function" ? options.liveWriter : null;
  const livePrefix = options.livePrefix || "";
  let pending = "";
  return new Promise((resolve) => {
    let settled = false;
    const settle = () => {
      if (settled) return;
      settled = true;
      resolve();
    };

    stream.on("data", (chunk) => {
      pending += chunk.toString();
      const lines = pending.split(/\r?\n/);
      pending = lines.pop() || "";
      for (const line of lines) {
        if (line.length === 0) continue;
        onLine?.(line);
        if (liveWriter) liveWriter(livePrefix ? `${livePrefix} ${line}` : line);
      }
    });
    stream.on("end", () => {
      if (pending.length > 0) {
        onLine?.(pending);
        if (liveWriter) liveWriter(livePrefix ? `${livePrefix} ${pending}` : pending);
      }
      settle();
    });
    stream.on("close", settle);
    stream.on("error", settle);
  });
}

export async function stopChildProcess(child, outputDrains = []) {
  if (!child) return;
  if (child.exitCode !== null) {
    await Promise.all(outputDrains);
    return;
  }

  killChildProcess(child, "SIGTERM");
  const exited = await Promise.race([
    new Promise((resolve) => child.once("exit", () => resolve(true))),
    sleep(5_000).then(() => false),
  ]);

  if (!exited && child.exitCode === null) {
    killChildProcess(child, "SIGKILL");
    await new Promise((resolve) => child.once("exit", resolve));
  }

  await Promise.all(outputDrains);
}

export function sleep(ms) {
  return new Promise((resolve) => setTimeout(resolve, ms));
}
