import { getNitroOrigin } from "#site-config/server/composables";
import { defu } from "defu";
import { withBase } from "ufo";
import { logger } from "../../../logger.js";
import { extractCodepoints, getDefaultFontFamily, loadFontsForRenderer } from "../fonts.js";
import { getExtractResourceUrls, getTakumi } from "./instances.js";
import { createTakumiNodes } from "./nodes.js";
const RE_QUOTES = /['"]/g;
async function getTakumiState(event) {
  const nitro = event._nitro;
  if (nitro._takumiState)
    return nitro._takumiState;
  const Renderer = await getTakumi();
  nitro._takumiState = {
    renderer: new Renderer(),
    loadedFontKeys: /* @__PURE__ */ new Set(),
    loadedFamilies: /* @__PURE__ */ new Set()
  };
  return nitro._takumiState;
}
async function loadFontsIntoRenderer(state, fonts) {
  for (const font of fonts) {
    if (!font.data)
      continue;
    const uniqueKey = font.cacheKey || `${font.family}-${font.weight}-${font.style}`;
    if (state.loadedFontKeys.has(uniqueKey))
      continue;
    const fontData = font.data instanceof ArrayBuffer ? new Uint8Array(font.data) : Uint8Array.from(font.data);
    try {
      await state.renderer.loadFont({
        name: font.family,
        data: fontData,
        weight: font.weight,
        style: font.style
      });
      state.loadedFamilies.add(font.family);
    } catch (err) {
      logger.warn(`Failed to load font "${font.family}" (weight: ${font.weight}) into takumi renderer: ${err.message}`);
    }
    state.loadedFontKeys.add(uniqueKey);
  }
}
function lookupFontFamily(family, loadedFamilies) {
  if (loadedFamilies.has(family))
    return family;
  for (const loaded of loadedFamilies) {
    if (loaded.toLowerCase() === family.toLowerCase())
      return loaded;
  }
}
function rewriteFontFamilies(node, loadedFamilies) {
  if (node.style?.fontFamily) {
    const families = node.style.fontFamily.split(",").map((f) => f.trim().replace(RE_QUOTES, ""));
    const resolved = families.map((f) => lookupFontFamily(f, loadedFamilies) || f);
    const seen = new Set(resolved.map((f) => f.toLowerCase()));
    for (const family of loadedFamilies) {
      if (!seen.has(family.toLowerCase())) {
        resolved.push(family);
        seen.add(family.toLowerCase());
      }
    }
    node.style.fontFamily = resolved.map((f) => `"${f}"`).join(", ");
  }
  if ("children" in node && node.children) {
    for (const child of node.children)
      rewriteFontFamilies(child, loadedFamilies);
  }
}
async function createImage(event, format) {
  const { options } = event;
  const { fontFamilyOverride, defaultFont } = getDefaultFontFamily(options);
  const nodes = await createTakumiNodes(event);
  const codepoints = extractCodepoints(nodes);
  const fonts = await loadFontsForRenderer(event, { supportedFormats: /* @__PURE__ */ new Set(["ttf", "woff2"]), component: options.component, fontFamilyOverride: fontFamilyOverride || defaultFont, codepoints });
  await event._nitro.hooks.callHook("nuxt-og-image:takumi:nodes", nodes, event);
  const state = await getTakumiState(event);
  await loadFontsIntoRenderer(state, fonts);
  const rootStyle = nodes.style ?? {};
  if (fontFamilyOverride && state.loadedFamilies.has(fontFamilyOverride)) {
    rootStyle.fontFamily = fontFamilyOverride;
  }
  nodes.style = rootStyle;
  rewriteFontFamilies(nodes, state.loadedFamilies);
  const extractResourceUrls = await getExtractResourceUrls();
  const resourceUrls = await extractResourceUrls(nodes);
  const origin = getNitroOrigin(event.e);
  const baseURL = event.runtimeConfig.app.baseURL;
  const fetchedResources = [];
  await Promise.all(resourceUrls.map(async (src) => {
    const urlsToTry = [src];
    if (src.startsWith("/")) {
      urlsToTry.push(withBase(src, origin));
      if (baseURL && baseURL !== "/" && !src.startsWith(baseURL)) {
        urlsToTry.push(withBase(withBase(src, baseURL), origin));
      }
    }
    for (const url of urlsToTry) {
      const data = await $fetch(url, { responseType: "arrayBuffer" }).catch(() => null);
      if (data) {
        fetchedResources.push({ src, data: new Uint8Array(data) });
        break;
      }
    }
  }));
  const maxDpr = event.runtimeConfig.security?.maxDpr || 2;
  const maxDim = event.runtimeConfig.security?.maxDimension || 2048;
  const dpr = Math.min(Math.max(1, options.takumi?.devicePixelRatio ?? 1), maxDpr);
  const renderOptions = defu(options.takumi, {
    width: Math.min(Number(options.width) * dpr, maxDim),
    height: Math.min(Number(options.height) * dpr, maxDim),
    format,
    fetchedResources,
    devicePixelRatio: dpr
  });
  return await state.renderer.render(nodes, renderOptions);
}
const TakumiRenderer = {
  name: "takumi",
  supportedFormats: ["png", "jpeg", "jpg", "webp", "json"],
  async createImage(e) {
    switch (e.extension) {
      case "png":
        return createImage(e, "png");
      case "jpeg":
      case "jpg":
        return createImage(e, "jpeg");
      case "webp":
        return createImage(e, "webp");
    }
  },
  async debug(e) {
    const [vnodes, fonts] = await Promise.all([
      createTakumiNodes(e),
      loadFontsForRenderer(e, { supportedFormats: /* @__PURE__ */ new Set(["ttf", "woff2"]), component: e.options.component })
    ]);
    return {
      vnodes,
      fonts: fonts.map(({ data: _, ...f }) => ({
        ...f,
        size: _.byteLength
      }))
    };
  }
};
export default TakumiRenderer;
