import {
  PDFDocument,
  PDFFont,
  PDFPage,
  StandardFonts,
  rgb,
} from "pdf-lib";
import type { ProcessedReceipt, TaxBreakdownItem, LineItem } from "../schema/receipt";
import { siteDomain } from "../seo/site";

/**
 * PDF-Export (A4, Hochformat) – ein moderner, druck- und archivierfähiger Belegreport.
 *
 * Grundsätze:
 *  - Clean, modernes & helles Design (keine klobigen KI-Kopfzeilen oder grellen Akzentstreifen).
 *  - Es werden KEINE Werte erfunden. Fehlt eine Angabe, bleibt das Feld leer.
 *  - Beträge unterschiedlicher Währungen werden NIE zu einer Summe addiert.
 *  - Statusangaben sind bewusst unicode-frei: Die Standard-14-PDF-
 *    Schriften decken nur WinAnsi/Latin-1 ab, Sonderzeichen würden zu
 *    kaputten Glyphen führen.
 *
 * Formel-/HTML-Injection-Sicherheit: pdf-lib zeichnet Strings als Literal-Text;
 * `normalizeText` entfernt zusätzlich alle Kontroll- und Nicht-WinAnsi-Zeichen.
 */

export type PdfLocale = "de" | "en";

export interface PdfExportOptions {
  /** Sprache der Beschriftungen. Zahlenformat folgt der Sprache. */
  locale?: PdfLocale;
  /** Optionaler Titel (überschreibt das Standard-"BELEGE EXPORT"). */
  title?: string;
  /** Optionaler Berichtszeitraum (ISO-Datum) für den Untertitel. */
  dateFrom?: string;
  dateTo?: string;
}

// ---------------------------------------------------------------------------
// Formatkonstanten & Farben (Clean & Light Theme)
// ---------------------------------------------------------------------------

type PdfColor = ReturnType<typeof rgb>;

const PAGE_W = 595.28; // A4 Breite
const PAGE_H = 841.89; // A4 Höhe
const MARGIN_X = 40;
const MARGIN_TOP = 64;
const MARGIN_BOTTOM = 48;

// Primärfarben & Nuancen (Hell, Modern, Hoher Kontrast)
const COLOR_INK = rgb(0.059, 0.09, 0.16); // #0F172A (Tiefer Schieferton)
const COLOR_BODY = rgb(0.2, 0.25, 0.33); // #334155 (Fließtext / Werte)
const COLOR_SLATE = rgb(0.39, 0.45, 0.55); // #64748B (Sekundärtext / Labels)
const COLOR_MUTED = rgb(0.58, 0.647, 0.722); // #94A3B8 (Dezente Metadaten)
const COLOR_BORDER = rgb(0.886, 0.91, 0.941); // #E2E8F0 (Haarlinien-Rahmen)
const COLOR_BORDER_LIGHT = rgb(0.94, 0.95, 0.96); // #F1F5F9 (Tabellenzeilen-Trenner)
const COLOR_SURFACE = rgb(0.973, 0.98, 0.988); // #F8FAFC (Karten- und Kachel-Hintergrund)
const COLOR_SURFACE_ALT = rgb(0.945, 0.961, 0.976); // #F1F5F9 (Tabellenkopf-Hintergrund)
const COLOR_WHITE = rgb(1, 1, 1);

// Semantische Statusfarben (Sanfte Pill-Badges)
const COLOR_OK = rgb(0.086, 0.639, 0.29); // #16A34A
const COLOR_OK_BG = rgb(0.92, 0.98, 0.94); // #DCFCE7
const COLOR_WARN = rgb(0.851, 0.467, 0.024); // #D97706
const COLOR_WARN_BG = rgb(0.99, 0.96, 0.89); // #FEF3C7

const LINE_H_MULT = 1.3;

// ---------------------------------------------------------------------------
// Sprache & Lokalisierung
// ---------------------------------------------------------------------------

interface PdfLabels {
  brandTitle: string;
  brandSubtitle: string;
  reportTitle: string;
  reportReceipts: (n: number) => string;
  generated: string;
  periodLabel: string;
  summaryTitle: string;
  countLabel: string;
  netTotal: string;
  vatTotal: string;
  grossTotal: string;
  mixedCurrencies: string;
  perCurrencyBreakdown: string;
  receiptHeader: (n: number) => string;
  colDate: string;
  colCategory: string;
  colDocType: string;
  colReceiptNo: string;
  colCurrency: string;
  colPayment: string;
  colTaxId: string;
  colSmallInvoice: string;
  smallInvoiceYes: string;
  smallInvoiceNo: string;
  colOccasion: string;
  colParticipants: string;
  amountNet: string;
  amountVat: (rate: string) => string;
  amountVatTotal: string;
  amountTip: string;
  amountGross: string;
  amountPaid: string;
  statusConfirmed: string;
  statusValid: string;
  statusReview: (n: number) => string;
  colItem: string;
  colQty: string;
  colUnitPrice: string;
  colVatRate: string;
  colLineTotal: string;
  noLineItems: (category: string) => string;
  footerNote: string;
  page: string;
  of: string;
}

const PDF_LABELS: Record<PdfLocale, PdfLabels> = {
  de: {
    brandTitle: "SCANRECEIPTS",
    brandSubtitle: "BELEG-EXPORT",
    reportTitle: "BELEGE EXPORT",
    reportReceipts: (n: number) => `${n} Beleg${n === 1 ? "" : "e"}`,
    generated: "Erstellt am",
    periodLabel: "Zeitraum",
    summaryTitle: "ZUSAMMENFASSUNG",
    countLabel: "ANZAHL BELEGE",
    netTotal: "NETTO GESAMT",
    vatTotal: "MWST GESAMT",
    grossTotal: "BRUTTO GESAMT",
    mixedCurrencies: "mehrere Währungen",
    perCurrencyBreakdown: "Aufteilung je Währung",
    receiptHeader: (n: number) => `BELEG #${String(n).padStart(2, "0")}`,
    colDate: "Belegdatum",
    colCategory: "Kategorie",
    colDocType: "Dokumenttyp",
    colReceiptNo: "Beleg-Nr.",
    colCurrency: "Währung",
    colPayment: "Zahlungsart",
    colTaxId: "Steuernummer / USt-IdNr.",
    colSmallInvoice: "Kleinbetrag § 33 UStDV",
    smallInvoiceYes: "Ja",
    smallInvoiceNo: "Nein",
    colOccasion: "Anlass (Bewirtung)",
    colParticipants: "Teilnehmer (Bewirtung)",
    amountNet: "Netto",
    amountVat: (rate: string) => `MwSt ${rate}`,
    amountVatTotal: "MwSt gesamt",
    amountTip: "Trinkgeld",
    amountGross: "Brutto Gesamt",
    amountPaid: "Gesamt gezahlt",
    statusConfirmed: "Bestätigt",
    statusValid: "Valide",
    statusReview: (n: number) => `Prüfen (${n})`,
    colItem: "Artikel / Position",
    colQty: "Menge",
    colUnitPrice: "Einzelpreis",
    colVatRate: "MwSt %",
    colLineTotal: "Gesamtpreis",
    noLineItems: (category: string) =>
      `Gesamtbeleg (${category}) – keine Einzelpositionen erfasst`,
    footerNote: "Vertraulich · Automatisch generierter Belegexport",
    page: "Seite",
    of: "von",
  },
  en: {
    brandTitle: "SCANRECEIPTS",
    brandSubtitle: "RECEIPT EXPORT",
    reportTitle: "RECEIPT EXPORT",
    reportReceipts: (n: number) => `${n} receipt${n === 1 ? "" : "s"}`,
    generated: "Generated",
    periodLabel: "Period",
    summaryTitle: "SUMMARY",
    countLabel: "RECEIPT COUNT",
    netTotal: "TOTAL NET",
    vatTotal: "VAT TOTAL",
    grossTotal: "GROSS TOTAL",
    mixedCurrencies: "multiple currencies",
    perCurrencyBreakdown: "Breakdown per currency",
    receiptHeader: (n: number) => `RECEIPT #${String(n).padStart(2, "0")}`,
    colDate: "Receipt date",
    colCategory: "Category",
    colDocType: "Document type",
    colReceiptNo: "Receipt no.",
    colCurrency: "Currency",
    colPayment: "Payment method",
    colTaxId: "Tax ID / VAT no.",
    colSmallInvoice: "Small-value invoice § 33 UStDV",
    smallInvoiceYes: "Yes",
    smallInvoiceNo: "No",
    colOccasion: "Occasion (hospitality)",
    colParticipants: "Attendees (hospitality)",
    amountNet: "Net",
    amountVat: (rate: string) => `VAT ${rate}`,
    amountVatTotal: "VAT total",
    amountTip: "Tip",
    amountGross: "Gross total",
    amountPaid: "Total paid",
    statusConfirmed: "Confirmed",
    statusValid: "Valid",
    statusReview: (n: number) => `Review (${n})`,
    colItem: "Item / Position",
    colQty: "Qty",
    colUnitPrice: "Unit price",
    colVatRate: "VAT %",
    colLineTotal: "Line total",
    noLineItems: (category: string) =>
      `Whole receipt (${category}) – no line items captured`,
    footerNote: "Confidential · Automated receipt export",
    page: "Page",
    of: "of",
  },
};

// ---------------------------------------------------------------------------
// Defensive Zugriffshelfer
// ---------------------------------------------------------------------------

function round2(value: number): number {
  return Math.round((value + Number.EPSILON) * 100) / 100;
}

function num(value: unknown): number {
  return typeof value === "number" && Number.isFinite(value) ? value : 0;
}

function isNum(value: unknown): value is number {
  return typeof value === "number" && Number.isFinite(value);
}

function text(value: unknown): string {
  return typeof value === "string" ? value : "";
}

function taxEntries(receipt: ProcessedReceipt): TaxBreakdownItem[] {
  const list = (receipt as { taxBreakdown?: unknown }).taxBreakdown;
  if (!Array.isArray(list)) return [];
  return list.filter((t): t is TaxBreakdownItem => !!t && typeof t === "object");
}

function lineItemsOf(receipt: ProcessedReceipt): LineItem[] {
  const list = (receipt as { lineItems?: unknown }).lineItems;
  if (!Array.isArray(list)) return [];
  return list.filter((i): i is LineItem => !!i && typeof i === "object");
}

function grossOf(receipt: ProcessedReceipt): number {
  return round2(num(receipt?.totalAmount?.value));
}

function totalTaxOf(receipt: ProcessedReceipt): number {
  return round2(taxEntries(receipt).reduce((sum, t) => sum + num(t.taxAmount), 0));
}

function netOf(receipt: ProcessedReceipt): number {
  const declared = (receipt as { netAmount?: unknown }).netAmount;
  if (isNum(declared)) return round2(declared);
  return round2(grossOf(receipt) - totalTaxOf(receipt));
}

function tipOf(receipt: ProcessedReceipt): number | null {
  const val = (receipt as { tipAmount?: unknown }).tipAmount;
  if (!isNum(val) || val <= 0) return null;
  return round2(val);
}

function currencyOf(receipt: ProcessedReceipt): string {
  const raw = text((receipt as { currency?: unknown }).currency).trim().toUpperCase();
  return raw.length > 0 ? raw : "EUR";
}

function readHospitality(
  receipt: ProcessedReceipt
): { occasion: string; participants: string } | null {
  const raw = (receipt as { hospitality?: unknown }).hospitality;
  if (!raw || typeof raw !== "object") return null;
  const rec = raw as { occasion?: unknown; participants?: unknown };
  return { occasion: text(rec.occasion), participants: text(rec.participants) };
}

function isHospitalityReceipt(receipt: ProcessedReceipt): boolean {
  return (
    receipt?.documentType === "BEWIRTUNGSBELEG" || receipt?.suggestedCategory === "Bewirtung"
  );
}

function isUserConfirmed(receipt: ProcessedReceipt): boolean {
  return (receipt?.validation as { userConfirmed?: unknown } | undefined)?.userConfirmed === true;
}

function issueCount(receipt: ProcessedReceipt): number {
  const issues = (receipt?.validation as { issues?: unknown } | undefined)?.issues;
  return Array.isArray(issues) ? issues.length : 0;
}

function receiptRates(receipt: ProcessedReceipt): number[] {
  const rates = new Set<number>();
  for (const entry of taxEntries(receipt)) {
    if (!isNum(entry.ratePercent)) continue;
    rates.add(Math.round(entry.ratePercent * 1000) / 1000);
  }
  return Array.from(rates).sort((a, b) => a - b);
}

function taxAmountForRate(receipt: ProcessedReceipt, rate: number): number {
  return round2(
    taxEntries(receipt)
      .filter((t) => isNum(t.ratePercent) && Math.round(t.ratePercent * 1000) / 1000 === rate)
      .reduce((sum, t) => sum + num(t.taxAmount), 0)
  );
}

function rateLabel(rate: number, locale: PdfLocale = "de"): string {
  const value = Math.round(rate * 1000) / 1000;
  if (locale === "en") return `${value}%`;
  const str = Number.isInteger(value) ? String(value) : String(value).replace(".", ",");
  return `${str} %`;
}

function currencySymbol(code: string): string {
  return code === "EUR" ? "€" : code;
}

function isSmallValueInvoice(receipt: ProcessedReceipt): boolean | null {
  if (currencyOf(receipt) !== "EUR") return null;
  return Math.abs(grossOf(receipt)) <= 250;
}

function statusLabel(receipt: ProcessedReceipt, L: PdfLabels): string {
  if (isUserConfirmed(receipt)) return L.statusConfirmed;
  if (receipt?.validation?.isMathValid) return L.statusValid;
  const count = issueCount(receipt);
  return count > 0 ? L.statusReview(count) : L.statusReview(0);
}

// ---------------------------------------------------------------------------
// Text- & Zahlenformatierung
// ---------------------------------------------------------------------------

function normalizeText(value: unknown): string {
  return text(value)
    .replace(/[\u2010\u2011\u2012\u2013\u2014\u2015]/g, "-")
    .replace(/[\u2018\u2019\u201A]/g, "'")
    .replace(/[\u201C\u201D\u201E]/g, '"')
    .replace(/\u2026/g, "...")
    .replace(/[^\x20-\x7E\u00A0-\u00FF\u20AC]/g, "");
}

function truncateToWidth(font: PDFFont, value: string, maxWidth: number, size: number): string {
  let str = normalizeText(value);
  if (font.widthOfTextAtSize(str, size) <= maxWidth) return str;
  while (str.length > 0 && font.widthOfTextAtSize(`${str}...`, size) > maxWidth) {
    str = str.slice(0, -1);
  }
  return `${str}...`;
}

function formatMoney(value: number, currency: string, locale: PdfLocale): string {
  const abs = Math.abs(value);
  const sign = value < 0 ? "-" : "";
  const decimals = abs.toFixed(2).split(".");
  const intPart = decimals[0].replace(/\B(?=(\d{3})+(?!\d))/g, locale === "de" ? "." : ",");
  const decPart = decimals[1];
  const number = locale === "de" ? `${intPart},${decPart}` : `${intPart}.${decPart}`;
  const symbol = currencySymbol(currency);
  return `${sign}${number} ${symbol}`;
}

function formatQuantity(value: unknown, locale: PdfLocale): string {
  if (!isNum(value)) return "";
  const rounded = Math.round(value * 100) / 100;
  if (Number.isInteger(rounded)) return String(rounded);
  const fixed = rounded.toFixed(2);
  return locale === "de" ? fixed.replace(".", ",") : fixed;
}

function formatDate(isoDate: unknown, locale: PdfLocale): string {
  const raw = text(isoDate).trim();
  const match = /^(\d{4})-(\d{2})-(\d{2})/.exec(raw);
  if (!match) return normalizeText(raw);
  if (locale === "en") return `${match[1]}-${match[2]}-${match[3]}`;
  return `${match[3]}.${match[2]}.${match[1]}`;
}

// ---------------------------------------------------------------------------
// Layout-Engine
// ---------------------------------------------------------------------------

class PdfReport {
  readonly doc: PDFDocument;
  readonly reg: PDFFont;
  readonly bold: PDFFont;
  readonly oblique: PDFFont;
  readonly locale: PdfLocale;
  readonly L: PdfLabels;

  page: PDFPage;
  /** Cursor ab Oberkante (in Points, nach unten wachsend). */
  y = MARGIN_TOP;
  pageNumber = 1;
  private readonly pages: PDFPage[] = [];

  constructor(
    doc: PDFDocument,
    fonts: { reg: PDFFont; bold: PDFFont; oblique: PDFFont },
    locale: PdfLocale,
    labels: PdfLabels
  ) {
    this.doc = doc;
    this.locale = locale;
    this.L = labels;
    this.reg = fonts.reg;
    this.bold = fonts.bold;
    this.oblique = fonts.oblique;
    this.page = this.addPage();
  }

  addPage(): PDFPage {
    const page = this.doc.addPage([PAGE_W, PAGE_H]);
    this.pages.push(page);
    return page;
  }

  ensure(space: number): void {
    if (this.y + space <= PAGE_H - MARGIN_BOTTOM) return;
    this.newPage();
  }

  newPage(): void {
    this.page = this.addPage();
    this.pageNumber++;
    this.y = MARGIN_TOP;
    this.drawHeader();
  }

  /** Heller, eleganter Seitenkopf ohne erdrückende dunkle Farbblöcke. */
  drawHeader(): void {
    const headerY = 28;

    // Brand: "SCANRECEIPTS"
    this.drawText(this.L.brandTitle, {
      x: MARGIN_X,
      y: headerY,
      size: 10,
      font: this.bold,
      color: COLOR_INK,
    });

    // Subtitle / Rubrik neben dem Logo
    const brandW = this.bold.widthOfTextAtSize(normalizeText(this.L.brandTitle), 10);
    this.drawText(`  ·   ${this.L.brandSubtitle}`, {
      x: MARGIN_X + brandW,
      y: headerY + 1,
      size: 7.5,
      font: this.bold,
      color: COLOR_MUTED,
    });

    // Erstellungsdatum rechtsbündig
    this.drawRightText(
      `${this.L.generated}: ${formatDate(new Date().toISOString(), this.locale)}`,
      {
        x: PAGE_W - MARGIN_X,
        y: headerY + 1,
        size: 8,
        font: this.reg,
        color: COLOR_SLATE,
      }
    );

    // Zarte Haarlinie unter dem Header
    this.drawHLine(MARGIN_X, PAGE_W - MARGIN_X, headerY + 16, COLOR_BORDER, 0.75);
  }

  /** y ist die Oberkante des Rechtecks (top-down). */
  drawRectFromTop(x: number, y: number, w: number, h: number, color: PdfColor): void {
    this.page.drawRectangle({ x, y: PAGE_H - y - h, width: w, height: h, color });
  }

  /** Zeichnet eine helle Karte mit sauberem Haarlinien-Rahmen. */
  drawCardBox(
    x: number,
    y: number,
    w: number,
    h: number,
    bgColor: PdfColor = COLOR_WHITE,
    borderColor: PdfColor = COLOR_BORDER
  ): void {
    this.drawRectFromTop(x, y, w, h, bgColor);
    this.drawHLine(x, x + w, y, borderColor, 0.75);
    this.drawHLine(x, x + w, y + h, borderColor, 0.75);
    this.page.drawLine({
      start: { x, y: PAGE_H - y },
      end: { x, y: PAGE_H - y - h },
      thickness: 0.75,
      color: borderColor,
    });
    this.page.drawLine({
      start: { x: x + w, y: PAGE_H - y },
      end: { x: x + w, y: PAGE_H - y - h },
      thickness: 0.75,
      color: borderColor,
    });
  }

  /** Zeichnet ein dezentes, modernes Status-Pill-Badge. */
  drawPill(
    textVal: string,
    opts: {
      rightX: number;
      y: number;
      font: PDFFont;
      size: number;
      textColor: PdfColor;
      bgColor: PdfColor;
    }
  ): void {
    const str = normalizeText(textVal);
    const textW = opts.font.widthOfTextAtSize(str, opts.size);
    const padX = 6;
    const pillW = textW + padX * 2;
    const pillH = opts.size + 6;
    const pillX = opts.rightX - pillW;
    const pillY = opts.y - 1;

    this.drawRectFromTop(pillX, pillY, pillW, pillH, opts.bgColor);
    this.drawCenteredText(str, {
      centerX: pillX + pillW / 2,
      y: pillY + 3,
      size: opts.size,
      font: opts.font,
      color: opts.textColor,
    });
  }

  /** Horizontale Linie (top-down). */
  drawHLine(x1: number, x2: number, y: number, color: PdfColor, thickness = 0.75): void {
    this.page.drawLine({
      start: { x: x1, y: PAGE_H - y },
      end: { x: x2, y: PAGE_H - y },
      thickness,
      color,
    });
  }

  drawText(
    value: string,
    opts: {
      x: number;
      y: number;
      size: number;
      font: PDFFont;
      color: PdfColor;
    }
  ): void {
    const str = normalizeText(value);
    if (!str) return;
    this.page.drawText(str, {
      x: opts.x,
      y: PAGE_H - opts.y - opts.size * 0.8,
      size: opts.size,
      font: opts.font,
      color: opts.color,
    });
  }

  drawRightText(
    value: string,
    opts: {
      x: number;
      y: number;
      size: number;
      font: PDFFont;
      color: PdfColor;
    }
  ): void {
    const str = normalizeText(value);
    if (!str) return;
    const width = opts.font.widthOfTextAtSize(str, opts.size);
    this.drawText(str, { ...opts, x: opts.x - width });
  }

  drawCenteredText(
    value: string,
    opts: {
      centerX: number;
      y: number;
      size: number;
      font: PDFFont;
      color: PdfColor;
    }
  ): void {
    const str = normalizeText(value);
    if (!str) return;
    const width = opts.font.widthOfTextAtSize(str, opts.size);
    this.drawText(str, { ...opts, x: opts.centerX - width / 2 });
  }

  drawWrapped(
    value: string,
    opts: {
      x: number;
      y: number;
      maxWidth: number;
      size: number;
      font: PDFFont;
      color: PdfColor;
      lineHeight?: number;
    }
  ): number {
    const str = normalizeText(value);
    if (!str) return opts.y;
    const lineHeight = opts.lineHeight ?? opts.size * LINE_H_MULT;
    const words = str.split(/\s+/).filter(Boolean);
    let cursor = opts.y;
    let line = "";
    const flush = () => {
      this.drawText(line, { x: opts.x, y: cursor, size: opts.size, font: opts.font, color: opts.color });
      cursor += lineHeight;
      line = "";
    };
    for (const word of words) {
      const candidate = line ? `${line} ${word}` : word;
      if (opts.font.widthOfTextAtSize(candidate, opts.size) <= opts.maxWidth) {
        line = candidate;
      } else {
        if (line) flush();
        if (opts.font.widthOfTextAtSize(word, opts.size) > opts.maxWidth) {
          let rest = word;
          while (rest.length > 0) {
            let cut = rest.length;
            while (
              cut > 1 &&
              opts.font.widthOfTextAtSize(rest.slice(0, cut), opts.size) > opts.maxWidth
            ) {
              cut--;
            }
            this.drawText(rest.slice(0, cut), {
              x: opts.x,
              y: cursor,
              size: opts.size,
              font: opts.font,
              color: opts.color,
            });
            cursor += lineHeight;
            rest = rest.slice(cut);
          }
        } else {
          line = word;
        }
      }
    }
    if (line) flush();
    return cursor;
  }

  measureWrapped(value: string, maxWidth: number, size: number, font: PDFFont): number {
    const str = normalizeText(value);
    if (!str) return 0;
    const lineHeight = size * LINE_H_MULT;
    const words = str.split(/\s+/).filter(Boolean);
    let lines = 1;
    let line = "";
    for (const word of words) {
      const candidate = line ? `${line} ${word}` : word;
      if (font.widthOfTextAtSize(candidate, size) <= maxWidth) {
        line = candidate;
      } else {
        lines++;
        if (font.widthOfTextAtSize(word, size) > maxWidth) {
          lines += Math.ceil(font.widthOfTextAtSize(word, size) / maxWidth) - 1;
        }
        line = word;
      }
    }
    return lines * lineHeight;
  }
}

// ---------------------------------------------------------------------------
// Hauptfunktion Export
// ---------------------------------------------------------------------------

export async function generateReceiptPdf(
  receipts: ProcessedReceipt[],
  options: PdfExportOptions = {}
): Promise<Uint8Array> {
  const list = Array.isArray(receipts) ? receipts.filter((r): r is ProcessedReceipt => !!r) : [];
  const locale: PdfLocale = options.locale === "en" ? "en" : "de";
  const L = PDF_LABELS[locale];

  const domain = siteDomain || "scanreceipts.app";

  const doc = await PDFDocument.create();
  doc.setTitle(options.title ?? L.reportTitle);
  doc.setProducer("ScanReceipts Engine");
  doc.setCreator(`ScanReceipts (${domain})`);

  const report = new PdfReport(
    doc,
    {
      reg: await doc.embedFont(StandardFonts.Helvetica),
      bold: await doc.embedFont(StandardFonts.HelveticaBold),
      oblique: await doc.embedFont(StandardFonts.HelveticaOblique),
    },
    locale,
    L
  );
  report.drawHeader();
  report.y = MARGIN_TOP;

  drawTitleBlock(report, list.length, options, L);
  drawSummary(report, list, L);

  // Karten pro Beleg
  list.forEach((receipt, idx) => {
    drawReceiptCard(report, receipt, idx + 1, L);
  });

  // Moderner, dezenter Fußbereich mit Seitenzahlen
  const totalPages = report.doc.getPageCount();
  for (let i = 0; i < totalPages; i++) {
    const page = report.doc.getPage(i);
    const footerY = 28;

    // Haarlinie über dem Fußbereich
    page.drawLine({
      start: { x: MARGIN_X, y: footerY + 14 },
      end: { x: PAGE_W - MARGIN_X, y: footerY + 14 },
      thickness: 0.75,
      color: COLOR_BORDER,
    });

    // Domain links
    page.drawText(normalizeText(domain), {
      x: MARGIN_X,
      y: footerY,
      size: 7.5,
      font: report.reg,
      color: COLOR_MUTED,
    });

    // Vertraulichkeitshinweis Mitte
    const centerNote = normalizeText(L.footerNote);
    const centerW = report.reg.widthOfTextAtSize(centerNote, 7);
    page.drawText(centerNote, {
      x: PAGE_W / 2 - centerW / 2,
      y: footerY,
      size: 7,
      font: report.reg,
      color: COLOR_MUTED,
    });

    // Seitenzahl rechts
    const pageStr = normalizeText(`${L.page} ${i + 1} ${L.of} ${totalPages}`);
    const pageW = report.reg.widthOfTextAtSize(pageStr, 7.5);
    page.drawText(pageStr, {
      x: PAGE_W - MARGIN_X - pageW,
      y: footerY,
      size: 7.5,
      font: report.reg,
      color: COLOR_MUTED,
    });
  }

  return doc.save();
}

// ---------------------------------------------------------------------------
// Titelblock & Zusammenfassung
// ---------------------------------------------------------------------------

function drawTitleBlock(
  report: PdfReport,
  count: number,
  options: PdfExportOptions,
  L: PdfLabels
): void {
  report.ensure(70);
  report.y += 10;

  report.drawText(options.title ?? L.reportTitle, {
    x: MARGIN_X,
    y: report.y,
    size: 19,
    font: report.bold,
    color: COLOR_INK,
  });
  report.y += 24;

  const meta: string[] = [L.reportReceipts(count)];
  if (options.dateFrom && options.dateTo) {
    meta.push(
      `${L.periodLabel}: ${formatDate(options.dateFrom, report.locale)} – ${formatDate(
        options.dateTo,
        report.locale
      )}`
    );
  }
  report.drawText(meta.join("   ·   "), {
    x: MARGIN_X,
    y: report.y,
    size: 8.5,
    font: report.reg,
    color: COLOR_SLATE,
  });
  report.y += 18;
}

function drawSummary(report: PdfReport, receipts: ProcessedReceipt[], L: PdfLabels): void {
  if (receipts.length === 0) {
    report.ensure(60);
    report.drawText("Keine Belege im Export.", {
      x: MARGIN_X,
      y: report.y,
      size: 9.5,
      font: report.oblique,
      color: COLOR_SLATE,
    });
    report.y += 30;
    return;
  }

  report.ensure(90);

  report.drawText(L.summaryTitle, {
    x: MARGIN_X,
    y: report.y,
    size: 8,
    font: report.bold,
    color: COLOR_SLATE,
  });
  report.y += 12;

  const currencies = Array.from(new Set(receipts.map(currencyOf))).sort();
  const mixed = currencies.length > 1;
  const sumFor = (fn: (r: ProcessedReceipt) => number): number =>
    round2(receipts.reduce((acc, r) => acc + fn(r), 0));

  const totalNet = sumFor(netOf);
  const totalVat = sumFor(totalTaxOf);
  const totalGross = sumFor(grossOf);

  const tiles: Array<{ label: string; value: string; isGross?: boolean }> = [
    { label: L.countLabel, value: String(receipts.length) },
    { label: L.netTotal, value: formatMoney(totalNet, mixed ? "EUR" : currencies[0], report.locale) },
    { label: L.vatTotal, value: formatMoney(totalVat, mixed ? "EUR" : currencies[0], report.locale) },
    {
      label: L.grossTotal,
      value: mixed ? L.mixedCurrencies : formatMoney(totalGross, currencies[0], report.locale),
      isGross: true,
    },
  ];

  const gap = 10;
  const tileW = (PAGE_W - 2 * MARGIN_X - 3 * gap) / 4;
  const tileH = 48;

  tiles.forEach((tile, i) => {
    const x = MARGIN_X + i * (tileW + gap);
    report.drawCardBox(x, report.y, tileW, tileH, COLOR_SURFACE, COLOR_BORDER);

    // Kachel-Titel (kleine Tracking-Schrift)
    report.drawText(tile.label, {
      x: x + 10,
      y: report.y + 10,
      size: 6.5,
      font: report.bold,
      color: COLOR_SLATE,
    });

    // Kachel-Wert (Groß & Klar)
    report.drawText(tile.value, {
      x: x + 10,
      y: report.y + 26,
      size: tile.isGross ? 12.5 : 11,
      font: report.bold,
      color: COLOR_INK,
    });
  });
  report.y += tileH + 14;

  if (mixed) {
    const parts = currencies.map((currency) => {
      const inCur = receipts.filter((r) => currencyOf(r) === currency);
      const sum = round2(inCur.reduce((acc, r) => acc + grossOf(r), 0));
      return `${currency}: ${formatMoney(sum, currency, report.locale)}`;
    });
    report.drawText(`${L.perCurrencyBreakdown}:  ${parts.join("   ·   ")}`, {
      x: MARGIN_X,
      y: report.y,
      size: 7.5,
      font: report.oblique,
      color: COLOR_SLATE,
    });
    report.y += 14;
  }

  report.drawHLine(MARGIN_X, PAGE_W - MARGIN_X, report.y, COLOR_BORDER, 0.75);
  report.y += 14;
}

// ---------------------------------------------------------------------------
// Belegkarte
// ---------------------------------------------------------------------------

function drawReceiptCard(
  report: PdfReport,
  receipt: ProcessedReceipt,
  index: number,
  L: PdfLabels
): void {
  const merchant = text(receipt?.merchant?.name);
  const status = statusLabel(receipt, L);
  const statusOk = isUserConfirmed(receipt) || receipt?.validation?.isMathValid === true;

  // Kartenhöhe vorab ermitteln
  const amounts = buildAmountRows(receipt, report.locale, L);
  const metaRows = buildMetaRows(receipt, L);
  const metaCols = 2;
  const metaGridRows = Math.ceil(metaRows.length / metaCols);
  const metaGridH = metaGridRows * 22 + 4;
  const amountsH = amounts.length * 15 + 16;
  const lineItems = lineItemsOf(receipt);
  const lineItemsH = estimateLineItemsHeight(report, receipt, lineItems, L);

  const headerH = 26;
  const bodyH = Math.max(metaGridH, amountsH);
  const cardH = headerH + 12 + bodyH + 12 + lineItemsH + 12;

  report.ensure(cardH + 14);

  const cardTop = report.y;
  const cardW = PAGE_W - 2 * MARGIN_X;
  const contentX = MARGIN_X + 12;
  const contentRight = MARGIN_X + cardW - 12;
  const contentW = contentRight - contentX;

  // Äußerer sauberer Kartenrahmen
  report.drawCardBox(MARGIN_X, cardTop, cardW, cardH, COLOR_WHITE, COLOR_BORDER);

  // Kartenkopf mit sanftem Hintergrund
  report.drawRectFromTop(MARGIN_X, cardTop, cardW, headerH, COLOR_SURFACE);
  report.drawHLine(MARGIN_X, MARGIN_X + cardW, cardTop + headerH, COLOR_BORDER, 0.75);

  // Belegnummer links
  report.drawText(L.receiptHeader(index), {
    x: contentX,
    y: cardTop + 8.5,
    size: 8.5,
    font: report.bold,
    color: COLOR_INK,
  });

  const headerIndexW = report.bold.widthOfTextAtSize(normalizeText(L.receiptHeader(index)), 8.5);

  // Status-Badge rechts als elegantes Pill
  const statusTextColor = statusOk ? COLOR_OK : COLOR_WARN;
  const statusBgColor = statusOk ? COLOR_OK_BG : COLOR_WARN_BG;
  report.drawPill(status, {
    rightX: contentRight,
    y: cardTop + 6,
    font: report.bold,
    size: 7.5,
    textColor: statusTextColor,
    bgColor: statusBgColor,
  });

  // Händlername neben der Belegnummer
  const statusPillW = report.bold.widthOfTextAtSize(normalizeText(status), 7.5) + 16;
  const merchantX = contentX + headerIndexW + 8;
  const merchantMaxW = Math.max(0, contentRight - merchantX - statusPillW - 10);
  if (merchant && merchantMaxW > 20) {
    report.drawText(`·   ${truncateToWidth(report.bold, merchant, merchantMaxW - 14, 8.5)}`, {
      x: merchantX,
      y: cardTop + 8.5,
      size: 8.5,
      font: report.bold,
      color: COLOR_BODY,
    });
  }

  let y = cardTop + headerH + 12;

  // Stammdaten (Links, 52% Breite)
  const metaW = contentW * 0.52;
  const amountX = contentX + metaW + 16;
  const amountW = contentRight - amountX;

  metaRows.forEach((row, i) => {
    const col = i % metaCols;
    const rowIdx = Math.floor(i / metaCols);
    const x = contentX + col * (metaW / metaCols);
    const ry = y + rowIdx * 22;

    // Feld-Bezeichnung
    report.drawText(row.label, {
      x,
      y: ry,
      size: 6.5,
      font: report.bold,
      color: COLOR_MUTED,
    });

    // Feld-Wert
    report.drawWrapped(row.value, {
      x,
      y: ry + 8.5,
      maxWidth: metaW / metaCols - 8,
      size: 8,
      font: report.reg,
      color: COLOR_INK,
    });
  });

  // Beträge (Rechts im dezent umrahmten Panel)
  report.drawCardBox(amountX, y - 2, amountW, amountsH, COLOR_SURFACE, COLOR_BORDER);
  let ay = y + 5;
  amounts.forEach((row) => {
    if (row.type === "separator") {
      report.drawHLine(amountX + 8, amountX + amountW - 8, ay - 2, COLOR_BORDER, 0.75);
      ay += 6;
      return;
    }
    report.drawText(row.label, {
      x: amountX + 8,
      y: ay,
      size: row.bold ? 8 : 7.5,
      font: row.bold ? report.bold : report.reg,
      color: row.bold ? COLOR_INK : COLOR_SLATE,
    });
    report.drawRightText(row.value, {
      x: amountX + amountW - 8,
      y: ay,
      size: row.bold ? 8.5 : 8,
      font: row.bold ? report.bold : report.reg,
      color: COLOR_INK,
    });
    ay += 14.5;
  });

  y = y + bodyH + 12;

  // Einzelpositionen-Tabelle
  drawLineItemsTable(report, receipt, lineItems, { x: contentX, y, right: contentRight }, L);

  report.y = cardTop + cardH + 14;
}

interface AmountRow {
  type?: "value" | "separator";
  label: string;
  value: string;
  bold?: boolean;
}

function buildAmountRows(
  receipt: ProcessedReceipt,
  locale: PdfLocale,
  L: PdfLabels
): AmountRow[] {
  const rows: AmountRow[] = [];
  const currency = currencyOf(receipt);

  rows.push({ label: L.amountNet, value: formatMoney(netOf(receipt), currency, locale) });

  const rates = receiptRates(receipt);
  for (const rate of rates) {
    const amount = taxAmountForRate(receipt, rate);
    if (Math.abs(amount) > 0.004 || rates.length === 1) {
      rows.push({
        label: L.amountVat(rateLabel(rate, locale)),
        value: formatMoney(amount, currency, locale),
      });
    }
  }

  const vatTotal = totalTaxOf(receipt);
  if (vatTotal !== 0 || rates.length === 0) {
    rows.push({ label: L.amountVatTotal, value: formatMoney(vatTotal, currency, locale) });
  }

  const tip = tipOf(receipt);
  if (tip !== null) {
    rows.push({ label: L.amountTip, value: formatMoney(tip, currency, locale) });
  }

  rows.push({ type: "separator", label: "", value: "" });
  rows.push({ label: L.amountGross, value: formatMoney(grossOf(receipt), currency, locale), bold: true });

  if (tip !== null) {
    const paid = round2(grossOf(receipt) + tip);
    rows.push({
      label: L.amountPaid,
      value: formatMoney(paid, currency, locale),
      bold: true,
    });
  }

  return rows;
}

function buildMetaRows(receipt: ProcessedReceipt, L: PdfLabels): Array<{ label: string; value: string }> {
  const rows: Array<{ label: string; value: string }> = [];
  const push = (label: string, value: unknown) => {
    const str = text(value);
    if (str) rows.push({ label, value: str });
  };

  push(L.colDate, formatDate(receipt?.date?.isoDate, "de"));
  push(L.colCategory, receipt?.suggestedCategory);
  push(L.colDocType, receipt?.documentType);
  push(L.colReceiptNo, receipt?.receiptNumber ?? "-");
  push(L.colCurrency, currencyOf(receipt));
  push(L.colPayment, (receipt as { paymentMethod?: unknown }).paymentMethod);
  push(L.colTaxId, receipt?.merchant?.taxId);

  const small = isSmallValueInvoice(receipt);
  if (small !== null) rows.push({ label: L.colSmallInvoice, value: small ? L.smallInvoiceYes : L.smallInvoiceNo });

  const hospitality = readHospitality(receipt);
  const showHospitality = isHospitalityReceipt(receipt) || hospitality !== null;
  if (showHospitality) {
    rows.push({ label: L.colOccasion, value: hospitality?.occasion ?? "" });
    rows.push({ label: L.colParticipants, value: hospitality?.participants ?? "" });
  }

  return rows;
}

interface TableLayout {
  x: number;
  y: number;
  right: number;
}

function estimateLineItemsHeight(
  report: PdfReport,
  receipt: ProcessedReceipt,
  lineItems: LineItem[],
  L: PdfLabels
): number {
  const width = PAGE_W - 2 * MARGIN_X - 24;
  if (lineItems.length === 0) return 38;
  let h = 20; // Tabellenkopf
  for (const item of lineItems) {
    const descH = report.measureWrapped(text(item.description), width - 240, 8, report.reg);
    h += Math.max(16, descH + 6);
  }
  return h;
}

function drawLineItemsTable(
  report: PdfReport,
  receipt: ProcessedReceipt,
  lineItems: LineItem[],
  layout: TableLayout,
  L: PdfLabels
): void {
  const { x, right } = layout;
  const width = right - x;
  const y = layout.y;

  const colQty = 48;
  const colUnit = 72;
  const colRate = 44;
  const colTotal = 78;
  const descW = width - colQty - colUnit - colRate - colTotal;

  const labelRow = (L1: string, q: string, u: string, r: string, t: string) => {
    // Heller Tabellenkopf
    report.drawRectFromTop(x, y, width, 18, COLOR_SURFACE_ALT);
    report.drawHLine(x, right, y, COLOR_BORDER, 0.75);
    report.drawHLine(x, right, y + 18, COLOR_BORDER, 0.75);

    report.drawText(L1, { x: x + 6, y: y + 5.5, size: 7, font: report.bold, color: COLOR_SLATE });
    report.drawText(q, { x: x + descW + 4, y: y + 5.5, size: 7, font: report.bold, color: COLOR_SLATE });
    report.drawText(u, { x: x + descW + colQty + 4, y: y + 5.5, size: 7, font: report.bold, color: COLOR_SLATE });
    report.drawText(r, { x: x + descW + colQty + colUnit + 4, y: y + 5.5, size: 7, font: report.bold, color: COLOR_SLATE });
    report.drawRightText(t, { x: right - 6, y: y + 5.5, size: 7, font: report.bold, color: COLOR_SLATE });
  };

  if (lineItems.length === 0) {
    labelRow(L.colItem, L.colQty, L.colUnitPrice, L.colVatRate, L.colLineTotal);
    const note = L.noLineItems(text(receipt?.suggestedCategory) || "-");
    const noteY = y + 24;
    const noteH = report.drawWrapped(note, {
      x: x + 6,
      y: noteY,
      maxWidth: width - 12,
      size: 7.5,
      font: report.oblique,
      color: COLOR_SLATE,
    });
    const blockH = Math.max(20, noteH - noteY + 12);
    report.drawRectFromTop(x, y + 18, width, blockH, COLOR_SURFACE);
    report.drawHLine(x, right, y + 18 + blockH, COLOR_BORDER, 0.75);
    // Erneut zeichnen für Layering
    report.drawWrapped(note, {
      x: x + 6,
      y: noteY,
      maxWidth: width - 12,
      size: 7.5,
      font: report.oblique,
      color: COLOR_SLATE,
    });
    return;
  }

  labelRow(L.colItem, L.colQty, L.colUnitPrice, L.colVatRate, L.colLineTotal);

  const currency = currencyOf(receipt);
  let ry = y + 18;
  lineItems.forEach((item, idx) => {
    const descH = report.measureWrapped(text(item.description), descW - 8, 8, report.reg);
    const rowH = Math.max(16, descH + 6);

    if (idx % 2 === 1) {
      report.drawRectFromTop(x, ry, width, rowH, COLOR_SURFACE);
    }
    report.drawHLine(x, right, ry + rowH, COLOR_BORDER_LIGHT, 0.5);

    report.drawWrapped(text(item.description), {
      x: x + 6,
      y: ry + 3,
      maxWidth: descW - 8,
      size: 8,
      font: report.reg,
      color: COLOR_INK,
    });

    report.drawCenteredText(formatQuantity(item.quantity, report.locale), {
      centerX: x + descW + colQty / 2,
      y: ry + 5,
      size: 7.5,
      font: report.reg,
      color: COLOR_BODY,
    });

    const unit = (item as { unitPrice?: unknown }).unitPrice;
    report.drawCenteredText(
      isNum(unit) ? formatMoney(round2(unit), currency, report.locale) : "-",
      { centerX: x + descW + colQty + colUnit / 2, y: ry + 5, size: 7.5, font: report.reg, color: COLOR_BODY }
    );

    report.drawCenteredText(
      isNum(item.taxRate) ? rateLabel(item.taxRate, report.locale) : "-",
      { centerX: x + descW + colQty + colUnit + colRate / 2, y: ry + 5, size: 7.5, font: report.reg, color: COLOR_BODY }
    );

    report.drawRightText(formatMoney(round2(num(item.price)), currency, report.locale), {
      x: right - 6,
      y: ry + 5,
      size: 8,
      font: report.bold,
      color: COLOR_INK,
    });

    ry += rowH;
  });

  report.drawHLine(x, right, ry, COLOR_BORDER, 0.75);
}
