/* Comprobante PDF de pago — tema Tecos Elite */

const RECEIPT_SCHOOL_NAME = 'TECOS ELITE VOLLEYBALL';
const RECEIPT_LOGO_PATH = (typeof window !== 'undefined' && window.TECOS_LOGO_PATH) || 'assets/logo-tecos.png?v=no-bg-2';
const RECEIPT_LOGO_PDF_ASPECT = (typeof LOGO_ASPECT !== 'undefined' && LOGO_ASPECT) || (918 / 887);

function receiptPdfLogoSize (maxH = 28) {
  const h = maxH;
  return { w: Math.round(h * RECEIPT_LOGO_PDF_ASPECT * 10) / 10, h };
}

function receiptPdfLogoTextOffset (logoSz, hasLogo) {
  if (!hasLogo) return 0;
  return logoSz.w + 8;
}

function addReceiptPdfLogoImage (doc, logoData, x, y, w, h) {
  if (!logoData) return;
  try {
    doc.addImage(logoData, receiptImgFormat(logoData), x, y, w, h);
  } catch { /* skip */ }
}

async function loadTecosLogoDataUrl () {
  return loadReceiptImage(RECEIPT_LOGO_PATH);
}

/** Encabezado oscuro con logo para reportes PDF (pagos, interesados, listados). */
function drawTecosPdfReportHeader (doc, {
  pageW,
  subtitle,
  meta,
  headerH = 32,
  logoDataUrl = null,
  margin = 14,
}) {
  doc.setFillColor(15, 23, 42);
  doc.rect(0, 0, pageW, headerH, 'F');
  doc.setFillColor(234, 179, 8);
  doc.rect(0, headerH - 2, pageW, 2, 'F');

  const logoSz = receiptPdfLogoSize(22);
  let textX = margin;
  if (logoDataUrl) {
    addReceiptPdfLogoImage(doc, logoDataUrl, margin, 4, logoSz.w, logoSz.h);
    textX = margin + receiptPdfLogoTextOffset(logoSz, true);
  }

  doc.setTextColor(255, 255, 255);
  doc.setFont('helvetica', 'bold');
  doc.setFontSize(20);
  doc.text('TECOS ELITE VOLLEYBALL', textX, 14);
  doc.setFontSize(10);
  doc.setFont('helvetica', 'normal');
  if (subtitle) doc.text(subtitle, textX, 22);
  if (meta) doc.text(meta, textX, 28);
  return headerH;
}

async function loadReceiptImage (url) {
  if (!url) return null;
  if (typeof urlToDataUrl === 'function') return urlToDataUrl(url);
  try {
    const res = await fetch(url, { mode: 'cors', cache: 'no-cache' });
    if (!res.ok) return null;
    const blob = await res.blob();
    return await new Promise((resolve, reject) => {
      const reader = new FileReader();
      reader.onload = () => resolve(reader.result);
      reader.onerror = reject;
      reader.readAsDataURL(blob);
    });
  } catch {
    return null;
  }
}

function receiptImgFormat (dataUrl) {
  if (!dataUrl || typeof dataUrl !== 'string') return 'JPEG';
  if (dataUrl.includes('image/png')) return 'PNG';
  if (dataUrl.includes('image/webp')) return 'WEBP';
  return 'JPEG';
}

/** Reduce peso del PDF para envío por WhatsApp (evita fallos del API). */
async function compressReceiptImageForPdf (dataUrl, maxW = 900, maxH = 650) {
  if (!dataUrl || typeof dataUrl !== 'string') return dataUrl;
  return new Promise((resolve) => {
    const img = new Image();
    img.crossOrigin = 'anonymous';
    img.onload = () => {
      let w = img.width;
      let h = img.height;
      const ratio = Math.min(maxW / w, maxH / h, 1);
      w = Math.max(1, Math.round(w * ratio));
      h = Math.max(1, Math.round(h * ratio));
      const canvas = document.createElement('canvas');
      canvas.width = w;
      canvas.height = h;
      const ctx = canvas.getContext('2d');
      ctx.drawImage(img, 0, 0, w, h);
      try {
        resolve(canvas.toDataURL('image/jpeg', 0.82));
      } catch {
        resolve(dataUrl);
      }
    };
    img.onerror = () => resolve(dataUrl);
    img.src = dataUrl;
  });
}

/** Texto corto bajo la imagen en WhatsApp (el detalle va en la imagen). */
function buildPaymentReceiptWaCaptionShort (alumno, payment) {
  const tutor = alumno?.tutor && alumno.tutor !== '—' ? alumno.tutor : 'familia';
  const due = payment?.due_date ? new Date(payment.due_date) : null;
  const mesLabel = due && !Number.isNaN(due.getTime())
    ? due.toLocaleDateString('es-MX', { month: 'long', year: 'numeric' })
    : (payment?.concept || 'mensualidad');
  return `Hola ${tutor}, tu comprobante de pago (${mesLabel}) — Tecos Elite VOLLEYBALL`;
}

function buildPaymentReceiptWaCaption (alumno, payment) {
  const st = payment?.students || {};
  const tutor = alumno?.tutor && alumno.tutor !== '—' ? alumno.tutor : 'familia';
  const due = payment?.due_date ? new Date(payment.due_date) : null;
  const mesLabel = due && !Number.isNaN(due.getTime())
    ? due.toLocaleDateString('es-MX', { month: 'long', year: 'numeric' })
    : (payment?.concept || 'mensualidad');
  const monto = Number(payment?.amount || 0).toLocaleString('es-MX');
  const concepto = payment?.concept || 'Mensualidad';
  const code = alumno?.id || alumno?.code || st.code || '—';
  const nombre = alumno?.name || st.full_name || '—';
  const pagado = payment?.paid_at || payment?.created_at;
  const fechaPago = pagado ? formatReceiptDateTime(pagado) : '—';

  return [
    `Hola ${tutor}, aquí está tu comprobante de pago.`,
    '',
    `Alumno: ${nombre}`,
    `ID: ${code}`,
    `Periodo: ${mesLabel}`,
    `Concepto: ${concepto}`,
    `Monto: $${monto} MXN`,
    `Fecha de pago: ${fechaPago}`,
    '',
    '— Tecos Elite VOLLEYBALL',
  ].join('\n');
}

function formatReceiptDateTime (iso) {
  if (!iso) return '—';
  const d = new Date(iso);
  if (Number.isNaN(d.getTime())) return '—';
  return d.toLocaleString('es-MX', {
    weekday: 'long',
    day: 'numeric',
    month: 'long',
    year: 'numeric',
    hour: '2-digit',
    minute: '2-digit',
  });
}

/**
 * @param {object} alumno — fila mapStudentRow
 * @param {object} payment — fila payments + students (fetchPaymentDetailAdmin)
 * @param {object} settings — academy settings
 */
async function buildPaymentReceiptPdfDoc (alumno, payment, settings = {}, theme = {}) {
  const { jsPDF } = window.jspdf || {};
  if (!jsPDF) throw new Error('Librería PDF no disponible');

  const st = payment?.students || {};
  const schoolName = settings?.venue_name || RECEIPT_SCHOOL_NAME;
  const primary = theme.primary || '#1e3a8a';
  const accent = theme.accent || '#eab308';
  const dark = [15, 23, 42];
  const lightBg = [248, 250, 252];

  const [logoData, photoData, receiptDataRaw] = await Promise.all([
    loadReceiptImage(RECEIPT_LOGO_PATH),
    loadReceiptImage(alumno?.photoUrl || (st.photo_path && typeof getStoragePublicUrl === 'function'
      ? getStoragePublicUrl('gallery', st.photo_path) : null)),
    payment?.receipt_path && typeof getReceiptSignedUrl === 'function'
      ? getReceiptSignedUrl(payment.receipt_path, 3600).then(u => loadReceiptImage(u)).catch(() => null)
      : Promise.resolve(null),
  ]);
  const receiptData = receiptDataRaw
    ? await compressReceiptImageForPdf(receiptDataRaw)
    : null;

  const doc = new jsPDF({ unit: 'mm', format: 'a4' });
  const pageW = doc.internal.pageSize.getWidth();
  const margin = 16;
  let y = 0;

  doc.setFillColor(...dark);
  doc.rect(0, 0, pageW, 42, 'F');
  doc.setFillColor(234, 179, 8);
  doc.rect(0, 40, pageW, 2, 'F');

  const logoSz = receiptPdfLogoSize(28);
  addReceiptPdfLogoImage(doc, logoData, margin, 7, logoSz.w, logoSz.h);
  const bannerTextX = margin + receiptPdfLogoTextOffset(logoSz, !!logoData);
  doc.setTextColor(255, 255, 255);
  doc.setFont('helvetica', 'bold');
  doc.setFontSize(20);
  doc.text(schoolName, bannerTextX, 16);
  doc.setFontSize(11);
  doc.setFont('helvetica', 'normal');
  doc.text('Comprobante de pago de mensualidad', bannerTextX, 24);
  doc.setFontSize(9);
  doc.text(`Folio: ${String(payment?.id || '').slice(0, 8).toUpperCase()}`, pageW - margin, 16, { align: 'right' });
  doc.text(formatReceiptDateTime(payment?.paid_at || payment?.created_at), pageW - margin, 24, { align: 'right' });

  y = 52;
  doc.setTextColor(30, 41, 59);

  doc.setFillColor(...lightBg);
  doc.rect(margin, y, pageW - margin * 2, 38, 'F');
  if (photoData) {
    try {
      doc.addImage(photoData, receiptImgFormat(photoData), margin + 4, y + 4, 30, 30);
    } catch { /* skip */ }
  }
  doc.setFont('helvetica', 'bold');
  doc.setFontSize(14);
  doc.text(alumno?.name || st.full_name || '—', margin + 40, y + 12);
  doc.setFont('helvetica', 'normal');
  doc.setFontSize(10);
  doc.text(`ID: ${alumno?.code || alumno?.id || st.code || '—'}  ·  ${alumno?.cat || st.category || '—'}`, margin + 40, y + 20);
  doc.text(`Tutor: ${alumno?.tutor || st.tutor_name || '—'}`, margin + 40, y + 27);
  doc.text(`Tel. tutor: ${alumno?.phone || st.tutor_phone || '—'}`, margin + 40, y + 33);
  y += 46;

  const due = payment?.due_date ? new Date(payment.due_date) : null;
  const mesLabel = due && !Number.isNaN(due.getTime())
    ? due.toLocaleDateString('es-MX', { month: 'long', year: 'numeric' })
    : (payment?.concept || '—');

  const monto = Number(payment?.amount) || 0;
  const statusLabel = portalPaymentStatusLabelPdf(payment?.status, payment?._portalStatus);

  doc.setFont('helvetica', 'bold');
  doc.setFontSize(12);
  doc.text('Detalle del pago', margin, y);
  y += 8;

  doc.autoTable({
    startY: y,
    head: [['Concepto', 'Periodo', 'Monto', 'Estado']],
    body: [[
      payment?.concept || 'Mensualidad',
      mesLabel,
      `$${monto.toLocaleString('es-MX', { minimumFractionDigits: 0 })} MXN`,
      statusLabel,
    ]],
    theme: 'grid',
    headStyles: { fillColor: dark, textColor: 255, fontStyle: 'bold' },
    styles: { fontSize: 10, cellPadding: 4 },
    margin: { left: margin, right: margin },
  });
  y = doc.lastAutoTable.finalY + 10;

  const isPaid = payment?.status === 'pagado';
  const infoRows = [
    ['Fecha límite', due && !Number.isNaN(due.getTime())
      ? due.toLocaleDateString('es-MX', { day: 'numeric', month: 'long', year: 'numeric' }) : '—'],
    ['Fecha de pago', isPaid
      ? formatReceiptDateTime(payment?.paid_at || payment?.created_at)
      : 'Pendiente de pago'],
    ['Método', payment?.receipt_path
      ? 'Transferencia bancaria'
      : (isPaid ? 'Registro administrativo' : 'Por definir')],
    ['Referencia', payment?.transfer_reference || '—'],
    ['Notas', payment?.admin_notes || '—'],
  ];

  doc.autoTable({
    startY: y,
    body: infoRows,
    theme: 'plain',
    styles: { fontSize: 10, cellPadding: 3 },
    columnStyles: { 0: { fontStyle: 'bold', cellWidth: 52 } },
    margin: { left: margin, right: margin },
  });
  y = doc.lastAutoTable.finalY + 12;

  if (receiptData) {
    doc.setFont('helvetica', 'bold');
    doc.setFontSize(11);
    doc.text('Comprobante adjunto', margin, y);
    y += 6;
    try {
      const maxW = pageW - margin * 2;
      const maxH = 70;
      doc.addImage(receiptData, receiptImgFormat(receiptData), margin, y, maxW, maxH, undefined, 'FAST');
      y += maxH + 8;
    } catch {
      doc.setFont('helvetica', 'normal');
      doc.setFontSize(9);
      doc.text('No se pudo incrustar la imagen del comprobante.', margin, y);
      y += 8;
    }
  }

  const footerY = Math.min(y + 10, doc.internal.pageSize.getHeight() - 24);
  doc.setDrawColor(226, 232, 240);
  doc.line(margin, footerY, pageW - margin, footerY);
  doc.setFontSize(8);
  doc.setTextColor(100, 116, 139);
  doc.text(
    `${schoolName} · Documento generado ${new Date().toLocaleString('es-MX')}`,
    pageW / 2,
    footerY + 8,
    { align: 'center' },
  );
  doc.setFont('helvetica', 'bold');
  doc.setTextColor(...dark);
  doc.text('¡Gracias por tu pago!', pageW / 2, footerY + 14, { align: 'center' });

  return doc;
}

function paymentReceiptPdfFilename (alumno, payment) {
  const st = payment?.students || {};
  const due = payment?.due_date ? new Date(payment.due_date) : null;
  const code = alumno?.code || alumno?.id || st.code || 'alumno';
  const tag = due && !Number.isNaN(due.getTime())
    ? `${due.getFullYear()}-${String(due.getMonth() + 1).padStart(2, '0')}`
    : new Date().toISOString().slice(0, 10);
  return `comprobante-pago-${code}-${tag}.pdf`;
}

async function exportPaymentReceiptPdf (alumno, payment, settings = {}, theme = {}) {
  const doc = await buildPaymentReceiptPdfDoc(alumno, payment, settings, theme);
  doc.save(paymentReceiptPdfFilename(alumno, payment));
}

async function exportPaymentReceiptPdfBlob (alumno, payment, settings = {}, theme = {}) {
  const doc = await buildPaymentReceiptPdfDoc(alumno, payment, settings, theme);
  try {
    return doc.output('blob', { compress: true });
  } catch {
    return doc.output('blob');
  }
}

/** Convierte el PDF generado (jsPDF) a JPEG con PDF.js para enviar por WhatsApp. */
async function pdfBlobToJpegBlob (pdfBlob, scale = 2) {
  const pdfjsLib = typeof window !== 'undefined' ? window.pdfjsLib : null;
  if (!pdfjsLib?.getDocument) {
    throw new Error('Falta el visor PDF en la página. Recarga el admin (F5) e intenta de nuevo.');
  }
  if (!pdfjsLib.GlobalWorkerOptions?.workerSrc) {
    pdfjsLib.GlobalWorkerOptions.workerSrc =
      'https://cdnjs.cloudflare.com/ajax/libs/pdf.js/3.11.174/pdf.worker.min.js';
  }
  const data = await pdfBlob.arrayBuffer();
  const pdf = await pdfjsLib.getDocument({ data }).promise;
  const page = await pdf.getPage(1);
  const viewport = page.getViewport({ scale });
  const canvas = document.createElement('canvas');
  canvas.width = Math.floor(viewport.width);
  canvas.height = Math.floor(viewport.height);
  const ctx = canvas.getContext('2d');
  if (!ctx) throw new Error('No se pudo preparar el lienzo para la imagen del comprobante.');
  ctx.fillStyle = '#ffffff';
  ctx.fillRect(0, 0, canvas.width, canvas.height);
  await page.render({ canvasContext: ctx, viewport }).promise;
  return new Promise((resolve, reject) => {
    canvas.toBlob(
      (blob) => (blob
        ? resolve(blob)
        : reject(new Error('No se pudo generar la imagen del comprobante.'))),
      'image/jpeg',
      0.92,
    );
  });
}

/** Imagen JPEG del comprobante (WhatsApp la envía con más fiabilidad que PDF). */
async function exportPaymentReceiptJpegBlob (alumno, payment, settings = {}, theme = {}) {
  const pdfBlob = await exportPaymentReceiptPdfBlob(alumno, payment, settings, theme);
  return pdfBlobToJpegBlob(pdfBlob);
}

function paymentReceiptJpegFilename (alumno, payment) {
  return paymentReceiptPdfFilename(alumno, payment).replace(/\.pdf$/i, '.jpg');
}

function receiptPdfDownloadBlob (blob, filename) {
  const url = URL.createObjectURL(blob);
  const a = document.createElement('a');
  a.href = url;
  a.download = filename;
  a.click();
  URL.revokeObjectURL(url);
}

/**
 * Genera comprobante PDF y lo envía por el bot WhatsApp al teléfono del tutor/alumno.
 */
async function sendPaymentReceiptPdfViaWhatsApp (alumno, payment, opts = {}) {
  if (typeof waSendPaymentReceiptPdfViaBot !== 'function') {
    throw new Error(
      'Servicio WhatsApp no cargado. Ejecuta «npm start» en whatsapp-service/, conéctalo en Admin → WhatsApp y recarga esta página.'
    );
  }

  const settings = opts.settings || await fetchAcademySettings();
  const blob = await exportPaymentReceiptJpegBlob(alumno, payment, settings, opts.theme || {});
  const filename = paymentReceiptJpegFilename(alumno, payment);
  const caption = buildPaymentReceiptWaCaptionShort(alumno, payment);

  const recipients = opts.recipients
    || (typeof getStudentPhoneRecipients === 'function' ? getStudentPhoneRecipients(alumno) : []);
  const phone = opts.phone || recipients[0]?.digits || recipients[0]?.display;
  if (!phone && !recipients.length) {
    throw new Error('Registra el teléfono del tutor o del alumno para enviar por WhatsApp.');
  }

  const targetPhone = phone || recipients[0]?.digits;
  const sendRes = await waSendPaymentReceiptPdfViaBot({
    phone: targetPhone,
    filename,
    caption,
    blob,
    alumno,
    payment,
  });

  if (typeof waLogMessage === 'function') {
    try {
      await waLogMessage({
        student_code: alumno?.id,
        student_name: alumno?.name,
        tutor_name: alumno?.tutor,
        phone: targetPhone,
        message_type: 'Comprobante imagen',
        body_preview: caption.slice(0, 240),
        urgent: false,
      });
    } catch (_) { /* historial opcional */ }
  }

  return {
    mode: 'bot',
    chatId: sendRes?.chatId || sendRes?.sentTo,
    verified: !!sendRes?.verified,
  };
}

const PAYMENT_STATUS_PDF = {
  pagado: 'PAGADO',
  pendiente: 'POR PAGAR',
  en_revision: 'EN REVISIÓN',
  revision: 'EN REVISIÓN',
  vencido: 'VENCIDO',
  rechazado: 'RECHAZADO',
  urgente: 'URGENTE',
  confirmado: 'CONFIRMADO',
  entregado: 'ENTREGADO',
};

function paymentStatusLabelPdf (status) {
  const s = String(status || '').toLowerCase();
  return PAYMENT_STATUS_PDF[s] || s.toUpperCase() || '—';
}

/** Etiqueta PDF según estado real del mes (portal), nunca «PAGADO» si está pendiente. */
function portalPaymentStatusLabelPdf (dbStatus, portalStatus) {
  const ps = String(portalStatus || '').toLowerCase();
  if (ps && ps !== 'pagado') return paymentStatusLabelPdf(ps === 'revision' ? 'en_revision' : ps);
  return paymentStatusLabelPdf(dbStatus);
}

function expedienteRowToPayment (row, settings = {}) {
  const raw = row?.raw || {};
  const s = normalizeAcademySettings?.(settings) || settings || {};
  let amount = row?.hideAmount ? (Number(s.monthly_fee) || 0) : (Number(row.a) || Number(raw.amount) || 0);
  if (raw.status === 'pagado') amount = Number(raw.amount) > 0 ? Number(raw.amount) : amount;
  const portalSt = row?.s || raw.status;
  const dbSt = raw.status === 'en_revision' ? 'en_revision' : (portalSt === 'revision' ? 'en_revision' : portalSt);
  return {
    id: raw.id || row?.id,
    concept: raw.concept || row?.c || 'Mensualidad',
    amount,
    due_date: raw.due_date,
    paid_at: raw.paid_at,
    status: dbSt === 'pagado' ? 'pagado' : dbSt,
    _portalStatus: portalSt,
    receipt_path: raw.receipt_path,
    transfer_reference: raw.transfer_reference,
    admin_notes: raw.admin_notes,
    created_at: raw.created_at,
  };
}

/**
 * Convierte un mes del calendario de pagos (portal) al formato expediente para PDF.
 */
function portalMonthSlotToExpedienteRow (slot, settings = {}) {
  const raw = slot?.rawPayment || {};
  const y = slot?.calendarYear ?? new Date().getFullYear();
  const mi = slot?.monthIndex ?? 0;
  const s = typeof normalizeAcademySettings === 'function' ? normalizeAcademySettings(settings) : settings;
  const due = raw.due_date || (typeof dueDateForBillingMonth === 'function'
    ? dueDateForBillingMonth(s, y, mi)
    : new Date(y, mi, 5).toISOString());
  const mesLabel = slot?.name
    ? `${String(slot.name).charAt(0).toUpperCase()}${String(slot.name).slice(1)} ${y}`
    : new Date(y, mi, 1).toLocaleDateString('es-MX', { month: 'long', year: 'numeric' });
  const concept = slot?.concept || raw.concept
    || (typeof mensualidadConceptForMonth === 'function' ? mensualidadConceptForMonth(y, mi) : 'Mensualidad');
  const fullRaw = raw.id ? { ...raw, status: raw.status || 'pagado' } : {
    id: slot?.paymentId,
    concept,
    amount: slot?.amt ?? raw.amount,
    due_date: due,
    paid_at: slot?.paidAt || raw.paid_at || null,
    status: 'pagado',
    receipt_path: slot?.receiptPath || raw.receipt_path || null,
    transfer_reference: slot?.transferRef || raw.transfer_reference || null,
    admin_notes: slot?.adminNotes || raw.admin_notes || null,
    created_at: raw.created_at || null,
  };
  return {
    m: mesLabel,
    c: fullRaw.concept || concept,
    a: Number(slot?.amt) || Number(fullRaw.amount) || 0,
    s: 'pagado',
    id: fullRaw.id || slot?.paymentId,
    hideAmount: !!slot?.hideAmount,
    raw: fullRaw,
  };
}

/**
 * Comprobante PDF de un mes del expediente (portal alumno).
 */
async function exportPortalMonthReceiptPdf (alumno, expedienteRow, settings = {}, theme = {}) {
  const payment = expedienteRowToPayment(expedienteRow, settings);
  await exportPaymentReceiptPdf(alumno, payment, settings, theme);
}

/** Comprobante PDF al abrir un mes pagado en el calendario del portal. */
async function exportPortalCalendarMonthReceiptPdf (alumno, monthSlot, settings = {}, theme = {}) {
  await exportPortalMonthReceiptPdf(alumno, portalMonthSlotToExpedienteRow(monthSlot, settings), settings, theme);
}

const ORDER_STATUS_PDF = {
  pendiente: 'POR PAGAR',
  en_revision: 'EN REVISIÓN',
  rechazado: 'RECHAZADO',
  confirmado: 'CONFIRMADO',
  entregado: 'ENTREGADO',
};

function orderStatusLabelPdf (status) {
  return ORDER_STATUS_PDF[String(status || '').toLowerCase()] || paymentStatusLabelPdf(status);
}

function orderProductSummary (order) {
  if (order?.p) return order.p;
  if (order?.product_summary) return order.product_summary;
  const items = order?.order_items || [];
  if (!items.length) return 'Pedido tienda';
  return items.map((i) => `${i.product_name}${i.size ? ` (${i.size})` : ''} ×${i.qty}`).join(', ');
}

/** Normaliza fila de historial admin o portal para PDF / WhatsApp de compra. */
function orderRowToPortalOrder (row) {
  if (!row) return null;
  const items = row.order_items || [];
  const summary = orderProductSummary(row);
  const num = row.order_number || row.n || '';
  const orderNumber = num.startsWith('#') ? num : (num ? `#${num}` : '');
  return {
    id: row.id,
    orderId: row.id,
    p: summary,
    product_summary: summary,
    d: row.d || (row.created_at
      ? new Date(row.created_at).toLocaleDateString('es-MX', { day: 'numeric', month: 'short', year: 'numeric' })
      : '—'),
    n: orderNumber,
    order_number: row.order_number || num.replace(/^#/, ''),
    a: Number(row.a ?? row.total) || 0,
    total: Number(row.total ?? row.a) || 0,
    s: row.s || row.status,
    status: row.status || row.s,
    transferRef: row.transferRef || row.transfer_reference || '',
    transfer_reference: row.transfer_reference || row.transferRef || '',
    adminNotes: row.adminNotes || row.admin_notes || null,
    receipt_path: row.receipt_path || null,
    created_at: row.created_at,
    order_items: items,
  };
}

function buildStoreOrderReceiptWaCaptionShort (alumno, order) {
  const tutor = alumno?.tutor && alumno.tutor !== '—' ? alumno.tutor : 'familia';
  const orderNum = String(order?.n || order?.order_number || '').replace(/^#/, '') || 'tienda';
  return `Hola ${tutor}, tu comprobante de compra (orden ${orderNum}) — Tecos Elite VOLLEYBALL`;
}

function storeOrderReceiptPdfFilename (alumno, order) {
  const code = alumno?.code || alumno?.id || 'alumno';
  const tag = String(order?.n || order?.order_number || 'tienda').replace(/[^\w#-]+/g, '-').replace(/^#/, '');
  return `comprobante-compra-${code}-${tag}.pdf`;
}

function storeOrderReceiptJpegFilename (alumno, order) {
  return storeOrderReceiptPdfFilename(alumno, order).replace(/\.pdf$/i, '.jpg');
}

/**
 * Comprobante PDF de compra en tienda (portal alumno o historial admin).
 */
async function buildStoreOrderReceiptPdfDoc (alumno, order, settings = {}, theme = {}) {
  const { jsPDF } = window.jspdf || {};
  if (!jsPDF) throw new Error('Librería PDF no disponible');

  const schoolName = settings?.venue_name || RECEIPT_SCHOOL_NAME;
  const dark = [15, 23, 42];
  const margin = 16;
  const statusLabel = orderStatusLabelPdf(order?.s || order?.status);
  const monto = Number(order?.a ?? order?.total) || 0;
  const orderNum = (order?.n || order?.order_number || '').replace(/^#/, '');
  const productLabel = orderProductSummary(order);
  const ref = order?.transferRef || order?.transfer_reference || orderNum || '—';

  const [logoData, photoData, receiptDataRaw] = await Promise.all([
    loadReceiptImage(RECEIPT_LOGO_PATH),
    loadReceiptImage(alumno?.photoUrl),
    order?.receipt_path && typeof getReceiptSignedUrl === 'function'
      ? getReceiptSignedUrl(order.receipt_path, 3600).then(u => loadReceiptImage(u)).catch(() => null)
      : Promise.resolve(null),
  ]);
  const receiptData = receiptDataRaw
    ? await compressReceiptImageForPdf(receiptDataRaw)
    : null;

  const doc = new jsPDF({ unit: 'mm', format: 'a4' });
  const pageW = doc.internal.pageSize.getWidth();
  let y = drawReceiptPdfBanner(doc, {
    schoolName,
    titleLine: 'Comprobante de compra — tienda',
    rightTop: orderNum ? `Orden ${orderNum}` : 'Orden tienda',
    rightBottom: formatReceiptDateTime(order?.created_at || new Date().toISOString()),
    logoData,
    margin,
    pageW,
  });

  y = drawReceiptStudentCard(doc, alumno, photoData, y, margin, pageW);

  doc.setFont('helvetica', 'bold');
  doc.setFontSize(12);
  doc.setTextColor(30, 41, 59);
  doc.text('Detalle de la compra', margin, y);
  y += 8;

  doc.autoTable({
    startY: y,
    head: [['Producto / orden', 'Referencia', 'Monto', 'Estado']],
    body: [[productLabel, ref, `$${monto.toLocaleString('es-MX', { minimumFractionDigits: 0 })} MXN`, statusLabel]],
    theme: 'grid',
    headStyles: { fillColor: dark, textColor: 255, fontStyle: 'bold' },
    styles: { fontSize: 10, cellPadding: 4 },
    margin: { left: margin, right: margin },
  });
  y = doc.lastAutoTable.finalY + 10;

  const infoRows = [
    ['Número de orden', orderNum || '—'],
    ['Fecha del pedido', order?.d || (order?.created_at
      ? new Date(order.created_at).toLocaleDateString('es-MX', { day: 'numeric', month: 'long', year: 'numeric' })
      : '—')],
    ['Estado del pago', statusLabel],
    ['Método', order?.receipt_path || receiptData ? 'Transferencia bancaria' : 'Pendiente de comprobante'],
  ];
  if (order?.adminNotes || order?.admin_notes) {
    infoRows.push(['Notas administración', order.adminNotes || order.admin_notes]);
  }

  doc.autoTable({
    startY: y,
    body: infoRows,
    theme: 'plain',
    styles: { fontSize: 10, cellPadding: 3 },
    columnStyles: { 0: { fontStyle: 'bold', cellWidth: 52 } },
    margin: { left: margin, right: margin },
  });
  y = doc.lastAutoTable.finalY + 12;

  if (receiptData) {
    doc.setFont('helvetica', 'bold');
    doc.setFontSize(11);
    doc.text('Comprobante de transferencia adjunto', margin, y);
    y += 6;
    try {
      const maxW = pageW - margin * 2;
      doc.addImage(receiptData, receiptImgFormat(receiptData), margin, y, maxW, 70, undefined, 'FAST');
      y += 78;
    } catch { /* skip */ }
  } else if (['POR PAGAR', 'RECHAZADO'].includes(statusLabel)) {
    doc.setFillColor(254, 243, 199);
    doc.roundedRect(margin, y, pageW - margin * 2, 16, 2, 2, 'F');
    doc.setFont('helvetica', 'bold');
    doc.setFontSize(10);
    doc.setTextColor(120, 53, 15);
    doc.text(
      statusLabel === 'RECHAZADO'
        ? 'Comprobante rechazado — sube un nuevo comprobante desde Mis compras.'
        : 'Pago pendiente — realiza la transferencia y sube tu comprobante.',
      pageW / 2,
      y + 10,
      { align: 'center' },
    );
    y += 22;
  }

  drawReceiptPdfFooter(doc, schoolName, y, margin, pageW);
  return doc;
}

async function exportStoreOrderReceiptPdf (alumno, order, settings = {}, theme = {}) {
  const doc = await buildStoreOrderReceiptPdfDoc(alumno, order, settings, theme);
  doc.save(storeOrderReceiptPdfFilename(alumno, order));
}

async function exportStoreOrderReceiptPdfBlob (alumno, order, settings = {}, theme = {}) {
  const doc = await buildStoreOrderReceiptPdfDoc(alumno, order, settings, theme);
  try {
    return doc.output('blob', { compress: true });
  } catch {
    return doc.output('blob');
  }
}

async function exportStoreOrderReceiptJpegBlob (alumno, order, settings = {}, theme = {}) {
  const pdfBlob = await exportStoreOrderReceiptPdfBlob(alumno, order, settings, theme);
  return pdfBlobToJpegBlob(pdfBlob);
}

/**
 * Genera comprobante de compra y lo envía por el bot WhatsApp (mismo flujo que mensualidad).
 */
async function sendStoreOrderReceiptPdfViaWhatsApp (alumno, order, opts = {}) {
  if (typeof waSendPaymentReceiptPdfViaBot !== 'function') {
    throw new Error(
      'Servicio WhatsApp no cargado. Ejecuta «npm start» en whatsapp-service/, conéctalo en Admin → WhatsApp y recarga esta página.'
    );
  }

  const portalOrder = orderRowToPortalOrder(order) || order;
  const settings = opts.settings || await fetchAcademySettings();
  const blob = await exportStoreOrderReceiptJpegBlob(alumno, portalOrder, settings, opts.theme || {});
  const filename = storeOrderReceiptJpegFilename(alumno, portalOrder);
  const caption = buildStoreOrderReceiptWaCaptionShort(alumno, portalOrder);

  const recipients = opts.recipients
    || (typeof getStudentPhoneRecipients === 'function' ? getStudentPhoneRecipients(alumno) : []);
  const phone = opts.phone || portalOrder?.phone || recipients[0]?.digits || recipients[0]?.display;
  if (!phone && !recipients.length) {
    throw new Error('Registra el teléfono del tutor o del alumno para enviar por WhatsApp.');
  }

  const targetPhone = phone || recipients[0]?.digits;
  const sendRes = await waSendPaymentReceiptPdfViaBot({
    phone: targetPhone,
    filename,
    caption,
    blob,
  });

  if (typeof waLogMessage === 'function') {
    try {
      await waLogMessage({
        student_code: alumno?.id,
        student_name: alumno?.name,
        tutor_name: alumno?.tutor,
        phone: targetPhone,
        message_type: 'Comprobante compra',
        body_preview: caption.slice(0, 240),
        urgent: false,
      });
    } catch (_) { /* historial opcional */ }
  }

  return {
    mode: 'bot',
    chatId: sendRes?.chatId || sendRes?.sentTo,
    verified: !!sendRes?.verified,
  };
}

function drawReceiptPdfBanner (doc, { schoolName, titleLine, rightTop, rightBottom, logoData, margin, pageW }) {
  const dark = [15, 23, 42];
  doc.setFillColor(...dark);
  doc.rect(0, 0, pageW, 42, 'F');
  doc.setFillColor(234, 179, 8);
  doc.rect(0, 40, pageW, 2, 'F');
  const logoSz = receiptPdfLogoSize(28);
  addReceiptPdfLogoImage(doc, logoData, margin, 7, logoSz.w, logoSz.h);
  const bannerTextX = margin + receiptPdfLogoTextOffset(logoSz, !!logoData);
  doc.setTextColor(255, 255, 255);
  doc.setFont('helvetica', 'bold');
  doc.setFontSize(20);
  doc.text(schoolName, bannerTextX, 16);
  doc.setFontSize(11);
  doc.setFont('helvetica', 'normal');
  doc.text(titleLine, bannerTextX, 24);
  doc.setFontSize(9);
  if (rightTop) doc.text(rightTop, pageW - margin, 16, { align: 'right' });
  if (rightBottom) doc.text(rightBottom, pageW - margin, 24, { align: 'right' });
  return 52;
}

function drawReceiptStudentCard (doc, alumno, photoData, y, margin, pageW) {
  const lightBg = [248, 250, 252];
  doc.setTextColor(30, 41, 59);
  doc.setFillColor(...lightBg);
  doc.rect(margin, y, pageW - margin * 2, 38, 'F');
  if (photoData) {
    try {
      doc.addImage(photoData, receiptImgFormat(photoData), margin + 4, y + 4, 30, 30);
    } catch { /* skip */ }
  }
  doc.setFont('helvetica', 'bold');
  doc.setFontSize(14);
  doc.text(alumno?.name || '—', margin + 40, y + 12);
  doc.setFont('helvetica', 'normal');
  doc.setFontSize(10);
  doc.text(`ID: ${alumno?.code || alumno?.id || '—'}  ·  ${alumno?.cat || '—'}`, margin + 40, y + 20);
  doc.text(`Tutor: ${alumno?.tutor || '—'}`, margin + 40, y + 27);
  doc.text(`Tel. tutor: ${alumno?.phone || '—'}`, margin + 40, y + 33);
  return y + 46;
}

function drawReceiptPdfFooter (doc, schoolName, y, margin, pageW) {
  const dark = [15, 23, 42];
  const pageH = doc.internal.pageSize.getHeight();
  const footerY = Math.max(y + 8, pageH - 28);
  doc.setDrawColor(226, 232, 240);
  doc.line(margin, footerY, pageW - margin, footerY);
  doc.setFontSize(8);
  doc.setTextColor(100, 116, 139);
  doc.setFont('helvetica', 'normal');
  doc.text(
    `${schoolName} · Documento generado ${new Date().toLocaleString('es-MX')}`,
    pageW / 2,
    footerY + 8,
    { align: 'center' },
  );
  doc.setFont('helvetica', 'bold');
  doc.setTextColor(...dark);
  doc.text('TECOS ELITE VOLLEYBALL', pageW / 2, footerY + 14, { align: 'center' });
}

async function fetchStudentPaymentsRaw (studentUuid) {
  const sb = typeof getSupabase === 'function' ? getSupabase() : null;
  if (!sb || !studentUuid) return [];
  const { data, error } = await sb.from('payments')
    .select('id, concept, amount, due_date, paid_at, status, receipt_path, transfer_reference, created_at, admin_notes')
    .eq('student_id', studentUuid)
    .order('due_date', { ascending: true });
  if (error) throw error;
  return data || [];
}

/**
 * Historial de pagos del alumno — mismo diseño que comprobante individual.
 * @param {object} alumno — mapStudentRow
 * @param {{ payments?: array, settings?: object, year?: number }} opts
 */
async function exportStudentPaymentHistoryPdf (alumno, opts = {}, theme = {}) {
  const { jsPDF } = window.jspdf || {};
  if (!jsPDF) throw new Error('Librería PDF no disponible');

  const settings = opts.settings || {};
  const schoolName = settings?.venue_name || RECEIPT_SCHOOL_NAME;
  const year = opts.year || new Date().getFullYear();
  const dark = [15, 23, 42];
  const margin = 16;

  const payments = opts.payments?.length
    ? opts.payments
    : await fetchStudentPaymentsRaw(alumno?._uuid);

  const [logoData, photoData] = await Promise.all([
    loadReceiptImage(RECEIPT_LOGO_PATH),
    loadReceiptImage(alumno?.photoUrl),
  ]);

  const doc = new jsPDF({ unit: 'mm', format: 'a4' });
  const pageW = doc.internal.pageSize.getWidth();
  let y = drawReceiptPdfBanner(doc, {
    schoolName,
    titleLine: 'Historial de pagos',
    rightTop: `Alumno ${alumno?.code || alumno?.id || '—'}`,
    rightBottom: `Año ${year}`,
    logoData,
    margin,
    pageW,
  });

  y = drawReceiptStudentCard(doc, alumno, photoData, y, margin, pageW);

  const pagados = payments.filter(p => p.status === 'pagado');
  const pendientes = payments.filter(p => ['pendiente', 'vencido', 'en_revision'].includes(p.status));
  const totalPagado = pagados.reduce((s, p) => s + (Number(p.amount) || 0), 0);
  const adeudoMes = Number(alumno?.adeudo) || 0;

  doc.setFont('helvetica', 'bold');
  doc.setFontSize(12);
  doc.text('Resumen', margin, y);
  y += 8;

  doc.autoTable({
    startY: y,
    body: [
      ['Mensualidades pagadas', String(pagados.length)],
      ['Registros pendientes / en revisión', String(pendientes.length)],
      ['Total pagado (registrado)', `$${totalPagado.toLocaleString('es-MX', { minimumFractionDigits: 0 })} MXN`],
      ['Adeudo mes en curso', adeudoMes > 0 ? `$${adeudoMes.toLocaleString('es-MX')} MXN` : 'Al corriente'],
      ['Tarifa base configurada', `$${Number(settings.monthly_fee || 0).toLocaleString('es-MX')} MXN`],
    ],
    theme: 'plain',
    styles: { fontSize: 10, cellPadding: 3 },
    columnStyles: { 0: { fontStyle: 'bold', cellWidth: 72 } },
    margin: { left: margin, right: margin },
  });
  y = doc.lastAutoTable.finalY + 12;

  doc.setFont('helvetica', 'bold');
  doc.setFontSize(12);
  doc.text('Detalle de movimientos', margin, y);
  y += 8;

  if (!payments.length) {
    doc.setFont('helvetica', 'normal');
    doc.setFontSize(10);
    doc.text('Sin pagos registrados en el sistema.', margin, y);
    y += 10;
  } else {
    const payBody = payments.map((p) => {
      const due = p.due_date ? new Date(p.due_date) : null;
      const periodo = due && !Number.isNaN(due.getTime())
        ? due.toLocaleDateString('es-MX', { month: 'short', year: 'numeric' })
        : '—';
      const vence = due && !Number.isNaN(due.getTime())
        ? due.toLocaleDateString('es-MX', { day: 'numeric', month: 'short', year: 'numeric' })
        : '—';
      const pagado = p.status === 'pagado' && p.paid_at
        ? formatReceiptDateTime(p.paid_at)
        : (p.status === 'pagado' ? '—' : 'Pendiente');
      const metodo = p.receipt_path ? 'Transferencia' : (p.status === 'pagado' ? 'Admin / efectivo' : '—');
      return [
        periodo,
        p.concept || 'Mensualidad',
        `$${(Number(p.amount) || 0).toLocaleString('es-MX')}`,
        vence,
        pagado,
        paymentStatusLabelPdf(p.status),
        metodo,
      ];
    });

    doc.autoTable({
      startY: y,
      head: [['Periodo', 'Concepto', 'Monto', 'Vence', 'Fecha pago', 'Estado', 'Método']],
      body: payBody,
      theme: 'grid',
      headStyles: { fillColor: dark, textColor: 255, fontStyle: 'bold' },
      styles: { fontSize: 8, cellPadding: 2.5 },
      margin: { left: margin, right: margin },
      columnStyles: { 4: { cellWidth: 38 } },
    });
    y = doc.lastAutoTable.finalY + 8;
  }

  drawReceiptPdfFooter(doc, schoolName, y, margin, pageW);

  const code = alumno?.code || alumno?.id || 'alumno';
  doc.save(`historial-pagos-${code}-${year}.pdf`);
}

Object.assign(window, {
  buildPaymentReceiptPdfDoc,
  exportPaymentReceiptPdf,
  exportPaymentReceiptPdfBlob,
  exportPaymentReceiptJpegBlob,
  pdfBlobToJpegBlob,
  paymentReceiptPdfFilename,
  paymentReceiptJpegFilename,
  buildPaymentReceiptWaCaption,
  buildPaymentReceiptWaCaptionShort,
  sendPaymentReceiptPdfViaWhatsApp,
  compressReceiptImageForPdf,
  exportPortalMonthReceiptPdf,
  exportPortalCalendarMonthReceiptPdf,
  portalMonthSlotToExpedienteRow,
  buildStoreOrderReceiptPdfDoc,
  exportStoreOrderReceiptPdf,
  exportStoreOrderReceiptPdfBlob,
  exportStoreOrderReceiptJpegBlob,
  storeOrderReceiptPdfFilename,
  storeOrderReceiptJpegFilename,
  buildStoreOrderReceiptWaCaptionShort,
  sendStoreOrderReceiptPdfViaWhatsApp,
  orderRowToPortalOrder,
  orderProductSummary,
  exportStudentPaymentHistoryPdf,
  expedienteRowToPayment,
  loadReceiptImage,
  loadTecosLogoDataUrl,
  addReceiptPdfLogoImage,
  drawTecosPdfReportHeader,
  receiptImgFormat,
  formatReceiptDateTime,
  paymentStatusLabelPdf,
  portalPaymentStatusLabelPdf,
  orderStatusLabelPdf,
  drawReceiptPdfBanner,
  drawReceiptStudentCard,
  drawReceiptPdfFooter,
  RECEIPT_LOGO_PATH,
});
