import { EventEmitter } from 'node:events';
import c from 'picocolors';
import createDebug from 'debug';

function createHmrEmitter() {
  const emitter = new EventEmitter();
  return emitter;
}
function viteNodeHmrPlugin() {
  const emitter = createHmrEmitter();
  return {
    name: "vite-node:hmr",
    configureServer(server) {
      const _send = server.ws.send;
      server.emitter = emitter;
      server.ws.send = function(payload) {
        _send(payload);
        emitter.emit("message", payload);
      };
    }
  };
}

const debugHmr = createDebug("vite-node:hmr");
const cache = /* @__PURE__ */ new WeakMap();
function getCache(runner) {
  if (!cache.has(runner)) {
    cache.set(runner, {
      hotModulesMap: /* @__PURE__ */ new Map(),
      dataMap: /* @__PURE__ */ new Map(),
      disposeMap: /* @__PURE__ */ new Map(),
      pruneMap: /* @__PURE__ */ new Map(),
      customListenersMap: /* @__PURE__ */ new Map(),
      ctxToListenersMap: /* @__PURE__ */ new Map(),
      messageBuffer: [],
      isFirstUpdate: false,
      pending: false,
      queued: []
    });
  }
  return cache.get(runner);
}
function sendMessageBuffer(runner, emitter) {
  const maps = getCache(runner);
  maps.messageBuffer.forEach((msg) => emitter.emit("custom", msg));
  maps.messageBuffer.length = 0;
}
async function reload(runner, files) {
  Array.from(runner.moduleCache.keys()).forEach((fsPath) => {
    if (!fsPath.includes("node_modules"))
      runner.moduleCache.delete(fsPath);
  });
  return Promise.all(files.map((file) => runner.executeId(file)));
}
function notifyListeners(runner, event, data) {
  const maps = getCache(runner);
  const cbs = maps.customListenersMap.get(event);
  if (cbs)
    cbs.forEach((cb) => cb(data));
}
async function queueUpdate(runner, p) {
  const maps = getCache(runner);
  maps.queued.push(p);
  if (!maps.pending) {
    maps.pending = true;
    await Promise.resolve();
    maps.pending = false;
    const loading = [...maps.queued];
    maps.queued = [];
    (await Promise.all(loading)).forEach((fn) => fn && fn());
  }
}
async function fetchUpdate(runner, { path, acceptedPath }) {
  const maps = getCache(runner);
  const mod = maps.hotModulesMap.get(path);
  if (!mod) {
    return;
  }
  const moduleMap = /* @__PURE__ */ new Map();
  const isSelfUpdate = path === acceptedPath;
  const modulesToUpdate = /* @__PURE__ */ new Set();
  if (isSelfUpdate) {
    modulesToUpdate.add(path);
  } else {
    for (const { deps } of mod.callbacks) {
      deps.forEach((dep) => {
        if (acceptedPath === dep)
          modulesToUpdate.add(dep);
      });
    }
  }
  const qualifiedCallbacks = mod.callbacks.filter(({ deps }) => {
    return deps.some((dep) => modulesToUpdate.has(dep));
  });
  await Promise.all(
    Array.from(modulesToUpdate).map(async (dep) => {
      const disposer = maps.disposeMap.get(dep);
      if (disposer)
        await disposer(maps.dataMap.get(dep));
      try {
        const newMod = await reload(runner, [dep]);
        moduleMap.set(dep, newMod);
      } catch (e) {
        warnFailedFetch(e, dep);
      }
    })
  );
  return () => {
    for (const { deps, fn } of qualifiedCallbacks)
      fn(deps.map((dep) => moduleMap.get(dep)));
    const loggedPath = isSelfUpdate ? path : `${acceptedPath} via ${path}`;
    console.log(`${c.cyan("[vite-node]")} hot updated: ${loggedPath}`);
  };
}
function warnFailedFetch(err, path) {
  if (!err.message.match("fetch"))
    console.error(err);
  console.error(
    `[hmr] Failed to reload ${path}. This could be due to syntax errors or importing non-existent modules. (see errors above)`
  );
}
async function handleMessage(runner, emitter, files, payload) {
  const maps = getCache(runner);
  switch (payload.type) {
    case "connected":
      sendMessageBuffer(runner, emitter);
      break;
    case "update":
      notifyListeners(runner, "vite:beforeUpdate", payload);
      if (maps.isFirstUpdate) {
        reload(runner, files);
        maps.isFirstUpdate = true;
      }
      payload.updates.forEach((update) => {
        if (update.type === "js-update") {
          queueUpdate(runner, fetchUpdate(runner, update));
        } else {
          console.error(`${c.cyan("[vite-node]")} no support css hmr.}`);
        }
      });
      break;
    case "full-reload":
      notifyListeners(runner, "vite:beforeFullReload", payload);
      reload(runner, files);
      break;
    case "prune":
      notifyListeners(runner, "vite:beforePrune", payload);
      payload.paths.forEach((path) => {
        const fn = maps.pruneMap.get(path);
        if (fn)
          fn(maps.dataMap.get(path));
      });
      break;
    case "error": {
      notifyListeners(runner, "vite:error", payload);
      const err = payload.err;
      console.error(`${c.cyan("[vite-node]")} Internal Server Error
${err.message}
${err.stack}`);
      break;
    }
  }
}
function createHotContext(runner, emitter, files, ownerPath) {
  debugHmr("createHotContext", ownerPath);
  const maps = getCache(runner);
  if (!maps.dataMap.has(ownerPath))
    maps.dataMap.set(ownerPath, {});
  const mod = maps.hotModulesMap.get(ownerPath);
  if (mod)
    mod.callbacks = [];
  const newListeners = /* @__PURE__ */ new Map();
  maps.ctxToListenersMap.set(ownerPath, newListeners);
  function acceptDeps(deps, callback = () => {
  }) {
    const mod2 = maps.hotModulesMap.get(ownerPath) || {
      id: ownerPath,
      callbacks: []
    };
    mod2.callbacks.push({
      deps,
      fn: callback
    });
    maps.hotModulesMap.set(ownerPath, mod2);
  }
  const hot = {
    get data() {
      return maps.dataMap.get(ownerPath);
    },
    acceptExports(_, callback) {
      acceptDeps([ownerPath], callback && (([mod2]) => callback(mod2)));
    },
    accept(deps, callback) {
      if (typeof deps === "function" || !deps) {
        acceptDeps([ownerPath], ([mod2]) => deps && deps(mod2));
      } else if (typeof deps === "string") {
        acceptDeps([deps], ([mod2]) => callback && callback(mod2));
      } else if (Array.isArray(deps)) {
        acceptDeps(deps, callback);
      } else {
        throw new TypeError("invalid hot.accept() usage.");
      }
    },
    dispose(cb) {
      maps.disposeMap.set(ownerPath, cb);
    },
    prune(cb) {
      maps.pruneMap.set(ownerPath, cb);
    },
    invalidate() {
      notifyListeners(runner, "vite:invalidate", { path: ownerPath, message: void 0 });
      return reload(runner, files);
    },
    on(event, cb) {
      const addToMap = (map) => {
        const existing = map.get(event) || [];
        existing.push(cb);
        map.set(event, existing);
      };
      addToMap(maps.customListenersMap);
      addToMap(newListeners);
    },
    send(event, data) {
      maps.messageBuffer.push(JSON.stringify({ type: "custom", event, data }));
      sendMessageBuffer(runner, emitter);
    }
  };
  return hot;
}

export { createHmrEmitter as a, createHotContext as c, getCache as g, handleMessage as h, reload as r, sendMessageBuffer as s, viteNodeHmrPlugin as v };