
import { jsPDF } from 'jspdf';
import QRCode from 'qrcode';
import { getHighQualityPdfSettings, loadImageHighQuality, applyHighQualityImageToPdf } from './pdfQualityUtils';
import { loadImage } from './pdfGenerationUtils';
import { formatDateSafe } from '../lib/dateUtils';
import { cleanText } from '@/lib/pdfTextUtils';
import { format } from 'date-fns';
import { formatPhotoTimestamp, getPhotoDateFromUrl } from './exifUtils';

// Safe yield helper to prevent main thread blocking and ensure it resolves
const safeYieldToMain = () => new Promise(resolve => setTimeout(resolve, 0));

// Draws a diagonal red "RECOMMENDED FOR FINING" stamp across the current page
const drawRecommendedForFiningStamp = (doc) => {
  try {
    const pageWidth = doc.internal.pageSize.getWidth();
    // Place stamp near the top-right of the page so it doesn't cover
    // photo evidence in the middle/bottom of the page.
    const stampW = Math.min(pageWidth - 160, 300);
    const stampH = 56;
    const cx = pageWidth - (stampW / 2) - 40;
    const cy = 110;

    doc.saveGraphicsState && doc.saveGraphicsState();
    // Lighter opacity so underlying content remains fully readable
    if (doc.setGState && doc.GState) {
      try { doc.setGState(new doc.GState({ opacity: 0.55 })); } catch (e) {}
    }

    // Rotate around center (jsPDF rotates around the x,y origin)
    const angle = -14;
    const rad = (angle * Math.PI) / 180;
    const cos = Math.cos(rad);
    const sin = Math.sin(rad);

    // Compute rotated rect corner so the rect is centered at (cx, cy)
    const rx = cx - (stampW / 2) * cos + (stampH / 2) * sin;
    const ry = cy - (stampW / 2) * sin - (stampH / 2) * cos;

    doc.setDrawColor(200, 0, 0);
    doc.setTextColor(200, 0, 0);
    doc.setLineWidth(2.5);
    doc.roundedRect(rx, ry, stampW, stampH, 10, 10, 'S', angle);

    doc.setFont('helvetica', 'bold');
    doc.setFontSize(20);
    // Text position: center of stamp, jsPDF text rotates around the text origin
    doc.text('RECOMMENDED FOR FINING', cx, cy + 7, { align: 'center', angle: -angle });

    doc.restoreGraphicsState && doc.restoreGraphicsState();
    // Reset state
    doc.setTextColor(0, 0, 0);
    doc.setDrawColor(0, 0, 0);
    doc.setLineWidth(1);
  } catch (e) {
    console.warn('Failed to draw recommended-for-fining stamp', e);
  }
};

const isRecommendedForFining = (status) =>
  String(status || '').toLowerCase().replace(/[\s-]+/g, '_') === 'recommended_for_fining';

// Timeout wrapper for promises (prevents hanging PDF generation)
const withTimeout = (promise, ms, errorMsg = 'Timeout') => {
  return Promise.race([
    promise,
    new Promise((_, reject) => setTimeout(() => reject(new Error(errorMsg)), ms))
  ]);
};

// Helper to safely convert values to string for PDF rendering
const safeStr = (val) => {
  if (val === null || val === undefined) return '';
  return cleanText(String(val));
};

const normalizeTimelineText = (val) => {
  const text = safeStr(val);
  return text.split('\n').map((line) => {
    const singleLetterMatches = line.match(/(?:^|\s)[A-Za-z](?=\s)/g) || [];
    if (singleLetterMatches.length < 8) return line;
    return line.replace(/\b(?:[A-Za-z]\s+){2,}[A-Za-z]\b/g, (match) => match.replace(/\s+/g, ''));
  }).join('\n');
};

// Helper to extract effective fields with robust null checking
const getEffectiveViolationData = (violation) => {
  if (!violation) return null;

  try {
    const history = Array.isArray(violation.notice_history) ? violation.notice_history : [];
    let latest = null;
    if (history.length > 0) {
        latest = history.reduce((a, b) => {
            const dateA = a.date ? new Date(a.date) : new Date(0);
            const dateB = b.date ? new Date(b.date) : new Date(0);
            return dateB > dateA ? b : a;
        });
    }
    
    const po = violation.property_owners || violation.owners || {};
    
    // Resolve owner name: prefer enriched allOwnerNames, then joined owner fields
    let ownerName = 'Unknown Owner';
    if (violation.allOwnerNames) {
      ownerName = violation.allOwnerNames;
    } else if (po.first_name || po.last_name) {
      ownerName = `${po.first_name || ''} ${po.last_name || ''}`.trim() || 'Unknown Owner';
    } else if (po.owner_name) {
      ownerName = po.owner_name;
    }
    
    let addressLines = [];
    if (po.mail_1) addressLines.push(po.mail_1);
    if (po.mail_2) addressLines.push(po.mail_2);
    if (po.mail_3) addressLines.push(po.mail_3);
    if (po.mail_4) addressLines.push(po.mail_4);

    if (addressLines.length === 0 && po.mailing_address) {
        addressLines = String(po.mailing_address).split('\n').map(l => l.trim()).filter(Boolean);
    }
    if (addressLines.length === 0 && (po.property_address || violation.address)) {
        const addr = po.property_address || violation.address;
        addressLines = String(addr).split('\n').map(l => l.trim()).filter(Boolean);
    }
    if (addressLines.length === 0) {
        addressLines = ['Unknown Address'];
    }

    return {
      raw_id: violation.id, // Retain raw UUID for QR code
      id: violation.id ? `V-${String(violation.id).substring(0, 8).toUpperCase()}` : 'N/A',
      article: safeStr(violation.article || violation.article_section || latest?.article || latest?.article_section || 'N/A'),
      section: safeStr(violation.section || latest?.section || 'N/A'),
      citation: safeStr(violation.citation || violation.citation_text || latest?.citation || latest?.citation_text || 'N/A'),
      requested_action: safeStr(violation.requested_action || latest?.requested_action || 'N/A'),
      status: safeStr(violation.status || 'Open'),
      violation_type: safeStr(violation.violation_type || 'General Violation'),
      addressLines: addressLines.map(safeStr),
      owner: safeStr(ownerName),
      address: safeStr(po.property_address || violation.address || 'Unknown'),
      violation_date: violation.violation_date || violation.created_at,
      due_date: violation.due_date,
      priority: safeStr(violation.priority || 'Normal'),
      assigned_to: safeStr(violation.assigned_user?.full_name || 'Unassigned'),
      tags: Array.isArray(violation.tags) ? violation.tags.join(', ') : safeStr(violation.tags || 'None'),
      notice_level: safeStr(violation.notice_level || 'N/A'),
      certified_mail: safeStr(violation.certified_mail || latest?.certified_mail || ''),
      description: safeStr(violation.description || violation.notes || ''),
      photo_urls: Array.isArray(violation.photo_urls) ? violation.photo_urls : (violation.photo_url ? [{url: violation.photo_url}] : []),
      timeline_entries: Array.isArray(violation.timeline_entries) ? violation.timeline_entries : []
    };
  } catch (err) {
    console.error("Error parsing violation data:", err);
    return {
        id: 'ERROR',
        raw_id: null,
        owner: 'Error parsing data',
        addressLines: ['Data corrupted'],
        timeline_entries: [],
        photo_urls: []
    };
  }
};

const drawTimelineEntries = (doc, entries, startX, startY, width) => {
    if (!entries || !Array.isArray(entries) || entries.length === 0) return startY;
    
    let currentY = startY;
    if (isNaN(currentY) || currentY < 0) currentY = 30; // Failsafe
    
    const pageHeight = doc.internal.pageSize.getHeight();
    const margin = 30;
    const safeWidth = Math.max(50, width - 20); // Prevent zero/negative width crash

    try {
        // Timeline Header: 10pt bold
        doc.setFontSize(10); doc.setFont('helvetica', 'bold'); doc.setTextColor('#34495e');
        doc.text('Activity History', startX, currentY);
        currentY += 12;

        const sortedEntries = [...entries].sort((a, b) => new Date(b.date || b.created_at || 0) - new Date(a.date || a.created_at || 0));

        for (let entryIdx = 0; entryIdx < sortedEntries.length; entryIdx++) {
            const entry = sortedEntries[entryIdx];
            if (currentY > pageHeight - margin - 20) { 
                doc.addPage(); 
                currentY = margin; 
            }
            
            const dateStr = formatDateSafe(entry.date || entry.created_at);
            const status = safeStr(entry.status || 'Update');
            const dotX = startX + 4;
            const lineTop = currentY + 2;
            const contentX = startX + 15;
            currentY += 10; // top padding for entry

            // Date: 9pt bold
            doc.setFontSize(9); doc.setFont('helvetica', 'bold'); doc.setTextColor('#1e293b');
            doc.text(dateStr, contentX, currentY);

            // Status: 9pt normal
            doc.setFontSize(9); doc.setFont('helvetica', 'normal'); doc.setTextColor('#64748b');
            doc.text(status.toUpperCase(), contentX + doc.getTextWidth(dateStr) + 8, currentY);
            currentY += 13;

            // Description: 9pt normal
            doc.setFontSize(9); doc.setTextColor('#334155');
            const descText = normalizeTimelineText(entry.description || 'No description');
            const descLines = doc.splitTextToSize(descText, safeWidth);
            for (let j = 0; j < descLines.length; j++) {
                if (currentY > pageHeight - margin - 10) {
                    doc.setDrawColor('#e2e8f0'); doc.setLineWidth(1); doc.line(dotX, lineTop, dotX, currentY);
                    doc.addPage();
                    currentY = margin;
                }
                doc.text(descLines[j], contentX, currentY);
                currentY += 12;
            }

            // Draw connecting line & dot
            const lineBottom = currentY + 4;
            doc.setDrawColor('#e2e8f0'); doc.setLineWidth(1); doc.line(dotX, lineTop, dotX, lineBottom);
            doc.setFillColor('#3b82f6'); doc.circle(dotX, lineTop + 2, 2, 'F');

            currentY += 8;

            // Divider between entries (but not after the last one)
            if (entryIdx < sortedEntries.length - 1) {
                doc.setDrawColor('#f1f5f9'); doc.setLineWidth(0.5);
                doc.line(startX, currentY, startX + safeWidth, currentY);
                currentY += 10;
            }
        }
    } catch (e) {
        console.error("Error drawing timeline:", e);
        doc.text("Error rendering timeline", startX, currentY + 10);
        return currentY + 20;
    }
    return currentY;
};

export const generateViolationStatusPDF = async (violations, settings = {}, onProgress, abortSignal) => {
  if (!violations || !Array.isArray(violations)) {
      throw new Error("Invalid violations data provided");
  }

  const { pageSize = 'letter', clientName = 'HOA Report', includeTimeline = false, includeImages = true, statusFilter = 'all' } = settings;
  
  let doc;
  try {
    doc = new jsPDF(getHighQualityPdfSettings('portrait', 'pt', pageSize));
    doc.setCharSpace(0);
  } catch (e) {
    console.error("Failed to initialize jsPDF:", e);
    throw new Error("Failed to initialize PDF document");
  }
  
  const pageWidth = doc.internal.pageSize.getWidth();
  const pageHeight = doc.internal.pageSize.getHeight();
  const margin = 30;
  const contentWidth = pageWidth - (margin * 2);
  const colWidth = Math.max(50, (contentWidth / 2) - 10);

  const colors = { headerBg: '#2c3e50', headerText: '#ffffff', label: '#34495e', subHeader: '#34495e', text: '#2c3e50', border: '#ecf0f1' };
  const fonts = { 
      header: { name: 'helvetica', style: 'bold', size: 16 }, 
      subHeader: { name: 'helvetica', style: 'bold', size: 11 }, 
      label: { name: 'helvetica', style: 'bold', size: 9 }, 
      value: { name: 'helvetica', style: 'normal', size: 9 }, 
      footer: { name: 'helvetica', style: 'normal', size: 8 } 
  };

  const drawField = (label, value, x, y, width) => {
    try {
        const safeValue = safeStr(value || 'N/A');
        const safeWidth = Math.max(50, width);
        let labelY = y + 6; 
        
        doc.setFont(fonts.label.name, fonts.label.style); doc.setFontSize(fonts.label.size); doc.setTextColor(colors.label);
        doc.text(cleanText(label), x, labelY);
        
        doc.setFont(fonts.value.name, fonts.value.style); doc.setFontSize(fonts.value.size); doc.setTextColor(colors.text);
        const splitValue = doc.splitTextToSize(safeValue, safeWidth);
        for (let j = 0; j < splitValue.length; j++) {
            doc.text(splitValue[j], x, labelY + 11 + (j * 11));
        }
        return 6 + (splitValue.length * 11) + 10;
    } catch (e) {
        console.error(`Error drawing field ${label}:`, e);
        return 20; 
    }
  };

  const filteredViolations = statusFilter === 'all' 
    ? violations 
    : violations.filter(v => safeStr(v.status).toLowerCase() === statusFilter.toLowerCase());

  const total = filteredViolations.length;
  if (total === 0) {
      doc.text("No violations found matching the criteria.", margin, margin + 20);
      return doc;
  }

  const CHUNK_SIZE = 3; 
  let errorCount = 0;
  const MAX_ERRORS = 20;

  for (let i = 0; i < total; i++) {
    if (abortSignal?.aborted) throw new Error('Report generation cancelled');

    if (i > 0 && i % CHUNK_SIZE === 0) {
        if (onProgress) onProgress(Math.round((i / total) * 100));
        await safeYieldToMain();
    }

    try {
        if (i > 0) doc.addPage();

        const vData = getEffectiveViolationData(filteredViolations[i]);
        if (!vData) continue; 

        let currentY = margin;

        // Header
        doc.setFillColor(colors.headerBg); doc.rect(0, 0, pageWidth, 60, 'F');
        doc.setFont(fonts.header.name, fonts.header.style); doc.setFontSize(fonts.header.size); doc.setTextColor(colors.headerText);
        doc.text('Violation Summary Report', margin, 38);
        doc.setFontSize(9); 
        doc.text(cleanText(clientName), pageWidth - margin, 25, { align: 'right' });
        doc.text(`Generated: ${formatDateSafe(new Date())}`, pageWidth - margin, 45, { align: 'right' });
        currentY = 80;

        // Owner/Address
        doc.setFont(fonts.subHeader.name, fonts.subHeader.style); doc.setFontSize(14); doc.setTextColor(colors.text);
        doc.text(vData.owner || 'Unknown Owner', margin, currentY);
        
        const idText = `ID: ${vData.id}`;
        doc.setFontSize(9); 
        const idWidth = doc.getTextWidth(idText) + 10;
        doc.setFillColor(colors.border); doc.roundedRect(pageWidth - margin - idWidth, currentY - 10, idWidth, 16, 2, 2, 'F');
        doc.setTextColor(colors.text); doc.text(idText, pageWidth - margin - 5, currentY + 1, { align: 'right' });
        currentY += 16; 

        doc.setFontSize(11); doc.setFont(fonts.subHeader.name, 'normal'); doc.setTextColor(colors.text);
        (vData.addressLines || []).forEach(line => { 
            doc.text(safeStr(line), margin, currentY); 
            currentY += 14; 
        });
        currentY += 5; doc.setDrawColor(colors.border); doc.setLineWidth(1); doc.line(margin, currentY, pageWidth - margin, currentY); currentY += 10; 

        const col1X = margin; const col2X = margin + colWidth + 20;
        let col1Y = currentY; let col2Y = currentY;

        col1Y += drawField('Violation Type:', vData.violation_type, col1X, col1Y, colWidth);
        col1Y += drawField('Article / Section:', vData.section && vData.section !== 'N/A' ? `${vData.article} / ${vData.section}` : vData.article, col1X, col1Y, colWidth);
        col1Y += drawField('Citation:', vData.citation, col1X, col1Y, colWidth);
        col1Y += drawField('Description / Notes:', vData.description, col1X, col1Y, colWidth);

        col2Y += drawField('Status:', vData.status, col2X, col2Y, colWidth);
        col2Y += drawField('Notice Level:', vData.notice_level, col2X, col2Y, colWidth);
        if (vData.certified_mail) {
          col2Y += drawField('Certified Mail #:', vData.certified_mail, col2X, col2Y, colWidth);
        }
        col2Y += drawField('Priority:', vData.priority, col2X, col2Y, colWidth);
        col2Y += drawField('Assigned To:', vData.assigned_to, col2X, col2Y, colWidth);
        col2Y += drawField('Violation Date:', formatDateSafe(vData.violation_date), col2X, col2Y, colWidth);
        col2Y += drawField('Due Date:', formatDateSafe(vData.due_date), col2X, col2Y, colWidth);
        
        currentY = Math.max(col1Y, col2Y) + 15;

        // Timeline
        if (includeTimeline && vData.timeline_entries && vData.timeline_entries.length > 0) {
            if (currentY + 60 > pageHeight - margin) { doc.addPage(); currentY = margin; }
            doc.setDrawColor(colors.border); doc.setLineWidth(1); doc.line(margin, currentY, pageWidth - margin, currentY); currentY += 20;
            currentY = drawTimelineEntries(doc, vData.timeline_entries, margin, currentY, contentWidth);
            currentY += 15;
        }

        // Dynamic Image Grid + QR Layout
        if (includeImages && (vData.photo_urls.length > 0 || vData.raw_id)) {
            if (currentY + 200 > pageHeight - margin) { doc.addPage(); currentY = margin; } 
            else { doc.setDrawColor(colors.border); doc.setLineWidth(1); doc.line(margin, currentY, pageWidth - margin, currentY); currentY += 20; }
            
            doc.setFont(fonts.label.name, fonts.label.style); doc.setFontSize(fonts.label.size); doc.setTextColor(colors.label);
            doc.text('Photo Evidence & Action Required', margin, currentY);
            currentY += 15;

            const photoData = vData.photo_urls.slice(0, 3);
            const totalItems = photoData.length + 1; // Photos + 1 QR
            
            let cols = 2, rows_grid = 2;
            if (totalItems === 1) { cols = 1; rows_grid = 1; }
            else if (totalItems === 2) { cols = 2; rows_grid = 1; }
            else if (totalItems === 3) { cols = 3; rows_grid = 1; }
            else if (totalItems === 4) { cols = 2; rows_grid = 2; }
            
            const gridGap = 15;
            const availableWidth = pageWidth - (margin * 2);
            const effectiveWidth = cols === 1 ? availableWidth / 2 : availableWidth;
            const startX = cols === 1 ? margin + (availableWidth / 4) : margin;
            
            const cellW = (effectiveWidth - (gridGap * (cols - 1))) / cols;
            // Constrain cell height so photos + captions don't overlap footer
            const availableGridHeight = pageHeight - currentY - margin - 60;
            const captionSpace = 20;
            const maxCellH = (availableGridHeight - (rows_grid - 1) * (gridGap + captionSpace)) / rows_grid - captionSpace;
            const cellH = Math.min(cellW * 0.75, Math.max(maxCellH, 80));

            const positions = [];
            for (let r = 0; r < rows_grid; r++) {
                for (let c = 0; c < cols; c++) {
                    if (positions.length < totalItems) {
                        positions.push({ x: startX + (c * (cellW + gridGap)), y: currentY + (r * (cellH + gridGap + 20)) });
                    }
                }
            }

            // Draw Photos
            for (let j = 0; j < photoData.length; j++) {
                const pos = positions[j];
                doc.setDrawColor(200); doc.setFillColor(250); 
                doc.rect(pos.x, pos.y, cellW, cellH, 'FD');

                const item = photoData[j];
                const imgSrc = typeof item === 'string' ? item : (item?.url || item?.src || item?.photo_url);
                
                if (imgSrc && typeof imgSrc === 'string') {
                    try {
                        const photo = await withTimeout(loadImageHighQuality(imgSrc), 8000).catch(() => null);
                        if (photo) {
                            await applyHighQualityImageToPdf(doc, photo, pos.x, pos.y, cellW, cellH);
                        } else {
                            doc.setFont('helvetica', 'normal'); doc.setFontSize(8); doc.setTextColor(150);
                            doc.text('Image unavailable', pos.x + (cellW / 2), pos.y + (cellH / 2), { align: 'center' });
                        }
                    } catch (e) {}
                }

                let tsText = 'Date unknown';
                if (imgSrc && typeof imgSrc === 'string' && imgSrc.startsWith('http')) {
                    try {
                        const exifDate = await getPhotoDateFromUrl(imgSrc);
                        if (exifDate) {
                            tsText = formatPhotoTimestamp(exifDate);
                        } else if (item.timestamp) {
                            tsText = formatPhotoTimestamp(item.timestamp);
                        }
                    } catch (e) { /* silent */ }
                } else if (item.timestamp) {
                    tsText = formatPhotoTimestamp(item.timestamp);
                }
                
                doc.setFontSize(8); doc.setFont('helvetica', 'normal'); doc.setTextColor(100);
                doc.text(tsText, pos.x, pos.y + cellH + 12);
            }

            // Draw QR Code in the last spot
            if (vData.raw_id) {
                const qrPos = positions[photoData.length];
                doc.setDrawColor(200); doc.setFillColor(255); 
                doc.rect(qrPos.x, qrPos.y, cellW, cellH, 'FD');

                // Use the app's own violation response page
                const baseUrl = "https://avria.cloud";
                const qrUrl = `${baseUrl}/violation/${vData.raw_id}`;
                
                try {
                    const qrSize = Math.min(cellW - 40, cellH - 60); 
                    const qrX = qrPos.x + (cellW - qrSize) / 2;
                    const qrY = qrPos.y + 15;
                    const qrDataUrl = await withTimeout(QRCode.toDataURL(qrUrl, { margin: 1, width: 300 }), 5000).catch(() => null);
                    
                    if (qrDataUrl) {
                        doc.addImage(qrDataUrl, 'PNG', qrX, qrY, qrSize, qrSize);
                    }
                    doc.setFont('helvetica', 'bold'); doc.setFontSize(10); doc.setTextColor("#000000");
                    doc.text("Scan to respond", qrPos.x + (cellW/2), qrY + qrSize + 15, { align: "center" });
                    doc.setFontSize(8); doc.setFont('helvetica', 'normal'); doc.setTextColor(100);
                    doc.text("Submit status updates securely online", qrPos.x + (cellW/2), qrY + qrSize + 27, { align: "center" });
                } catch (qrErr) {
                    console.error("Failed to generate QR in PDF", qrErr);
                }
            }

            currentY += (rows_grid * (cellH + gridGap + 20));
        }

        const pageStr = `Page ${i + 1} of ${total}`;
        doc.setFont(fonts.footer.name, fonts.footer.style); doc.setFontSize(fonts.footer.size); doc.setTextColor(colors.subHeader);
        doc.text(pageStr, pageWidth / 2, pageHeight - 15, { align: 'center' });

        if (isRecommendedForFining(vData.status)) {
          drawRecommendedForFiningStamp(doc);
        }

    } catch (err) {
        errorCount++;
        if (errorCount <= 5) {
            console.error(`Error generating page for violation index ${i}:`, err);
        }
        if (errorCount > MAX_ERRORS) {
            throw new Error(`PDF generation failed: Encountered ${errorCount} errors. Last error: ${err.message}`);
        }
    }
  }

  if (onProgress) onProgress(100);
  return doc;
};

export const generateViolationTimelinePDF = async (violations, settings = {}, onProgress, abortSignal) => {
    if (!violations || !Array.isArray(violations)) {
        throw new Error("Invalid violations data provided");
    }

    const { pageSize = 'letter', clientName = 'HOA Timeline Report' } = settings;
    let doc;
    try {
        doc = new jsPDF(getHighQualityPdfSettings('portrait', 'pt', pageSize));
        doc.setCharSpace(0); // Fix spacing
    } catch (e) {
        console.error("Failed to initialize jsPDF:", e);
        throw new Error("Failed to initialize PDF document");
    }

    const pageWidth = doc.internal.pageSize.getWidth();
    const pageHeight = doc.internal.pageSize.getHeight();
    const margin = 30;
    const contentWidth = pageWidth - (margin * 2);

    const total = violations.length;
    const CHUNK_SIZE = 3;
    let errorCount = 0;
    const MAX_ERRORS = 20;

    for (let i = 0; i < total; i++) {
        if (abortSignal?.aborted) throw new Error('Report generation cancelled');

        if (i > 0 && i % CHUNK_SIZE === 0) {
            if (onProgress) onProgress(Math.round((i / total) * 100));
            await safeYieldToMain();
        }

        try {
            if (i > 0) doc.addPage();

            const vData = getEffectiveViolationData(violations[i]);
            if (!vData) continue;

            let currentY = margin;

            doc.setFillColor('#2c3e50');
            doc.rect(0, 0, pageWidth, 60, 'F');
            doc.setFont('helvetica', 'bold');
            doc.setFontSize(16);
            doc.setTextColor('#ffffff');
            doc.text('Violation Timeline Report', margin, 38);
            doc.setFontSize(9);
            doc.text(cleanText(clientName), pageWidth - margin, 25, { align: 'right' });
            doc.text(`Generated: ${formatDateSafe(new Date())}`, pageWidth - margin, 45, { align: 'right' });
            currentY = 80;

            doc.setFont('helvetica', 'bold');
            doc.setFontSize(14);
            doc.setTextColor('#2c3e50');
            doc.text(vData.owner || 'Unknown Owner', margin, currentY);
            currentY += 16;

            doc.setFontSize(11);
            doc.setFont('helvetica', 'normal');
            (vData.addressLines || []).forEach(line => { 
                doc.text(safeStr(line), margin, currentY); 
                currentY += 14; 
            });
            currentY += 10;

            if (vData.timeline_entries && vData.timeline_entries.length > 0) {
                currentY = drawTimelineEntries(doc, vData.timeline_entries, margin, currentY, contentWidth);
            } else {
                doc.setFontSize(9);
                doc.setTextColor('#64748b');
                doc.text('No timeline entries available', margin, currentY);
            }

            const pageStr = `Page ${i + 1} of ${total}`;
            doc.setFont('helvetica', 'normal');
            doc.setFontSize(8);
            doc.setTextColor('#34495e');
            doc.text(pageStr, pageWidth / 2, pageHeight - 15, { align: 'center' });

            if (isRecommendedForFining(vData.status)) {
              drawRecommendedForFiningStamp(doc);
            }

        } catch (e) {
            errorCount++;
            if (errorCount <= 5) console.error(`Error processing timeline page ${i}:`, e);
            if (errorCount > MAX_ERRORS) throw new Error(`PDF timeline generation failed: Encountered ${errorCount} errors.`);
        }
    }

    if (onProgress) onProgress(100);
    return doc;
};

export const generateViolationsPDF = generateViolationStatusPDF;
