import jsPDF from "jspdf";
import { numberToWords } from "./numToWords";
import { MICR_FONT_B64 } from "./micrFont";

// Register MICR font into EACH jsPDF doc instance.
// Do NOT cache with a module-level flag — each new doc() object needs its own addFileToVFS call.
function ensureMicrFont(doc: jsPDF) {
  try {
    doc.addFileToVFS("MICRCHECK.ttf", MICR_FONT_B64);
    doc.addFont("MICRCHECK.ttf", "MICRCHECK", "normal");
    doc.setFont("MICRCHECK", "normal");
  } catch {
    doc.setFont("courier", "bold");
  }
}

// ── Types ────────────────────────────────────────────────────────────────────

export type CheckLineItem = {
  description: string;
  amount: number;
};

export type CheckData = {
  companyName: string;
  companyAddress?: string;
  bankName?: string;
  bankAddress?: string;
  routingNumber?: string;
  accountNumber?: string;
  fractionalRouting?: string;
  checkNumber: number;
  date: string;
  payee: string;
  payeeAddress?: string;
  amount: number;
  memo?: string;
  lineItems?: CheckLineItem[];
  signatureDataUrl?: string;
  printSignature?: boolean;
  voided?: boolean;
};

export type CheckPrintOptions = {
  position: "top" | "middle" | "bottom";
  style: "standard" | "voucher";
  fontSize: "small" | "medium" | "large";
  /** Global horizontal shift in inches — positive = right, negative = left */
  offsetX?: number;
  /** Global vertical shift in inches — positive = down, negative = up */
  offsetY?: number;
  /** Extra vertical shift for the MICR line only (fine-tune for check stock) */
  micrOffsetY?: number;
  /** Spaces between MICR check# and routing segments (default 1) */
  micrGap1?: number;
  /** Spaces between MICR routing and account segments (default 1) */
  micrGap2?: number;
};

// ── Constants ────────────────────────────────────────────────────────────────

const PW = 8.5;   // page width inches
const PH = 11;    // page height
const ML = 0.35;  // left margin
const MR = 0.35;  // right margin
const CW = PW - ML - MR;  // content width
const RIGHT = PW - MR;    // right edge

// Standard 3-part check stock — each section is 1/3 of the page
const SECTION_H = PH / 3;      // 3.667" per section
const CHECK_H   = SECTION_H;   // check occupies the full top section

// ── MICR line builder ────────────────────────────────────────────────────────
// MICRCHECK.ttf maps:  C → transit bracket   A → on-us bracket
// Format matches reference check: C000000305C A263191387A 1100034740184C
//   C{check#}C  A{routing}A  {account}C

function buildMicr(routing: string, account: string, checkNum: number, gap1 = 1, gap2 = 1): string {
  const r = (routing ?? "").replace(/\D/g, "").slice(0, 9);
  const a = (account ?? "").replace(/\D/g, "");
  const c = String(checkNum).padStart(9, "0");
  if (!r && !a) return "";
  const g1 = " ".repeat(Math.max(0, Math.round(gap1)));
  const g2 = " ".repeat(Math.max(0, Math.round(gap2)));
  // C{check#}C  {gap1}  A{routing}A  {gap2}  {account}C
  return `C${c}C${g1}A${r}A${g2}${a}C`;
}

// ── Fill a line to width with a pad character (for written-amount security) ─

function padLine(doc: jsPDF, text: string, x: number, rightEdge: number, padChar: string, fontSize: number): void {
  doc.setFontSize(fontSize);
  const textW = doc.getTextWidth(text);
  const padW  = doc.getTextWidth(padChar);
  const available = rightEdge - x;
  if (textW >= available) { doc.text(text, x, 0); return; }
  const padsNeeded = Math.floor((available - textW) / padW);
  const half = Math.floor(padsNeeded / 2);
  const padded = padChar.repeat(half) + " " + text.trim() + " " + padChar.repeat(padsNeeded - half);
  doc.text(padded, x, 0);
}

// ── Helpers ──────────────────────────────────────────────────────────────────

function hline(doc: jsPDF, x1: number, y: number, x2: number, w = 0.006, gray = 0) {
  doc.setLineWidth(w);
  doc.setDrawColor(gray);
  doc.line(x1, y, x2, y);
}

// ── Check face ───────────────────────────────────────────────────────────────

function drawCheck(doc: jsPDF, originY: number, c: CheckData, ox = 0, micrOy = 0, micrGap1 = 1, micrGap2 = 1) {
  const y = (dy: number) => originY + dy;
  const x = (dx: number) => dx + ox; // Apply global X offset to every horizontal coordinate

  // VOID watermark
  if (c.voided) {
    doc.saveGraphicsState();
    doc.setFont("helvetica", "bold");
    doc.setFontSize(100);
    doc.setTextColor(220, 50, 50);
    doc.text("VOID", x(PW / 2), y(CHECK_H / 2), { align: "center", angle: 20 });
    doc.restoreGraphicsState();
    doc.setTextColor(0);
  }

  // ── Top row ──────────────────────────────────────────────────────────────
  doc.setFont("helvetica", "bold");
  doc.setFontSize(12);
  doc.setTextColor(0);
  doc.text(String(c.checkNumber), x(RIGHT), y(0.18), { align: "right" });

  doc.setFont("helvetica", "bold");
  doc.setFontSize(8.5);
  doc.text(c.companyName, x(ML), y(0.18));

  doc.setFont("helvetica", "normal");
  doc.setFontSize(7.5);
  const companyAddrLines = (c.companyAddress ?? "").split(/\n/).filter(Boolean);
  companyAddrLines.slice(0, 4).forEach((line, i) => {
    doc.text(line, x(ML), y(0.32 + i * 0.135));
  });

  const bankX = ML + CW * 0.52;
  doc.setFont("helvetica", "bold");
  doc.setFontSize(9);
  doc.text(c.bankName ?? "", x(bankX), y(0.18));
  doc.setFont("helvetica", "normal");
  doc.setFontSize(7.5);
  if (c.bankAddress) doc.text(c.bankAddress, x(bankX), y(0.32));

  // ── Date line ────────────────────────────────────────────────────────────
  const dateLineY = y(0.78);
  doc.setFont("helvetica", "normal");
  doc.setFontSize(9);
  doc.text(c.date, x(RIGHT), dateLineY - 0.04, { align: "right" });
  hline(doc, x(ML + CW * 0.62), dateLineY, x(RIGHT));

  // ── Pay to the order of ──────────────────────────────────────────────────
  const payY   = y(1.10);
  const payeeX = ML + 0.78;

  doc.setFont("helvetica", "normal");
  doc.setFontSize(7);
  doc.text("PAY TO THE", x(ML), payY - 0.10);
  doc.text("ORDER OF",   x(ML), payY + 0.03);

  doc.setFont("helvetica", "bold");
  doc.setFontSize(10);
  doc.text(c.payee, x(payeeX), payY);
  hline(doc, x(payeeX - 0.02), payY + 0.06, x(RIGHT - 1.55));

  // Amount box
  doc.setFont("helvetica", "bold");
  doc.setFontSize(11);
  doc.text(`$${c.amount.toFixed(2)}`, x(RIGHT - 0.08), payY, { align: "right" });
  doc.setFont("helvetica", "normal");
  doc.setFontSize(7.5);
  doc.setTextColor(80);
  doc.text("DOLLARS", x(RIGHT - 0.08), payY + 0.17, { align: "right" });
  doc.setTextColor(0);

  // ── Written amount — with more breathing room after the pay-to line ────────
  // numberToWords() already ends with "Dollars" — no suffix needed
  const writtenY  = payY + 0.30;   // extra gap below pay-to line
  const amountWords = numberToWords(c.amount).toUpperCase();
  doc.setFont("helvetica", "normal");
  doc.setFontSize(8.5);
  const wordsW    = doc.getTextWidth(amountWords);
  const starW     = doc.getTextWidth("*");
  const starsTotal = Math.max(0, Math.floor((CW * 0.90 - wordsW) / starW) - 2);
  const leftStars  = Math.max(4, Math.floor(starsTotal * 0.18));
  const rightStars = Math.max(0, starsTotal - leftStars);
  doc.text("*".repeat(leftStars) + " " + amountWords + " " + "*".repeat(rightStars),
    x(ML), writtenY, { maxWidth: CW });
  // Underline the written amount
  hline(doc, x(ML), writtenY + 0.06, x(RIGHT), 0.008, 0);

  // ── Envelope address block — more space below written amount ─────────────
  // Layout: payee name (bold) on left  |  "Authorized Signer" on right (same Y)
  //         address line 1 (indented)
  //         address line 2 (indented)
  const addrLines = c.payeeAddress
    ? c.payeeAddress.split(/\n/).map(s => s.trim()).filter(Boolean)
    : [];
  const addrBlockY  = writtenY + 0.28;   // extra gap below written amount
  const addrIndentX = ML + 0.35;

  // Payee name bold (envelope window — first line)
  doc.setFont("helvetica", "bold");
  doc.setFontSize(9);
  doc.text(c.payee, x(addrIndentX), addrBlockY);

  // "Authorized Signer" on the RIGHT, same Y as payee name
  doc.setFont("helvetica", "normal");
  doc.setFontSize(7.5);
  doc.setTextColor(80);
  doc.text("Authorized Signer", x(RIGHT), addrBlockY, { align: "right" });
  doc.setTextColor(0);

  // Address lines below, slightly indented
  if (addrLines.length > 0) {
    doc.setFont("helvetica", "normal");
    doc.setFontSize(8.5);
    doc.setTextColor(30);
    addrLines.slice(0, 3).forEach((line, i) => {
      doc.text(line, x(addrIndentX + 0.05), addrBlockY + 0.155 + i * 0.145);
    });
    doc.setTextColor(0);
  }

  // ── Memo / Authorized Signature line — no grey divider, just space ────────
  const addrEndY    = addrBlockY + 0.155 + Math.max(addrLines.length, 0) * 0.145;
  const bottomLineY = addrEndY + 0.30;   // generous gap, no grey line between
  const sigLabelX   = PW / 2 + 0.2;

  // Memo (left side)
  doc.setFont("helvetica", "normal");
  doc.setFontSize(8);
  doc.setTextColor(0);
  doc.text("MEMO", x(ML), bottomLineY);
  hline(doc, x(ML + 0.5), bottomLineY, x(ML + 2.8));
  if (c.memo) {
    doc.setFontSize(7.5);
    doc.text(c.memo, x(ML + 0.55), bottomLineY - 0.04);
  }

  // "AUTHORIZED SIGNATURE" label centered above the right-side line
  doc.setFontSize(7);
  doc.setTextColor(80);
  const sigCenterX = sigLabelX + (RIGHT - sigLabelX) / 2;
  doc.text("AUTHORIZED SIGNATURE", x(sigCenterX), bottomLineY - 0.36, { align: "center" });
  doc.setTextColor(0);

  // Signature image — natural aspect ratio, bottom edge sits ON the line
  if (c.printSignature && c.signatureDataUrl) {
    try {
      const props = doc.getImageProperties(c.signatureDataUrl);
      const maxW  = RIGHT - sigLabelX - 0.1;
      const maxH  = 0.42;
      const ratio = props.width / props.height;
      let sigW = maxW;
      let sigH = sigW / ratio;
      if (sigH > maxH) { sigH = maxH; sigW = sigH * ratio; }
      const sigX = x(sigCenterX - sigW / 2);
      const sigY = bottomLineY - sigH;
      doc.addImage(c.signatureDataUrl, "PNG", sigX, sigY, sigW, sigH);
    } catch { /* silent */ }
  }

  // Signature LINE
  hline(doc, x(sigLabelX), bottomLineY, x(RIGHT));

  // ── MICR line (X offset + separate MICR Y fine-tune) ─────────────────────
  const micrY = y(CHECK_H - 0.22) + micrOy;
  const micr = buildMicr(
    (c.routingNumber ?? "").replace(/\D/g, ""),
    (c.accountNumber ?? "").replace(/\D/g, ""),
    c.checkNumber,
    micrGap1,
    micrGap2
  );

  if (micr) {
    ensureMicrFont(doc);
    doc.setFontSize(11);
    doc.setTextColor(0);
    doc.text(micr, x(PW / 2), micrY, { align: "center" });
    doc.setFont("helvetica", "normal");
  } else {
    doc.setFont("helvetica", "normal");
    doc.setFontSize(7);
    doc.setTextColor(160);
    doc.text("[ Configure routing & account numbers in Settings → Check Setup ]", x(PW / 2), micrY, { align: "center" });
    doc.setTextColor(0);
  }
}

// ── Voucher stub ─────────────────────────────────────────────────────────────

function drawStub(doc: jsPDF, originY: number, c: CheckData, ox = 0) {
  const y = (dy: number) => originY + dy;
  const x = (dx: number) => dx + ox;
  const pad = 0.04;

  doc.setFont("helvetica", "normal");
  doc.setFontSize(8.5);
  doc.setTextColor(0);
  doc.text(c.companyName, x(ML), y(pad + 0.18));

  doc.setFont("helvetica", "bold");
  doc.setFontSize(11);
  doc.text(String(c.checkNumber), x(RIGHT), y(pad + 0.18), { align: "right" });

  // Separator
  hline(doc, ML, y(pad + 0.26), RIGHT, 0.006);

  // Payee name (bold left) | Date (right)
  const rowY = y(pad + 0.44);
  doc.setFont("helvetica", "bold");
  doc.setFontSize(9);
  doc.text(c.payee, ML, rowY);
  doc.setFont("helvetica", "normal");
  doc.setFontSize(9);
  doc.text(c.date, RIGHT, rowY, { align: "right" });

  hline(doc, ML, y(pad + 0.52), RIGHT, 0.004, 140);

  // Line items
  let itemY = y(pad + 0.70);
  const lineItemH = 0.165;
  const items = c.lineItems ?? (c.memo ? [{ description: c.memo, amount: c.amount }] : []);

  doc.setFont("helvetica", "normal");
  doc.setFontSize(8.5);

  for (const item of items) {
    const descW = CW - 1.1;
    // Truncate description if too long
    let desc = item.description;
    while (doc.getTextWidth(desc) > descW && desc.length > 10) desc = desc.slice(0, -1);

    doc.text(desc, x(ML), itemY);
    doc.text(`$${item.amount.toFixed(2)}`, x(RIGHT), itemY, { align: "right" });
    itemY += lineItemH;
  }

  // Total at bottom-right
  const totalY = y(SECTION_H - 0.22);
  doc.setFont("helvetica", "bold");
  doc.setFontSize(9.5);
  doc.text(`$${c.amount.toFixed(2)}`, x(RIGHT), totalY, { align: "right" });
}

// ── Public API ───────────────────────────────────────────────────────────────

export function generateCheckPDF(checks: CheckData[], opts: CheckPrintOptions): string {
  const doc = new jsPDF({ unit: "in", format: "letter" });
  const ox = opts.offsetX ?? 0;
  const oy = opts.offsetY ?? 0;
  const micrOy = opts.micrOffsetY ?? 0;
  const g1 = opts.micrGap1 ?? 1;
  const g2 = opts.micrGap2 ?? 1;

  checks.forEach((c, idx) => {
    if (idx > 0) doc.addPage();

    if (opts.style === "voucher") {
      drawCheck(doc, 0 + oy, c, ox, micrOy, g1, g2);
      drawStub(doc, SECTION_H + oy, c, ox);
      drawStub(doc, SECTION_H * 2 + oy, c, ox);
    } else {
      let posY = 0;
      if (opts.position === "middle") posY = PH / 2 - CHECK_H / 2;
      if (opts.position === "bottom") posY = PH - CHECK_H;
      drawCheck(doc, posY + oy, c, ox, micrOy, g1, g2);
    }
  });

  return doc.output("dataurlstring");
}

/** Red overlay for check-stock alignment calibration */
export function generateTestAlignmentPDF(opts: CheckPrintOptions): string {
  const doc = new jsPDF({ unit: "in", format: "letter" });
  doc.setFont("helvetica", "normal");

  const sections = opts.style === "voucher"
    ? [
        { y: 0,            label: "CHECK FACE",  color: [200, 0, 0] as [number,number,number] },
        { y: SECTION_H,    label: "STUB 1",      color: [0, 120, 0] as [number,number,number] },
        { y: SECTION_H*2,  label: "STUB 2",      color: [0, 0, 180] as [number,number,number] },
      ]
    : [{ y: opts.position === "bottom" ? PH - CHECK_H : opts.position === "middle" ? PH/2 - CHECK_H/2 : 0, label: "CHECK", color: [200,0,0] as [number,number,number] }];

  for (const s of sections) {
    doc.setLineWidth(0.01);
    doc.setDrawColor(...s.color);
    doc.setLineDashPattern([0.06, 0.04], 0);
    doc.rect(ML, s.y + 0.01, CW, SECTION_H - 0.02);
    doc.setLineDashPattern([], 0);
    doc.setTextColor(...s.color);
    doc.setFontSize(10);
    doc.text(`${s.label} — ${SECTION_H.toFixed(3)}" tall`, ML + 0.1, s.y + 0.22);

    // MICR band indicator
    const micrTop = s.y + SECTION_H - 0.5;
    doc.setFillColor(s.color[0], s.color[1], s.color[2]);
    doc.setGState(doc.GState({ opacity: 0.1 }));
    doc.rect(ML, micrTop, CW, 0.38, "F");
    doc.setGState(doc.GState({ opacity: 1 }));
    doc.setFontSize(7);
    doc.text("MICR band", ML + 0.1, micrTop + 0.2);
  }

  doc.setTextColor(100);
  doc.setFontSize(9);
  doc.text("Alignment test — print on blank paper, hold against check stock to verify", ML, 10.85);
  return doc.output("dataurlstring");
}
