"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 __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
));

// scripts/cli.ts
var import_node_child_process = require("child_process");
var import_node_fs = require("fs");
var import_node_http = require("http");
var import_node_os = __toESM(require("os"));
var import_node_path = __toESM(require("path"));
var import_node_readline = __toESM(require("readline"));
var useColor = process.stdout.isTTY && !process.env.NO_COLOR;
var paint = (code) => (s) => useColor ? `\x1B[${code}m${s}\x1B[0m` : s;
var c = {
  bold: paint("1"),
  dim: paint("2"),
  red: paint("31"),
  green: paint("32"),
  yellow: paint("33"),
  blue: paint("34"),
  magenta: paint("35"),
  cyan: paint("36"),
  gray: paint("90")
};
var INDENT = " ".repeat(3);
var line = (s = "") => console.log(INDENT + s);
var tree = (s = "") => console.log(c.gray(INDENT + "\u2502 ") + s);
var bannerShown = false;
function banner() {
  if (bannerShown) return;
  bannerShown = true;
  line();
  line(c.bold(c.blue("blogkit-md")));
  line(c.gray("preview markdown as blogkit blog posts"));
  line();
}
var tilde = (p) => {
  const home = import_node_os.default.homedir();
  return p.startsWith(home) ? "~" + p.slice(home.length) : p;
};
function humanAge(iso) {
  const start = Date.parse(iso);
  if (Number.isNaN(start)) return "\u2014";
  const secs = Math.max(0, Math.floor((Date.now() - start) / 1e3));
  if (secs < 60) return `${secs}s`;
  if (secs < 3600) return `${Math.floor(secs / 60)}m`;
  if (secs < 86400) return `${Math.floor(secs / 3600)}h`;
  return `${Math.floor(secs / 86400)}d`;
}
function openBrowser(url) {
  if (process.env.BLOGKIT_MD_NO_OPEN) return;
  try {
    (0, import_node_child_process.exec)(`open ${url}`);
  } catch {
  }
}
var registryDir = () => process.env.BLOGKIT_MD_DIR || import_node_path.default.join(import_node_os.default.homedir(), ".blogkit-md");
var registryFile = () => import_node_path.default.join(registryDir(), "instances.json");
var logDir = () => import_node_path.default.join(registryDir(), "logs");
var isAlive = (pid) => {
  try {
    process.kill(pid, 0);
    return true;
  } catch {
    return false;
  }
};
function readRegistry() {
  try {
    const raw = JSON.parse((0, import_node_fs.readFileSync)(registryFile(), "utf8"));
    return Array.isArray(raw) ? raw : [];
  } catch {
    return [];
  }
}
function writeRegistry(list) {
  try {
    (0, import_node_fs.mkdirSync)(registryDir(), { recursive: true });
    (0, import_node_fs.writeFileSync)(registryFile(), JSON.stringify(list, null, 2));
  } catch {
  }
}
function pruneRegistry() {
  const all = readRegistry();
  const live = all.filter((index) => isAlive(index.pid));
  if (live.length !== all.length) writeRegistry(live);
  return live;
}
function addInstance(inst) {
  const list = pruneRegistry().filter((index) => index.pid !== inst.pid);
  list.push(inst);
  writeRegistry(list);
}
function removeInstance(pid) {
  writeRegistry(readRegistry().filter((index) => index.pid !== pid));
}
function findByDir(dir) {
  return pruneRegistry().find((index) => index.dir === dir);
}
function killInstance(inst) {
  try {
    process.kill(inst.pid, "SIGTERM");
  } catch {
  }
  removeInstance(inst.pid);
}
function instanceLabel(inst) {
  const kind = inst.isDirectory ? "\u{1F4C1}" : "\u{1F4C4}";
  const mode = inst.background ? c.magenta("bg") : c.gray("fg");
  return `${c.green("localhost:" + inst.port)}  ${kind} ${tilde(inst.dir)}  ${c.gray("pid " + inst.pid)} ${c.gray("\xB7 " + humanAge(inst.startedAt))} ${mode}`;
}
function printPlainList(instances) {
  banner();
  line(c.bold("Running instances"));
  instances.forEach((index) => tree(instanceLabel(index)));
  line();
}
async function listInstancesInteractive() {
  let instances = pruneRegistry();
  if (instances.length === 0) {
    banner();
    line(c.yellow("No running blogkit-md instances."));
    line();
    return;
  }
  if (!process.stdin.isTTY) {
    printPlainList(instances);
    return;
  }
  await new Promise((resolve) => {
    let index = 0;
    const stdin = process.stdin;
    try {
      import_node_readline.default.emitKeypressEvents(stdin);
      stdin.setRawMode(true);
      stdin.resume();
    } catch {
      printPlainList(instances);
      resolve();
      return;
    }
    const render = (footer) => {
      const out = [
        "",
        INDENT + c.bold(c.blue("blogkit-md")) + c.gray("  \xB7  running instances"),
        "",
        ...instances.map((inst, rowIndex) => {
          const selected = rowIndex === index;
          const pointer = selected ? c.blue("\u276F ") : "  ";
          const label = selected ? instanceLabel(inst) : c.dim(instanceLabel(inst));
          return INDENT + pointer + label;
        }),
        "",
        footer ?? c.gray(INDENT + "\u2191/\u2193 move   \u23CE stop selected   q quit"),
        ""
      ];
      process.stdout.write("\x1B[2J\x1B[H" + out.join("\n") + "\n");
    };
    const finish = () => {
      stdin.setRawMode(false);
      stdin.removeAllListeners("keypress");
      stdin.pause();
      resolve();
    };
    const onKey = (_string, key) => {
      if (!key) return;
      switch (key.name) {
        case "up":
        case "k": {
          index = (index - 1 + instances.length) % instances.length;
          render();
          break;
        }
        case "down":
        case "j": {
          index = (index + 1) % instances.length;
          render();
          break;
        }
        case "return": {
          const target = instances[index];
          killInstance(target);
          instances = pruneRegistry();
          if (instances.length === 0) {
            render(c.green(INDENT + `\u2713 Stopped localhost:${target.port}. No instances left.`));
            finish();
            return;
          }
          index = Math.min(index, instances.length - 1);
          render(c.green(INDENT + `\u2713 Stopped localhost:${target.port}.`));
          break;
        }
        default: {
          if (key.name === "q" || key.name === "escape" || key.ctrl && key.name === "c") {
            render(c.gray(INDENT + "Bye \u{1F44B}"));
            finish();
          }
        }
      }
    };
    stdin.on("keypress", onKey);
    render();
  });
}
function parseArgs(argv) {
  const isFlag = (a) => a.startsWith("-");
  const flagNames = new Set(argv.filter(isFlag).map((a) => a.split("=", 1)[0]));
  const inputArgument = argv.find((a) => !isFlag(a));
  const portArgument = argv.find((a) => a.startsWith("--port="));
  const requestedPort = portArgument ? Number.parseInt(portArgument.split("=", 2)[1], 10) || 0 : 0;
  return {
    inputArg: inputArgument,
    requestedPort,
    wantBackground: flagNames.has("--background") || flagNames.has("-b"),
    wantList: flagNames.has("--list") || flagNames.has("-l") || flagNames.has("--list-instances"),
    // legacy alias
    wantTear: flagNames.has("--tear") || flagNames.has("-t"),
    isDetachedChild: flagNames.has("--__detached"),
    // internal use only
    wantHelp: flagNames.has("--help") || flagNames.has("-h")
  };
}
function printHelp() {
  banner();
  line(c.bold("Usage"));
  tree("blogkit-md <file-or-directory> [options]");
  line();
  line(c.bold("Options"));
  tree(`${c.green("--port=<port>")}       run on a specific port (default: random free port)`);
  tree(`${c.green("-b, --background")}    run the preview server detached in the background`);
  tree(`${c.green("-t, --tear")}          stop the instance already serving this path, then start fresh`);
  tree(`${c.green("-l, --list")}          interactively list & stop running instances`);
  tree(`${c.green("-h, --help")}          show this help`);
  line();
  line(c.gray("If a path is already being served, running it again just reopens it."));
  line();
}
async function run(argv) {
  const parsed = parseArgs(argv);
  if (parsed.wantHelp) {
    printHelp();
    process.exit(0);
  }
  if (parsed.wantList) {
    await listInstancesInteractive();
    process.exit(0);
  }
  if (!parsed.inputArg) {
    printHelp();
    process.exit(1);
  }
  const inputPath = import_node_path.default.resolve(process.cwd(), parsed.inputArg);
  let inputStat;
  try {
    inputStat = (0, import_node_fs.statSync)(inputPath);
  } catch {
    banner();
    line(c.red("\u2717 Path does not exist"));
    tree(c.dim(inputPath));
    line();
    process.exit(1);
  }
  const isDirectory = inputStat.isDirectory();
  if (!parsed.isDetachedChild) {
    const existing = findByDir(inputPath);
    if (existing) {
      if (parsed.wantTear) {
        banner();
        line(c.yellow("\u25B8 Tearing down the running instance"));
        tree(
          `${c.green("localhost:" + existing.port)}  ${c.gray("pid " + existing.pid)}  ${existing.background ? c.magenta("background") : c.gray("foreground")}`
        );
        killInstance(existing);
        line(c.green(`\u2713 Stopped localhost:${existing.port}`));
        line();
        await new Promise((r) => setTimeout(r, 400));
      } else {
        const url = `http://localhost:${existing.port}`;
        banner();
        line(c.green("\u25B8 Already running \u2014 opening it in the browser"));
        tree(`${c.bold("URL")}   ${c.blue(url)}`);
        tree(`${c.bold("Path")}  ${c.dim(tilde(existing.dir))}`);
        line();
        line(c.gray("Restart it with ") + c.blue("--tear") + c.gray(", or list with ") + c.blue("--list"));
        line();
        openBrowser(url);
        process.exit(0);
      }
    }
  }
  const scriptPath = process.argv[1];
  const packageRoot = import_node_path.default.dirname(import_node_path.default.dirname(scriptPath));
  const nextBin = import_node_path.default.join(packageRoot, "node_modules/.bin/next");
  if (parsed.wantBackground && !parsed.isDetachedChild) {
    try {
      const port = parsed.requestedPort || await getFreePort();
      (0, import_node_fs.mkdirSync)(logDir(), { recursive: true });
      const logFile = import_node_path.default.join(logDir(), `${port}.log`);
      const out = (0, import_node_fs.openSync)(logFile, "a");
      const child = (0, import_node_child_process.spawn)(
        process.execPath,
        [scriptPath, inputPath, `--port=${port}`, "--__detached"],
        { detached: true, stdio: ["ignore", out, out] }
      );
      child.unref();
      banner();
      line(c.green("\u25B8 Started in the background"));
      tree(`${c.bold("URL")}   ${c.blue("http://localhost:" + port)}`);
      tree(`${c.bold("Path")}  ${c.dim(tilde(inputPath))}`);
      tree(`${c.bold("PID")}   ${c.dim(String(child.pid))}`);
      tree(`${c.bold("Log")}   ${c.dim(tilde(logFile))}`);
      line();
      line(c.gray("Stop it with:  ") + c.blue("blogkit-md --list"));
      line();
    } catch (error) {
      banner();
      line(c.red("\u2717 Failed to start in the background"));
      tree(c.dim(String(error)));
      line();
      process.exit(1);
    }
    process.exit(0);
  }
  await startServer(inputPath, isDirectory, parsed, { nextBin, packageRoot });
}
function getFreePort() {
  return new Promise((resolve, reject) => {
    const s = (0, import_node_http.createServer)();
    s.listen(0, () => {
      const port = s.address().port;
      s.close((error) => error ? reject(error) : resolve(port));
    });
    s.on("error", reject);
  });
}
async function startServer(inputPath, isDirectory, parsed, paths) {
  const clients = /* @__PURE__ */ new Set();
  const sseServer = (0, import_node_http.createServer)((request, res) => {
    res.writeHead(200, {
      "Content-Type": "text/event-stream",
      "Cache-Control": "no-cache",
      "Connection": "keep-alive",
      "Access-Control-Allow-Origin": "*"
    });
    res.write("data: connected\n\n");
    clients.add(res);
    request.on("close", () => clients.delete(res));
  });
  await new Promise((resolve) => sseServer.listen(0, resolve));
  const ssePort = sseServer.address().port;
  const nextPort = parsed.requestedPort || await getFreePort();
  const nextEnvironment = {
    ...process.env,
    ...isDirectory ? { MARKDOWN_DIR: inputPath } : { MARKDOWN_FILE: inputPath },
    SSE_PORT: String(ssePort)
  };
  const broadcastReload = () => {
    for (const client of clients) client.write("data: reload\n\n");
  };
  if (isDirectory) {
    (0, import_node_fs.watch)(inputPath, { recursive: true }, (_, filename) => {
      if (filename && filename.endsWith(".md")) broadcastReload();
    });
  } else {
    const markdownFilename = import_node_path.default.basename(inputPath);
    (0, import_node_fs.watch)(import_node_path.default.dirname(inputPath), (_, filename) => {
      if (filename === markdownFilename) broadcastReload();
    });
  }
  const me = {
    pid: process.pid,
    port: nextPort,
    ssePort,
    dir: inputPath,
    isDirectory,
    background: parsed.isDetachedChild,
    startedAt: (/* @__PURE__ */ new Date()).toISOString()
  };
  addInstance(me);
  if (!parsed.isDetachedChild) {
    banner();
    line(c.gray("starting preview server for ") + c.blue(tilde(inputPath)) + c.gray(" \u2026"));
  }
  const child = (0, import_node_child_process.spawn)(paths.nextBin, ["start", "--port", String(nextPort)], {
    cwd: paths.packageRoot,
    env: nextEnvironment,
    stdio: ["inherit", "pipe", "inherit"]
  });
  const shutdown = () => {
    try {
      child.kill("SIGTERM");
    } catch {
    }
    try {
      sseServer.close();
    } catch {
    }
    removeInstance(process.pid);
  };
  process.on("exit", shutdown);
  process.on("SIGINT", () => process.exit(0));
  process.on("SIGTERM", () => process.exit(0));
  let ready = false;
  child.stdout?.on("data", (chunk) => {
    if (!ready) {
      const match = chunk.toString().match(/http:\/\/localhost:\d+/);
      if (match) {
        ready = true;
        const url = `http://localhost:${nextPort}`;
        if (!parsed.isDetachedChild) {
          line();
          line(c.green("\u2713 Ready"));
          tree(`${c.bold("URL")}   ${c.blue(url)}`);
          tree(`${c.bold("Path")}  ${c.dim(tilde(inputPath))}`);
          line();
          line(c.gray("live reload on save  \xB7  Ctrl-C to stop"));
          line();
        }
        openBrowser(url);
      }
    }
  });
  child.on("exit", (code) => {
    try {
      sseServer.close();
    } catch {
    }
    removeInstance(process.pid);
    process.exit(code ?? 0);
  });
}

// scripts/blogkit-md.ts
run(process.argv.slice(2)).catch((error) => {
  console.error(error);
  process.exit(1);
});
