import { pdfConfig } from "../config/pdf.ts";
import {
  PDFDocument,
  PDFFont,
  PDFImage,
  PDFPage,
  rgb,
  StandardFonts,
} from "pdf-lib";

export type PdfLine = { label: string; value: string };
export type ApplicationPdfData = {
  publicNumber: string;
  submittedAt: string;
  academicYear: string;
  socialResponse: string;
  child: PdfLine[];
  guardians: Array<{ title: string; lines: PdfLine[] }>;
  household: Array<{ name: string; relationship: string; incomeCents: number }>;
  expenses: PdfLine[];
  documents: Array<{ type: string; name: string }>;
  declarations: PdfLine[];
};

const A4: [number, number] = [595.28, 841.89];
const ink = rgb(0.13, 0.16, 0.18),
  muted = rgb(0.38, 0.42, 0.44),
  red = rgb(
    parseInt(pdfConfig.accent.slice(1, 3), 16) / 255,
    parseInt(pdfConfig.accent.slice(3, 5), 16) / 255,
    parseInt(pdfConfig.accent.slice(5, 7), 16) / 255,
  ),
  paper = rgb(0.98, 0.97, 0.95),
  line = rgb(0.86, 0.84, 0.81);
const clean = (value: string) =>
  (value?.trim() || "-")
    .normalize("NFC")
    .replace(/[^\x20-\x7e\xa0-\xff€–—‘’“”]/g, "?");
const money = (cents: number) =>
  new Intl.NumberFormat("pt-PT", { style: "currency", currency: "EUR" }).format(
    cents / 100,
  );

function wrap(text: string, font: PDFFont, size: number, width: number) {
  const words = clean(text)
    .split(/\s+/)
    .flatMap((word) => {
      const pieces: string[] = [];
      let part = "";
      for (const letter of word) {
        if (part && font.widthOfTextAtSize(part + letter, size) > width) {
          pieces.push(part);
          part = "";
        }
        part += letter;
      }
      if (part) pieces.push(part);
      return pieces;
    });
  const rows: string[] = [];
  let row = "";
  for (const word of words) {
    const next = row ? `${row} ${word}` : word;
    if (font.widthOfTextAtSize(next, size) <= width) row = next;
    else {
      if (row) rows.push(row);
      row = word;
    }
  }
  if (row) rows.push(row);
  return rows;
}

export async function generateApplicationPdf(
  data: ApplicationPdfData,
  logo?: Uint8Array,
) {
  const pdf = await PDFDocument.create();
  pdf.setTitle(`${pdfConfig.title} - ${data.publicNumber}`);
  pdf.setAuthor(pdfConfig.author);
  pdf.setSubject("Inscrição online");
  const regular = await pdf.embedFont(StandardFonts.Helvetica),
    bold = await pdf.embedFont(StandardFonts.HelveticaBold);
  let logoImage: PDFImage | undefined;
  if (logo) {
    try {
      logoImage = await pdf.embedPng(logo);
    } catch {
      logoImage = await pdf.embedJpg(logo);
    }
  }
  let page: PDFPage,
    y = 0;
  const pages: PDFPage[] = [];
  const header = () => {
    page = pdf.addPage(A4);
    pages.push(page);
    page.drawRectangle({
      x: 0,
      y: 0,
      width: A4[0],
      height: A4[1],
      color: paper,
    });
    y = 785;
    y -= 62;
  };
  const ensure = (height: number) => {
    if (y - height < 54) header();
  };
  const section = (title: string) => {
    ensure(52);
    page.drawText(title.toUpperCase(), {
      x: 42,
      y,
      size: 9,
      font: bold,
      color: red,
    });
    y -= 18;
  };
  const pair = (label: string, value: string) => {
    const labelRows = wrap(clean(label), bold, 8.3, 112),
      valueRows = wrap(clean(value), regular, 9.5, 336),
      rows = Math.max(labelRows.length, valueRows.length),
      h = rows * 12 + 28;
    ensure(h);
    y -= 10;
    labelRows.forEach((row, i) =>
      page.drawText(row, {
        x: 42,
        y: y - i * 12,
        size: 8.3,
        font: bold,
        color: muted,
      }),
    );
    valueRows.forEach((row, i) =>
      page.drawText(row, {
        x: 175,
        y: y - i * 12,
        size: 9.5,
        font: regular,
        color: ink,
      }),
    );
    y -= rows * 12 + 8;
    page.drawLine({
      start: { x: 42, y },
      end: { x: 553, y },
      thickness: 0.45,
      color: line,
    });
    y -= 10;
  };
  const group = (title: string, lines: PdfLine[]) => {
    ensure(81);
    section(title);
    for (const item of lines) pair(item.label, item.value);
    y -= 8;
  };
  header();
  group("Inscrição", [
    { label: "Ano letivo", value: data.academicYear },
    { label: "Resposta social", value: data.socialResponse },
    {
      label: "Data de submissão",
      value: new Intl.DateTimeFormat("pt-PT", {
        dateStyle: "long",
        timeStyle: "short",
      }).format(new Date(data.submittedAt)),
    },
  ]);
  group("Criança", data.child);
  for (const guardian of data.guardians) group(guardian.title, guardian.lines);
  ensure(81);
  section("Agregado familiar");
  let total = 0;
  for (const member of data.household) {
    total += member.incomeCents;
    pair(`${member.name} (${member.relationship})`, money(member.incomeCents));
  }
  pair("Rendimento mensal total", money(total));
  y -= 8;
  if (data.expenses.length) group("Despesas mensais", data.expenses);
  ensure(81);
  section("Documentos entregues");
  for (const doc of data.documents) pair(doc.type, doc.name);
  y -= 8;
  group("Declarações", data.declarations);
  const finalPages = pdf.getPages();
  for (let i = 0; i < finalPages.length; i++) {
    const p = finalPages[i];
    if (logoImage) {
      const ratio = logoImage.width / logoImage.height,
        h = 45;
      p.drawImage(logoImage, {
        x: 42,
        y: 749,
        width: Math.min(h * ratio, 240),
        height: Math.min(h, 240 / ratio),
      });
    } else {
      const rows = wrap(pdfConfig.author, bold, 12, 250);
      rows
        .slice(0, 2)
        .forEach((row, j) =>
          p.drawText(row, {
            x: 42,
            y: 784 - j * 16,
            size: 12,
            font: bold,
            color: red,
          }),
        );
    }
    p.drawText(clean(pdfConfig.title.toUpperCase()), {
      x: 330,
      y: 784,
      size: 13,
      font: bold,
      color: ink,
    });
    p.drawText(data.publicNumber, {
      x: 330,
      y: 765,
      size: 9,
      font: regular,
      color: red,
    });
    p.drawLine({
      start: { x: 42, y: 749 },
      end: { x: 553, y: 749 },
      thickness: 1,
      color: line,
    });
    p.drawLine({
      start: { x: 42, y: 40 },
      end: { x: 553, y: 40 },
      thickness: 0.45,
      color: line,
    });
    p.drawText(clean(pdfConfig.footer), {
      x: 42,
      y: 24,
      size: 7.5,
      font: regular,
      color: muted,
    });
    const no = `${i + 1} / ${finalPages.length}`;
    p.drawText(no, {
      x: 553 - regular.widthOfTextAtSize(no, 7.5),
      y: 24,
      size: 7.5,
      font: regular,
      color: muted,
    });
  }
  return pdf.save();
}
