import type { TDocumentDefinitions, Content, TableCell } from 'pdfmake/interfaces';

export type DocumentType = 'GRN' | 'RCR' | 'PICK_LIST' | 'PACKING_LIST' | 'DN' | 'GATE_PASS' | 'INVOICE' | 'COUNT_SHEET' | 'LOAD_MANIFEST';

export interface TemplateContext {
  tenantName: string;
  branding: Record<string, unknown>;
  currency: string;
}

const NAVY = '#101820';
const GREY = '#6B7280';
const LIGHT = '#F3F4F6';

type Row = Record<string, unknown>;
const s = (v: unknown): string => (v === null || v === undefined ? '' : typeof v === 'object' && v instanceof Date ? v.toISOString().slice(0, 10) : String(v));
const dt = (v: unknown): string => {
  if (!v) return '';
  const d = typeof v === 'string' || typeof v === 'number' ? new Date(v) : (v as Date);
  return isNaN(d.getTime()) ? s(v) : d.toISOString().replace('T', ' ').slice(0, 16);
};

function header(title: string, number: string, ctx: TemplateContext, meta: [string, unknown][]): Content {
  const primary = (ctx.branding.primaryColor as string) ?? NAVY;
  return [
    {
      columns: [
        { width: '*', stack: [{ text: ctx.tenantName, bold: true, fontSize: 14, color: primary }, { text: (ctx.branding.addressLine as string) ?? '', color: GREY, fontSize: 8 }] },
        { width: 'auto', stack: [{ text: title, bold: true, fontSize: 16, alignment: 'right', color: primary }, { text: number, fontSize: 11, alignment: 'right' }] },
      ],
    },
    { canvas: [{ type: 'line', x1: 0, y1: 6, x2: 523, y2: 6, lineWidth: 1, lineColor: primary }], margin: [0, 0, 0, 8] },
    {
      table: { widths: ['auto', '*', 'auto', '*'], body: chunkPairs(meta.map(([k, v]) => [{ text: k, bold: true, color: GREY }, { text: s(v) }])) },
      layout: 'noBorders',
      margin: [0, 0, 0, 10],
    } as Content,
  ];
}

function chunkPairs(cells: TableCell[][]): TableCell[][] {
  const rows: TableCell[][] = [];
  for (let i = 0; i < cells.length; i += 2) {
    const a = cells[i];
    const b = cells[i + 1] ?? [{ text: '' }, { text: '' }];
    rows.push([...a, ...b]);
  }
  return rows.length ? rows : [[{ text: '' }, { text: '' }, { text: '' }, { text: '' }]];
}

function table(headers: string[], rows: unknown[][], widths?: (string | number)[]): Content {
  return {
    table: {
      headerRows: 1,
      widths: widths ?? headers.map(() => '*'),
      body: [headers.map((h) => ({ text: h, bold: true, fillColor: LIGHT, fontSize: 8 })), ...rows.map((r) => r.map((c): TableCell => (typeof c === 'object' && c !== null && 'text' in (c as object) ? (c as TableCell) : { text: s(c), fontSize: 8 })))],
    },
    layout: { hLineWidth: () => 0.5, vLineWidth: () => 0, hLineColor: () => '#D1D5DB', paddingTop: () => 3, paddingBottom: () => 3 },
    margin: [0, 0, 0, 10],
  };
}

function signatures(labels: string[]): Content {
  return {
    margin: [0, 24, 0, 0],
    columns: labels.map((l) => ({ stack: [{ canvas: [{ type: 'line', x1: 0, y1: 0, x2: 140, y2: 0, lineWidth: 0.5 }] }, { text: l, fontSize: 8, color: GREY, margin: [0, 3, 0, 0] }] })),
  };
}

function footer(ctx: TemplateContext): TDocumentDefinitions['footer'] {
  return (page, pages) => ({ columns: [{ text: (ctx.branding.documentFooter as string) ?? 'System generated document', fontSize: 7, color: GREY }, { text: `Page ${page} of ${pages}`, alignment: 'right', fontSize: 7, color: GREY }], margin: [36, 10, 36, 0] });
}

export function buildDocument(type: DocumentType, number: string, data: Row, ctx: TemplateContext): TDocumentDefinitions {
  const base: TDocumentDefinitions = { content: [], footer: footer(ctx), info: { title: `${type} ${number}`, author: ctx.tenantName } };
  switch (type) {
    case 'GRN':
    case 'RCR':
      return { ...base, content: grnContent(type, number, data, ctx) };
    case 'PICK_LIST':
      return { ...base, content: pickListContent(number, data, ctx) };
    case 'PACKING_LIST':
      return { ...base, content: packingListContent(number, data, ctx) };
    case 'DN':
      return { ...base, content: deliveryNoteContent(number, data, ctx) };
    case 'GATE_PASS':
      return { ...base, content: gatePassContent(number, data, ctx) };
    case 'LOAD_MANIFEST':
      return { ...base, content: loadManifestContent(number, data, ctx) };
    case 'INVOICE':
      return { ...base, content: invoiceContent(number, data, ctx) };
    case 'COUNT_SHEET':
      return { ...base, content: countSheetContent(number, data, ctx) };
  }
}

function grnContent(type: 'GRN' | 'RCR', number: string, data: Row, ctx: TemplateContext): Content {
  const asn = data.asn as Row;
  const grn = data.grn as Row;
  const summary = data.summary as { lines: Row[]; receipts: Row[]; discrepancies: Row[]; totals: Row };
  const title = type === 'GRN' ? 'GOODS RECEIPT NOTE' : 'RECEIPT CONFIRMATION REPORT';
  const content: Content[] = [
    header(title, number, ctx, [
      ['ASN', asn.asnNumber], ['Customer Ref', asn.externalRef], ['Storer', (asn.storerName as string) ?? ''], ['GRN', grn.grnNumber],
      ['Arrived', dt(asn.arrivedAt)], ['Closed', dt(asn.closedAt ?? grn.postedAt)], ['Container', asn.containerNo], ['Vehicle / Seal', [asn.vehicleNo, asn.sealNo].filter(Boolean).join(' / ')],
    ]),
    table(
      ['#', 'SKU', 'Description', 'Lot', 'Expected', 'Good', 'Damaged', 'Rejected', 'Variance'],
      summary.lines.map((l) => [l.lineNo, l.skuCode, l.description, l.expectedLot, l.expected, l.good, l.damaged, l.rejected, { text: s(l.variance), color: s(l.variance) === '0' ? undefined : '#B91C1C', fontSize: 8 }]),
      [18, 70, '*', 50, 45, 40, 45, 45, 45],
    ),
    { columns: [{ text: `Totals: expected ${s(summary.totals.expected)} | good ${s(summary.totals.good)} | damaged ${s(summary.totals.damaged)} | rejected ${s(summary.totals.rejected)} | pallets ${s(summary.totals.pallets)}`, bold: true }], margin: [0, 0, 0, 10] },
  ];
  if (summary.discrepancies.length) {
    content.push({ text: 'Discrepancies', bold: true, margin: [0, 4, 0, 4] });
    content.push(table(['Type', 'SKU', 'Expected', 'Actual', 'Variance'], summary.discrepancies.map((d) => [d.type, d.skuCode, d.expected, d.actual, d.variance])));
  }
  if (type === 'GRN') {
    content.push({ text: 'Pallet detail', bold: true, margin: [0, 4, 0, 4] });
    content.push(table(['LPN', 'SKU', 'Qty', 'Lot', 'Expiry', 'Condition', 'Received'], summary.receipts.map((r) => [r.lpnNumber, r.skuCode, r.qty, r.lotNumber, r.expiryDate, r.condition, dt(r.receivedAt)])));
  } else {
    content.push({ text: 'This report confirms the quantities physically received at our facility. Discrepancies listed above are reported for your records; please acknowledge within 48 hours. Damaged or rejected goods are held in quarantine pending your instructions.', fontSize: 8, color: GREY, margin: [0, 6, 0, 0] });
  }
  content.push(signatures(['Received by (Warehouse)', 'Checked by (Supervisor)', type === 'RCR' ? 'Acknowledged by (Customer)' : 'Driver']));
  return content;
}

function pickListContent(number: string, data: Row, ctx: TemplateContext): Content {
  const tasks = data.tasks as Row[];
  const wave = data.wave as Row | undefined;
  const orders = (data.orders as Row[]) ?? [];
  return [
    header('PICK LIST', number, ctx, [['Wave', wave?.waveNumber ?? ''], ['Orders', orders.map((o) => o.orderNumber).join(', ')], ['Printed', dt(new Date())], ['Tasks', tasks.length]]),
    table(['Seq', 'Location', 'LPN', 'SKU', 'Description', 'Lot', 'Qty', 'Order', 'Done'], tasks.map((t) => [t.sequence, t.fromLocationCode, t.lpnNumber, t.skuCode, t.description, t.lotNumber, t.qty, t.orderNumber, '☐']), [25, 70, 60, 60, '*', 45, 40, 70, 25]),
    signatures(['Picker', 'Checker']),
  ];
}

function packingListContent(number: string, data: Row, ctx: TemplateContext): Content {
  const shipment = data.shipment as Row;
  const order = data.order as Row;
  const cartons = data.cartons as Row[];
  const shipTo = (order.shipTo as Row) ?? {};
  return [
    header('PACKING LIST', number, ctx, [['Order', order.orderNumber], ['Customer Ref', order.externalRef], ['Ship to', shipTo.name], ['Cartons', cartons.length], ['Packed', dt(shipment.packedAt)], ['Weight (kg)', shipment.totalWeightKg]]),
    ...cartons.map((c) => [
      { text: `${s(c.type)} ${s(c.cartonNumber)}  ${c.weightKg ? `${s(c.weightKg)} kg` : ''}`, bold: true, margin: [0, 4, 0, 2] } as Content,
      table(['SKU', 'Description', 'Lot', 'Expiry', 'Qty', 'Serials'], ((c.lines as Row[]) ?? []).map((l) => [l.skuCode, l.description, l.lotNumber, l.expiryDate, l.qty, ((l.serials as string[]) ?? []).join(', ')]), [70, '*', 50, 50, 40, 120]),
    ]).flat(),
  ];
}

function deliveryNoteContent(number: string, data: Row, ctx: TemplateContext): Content {
  const order = data.order as Row;
  const shipment = data.shipment as Row;
  const lines = data.lines as Row[];
  const shipTo = (order.shipTo as Row) ?? {};
  const addr = (shipTo.address as Row) ?? {};
  return [
    header('DELIVERY NOTE', number, ctx, [
      ['Order', order.orderNumber], ['Customer Ref', order.externalRef], ['Storer', data.storerName], ['Shipment', shipment.shipmentNumber],
      ['Deliver to', shipTo.name], ['Address', [addr.line1, addr.line2, addr.city, addr.country].filter(Boolean).join(', ')], ['Contact', [shipTo.contact, shipTo.phone].filter(Boolean).join(' ')], ['Carrier / Vehicle', [data.carrierName, data.vehicleNo].filter(Boolean).join(' / ')],
    ]),
    table(['#', 'SKU', 'Description', 'Lot', 'Expiry', 'Ordered', 'Shipped', 'UoM'], lines.map((l, i) => [i + 1, l.skuCode, l.description, l.lotNumber, l.expiryDate, l.orderedQty, l.shippedQty, l.uom]), [18, 70, '*', 50, 50, 45, 45, 30]),
    { text: `Cartons: ${s(shipment.cartonCount)}   Pallets: ${s(shipment.palletCount)}   Weight: ${s(shipment.totalWeightKg)} kg   Volume: ${s(shipment.totalCbm)} m³`, margin: [0, 0, 0, 6] },
    { text: shipTo.instructions ? `Instructions: ${s(shipTo.instructions)}` : '', fontSize: 8 },
    { text: 'Goods received in good order and condition unless noted below. Rejections / remarks:', fontSize: 8, margin: [0, 14, 0, 30] },
    signatures(['Prepared by', 'Driver (name / ID / signature)', 'Received by (name / stamp / date)']),
  ];
}

function gatePassContent(number: string, data: Row, ctx: TemplateContext): Content {
  const load = data.load as Row;
  const shipments = data.shipments as Row[];
  return [
    header('GATE PASS', number, ctx, [
      ['Load', load.loadNumber], ['Vehicle', load.vehicleNo], ['Driver', load.driverName], ['Driver ID', load.driverIdNo],
      ['Carrier', load.carrierName], ['Seal', load.sealNo], ['Departure', dt(load.departedAt ?? new Date())], ['Dock', data.dockCode],
    ]),
    table(['Shipment', 'Order', 'Customer Ref', 'Deliver to', 'Cartons', 'Pallets', 'DN'], shipments.map((x) => [x.shipmentNumber, x.orderNumber, x.externalRef, x.shipToName, x.cartonCount, x.palletCount, x.dnNumber])),
    { text: 'The above vehicle is authorised to leave the premises with the listed consignments. Security to verify seal and vehicle number.', fontSize: 8, margin: [0, 6, 0, 0] },
    signatures(['Warehouse Supervisor', 'Security', 'Driver']),
  ];
}

function loadManifestContent(number: string, data: Row, ctx: TemplateContext): Content {
  const load = data.load as Row;
  const shipments = data.shipments as Row[];
  return [
    header('LOAD MANIFEST', number, ctx, [['Load', load.loadNumber], ['Vehicle', load.vehicleNo], ['Carrier', load.carrierName], ['Shipments', shipments.length]]),
    table(['Stop', 'Shipment', 'Order', 'Deliver to', 'Cartons', 'Pallets', 'Weight', 'Signed'], shipments.map((x, i) => [i + 1, x.shipmentNumber, x.orderNumber, x.shipToName, x.cartonCount, x.palletCount, x.totalWeightKg, '☐'])),
  ];
}

function invoiceContent(number: string, data: Row, ctx: TemplateContext): Content {
  const inv = data.invoice as Row;
  const storer = data.storer as Row;
  const lines = data.lines as Row[];
  const cur = s(inv.currency ?? ctx.currency);
  const addr = (storer.address as Row) ?? {};
  const groups = Array.from(new Set(lines.map((l) => s(l.group))));
  const body: Content[] = [];
  for (const g of groups) {
    const gl = lines.filter((l) => s(l.group) === g);
    body.push({ text: g.replace(/_/g, ' '), bold: true, margin: [0, 6, 0, 2] });
    body.push(table(['Description', 'Qty', 'Unit', 'Rate', `Amount (${cur})`], gl.map((l) => [l.description, l.quantity, s(l.unit).replace(/_/g, ' ').toLowerCase(), l.rate, { text: s(l.amount), alignment: 'right', fontSize: 8 }]), ['*', 60, 90, 60, 80]));
    const detail = gl.find((l) => l.detail && (l.detail as Row).weeks);
    if (detail) {
      const weeks = (detail.detail as Row).weeks as Row[];
      body.push(table(['Week', 'From', 'To', 'Opening CBM', 'In CBM', 'Out CBM', 'Chargeable', 'Rate', 'Amount'], weeks.map((w) => [w.key, w.start, w.end, w.opening, w.inbound, w.outbound, w.chargeable, w.rate, w.amount]), [50, 50, 50, 55, 45, 45, 55, 40, 55]));
    }
  }
  return [
    header('INVOICE', number, ctx, [
      ['Bill to', storer.legalName ?? storer.name], ['Address', [addr.line1, addr.city, addr.country].filter(Boolean).join(', ')], ['Tax ID', storer.taxId], ['Contract', data.contractNumber],
      ['Period', `${s(inv.periodStart)} to ${s(inv.periodEnd)}`], ['Issued', s(inv.issuedAt ?? '').slice(0, 10)], ['Due', inv.dueAt], ['Currency', cur],
    ]),
    ...body,
    {
      columns: [{ width: '*', text: '' }, { width: 220, table: { widths: ['*', 80], body: [[{ text: 'Subtotal', bold: true }, { text: `${s(inv.subtotal)} ${cur}`, alignment: 'right' }], [{ text: 'Tax', bold: true }, { text: `${s(inv.taxTotal)} ${cur}`, alignment: 'right' }], [{ text: 'TOTAL', bold: true, fontSize: 11 }, { text: `${s(inv.total)} ${cur}`, alignment: 'right', bold: true, fontSize: 11 }]] }, layout: 'noBorders' }],
      margin: [0, 10, 0, 0],
    },
    { text: s(inv.notes ?? ''), fontSize: 8, color: GREY, margin: [0, 10, 0, 0] },
    { text: (ctx.branding.invoiceFooter as string) ?? '', fontSize: 8, color: GREY, margin: [0, 10, 0, 0] },
  ];
}

function countSheetContent(number: string, data: Row, ctx: TemplateContext): Content {
  const count = data.count as Row;
  const lines = data.lines as Row[];
  const blind = !!count.blind;
  return [
    header('COUNT SHEET', number, ctx, [['Count', count.countNumber], ['Type', count.type], ['Blind', blind ? 'Yes' : 'No'], ['Lines', lines.length]]),
    table(['Location', 'SKU', 'Description', 'LPN', 'Lot', ...(blind ? [] : ['System']), 'Counted', 'Counter'], lines.map((l) => [l.locationCode, l.skuCode, l.skuDescription, l.lpnNumber, l.lotNumber, ...(blind ? [] : [l.systemQty]), '', '']), [70, 60, '*', 60, 45, ...(blind ? [] : [45]), 50, 50]),
  ];
}
