import process from 'node:process';
import sirv from 'sirv';
import c from 'picocolors';
import { debounce } from 'perfect-debounce';
import { dirname, resolve, isAbsolute, join } from 'node:path';
import { fileURLToPath } from 'node:url';
import { Buffer as Buffer$1 } from 'node:buffer';
import { createFilter } from '@rollup/pluginutils';
import Debug from 'debug';
import { parse } from 'error-stack-parser-es';
import fs from 'fs-extra';
import { createServer } from 'node:http';

const DEFAULT_TIMEOUT = 6e4;
function defaultSerialize(i) {
  return i;
}
const defaultDeserialize = defaultSerialize;
const { clearTimeout, setTimeout: setTimeout$1 } = globalThis;
const random = Math.random.bind(Math);
function createBirpc(functions, options) {
  const {
    post,
    on,
    eventNames = [],
    serialize = defaultSerialize,
    deserialize = defaultDeserialize,
    resolver,
    timeout = DEFAULT_TIMEOUT
  } = options;
  const rpcPromiseMap = /* @__PURE__ */ new Map();
  let _promise;
  const rpc = new Proxy({}, {
    get(_, method) {
      if (method === "$functions")
        return functions;
      const sendEvent = (...args) => {
        post(serialize({ m: method, a: args, t: "q" }));
      };
      if (eventNames.includes(method)) {
        sendEvent.asEvent = sendEvent;
        return sendEvent;
      }
      const sendCall = async (...args) => {
        await _promise;
        return new Promise((resolve, reject) => {
          const id = nanoid();
          let timeoutId;
          if (timeout >= 0) {
            timeoutId = setTimeout$1(() => {
              reject(new Error(`[birpc] timeout on calling "${method}"`));
              rpcPromiseMap.delete(id);
            }, timeout).unref?.();
          }
          rpcPromiseMap.set(id, { resolve, reject, timeoutId });
          post(serialize({ m: method, a: args, i: id, t: "q" }));
        });
      };
      sendCall.asEvent = sendEvent;
      return sendCall;
    }
  });
  _promise = on(async (data, ...extra) => {
    const msg = deserialize(data);
    if (msg.t === "q") {
      const { m: method, a: args } = msg;
      let result, error;
      const fn = resolver ? resolver(method, functions[method]) : functions[method];
      if (!fn) {
        error = new Error(`[birpc] function "${method}" not found`);
      } else {
        try {
          result = await fn.apply(rpc, args);
        } catch (e) {
          error = e;
        }
      }
      if (msg.i) {
        if (error && options.onError)
          options.onError(error, method, args);
        post(serialize({ t: "s", i: msg.i, r: result, e: error }), ...extra);
      }
    } else {
      const { i: ack, r: result, e: error } = msg;
      const promise = rpcPromiseMap.get(ack);
      if (promise) {
        clearTimeout(promise.timeoutId);
        if (error)
          promise.reject(error);
        else
          promise.resolve(result);
      }
      rpcPromiseMap.delete(ack);
    }
  });
  return rpc;
}
const cacheMap = /* @__PURE__ */ new WeakMap();
function cachedMap(items, fn) {
  return items.map((i) => {
    let r = cacheMap.get(i);
    if (!r) {
      r = fn(i);
      cacheMap.set(i, r);
    }
    return r;
  });
}
function createBirpcGroup(functions, channels, options = {}) {
  const getChannels = () => typeof channels === "function" ? channels() : channels;
  const getClients = (channels2 = getChannels()) => cachedMap(channels2, (s) => createBirpc(functions, { ...options, ...s }));
  const broadcastProxy = new Proxy({}, {
    get(_, method) {
      const client = getClients();
      const callbacks = client.map((c) => c[method]);
      const sendCall = (...args) => {
        return Promise.all(callbacks.map((i) => i(...args)));
      };
      sendCall.asEvent = (...args) => {
        callbacks.map((i) => i.asEvent(...args));
      };
      return sendCall;
    }
  });
  function updateChannels(fn) {
    const channels2 = getChannels();
    fn?.(channels2);
    return getClients(channels2);
  }
  getClients();
  return {
    get clients() {
      return getClients();
    },
    functions,
    updateChannels,
    broadcast: broadcastProxy,
    /**
     * @deprecated use `broadcast`
     */
    // @ts-expect-error deprecated
    boardcast: broadcastProxy
  };
}
const urlAlphabet = "useandom-26T198340PX75pxJACKVERYMINDBUSHWOLF_GQZbfghjklqvwyzrict";
function nanoid(size = 21) {
  let id = "";
  let i = size;
  while (i--)
    id += urlAlphabet[random() * 64 | 0];
  return id;
}

function createRPCServer(name, ws, functions, options = {}) {
  const event = `${name}:rpc`;
  const group = createBirpcGroup(
    functions,
    () => cachedMap(
      Array.from(ws?.clients || []),
      (socket) => {
        return {
          on: (fn) => {
            ws.on(event, (data, source) => {
              if (socket === source)
                fn(data);
            });
          },
          post: (data) => {
            socket.send(event, data);
          }
        };
      }
    ),
    options
  );
  ws.on("connection", () => {
    group.updateChannels();
  });
  return group.broadcast;
}

const DIR_DIST = typeof __dirname !== "undefined" ? __dirname : dirname(fileURLToPath(import.meta.url));
const DIR_CLIENT = resolve(DIR_DIST, "../dist/client");

const DUMMY_LOAD_PLUGIN_NAME = "__load__";

var __defProp$1 = Object.defineProperty;
var __defNormalProp$1 = (obj, key, value) => key in obj ? __defProp$1(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
var __publicField$1 = (obj, key, value) => {
  __defNormalProp$1(obj, typeof key !== "symbol" ? key + "" : key, value);
  return value;
};
class Recorder {
  constructor(context) {
    this.context = context;
    __publicField$1(this, "transform", {});
    __publicField$1(this, "resolveId", {});
    __publicField$1(this, "transformCounter", {});
    this.context = context;
  }
  recordTransform(id, info, preTransformCode) {
    id = this.context.normalizeId(id);
    if (!this.transform[id] || !this.transform[id].some((tr) => tr.result)) {
      this.transform[id] = [{
        name: DUMMY_LOAD_PLUGIN_NAME,
        result: preTransformCode,
        start: info.start,
        end: info.start,
        sourcemaps: info.sourcemaps
      }];
      this.transformCounter[id] = (this.transformCounter[id] || 0) + 1;
    }
    this.transform[id].push(info);
  }
  recordLoad(id, info) {
    id = this.context.normalizeId(id);
    this.transform[id] = [info];
    this.transformCounter[id] = (this.transformCounter[id] || 0) + 1;
  }
  recordResolveId(id, info) {
    id = this.context.normalizeId(id);
    if (!this.resolveId[id])
      this.resolveId[id] = [];
    this.resolveId[id].push(info);
  }
  invalidate(id) {
    id = this.context.normalizeId(id);
    delete this.transform[id];
  }
}

async function openBrowser(address) {
  await import('open').then((r) => r.default(address, { newInstance: true })).catch(() => {
  });
}
function removeVersionQuery(url) {
  if (url.includes("v=")) {
    return url.replace(/&v=\w+/, "").replace(/\?v=\w+/, "?").replace(/\?$/, "");
  }
  return url;
}

var __defProp = Object.defineProperty;
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
var __publicField = (obj, key, value) => {
  __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
  return value;
};
class ViteInspectContext {
  constructor(options) {
    this.options = options;
    __publicField(this, "filter");
    __publicField(this, "config");
    __publicField(this, "recorderClient");
    __publicField(this, "recorderServer");
    this.filter = createFilter(options.include, options.exclude);
    this.recorderClient = new Recorder(this);
    this.recorderServer = new Recorder(this);
  }
  normalizeId(id) {
    if (this.options.removeVersionQuery !== false)
      return removeVersionQuery(id);
    return id;
  }
  getRecorder(ssr) {
    return ssr ? this.recorderServer : this.recorderClient;
  }
  resolveId(id = "", ssr = false) {
    if (id.startsWith("./"))
      id = resolve(this.config.root, id).replace(/\\/g, "/");
    return this.resolveIdRecursive(id, ssr);
  }
  resolveIdRecursive(id, ssr = false) {
    const rec = this.getRecorder(ssr);
    const resolved = rec.resolveId[id]?.[0]?.result;
    return resolved ? this.resolveIdRecursive(resolved, ssr) : id;
  }
  getList(server) {
    const isVirtual = (pluginName) => pluginName !== DUMMY_LOAD_PLUGIN_NAME;
    const getDeps = (id) => Array.from(server.moduleGraph.getModuleById(id)?.importedModules || []).map((i) => i.id || "").filter(Boolean);
    return {
      root: this.config.root,
      modules: this.getModulesInfo(this.recorderClient, getDeps, isVirtual),
      ssrModules: this.getModulesInfo(this.recorderServer, getDeps, isVirtual)
    };
  }
  getModulesInfo(recorder, getDeps, isVirtual) {
    function transformIdMap(recorder2) {
      return Object.values(recorder2.resolveId).reduce((map, ids2) => {
        ids2.forEach((id) => {
          var _a;
          map[_a = id.result] ?? (map[_a] = []);
          map[id.result].push(id);
        });
        return map;
      }, {});
    }
    const transformedIdMap = transformIdMap(recorder);
    const ids = new Set(Object.keys(recorder.transform).concat(Object.keys(transformedIdMap)));
    return Array.from(ids).sort().map((id) => {
      let totalTime = 0;
      const plugins = (recorder.transform[id] || []).filter((tr) => tr.result).map((transItem) => {
        const delta = transItem.end - transItem.start;
        totalTime += delta;
        return { name: transItem.name, transform: delta };
      }).concat(
        // @ts-expect-error transform is optional
        (transformedIdMap[id] || []).map((idItem) => {
          return { name: idItem.name, resolveId: idItem.end - idItem.start };
        })
      );
      function getSize(str) {
        if (!str)
          return 0;
        return Buffer$1.byteLength(str, "utf8");
      }
      return {
        id,
        deps: getDeps ? getDeps(id) : [],
        plugins,
        virtual: isVirtual(plugins[0]?.name || "", recorder.transform[id]?.[0].name || ""),
        totalTime,
        invokeCount: recorder.transformCounter?.[id] || 0,
        sourceSize: getSize(recorder.transform[id]?.[0]?.result),
        distSize: getSize(recorder.transform[id]?.[recorder.transform[id].length - 1]?.result)
      };
    });
  }
  getPluginMetrics(ssr = false) {
    const map = {};
    const defaultMetricInfo = () => ({
      transform: { invokeCount: 0, totalTime: 0 },
      resolveId: { invokeCount: 0, totalTime: 0 }
    });
    this.config.plugins.forEach((i) => {
      map[i.name] = {
        ...defaultMetricInfo(),
        name: i.name,
        enforce: i.enforce
      };
    });
    const recorder = this.getRecorder(ssr);
    Object.values(recorder.transform).forEach((transformInfos) => {
      transformInfos.forEach(({ name, start, end }) => {
        if (name === DUMMY_LOAD_PLUGIN_NAME)
          return;
        if (!map[name])
          map[name] = { ...defaultMetricInfo(), name };
        map[name].transform.totalTime += end - start;
        map[name].transform.invokeCount += 1;
      });
    });
    Object.values(recorder.resolveId).forEach((resolveIdInfos) => {
      resolveIdInfos.forEach(({ name, start, end }) => {
        if (!map[name])
          map[name] = { ...defaultMetricInfo(), name };
        map[name].resolveId.totalTime += end - start;
        map[name].resolveId.invokeCount += 1;
      });
    });
    const metrics = Object.values(map).filter(Boolean).sort((a, b) => a.name.localeCompare(b.name));
    return metrics;
  }
}

const debug = Debug("vite-plugin-inspect");
function hijackHook(plugin, name, wrapper) {
  if (!plugin[name])
    return;
  debug(`hijack plugin "${name}"`, plugin.name);
  let order = plugin.order || plugin.enforce || "normal";
  const hook = plugin[name];
  if ("handler" in hook) {
    const oldFn = hook.handler;
    order += `-${hook.order || hook.enforce || "normal"}`;
    hook.handler = function(...args) {
      return wrapper(oldFn, this, args, order);
    };
  } else if ("transform" in hook) {
    const oldFn = hook.transform;
    order += `-${hook.order || hook.enforce || "normal"}`;
    hook.transform = function(...args) {
      return wrapper(oldFn, this, args, order);
    };
  } else {
    const oldFn = hook;
    plugin[name] = function(...args) {
      return wrapper(oldFn, this, args, order);
    };
  }
}
function hijackPlugin(plugin, ctx) {
  hijackHook(plugin, "transform", async (fn, context, args, order) => {
    const code = args[0];
    const id = args[1];
    const ssr = args[2]?.ssr;
    let _result;
    let error;
    const start = Date.now();
    try {
      _result = await fn.apply(context, args);
    } catch (_err) {
      error = _err;
    }
    const end = Date.now();
    const result = error ? "[Error]" : typeof _result === "string" ? _result : _result?.code;
    if (ctx.filter(id)) {
      const sourcemaps = typeof _result === "string" ? null : _result?.map;
      const rec = ctx.getRecorder(ssr);
      rec.recordTransform(id, {
        name: plugin.name,
        result,
        start,
        end,
        order,
        sourcemaps,
        error: error ? parseError(error) : void 0
      }, code);
    }
    if (error)
      throw error;
    return _result;
  });
  hijackHook(plugin, "load", async (fn, context, args) => {
    const id = args[0];
    const ssr = args[1]?.ssr;
    let _result;
    let error;
    const start = Date.now();
    try {
      _result = await fn.apply(context, args);
    } catch (err) {
      error = err;
    }
    const end = Date.now();
    const result = error ? "[Error]" : typeof _result === "string" ? _result : _result?.code;
    const sourcemaps = typeof _result === "string" ? null : _result?.map;
    if (result) {
      ctx.getRecorder(ssr).recordLoad(id, {
        name: plugin.name,
        result,
        start,
        end,
        sourcemaps,
        error: error ? parseError(error) : void 0
      });
    }
    if (error)
      throw error;
    return _result;
  });
  hijackHook(plugin, "resolveId", async (fn, context, args) => {
    const id = args[0];
    const ssr = args[2]?.ssr;
    let _result;
    let error;
    const start = Date.now();
    try {
      _result = await fn.apply(context, args);
    } catch (err) {
      error = err;
    }
    const end = Date.now();
    if (!ctx.filter(id)) {
      if (error)
        throw error;
      return _result;
    }
    const result = error ? stringifyError(error) : typeof _result === "object" ? _result?.id : _result;
    if (result && result !== id) {
      ctx.getRecorder(ssr).recordResolveId(id, {
        name: plugin.name,
        result,
        start,
        end,
        error
      });
    }
    if (error)
      throw error;
    return _result;
  });
}
function parseError(error) {
  const stack = parse(error, { allowEmpty: true });
  const message = error.message || String(error);
  return {
    message,
    stack,
    raw: error
  };
}
function stringifyError(err) {
  return String(err.stack ? err.stack : err);
}

const defaults = Object.freeze({
  ignoreUnknown: false,
  respectType: false,
  respectFunctionNames: false,
  respectFunctionProperties: false,
  unorderedObjects: true,
  unorderedArrays: false,
  unorderedSets: false,
  excludeKeys: void 0,
  excludeValues: void 0,
  replacer: void 0
});
function objectHash(object, options) {
  if (options) {
    options = { ...defaults, ...options };
  } else {
    options = defaults;
  }
  const hasher = createHasher(options);
  hasher.dispatch(object);
  return hasher.toString();
}
const defaultPrototypesKeys = Object.freeze([
  "prototype",
  "__proto__",
  "constructor"
]);
function createHasher(options) {
  let buff = "";
  let context = /* @__PURE__ */ new Map();
  const write = (str) => {
    buff += str;
  };
  return {
    toString() {
      return buff;
    },
    getContext() {
      return context;
    },
    dispatch(value) {
      if (options.replacer) {
        value = options.replacer(value);
      }
      const type = value === null ? "null" : typeof value;
      return this[type](value);
    },
    object(object) {
      if (object && typeof object.toJSON === "function") {
        return this.object(object.toJSON());
      }
      const objString = Object.prototype.toString.call(object);
      let objType = "";
      const objectLength = objString.length;
      if (objectLength < 10) {
        objType = "unknown:[" + objString + "]";
      } else {
        objType = objString.slice(8, objectLength - 1);
      }
      objType = objType.toLowerCase();
      let objectNumber = null;
      if ((objectNumber = context.get(object)) === void 0) {
        context.set(object, context.size);
      } else {
        return this.dispatch("[CIRCULAR:" + objectNumber + "]");
      }
      if (typeof Buffer !== "undefined" && Buffer.isBuffer && Buffer.isBuffer(object)) {
        write("buffer:");
        return write(object.toString("utf8"));
      }
      if (objType !== "object" && objType !== "function" && objType !== "asyncfunction") {
        if (this[objType]) {
          this[objType](object);
        } else if (!options.ignoreUnknown) {
          this.unkown(object, objType);
        }
      } else {
        let keys = Object.keys(object);
        if (options.unorderedObjects) {
          keys = keys.sort();
        }
        let extraKeys = [];
        if (options.respectType !== false && !isNativeFunction(object)) {
          extraKeys = defaultPrototypesKeys;
        }
        if (options.excludeKeys) {
          keys = keys.filter((key) => {
            return !options.excludeKeys(key);
          });
          extraKeys = extraKeys.filter((key) => {
            return !options.excludeKeys(key);
          });
        }
        write("object:" + (keys.length + extraKeys.length) + ":");
        const dispatchForKey = (key) => {
          this.dispatch(key);
          write(":");
          if (!options.excludeValues) {
            this.dispatch(object[key]);
          }
          write(",");
        };
        for (const key of keys) {
          dispatchForKey(key);
        }
        for (const key of extraKeys) {
          dispatchForKey(key);
        }
      }
    },
    array(arr, unordered) {
      unordered = unordered === void 0 ? options.unorderedArrays !== false : unordered;
      write("array:" + arr.length + ":");
      if (!unordered || arr.length <= 1) {
        for (const entry of arr) {
          this.dispatch(entry);
        }
        return;
      }
      const contextAdditions = /* @__PURE__ */ new Map();
      const entries = arr.map((entry) => {
        const hasher = createHasher(options);
        hasher.dispatch(entry);
        for (const [key, value] of hasher.getContext()) {
          contextAdditions.set(key, value);
        }
        return hasher.toString();
      });
      context = contextAdditions;
      entries.sort();
      return this.array(entries, false);
    },
    date(date) {
      return write("date:" + date.toJSON());
    },
    symbol(sym) {
      return write("symbol:" + sym.toString());
    },
    unkown(value, type) {
      write(type);
      if (!value) {
        return;
      }
      write(":");
      if (value && typeof value.entries === "function") {
        return this.array(
          Array.from(value.entries()),
          true
          /* ordered */
        );
      }
    },
    error(err) {
      return write("error:" + err.toString());
    },
    boolean(bool) {
      return write("bool:" + bool);
    },
    string(string) {
      write("string:" + string.length + ":");
      write(string);
    },
    function(fn) {
      write("fn:");
      if (isNativeFunction(fn)) {
        this.dispatch("[native]");
      } else {
        this.dispatch(fn.toString());
      }
      if (options.respectFunctionNames !== false) {
        this.dispatch("function-name:" + String(fn.name));
      }
      if (options.respectFunctionProperties) {
        this.object(fn);
      }
    },
    number(number) {
      return write("number:" + number);
    },
    xml(xml) {
      return write("xml:" + xml.toString());
    },
    null() {
      return write("Null");
    },
    undefined() {
      return write("Undefined");
    },
    regexp(regex) {
      return write("regex:" + regex.toString());
    },
    uint8array(arr) {
      write("uint8array:");
      return this.dispatch(Array.prototype.slice.call(arr));
    },
    uint8clampedarray(arr) {
      write("uint8clampedarray:");
      return this.dispatch(Array.prototype.slice.call(arr));
    },
    int8array(arr) {
      write("int8array:");
      return this.dispatch(Array.prototype.slice.call(arr));
    },
    uint16array(arr) {
      write("uint16array:");
      return this.dispatch(Array.prototype.slice.call(arr));
    },
    int16array(arr) {
      write("int16array:");
      return this.dispatch(Array.prototype.slice.call(arr));
    },
    uint32array(arr) {
      write("uint32array:");
      return this.dispatch(Array.prototype.slice.call(arr));
    },
    int32array(arr) {
      write("int32array:");
      return this.dispatch(Array.prototype.slice.call(arr));
    },
    float32array(arr) {
      write("float32array:");
      return this.dispatch(Array.prototype.slice.call(arr));
    },
    float64array(arr) {
      write("float64array:");
      return this.dispatch(Array.prototype.slice.call(arr));
    },
    arraybuffer(arr) {
      write("arraybuffer:");
      return this.dispatch(new Uint8Array(arr));
    },
    url(url) {
      return write("url:" + url.toString());
    },
    map(map) {
      write("map:");
      const arr = [...map];
      return this.array(arr, options.unorderedSets !== false);
    },
    set(set) {
      write("set:");
      const arr = [...set];
      return this.array(arr, options.unorderedSets !== false);
    },
    file(file) {
      write("file:");
      return this.dispatch([file.name, file.size, file.type, file.lastModfied]);
    },
    blob() {
      if (options.ignoreUnknown) {
        return write("[blob]");
      }
      throw new Error(
        'Hashing Blob objects is currently not supported\nUse "options.replacer" or "options.ignoreUnknown"\n'
      );
    },
    domwindow() {
      return write("domwindow");
    },
    bigint(number) {
      return write("bigint:" + number.toString());
    },
    /* Node.js standard native objects */
    process() {
      return write("process");
    },
    timer() {
      return write("timer");
    },
    pipe() {
      return write("pipe");
    },
    tcp() {
      return write("tcp");
    },
    udp() {
      return write("udp");
    },
    tty() {
      return write("tty");
    },
    statwatcher() {
      return write("statwatcher");
    },
    securecontext() {
      return write("securecontext");
    },
    connection() {
      return write("connection");
    },
    zlib() {
      return write("zlib");
    },
    context() {
      return write("context");
    },
    nodescript() {
      return write("nodescript");
    },
    httpparser() {
      return write("httpparser");
    },
    dataview() {
      return write("dataview");
    },
    signal() {
      return write("signal");
    },
    fsevent() {
      return write("fsevent");
    },
    tlswrap() {
      return write("tlswrap");
    }
  };
}
const nativeFunc = "[native code] }";
const nativeFuncLength = nativeFunc.length;
function isNativeFunction(f) {
  if (typeof f !== "function") {
    return false;
  }
  return Function.prototype.toString.call(f).slice(-nativeFuncLength) === nativeFunc;
}

class WordArray {
  constructor(words, sigBytes) {
    words = this.words = words || [];
    this.sigBytes = sigBytes === void 0 ? words.length * 4 : sigBytes;
  }
  toString(encoder) {
    return (encoder || Hex).stringify(this);
  }
  concat(wordArray) {
    this.clamp();
    if (this.sigBytes % 4) {
      for (let i = 0; i < wordArray.sigBytes; i++) {
        const thatByte = wordArray.words[i >>> 2] >>> 24 - i % 4 * 8 & 255;
        this.words[this.sigBytes + i >>> 2] |= thatByte << 24 - (this.sigBytes + i) % 4 * 8;
      }
    } else {
      for (let j = 0; j < wordArray.sigBytes; j += 4) {
        this.words[this.sigBytes + j >>> 2] = wordArray.words[j >>> 2];
      }
    }
    this.sigBytes += wordArray.sigBytes;
    return this;
  }
  clamp() {
    this.words[this.sigBytes >>> 2] &= 4294967295 << 32 - this.sigBytes % 4 * 8;
    this.words.length = Math.ceil(this.sigBytes / 4);
  }
  clone() {
    return new WordArray([...this.words]);
  }
}
const Hex = {
  stringify(wordArray) {
    const hexChars = [];
    for (let i = 0; i < wordArray.sigBytes; i++) {
      const bite = wordArray.words[i >>> 2] >>> 24 - i % 4 * 8 & 255;
      hexChars.push((bite >>> 4).toString(16), (bite & 15).toString(16));
    }
    return hexChars.join("");
  }
};
const Base64 = {
  stringify(wordArray) {
    const keyStr = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
    const base64Chars = [];
    for (let i = 0; i < wordArray.sigBytes; i += 3) {
      const byte1 = wordArray.words[i >>> 2] >>> 24 - i % 4 * 8 & 255;
      const byte2 = wordArray.words[i + 1 >>> 2] >>> 24 - (i + 1) % 4 * 8 & 255;
      const byte3 = wordArray.words[i + 2 >>> 2] >>> 24 - (i + 2) % 4 * 8 & 255;
      const triplet = byte1 << 16 | byte2 << 8 | byte3;
      for (let j = 0; j < 4 && i * 8 + j * 6 < wordArray.sigBytes * 8; j++) {
        base64Chars.push(keyStr.charAt(triplet >>> 6 * (3 - j) & 63));
      }
    }
    return base64Chars.join("");
  }
};
const Latin1 = {
  parse(latin1Str) {
    const latin1StrLength = latin1Str.length;
    const words = [];
    for (let i = 0; i < latin1StrLength; i++) {
      words[i >>> 2] |= (latin1Str.charCodeAt(i) & 255) << 24 - i % 4 * 8;
    }
    return new WordArray(words, latin1StrLength);
  }
};
const Utf8 = {
  parse(utf8Str) {
    return Latin1.parse(unescape(encodeURIComponent(utf8Str)));
  }
};
class BufferedBlockAlgorithm {
  constructor() {
    this._data = new WordArray();
    this._nDataBytes = 0;
    this._minBufferSize = 0;
    this.blockSize = 512 / 32;
  }
  reset() {
    this._data = new WordArray();
    this._nDataBytes = 0;
  }
  _append(data) {
    if (typeof data === "string") {
      data = Utf8.parse(data);
    }
    this._data.concat(data);
    this._nDataBytes += data.sigBytes;
  }
  // eslint-disable-next-line @typescript-eslint/no-unused-vars
  _doProcessBlock(_dataWords, _offset) {
  }
  _process(doFlush) {
    let processedWords;
    let nBlocksReady = this._data.sigBytes / (this.blockSize * 4);
    if (doFlush) {
      nBlocksReady = Math.ceil(nBlocksReady);
    } else {
      nBlocksReady = Math.max((nBlocksReady | 0) - this._minBufferSize, 0);
    }
    const nWordsReady = nBlocksReady * this.blockSize;
    const nBytesReady = Math.min(nWordsReady * 4, this._data.sigBytes);
    if (nWordsReady) {
      for (let offset = 0; offset < nWordsReady; offset += this.blockSize) {
        this._doProcessBlock(this._data.words, offset);
      }
      processedWords = this._data.words.splice(0, nWordsReady);
      this._data.sigBytes -= nBytesReady;
    }
    return new WordArray(processedWords, nBytesReady);
  }
}
class Hasher extends BufferedBlockAlgorithm {
  update(messageUpdate) {
    this._append(messageUpdate);
    this._process();
    return this;
  }
  finalize(messageUpdate) {
    if (messageUpdate) {
      this._append(messageUpdate);
    }
  }
}

const H = [
  1779033703,
  -1150833019,
  1013904242,
  -1521486534,
  1359893119,
  -1694144372,
  528734635,
  1541459225
];
const K = [
  1116352408,
  1899447441,
  -1245643825,
  -373957723,
  961987163,
  1508970993,
  -1841331548,
  -1424204075,
  -670586216,
  310598401,
  607225278,
  1426881987,
  1925078388,
  -2132889090,
  -1680079193,
  -1046744716,
  -459576895,
  -272742522,
  264347078,
  604807628,
  770255983,
  1249150122,
  1555081692,
  1996064986,
  -1740746414,
  -1473132947,
  -1341970488,
  -1084653625,
  -958395405,
  -710438585,
  113926993,
  338241895,
  666307205,
  773529912,
  1294757372,
  1396182291,
  1695183700,
  1986661051,
  -2117940946,
  -1838011259,
  -1564481375,
  -1474664885,
  -1035236496,
  -949202525,
  -778901479,
  -694614492,
  -200395387,
  275423344,
  430227734,
  506948616,
  659060556,
  883997877,
  958139571,
  1322822218,
  1537002063,
  1747873779,
  1955562222,
  2024104815,
  -2067236844,
  -1933114872,
  -1866530822,
  -1538233109,
  -1090935817,
  -965641998
];
const W = [];
class SHA256 extends Hasher {
  constructor() {
    super(...arguments);
    this._hash = new WordArray([...H]);
  }
  reset() {
    super.reset();
    this._hash = new WordArray([...H]);
  }
  _doProcessBlock(M, offset) {
    const H2 = this._hash.words;
    let a = H2[0];
    let b = H2[1];
    let c = H2[2];
    let d = H2[3];
    let e = H2[4];
    let f = H2[5];
    let g = H2[6];
    let h = H2[7];
    for (let i = 0; i < 64; i++) {
      if (i < 16) {
        W[i] = M[offset + i] | 0;
      } else {
        const gamma0x = W[i - 15];
        const gamma0 = (gamma0x << 25 | gamma0x >>> 7) ^ (gamma0x << 14 | gamma0x >>> 18) ^ gamma0x >>> 3;
        const gamma1x = W[i - 2];
        const gamma1 = (gamma1x << 15 | gamma1x >>> 17) ^ (gamma1x << 13 | gamma1x >>> 19) ^ gamma1x >>> 10;
        W[i] = gamma0 + W[i - 7] + gamma1 + W[i - 16];
      }
      const ch = e & f ^ ~e & g;
      const maj = a & b ^ a & c ^ b & c;
      const sigma0 = (a << 30 | a >>> 2) ^ (a << 19 | a >>> 13) ^ (a << 10 | a >>> 22);
      const sigma1 = (e << 26 | e >>> 6) ^ (e << 21 | e >>> 11) ^ (e << 7 | e >>> 25);
      const t1 = h + sigma1 + ch + K[i] + W[i];
      const t2 = sigma0 + maj;
      h = g;
      g = f;
      f = e;
      e = d + t1 | 0;
      d = c;
      c = b;
      b = a;
      a = t1 + t2 | 0;
    }
    H2[0] = H2[0] + a | 0;
    H2[1] = H2[1] + b | 0;
    H2[2] = H2[2] + c | 0;
    H2[3] = H2[3] + d | 0;
    H2[4] = H2[4] + e | 0;
    H2[5] = H2[5] + f | 0;
    H2[6] = H2[6] + g | 0;
    H2[7] = H2[7] + h | 0;
  }
  finalize(messageUpdate) {
    super.finalize(messageUpdate);
    const nBitsTotal = this._nDataBytes * 8;
    const nBitsLeft = this._data.sigBytes * 8;
    this._data.words[nBitsLeft >>> 5] |= 128 << 24 - nBitsLeft % 32;
    this._data.words[(nBitsLeft + 64 >>> 9 << 4) + 14] = Math.floor(
      nBitsTotal / 4294967296
    );
    this._data.words[(nBitsLeft + 64 >>> 9 << 4) + 15] = nBitsTotal;
    this._data.sigBytes = this._data.words.length * 4;
    this._process();
    return this._hash;
  }
}
function sha256base64(message) {
  return new SHA256().finalize(message).toString(Base64);
}

function hash(object, options = {}) {
  const hashed = typeof object === "string" ? object : objectHash(object, options);
  return sha256base64(hashed).slice(0, 10);
}

async function generateBuild(ctx, config) {
  const {
    outputDir = ".vite-inspect"
  } = ctx.options;
  const targetDir = isAbsolute(outputDir) ? outputDir : resolve(config.root, outputDir);
  const reportsDir = join(targetDir, "reports");
  await fs.emptyDir(targetDir);
  await fs.ensureDir(reportsDir);
  await fs.copy(DIR_CLIENT, targetDir);
  const isVirtual = (pluginName, transformName) => pluginName !== DUMMY_LOAD_PLUGIN_NAME && transformName !== "vite:load-fallback";
  function list() {
    return {
      root: config.root,
      modules: ctx.getModulesInfo(ctx.recorderClient, null, isVirtual),
      ssrModules: ctx.getModulesInfo(ctx.recorderServer, null, isVirtual)
    };
  }
  async function dumpModuleInfo(dir, recorder, ssr = false) {
    await fs.ensureDir(dir);
    return Promise.all(
      Object.entries(recorder.transform).map(
        ([id, info]) => fs.writeJSON(
          join(dir, `${hash(id)}.json`),
          {
            resolvedId: ctx.resolveId(id, ssr),
            transforms: info
          },
          { spaces: 2 }
        )
      )
    );
  }
  await Promise.all([
    fs.writeFile(
      join(targetDir, "index.html"),
      (await fs.readFile(join(targetDir, "index.html"), "utf-8")).replace(
        'data-vite-inspect-mode="DEV"',
        'data-vite-inspect-mode="BUILD"'
      )
    ),
    fs.writeJSON(
      join(reportsDir, "list.json"),
      list(),
      { spaces: 2 }
    ),
    fs.writeJSON(
      join(reportsDir, "metrics.json"),
      ctx.getPluginMetrics(false),
      { spaces: 2 }
    ),
    fs.writeJSON(
      join(reportsDir, "metrics-ssr.json"),
      ctx.getPluginMetrics(true),
      { spaces: 2 }
    ),
    dumpModuleInfo(join(reportsDir, "transform"), ctx.recorderClient),
    dumpModuleInfo(join(reportsDir, "transform-ssr"), ctx.recorderServer, true)
  ]);
  return targetDir;
}

function createPreviewServer(staticPath) {
  const server = createServer();
  const statics = sirv(staticPath);
  server.on("request", (req, res) => {
    statics(req, res, () => {
      res.statusCode = 404;
      res.end("File not found");
    });
  });
  server.listen(0, () => {
    const { port } = server.address();
    const url = `http://localhost:${port}`;
    console.log(`  ${c.green("\u279C")}  ${c.bold("Inspect Preview Started")}: ${url}`);
    openBrowser(url);
  });
}

const NAME = "vite-plugin-inspect";
const isCI = !!process.env.CI;
function PluginInspect(options = {}) {
  const {
    dev = true,
    build = false,
    silent = false,
    open: _open = false
  } = options;
  if (!dev && !build) {
    return {
      name: NAME
    };
  }
  const ctx = new ViteInspectContext(options);
  const timestampRE = /\bt=\d{13}&?\b/;
  const trailingSeparatorRE = /[?&]$/;
  let config;
  const serverPerf = {
    middleware: {}
  };
  function setupMiddlewarePerf(middlewares) {
    let firstMiddlewareIndex = -1;
    middlewares.forEach((middleware, index) => {
      const { handle: originalHandle } = middleware;
      if (typeof originalHandle !== "function" || !originalHandle.name)
        return middleware;
      middleware.handle = (...middlewareArgs) => {
        var _a;
        let req;
        if (middlewareArgs.length === 4)
          [, req] = middlewareArgs;
        else
          [req] = middlewareArgs;
        const start = Date.now();
        const url = req.url?.replace(timestampRE, "").replace(trailingSeparatorRE, "");
        (_a = serverPerf.middleware)[url] ?? (_a[url] = []);
        if (firstMiddlewareIndex < 0)
          firstMiddlewareIndex = index;
        if (index === firstMiddlewareIndex)
          serverPerf.middleware[url] = [];
        const result = originalHandle(...middlewareArgs);
        Promise.resolve(result).then(() => {
          const total = Date.now() - start;
          const metrics = serverPerf.middleware[url];
          serverPerf.middleware[url].push({
            self: metrics.length ? Math.max(total - metrics[metrics.length - 1].total, 0) : total,
            total,
            name: originalHandle.name
          });
        });
        return result;
      };
      Object.defineProperty(middleware.handle, "name", {
        value: originalHandle.name,
        configurable: true,
        enumerable: true
      });
      return middleware;
    });
  }
  function configureServer(server) {
    const _invalidateModule = server.moduleGraph.invalidateModule;
    server.moduleGraph.invalidateModule = function(...args) {
      const mod = args[0];
      if (mod?.id) {
        ctx.recorderClient.invalidate(mod.id);
        ctx.recorderServer.invalidate(mod.id);
      }
      return _invalidateModule.apply(this, args);
    };
    const base = (options.base ?? server.config.base) || "/";
    server.middlewares.use(`${base}__inspect`, sirv(DIR_CLIENT, {
      single: true,
      dev: true
    }));
    const rpcFunctions = {
      list: () => ctx.getList(server),
      getIdInfo,
      getPluginMetrics: (ssr = false) => ctx.getPluginMetrics(ssr),
      getServerMetrics,
      resolveId: (id, ssr = false) => ctx.resolveId(id, ssr),
      clear: clearId,
      moduleUpdated: () => {
      }
    };
    const rpcServer = createRPCServer("vite-plugin-inspect", server.ws, rpcFunctions);
    const debouncedModuleUpdated = debounce(() => {
      rpcServer.moduleUpdated.asEvent();
    }, 100);
    server.middlewares.use((req, res, next) => {
      debouncedModuleUpdated();
      next();
    });
    function getServerMetrics() {
      return serverPerf || {};
    }
    async function getIdInfo(id, ssr = false, clear = false) {
      if (clear) {
        clearId(id, ssr);
        try {
          await server.transformRequest(id, { ssr });
        } catch {
        }
      }
      const resolvedId = ctx.resolveId(id, ssr);
      const recorder = ctx.getRecorder(ssr);
      return {
        resolvedId,
        transforms: recorder.transform[resolvedId] || []
      };
    }
    function clearId(_id, ssr = false) {
      const id = ctx.resolveId(_id);
      if (id) {
        const mod = server.moduleGraph.getModuleById(id);
        if (mod)
          server.moduleGraph.invalidateModule(mod);
        ctx.getRecorder(ssr).invalidate(id);
      }
    }
    const _print = server.printUrls;
    server.printUrls = () => {
      let host = `${config.server.https ? "https" : "http"}://localhost:${config.server.port || "80"}`;
      const url = server.resolvedUrls?.local[0];
      if (url) {
        try {
          const u = new URL(url);
          host = `${u.protocol}//${u.host}`;
        } catch (error) {
          config.logger.warn(`Parse resolved url failed: ${error}`);
        }
      }
      _print();
      if (!silent) {
        const colorUrl = (url2) => c.green(url2.replace(/:(\d+)\//, (_, port) => `:${c.bold(port)}/`));
        config.logger.info(`  ${c.green("\u279C")}  ${c.bold("Inspect")}: ${colorUrl(`${host}${base}__inspect/`)}`);
      }
      if (_open && !isCI) {
        setTimeout(() => {
          openBrowser(`${host}${base}__inspect/`);
        }, 500);
      }
    };
    return rpcFunctions;
  }
  const plugin = {
    name: NAME,
    enforce: "pre",
    apply(_, { command }) {
      if (command === "serve" && dev)
        return true;
      if (command === "build" && build)
        return true;
      return false;
    },
    configResolved(_config) {
      config = ctx.config = _config;
      config.plugins.forEach((plugin2) => hijackPlugin(plugin2, ctx));
      const _createResolver = config.createResolver;
      config.createResolver = function(...args) {
        const _resolver = _createResolver.apply(this, args);
        return async function(...args2) {
          const id = args2[0];
          const aliasOnly = args2[2];
          const ssr = args2[3];
          const start = Date.now();
          const result = await _resolver.apply(this, args2);
          const end = Date.now();
          if (result && result !== id) {
            const pluginName = aliasOnly ? "alias" : "vite:resolve (+alias)";
            ctx.getRecorder(ssr).recordResolveId(id, { name: pluginName, result, start, end });
          }
          return result;
        };
      };
    },
    configureServer(server) {
      const rpc = configureServer(server);
      plugin.api = {
        rpc
      };
      return () => {
        setupMiddlewarePerf(server.middlewares.stack);
      };
    },
    load: {
      order: "pre",
      handler(id, { ssr } = {}) {
        ctx.getRecorder(ssr).invalidate(id);
        return null;
      }
    },
    handleHotUpdate({ modules, server }) {
      const ids = modules.map((module) => module.id);
      server.ws.send({
        type: "custom",
        event: "vite-plugin-inspect:update",
        data: { ids }
      });
    },
    async buildEnd() {
      if (!build)
        return;
      const dir = await generateBuild(ctx, config);
      config.logger.info(`${c.green("Inspect report generated at")}  ${c.dim(`${dir}`)}`);
      if (_open && !isCI)
        createPreviewServer(dir);
    }
  };
  return plugin;
}
PluginInspect.getViteInspectAPI = function(plugins) {
  return plugins.find((p) => p.name === NAME)?.api;
};

export { PluginInspect as default };
