import jsPDF from "jspdf";
import { drawReportCoverPage, type ReportCoverData } from "@/lib/reportCover";

export type RowKind = "section" | "group" | "sub" | "total" | "grand" | "spacer";

export type StructuredRow = {
  kind: RowKind;
  label: string;
  code?: string;
  amount?: number;
  compare?: number;
  /** GL account id for per-account rows — enables drill-down from on-screen reports. */
  accountId?: string;
};

export type StructuredReport = {
  title: string;
  rows: StructuredRow[];
  balanced?: boolean;
  outOfBalanceAmount?: number;
  cashHighlight?: { label: string; amount: number };
};

export type RenderOpts = {
  companyName: string;
  appName: string;
  rangeLabel: string;
  currency: string;
  showCodes: boolean;
  showCompare: boolean;
  showZero: boolean;
  /** Optional metadata lines for the header block (bold label + value). */
  meta?: { label: string; value: string }[];
  /** Preloaded logo (dataURL + dimensions) for the branded header. */
  logo?: { dataURL: string; w: number; h: number } | null;
};

type RGB = [number, number, number];

const TEXT: RGB = [33, 37, 41];
const MUTED: RGB = [110, 116, 122];
const RULE: RGB = [20, 20, 20];
const BAND: RGB = [232, 235, 238];        // section header band
const ZEBRA: RGB = [247, 248, 250];       // alternating line-item rows
const HEADER_FILL: RGB = [237, 239, 242]; // column-header row
const BORDER: RGB = [196, 200, 205];
const RED: RGB = [185, 28, 28];

/** Accounting number: thousands separators, 2 decimals, leading minus for negatives. */
export function fmtAmount(n: number | undefined | null): string {
  if (n === undefined || n === null) return "";
  return n.toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 2 });
}

/** Draw the report body onto `doc`, starting on its current page. */
function buildReport(doc: jsPDF, report: StructuredReport, opts: RenderOpts): jsPDF {
  const firstPage = doc.getNumberOfPages();
  const W = doc.internal.pageSize.getWidth();
  const H = doc.internal.pageSize.getHeight();
  const ML = 40, MR = 40;
  const contentR = W - MR;
  const amtRight = contentR - 6;
  const COLW = 86;   // width of each numeric column
  const UNDER = 78;  // underline width under a numeric column
  // Numeric column right edges. With a comparison: Amount | Comparative | Change | Change %.
  const colPct = amtRight;
  const colChange = amtRight - COLW;
  const colCompare = amtRight - 2 * COLW;
  const colAmount = opts.showCompare ? amtRight - 3 * COLW : amtRight;
  const numericLeft = colAmount - UNDER; // left boundary of the numeric block (for divider)
  const pctStr = (a?: number, c?: number) =>
    a === undefined || c === undefined || Math.abs(c) < 0.005 ? "—" : `${(((a - c) / Math.abs(c)) * 100).toFixed(1)}%`;
  const isBalanceSheet = /balance sheet/i.test(report.title);
  const rightHeader = isBalanceSheet ? "Balance" : "Amount";
  let y = 0;

  const drawColHeader = () => {
    const h = 18;
    doc.setFillColor(...HEADER_FILL);
    doc.rect(ML, y, contentR - ML, h, "F");
    doc.setDrawColor(...BORDER); doc.setLineWidth(0.5);
    doc.rect(ML, y, contentR - ML, h);
    // vertical divider before the numeric block
    doc.line(numericLeft - 6, y, numericLeft - 6, y + h);
    doc.setFont("helvetica", "bold"); doc.setFontSize(opts.showCompare ? 8 : 9); doc.setTextColor(...TEXT);
    doc.text("Account Name", ML + 6, y + 12);
    doc.text(rightHeader, colAmount, y + 12, { align: "right" });
    if (opts.showCompare) {
      doc.text("Comparative", colCompare, y + 12, { align: "right" });
      doc.text("Change", colChange, y + 12, { align: "right" });
      doc.text("Change %", colPct, y + 12, { align: "right" });
    }
    y += h;
  };

  // Full header (page 1): title + metadata block + column header
  const drawFullHeader = () => {
    // Branded logo (top-left) + centered title — matches the main reports.
    if (opts.logo) {
      try {
        const lh = 40; const lw = Math.min(150, lh * (opts.logo.w / opts.logo.h));
        doc.addImage(opts.logo.dataURL, "PNG", ML, 26, lw, lh, undefined, "FAST");
      } catch { /* ignore */ }
    }
    doc.setFont("helvetica", "bold"); doc.setFontSize(16); doc.setTextColor(...TEXT);
    doc.text(report.title, W / 2, 48, { align: "center" });
    y = 82;

    const meta = opts.meta ?? [
      { label: "Properties:", value: opts.companyName || "—" },
      { label: isBalanceSheet ? "As of:" : "Period:", value: opts.rangeLabel },
      { label: "Accounting Basis:", value: "Accrual" },
    ];
    doc.setFontSize(9.5);
    for (const m of meta) {
      doc.setFont("helvetica", "bold"); doc.setTextColor(...TEXT);
      doc.text(m.label, ML, y);
      const lw = doc.getTextWidth(m.label);
      doc.setFont("helvetica", "normal");
      doc.text(` ${m.value}`, ML + lw, y);
      y += 14;
    }
    y += 6;
    drawColHeader();
  };

  // Continuation header (page 2+): centered title + column header only
  const drawContHeader = () => {
    doc.setFont("helvetica", "bold"); doc.setFontSize(12); doc.setTextColor(...TEXT);
    doc.text(report.title, W / 2, 40, { align: "center" });
    y = 56;
    drawColHeader();
  };

  const drawFooter = () => {
    const total = doc.getNumberOfPages();
    const gen = new Date().toLocaleString("en-US");
    const pages = total - firstPage + 1;
    // Footer only on content pages (skip any preceding cover page).
    for (let p = firstPage; p <= total; p++) {
      doc.setPage(p);
      doc.setFont("helvetica", "normal"); doc.setFontSize(8); doc.setTextColor(...MUTED);
      doc.text(`Generated ${gen}`, ML, H - 28);
      doc.text(`Page ${p - firstPage + 1} of ${pages}`, contentR, H - 28, { align: "right" });
    }
  };

  const ensure = (need: number) => {
    if (y + need > H - 50) { doc.addPage(); drawContHeader(); }
  };

  drawFullHeader();

  let alt = false;
  for (const row of report.rows) {
    if (row.kind === "spacer") { y += 6; alt = false; continue; }
    if (row.kind === "sub" && !opts.showZero && (row.amount ?? 0) === 0) continue;

    if (row.kind === "section") {
      ensure(20);
      doc.setFillColor(...BAND);
      doc.rect(ML, y, contentR - ML, 18, "F");
      doc.setFont("helvetica", "bold"); doc.setFontSize(9.5); doc.setTextColor(...TEXT);
      doc.text(row.label, ML + 6, y + 12);
      y += 18; alt = false; continue;
    }

    if (row.kind === "group") {
      ensure(16);
      doc.setFont("helvetica", "bold"); doc.setFontSize(9); doc.setTextColor(...TEXT);
      doc.text(row.label, ML + 12, y + 11);
      y += 15; alt = false; continue;
    }

    const h = 16;
    ensure(h + 2);
    const top = y;
    const bold = row.kind === "total" || row.kind === "grand";

    // zebra striping on line items
    if (row.kind === "sub" && alt) {
      doc.setFillColor(...ZEBRA);
      doc.rect(ML, top, contentR - ML, h, "F");
    }

    // rule above subtotals (thin) and grand totals (heavy), over the number columns
    if (row.kind === "total" || row.kind === "grand") {
      doc.setDrawColor(...RULE);
      doc.setLineWidth(row.kind === "grand" ? 1.4 : 0.7);
      const underCols = opts.showCompare ? [colAmount, colCompare, colChange] : [colAmount];
      for (const x of underCols) doc.line(x - UNDER, top + 1, x, top + 1);
    }

    // label
    const xLabel = row.kind === "sub" ? ML + 18 : row.kind === "total" ? ML + 12 : ML + 6;
    doc.setFont("helvetica", bold ? "bold" : "normal"); doc.setFontSize(9); doc.setTextColor(...TEXT);
    if (row.kind === "sub" && opts.showCodes && row.code) {
      doc.setTextColor(...MUTED); doc.setFontSize(8.5);
      doc.text(row.code, xLabel, top + 11);
      const cw = doc.getTextWidth(row.code) + 6;
      doc.setTextColor(...TEXT); doc.setFontSize(9);
      doc.text(row.label, xLabel + cw, top + 11);
    } else {
      doc.text(row.label, xLabel, top + 11);
    }

    // amounts
    const drawNum = (val: number | undefined, x: number) => {
      if (val === undefined) return;
      doc.setFont("helvetica", bold ? "bold" : "normal"); doc.setFontSize(9);
      doc.setTextColor(...(val < 0 ? RED : TEXT));
      doc.text(fmtAmount(val), x, top + 11, { align: "right" });
    };
    drawNum(row.amount, colAmount);
    if (opts.showCompare) {
      drawNum(row.compare, colCompare);
      if (row.amount !== undefined && row.compare !== undefined) drawNum(row.amount - row.compare, colChange);
      if (row.amount !== undefined) {
        doc.setFont("helvetica", bold ? "bold" : "normal"); doc.setFontSize(9); doc.setTextColor(...MUTED);
        doc.text(pctStr(row.amount, row.compare), colPct, top + 11, { align: "right" });
      }
    }

    y += h;
    alt = row.kind === "sub" ? !alt : false;
  }

  // Balance-sheet balanced callout
  if (report.balanced !== undefined) {
    ensure(28); y += 6;
    const ok = report.balanced;
    const border: RGB = ok ? [22, 128, 61] : [185, 28, 28];
    const fill: RGB = ok ? [240, 253, 244] : [254, 242, 242];
    const txt: RGB = ok ? [22, 101, 52] : [153, 27, 27];
    doc.setDrawColor(...border); doc.setLineWidth(0.8);
    doc.setFillColor(...fill);
    doc.rect(ML, y, contentR - ML, 22, "FD");
    doc.setFont("helvetica", "bold"); doc.setFontSize(9.5); doc.setTextColor(...txt);
    const msg = ok
      ? "Balance Sheet is balanced"
      : `Balance Sheet is OUT OF BALANCE by ${fmtAmount(report.outOfBalanceAmount ?? 0)} (Assets - Liabilities - Equity)`;
    doc.text(msg, ML + 8, y + 14);
    y += 28;
  }

  // Cash-flow net-change highlight
  if (report.cashHighlight) {
    ensure(28); y += 6;
    doc.setFillColor(...HEADER_FILL);
    doc.rect(ML, y, contentR - ML, 22, "F");
    doc.setFont("helvetica", "bold"); doc.setFontSize(10); doc.setTextColor(...TEXT);
    doc.text(report.cashHighlight.label, ML + 8, y + 14);
    const amt = report.cashHighlight.amount;
    const c: RGB = amt < 0 ? RED : TEXT;
    doc.setTextColor(...c);
    doc.text(fmtAmount(amt), colAmount, y + 14, { align: "right" });
    y += 28;
  }

  drawFooter();
  return doc;
}

/** Render a financial report PDF (no cover page). */
export function renderReportPdf(report: StructuredReport, opts: RenderOpts): jsPDF {
  // Landscape when a comparison period is shown — room for the extra columns.
  const doc = new jsPDF({ unit: "pt", format: "letter", orientation: opts.showCompare ? "landscape" : "portrait" });
  return buildReport(doc, report, opts);
}

/**
 * Render a financial report PDF that opens with the shared branded cover page
 * (same look as the general Report Generator), followed by the report body.
 */
export async function renderReportPdfWithCover(
  report: StructuredReport,
  opts: RenderOpts,
  cover: ReportCoverData,
): Promise<jsPDF> {
  const doc = new jsPDF({ unit: "pt", format: "letter", orientation: opts.showCompare ? "landscape" : "portrait" });
  await drawReportCoverPage(doc, doc.internal.pageSize.getWidth(), doc.internal.pageSize.getHeight(), cover);
  doc.addPage();
  return buildReport(doc, report, opts);
}
