import { normalizePath } from "vite";
import fsMod from "node:fs";
import { createHash } from "node:crypto";
import { addRollupInput } from "../add-rollup-input.js";
import {} from "../internal.js";
import { generateContentEntryFile, generateLookupMap } from "../../../content/vite-plugin-content-virtual-mod.js";
import { joinPaths, removeFileExtension, removeLeadingForwardSlash } from "../../path.js";
import { fileURLToPath } from "node:url";
import { hasContentFlag } from "../../../content/utils.js";
import { CONTENT_RENDER_FLAG, PROPAGATED_ASSET_FLAG } from "../../../content/consts.js";
import { copyFiles } from "../static-build.js";
import pLimit from "p-limit";
import { extendManualChunks } from "./util.js";
import { isServerLikeOutput } from "../../../prerender/utils.js";
import { encodeName } from "../util.js";
const CONTENT_CACHE_DIR = "./content/";
const CONTENT_MANIFEST_FILE = "./manifest.json";
const CONTENT_MANIFEST_VERSION = 0;
const virtualEmptyModuleId = `virtual:empty-content`;
const resolvedVirtualEmptyModuleId = `\0${virtualEmptyModuleId}`;
function createContentManifest() {
  return { version: -1, entries: [], serverEntries: [], clientEntries: [] };
}
function vitePluginContent(opts, lookupMap, internals) {
  const { config } = opts.settings;
  const { cacheDir } = config;
  const distRoot = config.outDir;
  const distContentRoot = new URL("./content/", distRoot);
  const cachedChunks = new URL("./chunks/", opts.settings.config.cacheDir);
  const distChunks = new URL("./chunks/", opts.settings.config.outDir);
  const contentCacheDir = new URL(CONTENT_CACHE_DIR, cacheDir);
  const contentManifestFile = new URL(CONTENT_MANIFEST_FILE, contentCacheDir);
  const cache = contentCacheDir;
  const cacheTmp = new URL("./.tmp/", cache);
  let oldManifest = createContentManifest();
  let newManifest = createContentManifest();
  let entries;
  let injectedEmptyFile = false;
  if (fsMod.existsSync(contentManifestFile)) {
    try {
      const data = fsMod.readFileSync(contentManifestFile, { encoding: "utf8" });
      oldManifest = JSON.parse(data);
      internals.cachedClientEntries = oldManifest.clientEntries;
    } catch {
    }
  }
  return {
    name: "@astro/plugin-build-content",
    async options(options) {
      let newOptions = Object.assign({}, options);
      newManifest = await generateContentManifest(opts, lookupMap);
      entries = getEntriesFromManifests(oldManifest, newManifest);
      for (const { type, entry } of entries.buildFromSource) {
        const fileURL = encodeURI(joinPaths(opts.settings.config.root.toString(), entry));
        const input = fileURLToPath(fileURL);
        const inputs = [`${input}?${collectionTypeToFlag(type)}`];
        if (type === "content") {
          inputs.push(`${input}?${CONTENT_RENDER_FLAG}`);
        }
        newOptions = addRollupInput(newOptions, inputs);
      }
      if (fsMod.existsSync(cachedChunks)) {
        await copyFiles(cachedChunks, distChunks, true);
      }
      if (entries.buildFromSource.length === 0) {
        newOptions = addRollupInput(newOptions, [virtualEmptyModuleId]);
        injectedEmptyFile = true;
      }
      return newOptions;
    },
    outputOptions(outputOptions) {
      const rootPath = normalizePath(fileURLToPath(opts.settings.config.root));
      const srcPath = normalizePath(fileURLToPath(opts.settings.config.srcDir));
      extendManualChunks(outputOptions, {
        before(id, meta) {
          if (id.startsWith(srcPath) && id.slice(srcPath.length).startsWith("content")) {
            const info = meta.getModuleInfo(id);
            if (info?.dynamicImporters.length === 1 && hasContentFlag(info.dynamicImporters[0], PROPAGATED_ASSET_FLAG)) {
              const [srcRelativePath2] = id.replace(rootPath, "/").split("?");
              const resultId = encodeName(`${removeLeadingForwardSlash(removeFileExtension(srcRelativePath2))}.render.mjs`);
              return resultId;
            }
            const [srcRelativePath, flag] = id.replace(rootPath, "/").split("?");
            const collectionEntry = findEntryFromSrcRelativePath(lookupMap, srcRelativePath);
            if (collectionEntry) {
              let suffix = ".mjs";
              if (flag === PROPAGATED_ASSET_FLAG) {
                suffix = ".entry.mjs";
              }
              id = removeLeadingForwardSlash(removeFileExtension(encodeName(id.replace(srcPath, "/")))) + suffix;
              return id;
            }
          }
        }
      });
    },
    resolveId(id) {
      if (id === virtualEmptyModuleId) {
        return resolvedVirtualEmptyModuleId;
      }
    },
    async load(id) {
      if (id === resolvedVirtualEmptyModuleId) {
        return {
          code: `// intentionally left empty!
export default {}`
        };
      }
    },
    async generateBundle(_options, bundle) {
      const code = await generateContentEntryFile({ settings: opts.settings, fs: fsMod, lookupMap, IS_DEV: false, IS_SERVER: false });
      this.emitFile({
        type: "prebuilt-chunk",
        code,
        fileName: "content/entry.mjs"
      });
      if (!injectedEmptyFile)
        return;
      Object.keys(bundle).forEach((key) => {
        const mod = bundle[key];
        if (mod.type === "asset")
          return;
        if (mod.facadeModuleId === resolvedVirtualEmptyModuleId) {
          delete bundle[key];
        }
      });
    },
    async writeBundle() {
      const clientComponents = /* @__PURE__ */ new Set([
        ...oldManifest.clientEntries,
        ...internals.discoveredHydratedComponents.keys(),
        ...internals.discoveredClientOnlyComponents.keys(),
        ...internals.discoveredScripts
      ]);
      const serverComponents = /* @__PURE__ */ new Set([
        ...oldManifest.serverEntries,
        ...internals.discoveredHydratedComponents.keys()
      ]);
      newManifest.serverEntries = Array.from(serverComponents);
      newManifest.clientEntries = Array.from(clientComponents);
      await fsMod.promises.mkdir(contentCacheDir, { recursive: true });
      await fsMod.promises.writeFile(contentManifestFile, JSON.stringify(newManifest), { encoding: "utf8" });
      const cacheExists = fsMod.existsSync(cache);
      fsMod.mkdirSync(cache, { recursive: true });
      await fsMod.promises.mkdir(cacheTmp, { recursive: true });
      await copyFiles(distContentRoot, cacheTmp, true);
      if (cacheExists) {
        await copyFiles(contentCacheDir, distContentRoot, false);
      }
      await copyFiles(cacheTmp, contentCacheDir);
      await fsMod.promises.rm(cacheTmp, { recursive: true, force: true });
    }
  };
}
const entryCache = /* @__PURE__ */ new Map();
function findEntryFromSrcRelativePath(lookupMap, srcRelativePath) {
  let value = entryCache.get(srcRelativePath);
  if (value)
    return value;
  for (const collection of Object.values(lookupMap)) {
    for (const entry of Object.values(collection)) {
      for (const entryFile of Object.values(entry)) {
        if (entryFile === srcRelativePath) {
          value = entryFile;
          entryCache.set(srcRelativePath, entryFile);
          return value;
        }
      }
    }
  }
}
function getEntriesFromManifests(oldManifest, newManifest) {
  const { version: oldVersion, entries: oldEntries } = oldManifest;
  const { version: newVersion, entries: newEntries } = newManifest;
  let entries = { restoreFromCache: [], buildFromSource: [] };
  const newEntryMap = new Map(newEntries);
  if (oldVersion !== newVersion || oldEntries.length === 0) {
    entries.buildFromSource = Array.from(newEntryMap.keys());
    return entries;
  }
  const oldEntryHashMap = new Map(oldEntries.map(([key, hash]) => [hash, key]));
  for (const [entry, hash] of newEntryMap) {
    if (oldEntryHashMap.has(hash)) {
      entries.restoreFromCache.push(entry);
    } else {
      entries.buildFromSource.push(entry);
    }
  }
  return entries;
}
async function generateContentManifest(opts, lookupMap) {
  let manifest = { version: CONTENT_MANIFEST_VERSION, entries: [], serverEntries: [], clientEntries: [] };
  const limit = pLimit(10);
  const promises = [];
  for (const [collection, { type, entries }] of Object.entries(lookupMap)) {
    for (const entry of Object.values(entries)) {
      const key = { collection, type, entry };
      const fileURL = new URL(encodeURI(joinPaths(opts.settings.config.root.toString(), entry)));
      promises.push(limit(async () => {
        const data = await fsMod.promises.readFile(fileURL, { encoding: "utf8" });
        manifest.entries.push([key, checksum(data)]);
      }));
    }
  }
  await Promise.all(promises);
  return manifest;
}
function checksum(data) {
  return createHash("sha1").update(data).digest("base64");
}
function collectionTypeToFlag(type) {
  const name = type[0].toUpperCase() + type.slice(1);
  return `astro${name}CollectionEntry`;
}
function pluginContent(opts, internals) {
  const cachedChunks = new URL("./chunks/", opts.settings.config.cacheDir);
  const distChunks = new URL("./chunks/", opts.settings.config.outDir);
  return {
    targets: ["server"],
    hooks: {
      async "build:before"() {
        if (!opts.settings.config.experimental.contentCollectionCache) {
          return { vitePlugin: void 0 };
        }
        if (isServerLikeOutput(opts.settings.config)) {
          return { vitePlugin: void 0 };
        }
        const lookupMap = await generateLookupMap({ settings: opts.settings, fs: fsMod });
        return {
          vitePlugin: vitePluginContent(opts, lookupMap, internals)
        };
      },
      async "build:post"() {
        if (!opts.settings.config.experimental.contentCollectionCache) {
          return;
        }
        if (isServerLikeOutput(opts.settings.config)) {
          return;
        }
        if (fsMod.existsSync(distChunks)) {
          await copyFiles(distChunks, cachedChunks, true);
        }
      }
    }
  };
}
export {
  pluginContent
};
