import { logger as p, errorLogPath as G } from "@php-wasm/logger";
import { ProgressTracker as K, EmscriptenDownloadMonitor as ee } from "@php-wasm/progress";
import { SupportedPHPVersions as te, PHPResponse as re, consumeAPI as U, exposeAPI as oe, exposeSyncAPI as ie } from "@php-wasm/universal";
import { resolveRemoteBlueprint as ne, isBlueprintBundle as se, compileBlueprint as ae, runBlueprintSteps as le } from "@wp-playground/blueprints";
import { RecommendedPHPVersion as O, zipDirectory as j, unzipFile as pe } from "@wp-playground/common";
import b from "fs";
import g, { basename as de } from "path";
import { MessageChannel as ce, Worker as ue } from "worker_threads";
import { resolveWordPressRelease as fe } from "@wp-playground/wordpress";
import { p as D, a as me, e as we } from "./mounts-B-Qdcyyt.js";
import v from "fs-extra";
import he, { cpus as ye } from "os";
import be from "express";
import { FileLockManagerForNode as ge } from "@php-wasm/node";
import { jspi as Pe } from "wasm-feature-detect";
import ke from "yargs";
import { NodeJsFilesystem as ve, OverlayFilesystem as We, InMemoryFilesystem as Re, ZipFilesystem as Se } from "@wp-playground/storage";
const $ = g.join(he.homedir(), ".wordpress-playground");
async function xe(e) {
  return await Z(
    "https://github.com/WordPress/sqlite-database-integration/archive/refs/heads/develop.zip",
    "sqlite.zip",
    e
  );
}
async function Z(e, r, t) {
  const s = g.join($, r);
  return v.existsSync(s) || (v.ensureDirSync($), await Le(e, s, t)), V(s);
}
async function Le(e, r, t) {
  const i = (await t.monitorFetch(fetch(e))).body.getReader(), o = `${r}.partial`, a = v.createWriteStream(o);
  for (; ; ) {
    const { done: d, value: c } = await i.read();
    if (c && a.write(c), d)
      break;
  }
  a.close(), a.closed || await new Promise((d, c) => {
    a.on("finish", () => {
      v.renameSync(o, r), d(null);
    }), a.on("error", (M) => {
      v.removeSync(o), c(M);
    });
  });
}
function V(e, r) {
  return new File([v.readFileSync(e)], de(e));
}
async function Fe(e) {
  const r = be(), t = await new Promise((o, a) => {
    const d = r.listen(e.port, () => {
      const c = d.address();
      c === null || typeof c == "string" ? a(new Error("Server address is not available")) : o(d);
    });
  });
  r.use("/", async (o, a) => {
    const d = await e.handleRequest({
      url: o.url,
      headers: Me(o),
      method: o.method,
      body: await Ce(o)
    });
    a.statusCode = d.httpStatusCode;
    for (const c in d.headers)
      a.setHeader(c, d.headers[c]);
    a.end(d.bytes);
  });
  const i = t.address().port;
  return await e.onBind(t, i);
}
const Ce = async (e) => await new Promise((r) => {
  const t = [];
  e.on("data", (s) => {
    t.push(s);
  }), e.on("end", () => {
    r(new Uint8Array(Buffer.concat(t)));
  });
}), Me = (e) => {
  const r = {};
  if (e.rawHeaders && e.rawHeaders.length)
    for (let t = 0; t < e.rawHeaders.length; t += 2)
      r[e.rawHeaders[t].toLowerCase()] = e.rawHeaders[t + 1];
  return r;
}, Ie = "" + new URL("worker-thread-CYvRK9UX.js", import.meta.url).href;
class $e {
  constructor(r) {
    this.workerLoads = [], this.addWorker(r);
  }
  addWorker(r) {
    this.workerLoads.push({
      worker: r,
      activeRequests: /* @__PURE__ */ new Set()
    });
  }
  async handleRequest(r) {
    let t = this.workerLoads[0];
    for (let i = 1; i < this.workerLoads.length; i++) {
      const o = this.workerLoads[i];
      o.activeRequests.size < t.activeRequests.size && (t = o);
    }
    const s = t.worker.request(r);
    return t.activeRequests.add(s), s.url = r.url, s.finally(() => {
      t.activeRequests.delete(s);
    });
  }
}
function Ee(e) {
  return /^latest$|^trunk$|^nightly$|^(?:(\d+)\.(\d+)(?:\.(\d+))?)((?:-beta(?:\d+)?)|(?:-RC(?:\d+)?))?$/.test(e);
}
class N extends Error {
  constructor(r, t) {
    super(r, {
      ...t,
      cause: {
        isReportableInCLI: !0
      }
    }), this.isReportableInCLI = !0, this.isReportableInCLI = !0;
  }
  static getReportableCause(r) {
    let t = 0;
    const s = 15, i = [r];
    for (; i.length > 0 && t < s; ) {
      const o = i.pop();
      if (o instanceof Error) {
        if (o.isReportableInCLI)
          return o;
        Array.isArray(o.cause) ? i.push(...o.cause) : i.push(o.cause), t++, t >= s && p.warn(
          "Recursion limit exceeded while checking if error is reportable"
        );
      }
    }
    return null;
  }
}
async function Be({
  sourceString: e,
  blueprintMayReadAdjacentFiles: r
}) {
  if (!e)
    return;
  if (e.startsWith("http://") || e.startsWith("https://"))
    return await ne(e);
  let t = g.resolve(process.cwd(), e);
  if (!b.existsSync(t))
    throw new Error(`Blueprint file does not exist: ${t}`);
  const s = b.statSync(t);
  if (s.isDirectory() && (t = g.join(t, "blueprint.json")), !s.isFile() && s.isSymbolicLink())
    throw new Error(
      `Blueprint path is neither a file nor a directory: ${t}`
    );
  const i = g.extname(t);
  switch (i) {
    case ".zip":
      return Se.fromArrayBuffer(
        b.readFileSync(t).buffer
      );
    case ".json": {
      const o = b.readFileSync(t, "utf-8");
      try {
        JSON.parse(o);
      } catch {
        throw new Error(
          `Blueprint file at ${t} is not a valid JSON file`
        );
      }
      const a = g.dirname(t), d = new ve(a);
      return new We([
        new Re({
          "blueprint.json": o
        }),
        /**
         * Wrap the NodeJS filesystem to prevent access to local files
         * unless the user explicitly allowed it.
         */
        {
          read(c) {
            if (!r)
              throw new N(
                `Error: Blueprint contained tried to read a local file at path "${c}" (via a resource of type "bundled"). Playground restricts access to local resources by default as a security measure. 

You can allow this Blueprint to read files from the same parent directory by explicitly adding the --blueprint-may-read-adjacent-files option to your command.`
              );
            return d.read(c);
          }
        }
      ]);
    }
    default:
      throw new Error(
        `Unsupported blueprint file extension: ${i}. Only .zip and .json files are supported.`
      );
  }
}
async function Ke() {
  const e = ke(process.argv.slice(2)).usage("Usage: wp-playground <command> [options]").positional("command", {
    describe: "Command to run",
    choices: ["server", "run-blueprint", "build-snapshot"],
    demandOption: !0
  }).option("outfile", {
    describe: "When building, write to this output file.",
    type: "string",
    default: "wordpress.zip"
  }).option("port", {
    describe: "Port to listen on when serving.",
    type: "number",
    default: 9400
  }).option("php", {
    describe: "PHP version to use.",
    type: "string",
    default: O,
    choices: te
  }).option("wp", {
    describe: "WordPress version to use.",
    type: "string",
    default: "latest"
  }).option("mount", {
    describe: "Mount a directory to the PHP runtime (can be used multiple times). Format: /host/path:/vfs/path",
    type: "array",
    string: !0,
    coerce: D
  }).option("mount-before-install", {
    describe: "Mount a directory to the PHP runtime before WordPress installation (can be used multiple times). Format: /host/path:/vfs/path",
    type: "array",
    string: !0,
    coerce: D
  }).option("mount-dir", {
    describe: 'Mount a directory to the PHP runtime (can be used multiple times). Format: "/host/path" "/vfs/path"',
    type: "array",
    nargs: 2,
    array: !0
    // coerce: parseMountDirArguments,
  }).option("mount-dir-before-install", {
    describe: 'Mount a directory before WordPress installation (can be used multiple times). Format: "/host/path" "/vfs/path"',
    type: "string",
    nargs: 2,
    array: !0,
    coerce: me
  }).option("login", {
    describe: "Should log the user in",
    type: "boolean",
    default: !1
  }).option("blueprint", {
    describe: "Blueprint to execute.",
    type: "string"
  }).option("blueprint-may-read-adjacent-files", {
    describe: 'Consent flag: Allow "bundled" resources in a local blueprint to read files in the same directory as the blueprint file.',
    type: "boolean",
    default: !1
  }).option("skip-wordpress-setup", {
    describe: "Do not download, unzip, and install WordPress. Useful for mounting a pre-configured WordPress directory at /wordpress.",
    type: "boolean",
    default: !1
  }).option("skip-sqlite-setup", {
    describe: "Skip the SQLite integration plugin setup to allow the WordPress site to use MySQL.",
    type: "boolean",
    default: !1
  }).option("quiet", {
    describe: "Do not output logs and progress messages.",
    type: "boolean",
    default: !1
  }).option("debug", {
    describe: "Print PHP error log content if an error occurs during Playground boot.",
    type: "boolean",
    default: !1
  }).option("auto-mount", {
    describe: "Automatically mount the current working directory. You can mount a WordPress directory, a plugin directory, a theme directory, a wp-content directory, or any directory containing PHP and HTML files.",
    type: "boolean",
    default: !1
  }).option("follow-symlinks", {
    describe: `Allow Playground to follow symlinks by automatically mounting symlinked directories and files encountered in mounted directories. 
Warning: Following symlinks will expose files outside mounted directories to Playground and could be a security risk.`,
    type: "boolean",
    default: !1
  }).option("experimentalTrace", {
    describe: "Print detailed messages about system behavior to the console. Useful for troubleshooting.",
    type: "boolean",
    default: !1,
    // Hide this option because we want to replace with a more general log-level flag.
    hidden: !0
  }).option("internal-cookie-store", {
    describe: "Enable internal cookie handling. When enabled, Playground will manage cookies internally using an HttpCookieStore that persists cookies across requests. When disabled, cookies are handled externally (e.g., by a browser in Node.js environments).",
    type: "boolean",
    default: !1
  }).option("xdebug", {
    describe: "Enable Xdebug.",
    type: "boolean",
    default: !1
  }).option("experimentalMultiWorker", {
    describe: "Enable experimental multi-worker support which requires JSPI and a /wordpress directory backed by a real filesystem. Pass a positive number to specify the number of workers to use. Otherwise, default to the number of CPUs minus 1.",
    type: "number",
    coerce: (i) => i ?? ye().length - 1
  }).showHelpOnFail(!1).check(async (i) => {
    if (i.wp !== void 0 && !Ee(i.wp))
      try {
        new URL(i.wp);
      } catch {
        throw new Error(
          'Unrecognized WordPress version. Please use "latest", a URL, or a numeric version such as "6.2", "6.0.1", "6.2-beta1", or "6.2-RC1"'
        );
      }
    if (i.experimentalMultiWorker !== void 0) {
      if (i.experimentalMultiWorker <= 1)
        throw new Error(
          "The --experimentalMultiWorker flag must be a positive integer greater than 1."
        );
      const o = (a) => a.vfsPath === "/wordpress";
      if (!i.mount?.some(o) && !i["mount-before-install"]?.some(
        o
      ))
        throw new Error(
          "Please mount a real filesystem directory as the /wordpress directory before using the --experimentalMultiWorker flag."
        );
    }
    return !0;
  });
  e.wrap(e.terminalWidth());
  const r = await e.argv, t = r._[0];
  ["run-blueprint", "server", "build-snapshot"].includes(t) || (e.showHelp(), process.exit(1));
  const s = {
    ...r,
    command: t,
    blueprint: await Be({
      sourceString: r.blueprint,
      blueprintMayReadAdjacentFiles: r.blueprintMayReadAdjacentFiles
    }),
    mount: [...r.mount || [], ...r["mount-dir"] || []],
    "mount-before-install": [
      ...r["mount-before-install"] || [],
      ...r["mount-dir-before-install"] || []
    ]
  };
  try {
    return Ae(s);
  } catch (i) {
    const o = N.getReportableCause(i);
    if (o)
      console.log(""), console.log(o.message), process.exit(1);
    else
      throw i;
  }
}
async function Ae(e) {
  let r, t;
  const s = [];
  e.autoMount && (e = we(e));
  async function i(n) {
    await t.run({
      code: `<?php
			$zip = new ZipArchive();
			if(false === $zip->open('/tmp/build.zip', ZipArchive::CREATE | ZipArchive::OVERWRITE)) {
				throw new Exception('Failed to create ZIP');
			}
			$files = new RecursiveIteratorIterator(
				new RecursiveDirectoryIterator('/wordpress')
			);
			foreach ($files as $file) {
				echo $file . PHP_EOL;
				if (!$file->isFile()) {
					continue;
				}
				$zip->addFile($file->getPathname(), $file->getPathname());
			}
			$zip->close();

		`
    });
    const l = await t.readFileAsBuffer("/tmp/build.zip");
    b.writeFileSync(n, l);
  }
  async function o(n) {
    const l = se(e.blueprint) ? e.blueprint : {
      login: e.login,
      ...e.blueprint,
      preferredVersions: {
        php: e.php ?? e?.blueprint?.preferredVersions?.php ?? O,
        wp: e.wp ?? e?.blueprint?.preferredVersions?.wp ?? "latest",
        ...e.blueprint?.preferredVersions || {}
      }
    }, f = new K();
    let w = "", m = !1;
    return f.addEventListener("progress", (u) => {
      if (m)
        return;
      m = u.detail.progress === 100;
      const R = Math.floor(u.detail.progress);
      w = u.detail.caption || w || "Running the Blueprint";
      const P = `${w.trim()} – ${R}%`;
      e.quiet || d(
        process.stdout,
        P,
        m
      );
    }), await ae(l, {
      progress: f,
      additionalSteps: n
    });
  }
  let a = "";
  function d(n, l, f) {
    l !== a && (a = l, n.isTTY ? (n.cursorTo(0), n.write(l), n.clearLine(1), f && n.write(`
`)) : n.write(`${l}
`));
  }
  async function c(n) {
    const l = new ue(n);
    return new Promise(
      (f, w) => {
        l.once("message", function(m) {
          m.command === "worker-script-initialized" && f({ worker: l, phpPort: m.phpPort });
        }), l.once("error", function(m) {
          console.error(m);
          const u = new Error(
            `Worker failed to load at ${n}. ${m.message ? `Original error: ${m.message}` : ""}`
          );
          u.filename = n, w(u);
        });
      }
    );
  }
  function M(n) {
    const l = new URL(
      Ie,
      import.meta.url
    ), f = [];
    for (let w = 0; w < n; w++)
      f.push(c(l));
    return Promise.all(f);
  }
  e.quiet && (p.handlers = []);
  const W = await o(
    e["additional-blueprint-steps"] || []
  ), J = await import("fs-ext").then((n) => n.flockSync).catch(() => {
    p.warn(
      "The fs-ext package is not installed. Internal file locking will not be integrated with host OS file locking."
    );
  }), E = new ge(J);
  async function B() {
    const { port1: n, port2: l } = new ce();
    return await Pe() ? oe(E, null, n) : await ie(E, n), l;
  }
  let A = !1;
  return p.log("Starting a PHP server..."), Fe({
    port: e.port,
    onBind: async (n, l) => {
      const f = `http://127.0.0.1:${l}`, w = e.experimentalMultiWorker ?? 1, m = M(w);
      p.log(`Setting up WordPress ${e.wp}`);
      let u;
      const R = new ee();
      if (!e.skipWordPressSetup) {
        let h = !1;
        R.addEventListener("progress", (k) => {
          if (h)
            return;
          const { loaded: x, total: I } = k.detail, L = Math.floor(
            Math.min(100, 100 * x / I)
          );
          h = L === 100, e.quiet || d(
            process.stdout,
            `Downloading WordPress ${L}%...`,
            h
          );
        }), u = await fe(e.wp), p.log(
          `Resolved WordPress release URL: ${u?.releaseUrl}`
        );
      }
      const P = u && g.join(
        $,
        `prebuilt-wp-content-for-wp-${u.version}.zip`
      ), H = u ? b.existsSync(P) ? V(P) : await Z(
        u.releaseUrl,
        `${u.version}.zip`,
        R
      ) : void 0;
      p.log("Fetching SQLite integration plugin...");
      const X = e.skipSqliteSetup ? void 0 : await xe(R), z = e.followSymlinks === !0, q = e.experimentalTrace === !0;
      try {
        const h = e["mount-before-install"] || [], k = e.mount || [], [x, ...I] = await m;
        t = U(
          x.phpPort
        ), s.push({
          playground: t,
          worker: x.worker
        }), await t.isConnected();
        const L = await B();
        p.log("Booting WordPress...");
        const F = Math.floor(
          Number.MAX_SAFE_INTEGER / w
        );
        if (await t.useFileLockManager(L), await t.boot({
          phpVersion: W.versions.php,
          wpVersion: W.versions.wp,
          absoluteUrl: f,
          mountsBeforeWpInstall: h,
          mountsAfterWpInstall: k,
          wordPressZip: H && await H.arrayBuffer(),
          sqliteIntegrationPluginZip: await X.arrayBuffer(),
          firstProcessId: 0,
          processIdSpaceLength: F,
          followSymlinks: z,
          trace: q,
          internalCookieStore: e.internalCookieStore,
          withXdebug: e.xdebug
        }), u && !e["mount-before-install"] && !b.existsSync(P) && (p.log(
          "Caching preinstalled WordPress for the next boot..."
        ), b.writeFileSync(
          P,
          await j(t, "/wordpress")
        ), p.log("Cached!")), r = new $e(t), await t.isReady(), A = !0, p.log("Booted!"), W && (p.log("Running the Blueprint..."), await le(W, t), p.log("Finished running the blueprint")), e.command === "build-snapshot" ? (await i(e.outfile), p.log(`WordPress exported to ${e.outfile}`), process.exit(0)) : e.command === "run-blueprint" && (p.log("Blueprint executed"), process.exit(0)), e.experimentalMultiWorker && e.experimentalMultiWorker > 1) {
          p.log("Preparing additional workers...");
          const T = await j(
            t,
            "/internal"
          ), S = F;
          await Promise.all(
            I.map(async (C, _) => {
              const y = U(C.phpPort);
              s.push({
                playground: y,
                worker: C.worker
              }), await y.isConnected();
              const Y = S + _ * F, Q = await B();
              await y.useFileLockManager(
                Q
              ), await y.boot({
                phpVersion: W.versions.php,
                absoluteUrl: f,
                mountsBeforeWpInstall: h,
                mountsAfterWpInstall: k,
                // Skip WordPress zip because we share the /wordpress directory
                // populated by the initial worker.
                wordPressZip: void 0,
                // Skip SQLite integration plugin for now because we
                // will copy it from primary's `/internal` directory.
                sqliteIntegrationPluginZip: void 0,
                dataSqlPath: "/wordpress/wp-content/database/.ht.sqlite",
                firstProcessId: Y,
                processIdSpaceLength: F,
                followSymlinks: z,
                trace: q,
                internalCookieStore: e.internalCookieStore,
                withXdebug: e.xdebug
              }), await y.isReady(), await y.writeFile(
                "/tmp/internal.zip",
                T
              ), await pe(
                y,
                "/tmp/internal.zip",
                "/internal"
              ), await y.unlink(
                "/tmp/internal.zip"
              ), r.addWorker(y);
            })
          ), p.log("Ready!");
        }
        return p.log(`WordPress is running on ${f}`), {
          playground: t,
          server: n,
          [Symbol.asyncDispose]: async function() {
            await Promise.all(
              s.map(
                async ({ playground: S, worker: C }) => {
                  await S.dispose(), await C.terminate();
                }
              )
            ), await new Promise((S) => n.close(S));
          }
        };
      } catch (h) {
        if (!e.debug)
          throw h;
        const k = await t.readFileAsText(G);
        throw new Error(k, { cause: h });
      }
    },
    async handleRequest(n) {
      return A ? await r.handleRequest(n) : re.forHttpCode(
        502,
        "WordPress is not ready yet"
      );
    }
  });
}
export {
  Ke as p,
  Ae as r
};
//# sourceMappingURL=run-cli-W9VNXESj.js.map
