(() => {
  const PAGE_WIDTH = 595.28;
  const PAGE_HEIGHT = 841.89;
  const CANVAS_SCALE = 2;
  const MARGIN = 72;
  const FOOTER_HEIGHT = 70;
  const COLORS = Object.freeze({
    ink: "#17352b",
    body: "#263b34",
    muted: "#63736d",
    green: "#00a76f",
    greenDark: "#087450",
    greenSoft: "#eaf8f2",
    greenLine: "#9bd8c2",
    line: "#d6dfdb",
    canvas: "#f7faf9",
    white: "#ffffff",
    warning: "#fff8e8",
    warningLine: "#e9c472",
    warningInk: "#66491a",
  });

  function cleanText(value, fallback = "") {
    return String(value ?? "").replace(/\s+/g, " ").trim() || fallback;
  }

  function formatAgencyAddress(value) {
    const address = String(value ?? "").replace(/\r\n?/g, "\n").trim();
    if (!address || address.includes("\n")) return address;
    const streetStart = address.search(
      /\s+(?=(?:ul\.?|al\.?|aleja|os\.?|osiedle|pl\.?|plac|rondo)\s)/iu,
    );
    if (streetStart <= 0) return address;
    const firstLine = address.slice(0, streetStart).replace(/[,\s]+$/u, "");
    const secondLine = address.slice(streetStart).trim();
    return firstLine && secondLine ? `${firstLine}\n${secondLine}` : address;
  }

  function wrapText(context, value, maxWidth) {
    const paragraphs = String(value ?? "").replace(/\r\n?/g, "\n").split("\n");
    const lines = [];
    for (const paragraph of paragraphs) {
      const words = paragraph.replace(/\s+/g, " ").trim().split(" ").filter(Boolean);
      if (words.length === 0) {
        if (lines.length > 0) lines.push("");
        continue;
      }
      let current = "";
      for (const word of words) {
        const candidate = current ? `${current} ${word}` : word;
        if (context.measureText(candidate).width <= maxWidth) {
          current = candidate;
          continue;
        }
        if (current) lines.push(current);
        current = word;
        while (current && context.measureText(current).width > maxWidth) {
          let fitted = "";
          for (const character of current) {
            if (fitted && context.measureText(fitted + character).width > maxWidth) break;
            fitted += character;
          }
          if (!fitted) fitted = current[0];
          lines.push(fitted);
          current = current.slice(fitted.length);
        }
      }
      if (current) lines.push(current);
    }
    return lines.length > 0 ? lines : [""];
  }

  function limitLines(context, lines, maxLines, maxWidth) {
    if (lines.length <= maxLines) return lines;
    const limited = lines.slice(0, maxLines);
    let lastLine = limited[maxLines - 1];
    while (lastLine && context.measureText(`${lastLine}…`).width > maxWidth) {
      lastLine = lastLine.slice(0, -1).trimEnd();
    }
    limited[maxLines - 1] = `${lastLine}…`;
    return limited;
  }

  function roundRect(context, x, y, width, height, radius) {
    const r = Math.min(radius, width / 2, height / 2);
    context.beginPath();
    context.moveTo(x + r, y);
    context.arcTo(x + width, y, x + width, y + height, r);
    context.arcTo(x + width, y + height, x, y + height, r);
    context.arcTo(x, y + height, x, y, r);
    context.arcTo(x, y, x + width, y, r);
    context.closePath();
  }

  function fillRoundedRect(context, x, y, width, height, radius, fill, stroke = "") {
    roundRect(context, x, y, width, height, radius);
    context.fillStyle = fill;
    context.fill();
    if (stroke) {
      context.strokeStyle = stroke;
      context.lineWidth = 1;
      context.stroke();
    }
  }

  function loadImage(source) {
    const normalizedSource = String(source || "").trim();
    if (!normalizedSource || typeof globalThis.Image !== "function") return Promise.resolve(null);
    return new Promise((resolve) => {
      const image = new globalThis.Image();
      image.onload = () => resolve(image);
      image.onerror = () => resolve(null);
      image.src = normalizedSource;
    });
  }

  function drawContainedImage(context, image, x, y, maxWidth, maxHeight) {
    const sourceWidth = Number(image?.naturalWidth || image?.width);
    const sourceHeight = Number(image?.naturalHeight || image?.height);
    if (
      !Number.isFinite(sourceWidth)
      || !Number.isFinite(sourceHeight)
      || sourceWidth <= 0
      || sourceHeight <= 0
    ) return 0;
    const scale = Math.min(maxWidth / sourceWidth, maxHeight / sourceHeight, 1);
    const width = sourceWidth * scale;
    const height = sourceHeight * scale;
    context.drawImage(image, x, y + ((maxHeight - height) / 2), width, height);
    return width;
  }

  function formatDateTime(value) {
    const timestamp = value instanceof Date ? value.getTime() : Date.parse(String(value || ""));
    if (!Number.isFinite(timestamp)) return "";
    return new Intl.DateTimeFormat("pl-PL", {
      dateStyle: "long",
      timeStyle: "short",
    }).format(new Date(timestamp));
  }

  function getComponentRows(entry) {
    if (Array.isArray(entry?.componentsList) && entry.componentsList.length > 0) {
      return entry.componentsList
        .map((component) => ({
          label: cleanText(component?.label),
          amount: cleanText(component?.amount),
        }))
        .filter((component) => component.label && component.amount)
        .slice(0, 12);
    }
    const components = entry?.components || {};
    return [
      ["OC", components.oc],
      ["AC", components.ac],
      ["Wartość pojazdu", components.vehicleValue],
      ["Assistance", components.assistance],
      ["NNW", components.nnw],
    ].filter(([, amount]) => cleanText(amount))
      .map(([label, amount]) => ({ label, amount: cleanText(amount) }));
  }

  function findOcAmount(entry) {
    const explicitAmount = cleanText(entry?.ocAmount);
    if (explicitAmount) return explicitAmount;
    const component = getComponentRows(entry).find((row) => (
      /^oc\b(?!\s*[/+])/i.test(row.label)
    ));
    return cleanText(component?.amount || entry?.components?.oc);
  }

  function createBlankPage(options, continuation = false) {
    const canvas = document.createElement("canvas");
    canvas.width = Math.round(PAGE_WIDTH * CANVAS_SCALE);
    canvas.height = Math.round(PAGE_HEIGHT * CANVAS_SCALE);
    const context = canvas.getContext("2d");
    if (!context) throw new Error("Przeglądarka nie może przygotować stron PDF.");
    context.fillStyle = COLORS.white;
    context.fillRect(0, 0, canvas.width, canvas.height);
    context.textBaseline = "alphabetic";

    const page = {
      canvas,
      context,
      contentWidth: canvas.width - (MARGIN * 2),
      contentBottom: canvas.height - FOOTER_HEIGHT,
      y: MARGIN,
    };
    if (continuation) {
      const agencyName = cleanText(options.agency?.displayName, "Zestawienie składek");
      const registration = cleanText(options.quote?.registration);
      context.fillStyle = COLORS.ink;
      context.font = "800 24px Arial, sans-serif";
      context.fillText(agencyName, MARGIN, 53, page.contentWidth * 0.62);
      context.fillStyle = COLORS.muted;
      context.font = "600 16px Arial, sans-serif";
      context.textAlign = "right";
      context.fillText(
        registration ? `Porównanie składek · ${registration}` : "Porównanie składek",
        MARGIN + page.contentWidth,
        52,
        page.contentWidth * 0.36,
      );
      context.textAlign = "left";
      context.fillStyle = COLORS.green;
      context.fillRect(MARGIN, 70, page.contentWidth, 4);
      page.y = 96;
    }
    return page;
  }

  function drawFirstPageHeader(page, options, agencyLogo = null) {
    const { context, contentWidth } = page;
    const agency = options.agency || {};
    const quote = options.quote || {};
    const leftWidth = Math.round(contentWidth * 0.5);
    const rightX = MARGIN + Math.round(contentWidth * 0.56);
    const rightWidth = MARGIN + contentWidth - rightX;

    context.fillStyle = COLORS.green;
    context.fillRect(MARGIN, MARGIN, 7, 72);
    const logoWidth = agencyLogo
      ? drawContainedImage(context, agencyLogo, MARGIN + 20, MARGIN + 6, 96, 60)
      : 0;
    const agencyTextX = MARGIN + 20 + (logoWidth > 0 ? logoWidth + 14 : 0);
    const agencyTextWidth = Math.max(80, leftWidth - (agencyTextX - MARGIN));
    context.fillStyle = COLORS.ink;
    context.font = "900 24px Arial, sans-serif";
    context.fillText(
      cleanText(agency.displayName, "Agencja ubezpieczeniowa"),
      agencyTextX,
      MARGIN + 25,
      agencyTextWidth,
    );
    context.fillStyle = COLORS.greenDark;
    context.font = "700 14px Arial, sans-serif";
    context.fillText(cleanText(agency.name, "—"), agencyTextX, MARGIN + 51, agencyTextWidth);

    context.fillStyle = COLORS.body;
    context.font = "600 11px Arial, sans-serif";
    const addressLines = wrapText(
      context,
      formatAgencyAddress(agency.address) || "—",
      rightWidth,
    ).slice(0, 2);
    const contactLines = [
      ...addressLines,
      cleanText(agency.email, "—"),
      `tel. ${cleanText(agency.phone, "—")}`,
    ];
    contactLines.forEach((line, index) => {
      context.fillText(line, rightX, MARGIN + 13 + (index * 16), rightWidth);
    });

    const dividerY = MARGIN + 88;
    context.fillStyle = COLORS.green;
    context.fillRect(MARGIN, dividerY, contentWidth, 3);

    context.fillStyle = COLORS.ink;
    context.font = "900 28px Arial, sans-serif";
    context.fillText("Porównanie składek", MARGIN, dividerY + 38, contentWidth * 0.55);
    context.fillStyle = COLORS.muted;
    context.font = "600 12px Arial, sans-serif";
    context.textAlign = "right";
    context.fillText(
      `Kalkulacja: ${formatDateTime(quote.calculatedAt) || formatDateTime(quote.generatedAt)}`,
      MARGIN + contentWidth,
      dividerY + 35,
      contentWidth * 0.43,
    );
    context.textAlign = "left";

    const metaY = dividerY + 52;
    const metaHeight = 60;
    fillRoundedRect(context, MARGIN, metaY, contentWidth, metaHeight, 8, COLORS.canvas, COLORS.line);
    const metaColumns = [
      ["Klient", cleanText(quote.clientName, "Nie podano")],
      ["Numer rejestracyjny", cleanText(quote.registration, "Nie podano")],
      ["Pojazd", cleanText(quote.vehicleName, "Nie podano")],
      ["Okres ubezpieczenia", cleanText(quote.insurancePeriod, "Nie ustalono")],
    ];
    const columnWidth = contentWidth / metaColumns.length;
    metaColumns.forEach(([label, value], index) => {
      const x = MARGIN + (index * columnWidth);
      if (index > 0) {
        context.fillStyle = COLORS.line;
        context.fillRect(x, metaY + 10, 1, metaHeight - 20);
      }
      context.fillStyle = COLORS.muted;
      context.font = "800 9px Arial, sans-serif";
      context.fillText(label.toLocaleUpperCase("pl-PL"), x + 12, metaY + 19, columnWidth - 24);
      context.fillStyle = COLORS.ink;
      context.font = "800 14px Arial, sans-serif";
      const lines = limitLines(context, wrapText(context, value, columnWidth - 24), 2, columnWidth - 24);
      lines.forEach((line, lineIndex) => {
        context.fillText(line, x + 12, metaY + 41 + (lineIndex * 15), columnWidth - 24);
      });
    });
    page.y = metaY + metaHeight + 12;
  }

  function getTableColumns(contentWidth) {
    const variantWidth = Math.round(contentWidth * 0.2);
    const totalWidth = Math.round(contentWidth * 0.16);
    return {
      variantWidth,
      scopeWidth: contentWidth - variantWidth - totalWidth,
      totalWidth,
    };
  }

  function drawTableHeader(page) {
    const { context, contentWidth } = page;
    const columns = getTableColumns(contentWidth);
    const y = page.y;
    const height = 25;
    context.fillStyle = COLORS.ink;
    context.fillRect(MARGIN, y, contentWidth, height);
    context.fillStyle = COLORS.white;
    context.font = "800 10px Arial, sans-serif";
    context.fillText("WARIANT", MARGIN + 10, y + 17, columns.variantWidth - 20);
    context.fillText(
      "ZAKRES I SKŁADKI",
      MARGIN + columns.variantWidth + 10,
      y + 17,
      columns.scopeWidth - 20,
    );
    context.textAlign = "right";
    context.fillText(
      "SKŁADKA ŁĄCZNA",
      MARGIN + contentWidth - 10,
      y + 17,
      columns.totalWidth - 20,
    );
    context.textAlign = "left";
    page.y += height;
  }

  function drawInsurerHeading(page, insurer, variantCount) {
    const { context, contentWidth } = page;
    const height = 29;
    context.fillStyle = COLORS.greenSoft;
    context.fillRect(MARGIN, page.y, contentWidth, height);
    context.fillStyle = COLORS.green;
    context.fillRect(MARGIN, page.y, 5, height);
    context.fillStyle = COLORS.greenDark;
    context.font = "900 16px Arial, sans-serif";
    context.fillText(cleanText(insurer, "Towarzystwo"), MARGIN + 14, page.y + 20, contentWidth * 0.68);
    context.fillStyle = COLORS.muted;
    context.font = "700 10px Arial, sans-serif";
    context.textAlign = "right";
    context.fillText(
      variantCount === 1 ? "1 wybrany wariant" : `${variantCount} wybrane warianty`,
      MARGIN + contentWidth - 10,
      page.y + 19,
      contentWidth * 0.28,
    );
    context.textAlign = "left";
    page.y += height;
  }

  function prepareVariantLayout(context, entry, contentWidth) {
    const columns = getTableColumns(contentWidth);
    context.font = "800 13px Arial, sans-serif";
    const labelLines = limitLines(
      context,
      wrapText(context, cleanText(entry?.label, "Wariant"), columns.variantWidth - 18),
      2,
      columns.variantWidth - 18,
    );
    const components = getComponentRows(entry);
    const scopeText = components.length > 0
      ? components.map((component) => `${component.label}: ${component.amount}`).join(" · ")
      : cleanText(entry?.details, "Brak rozbicia zakresu");
    context.font = "600 12px Arial, sans-serif";
    const scopeLines = wrapText(context, scopeText, columns.scopeWidth - 20);
    const lineHeight = 15;
    const recommended = entry?.recommended === true;
    const recommendationHeight = recommended ? 21 : 0;
    const contentHeight = Math.max(labelLines.length * 16, scopeLines.length * lineHeight, 18);
    return {
      labelLines,
      scopeLines,
      columns,
      recommended,
      recommendationHeight,
      height: Math.max(38, contentHeight + 14 + recommendationHeight),
    };
  }

  function drawVariant(page, entry, layout, rowIndex = 0) {
    const { context, contentWidth } = page;
    const x = MARGIN;
    const y = page.y;
    const { columns } = layout;
    context.fillStyle = layout.recommended
      ? COLORS.greenSoft
      : rowIndex % 2 === 0
        ? COLORS.white
        : COLORS.canvas;
    context.fillRect(x, y, contentWidth, layout.height);
    if (layout.recommended) {
      context.fillStyle = COLORS.green;
      context.fillRect(x, y, contentWidth, layout.recommendationHeight);
      context.fillStyle = COLORS.white;
      context.font = "900 11px Arial, sans-serif";
      context.fillText(
        "REKOMENDOWANY PRZEZ AGENTA",
        x + 11,
        y + 15,
        contentWidth - 22,
      );
    }
    context.fillStyle = COLORS.line;
    context.fillRect(x, y + layout.height - 1, contentWidth, 1);
    const bodyY = y + layout.recommendationHeight;
    const bodyHeight = layout.height - layout.recommendationHeight;
    context.fillRect(x + columns.variantWidth, bodyY, 1, bodyHeight);
    context.fillRect(x + columns.variantWidth + columns.scopeWidth, bodyY, 1, bodyHeight);

    context.fillStyle = COLORS.ink;
    context.font = "900 14px Arial, sans-serif";
    const variantHeight = layout.labelLines.length * 16;
    const labelTop = bodyY + Math.max(0, (bodyHeight - variantHeight) / 2);
    context.fillStyle = layout.recommended ? COLORS.greenDark : COLORS.ink;
    layout.labelLines.forEach((line, index) => {
      context.fillText(line, x + 9, labelTop + 13 + (index * 16), columns.variantWidth - 18);
    });

    context.fillStyle = COLORS.body;
    context.font = "600 12px Arial, sans-serif";
    const scopeX = x + columns.variantWidth + 10;
    const scopeTop = bodyY + Math.max(0, (bodyHeight - (layout.scopeLines.length * 15)) / 2);
    layout.scopeLines.forEach((line, index) => {
      context.fillText(line, scopeX, scopeTop + 12 + (index * 15), columns.scopeWidth - 20);
    });

    context.fillStyle = COLORS.greenDark;
    context.font = layout.recommended
      ? "900 18px Arial, sans-serif"
      : "900 16px Arial, sans-serif";
    context.textAlign = "right";
    context.fillText(
      cleanText(entry?.amount, "—"),
      x + contentWidth - 10,
      bodyY + (bodyHeight / 2) + 6,
      columns.totalWidth - 20,
    );
    context.textAlign = "left";
    page.y += layout.height;
  }

  function prepareDisclaimerLayout(context, disclaimer, contentWidth) {
    context.font = "600 11px Arial, sans-serif";
    const lines = wrapText(context, disclaimer, contentWidth - 28);
    return {
      lines,
      height: 34 + (lines.length * 15),
    };
  }

  function drawDisclaimer(page, disclaimer, layout) {
    const { context, contentWidth } = page;
    fillRoundedRect(
      context,
      MARGIN,
      page.y,
      contentWidth,
      layout.height,
      7,
      COLORS.warning,
      COLORS.warningLine,
    );
    context.fillStyle = COLORS.warningInk;
    context.font = "900 10px Arial, sans-serif";
    context.fillText("WAŻNA INFORMACJA", MARGIN + 14, page.y + 18, contentWidth - 28);
    context.font = "600 11px Arial, sans-serif";
    layout.lines.forEach((line, index) => {
      context.fillText(line, MARGIN + 14, page.y + 34 + (index * 15), contentWidth - 28);
    });
    page.y += layout.height;
  }

  function finishPages(pages) {
    pages.forEach((page, index) => {
      const { context, canvas, contentWidth } = page;
      context.fillStyle = COLORS.line;
      context.fillRect(MARGIN, canvas.height - 48, contentWidth, 1);
      context.fillStyle = COLORS.muted;
      context.font = "600 12px Arial, sans-serif";
      context.fillText("Zestawienie składek ubezpieczeniowych", MARGIN, canvas.height - 24, contentWidth * 0.7);
      context.textAlign = "right";
      context.fillText(`Strona ${index + 1} z ${pages.length}`, MARGIN + contentWidth, canvas.height - 24);
      context.textAlign = "left";
    });
  }

  async function create(options) {
    if (!globalThis.PDFLib?.PDFDocument) {
      throw new Error("Brakuje lokalnego modułu do tworzenia PDF.");
    }
    const insurers = Array.isArray(options?.insurers)
      ? options.insurers.filter((group) => Array.isArray(group?.entries) && group.entries.length > 0)
      : [];
    if (insurers.length === 0) throw new Error("Brak wybranych składek do zapisania w PDF.");

    const agencyLogo = await loadImage(options?.agency?.logoDataUrl);
    const pages = [];
    let page = createBlankPage(options);
    pages.push(page);
    drawFirstPageHeader(page, options, agencyLogo);
    drawTableHeader(page);

    const newPage = ({ withTableHeader = true } = {}) => {
      page = createBlankPage(options, true);
      pages.push(page);
      if (withTableHeader) drawTableHeader(page);
      return page;
    };
    const ensureSpace = (height, newPageOptions = {}) => {
      if (page.y + height <= page.contentBottom) return;
      newPage(newPageOptions);
    };

    insurers.forEach((group) => {
      ensureSpace(29 + 38);
      drawInsurerHeading(page, group.insurer, group.entries.length);
      group.entries.forEach((entry, entryIndex) => {
        let layout = prepareVariantLayout(page.context, entry, page.contentWidth);
        if (page.y + layout.height > page.contentBottom) {
          newPage();
          drawInsurerHeading(page, group.insurer, group.entries.length);
        }
        layout = prepareVariantLayout(page.context, entry, page.contentWidth);
        drawVariant(page, entry, layout, entryIndex);
      });
      page.y += 6;
    });

    const disclaimer = cleanText(options?.disclaimer);
    if (disclaimer) {
      let layout = prepareDisclaimerLayout(page.context, disclaimer, page.contentWidth);
      ensureSpace(layout.height + 8, { withTableHeader: false });
      layout = prepareDisclaimerLayout(page.context, disclaimer, page.contentWidth);
      drawDisclaimer(page, disclaimer, layout);
    }

    finishPages(pages);
    const pdfDocument = await globalThis.PDFLib.PDFDocument.create();
    const generatedAt = options.quote?.generatedAt instanceof Date
      ? options.quote.generatedAt
      : new Date();
    pdfDocument.setTitle(
      options.quote?.registration
        ? `Porównanie składek ${options.quote.registration}`
        : "Porównanie składek",
    );
    pdfDocument.setSubject("Zestawienie kalkulacji składek ubezpieczeniowych");
    const agencyName = cleanText(
      options.agency?.displayName || options.agency?.name,
      "Agencja ubezpieczeniowa",
    );
    pdfDocument.setAuthor(agencyName);
    pdfDocument.setCreator(agencyName);
    pdfDocument.setCreationDate(generatedAt);
    pdfDocument.setModificationDate(generatedAt);

    for (const pageState of pages) {
      const pngImage = await pdfDocument.embedPng(pageState.canvas.toDataURL("image/png"));
      const pdfPage = pdfDocument.addPage([PAGE_WIDTH, PAGE_HEIGHT]);
      pdfPage.drawImage(pngImage, {
        x: 0,
        y: 0,
        width: PAGE_WIDTH,
        height: PAGE_HEIGHT,
      });
    }
    return pdfDocument.save();
  }

  globalThis.QuoteOfferPdfRenderer = Object.freeze({ create });
})();
