import { PHPResponse as R, LatestSupportedPHPVersion as b, loadPHPRuntime as z, FSHelpers as p, __private__dont__use as g } from "@php-wasm/universal";
import * as f from "comlink";
import { jspi as L } from "wasm-feature-detect";
import { Semaphore as O, joinPaths as k } from "@php-wasm/util";
import { logger as h } from "@php-wasm/logger";
import { journalFSEvents as v } from "@php-wasm/fs-journal";
function B(r, t = void 0) {
  W();
  const e = r instanceof Worker ? r : f.windowEndpoint(r, t), n = f.wrap(e), a = y(n);
  return new Proxy(a, {
    get: (i, s) => s === "isConnected" ? async () => {
      for (; ; )
        try {
          await S(n.isConnected(), 200);
          break;
        } catch {
        }
    } : n[s]
  });
}
async function S(r, t) {
  return new Promise((e, n) => {
    setTimeout(n, t), r.then(e);
  });
}
function G(r, t) {
  W();
  const e = Promise.resolve();
  let n, a;
  const i = new Promise((c, u) => {
    n = c, a = u;
  }), s = y(r), o = new Proxy(s, {
    get: (c, u) => u === "isConnected" ? () => e : u === "isReady" ? () => i : u in c ? c[u] : t == null ? void 0 : t[u]
  });
  return f.expose(
    o,
    typeof window < "u" ? f.windowEndpoint(self.parent) : void 0
  ), [n, a, o];
}
let P = !1;
function W() {
  if (P)
    return;
  P = !0, f.transferHandlers.set("EVENT", {
    canHandle: (e) => e instanceof CustomEvent,
    serialize: (e) => [
      {
        detail: e.detail
      },
      []
    ],
    deserialize: (e) => e
  }), f.transferHandlers.set("FUNCTION", {
    canHandle: (e) => typeof e == "function",
    serialize(e) {
      const { port1: n, port2: a } = new MessageChannel();
      return f.expose(e, n), [a, [a]];
    },
    deserialize(e) {
      return e.start(), f.wrap(e);
    }
  }), f.transferHandlers.set("PHPResponse", {
    canHandle: (e) => typeof e == "object" && e !== null && "headers" in e && "bytes" in e && "errors" in e && "exitCode" in e && "httpStatusCode" in e,
    serialize(e) {
      return [e.toRawData(), []];
    },
    deserialize(e) {
      return R.fromRawData(e);
    }
  });
  const r = f.transferHandlers.get("throw"), t = r == null ? void 0 : r.serialize;
  r.serialize = ({ value: e }) => {
    const n = t({ value: e });
    return e.response && (n[0].value.response = e.response), e.source && (n[0].value.source = e.source), n;
  };
}
function y(r) {
  return new Proxy(r, {
    get(t, e) {
      switch (typeof t[e]) {
        case "function":
          return (...n) => t[e](...n);
        case "object":
          return t[e] === null ? t[e] : y(t[e]);
        case "undefined":
        case "number":
        case "string":
          return t[e];
        default:
          return f.proxy(t[e]);
      }
    }
  });
}
async function M(r = b) {
  if (await L())
    switch (r) {
      case "8.3":
        return await import("./jspi/php_8_3.js");
      case "8.2":
        return await import("./jspi/php_8_2.js");
      case "8.1":
        return await import("./jspi/php_8_1.js");
      case "8.0":
        return await import("./jspi/php_8_0.js");
      case "7.4":
        return await import("./jspi/php_7_4.js");
      case "7.3":
        return await import("./jspi/php_7_3.js");
      case "7.2":
        return await import("./jspi/php_7_2.js");
      case "7.1":
        return await import("./jspi/php_7_1.js");
      case "7.0":
        return await import("./jspi/php_7_0.js");
    }
  else
    switch (r) {
      case "8.3":
        return await import("./asyncify/php_8_3.js");
      case "8.2":
        return await import("./asyncify/php_8_2.js");
      case "8.1":
        return await import("./asyncify/php_8_1.js");
      case "8.0":
        return await import("./asyncify/php_8_0.js");
      case "7.4":
        return await import("./asyncify/php_7_4.js");
      case "7.3":
        return await import("./asyncify/php_7_3.js");
      case "7.2":
        return await import("./asyncify/php_7_2.js");
      case "7.1":
        return await import("./asyncify/php_7_1.js");
      case "7.0":
        return await import("./asyncify/php_7_0.js");
    }
  throw new Error(`Unsupported PHP version ${r}`);
}
const I = () => ({
  websocket: {
    decorator: (r) => class extends r {
      constructor() {
        try {
          super();
        } catch {
        }
      }
      send() {
        return null;
      }
    }
  }
});
async function Q(r, t = {}) {
  var n;
  const e = await M(r);
  return (n = t.onPhpLoaderModuleLoaded) == null || n.call(t, e), await z(e, {
    ...t.emscriptenOptions || {},
    ...I()
  });
}
function X(r, t) {
  window.addEventListener("message", (e) => {
    e.source === r.contentWindow && (t && e.origin !== t || typeof e.data != "object" || e.data.type !== "relay" || window.parent.postMessage(e.data, "*"));
  }), window.addEventListener("message", (e) => {
    var n;
    e.source === window.parent && (typeof e.data != "object" || e.data.type !== "relay" || (n = r == null ? void 0 : r.contentWindow) == null || n.postMessage(e.data));
  });
}
async function Y(r) {
  const t = new Worker(r, { type: "module" });
  return new Promise((e, n) => {
    t.onerror = (i) => {
      const s = new Error(
        `WebWorker failed to load at ${r}. ${i.message ? `Original error: ${i.message}` : ""}`
      );
      s.filename = i.filename, n(s);
    };
    function a(i) {
      i.data === "worker-script-started" && (e(t), t.removeEventListener("message", a));
    }
    t.addEventListener("message", a);
  });
}
function Z(r, t = { initialSync: {} }) {
  return t = {
    ...t,
    initialSync: {
      ...t.initialSync,
      direction: t.initialSync.direction ?? "opfs-to-memfs"
    }
  }, async function(e, n, a) {
    return t.initialSync.direction === "opfs-to-memfs" ? (p.fileExists(n, a) && p.rmdir(n, a), p.mkdir(n, a), await _(n, r, a)) : await x(
      n,
      r,
      a,
      t.initialSync.onProgress
    ), A(e, r, a);
  };
}
async function _(r, t, e) {
  p.mkdir(r, e);
  const n = new O({
    concurrency: 40
  }), a = [], i = [
    [t, e]
  ];
  for (; i.length > 0; ) {
    const [s, o] = i.pop();
    for await (const c of s.values()) {
      const u = n.run(async () => {
        const w = k(
          o,
          c.name
        );
        if (c.kind === "directory") {
          try {
            r.mkdir(w);
          } catch (l) {
            if ((l == null ? void 0 : l.errno) !== 20)
              throw h.error(l), l;
          }
          i.push([c, w]);
        } else if (c.kind === "file") {
          const l = await c.getFile(), d = new Uint8Array(await l.arrayBuffer());
          r.createDataFile(
            o,
            c.name,
            d,
            !0,
            !0,
            !0
          );
        }
        a.splice(a.indexOf(u), 1);
      });
      a.push(u);
    }
    for (; i.length === 0 && a.length > 0; )
      await Promise.any(a);
  }
}
async function x(r, t, e, n) {
  r.mkdirTree(e);
  const a = [];
  async function i(w, l) {
    await Promise.all(
      r.readdir(w).filter(
        (d) => d !== "." && d !== ".."
      ).map(async (d) => {
        const m = k(w, d);
        if (!$(r, m)) {
          a.push([l, m, d]);
          return;
        }
        const D = await l.getDirectoryHandle(d, {
          create: !0
        });
        return await i(m, D);
      })
    );
  }
  await i(e, t);
  let s = 0;
  const o = n && j(n, 100), c = 100, u = /* @__PURE__ */ new Set();
  try {
    for (const [w, l, d] of a) {
      const m = C(
        w,
        d,
        r,
        l
      ).then(() => {
        s++, u.delete(m), o == null || o({
          files: s,
          total: a.length
        });
      });
      u.add(m), u.size >= c && (await Promise.race(u), o == null || o({
        files: s,
        total: a.length
      }));
    }
  } finally {
    await Promise.allSettled(u);
  }
}
function $(r, t) {
  return r.isDir(r.lookupPath(t, { follow: !0 }).node.mode);
}
async function C(r, t, e, n) {
  let a;
  try {
    a = e.readFile(n, {
      encoding: "binary"
    });
  } catch {
    return;
  }
  const i = await r.getFileHandle(t, { create: !0 }), s = i.createWritable !== void 0 ? (
    // Google Chrome, Firefox, probably more browsers
    await i.createWritable()
  ) : (
    // Safari
    await i.createSyncAccessHandle()
  );
  try {
    await s.truncate(0), await s.write(a);
  } finally {
    await s.close();
  }
}
function A(r, t, e) {
  const n = [], a = v(r, e, (o) => {
    n.push(o);
  }), i = new J(r, t, e);
  async function s() {
    const o = await r.semaphore.acquire();
    try {
      for (; n.length; )
        await i.processEntry(n.shift());
    } finally {
      o();
    }
  }
  return r.addEventListener("request.end", s), function() {
    a(), r.removeEventListener("request.end", s);
  };
}
class J {
  constructor(t, e, n) {
    this.php = t, this.opfs = e, this.memfsRoot = E(n);
  }
  toOpfsPath(t) {
    return E(t.substring(this.memfsRoot.length));
  }
  async processEntry(t) {
    if (!t.path.startsWith(this.memfsRoot) || t.path === this.memfsRoot)
      return;
    const e = this.toOpfsPath(t.path), n = await T(this.opfs, e), a = H(e);
    if (a)
      try {
        if (t.operation === "DELETE")
          try {
            await n.removeEntry(a, {
              recursive: !0
            });
          } catch {
          }
        else if (t.operation === "CREATE")
          t.nodeType === "directory" ? await n.getDirectoryHandle(a, {
            create: !0
          }) : await n.getFileHandle(a, {
            create: !0
          });
        else if (t.operation === "WRITE")
          await C(
            n,
            a,
            this.php[g].FS,
            t.path
          );
        else if (t.operation === "RENAME" && t.toPath.startsWith(this.memfsRoot)) {
          const i = this.toOpfsPath(t.toPath), s = await T(
            this.opfs,
            i
          ), o = H(i);
          if (t.nodeType === "directory") {
            const c = await s.getDirectoryHandle(
              a,
              {
                create: !0
              }
            );
            await x(
              this.php[g].FS,
              c,
              t.toPath
            ), await n.removeEntry(a, {
              recursive: !0
            });
          } else
            (await n.getFileHandle(a)).move(s, o);
        }
      } catch (i) {
        throw h.log({ entry: t, name: a }), h.error(i), i;
      }
  }
}
function E(r) {
  return r.replace(/\/$/, "").replace(/\/\/+/g, "/");
}
function H(r) {
  return r.substring(r.lastIndexOf("/") + 1);
}
async function T(r, t) {
  const e = t.replace(/^\/+|\/+$/g, "").replace(/\/+/, "/");
  if (!e)
    return r;
  const n = e.split("/");
  let a = r;
  for (let i = 0; i < n.length - 1; i++) {
    const s = n[i];
    a = await a.getDirectoryHandle(s, { create: !0 });
  }
  return a;
}
function j(r, t) {
  let e = 0, n, a;
  return function(...s) {
    a = s;
    const o = Date.now() - e;
    if (n === void 0) {
      const c = Math.max(0, t - o);
      n = setTimeout(() => {
        n = void 0, e = Date.now(), r(...a);
      }, c);
    }
  };
}
export {
  B as consumeAPI,
  Z as createDirectoryHandleMountHandler,
  G as exposeAPI,
  M as getPHPLoaderModule,
  Q as loadWebRuntime,
  X as setupPostMessageRelay,
  Y as spawnPHPWorkerThread
};
