
import React, { useState, useEffect, useRef } from 'react';
import { logViolationCreated, logStageChange, logNoticeGenerated } from '@/lib/violationTimelineLogger';
import { formatMailingAddress } from '@/lib/formatMailingAddress';
import { combineOwnerNames } from '@/lib/ownerAddressUtils';
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
import { Input } from '@/components/ui/input';
import { Label } from '@/components/ui/label';
import { Button } from '@/components/ui/button';
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select';
import { Textarea } from '@/components/ui/textarea';
import { useToast } from '@/components/ui/use-toast';
import { 
  FileDown, 
  Loader2, 
  Plus, 
  Trash2, 
  AlertCircle, 
  X, 
  Braces, 
  User, 
  FileText, 
  Calendar, 
  Settings, 
  Upload, 
  Image as ImageIcon, 
  Search,
  MapPin,
  Star
} from 'lucide-react';
import { jsPDF } from 'jspdf';
import QRCode from 'qrcode';
import { supabase } from '@/integrations/supabase/client';
import { Checkbox } from '@/components/ui/checkbox';
import { v4 as uuidv4 } from 'uuid';
import { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/popover';
import { useAuth } from '@/contexts/AuthContext';
import { Separator } from '@/components/ui/separator';
import { format, isValid } from 'date-fns';
import { formatDateLongEST } from '@/lib/timezoneUtils';

// Standard ES Module Imports
import { NOTICE_LEVELS } from '@/lib/ViolationNoticeProgressionHelper';
import { useCustomVariables } from '@/hooks/useCustomVariables';
import { CustomVariablesInserter } from '@/components/CustomVariablesInserter';
import { getHighQualityPdfSettings, applyHighQualityImageToPdf, loadImageHighQuality } from '@/lib/pdfQualityUtils';
import { loadNotoSymbolsFont, registerNotoSymbolsFont } from '@/lib/fontManager';
import { formatPhotoTimestamp, getPhotoDate, getPhotoDateFromUrl } from '@/lib/exifUtils';
import { applyCropToCanvas } from '@/lib/imageOrientationUtils';
import acmLogoFullAsset from '@/assets/acm-logo-full.png';

/*
 * MANUAL TEST SCENARIO: Address Retrieval & UI
 * 1. Select a property from the Combobox.
 * 2. If the property has a complete mailing address, it should default to 'Primary Mailing' 
 *    and display the multiline format (Street \n City, State ZIP).
 * 3. If the property lacks a mailing address, console.warn logs the missing data 
 *    and the UI displays a clear red warning: "No valid address found...".
 * 4. Test selecting "Alt. Address 1". The preview should instantly update to the Alt address.
 */

// Fallback for variable replacement in case utility is missing or strictly structured
const replaceVariables = (text, varsMap) => {
  if (!text) return '';
  let result = text;
  Object.keys(varsMap).forEach(key => {
    const regex = new RegExp(`\\{\\{${key}\\}\\}`, 'g');
    result = result.replace(regex, varsMap[key] || '');
  });
  return result;
};

// 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 for formatting multiline address robustly
const formatMultiline = (street, city, state, zip, legacy) => {
    let hasStructured = street || city || state || zip;
    if (hasStructured) {
        const parts = [];
        if (street) parts.push(street);
        let csz = [];
        if (city) csz.push(city);
        if (state) csz.push(state);
        let cszStr = csz.join(', ');
        if (zip) cszStr += (cszStr ? ' ' : '') + zip;
        if (cszStr) parts.push(cszStr);
        return parts.join('\n');
    }
    return legacy || '';
};

// Custom Searchable Address Combobox
const normalizeAddr = (s) => String(s || '').replace(/\s+/g, ' ').trim().toLowerCase();

// Build a list of recipients for a unit/violation.
// - Each active co-owner: name + their mailing address (or property if no separate mailing).
// - Each owner's alternate addresses (if set).
// - If ANY owner has a mailing address different from the property address,
//   add a recipient "Current Tenants or Unknown Party in Possession" at the property address.
// Each recipient has its own certified mail # field.
const buildRecipientsForUnit = (coOwners, propertyAddress) => {
  const propKey = normalizeAddr(propertyAddress);
  const recipients = [];
  let anyOwnerOffsite = false;

  for (const o of (coOwners || [])) {
    const ownerName = (o.owner_name || `${o.first_name || ''} ${o.last_name || ''}`.trim()) || 'Owner';
    const mailing = (o.mailing_address || '').trim();
    const property = (o.property_address || '').trim();
    const alt1 = (o.alternate_address_1 || o.alt_address_1 || '').trim();
    const alt2 = (o.alternate_address_2 || o.alt_address_2 || '').trim();

    if (mailing && propKey && normalizeAddr(mailing) !== propKey) {
      recipients.push({ name: ownerName, address: mailing, certifiedMail: '', kind: 'owner_mailing', label: `${ownerName} — Mailing` });
      anyOwnerOffsite = true;
    } else if (property) {
      recipients.push({ name: ownerName, address: property, certifiedMail: '', kind: 'owner_property', label: `${ownerName} — Property` });
    } else if (mailing) {
      recipients.push({ name: ownerName, address: mailing, certifiedMail: '', kind: 'owner_mailing', label: `${ownerName} — Mailing` });
    }
    if (alt1) recipients.push({ name: ownerName, address: alt1, certifiedMail: '', kind: 'alt', label: `${ownerName} — Alt 1` });
    if (alt2) recipients.push({ name: ownerName, address: alt2, certifiedMail: '', kind: 'alt', label: `${ownerName} — Alt 2` });
  }

  if (anyOwnerOffsite && propertyAddress) {
    recipients.push({
      name: 'Current Tenants or Unknown Party in Possession',
      address: propertyAddress,
      certifiedMail: '',
      kind: 'tenant',
      label: 'Current Tenants / Unknown Party',
    });
  }

  // Group by address: when separate owners share the same mailing address,
  // combine them into a single recipient with merged names (e.g. "John & Jane Doe").
  // The tenant recipient is kept separate even if it shares the property address.
  const byAddr = new Map();
  const order = [];
  for (const r of recipients) {
    const addrKey = normalizeAddr(r.address);
    const groupKey = r.kind === 'tenant' ? `__tenant__|${addrKey}` : addrKey;
    if (!addrKey) continue;
    if (!byAddr.has(groupKey)) {
      byAddr.set(groupKey, { ...r, names: [r.name] });
      order.push(groupKey);
    } else {
      const existing = byAddr.get(groupKey);
      if (!existing.names.some(n => normalizeAddr(n) === normalizeAddr(r.name))) {
        existing.names.push(r.name);
      }
    }
  }
  const joinNames = (names) => {
    if (names.length <= 1) return names[0] || '';
    if (names.length === 2) return `${names[0]} & ${names[1]}`;
    return names.slice(0, -1).join(', ') + ' & ' + names[names.length - 1];
  };
  return order.map((k, idx) => {
    const r = byAddr.get(k);
    const combinedName = r.kind === 'tenant' ? r.name : joinNames(r.names);
    return {
      name: combinedName,
      address: r.address,
      certifiedMail: '',
      kind: r.kind,
      label: r.kind === 'tenant' ? r.label : `${combinedName} — ${r.label.split('— ')[1] || ''}`.trim(),
      rid: `${Date.now()}-${idx}-${Math.random().toString(36).slice(2, 7)}`,
    };
  });
};

const AddressCombobox = ({ row, availableOwners, onSelect }) => {
    const [open, setOpen] = useState(false);
    const [search, setSearch] = useState('');

    const filtered = availableOwners.filter(o => 
        o.property_address?.toLowerCase().includes(search.toLowerCase())
    );

    const highlight = (text, q) => {
        if(!q) return text;
        const parts = text.split(new RegExp(`(${q})`, 'gi'));
        return parts.map((part, i) =>
            part.toLowerCase() === q.toLowerCase() ? 
            <span key={i} className="bg-yellow-200 text-slate-900 font-bold rounded-sm px-[1px]">{part}</span> : part
        );
    }

    return (
        <Popover open={open} onOpenChange={setOpen}>
            <PopoverTrigger asChild>
                <Button variant="outline" className={`w-full justify-between text-left font-normal bg-slate-50 border-slate-200 hover:bg-slate-100 transition-all duration-200 ${!row.propertyId && 'text-slate-500'}`}>
                    <div className="flex items-center gap-2 truncate">
                        <MapPin className="w-4 h-4 text-blue-500 shrink-0" />
                        <span className="truncate">
                            {row.propertyAddress && row.propertyId ? row.propertyAddress : "Search by property address..."}
                        </span>
                    </div>
                    <Search className="w-4 h-4 text-slate-400 shrink-0 ml-2" />
                </Button>
            </PopoverTrigger>
            <PopoverContent className="w-[400px] p-0 shadow-xl border-slate-200" align="start">
                <div className="p-2 border-b border-slate-100 flex items-center gap-2 sticky top-0 bg-white z-10">
                    <Search className="w-4 h-4 text-slate-400" />
                    <Input
                        placeholder="Type to filter addresses..."
                        value={search}
                        onChange={e => setSearch(e.target.value)}
                        className="h-9 border-none shadow-none focus-visible:ring-0 px-1 text-sm font-medium"
                        autoFocus
                    />
                    {search && (
                        <Button variant="ghost" size="icon" className="h-7 w-7 rounded-full hover:bg-slate-100" onClick={() => setSearch('')}>
                            <X className="w-3.5 h-3.5 text-slate-500" />
                        </Button>
                    )}
                </div>
                <div className="max-h-64 overflow-y-auto p-1 scrollbar-thin">
                    {filtered.length === 0 ? <p className="p-4 text-sm text-slate-500 text-center italic">No addresses found matching "{search}"</p> :
                        filtered.map(o => (
                            <div
                                key={o.id}
                                className="px-3 py-2.5 text-sm cursor-pointer hover:bg-blue-50 hover:text-blue-700 rounded-md transition-colors flex items-center justify-between group"
                                onClick={() => {
                                    onSelect(o.id);
                                    setOpen(false);
                                    setSearch('');
                                }}
                            >
                                <span className="truncate">{highlight(o.property_address, search)}</span>
                            </div>
                        ))
                    }
                </div>
            </PopoverContent>
        </Popover>
    );
};

export default function ViolationNoticeForm({ initialViolations = null, onClose = null }) {
  const { toast } = useToast();
  const { user } = useAuth();
  const [clients, setClients] = useState([]);
  const [loading, setLoading] = useState(false);
  const [selectedClientId, setSelectedClientId] = useState('');
  const [autoSaveToLog, setAutoSaveToLog] = useState(true);
  const [isSaving, setIsSaving] = useState(false);
  
  // Custom Variables Hook
  const { variables: customVariables, fetchVariables: fetchCustomVariables } = useCustomVariables();

  // PDF Settings
  const [pdfConfig] = useState({ font: 'helvetica', fontSize: '9' });
  const [availableOwners, setAvailableOwners] = useState([]);
  const [availableViolationTypes, setAvailableViolationTypes] = useState([]);

  // Global Settings
  const [globalData, setGlobalData] = useState({
    associationName: '',
    associationAddressLine1: 'P.O. Box 560099',
    associationAddressLine2: 'Rockledge, Florida 32956',
    noticeLogo: '', footerLogo: '',
    managementCompany: 'Avria Community Management, LLC',
    managementEmail: 'vw@avriacam.com',
    managementPhone: '(321) 405-2449',
    managementWebsite: ''
  });

  const [rows, setRows] = useState([]);
  const initializedRef = useRef(false);

  useEffect(() => {
    fetchClients();
  }, []);

  useEffect(() => {
    const formattedDate = formatDateLongEST(new Date());
    const dueDateObj = new Date();
    dueDateObj.setDate(new Date().getDate() + 14);
    const formattedDueDate = formatDateLongEST(dueDateObj);

    if (initialViolations && initialViolations.length > 0 && !initializedRef.current) {
      const clientId = initialViolations[0].association_id || initialViolations[0].client_id;
      if (clientId && clientId !== selectedClientId) setSelectedClientId(clientId);

      const mappedRows = initialViolations.map((v, idx) => {
        const history = v.notice_history || [];
        const latestHistory = history.length > 0 ? history[history.length - 1] : {};

        let photos = [];
        if (v.photo_urls && Array.isArray(v.photo_urls) && v.photo_urls.length > 0) {
            photos = v.photo_urls.slice(0, 3).map(p => {
                if (typeof p === 'string') return { url: p, timestamp: null };
                return { url: p.url || p.src || p.photo_url || '', timestamp: p.timestamp || null };
            });
        } else if (v.photo_url) {
            photos = [{ url: v.photo_url, timestamp: null }];
        }
        
        const owner = v.owners || v.property_owners || {};
        // Temporary single-owner name; fetchOwners will resolve all co-owners and overwrite
        const ownerName = owner.owner_name || `${owner.first_name || ''} ${owner.last_name || ''}`.trim() || '';
        
        const { full: formattedMailing } = formatMailingAddress(owner, ownerName);
        const mailing = owner.mailing_address || owner.property_address || '';
        const alt1 = owner.alternate_address_1 || owner.alt_address_1 || '';
        const alt2 = owner.alternate_address_2 || owner.alt_address_2 || '';
        const propertyAddr = owner.property_address || v.address || '';
        const initialRecipients = buildRecipientsForUnit([owner], propertyAddr);

        return {
          id: idx + 1, dbId: v.id, propertyId: v.property_id || null,
          ownerName: ownerName,
          propertyAddress: propertyAddr,
          
          mailingAddress: mailing,
          altAddress1: alt1,
          altAddress2: alt2,
          selectedAddressType: mailing ? 'primary' : 'custom',
          customAddress: '',
          recipients: initialRecipients,

          date: formattedDate,
          dueDate: v.due_date ? formatDateLongEST(v.due_date) : formattedDueDate,
          noticeLevel: v.stage || v.notice_level || NOTICE_LEVELS.FIRST,
          noticeType: ((v.stage || v.notice_level) === NOTICE_LEVELS.SECOND) ? 'Second Notice of Violation' : ((v.stage || v.notice_level) === NOTICE_LEVELS.THIRD) ? 'Final Notice of Violation' : 'First Notice of Violation',
          violationType: v.violation_type || 'General Maintenance',
          articleSection: latestHistory.article_section || v.article || 'Article X',
          citation: latestHistory.citation || v.citation || 'Please refer to the governing documents.',
          requestedAction: v.description || latestHistory.requested_action || 'Please remedy the violation by the deadline.',
          photos: photos,
          certifiedMail: v.certified_mail || '',
        };
      });
      setRows(mappedRows);
      initializedRef.current = true;
    } else if (!initializedRef.current && rows.length === 0) {
      setRows([{
        id: 1, dbId: null, propertyId: null, ownerName: '', propertyAddress: '',
        mailingAddress: '', altAddress1: '', altAddress2: '', selectedAddressType: 'primary', customAddress: '',
        recipients: [],
        date: formattedDate, dueDate: formattedDueDate,
        noticeLevel: NOTICE_LEVELS.FIRST, noticeType: 'First Notice of Violation',
        violationType: 'Weeds/Landscaping', articleSection: 'Article V', citation: '', requestedAction: '', photos: [],
      }]);
      initializedRef.current = true;
    }
  }, [initialViolations, selectedClientId, rows.length]);

  useEffect(() => {
    if (selectedClientId) {
      fetchOwners(selectedClientId);
      fetchViolationTypes(selectedClientId);
      if (typeof fetchCustomVariables === 'function') fetchCustomVariables(selectedClientId);
      
      const client = clients.find(c => c.id === selectedClientId);
      if (client) {
        setGlobalData(prev => ({
          ...prev, associationName: client.name,
          managementEmail: client.contact_email || client.email || prev.managementEmail,
          managementPhone: client.phone || prev.managementPhone,
          managementWebsite: client.website || '',
          noticeLogo: client.logo_url || '', footerLogo: ''
        }));
      }
    } else {
      setAvailableOwners([]); setAvailableViolationTypes([]);
    }
  }, [selectedClientId, clients]);

  const fetchClients = async () => {
    const { data, error } = await supabase.from('associations').select('id, name, email, phone, logo_url, address, city, state, zip, website, care_of_address, contact_email').eq('status', 'active');
    if (!error) setClients(data || []);
  };

  // Find all co-owners for a given violation address or unit, combine names, collect addresses
  // Excludes archived (previous) owners so old owners don't appear on notices.
  const findCoOwners = (mapped, violationAddress, unitId, ownerId) => {
    const normalize = (addr) => (addr || '').toLowerCase().replace(/[.,#\-\s]+/g, ' ').trim();
    const normAddr = normalize(violationAddress);
    const isActive = (o) => o?.status !== 'archived';

    // Find matching owners: by unit_id first, then by address, then by owner_id
    let coOwners = [];
    if (unitId) {
      coOwners = mapped.filter(o => o.unit_id === unitId && isActive(o));
    }
    if (coOwners.length === 0 && normAddr) {
      coOwners = mapped.filter(o => normalize(o.property_address) === normAddr && isActive(o));
    }
    if (coOwners.length === 0 && ownerId) {
      const single = mapped.find(o => o.id === ownerId && isActive(o));
      if (single) coOwners = [single];
    }

    if (coOwners.length === 0) return null;

    // Combine owner names
    const combinedName = combineOwnerNames(coOwners);

    // Collect addresses — use first owner's mailing, then check all for alt addresses
    const primaryOwner = coOwners[0];
    const mailing = primaryOwner.mailing_address || primaryOwner.property_address || '';

    // Collect all unique alt addresses across co-owners
    let alt1 = '';
    let alt2 = '';
    for (const o of coOwners) {
      if (!alt1 && o.alternate_address_1) alt1 = o.alternate_address_1;
      if (!alt2 && o.alternate_address_2) alt2 = o.alternate_address_2;
      if (alt1 && alt2) break;
    }
    
    return { combinedName, mailing, alt1, alt2, primaryOwner, coOwners };
  };

  const fetchOwners = async (clientId) => {
    const { data, error } = await supabase.from('owners')
      .select('id, first_name, last_name, property_address, mailing_address, alternate_address_1, alternate_address_2, email, phone, unit_id, status, units(unit_number, account_number, is_rental)')
      .eq('association_id', clientId)
      .order('property_address');
      
    if (!error) {
      // Map owners to have owner_name for compatibility
      const mapped = (data || []).map(o => ({
        ...o,
        owner_name: `${o.first_name || ''} ${o.last_name || ''}`.trim(),
        alt_address_1: o.alternate_address_1,
        alt_address_2: o.alternate_address_2,
      }));
      setAvailableOwners(mapped);
      if (initialViolations && mapped.length > 0) {
        setRows(prevRows => prevRows.map((row, idx) => {
          const v = initialViolations[idx];
          const violAddr = row.propertyAddress || v?.address || '';
          const unitId = v?.unit_id || null;
          const ownerId = v?.owner_id || null;
          
          const result = findCoOwners(mapped, violAddr, unitId, ownerId);
          if (result) {
            const builtRecipients = buildRecipientsForUnit(result.coOwners, violAddr);
            return { 
              ...row, 
              ownerName: result.combinedName, 
              mailingAddress: result.mailing,
              altAddress1: result.alt1,
              altAddress2: result.alt2,
              selectedAddressType: result.mailing ? 'primary' : 'custom',
              recipients: builtRecipients.length > 0 ? builtRecipients : row.recipients,
            };
          }
          return row;
        }));
      }
    }
  };

  const fetchViolationTypes = async (clientId) => {
    const { data, error } = await supabase.from('violation_types').select('*').eq('association_id', clientId);
    if (!error) setAvailableViolationTypes(data || []);
  };

  const handleClientChange = (clientId) => {
    setSelectedClientId(clientId);
    const client = clients.find(c => c.id === clientId);
    if (client) {
      setGlobalData(prev => ({
        ...prev, associationName: client.name, managementEmail: client.email || prev.managementEmail,
        managementPhone: client.phone || prev.managementPhone,
        noticeLogo: client.logo_url || '', footerLogo: ''
      }));
    }
  };

  const handlePOBoxChange = (value) => setGlobalData(prev => ({ ...prev, associationAddressLine1: value }));
  const handleRowChange = (id, field, value) => setRows(prev => prev.map(row => row.id === id ? { ...row, [field]: value } : row));

  const handleNoticeLevelChange = (rowId, level) => {
    setRows(prev => prev.map(row => {
      if (row.id === rowId) {
        let displayType = 'First Notice of Violation';
        if (level === NOTICE_LEVELS.SECOND) displayType = 'Second Notice of Violation';
        if (level === NOTICE_LEVELS.THIRD) displayType = 'Final Notice of Violation';
        return { ...row, noticeLevel: level, noticeType: displayType };
      }
      return row;
    }));
  };

  const handleSelectViolationType = (rowId, typeId) => {
    const vType = availableViolationTypes.find(v => v.id === typeId);
    if (vType) {
      const cureDays = Math.max(1, parseInt(vType.cure_days, 10) || 14);
      const stageCount = Math.min(3, Math.max(1, parseInt(vType.stage_count, 10) || 3));
      const dueDateObj = new Date();
      dueDateObj.setDate(new Date().getDate() + cureDays);
      const newDueDate = formatDateLongEST(dueDateObj);
      setRows(prev => prev.map(row => row.id === rowId ? {
          ...row,
          violationType: vType.category,
          articleSection: vType.article_section,
          citation: vType.citation,
          requestedAction: vType.requested_action,
          cureDays,
          stageCount,
          letterTemplates: vType.letter_templates || {},
          dueDate: newDueDate,
        } : row));
    }
  };

  const handleOwnerSelect = (rowId, ownerId) => {
    const owner = availableOwners.find(o => o.id === ownerId);
    if (owner) {
      // Find co-owners sharing the same unit
      const coOwners = owner.unit_id
        ? availableOwners.filter(o => o.unit_id === owner.unit_id)
        : [owner];
      const combinedName = combineOwnerNames(coOwners);

      const mailing = owner.mailing_address || owner.property_address || '';
      const alt1 = owner.alt_address_1 || owner.alternate_address_1 || '';
      const alt2 = owner.alt_address_2 || owner.alternate_address_2 || '';
      
      if (!mailing) {
          console.warn(`[Debug] Missing mailing address for owner ${owner.id}`);
      }

      const builtRecipients = buildRecipientsForUnit(coOwners, owner.property_address || '');

      setRows(prev => prev.map(row => row.id === rowId ? { 
        ...row, 
        propertyId: owner.id, 
        ownerName: combinedName, 
        propertyAddress: owner.property_address,
        mailingAddress: mailing,
        altAddress1: alt1,
        altAddress2: alt2,
        selectedAddressType: mailing ? 'primary' : 'custom',
        customAddress: '',
        recipients: builtRecipients,
      } : row));
    }
  };

  const handlePhotoUpload = async (rowId, e) => {
    const files = Array.from(e.target.files);
    if (!files || files.length === 0) return;

    const row = rows.find(r => r.id === rowId);
    const currentCount = row?.photos?.length || 0;
    const remainingSlots = 3 - currentCount;

    if (remainingSlots <= 0) {
        toast({ variant: "destructive", title: "Limit Reached", description: "You can only attach a maximum of 3 photos per violation." });
        return;
    }

    const filesToProcess = files.slice(0, remainingSlots);
    if (files.length > remainingSlots) {
        toast({ 
            title: "Photos Limited", 
            description: `Only the first ${remainingSlots} photo(s) were added to stay within the 3 photo limit.` 
        });
    }

    const newPhotos = await Promise.all(filesToProcess.map(async file => {
        const dateTaken = await getPhotoDate(file);
        const rawDataUrl = await new Promise((resolve) => {
            const reader = new FileReader();
            reader.onload = (evt) => resolve(evt.target.result);
            reader.readAsDataURL(file);
        });

        const croppedDataUrl = await applyCropToCanvas(rawDataUrl, 4/3, 800);

        return { 
            url: croppedDataUrl || rawDataUrl, 
            timestamp: dateTaken || null 
        };
    }));

    setRows(prev => prev.map(r => {
      if (r.id === rowId) {
        return { ...r, photos: [...(r.photos || []), ...newPhotos] };
      }
      return r;
    }));
    
    e.target.value = '';
  };

  const removePhoto = (rowId, photoIndex) => {
    setRows(prev => prev.map(row => {
      if (row.id === rowId) {
        const newPhotos = [...(row.photos || [])];
        newPhotos.splice(photoIndex, 1);
        return { ...row, photos: newPhotos };
      }
      return row;
    }));
  };

  const addRow = () => {
    const newId = Math.max(...rows.map(r => r.id), 0) + 1;
    const lastRow = rows[rows.length - 1];
    const formattedDate = formatDateLongEST(new Date());
    const dueDateObj = new Date(); dueDateObj.setDate(new Date().getDate() + 14);
    const formattedDueDate = formatDateLongEST(dueDateObj);

    setRows(prev => [...prev, {
      id: newId, dbId: null, propertyId: null, ownerName: '', propertyAddress: '',
      mailingAddress: '', altAddress1: '', altAddress2: '', selectedAddressType: 'primary', customAddress: '',
      recipients: [],
      date: lastRow ? lastRow.date : formattedDate, dueDate: lastRow ? lastRow.dueDate : formattedDueDate,
      noticeLevel: NOTICE_LEVELS.FIRST, noticeType: 'First Notice of Violation',
      violationType: 'General Maintenance', articleSection: 'Article X', citation: 'Please refer to the governing documents.',
      requestedAction: 'Please remedy the violation by the deadline.', photos: [],
    }]);
  };

  const removeRow = (id) => {
    if (rows.length > 1) setRows(prev => prev.filter(r => r.id !== id));
    else toast({ variant: "destructive", title: "Cannot delete", description: "You must have at least one row." });
  };

  // ---- Recipient helpers ----
  const updateRecipient = (rowId, rid, field, value) => {
    setRows(prev => prev.map(r => r.id === rowId
      ? { ...r, recipients: (r.recipients || []).map(rc => rc.rid === rid ? { ...rc, [field]: value } : rc) }
      : r
    ));
  };
  const removeRecipient = (rowId, rid) => {
    setRows(prev => prev.map(r => r.id === rowId
      ? { ...r, recipients: (r.recipients || []).filter(rc => rc.rid !== rid) }
      : r
    ));
  };
  const addRecipient = (rowId) => {
    setRows(prev => prev.map(r => r.id === rowId
      ? { ...r, recipients: [
          ...(r.recipients || []),
          { rid: `${Date.now()}-${Math.random().toString(36).slice(2,7)}`, name: '', address: '', certifiedMail: '', kind: 'custom', label: 'Custom' }
        ] }
      : r
    ));
  };
  const regenerateRecipients = (rowId) => {
    const row = rows.find(r => r.id === rowId);
    if (!row) return;
    let coOwners = [];
    if (row.propertyId) {
      const owner = availableOwners.find(o => o.id === row.propertyId);
      if (owner) {
        coOwners = owner.unit_id
          ? availableOwners.filter(o => o.unit_id === owner.unit_id)
          : [owner];
      }
    }
    const built = buildRecipientsForUnit(coOwners, row.propertyAddress);
    setRows(prev => prev.map(r => r.id === rowId ? { ...r, recipients: built } : r));
  };

  const fillTemplate = (tpl, vars) => {
    if (!tpl) return '';
    return Object.entries(vars).reduce(
      (acc, [k, v]) => acc.replaceAll(`{{${k}}}`, v ?? ''),
      tpl
    );
  };

  const getOpeningParagraph = (noticeType, associationName, dueDate, row) => {
    const level = row?.noticeLevel;
    const customTpl = row?.letterTemplates?.[level];
    if (customTpl && String(customTpl).trim()) {
      return fillTemplate(customTpl, {
        dueDate,
        associationName,
        ownerName: row?.ownerName || 'Homeowner',
        address: row?.propertyAddress || '',
      });
    }
    const type = (noticeType || '').toLowerCase();
    if (type.includes('second')) return `This letter serves as a SECOND NOTICE regarding the violation at your property. A recent inspection has confirmed that the condition previously cited has not been corrected. As a member of the ${associationName}, you are required to maintain your property in accordance with the community's governing documents. Please rectify this violation by ${dueDate} to avoid further escalation of this matter.`;
    if (type.includes('third') || type.includes('final') || type.includes('intent')) return `This is a FINAL NOTICE regarding the ongoing violation on your property. Despite previous communications, the violation remains uncorrected. Failure to remedy this situation by ${dueDate} may result in the imposition of fines, suspension of use rights, or legal action as authorized by the ${associationName}'s governing documents and Florida Statutes. Please treat this matter with urgency.`;
    return `During a recent inspection of the community, we noted that your property is out of compliance with ${associationName}'s Covenants, Conditions, and Restrictions (CC&Rs). Your property violates the governing documents, including, but not limited to, the referenced section below. This notice is being sent as an initial friendly reminder asking that you correct the situation listed below. Please take the necessary steps to correct the situation by ${dueDate} to avoid further action.`;
  };

  const dataURLtoBlob = (dataurl) => {
    try {
      const arr = dataurl.split(','); const mime = arr[0].match(/:(.*?);/)[1];
      const bstr = atob(arr[1]); let n = bstr.length; const u8arr = new Uint8Array(n);
      while(n--) u8arr[n] = bstr.charCodeAt(n);
      return new Blob([u8arr], {type:mime});
    } catch (e) { return null; }
  };
  
  const getSelectedPrintAddress = (row) => {
      if (row.printAddressOverride) return row.printAddressOverride;
      switch(row.selectedAddressType) {
          case 'primary': return row.mailingAddress;
          case 'alt1': return row.altAddress1;
          case 'alt2': return row.altAddress2;
          case 'custom': return row.customAddress;
          default: return row.mailingAddress;
      }
  };

  const normalizePrintAddress = (address) => String(address || '').replace(/\s+/g, ' ').trim().toLowerCase();

  // Get final list of recipients for a row (each = a notice page).
  // Falls back to the legacy primary/alt logic if recipients[] is empty.
  const getRowRecipients = (row) => {
    const list = Array.isArray(row.recipients) ? row.recipients.filter(r => (r.address || '').trim()) : [];
    if (list.length > 0) return list;
    // Legacy fallback
    const fallback = [];
    const push = (name, address, label, kind) => {
      const a = String(address || '').trim();
      if (!a) return;
      const key = normalizePrintAddress(a);
      if (fallback.some(r => normalizePrintAddress(r.address) === key)) return;
      fallback.push({ rid: `${label}-${key}`, name: name || row.ownerName, address: a, certifiedMail: row.certifiedMail || '', label, kind });
    };
    if (row.selectedAddressType === 'custom') push(row.ownerName, row.customAddress, 'Custom', 'custom');
    push(row.ownerName, row.mailingAddress, 'Primary Mailing', 'owner_mailing');
    push(row.ownerName, row.propertyAddress, 'Property', 'owner_property');
    push(row.ownerName, row.altAddress1, 'Alt 1', 'alt');
    push(row.ownerName, row.altAddress2, 'Alt 2', 'alt');
    return fallback.length > 0 ? fallback : [{ rid: 'missing', name: row.ownerName, address: '', certifiedMail: '', label: 'No address on file', kind: 'missing' }];
  };

  const getViolationPrintAddresses = (row) => {
      const candidates = [];
      const addCandidate = (label, address) => {
          const trimmed = String(address || '').trim();
          const key = normalizePrintAddress(trimmed);
          if (trimmed && key && !candidates.some(candidate => candidate.key === key)) {
              candidates.push({ label, address: trimmed, key });
          }
      };

      if (row.selectedAddressType === 'custom') addCandidate('Custom Address', row.customAddress);
      addCandidate('Primary Mailing Address', row.mailingAddress);
      addCandidate('Property Address', row.propertyAddress);
      addCandidate('Alternate Address 1', row.altAddress1);
      addCandidate('Alternate Address 2', row.altAddress2);

      return candidates.length > 0 ? candidates : [{ label: 'No address on file', address: '', key: 'missing' }];
  };

  const handleSaveToDatabase = async () => {
    if (!selectedClientId) return false;
    setIsSaving(true);
    let successCount = 0; let failCount = 0;
    const varsMap = {};
    (customVariables || []).forEach(v => { varsMap[v.variable_name.replace(/[{}]/g, '')] = v.predefined_value || ''; });
    varsMap['clientName'] = globalData.associationName;

    for (const row of rows) {
      if (!row.propertyAddress) continue;
      try {
        const selectedOwner = availableOwners.find(owner => owner.id === row.propertyId);
        const stage = row.noticeLevel || NOTICE_LEVELS.FIRST;
        const violationTitle = row.violationType || 'Violation';

        let photoUrl = null; let photoUrls = [];
        for (const p of (row.photos || [])) {
            const photoData = p.url || p.src || p;
            const timestamp = typeof p?.timestamp === 'string'
              ? (p.timestamp.trim() || null)
              : (p?.timestamp instanceof Date && !Number.isNaN(p.timestamp.getTime()) ? p.timestamp.toISOString() : null);
            if (typeof photoData === 'string' && photoData.startsWith('data:image')) {
                const blob = dataURLtoBlob(photoData);
                if (blob) {
                    const fileExt = blob.type.split('/')[1] || 'jpeg';
                    const fileName = `${uuidv4()}.${fileExt}`;
                    const { error: uploadError } = await supabase.storage.from('violation-photos').upload(fileName, blob);
                    if (!uploadError) {
                        const { data: urlData } = supabase.storage.from('violation-photos').getPublicUrl(fileName);
                        const url = urlData.publicUrl;
                        if (!photoUrl) photoUrl = url;
                        photoUrls.push({ url: url, timestamp });
                    }
                }
            } else if (typeof photoData === 'string' && photoData.startsWith('http')) {
                if (!photoUrl) photoUrl = photoData;
                photoUrls.push({ url: photoData, timestamp });
            }
        }

        let violationDate = null;
        if (row.date) {
             const parsedDate = new Date(row.date);
             if (!isNaN(parsedDate)) {
                 const offset = parsedDate.getTimezoneOffset() * 60000;
                 violationDate = new Date(parsedDate.getTime() - offset).toISOString().split('T')[0];
             }
        }
        let dueDate = null;
        if (row.dueDate) {
            const parsedDueDate = new Date(row.dueDate);
            if (!isNaN(parsedDueDate)) {
                const offset = parsedDueDate.getTimezoneOffset() * 60000;
                dueDate = new Date(parsedDueDate.getTime() - offset).toISOString().split('T')[0];
            }
        }

        const rowVars = { ...varsMap, propertyAddress: row.propertyAddress, ownerName: row.ownerName, currentDate: row.date };
        const resolvedAction = replaceVariables(row.requestedAction, rowVars);
        const resolvedCitation = replaceVariables(row.citation, rowVars);
        
        let extraNotes = '';
        const recipientsForNotes = getRowRecipients(row).filter(item => item.address);
        if (recipientsForNotes.length > 0) {
            extraNotes = `\n[Notice generated for:\n${recipientsForNotes.map(item => {
              const cm = item.certifiedMail ? `\nCertified Mail #: ${item.certifiedMail}` : '';
              return `${item.label || item.name}:\n${item.name}\n${item.address}${cm}`;
            }).join('\n\n')}]`;
        }
        
        const notes = `Owner: ${row.ownerName}\nArticle: ${row.articleSection}\nCitation: ${resolvedCitation}\nAction: ${resolvedAction}${extraNotes}`;

        const historyEntry = {
            date: violationDate || new Date().toISOString(), status: 'open', action: 'Violation Created',
            notice_level: stage, violation_type: row.violationType, article_section: row.articleSection,
            citation: resolvedCitation, requested_action: resolvedAction, notes: notes
        };

        if (row.dbId) {
          const { data: currentViolation, error: currentViolationError } = await supabase
            .from('violations')
            .select('notice_level, stage, status, notice_history')
            .eq('id', row.dbId)
            .single();
          if (currentViolationError) throw currentViolationError;

          const existingHistory = Array.isArray(currentViolation?.notice_history) ? currentViolation.notice_history : [];
          const updatedHistory = [...existingHistory, historyEntry];

          const { error } = await supabase.from('violations').update({
            title: violationTitle,
            owner_id: selectedOwner?.id || row.propertyId || undefined,
            property_id: selectedOwner?.id || row.propertyId || undefined,
            unit_id: selectedOwner?.unit_id || undefined,
            stage,
            notice_level: stage,
            violation_type: row.violationType,
            description: resolvedAction,
            due_date: dueDate,
            notes: notes,
            notice_history: updatedHistory,
            photo_url: photoUrl || undefined,
            photo_urls: photoUrls.length > 0 ? photoUrls : undefined,
          }).eq('id', row.dbId);
          if (error) throw error;

          const previousStage = currentViolation?.stage || currentViolation?.notice_level;
          if (previousStage !== stage) {
            await logStageChange(row.dbId, previousStage, stage);
          }

          successCount++;
        } else {
          const violationRecord = {
              association_id: selectedClientId,
              owner_id: selectedOwner?.id || row.propertyId || null,
              property_id: selectedOwner?.id || row.propertyId || null,
              unit_id: selectedOwner?.unit_id || null,
              address: row.propertyAddress,
              title: violationTitle,
              violation_type: row.violationType,
              status: 'open',
              priority: 'medium',
              stage,
              notice_level: stage,
              violation_date: violationDate,
              due_date: dueDate,
              notes: notes,
              description: resolvedAction,
              photo_url: photoUrl || null,
              photo_urls: photoUrls.length > 0 ? photoUrls : null,
              notice_history: [historyEntry],
              created_by: user?.id
          };

          const { data: insertedData, error } = await supabase.from('violations').insert([violationRecord]).select('id').single();
          if (error) throw error;
          
          row.dbId = insertedData.id;

          await logViolationCreated(insertedData.id, {
            address: row.propertyAddress,
            violationType: row.violationType,
            noticeLevel: stage,
            ownerName: row.ownerName,
            associationName: globalData.associationName,
          });

          successCount++;
        }
      } catch (err) { console.error('Save violation error:', err); failCount++; }
    }
    
    setIsSaving(false);
    if (successCount > 0) toast({ title: "Synced", description: `Saved ${successCount} violation(s) to database.` });
    if (failCount > 0) toast({ variant: "destructive", title: "Sync Issues", description: `Failed to save ${failCount} violation(s).` });
    return successCount > 0;
  };

  const generatePDF = async (action = 'download') => {
    if (rows.length === 0) return;
    setLoading(true);
    
    try {
      if (autoSaveToLog && selectedClientId) {
          await handleSaveToDatabase();
      }

      // Auto-log notice generation for each row with a DB id
      for (const row of rows) {
        if (row.dbId) {
          try {
            await logNoticeGenerated(row.dbId, { noticeLevel: row.noticeLevel, action: 'generate' });
          } catch (e) { console.warn('Timeline log failed for notice gen:', e); }
        }
      }

      try { await loadNotoSymbolsFont(); } catch (e) { console.warn('Noto font failed to load', e); }
      
      const settings = getHighQualityPdfSettings('portrait', 'pt', 'letter');
      const doc = new jsPDF(settings);
      
      try { registerNotoSymbolsFont(doc); } catch (e) { console.warn('Noto font register failed', e); }

      const pageWidth = doc.internal.pageSize.getWidth();
      const pageHeight = doc.internal.pageSize.getHeight();
      const margin = 60; const returnAddressX = 38; const returnAddressY = 45; 
      const footerHeight = 80; const maxY = pageHeight - footerHeight;
      const baseFontSize = parseInt(pdfConfig.fontSize) || 9;
      const baseFont = pdfConfig.font;

      const varsMap = {};
      (customVariables || []).forEach(v => { varsMap[v.variable_name.replace(/[{}]/g, '')] = v.predefined_value || ''; });
      varsMap['clientName'] = globalData.associationName;

      const drawText = (text, x, y, options = {}) => {
        const fontSize = options.fontSize || baseFontSize;
        const fontName = options.font || baseFont;
        const fontStyle = options.style || "normal";
        const align = options.align || "left";
        const color = options.color || "#000000";
        const maxWidth = options.maxWidth || (pageWidth - (margin * 2));

        doc.setFont(fontName, fontStyle);
        doc.setFontSize(fontSize);
        doc.setTextColor(color);

        const textOptions = { align: align, maxWidth: maxWidth };
        if (options.paragraph || align === "justify") {
          if (align === "justify") {
            doc.text(String(text || ''), x, y, { align: "justify", maxWidth, lineHeightFactor: 1.35 });
            const lines = doc.splitTextToSize(String(text || ''), maxWidth);
            return { height: lines.length * fontSize * 1.35 };
          }
        }
        doc.text(String(text || ''), x, y, textOptions); 
        return { width: doc.getTextWidth(String(text || '')), height: fontSize };
      };

      // Load ACM logo for footer
      let acmFooterImgData = null;
      try {
        const acmImg = new Image();
        acmImg.crossOrigin = 'anonymous';
        await new Promise((resolve, reject) => {
          acmImg.onload = resolve;
          acmImg.onerror = reject;
          // Use the imported acm-logo-full asset
          acmImg.src = acmLogoFullAsset;
        });
        const c = document.createElement('canvas');
        c.width = acmImg.naturalWidth; c.height = acmImg.naturalHeight;
        c.getContext('2d').drawImage(acmImg, 0, 0);
        acmFooterImgData = c.toDataURL('image/png');
      } catch(e) { console.warn('Failed to load ACM footer logo', e); }

      const drawFooter = async (pageNum, violationIdLabel) => {
        const footerY = pageHeight - 60; const centerX = pageWidth / 2;
        
        // ACM Company Logo
        if (acmFooterImgData) {
          try {
            const props = doc.getImageProperties(acmFooterImgData);
            const targetH = 28; const targetW = targetH * (props.width / props.height);
            doc.addImage(acmFooterImgData, 'PNG', centerX - (targetW / 2), footerY - 32, targetW, targetH);
          } catch(e) {}
        } else {
            drawText("ACM", centerX, footerY - 20, { align: "center", fontSize: 18, style: "bold", font: "times" });
            drawText("AVRIA COMMUNITY MANAGEMENT", centerX, footerY - 10, { align: "center", fontSize: 7, style: "bold" });
        }
        
        doc.setDrawColor(0); doc.setLineWidth(1);
        doc.line(margin, footerY, pageWidth - margin, footerY);

        // Subtle Violation ID in light grey at the very bottom-left of the page
        if (violationIdLabel) {
          const idText = `V-${String(violationIdLabel).substring(0, 8).toUpperCase()}`;
          doc.setFont(baseFont, "normal"); doc.setFontSize(6.5);
          doc.setTextColor("#9ca3af");
          doc.text(idText, margin, pageHeight - 8);
          doc.setTextColor("#000000");
        }

        // Association contact info below the line
        const infoY = footerY + 12;
        doc.setFontSize(7.5); doc.setTextColor("#333333"); doc.setFont(baseFont, "normal");
        const assocName = globalData.associationName;
        if (assocName) {
          drawText(assocName, centerX, infoY, { align: "center", fontSize: 7.5, style: "bold" });
        }
        const contactY = infoY + 10;
        const contactParts = [];
        if (globalData.managementPhone) contactParts.push(globalData.managementPhone);
        if (globalData.managementEmail) contactParts.push(globalData.managementEmail);
        if (globalData.managementWebsite) contactParts.push(globalData.managementWebsite);
        drawText(contactParts.join('  |  '), centerX, contactY, { align: "center", fontSize: 7 });
        
        doc.setFontSize(8); doc.setTextColor("#999999");
        drawText(String(pageNum), centerX, contactY + 12, { align: "center" });
      };

      const noticeJobs = rows.flatMap((row) =>
        getRowRecipients(row).map((recipient, addressIndex) => ({
          row,
          recipient,
          addressIndex,
        }))
      );

      for (let i = 0; i < noticeJobs.length; i++) {
        const { row, recipient, addressIndex } = noticeJobs[i];
        if (i > 0) doc.addPage();

        const ownerData = availableOwners.find(o => o.id === row.propertyId);
        const rowVars = { ...varsMap, propertyAddress: row.propertyAddress, ownerName: row.ownerName, currentDate: row.date, accountNumber: ownerData?.units?.account_number || 'N/A' };
        const resolvedAction = replaceVariables(row.requestedAction, rowVars);
        const resolvedCitation = replaceVariables(row.citation, rowVars);
        
        let raY = returnAddressY;
        doc.setFont(baseFont, "normal"); doc.setFontSize(baseFontSize);

        drawText(globalData.associationName, returnAddressX, raY); raY += 12;
        drawText(`c/o ${globalData.managementCompany}`, returnAddressX, raY); raY += 12;
        drawText(globalData.associationAddressLine1, returnAddressX, raY); raY += 12;
        drawText(globalData.associationAddressLine2, returnAddressX, raY);

        if (globalData.noticeLogo) {
          try {
            let newLogoW = 150;
            const props = doc.getImageProperties(globalData.noticeLogo);
            let newLogoH = newLogoW * (props.height / props.width);
            const maxLogoH = 75;
            if (newLogoH > maxLogoH) {
              newLogoH = maxLogoH;
              newLogoW = maxLogoH * (props.width / props.height);
            }
            const isPng = String(globalData.noticeLogo).startsWith("data:image/png");
            doc.addImage(globalData.noticeLogo, isPng ? "PNG" : "JPEG", pageWidth - margin - newLogoW, margin - 15, newLogoW, newLogoH);
          } catch(e) {}
        }
        
        const recipientAddressX = 60; let recipientAddressY = 175;
        const certifiedMailNum = (recipient.certifiedMail && String(recipient.certifiedMail).trim()) || (row.certifiedMail && String(row.certifiedMail).trim()) || '';
        if (certifiedMailNum) {
          doc.setFont(baseFont, 'bold'); doc.setFontSize(baseFontSize);
          doc.text(`CERTIFIED MAIL #: ${certifiedMailNum}`, recipientAddressX, recipientAddressY);
          recipientAddressY += 16;
          doc.setFont(baseFont, 'normal');
        }
        const mailFields = [];
        const recipientName = (recipient.name || '').trim() || row.ownerName || '';
        if (recipientName) mailFields.push(recipientName);
        
        const addressForPrint = recipient.address;

        if (addressForPrint && addressForPrint.trim().length > 0) {
            const lines = addressForPrint.split('\n');
            lines.forEach(l => { if (l.trim()) mailFields.push(l.trim()) });
        } else {
            mailFields.push("No address on file");
        }

        mailFields.forEach(line => { 
            if(line) {
                const splitLines = doc.splitTextToSize(String(line), 250);
                splitLines.forEach(sl => {
                   doc.text(sl, recipientAddressX, recipientAddressY); 
                   recipientAddressY += 12; 
                });
            }
        });

        const dateX = 550; const dateY = 150;
        drawText(row.date, dateX, dateY, { align: "right", style: "bold" });
        let currentY = Math.max(margin + 60, recipientAddressY) + 30; 

        const rePrefix = "Re:";
        const reTextPart = `${row.noticeType} - ${row.propertyAddress}`;
        drawText(rePrefix, margin, currentY, { style: "bold" });
        const rePrefixWidth = doc.getTextWidth(rePrefix);
        drawText(reTextPart, margin + rePrefixWidth + 8, currentY, { style: "bold" });
        currentY += 10;
        drawText(globalData.associationName, margin + rePrefixWidth + 8, currentY); 
        if (addressIndex > 0) {
          currentY += 12;
          drawText(`Additional copy: ${recipient.label || recipient.name || ''}`, margin + rePrefixWidth + 8, currentY, { fontSize: baseFontSize - 1, color: "#555555" });
        }
        currentY += 50; 

        const greetingName = recipient.kind === 'tenant'
          ? 'Current Tenants or Unknown Party in Possession'
          : (recipient.name || row.ownerName || 'Homeowner');
        drawText(`Dear ${greetingName}:`, margin, currentY); currentY += 15;
        const intro = getOpeningParagraph(row.noticeType, globalData.associationName, row.dueDate, row);
        const introHeight = drawText(intro, margin, currentY, { paragraph: true, align: "justify" }).height;
        currentY += introHeight + 15;

        const boxTop = currentY; const boxPadding = 15;
        doc.setFont(baseFont, "bold");
        doc.text(`${row.violationType}, ${row.articleSection}`, margin + boxPadding, currentY + boxPadding);
        const boxWidth = pageWidth - (margin * 2); const citationMaxWidth = boxWidth - (boxPadding * 2);
        const citationHeight = drawText(resolvedCitation, margin + boxPadding, currentY + boxPadding + 15, { maxWidth: citationMaxWidth, align: 'justify', style: 'italic' }).height;
        const boxHeight = 30 + citationHeight + 10; 
        doc.setDrawColor(0); doc.rect(margin, boxTop, boxWidth, boxHeight);
        currentY = boxTop + boxHeight + 15;

        doc.setFont(baseFont, "bold");
        drawText("Requested Action:", margin, currentY, { style: "bold" });
        doc.setFont(baseFont, "normal");
        const actionIndent = 100; const actionWidth = pageWidth - margin - actionIndent - margin; 
        const actionHeight = drawText(resolvedAction, margin + actionIndent, currentY, { maxWidth: actionWidth, align: 'justify' }).height;
        currentY += actionHeight + 10;

        doc.setFont(baseFont, "bold");
        drawText("Deadline:", margin, currentY, { style: "bold" });
        doc.setFont(baseFont, "normal");
        doc.text(String(row.dueDate || ''), margin + 100, currentY, { style: "underline" }); 
        const deadlineWidth = doc.getTextWidth(String(row.dueDate || ''));
        doc.line(margin + 100, currentY + 2, margin + 100 + deadlineWidth, currentY + 2);
        currentY += 20;

        const closing = "The Board's goal is to maintain a beautiful and well-kept neighborhood for all residents—supporting curb appeal and protecting property values. If you've already taken care of this or believe this notice may be in error, feel free to contact us via call or text at " + globalData.managementPhone + ", or email us at " + globalData.managementEmail + ". We also encourage all homeowners to register for online portal access for easy communication and updates.";
        const violationIdLabel = row.dbId || row.id;
        if (currentY + 100 > maxY) { await drawFooter(doc.internal.getNumberOfPages(), violationIdLabel); doc.addPage(); currentY = margin; }
        const closingH = drawText(closing, margin, currentY, { paragraph: true, align: "justify" }).height;
        currentY += closingH + 20;

        if (currentY + 60 > maxY) { await drawFooter(doc.internal.getNumberOfPages(), violationIdLabel); doc.addPage(); currentY = margin; }
        drawText("Thank you for your attention and cooperation!", margin, currentY); currentY += 20;
        drawText("Warm Regards,", margin, currentY); currentY += 12;
        drawText(globalData.managementCompany, margin, currentY); currentY += 12;
        drawText(`Managing Agent for ${globalData.associationName}`, margin, currentY);
        await drawFooter(doc.internal.getNumberOfPages(), violationIdLabel);

        // --- New Page for Evidence & Response QR ---
        doc.addPage();
        currentY = margin;
        
        doc.setFont(baseFont, "italic"); doc.setFontSize(baseFontSize - 1);
        const combinedDisclaimer = "Disclaimer: Please note that the documented photographs may not capture every area of the property where this violation may apply. The homeowner is responsible for curing any portion of the property that falls within the scope of the cited violation (for example, if weeds are present in multiple areas, not all of which are shown in the photographs). It is the owner's responsibility to notify management once corrective action has been taken so the violation may be reviewed and resolved. Failure to correct the violation by the deadline may result in referral to the Association's fining committee for possible fines and/or other remedies as allowed under Florida Stat. §720 and the governing documents.";
        const d1H = drawText(combinedDisclaimer, margin, currentY, { paragraph: true, align: "justify" }).height;
        currentY += d1H + 30;

        doc.setFont(baseFont, "normal"); doc.setFontSize(10);
        drawText(row.noticeType, margin, currentY, { style: "bold" });
        drawText(row.date, pageWidth - margin, currentY, { align: "right", style: "bold" });
        currentY += 15;
        drawText(row.propertyAddress, margin, currentY);
        currentY += 25;

        doc.setFont(baseFont, "bold");
        drawText("Requested Action:", margin, currentY, { style: "bold" });
        doc.setFont(baseFont, "normal");
        const actionHeight2 = drawText(resolvedAction, margin, currentY + 15, { maxWidth: pageWidth - margin - 40, align: 'justify' }).height;
        currentY += 15 + actionHeight2 + 10;

        doc.setFont(baseFont, "bold");
        drawText("Deadline:", margin, currentY, { style: "bold" });
        doc.setFont(baseFont, "normal");
        doc.text(String(row.dueDate || ''), margin, currentY + 15);
        currentY += 35;

        doc.setFont(baseFont, "bold"); doc.setFontSize(11);
        drawText("Violation Evidence & Response Portal:", margin, currentY);
        currentY += 20;

        // Dynamic Grid Logic for Photos + QR
        const photoData = (row.photos || []).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);
        // Constrain single item width
        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 grid + captions + bottom disclaimer fit above footer
        const bottomReserve = footerHeight + 80; // footer + disclaimer + padding
        const availableGridHeight = pageHeight - currentY - bottomReserve;
        const captionSpace = 20; // space for timestamp text below each photo
        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(baseFont, '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(baseFont, 'normal'); doc.setTextColor(100);
            doc.text(tsText, pos.x, pos.y + cellH + 12);
        }

        // Draw QR Code
        const qrPos = positions[photoData.length];
        doc.setDrawColor(200); doc.setFillColor(255); 
        doc.rect(qrPos.x, qrPos.y, cellW, cellH, 'FD');

        const violationId = row.dbId || row.id;
        const appUrl = "https://avria.cloud";
        const qrUrl = `${appUrl}/violation/${violationId}`;
        
        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(baseFont, "bold"); doc.setFontSize(10); doc.setTextColor("#000000");
            drawText("Scan to respond", qrPos.x + (cellW/2), qrY + qrSize + 15, { align: "center", style: "bold" });
            doc.setFontSize(8); doc.setFont(baseFont, "normal"); doc.setTextColor(100);
            drawText("Submit status updates securely online", qrPos.x + (cellW/2), qrY + qrSize + 27, { align: "center" });
        } catch (qrErr) {
            console.error("Failed to draw QR in PDF", qrErr);
        }

        const botDisc = "This notice is issued in accordance with the Association's governing documents and Florida Statutes. Nothing herein waives or limits the Association's rights and remedies, all of which are expressly reserved.";
        const finalY = pageHeight - footerHeight - 30;
        doc.setFont(baseFont, "italic"); doc.setFontSize(8); doc.setTextColor("#000000");
        doc.setLineWidth(0.5); doc.line(margin, finalY - 10, pageWidth - margin, finalY - 10);
        drawText(botDisc, margin, finalY, { paragraph: true, align: "justify" });
        await drawFooter(doc.internal.getNumberOfPages(), violationIdLabel);
      } 

      if (action === 'blob') return doc.output('blob');
      const filenameDate = new Date().toISOString().slice(0,10);
      doc.save(`Violation_Notices_${filenameDate}.pdf`);
      toast({ title: "PDF Generated", description: "Violation notices PDF has been successfully created." });
      if (onClose) onClose();
    } catch (err) {
      console.error("PDF Error:", err);
      toast({ variant: "destructive", title: "PDF Error", description: "Failed to generate PDF. Please try again." });
    } finally {
      setLoading(false);
    }
  };

  const insertVariable = (rowId, field, variableName) => {
    const row = rows.find(r => r.id === rowId);
    if (!row) return;
    const currentVal = row[field] || '';
    const newVal = currentVal + (variableName.startsWith('{{') ? variableName : `{{${variableName}}}`);
    handleRowChange(rowId, field, newVal);
  };

  const VariableButton = ({ rowId, field }) => (
    <Popover>
        <PopoverTrigger asChild>
            <Button variant="ghost" size="icon" className="h-6 w-6 ml-1 text-slate-400 hover:text-blue-600" title="Insert Variable">
                <Braces className="w-3 h-3" />
            </Button>
        </PopoverTrigger>
        <PopoverContent className="w-80 p-0" align="start">
            <CustomVariablesInserter 
                clientId={selectedClientId} 
                onSelect={(varName) => insertVariable(rowId, field, varName)}
                embedMode={true}
            />
        </PopoverContent>
    </Popover>
  );

  return (
    <div className="w-full max-w-5xl mx-auto px-2 sm:px-4 py-4 sm:py-8 space-y-8 overflow-x-hidden forms-page-compact-wrapper">
      <Card className="shadow-lg border-none bg-slate-50/50">
        <CardHeader className="bg-white border-b rounded-t-lg px-6 py-4 flex flex-row items-center justify-between">
          <CardTitle className="text-xl flex items-center gap-2">
             <AlertCircle className="w-6 h-6 text-red-600" />
             Violation Notice Generator
          </CardTitle>
          {onClose && (
            <Button variant="ghost" size="icon" onClick={onClose}>
               <X className="w-5 h-5 text-slate-500" />
            </Button>
          )}
        </CardHeader>
        <CardContent className="p-6 space-y-8">
          <div className="space-y-6">
            <div className="flex items-center gap-2 mb-2">
                <Settings className="w-4 h-4 text-slate-400" />
                <h3 className="font-bold text-slate-800 uppercase tracking-wider text-xs">Association Settings</h3>
            </div>
            <div className="grid grid-cols-1 md:grid-cols-2 gap-6 p-6 bg-white rounded-xl border shadow-sm">
              <div className="space-y-2">
                <Label className="text-sm font-semibold">Association (Client)</Label>
                <Select onValueChange={handleClientChange} value={selectedClientId}>
                  <SelectTrigger className="w-full bg-slate-50/50"><SelectValue placeholder="Select Association" /></SelectTrigger>
                  <SelectContent>{clients.map((client) => (<SelectItem key={client.id} value={client.id}>{client.name}</SelectItem>))}</SelectContent>
                </Select>
              </div>
              <div className="space-y-2">
                <Label className="text-sm font-semibold">P.O. Box Address</Label>
                <Select value={globalData.associationAddressLine1} onValueChange={handlePOBoxChange}>
                  <SelectTrigger className="w-full bg-slate-50/50"><SelectValue placeholder="Select P.O. Box" /></SelectTrigger>
                  <SelectContent>
                    <SelectItem value="P.O. Box 560060">P.O. Box 560060</SelectItem>
                    <SelectItem value="P.O. Box 560099">P.O. Box 560099</SelectItem>
                  </SelectContent>
                </Select>
              </div>
            </div>
          </div>
          <Separator className="bg-slate-200" />
          <div className="space-y-8">
            <div className="flex items-center justify-between">
                <div className="flex items-center gap-2">
                    <FileText className="w-4 h-4 text-slate-400" />
                    <h3 className="font-bold text-slate-800 uppercase tracking-wider text-xs">Violation Records ({rows.length})</h3>
                </div>
                <Button variant="outline" size="sm" onClick={addRow} className="gap-2 bg-white hover:bg-slate-50"><Plus className="w-4 h-4 text-blue-600" /> Add Violation</Button>
            </div>
            <div className="space-y-12">
              {rows.map((row, index) => {
                const currentPhotoCount = row.photos?.length || 0;
                const canAddPhotos = currentPhotoCount < 3;

                return (
                <div key={row.id} className="relative p-1">
                  <Card className="border-2 border-slate-200 shadow-md relative overflow-visible bg-white">
                    <div className="absolute -top-4 -left-4 w-10 h-10 bg-slate-800 text-white rounded-full flex items-center justify-center font-bold text-sm shadow-md border-4 border-white z-10">{index + 1}</div>
                    <div className="absolute -top-3 right-4 z-10 flex gap-2">
                        <Button variant="destructive" size="sm" onClick={() => removeRow(row.id)} className="h-7 rounded-full shadow-lg"><Trash2 className="w-3.5 h-3.5 mr-1" /> Remove</Button>
                    </div>
                    <CardContent className="p-8 space-y-8">
                      <div className="space-y-4">
                        <div className="flex items-center gap-2 text-slate-500 mb-2"><User className="w-4 h-4" /><span className="text-xs font-bold uppercase">Owner & Mailing Details</span></div>
                        <div className="grid grid-cols-1 md:grid-cols-2 gap-6">
                            <div className="space-y-2">
                                <Label className="text-xs font-semibold">1. Search & Select Property</Label>
                                <AddressCombobox row={row} availableOwners={availableOwners} onSelect={(val) => handleOwnerSelect(row.id, val)} />
                            </div>
                            <div className="space-y-2">
                                <Label className="text-xs font-semibold">Owner Name (Editable)</Label>
                                <Input placeholder="e.g. John Doe" value={row.ownerName} onChange={(e) => handleRowChange(row.id, 'ownerName', e.target.value)} className="w-full bg-slate-50/50 focus:bg-white" />
                            </div>
                            <div className="space-y-2">
                                <Label className="text-xs font-semibold">Property Address (Editable)</Label>
                                <Textarea placeholder="Property location used in letter body..." value={row.propertyAddress} onChange={(e) => handleRowChange(row.id, 'propertyAddress', e.target.value)} className="w-full bg-slate-50/50 min-h-[40px] h-10 resize-none py-2" />
                            </div>
                            {/* Recipients editor — one notice per recipient */}
                            <div className="md:col-span-2 bg-slate-50 p-4 rounded-md border border-slate-200 space-y-3">
                                <div className="flex items-center justify-between">
                                  <Label className="text-[10px] uppercase font-bold text-slate-500 block">
                                    Notice Recipients ({(row.recipients || []).length})
                                  </Label>
                                  <div className="flex gap-2">
                                    <Button type="button" variant="outline" size="sm" className="h-7 text-xs" onClick={() => regenerateRecipients(row.id)}>
                                      Auto-populate
                                    </Button>
                                    <Button type="button" variant="outline" size="sm" className="h-7 text-xs gap-1" onClick={() => addRecipient(row.id)}>
                                      <Plus className="w-3 h-3" /> Add
                                    </Button>
                                  </div>
                                </div>
                                <p className="text-[11px] text-slate-500 leading-snug">
                                  Each recipient gets its own notice page. When an owner lives elsewhere, a notice to
                                  "Current Tenants or Unknown Party in Possession" is auto-added for the property address.
                                </p>
                                {(row.recipients || []).length === 0 ? (
                                  <div className="flex items-center gap-2 text-amber-700 bg-amber-50 p-2 rounded text-xs border border-amber-200">
                                    <AlertCircle className="w-4 h-4 shrink-0" />
                                    <span>No recipients yet. Click Auto-populate or Add to begin.</span>
                                  </div>
                                ) : (
                                  <div className="space-y-3">
                                    {(row.recipients || []).map((rc, rcIdx) => (
                                      <div key={rc.rid} className="bg-white border border-slate-200 rounded-md p-3 space-y-2">
                                        <div className="flex items-center justify-between">
                                          <span className="text-[10px] font-bold uppercase text-slate-500">
                                            #{rcIdx + 1} · {rc.label || rc.kind || 'Recipient'}
                                          </span>
                                          <Button type="button" variant="ghost" size="icon" className="h-6 w-6 text-red-500 hover:bg-red-50" onClick={() => removeRecipient(row.id, rc.rid)}>
                                            <Trash2 className="w-3.5 h-3.5" />
                                          </Button>
                                        </div>
                                        <div className="grid grid-cols-1 md:grid-cols-2 gap-2">
                                          <div className="space-y-1">
                                            <Label className="text-[10px] font-semibold text-slate-500">Recipient Name</Label>
                                            <Input
                                              value={rc.name || ''}
                                              onChange={(e) => updateRecipient(row.id, rc.rid, 'name', e.target.value)}
                                              placeholder="e.g. John Doe or Current Tenants…"
                                              className="h-8 text-sm"
                                            />
                                          </div>
                                          <div className="space-y-1">
                                            <Label className="text-[10px] font-semibold text-slate-500">Certified Mail #</Label>
                                            <Input
                                              value={rc.certifiedMail || ''}
                                              onChange={(e) => updateRecipient(row.id, rc.rid, 'certifiedMail', e.target.value)}
                                              placeholder="Optional"
                                              className="h-8 text-sm font-mono"
                                            />
                                          </div>
                                        </div>
                                        <div className="space-y-1">
                                          <Label className="text-[10px] font-semibold text-slate-500">Mailing Address</Label>
                                          <Textarea
                                            value={rc.address || ''}
                                            onChange={(e) => updateRecipient(row.id, rc.rid, 'address', e.target.value)}
                                            placeholder="Street&#10;City, ST ZIP"
                                            className="text-sm min-h-[52px] resize-none"
                                          />
                                        </div>
                                      </div>
                                    ))}
                                  </div>
                                )}
                            </div>
                        </div>
                      </div>
                      <Separator />
                      <div className="space-y-6">
                        <div className="flex items-center gap-2 text-slate-500 mb-2"><Calendar className="w-4 h-4" /><span className="text-xs font-bold uppercase">Violation Timeline & Type</span></div>
                        <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6">
                            <div className="space-y-2"><Label className="text-xs font-semibold">Notice Date</Label><Input value={row.date} onChange={(e) => handleRowChange(row.id, 'date', e.target.value)} className="w-full" /></div>
                            <div className="space-y-2"><Label className="text-xs font-semibold text-red-600">Due Date (Deadline)</Label><Input value={row.dueDate} onChange={(e) => handleRowChange(row.id, 'dueDate', e.target.value)} className="w-full border-red-100 text-red-700 font-medium" /></div>
                            <div className="space-y-2"><Label className="text-xs font-semibold">Notice Level</Label><Select value={row.noticeLevel} onValueChange={(val) => handleNoticeLevelChange(row.id, val)}><SelectTrigger className="w-full"><SelectValue /></SelectTrigger><SelectContent>{[NOTICE_LEVELS.FIRST, NOTICE_LEVELS.SECOND, NOTICE_LEVELS.THIRD].slice(0, row.stageCount || 3).map(lvl => (<SelectItem key={lvl} value={lvl}>{lvl}</SelectItem>))}</SelectContent></Select></div>
                            <div className="space-y-2"><Label className="text-xs font-semibold">Violation Category</Label><Input value={row.violationType} onChange={(e) => handleRowChange(row.id, 'violationType', e.target.value)} className="w-full" placeholder="e.g. Landscaping" /></div>
                        </div>
                        {availableViolationTypes.length > 0 && (
                             <div className="bg-blue-50/50 p-4 rounded-lg border border-blue-100 flex flex-col sm:flex-row sm:items-center justify-between gap-4">
                                 <div><p className="text-xs font-bold text-blue-700 mb-1">Populate from Template</p><p className="text-[10px] text-blue-600/70">Fill category, article, and citation text instantly.</p></div>
                                 <Select onValueChange={(val) => handleSelectViolationType(row.id, val)}><SelectTrigger className="w-full sm:w-64 h-9 bg-white border-blue-200"><SelectValue placeholder="Select Citation Template..." /></SelectTrigger><SelectContent>{availableViolationTypes.map(v => (<SelectItem key={v.id} value={v.id}>{v.category}</SelectItem>))}</SelectContent></Select>
                             </div>
                        )}
                        <div className="space-y-2"><Label className="text-xs font-semibold">Article/Section</Label><Input value={row.articleSection} onChange={(e) => handleRowChange(row.id, 'articleSection', e.target.value)} className="w-full bg-slate-50/50" /></div>
                      </div>
                      <Separator />
                      <div className="grid grid-cols-1 lg:grid-cols-2 gap-8">
                        <div className="space-y-3">
                            <div className="flex items-center justify-between"><Label className="text-xs font-bold uppercase text-slate-500">Legal Citation</Label><VariableButton rowId={row.id} field="citation" /></div>
                            <Textarea value={row.citation} onChange={(e) => handleRowChange(row.id, 'citation', e.target.value)} placeholder="CC&R text..." className="min-h-[180px] w-full font-mono text-xs bg-slate-50" />
                        </div>
                        <div className="space-y-3">
                            <div className="flex items-center justify-between"><Label className="text-xs font-bold uppercase text-slate-500">Requested Action</Label><VariableButton rowId={row.id} field="requestedAction" /></div>
                            <Textarea value={row.requestedAction} onChange={(e) => handleRowChange(row.id, 'requestedAction', e.target.value)} placeholder="Remediation steps..." className="min-h-[180px] w-full bg-slate-50" />
                        </div>
                      </div>
                      <div className="space-y-4 pt-4 border-t border-slate-100">
                        <div className="flex items-center justify-between">
                            <Label className="text-sm font-bold text-slate-800 flex items-center gap-2"><ImageIcon className="w-4 h-4 text-blue-500" /> Evidence Photos</Label>
                            <div className="flex items-center gap-3">
                                <span className={`text-xs font-bold ${currentPhotoCount >= 3 ? 'text-red-500' : 'text-slate-500'}`}>{currentPhotoCount} / 3 Photos</span>
                                <div className="relative">
                                    <Input type="file" multiple accept="image/*" className={`absolute inset-0 w-full h-full opacity-0 z-10 ${canAddPhotos ? 'cursor-pointer' : 'cursor-not-allowed'}`} onChange={(e) => handlePhotoUpload(row.id, e)} disabled={!canAddPhotos} />
                                    <Button variant={canAddPhotos ? "outline" : "secondary"} size="sm" className="gap-2 pointer-events-none relative z-0"><Upload className="w-4 h-4" /> Add Photos</Button>
                                </div>
                            </div>
                        </div>
                        {row.photos && row.photos.length > 0 ? (
                            <div className="photo-grid-layout mt-4">
                                {row.photos.map((p, pIdx) => {
                                  const imgSrc = typeof p === 'string' ? p : (p?.url || p?.src);
                                  if (!imgSrc) return null;
                                  return (
                                      <div key={pIdx} className="photo-grid-item relative group">
                                          <div className="absolute top-2 right-2 z-10 opacity-0 group-hover:opacity-100 transition-opacity">
                                              <Button variant="destructive" size="icon" className="h-6 w-6" onClick={() => removePhoto(row.id, pIdx)}><X className="w-3 h-3" /></Button>
                                          </div>
                                          <div className="w-full aspect-[4/3] rounded-md overflow-hidden bg-slate-200 flex items-center justify-center">
                                              <img src={imgSrc} alt={`Violation Evidence ${pIdx + 1}`} className="photo-grid-image" />
                                          </div>
                                      </div>
                                  );
                                })}
                            </div>
                        ) : (
                            <div className="w-full border-2 border-dashed border-slate-200 rounded-lg p-8 flex flex-col items-center justify-center text-slate-400 bg-slate-50">
                                <ImageIcon className="w-8 h-8 mb-2 opacity-50" />
                                <p className="text-sm font-medium">No photos uploaded yet</p>
                            </div>
                        )}
                      </div>
                    </CardContent>
                  </Card>
                </div>
              )})}
            </div>
            <div className="flex justify-center pt-4">
               <Button variant="outline" size="lg" onClick={addRow} className="gap-2 border-2 border-dashed border-slate-300 w-full md:w-auto px-12 transition-all"><Plus className="w-5 h-5" /> Add Another Notice</Button>
            </div>
          </div>
          <Separator className="bg-slate-200" />
          <div className="flex flex-col lg:flex-row justify-between items-center bg-slate-800 text-white p-6 rounded-2xl gap-6 shadow-xl">
             <div className="flex items-center space-x-3 bg-slate-700/50 px-4 py-2.5 rounded-full border border-slate-600">
                <Checkbox id="autoSave" checked={autoSaveToLog} onCheckedChange={setAutoSaveToLog} className="border-slate-400" />
                <Label htmlFor="autoSave" className="text-sm font-medium cursor-pointer text-slate-200">Auto-sync to <span className="text-blue-400 font-bold">Violation Log</span></Label>
            </div>
            <div className="flex flex-wrap justify-center items-center gap-3">
                {onClose && (<Button variant="ghost" className="text-white hover:bg-slate-700 rounded-full h-11 px-6 font-semibold" onClick={onClose} disabled={loading}>Cancel</Button>)}
                <Button onClick={() => generatePDF()} disabled={loading || isSaving} className="bg-blue-600 hover:bg-blue-500 text-white rounded-full h-11 px-8 font-bold shadow-lg shadow-blue-900/20">{loading ? <Loader2 className="w-5 h-5 animate-spin mr-2" /> : <FileDown className="w-4 h-4 mr-2" />}{loading ? 'Generating PDF...' : 'Download Notices (PDF)'}</Button>
            </div>
          </div>
        </CardContent>
      </Card>
    </div>
  );
}
