import { resolve } from "#og-image-virtual/public-assets.mjs";
import { fontRequirements, getComponentFontMap } from "#og-image/font-requirements";
import resolvedFonts from "#og-image/fonts";
import availableFonts from "#og-image/fonts-available";
import { logger } from "../../logger.js";
import { fontArrayCache, fontCache } from "./cache/lru.js";
import { codepointsIntersectRanges, parseUnicodeRange } from "./unicode-range.js";
export { codepointsIntersectRanges, extractCodepoints, parseUnicodeRange } from "./unicode-range.js";
function fontFormat(src) {
  if (src.endsWith(".woff2"))
    return "woff2";
  if (src.endsWith(".woff"))
    return "woff";
  if (src.endsWith(".otf"))
    return "otf";
  return "ttf";
}
async function loadFont(event, font, src) {
  const cacheKey = `${font.family}-${font.weight}-${font.style}-${src}`;
  const cached = fontCache.get(cacheKey);
  if (cached)
    return cached;
  const data = await resolve(event, { ...font, src }).catch((err) => {
    logger.warn(`Failed to load font ${font.family}: ${err.message}`);
    return null;
  });
  if (!data)
    return null;
  fontCache.set(cacheKey, data);
  return data;
}
function findClosestWeight(target, available) {
  return available.reduce(
    (closest, w) => Math.abs(w - target) < Math.abs(closest - target) ? w : closest
  );
}
function selectFontsForRequirements(allFonts, requirements) {
  if (requirements.hasDynamicBindings)
    return [...allFonts];
  const fallbackFonts = [];
  const byFamily = /* @__PURE__ */ new Map();
  const requiredFamilies = requirements.families.length > 0 ? new Set(requirements.families) : null;
  for (const f of allFonts) {
    if (f.fallback) {
      fallbackFonts.push(f);
      continue;
    }
    if (!requirements.styles.includes(f.style))
      continue;
    if (requiredFamilies && !requiredFamilies.has(f.family))
      continue;
    const arr = byFamily.get(f.family) || [];
    arr.push(f);
    byFamily.set(f.family, arr);
  }
  const selected = [];
  for (const [, familyFonts] of byFamily) {
    const availableWeights = [...new Set(familyFonts.map((f) => f.weight))].toSorted((a, b) => a - b);
    if (availableWeights.length === 0)
      continue;
    const selectedWeights = /* @__PURE__ */ new Set();
    for (const w of requirements.weights) {
      if (availableWeights.includes(w)) {
        selectedWeights.add(w);
      } else {
        selectedWeights.add(findClosestWeight(w, availableWeights));
      }
    }
    selected.push(...familyFonts.filter((f) => selectedWeights.has(f.weight)));
  }
  selected.push(...fallbackFonts);
  return selected;
}
const _warnedFontKeys = /* @__PURE__ */ new Set();
export function loadAllFontsDebug(component) {
  const map = getComponentFontMap();
  const componentReqs = component ? map[component] : null;
  return {
    component,
    componentReqs: componentReqs ? { families: componentReqs.families, weights: componentReqs.weights, hasDynamicBindings: componentReqs.hasDynamicBindings } : null,
    globalReqs: { families: fontRequirements.families, weights: fontRequirements.weights },
    componentFontMapKeys: Object.keys(map),
    resolvedFontFamilies: [...new Set(resolvedFonts.map((f) => f.family))]
  };
}
export async function loadAllFonts(event, options) {
  const map = getComponentFontMap();
  const componentReqs = options.component ? map[options.component] : null;
  const usingGlobalFallback = !!(componentReqs && componentReqs.hasDynamicBindings);
  const reqs = componentReqs && !componentReqs.hasDynamicBindings ? componentReqs : fontRequirements;
  const fonts = selectFontsForRequirements(resolvedFonts, reqs);
  if (options.fontFamilyOverride) {
    const loadedFamilies = new Set(fonts.map((f) => f.family));
    if (!loadedFamilies.has(options.fontFamilyOverride)) {
      const overrideFonts = availableFonts.filter((f) => f.family === options.fontFamilyOverride);
      if (overrideFonts.length > 0) {
        fonts.push(...overrideFonts);
      } else {
        const resolvedOverride = resolvedFonts.filter((f) => f.family === options.fontFamilyOverride);
        fonts.push(...resolvedOverride);
      }
    }
  }
  if (options.codepoints && options.codepoints.size > 0) {
    for (let i = fonts.length - 1; i >= 0; i--) {
      const f = fonts[i];
      if (!f.unicodeRange)
        continue;
      const ranges = parseUnicodeRange(f.unicodeRange);
      if (!ranges)
        continue;
      if (!codepointsIntersectRanges(options.codepoints, ranges))
        fonts.splice(i, 1);
    }
  }
  const results = await Promise.all(
    fonts.map(async (f) => {
      let src = f.src;
      const srcFormat = fontFormat(f.src);
      if (!options.supportedFormats.has(srcFormat)) {
        if (f.satoriSrc && options.supportedFormats.has(fontFormat(f.satoriSrc))) {
          src = f.satoriSrc;
        } else {
          return null;
        }
      }
      let data = await loadFont(event, f, src);
      if (!data && src !== f.src && options.supportedFormats.has(srcFormat)) {
        data = await loadFont(event, f, f.src);
        if (data)
          src = f.src;
      }
      if (!data)
        return null;
      return {
        ...f,
        cacheKey: `${f.family}-${f.weight}-${f.style}-${src}`,
        data
      };
    })
  );
  const loaded = results.filter((f) => f !== null);
  const fingerprint = loaded.map((f) => f.cacheKey).sort().join("|");
  const cachedArray = fontArrayCache.get(fingerprint);
  if (cachedArray)
    return cachedArray;
  fontArrayCache.set(fingerprint, loaded);
  const isCommunity = options.component && map[options.component]?.category === "community";
  if (!options.supportedFormats.has("woff2") && loaded.length === 0 && fonts.length > 0 && !isCommunity) {
    const variableFamilies = [...new Set(fonts.map((f) => f.family))];
    const warnKey = `variable-fonts-${variableFamilies.join(",")}`;
    if (!_warnedFontKeys.has(warnKey)) {
      if (_warnedFontKeys.size >= 100)
        _warnedFontKeys.clear();
      _warnedFontKeys.add(warnKey);
      logger.warn(`All fonts are variable fonts (${variableFamilies.join(", ")}). Variable fonts are not supported by Satori renderer. Falling back to bundled Inter font. Consider using the 'takumi' renderer for variable font support.`);
    }
  }
  if (import.meta.dev && reqs.weights.length > 0 && !isCommunity) {
    const families = reqs.families.length > 0 ? reqs.families : [...new Set(loaded.map((f) => f.family))];
    const component = options.component ? ` (${options.component})` : "";
    const fallbackNote = usingGlobalFallback ? " Note: using global font requirements because this component has dynamic font bindings." : "";
    for (const family of families) {
      const loadedWeights = [...new Set(loaded.filter((f) => f.family === family).map((f) => f.weight))].toSorted((a, b) => a - b);
      if (loadedWeights.length === 0)
        continue;
      for (const reqWeight of reqs.weights) {
        if (loadedWeights.includes(reqWeight))
          continue;
        const closest = findClosestWeight(reqWeight, loadedWeights);
        const warnKey = `${family}-${reqWeight}-${closest}-${options.component || ""}`;
        if (_warnedFontKeys.has(warnKey))
          continue;
        if (_warnedFontKeys.size >= 100)
          _warnedFontKeys.clear();
        _warnedFontKeys.add(warnKey);
        logger.warn(`Font "${family}" weight ${reqWeight} not configured${component}, using ${closest} instead.${fallbackNote}`);
      }
    }
  }
  return loaded;
}
export async function loadDefinedFonts(event, fontDefs) {
  const results = [];
  for (const def of fontDefs) {
    if (!def || typeof def !== "object" || !def.path)
      continue;
    const name = def.name;
    const weight = def.weight || 400;
    const style = def.style === "italic" ? "italic" : "normal";
    const path = def.path;
    const fontConfig = { family: name, weight, style, src: path, localPath: path };
    const data = await resolve(event.e, fontConfig).catch(() => null);
    if (data) {
      results.push({
        ...fontConfig,
        cacheKey: `custom-${name}-${weight}-${style}-${path}`,
        data
      });
    }
  }
  return results;
}
export async function loadFontsForRenderer(event, options) {
  const baseFonts = await loadAllFonts(event.e, options);
  if (!Array.isArray(options.fontDefs) || options.fontDefs.length === 0)
    return baseFonts;
  const customFonts = await loadDefinedFonts(event, options.fontDefs);
  return [...baseFonts, ...customFonts];
}
export function getDefaultFontFamily(options) {
  const fontFamilyOverride = options.props?.fontFamily;
  const defaultFont = resolvedFonts[0]?.family;
  return { fontFamilyOverride, defaultFont };
}
