import { slug as githubSlug } from "github-slugger";
import matter from "gray-matter";
import fsMod from "node:fs";
import path from "node:path";
import { fileURLToPath, pathToFileURL } from "node:url";
import { normalizePath } from "vite";
import { z } from "zod";
import { emitESMImage } from "../assets/utils/emitAsset.js";
import { AstroError, AstroErrorData } from "../core/errors/index.js";
import { CONTENT_TYPES_FILE } from "./consts.js";
import { errorMap } from "./error-map.js";
const collectionConfigParser = z.object({
  schema: z.any().optional()
});
function getDotAstroTypeReference({ root, srcDir }) {
  const { cacheDir } = getContentPaths({ root, srcDir });
  const contentTypesRelativeToSrcDir = normalizePath(
    path.relative(fileURLToPath(srcDir), fileURLToPath(new URL(CONTENT_TYPES_FILE, cacheDir)))
  );
  return `/// <reference path=${JSON.stringify(contentTypesRelativeToSrcDir)} />`;
}
const contentConfigParser = z.object({
  collections: z.record(collectionConfigParser)
});
const msg = {
  collectionConfigMissing: (collection) => `${collection} does not have a config. We suggest adding one for type safety!`
};
async function patchAssets(frontmatterEntry, watchMode, fileEmitter, astroSettings) {
  for (const key of Object.keys(frontmatterEntry)) {
    if (typeof frontmatterEntry[key] === "object" && frontmatterEntry[key] !== null) {
      if (frontmatterEntry[key]["__astro_asset"]) {
        frontmatterEntry[key] = await emitESMImage(
          frontmatterEntry[key].src,
          watchMode,
          fileEmitter,
          astroSettings
        );
      } else {
        await patchAssets(frontmatterEntry[key], watchMode, fileEmitter, astroSettings);
      }
    }
  }
}
function getEntrySlug({
  id,
  collection,
  slug,
  unvalidatedSlug
}) {
  try {
    return z.string().default(slug).parse(unvalidatedSlug);
  } catch {
    throw new AstroError({
      ...AstroErrorData.InvalidContentEntrySlugError,
      message: AstroErrorData.InvalidContentEntrySlugError.message(collection, id)
    });
  }
}
async function getEntryData(entry, collectionConfig) {
  let { slug, ...data } = entry.unvalidatedData;
  if (collectionConfig.schema) {
    if (typeof collectionConfig.schema === "object" && !("safeParseAsync" in collectionConfig.schema)) {
      throw new AstroError({
        title: "Invalid content collection config",
        message: `New: Content collection schemas must be Zod objects. Update your collection config to use \`schema: z.object({...})\` instead of \`schema: {...}\`.`,
        hint: "See https://docs.astro.build/en/reference/api-reference/#definecollection for an example.",
        code: 99999
      });
    }
    if (typeof collectionConfig.schema === "object" && "shape" in collectionConfig.schema && collectionConfig.schema.shape.slug) {
      throw new AstroError({
        ...AstroErrorData.ContentSchemaContainsSlugError,
        message: AstroErrorData.ContentSchemaContainsSlugError.message(entry.collection)
      });
    }
    const parsed = await collectionConfig.schema.safeParseAsync(entry.unvalidatedData, {
      errorMap
    });
    if (parsed.success) {
      data = parsed.data;
    } else {
      const formattedError = new AstroError({
        ...AstroErrorData.InvalidContentEntryFrontmatterError,
        message: AstroErrorData.InvalidContentEntryFrontmatterError.message(
          entry.collection,
          entry.id,
          parsed.error
        ),
        location: {
          file: entry._internal.filePath,
          line: getFrontmatterErrorLine(
            entry._internal.rawData,
            String(parsed.error.errors[0].path[0])
          ),
          column: 0
        }
      });
      throw formattedError;
    }
  }
  return data;
}
function getContentEntryExts(settings) {
  return settings.contentEntryTypes.map((t) => t.extensions).flat();
}
class NoCollectionError extends Error {
}
function getEntryInfo({
  entry,
  contentDir,
  allowFilesOutsideCollection = false
}) {
  const rawRelativePath = path.relative(fileURLToPath(contentDir), fileURLToPath(entry));
  const rawCollection = path.dirname(rawRelativePath).split(path.sep).shift();
  const isOutsideCollection = rawCollection === ".." || rawCollection === ".";
  if (!rawCollection || !allowFilesOutsideCollection && isOutsideCollection)
    return new NoCollectionError();
  const rawId = path.relative(rawCollection, rawRelativePath);
  const rawIdWithoutFileExt = rawId.replace(new RegExp(path.extname(rawId) + "$"), "");
  const rawSlugSegments = rawIdWithoutFileExt.split(path.sep);
  const slug = rawSlugSegments.map((segment) => githubSlug(segment)).join("/").replace(/\/index$/, "");
  const res = {
    id: normalizePath(rawId),
    slug,
    collection: normalizePath(rawCollection)
  };
  return res;
}
function getEntryType(entryPath, paths, contentFileExts) {
  const { ext, base } = path.parse(entryPath);
  const fileUrl = pathToFileURL(entryPath);
  if (hasUnderscoreBelowContentDirectoryPath(fileUrl, paths.contentDir) || isOnIgnoreList(base)) {
    return "ignored";
  } else if (contentFileExts.includes(ext)) {
    return "content";
  } else if (fileUrl.href === paths.config.url.href) {
    return "config";
  } else {
    return "unsupported";
  }
}
function isOnIgnoreList(fileName) {
  return [".DS_Store"].includes(fileName);
}
function hasUnderscoreBelowContentDirectoryPath(fileUrl, contentDir) {
  const parts = fileUrl.pathname.replace(contentDir.pathname, "").split("/");
  for (const part of parts) {
    if (part.startsWith("_"))
      return true;
  }
  return false;
}
function getFrontmatterErrorLine(rawFrontmatter, frontmatterKey) {
  const indexOfFrontmatterKey = rawFrontmatter.indexOf(`
${frontmatterKey}`);
  if (indexOfFrontmatterKey === -1)
    return 0;
  const frontmatterBeforeKey = rawFrontmatter.substring(0, indexOfFrontmatterKey + 1);
  const numNewlinesBeforeKey = frontmatterBeforeKey.split("\n").length;
  return numNewlinesBeforeKey;
}
function parseFrontmatter(fileContents, filePath) {
  try {
    matter.clearCache();
    return matter(fileContents);
  } catch (e) {
    if (e.name === "YAMLException") {
      const err = e;
      err.id = filePath;
      err.loc = { file: e.id, line: e.mark.line + 1, column: e.mark.column };
      err.message = e.reason;
      throw err;
    } else {
      throw e;
    }
  }
}
const globalContentConfigObserver = contentObservable({ status: "init" });
async function loadContentConfig({
  fs,
  settings,
  viteServer
}) {
  const contentPaths = getContentPaths(settings.config, fs);
  let unparsedConfig;
  if (!contentPaths.config.exists) {
    return void 0;
  }
  try {
    const configPathname = fileURLToPath(contentPaths.config.url);
    unparsedConfig = await viteServer.ssrLoadModule(configPathname);
  } catch (e) {
    throw e;
  }
  const config = contentConfigParser.safeParse(unparsedConfig);
  if (config.success) {
    return config.data;
  } else {
    return void 0;
  }
}
function contentObservable(initialCtx) {
  const subscribers = /* @__PURE__ */ new Set();
  let ctx = initialCtx;
  function get() {
    return ctx;
  }
  function set(_ctx) {
    ctx = _ctx;
    subscribers.forEach((fn) => fn(ctx));
  }
  function subscribe(fn) {
    subscribers.add(fn);
    return () => {
      subscribers.delete(fn);
    };
  }
  return {
    get,
    set,
    subscribe
  };
}
function getContentPaths({ srcDir, root }, fs = fsMod) {
  const configStats = search(fs, srcDir);
  const templateDir = new URL("../../src/content/template/", import.meta.url);
  return {
    cacheDir: new URL(".astro/", root),
    contentDir: new URL("./content/", srcDir),
    assetsDir: new URL("./assets/", srcDir),
    typesTemplate: new URL("types.d.ts", templateDir),
    virtualModTemplate: new URL("virtual-mod.mjs", templateDir),
    config: configStats
  };
}
function search(fs, srcDir) {
  const paths = ["config.mjs", "config.js", "config.ts"].map(
    (p) => new URL(`./content/${p}`, srcDir)
  );
  for (const file of paths) {
    if (fs.existsSync(file)) {
      return { exists: true, url: file };
    }
  }
  return { exists: false, url: paths[0] };
}
export {
  NoCollectionError,
  collectionConfigParser,
  contentConfigParser,
  contentObservable,
  getContentEntryExts,
  getContentPaths,
  getDotAstroTypeReference,
  getEntryData,
  getEntryInfo,
  getEntrySlug,
  getEntryType,
  globalContentConfigObserver,
  loadContentConfig,
  msg,
  parseFrontmatter,
  patchAssets
};
