import { createRequire } from "node:module";
import { extname } from "node:path";
import { fileURLToPath, pathToFileURL } from "node:url";
import { AstroError, AstroErrorData } from "../../core/errors/index.js";
import { DEFAULT_FALLBACKS, FONT_TYPES, LOCAL_PROVIDER_NAME, SYSTEM_METRICS } from "./constants.js";
import { resolveProvider } from "./providers/utils.js";
function toCSS(properties, indent = 2) {
  return Object.entries(properties).filter(([, value]) => Boolean(value)).map(([key, value]) => `${" ".repeat(indent)}${key}: ${value};`).join("\n");
}
function renderFontFace(properties) {
  return `@font-face {
	${toCSS(properties)}
}
`;
}
function unifontFontFaceDataToProperties(font) {
  return {
    src: font.src ? renderFontSrc(font.src) : void 0,
    "font-display": font.display ?? "swap",
    "unicode-range": font.unicodeRange?.join(","),
    "font-weight": Array.isArray(font.weight) ? font.weight.join(" ") : font.weight?.toString(),
    "font-style": font.style,
    "font-stretch": font.stretch,
    "font-feature-settings": font.featureSettings,
    "font-variation-settings": font.variationSettings
  };
}
function generateFontFace(family, font) {
  return renderFontFace({
    "font-family": family,
    ...unifontFontFaceDataToProperties(font)
  });
}
function renderFontSrc(sources) {
  return sources.map((src) => {
    if ("url" in src) {
      let rendered = `url("${src.url}")`;
      if (src.format) {
        rendered += ` format("${src.format}")`;
      }
      if (src.tech) {
        rendered += ` tech(${src.tech})`;
      }
      return rendered;
    }
    return `local("${src.name}")`;
  }).join(", ");
}
const QUOTES_RE = /^["']|["']$/g;
function withoutQuotes(str) {
  return str.trim().replace(QUOTES_RE, "");
}
function extractFontType(str) {
  const extension = extname(str).slice(1);
  if (!isFontType(extension)) {
    throw new AstroError(
      {
        ...AstroErrorData.CannotExtractFontType,
        message: AstroErrorData.CannotExtractFontType.message(str)
      },
      {
        cause: `Unexpected extension, got "${extension}"`
      }
    );
  }
  return extension;
}
function isFontType(str) {
  return FONT_TYPES.includes(str);
}
async function cache(storage, key, cb) {
  const existing = await storage.getItemRaw(key);
  if (existing) {
    return existing;
  }
  const data = await cb();
  await storage.setItemRaw(key, data);
  return data;
}
function proxyURL({ value, hashString, collect }) {
  const type = extractFontType(value);
  const hash = `${hashString(value)}.${type}`;
  const url = collect({ hash, type, value });
  return url;
}
function isGenericFontFamily(str) {
  return Object.keys(DEFAULT_FALLBACKS).includes(str);
}
async function generateFallbacksCSS({
  family,
  fallbacks: _fallbacks,
  font: fontData,
  metrics
}) {
  let fallbacks = [..._fallbacks];
  if (fallbacks.length === 0) {
    return null;
  }
  if (fontData.length === 0 || !metrics) {
    return { fallbacks };
  }
  const lastFallback = fallbacks[fallbacks.length - 1];
  if (!isGenericFontFamily(lastFallback)) {
    return { fallbacks };
  }
  const localFonts = DEFAULT_FALLBACKS[lastFallback];
  if (localFonts.length === 0) {
    return { fallbacks };
  }
  if (localFonts.includes(
    // @ts-expect-error TS is not smart enough
    family.name
  )) {
    return { fallbacks };
  }
  const localFontsMappings = localFonts.map((font) => ({
    font,
    name: `"${family.nameWithHash} fallback: ${font}"`
  }));
  fallbacks = [.../* @__PURE__ */ new Set([...localFontsMappings.map((m) => m.name), ...fallbacks])];
  let css = "";
  for (const { font, name } of localFontsMappings) {
    for (const { hash, url, data } of fontData) {
      css += metrics.generateFontFace({
        metrics: await metrics.getMetricsForFamily(family.name, { hash, url, data }),
        fallbackMetrics: SYSTEM_METRICS[font],
        font,
        name,
        properties: unifontFontFaceDataToProperties(data)
      });
    }
  }
  return { css, fallbacks };
}
function dedupe(arr) {
  return [...new Set(arr)];
}
function resolveVariants({
  variants,
  root
}) {
  return variants.map((variant) => ({
    ...variant,
    weight: variant.weight.toString(),
    src: variant.src.map((value) => {
      const isValue = typeof value === "string" || value instanceof URL;
      const url = (isValue ? value : value.url).toString();
      const tech = isValue ? void 0 : value.tech;
      return {
        url: fileURLToPath(resolveEntrypoint(root, url)),
        tech
      };
    })
  }));
}
async function resolveFontFamily({
  family,
  generateNameWithHash,
  root,
  resolveMod
}) {
  const nameWithHash = generateNameWithHash(family);
  if (family.provider === LOCAL_PROVIDER_NAME) {
    return {
      ...family,
      nameWithHash,
      variants: resolveVariants({ variants: family.variants, root }),
      fallbacks: family.fallbacks ? dedupe(family.fallbacks) : void 0
    };
  }
  return {
    ...family,
    nameWithHash,
    provider: await resolveProvider({
      root,
      resolveMod,
      provider: family.provider
    }),
    weights: family.weights ? dedupe(family.weights.map((weight) => weight.toString())) : void 0,
    styles: family.styles ? dedupe(family.styles) : void 0,
    subsets: family.subsets ? dedupe(family.subsets) : void 0,
    fallbacks: family.fallbacks ? dedupe(family.fallbacks) : void 0,
    unicodeRange: family.unicodeRange ? dedupe(family.unicodeRange) : void 0
  };
}
function sortObjectByKey(unordered) {
  const ordered = Object.keys(unordered).sort().reduce((obj, key) => {
    obj[key] = unordered[key];
    return obj;
  }, {});
  return ordered;
}
function familiesToUnifontProviders({
  families,
  hashString
}) {
  const hashes = /* @__PURE__ */ new Set();
  const providers = [];
  for (const { provider } of families) {
    if (provider === LOCAL_PROVIDER_NAME) {
      continue;
    }
    const unifontProvider = provider.provider(provider.config);
    const hash = hashString(
      JSON.stringify(
        sortObjectByKey({
          name: unifontProvider._name,
          ...provider.config
        })
      )
    );
    unifontProvider._name += `-${hash}`;
    provider.name = unifontProvider._name;
    if (!hashes.has(hash)) {
      hashes.add(hash);
      providers.push(unifontProvider);
    }
  }
  return { families, providers };
}
function resolveEntrypoint(root, entrypoint) {
  const require2 = createRequire(root);
  try {
    return pathToFileURL(require2.resolve(entrypoint));
  } catch {
    return new URL(entrypoint, root);
  }
}
export {
  cache,
  extractFontType,
  familiesToUnifontProviders,
  generateFallbacksCSS,
  generateFontFace,
  isFontType,
  isGenericFontFamily,
  proxyURL,
  renderFontFace,
  renderFontSrc,
  resolveEntrypoint,
  resolveFontFamily,
  sortObjectByKey,
  toCSS,
  withoutQuotes
};
