import { extname } from "node:path";
import { fileURLToPath, pathToFileURL } from "node:url";
import colors from "piccolore";
import { normalizePath } from "vite";
import { serializeRouteData } from "../core/app/entrypoints/index.js";
import { warnMissingAdapter } from "../core/dev/adapter-validation.js";
import { createRoutesList } from "../core/routing/create-manifest.js";
import { getRoutePrerenderOption } from "../core/routing/prerender.js";
import { isEndpoint, isPage } from "../core/util.js";
import { rootRelativePath } from "../core/viteUtils.js";
import { createDefaultAstroMetadata } from "../vite-plugin-astro/metadata.js";
import { ASTRO_VITE_ENVIRONMENT_NAMES } from "../core/constants.js";
import { isAstroServerEnvironment } from "../environments.js";
import { RESOLVED_MODULE_DEV_CSS_ALL } from "../vite-plugin-css/const.js";
import { PAGE_SCRIPT_ID } from "../vite-plugin-scripts/index.js";
const ASTRO_ROUTES_MODULE_ID = "virtual:astro:routes";
const ASTRO_ROUTES_MODULE_ID_RESOLVED = "\0" + ASTRO_ROUTES_MODULE_ID;
const KNOWN_FILE_EXTENSIONS = [".astro", ".js", ".ts"];
function getDevRouteScripts(command, scripts) {
  if (command !== "dev") return [];
  const result = [];
  const hasPageScripts = scripts.some((s) => s.stage === "page");
  if (hasPageScripts) {
    result.push({
      type: "external",
      value: `/@id/${PAGE_SCRIPT_ID}`
    });
  }
  for (const script of scripts) {
    if (script.stage === "head-inline") {
      result.push({ stage: script.stage, children: script.content });
    }
  }
  return result;
}
async function astroPluginRoutes({
  settings,
  logger,
  fsMod,
  routesList: initialRoutesList,
  command
}) {
  logger.debug("update", "Re-calculate routes");
  let serializedRouteInfo = initialRoutesList.routes.map(
    (r) => {
      return {
        file: "",
        links: [],
        scripts: getDevRouteScripts(command, settings.scripts),
        styles: [],
        routeData: serializeRouteData(r, settings.config.trailingSlash)
      };
    }
  );
  const normalizedSrcDir = normalizePath(fileURLToPath(settings.config.srcDir));
  async function rebuildRoutes(path = null, server) {
    if (path != null && normalizePath(path).startsWith(normalizedSrcDir)) {
      logger.debug(
        "update",
        `Re-calculating routes for ${normalizePath(path).slice(normalizedSrcDir.length)}`
      );
      const file = pathToFileURL(normalizePath(path));
      const newRoutesList = await createRoutesList(
        {
          settings,
          fsMod
        },
        logger,
        { dev: command === "dev" }
      );
      initialRoutesList.routes.length = 0;
      initialRoutesList.routes.push(...newRoutesList.routes);
      serializedRouteInfo = initialRoutesList.routes.map((r) => {
        return {
          file: fileURLToPath(file),
          links: [],
          scripts: getDevRouteScripts(command, settings.scripts),
          styles: [],
          routeData: serializeRouteData(r, settings.config.trailingSlash)
        };
      });
      const environmentsToInvalidate = [];
      for (const name of [
        ASTRO_VITE_ENVIRONMENT_NAMES.ssr,
        ASTRO_VITE_ENVIRONMENT_NAMES.prerender
      ]) {
        const environment = server.environments[name];
        if (environment) {
          environmentsToInvalidate.push(environment);
        }
      }
      for (const environment of environmentsToInvalidate) {
        const virtualMod = environment.moduleGraph.getModuleById(ASTRO_ROUTES_MODULE_ID_RESOLVED);
        if (!virtualMod) continue;
        environment.moduleGraph.invalidateModule(virtualMod);
        const cssMod = environment.moduleGraph.getModuleById(RESOLVED_MODULE_DEV_CSS_ALL);
        if (cssMod) {
          environment.moduleGraph.invalidateModule(cssMod);
        }
        environment.hot.send("astro:routes-updated", {});
      }
    }
  }
  return {
    name: "astro:routes",
    configureServer(server) {
      server.watcher.on("add", (path) => rebuildRoutes(path, server));
      server.watcher.on("unlink", (path) => rebuildRoutes(path, server));
      server.watcher.on("change", (path) => rebuildRoutes(path, server));
    },
    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(`^${ASTRO_ROUTES_MODULE_ID}$`)
      },
      handler() {
        return ASTRO_ROUTES_MODULE_ID_RESOLVED;
      }
    },
    load: {
      filter: {
        id: new RegExp(`^${ASTRO_ROUTES_MODULE_ID_RESOLVED}$`)
      },
      handler() {
        const environmentName = this.environment.name;
        const filteredRoutes = serializedRouteInfo.filter((routeInfo) => {
          if (command === "build") {
            if (environmentName === ASTRO_VITE_ENVIRONMENT_NAMES.prerender) {
              return routeInfo.routeData.prerender;
            }
            if (environmentName === ASTRO_VITE_ENVIRONMENT_NAMES.ssr) {
              return !routeInfo.routeData.prerender;
            }
          }
          return true;
        });
        const code = `
				import { deserializeRouteInfo } from 'astro/app';
				const serializedData = ${JSON.stringify(filteredRoutes)};
				const routes = serializedData.map(deserializeRouteInfo);
				export { routes };
				`;
        return { code };
      }
    },
    async transform(code, id) {
      if (!isAstroServerEnvironment(this.environment)) return;
      const filename = normalizePath(id);
      let fileURL;
      try {
        fileURL = new URL(`file://${filename}`);
      } catch {
        return;
      }
      const fileIsPage = isPage(fileURL, settings);
      const fileIsEndpoint = isEndpoint(fileURL, settings);
      if (!(fileIsPage || fileIsEndpoint)) return;
      const route = initialRoutesList.routes.find((r) => {
        const filePath = new URL(`./${r.component}`, settings.config.root);
        return normalizePath(fileURLToPath(filePath)) === filename;
      });
      if (!route) {
        return;
      }
      if (!route.prerender && code.includes("getStaticPaths") && // this should only be valid for `.astro`, `.js` and `.ts` files
      KNOWN_FILE_EXTENSIONS.includes(extname(filename))) {
        logger.warn(
          "router",
          `getStaticPaths() ignored in dynamic page ${colors.bold(
            rootRelativePath(settings.config.root, fileURL, true)
          )}. Add \`export const prerender = true;\` to prerender the page as static HTML during the build process.`
        );
      }
      const { meta = {} } = this.getModuleInfo(id) ?? {};
      return {
        code,
        map: null,
        meta: {
          ...meta,
          astro: {
            ...meta.astro ?? createDefaultAstroMetadata(),
            pageOptions: {
              prerender: route.prerender
            }
          }
        }
      };
    },
    // Handle hot updates to update the prerender option
    async handleHotUpdate(ctx) {
      const filename = normalizePath(ctx.file);
      let fileURL;
      try {
        fileURL = new URL(`file://${filename}`);
      } catch {
        return;
      }
      const fileIsPage = isPage(fileURL, settings);
      const fileIsEndpoint = isEndpoint(fileURL, settings);
      if (!(fileIsPage || fileIsEndpoint)) return;
      const route = initialRoutesList.routes.find((r) => {
        const filePath = new URL(`./${r.component}`, settings.config.root);
        return normalizePath(fileURLToPath(filePath)) === filename;
      });
      if (!route) {
        return;
      }
      await getRoutePrerenderOption(await ctx.read(), route, settings, logger);
      warnMissingAdapter(logger, settings);
    }
  };
}
export {
  ASTRO_ROUTES_MODULE_ID,
  astroPluginRoutes as default,
  getDevRouteScripts
};
