import { localDateCzStr } from "./date";

/**
 * Shared helpers for the four PDF routes (offers-pdf, issued-orders-pdf,
 * invoices-pdf, orders-pdf). These used to be near-verbatim copies in each
 * route file; the canonical variants live here:
 *
 * - `cleanQuillHtml` is the STRICT variant (strips javascript:/data:/vbscript:
 *   in BOTH href and src — defense-in-depth, DOMPurify runs first).
 * - `formatNum` uses the NBSP (U+00A0) thousands separator so amounts never
 *   line-wrap inside the number.
 * - `formatCurrency` is the per-currency-symbol variant from offers
 *   (€ / $ / Kč / £, anything else falls back to "<num> <code>").
 */

/** dd.MM.yyyy in Europe/Prague; invalid input echoes back as-is. */
export function formatDate(date: Date | string | null | undefined): string {
  if (!date) return "";
  const d = new Date(date);
  if (isNaN(d.getTime())) return String(date);
  return localDateCzStr(d);
}

/** Format number with comma decimal separator and NBSP thousands separator. */
export function formatNum(n: number, decimals = 2): string {
  const abs = Math.abs(n);
  const fixed = abs.toFixed(decimals);
  const [intPart, decPart] = fixed.split(".");
  const withSep = intPart.replace(/\B(?=(\d{3})+(?!\d))/g, " ");
  const result = decPart ? `${withSep},${decPart}` : withSep;
  return n < 0 ? `-${result}` : result;
}

/** Per-currency symbol formatting (offers variant). */
export function formatCurrency(amount: number, currency: string): string {
  const n = Number(amount) || 0;
  switch (currency) {
    case "EUR":
      return `${formatNum(n, 2)} €`;
    case "USD":
      return `$${Math.abs(n)
        .toFixed(2)
        .replace(/\B(?=(\d{3})+(?!\d))/g, ",")}`;
    case "CZK":
      return `${formatNum(n, 2)} Kč`;
    case "GBP":
      return `£${Math.abs(n)
        .toFixed(2)
        .replace(/\B(?=(\d{3})+(?!\d))/g, ",")}`;
    default:
      return `${formatNum(n, 2)} ${currency}`;
  }
}

export function escapeHtml(str: string | null | undefined): string {
  if (!str) return "";
  return str
    .replace(/&/g, "&amp;")
    .replace(/</g, "&lt;")
    .replace(/>/g, "&gt;")
    .replace(/"/g, "&quot;");
}

/**
 * Company logo from company_settings.logo_data as a data: URI (mime sniffed
 * from magic bytes). Data URIs are the ONLY image source Chromium loads in
 * header/footer templates; the body templates reuse the same URI.
 */
export function logoDataUriFromSettings(
  settings: Record<string, unknown> | null,
): string {
  if (!settings?.logo_data) return "";
  const buf = Buffer.from(settings.logo_data as Buffer);
  let mime = "image/png";
  if (buf[0] === 0xff && buf[1] === 0xd8) mime = "image/jpeg";
  else if (buf[0] === 0x47 && buf[1] === 0x49) mime = "image/gif";
  else if (buf[0] === 0x52 && buf[1] === 0x49) mime = "image/webp";
  return `data:${escapeHtml(mime)};base64,${buf.toString("base64")}`;
}

/**
 * Unified repeating per-page header for the red-accent PDF family (invoices +
 * issued orders), rendered by Puppeteer in the top page margin: company logo
 * left (max 22mm tall — same size as the documents' original body header),
 * red bold heading right, 2pt #de3a3a rule underneath.
 *
 * Contract shared with the footers: inline styles only, explicit font-size
 * (Chromium defaults template text to 0), images as data: URIs only, padding
 * matched to the documents' 12mm @page side margins. The @page top margin
 * must be 32mm to make room (htmlToPdf grows it when headerTemplate is set,
 * but Chromium lays out by the CSS @page margins — keep them in sync).
 */
export function buildPdfHeaderTemplate(
  logoDataUri: string,
  heading: string,
): string {
  return `<div style="width:100%; font-family:Tahoma, sans-serif; padding:0 12mm; box-sizing:border-box;">
    <div style="display:flex; justify-content:space-between; align-items:center; padding-bottom:1mm; border-bottom:2pt solid #de3a3a;">
      <div style="flex:0 0 auto;">${logoDataUri ? `<img src="${logoDataUri}" style="max-width:42mm; max-height:22mm; object-fit:contain;" />` : ""}</div>
      <div style="font-size:13pt; font-weight:700; color:#de3a3a; text-align:right; letter-spacing:0.03em;">${escapeHtml(heading)}</div>
    </div>
  </div>`;
}

/**
 * Sanitize Quill HTML for the PDF templates: remove dangerous tags, event
 * handlers, javascript:/data:/vbscript: URLs (href AND src), inline styles,
 * and merge adjacent identical spans. Runs AFTER DOMPurify at every call site
 * (regex pass = defense-in-depth, not the primary sanitizer).
 */
export function cleanQuillHtml(html: string | null | undefined): string {
  if (!html) return "";
  let s = html;
  // Remove dangerous tags with content
  s = s.replace(
    /<(script|iframe|object|embed|style|link|meta|base|form|input|textarea|button|select|svg|math)[^>]*>[\s\S]*?<\/\1>/gi,
    "",
  );
  s = s.replace(
    /<(script|iframe|object|embed|style|link|meta|base|form|input|textarea|button|select|svg|math)[^>]*\/?>/gi,
    "",
  );
  // Strip event handlers
  s = s.replace(/\s+on\w+\s*=\s*("[^"]*"|'[^']*'|[^\s>]*)/gi, "");
  s = s.replace(/\s+on\w+\s*=\s*[^\s>]*/gi, "");
  // Strip javascript:/data:/vbscript: in href and src
  s = s.replace(
    /href\s*=\s*["']?\s*(javascript|data|vbscript)\s*:[^"'>\s]*/gi,
    'href="#"',
  );
  s = s.replace(
    /src\s*=\s*["']?\s*(javascript|data|vbscript)\s*:[^"'>\s]*/gi,
    'src=""',
  );
  // Replace &nbsp; with regular space (outside of tags)
  s = s.replace(/(&nbsp;)/g, " ");
  s = s.replace(/\s+style\s*=\s*("[^"]*"|'[^']*'|[^\s>]*)/gi, "");
  // Merge adjacent spans with same attributes
  let prev = "";
  while (prev !== s) {
    prev = s;
    s = s.replace(/<span([^>]*)>(.*?)<\/span>\s*<span\1>/gs, "<span$1>$2");
  }
  return s;
}

/** CSS rules for Quill's .ql-indent-1..9 classes (identical in all templates). */
export function buildQuillIndentCss(): string {
  let indentCSS = "";
  for (let n = 1; n <= 9; n++) {
    const pad = n * 3;
    const liPad = n * 3 + 1.5;
    indentCSS += `  .ql-indent-${n} { padding-left: ${pad}em; }\n`;
    indentCSS += `  li.ql-indent-${n} { padding-left: ${liPad}em; }\n`;
  }
  return indentCSS;
}
