import { fileURLToPath } from "node:url";
import { normalizePath } from "vite";
import { ACTIONS_ENTRYPOINT_VIRTUAL_MODULE_ID } from "../actions/consts.js";
import { toFallbackType } from "../core/app/common.js";
import { toRoutingStrategy } from "../core/app/entrypoints/index.js";
import { MANIFEST_REPLACE } from "../core/build/plugins/plugin-manifest.js";
import {
  getAlgorithm,
  getDirectives,
  getScriptHashes,
  getScriptResources,
  getStrictDynamic,
  getStyleHashes,
  getStyleResources,
  shouldTrackCspHashes
} from "../core/csp/common.js";
import { createKey, encodeKey, getEnvironmentKey, hasEnvironmentKey } from "../core/encryption.js";
import { MIDDLEWARE_MODULE_ID } from "../core/middleware/vite-plugin.js";
import { SERVER_ISLAND_MANIFEST } from "../core/server-islands/vite-plugin-server-islands.js";
import { VIRTUAL_CACHE_PROVIDER_ID } from "../core/cache/vite-plugin.js";
import { VIRTUAL_SESSION_DRIVER_ID } from "../core/session/vite-plugin.js";
import { VIRTUAL_PAGES_MODULE_ID } from "../vite-plugin-pages/index.js";
import { ASTRO_RENDERERS_MODULE_ID } from "../vite-plugin-renderers/index.js";
import { ASTRO_ROUTES_MODULE_ID } from "../vite-plugin-routes/index.js";
import { cacheConfigToManifest } from "../core/cache/utils.js";
import { sessionConfigToManifest } from "../core/session/utils.js";
import { ASTRO_VITE_ENVIRONMENT_NAMES } from "../core/constants.js";
import { resolveMiddlewareMode } from "../integrations/adapter-utils.js";
const SERIALIZED_MANIFEST_ID = "virtual:astro:manifest";
const SERIALIZED_MANIFEST_RESOLVED_ID = "\0" + SERIALIZED_MANIFEST_ID;
function serializedManifestPlugin({
  settings,
  command,
  sync
}) {
  const normalizedSrcDir = normalizePath(fileURLToPath(settings.config.srcDir));
  let encodedKeyPromise;
  function getEncodedKey() {
    encodedKeyPromise ??= (async () => {
      const key = hasEnvironmentKey() ? await getEnvironmentKey() : await createKey();
      return encodeKey(key);
    })();
    return encodedKeyPromise;
  }
  function reloadManifest(path, server) {
    if (path != null && normalizePath(path).startsWith(normalizedSrcDir)) {
      const environment = server.environments[ASTRO_VITE_ENVIRONMENT_NAMES.ssr];
      const virtualMod = environment.moduleGraph.getModuleById(SERIALIZED_MANIFEST_RESOLVED_ID);
      if (!virtualMod) return;
      environment.moduleGraph.invalidateModule(virtualMod);
    }
  }
  return {
    name: SERIALIZED_MANIFEST_ID,
    enforce: "pre",
    configureServer(server) {
      server.watcher.on("add", (path) => reloadManifest(path, server));
      server.watcher.on("unlink", (path) => reloadManifest(path, server));
      server.watcher.on("change", (path) => reloadManifest(path, server));
    },
    // Restrict to server environments only since the generated code imports
    // server-only virtual modules (virtual:astro:routes, virtual:astro:pages)
    applyToEnvironment(environment) {
      return environment.name === ASTRO_VITE_ENVIRONMENT_NAMES.astro || environment.name === ASTRO_VITE_ENVIRONMENT_NAMES.ssr || environment.name === ASTRO_VITE_ENVIRONMENT_NAMES.prerender;
    },
    resolveId: {
      filter: {
        id: new RegExp(`^${SERIALIZED_MANIFEST_ID}$`)
      },
      handler() {
        return SERIALIZED_MANIFEST_RESOLVED_ID;
      }
    },
    load: {
      filter: {
        id: new RegExp(`^${SERIALIZED_MANIFEST_RESOLVED_ID}$`)
      },
      async handler() {
        let manifestData;
        if (command === "build" && !sync) {
          manifestData = `'${MANIFEST_REPLACE}'`;
        } else {
          const serialized = await createSerializedManifest(settings, await getEncodedKey());
          manifestData = JSON.stringify(serialized);
        }
        const hasCacheConfig = !!settings.config.experimental?.cache?.provider;
        const cacheProviderLine = hasCacheConfig ? `cacheProvider: () => import('${VIRTUAL_CACHE_PROVIDER_ID}'),` : "";
        const code = `
					import { deserializeManifest as _deserializeManifest } from 'astro/app';
					import { renderers } from '${ASTRO_RENDERERS_MODULE_ID}';
					import { routes } from '${ASTRO_ROUTES_MODULE_ID}';
					import { pageMap } from '${VIRTUAL_PAGES_MODULE_ID}';

					const _manifest = _deserializeManifest((${manifestData}));

				  // _manifest.routes contains enriched route info with scripts and styles,
				  // TODO port this info over to virtual:astro:routes to prevent the need to
				  // have this duplication
					const isDev = ${JSON.stringify(command === "dev")};
					const manifestRoutes = isDev ? routes : _manifest.routes;
					
					const manifest = Object.assign(_manifest, {
					  renderers,
					  actions: () => import('${ACTIONS_ENTRYPOINT_VIRTUAL_MODULE_ID}'),
					  middleware: () => import('${MIDDLEWARE_MODULE_ID}'),
					  sessionDriver: () => import('${VIRTUAL_SESSION_DRIVER_ID}'),
					  ${cacheProviderLine}
					  serverIslandMappings: () => import('${SERVER_ISLAND_MANIFEST}'),
					  routes: manifestRoutes,
					  pageMap,
					});
					export { manifest };
				`;
        return { code };
      }
    }
  };
}
async function createSerializedManifest(settings, encodedKey) {
  let i18nManifest;
  let csp;
  if (settings.config.i18n) {
    i18nManifest = {
      fallback: settings.config.i18n.fallback,
      strategy: toRoutingStrategy(settings.config.i18n.routing, settings.config.i18n.domains),
      defaultLocale: settings.config.i18n.defaultLocale,
      locales: settings.config.i18n.locales,
      domainLookupTable: {},
      fallbackType: toFallbackType(settings.config.i18n.routing),
      domains: settings.config.i18n.domains
    };
  }
  if (shouldTrackCspHashes(settings.config.security.csp)) {
    csp = {
      cspDestination: settings.adapter?.adapterFeatures?.staticHeaders ? "adapter" : void 0,
      scriptHashes: getScriptHashes(settings.config.security.csp),
      scriptResources: getScriptResources(settings.config.security.csp),
      styleHashes: getStyleHashes(settings.config.security.csp),
      styleResources: getStyleResources(settings.config.security.csp),
      algorithm: getAlgorithm(settings.config.security.csp),
      directives: getDirectives(settings),
      isStrictDynamic: getStrictDynamic(settings.config.security.csp)
    };
  }
  let experimentalLogger = void 0;
  if (settings.config.experimental.logger) {
    experimentalLogger = settings.config.experimental.logger;
  }
  return {
    rootDir: settings.config.root.toString(),
    srcDir: settings.config.srcDir.toString(),
    cacheDir: settings.config.cacheDir.toString(),
    outDir: settings.config.outDir.toString(),
    buildServerDir: settings.config.build.server.toString(),
    buildClientDir: settings.config.build.client.toString(),
    publicDir: settings.config.publicDir.toString(),
    assetsDir: settings.config.build.assets,
    trailingSlash: settings.config.trailingSlash,
    buildFormat: settings.config.build.format,
    compressHTML: settings.config.compressHTML,
    serverLike: settings.buildOutput === "server",
    middlewareMode: resolveMiddlewareMode(settings.adapter?.adapterFeatures),
    assets: [],
    entryModules: {},
    routes: [],
    adapterName: settings?.adapter?.name ?? "",
    clientDirectives: Array.from(settings.clientDirectives.entries()),
    renderers: [],
    base: settings.config.base,
    userAssetsBase: settings.config?.vite?.base,
    assetsPrefix: settings.config.build.assetsPrefix,
    site: settings.config.site,
    componentMetadata: [],
    inlinedScripts: [],
    i18n: i18nManifest,
    checkOrigin: (settings.config.security?.checkOrigin && settings.buildOutput === "server") ?? false,
    allowedDomains: settings.config.security?.allowedDomains,
    actionBodySizeLimit: settings.config.security?.actionBodySizeLimit ? settings.config.security.actionBodySizeLimit : 1024 * 1024,
    // 1mb default
    serverIslandBodySizeLimit: settings.config.security?.serverIslandBodySizeLimit ? settings.config.security.serverIslandBodySizeLimit : 1024 * 1024,
    // 1mb default
    key: encodedKey ?? await encodeKey(hasEnvironmentKey() ? await getEnvironmentKey() : await createKey()),
    sessionConfig: sessionConfigToManifest(settings.config.session),
    cacheConfig: cacheConfigToManifest(
      settings.config.experimental?.cache,
      settings.config.experimental?.routeRules
    ),
    csp,
    image: {
      objectFit: settings.config.image.objectFit,
      objectPosition: settings.config.image.objectPosition,
      layout: settings.config.image.layout
    },
    devToolbar: {
      enabled: settings.config.devToolbar.enabled && await settings.preferences.get("devToolbar.enabled"),
      latestAstroVersion: settings.latestAstroVersion,
      debugInfoOutput: "",
      placement: settings.config.devToolbar.placement
    },
    logLevel: settings.logLevel,
    shouldInjectCspMetaTags: false,
    experimentalQueuedRendering: settings.config.experimental?.queuedRendering,
    experimentalLogger
  };
}
export {
  SERIALIZED_MANIFEST_ID,
  SERIALIZED_MANIFEST_RESOLVED_ID,
  serializedManifestPlugin
};
