import { parse as parseFont } from "opentype.js";

export type ProductionSvgProject = Record<string, string | number | boolean | null>;

const fontFiles: Record<string, string> = {
  grotesk: "/fonts/montserrat-700.woff",
  rounded: "/fonts/montserrat-700.woff",
  montserrat: "/fonts/montserrat-700.woff",
  condensed: "/fonts/bebas-neue-400.woff",
  bebas: "/fonts/bebas-neue-400.woff",
  oswald: "/fonts/oswald-600.woff",
  serif: "/fonts/playfair-display-700.woff",
  playfair: "/fonts/playfair-display-700.woff",
  pacifico: "/fonts/pacifico-400.woff",
  dancing: "/fonts/dancing-script-600.woff",
  allura: "/fonts/allura-400.woff",
  sacramento: "/fonts/sacramento-400.woff",
  satisfy: "/fonts/satisfy-400.woff",
};

export async function createProductionSvg(
  project: ProductionSvgProject,
  options: { logoUrl?: string } = {},
) {
  const widthMm = Math.max(10, Number(project.width_cm) * 10);
  const heightMm = Math.max(10, Number(project.height_cm) * 10);
  const logoType = String(project.logo_type ?? "");

  if (options.logoUrl && logoType === "image/svg+xml") {
    return svgEnvelope(widthMm, heightMm, await fetchAndEmbedSvg(options.logoUrl, widthMm, heightMm), project);
  }

  const fontResponse = await fetch(fontFiles[String(project.font)] ?? `/api/font-assets?slug=${encodeURIComponent(String(project.font))}`);
  if (!fontResponse.ok) throw new Error("Police introuvable.");
  const font = parseFont(await fontResponse.arrayBuffer());
  const text = String(project.sign_text || "ENSEIGNE");
  const glyphs = font.stringToGlyphs(text);
  const gapMm = Math.max(0, Number(project.letter_spacing_cm ?? 4) * 10);
  const availableMm = Math.max(widthMm * .2, widthMm - gapMm * Math.max(0, glyphs.length - 1));
  const totalAdvance = glyphs.reduce((sum, glyph) => sum + (glyph.advanceWidth || font.unitsPerEm), 0);
  const scaleX = availableMm / Math.max(1, totalAdvance);
  const scaleY = heightMm / Math.max(1, font.ascender - font.descender);
  const baseline = font.ascender * scaleY;
  let cursor = 0;
  const paths = glyphs.map((glyph, index) => {
    const path = glyph.getPath(0, 0, font.unitsPerEm).toPathData(3);
    const node = `<path d="${path}" transform="translate(${cursor.toFixed(3)} ${baseline.toFixed(3)}) scale(${scaleX.toFixed(6)} ${scaleY.toFixed(6)})" />`;
    cursor += (glyph.advanceWidth || font.unitsPerEm) * scaleX + (index < glyphs.length - 1 ? gapMm : 0);
    return node;
  }).join("");
  return svgEnvelope(
    widthMm,
    heightMm,
    `<g id="CUT_CONTOURS" fill="${escapeAttribute(String(project.sign_color || "#111111"))}">${paths}</g>`,
    project,
  );
}

export async function createNeonProductionSvg(
  project: ProductionSvgProject,
  options: { designUrl?: string } = {},
) {
  const widthMm = Math.max(10, Number(project.width_cm) * 10);
  const heightMm = Math.max(10, Number(project.height_cm) * 10);
  if (options.designUrl && String(project.design_type ?? "") === "image/svg+xml") {
    return svgEnvelope(widthMm, heightMm, await fetchAndEmbedSvg(options.designUrl, widthMm, heightMm), project);
  }

  const fontResponse = await fetch(fontFiles[String(project.font)] ?? `/api/font-assets?slug=${encodeURIComponent(String(project.font))}`);
  if (!fontResponse.ok) throw new Error("Police manuscrite introuvable.");
  const font = parseFont(await fontResponse.arrayBuffer());
  const text = String(project.neon_text || "NÉON");
  const glyphs = font.stringToGlyphs(text);
  const totalAdvance = glyphs.reduce((sum, glyph) => sum + (glyph.advanceWidth || font.unitsPerEm), 0);
  const scaleX = widthMm / Math.max(1, totalAdvance);
  const scaleY = heightMm / Math.max(1, font.ascender - font.descender);
  const baseline = font.ascender * scaleY;
  let cursor = 0;
  const paths = glyphs.map((glyph) => {
    const path = glyph.getPath(0, 0, font.unitsPerEm).toPathData(3);
    const node = `<path d="${path}" transform="translate(${cursor.toFixed(3)} ${baseline.toFixed(3)}) scale(${scaleX.toFixed(6)} ${scaleY.toFixed(6)})" />`;
    cursor += (glyph.advanceWidth || font.unitsPerEm) * scaleX;
    return node;
  }).join("");
  return svgEnvelope(
    widthMm,
    heightMm,
    `<g id="NEON_CONTOURS" fill="${escapeAttribute(String(project.color || "#ff426d"))}">${paths}</g>`,
    project,
  );
}

async function fetchAndEmbedSvg(url: string, widthMm: number, heightMm: number) {
  const source = await fetch(url).then((response) => {
    if (!response.ok) throw new Error("Fichier SVG introuvable.");
    return response.text();
  });
  const parsed = new DOMParser().parseFromString(source, "image/svg+xml");
  parsed.querySelectorAll("script,style,foreignObject,iframe,object,embed,animate,set").forEach((node) => node.remove());
  parsed.querySelectorAll("*").forEach((node) => Array.from(node.attributes).forEach((attribute) => {
    if (attribute.name.startsWith("on") || attribute.name === "style" || (/^(?:href|xlink:href)$/i.test(attribute.name) && /^(?:https?:|javascript:)/i.test(attribute.value))) {
      node.removeAttribute(attribute.name);
    }
  }));
  const sourceSvg = parsed.documentElement;
  const viewBox = sourceSvg.getAttribute("viewBox") || `0 0 ${sourceSvg.getAttribute("width") || 1000} ${sourceSvg.getAttribute("height") || 400}`;
  return `<svg x="0" y="0" width="${widthMm}" height="${heightMm}" viewBox="${escapeAttribute(viewBox)}" preserveAspectRatio="none">${sourceSvg.innerHTML}</svg>`;
}

function svgEnvelope(widthMm: number, heightMm: number, body: string, project: ProductionSvgProject) {
  return `<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" width="${widthMm}mm" height="${heightMm}mm" viewBox="0 0 ${widthMm} ${heightMm}" preserveAspectRatio="none">
  <title>${escapeXml(String(project.reference))} · ${escapeXml(String(project.sign_text || project.neon_text || "Projet"))}</title>
  <metadata>Échelle 1:1 · ${widthMm} × ${heightMm} mm · ${project.neon_text ? `néon flex ${escapeXml(String(project.tube_mm))} mm` : `profil ${escapeXml(String(project.profile_code))}`}</metadata>
  ${body}
</svg>`;
}

function escapeXml(value: string) {
  return value.replace(/[<>&'"]/g, (character) => ({ "<": "&lt;", ">": "&gt;", "&": "&amp;", "'": "&apos;", '"': "&quot;" })[character] ?? character);
}

function escapeAttribute(value: string) {
  return escapeXml(value);
}
