import { useState, useEffect, useRef, useMemo, useCallback } from "react";
import { formatMailingAddress } from '@/lib/formatMailingAddress';
import { FileText, Download, Users, Search, Plus, Save, Eye, FolderOpen, Trash2, UserPlus, X, FolderUp, CalendarIcon, Building2 } from "lucide-react";
import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover";
import { Calendar } from "@/components/ui/calendar";
import { cn } from "@/lib/utils";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
import { Card, CardContent } from "@/components/ui/card";
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
import { Separator } from "@/components/ui/separator";
import { ScrollArea } from "@/components/ui/scroll-area";
import { Checkbox } from "@/components/ui/checkbox";
import { Textarea } from "@/components/ui/textarea";
import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogTrigger } from "@/components/ui/dialog";
import { useToast } from "@/hooks/use-toast";
import { supabase } from "@/integrations/supabase/client";
import { useSavedFormTemplates } from "@/hooks/useSavedFormTemplates";

import ReactQuill, { Quill } from "react-quill-new";
import "react-quill-new/dist/quill.snow.css";
import jsPDF from "jspdf";
import autoTable from "jspdf-autotable";
import QRCode from "qrcode";
import { format } from "date-fns";
import { formatDateLongEST, toEST } from "@/lib/timezoneUtils";
// html-to-text no longer needed for body rendering
import SaveToDocumentsDialog from "@/components/documents/SaveToDocumentsDialog";

// Generate a 10-char sign-up code (matches SignupCodesPage format)
function generateSignupCodeStr() {
  const chars = "ABCDEFGHJKLMNPQRSTUVWXYZ23456789";
  let out = "";
  for (let i = 0; i < 10; i++) out += chars[Math.floor(Math.random() * chars.length)];
  return out;
}

// Filter helper used when assigning per-recipient sign-up codes
function selectedOwnerObjsForCodes(owners, selectedOwners, excludeElectronicConsent) {
  return owners.filter(o => selectedOwners.includes(o.id) && !(excludeElectronicConsent && o.electronic_consent));
}

// Register custom fonts with Quill
const Font = Quill.import("formats/font");
Font.whitelist = ["arial", "times-new-roman", "georgia", "courier-new", "verdana", "trebuchet-ms"];
Quill.register(Font, true);

// Register font sizes
const Size = Quill.import("attributors/style/size");
Size.whitelist = ["9px", "10px", "11px", "12px", "14px", "16px", "18px", "20px", "24px"];
Quill.register(Size, true);

// Custom page break blot
const BlockEmbed = Quill.import("blots/block/embed");
class PageBreakBlot extends BlockEmbed {
  static blotName = "pageBreak";
  static tagName = "div";
  static className = "page-break-marker";

  static create() {
    const node = super.create();
    node.setAttribute("contenteditable", "false");
    node.dataset.pageBreak = "true";
    return node;
  }

  static value() {
    return true;
  }
}
Quill.register(PageBreakBlot);

// Map Quill font class names to jsPDF font names
const FONT_MAP = {
  "arial": "helvetica",
  "times-new-roman": "times",
  "georgia": "times",
  "courier-new": "courier",
  "verdana": "helvetica",
  "trebuchet-ms": "helvetica",
};

// Parse a CSS size string to pt for jsPDF
const cssSizeToPt = (sizeStr) => {
  if (!sizeStr) return null;
  const num = parseFloat(sizeStr);
  if (isNaN(num)) return null;
  if (sizeStr.includes("px")) return num * 0.75; // px to pt
  if (sizeStr.includes("pt")) return num;
  return num * 0.75;
};

// Parse hex/rgb color string to [r,g,b]
const parseColor = (color) => {
  if (!color) return null;
  if (color.startsWith("#")) {
    const hex = color.slice(1);
    if (hex.length === 3) {
      return [parseInt(hex[0]+hex[0],16), parseInt(hex[1]+hex[1],16), parseInt(hex[2]+hex[2],16)];
    }
    if (hex.length === 6) {
      return [parseInt(hex.slice(0,2),16), parseInt(hex.slice(2,4),16), parseInt(hex.slice(4,6),16)];
    }
  }
  const rgbMatch = color.match(/rgb\((\d+),\s*(\d+),\s*(\d+)\)/);
  if (rgbMatch) return [+rgbMatch[1], +rgbMatch[2], +rgbMatch[3]];
  return null;
};

// Extract alignment from element classes/styles
const getBlockAlignment = (el) => {
  for (const cls of el.classList) {
    if (cls === "ql-align-center") return "center";
    if (cls === "ql-align-right") return "right";
    if (cls === "ql-align-justify") return "justify";
  }
  if (el.style.textAlign) return el.style.textAlign;
  return "left";
};

// Extract indent level from element classes
const getBlockIndent = (el) => {
  for (const cls of el.classList) {
    const m = cls.match(/^ql-indent-(\d+)$/);
    if (m) return parseInt(m[1]);
  }
  return 0;
};

// Parse a table element into a block
const parseTable = (tableEl, processNode) => {
  const rows = [];
  const headerRows = [];
  const trElements = tableEl.querySelectorAll("tr");
  
  trElements.forEach((tr) => {
    const cells = [];
    const isHeader = tr.parentElement?.tagName?.toLowerCase() === "thead" || 
                     tr.querySelector("th") !== null;
    
    for (const cell of tr.children) {
      const tag = cell.tagName.toLowerCase();
      if (tag === "td" || tag === "th") {
        const segs = processNode(cell, { font: "helvetica", size: 9 });
        const text = segs.map(s => s.text || "").join("").trim();
        cells.push({
          content: text,
          isHeader: tag === "th" || isHeader,
          segments: segs.filter(s => !s.pageBreak && !s.qrImage),
          colSpan: parseInt(cell.getAttribute("colspan") || "1"),
          rowSpan: parseInt(cell.getAttribute("rowspan") || "1"),
        });
      }
    }
    
    if (isHeader) headerRows.push(cells);
    else rows.push(cells);
  });

  // Get border style from table
  const borderAttr = tableEl.getAttribute("border");
  const tableStyle = tableEl.style;
  let showBorders = true; // default
  if (borderAttr === "0" || tableStyle.borderCollapse === "collapse" && tableStyle.border === "none") {
    showBorders = false;
  }

  return { type: "table", headerRows, bodyRows: rows, showBorders };
};

// Parse Quill HTML into styled segments for jsPDF rendering
const parseHtmlToSegments = (html) => {
  const parser = new DOMParser();
  const doc = parser.parseFromString(`<div>${html || ""}</div>`, "text/html");
  const root = doc.body.firstChild;
  const blocks = [];

  const processNode = (node, inherited) => {
    if (node.nodeType === Node.TEXT_NODE) {
      const text = node.textContent;
      if (text) return [{ text, ...inherited }];
      return [];
    }

    if (node.nodeType !== Node.ELEMENT_NODE) return [];

    const el = node;
    const tagName = el.tagName.toLowerCase();

    // QR code inline image
    if (tagName === "img" && el.classList.contains("qr-code-inline")) {
      return [{ qrImage: true, src: el.getAttribute("src"), url: el.getAttribute("data-qr-url") }];
    }

    // Page break marker
    if (el.classList.contains("page-break-marker") || el.dataset?.pageBreak === "true") {
      return [{ pageBreak: true }];
    }

    // Build style overrides
    const style = { ...inherited };

    // Font from Quill class (ql-font-xxx)
    for (const cls of el.classList) {
      if (cls.startsWith("ql-font-")) {
        const fontKey = cls.replace("ql-font-", "");
        style.font = FONT_MAP[fontKey] || "helvetica";
      }
    }

    // Inline style font-family
    if (el.style.fontFamily) {
      const ff = el.style.fontFamily.toLowerCase().replace(/['"]/g, "").trim();
      if (ff.includes("times")) style.font = "times";
      else if (ff.includes("courier")) style.font = "courier";
      else style.font = "helvetica";
    }

    // Size from inline style
    if (el.style.fontSize) {
      const pt = cssSizeToPt(el.style.fontSize);
      if (pt) style.size = pt;
    }

    // Size from Quill class (ql-size-xxx)
    for (const cls of el.classList) {
      if (cls.startsWith("ql-size-")) {
        const pt = cssSizeToPt(cls.replace("ql-size-", ""));
        if (pt) style.size = pt;
      }
    }

    // Color
    if (el.style.color) {
      const c = parseColor(el.style.color);
      if (c) style.color = c;
    }

    // Bold/italic
    if (tagName === "strong" || tagName === "b") style.bold = true;
    if (tagName === "em" || tagName === "i") style.italic = true;
    if (tagName === "u") style.underline = true;
    if (tagName === "s" || tagName === "strike" || tagName === "del") style.strikethrough = true;

    // Headers
    if (tagName === "h1") { style.size = 18; style.bold = true; }
    if (tagName === "h2") { style.size = 14; style.bold = true; }
    if (tagName === "h3") { style.size = 12; style.bold = true; }

    // Recurse into children
    const segments = [];
    for (const child of el.childNodes) {
      segments.push(...processNode(child, style));
    }
    return segments;
  };

  // Walk top-level block elements
  const walkBlocks = (container) => {
    for (const child of container.childNodes) {
      if (child.nodeType === Node.TEXT_NODE) {
        const text = child.textContent?.trim();
        if (text) blocks.push({ type: "text", segments: [{ text, font: "helvetica", size: 9 }], align: "left", indent: 0 });
        continue;
      }
      if (child.nodeType !== Node.ELEMENT_NODE) continue;

      const el = child;
      const tag = el.tagName.toLowerCase();

      // Page break
      if (el.classList.contains("page-break-marker") || el.dataset?.pageBreak === "true") {
        blocks.push({ type: "pageBreak" });
        continue;
      }

      // Tables
      if (tag === "table") {
        blocks.push(parseTable(el, processNode));
        continue;
      }

      // Lists
      if (tag === "ol" || tag === "ul") {
        let idx = 0;
        for (const li of el.children) {
          if (li.tagName.toLowerCase() === "li") {
            idx++;
            const liAlign = getBlockAlignment(li);
            const liIndent = getBlockIndent(li);
            const segs = processNode(li, { font: "helvetica", size: 9 });
            blocks.push({
              type: "text",
              segments: segs.filter(s => !s.pageBreak),
              listType: tag === "ol" ? "ordered" : "bullet",
              listIndex: idx,
              indent: liIndent || parseInt(el.classList.toString().match(/ql-indent-(\d+)/)?.[1] || "0"),
              align: liAlign,
            });
          }
        }
        continue;
      }

      // Regular block — extract alignment and indent
      const blockAlign = getBlockAlignment(el);
      const blockIndent = getBlockIndent(el);

      const segments = processNode(el, { font: "helvetica", size: 9 });
      const pageBreaks = segments.filter(s => s.pageBreak);
      const qrImages = segments.filter(s => s.qrImage);
      const textSegs = segments.filter(s => !s.pageBreak && !s.qrImage);

      if (pageBreaks.length > 0) {
        blocks.push({ type: "pageBreak" });
      }
      qrImages.forEach(qr => {
        blocks.push({ type: "qrImage", src: qr.src, url: qr.url });
      });
      if (textSegs.length > 0 || tag === "p" || tag === "br") {
        blocks.push({ 
          type: "text", 
          segments: textSegs.length > 0 ? textSegs : [{ text: "" }],
          align: blockAlign,
          indent: blockIndent,
        });
      }
    }
  };

  walkBlocks(root);
  return blocks;
};

export default function LetterGenerator() {
  const { toast } = useToast();
  const quillRef = useRef(null);
  const [associations, setAssociations] = useState([]);
  const [selectedAssociation, setSelectedAssociation] = useState("");
  const [association, setAssociation] = useState(null);
  const [owners, setOwners] = useState([]);
  const [selectedOwners, setSelectedOwners] = useState([]);
  const [ownerSearch, setOwnerSearch] = useState("");
  const [excludeElectronicConsent, setExcludeElectronicConsent] = useState(false);
  const [subject, setSubject] = useState("");
  const [certifiedMail, setCertifiedMail] = useState("");
  const [templateName, setTemplateName] = useState("");
  const [editorContent, setEditorContent] = useState("");

  // Custom variables for the association
  const [customVars, setCustomVars] = useState([]);

  // Manual recipients
  const [manualRecipients, setManualRecipients] = useState([]);
  const [showManualDialog, setShowManualDialog] = useState(false);
  const [manualName, setManualName] = useState("");
  const [manualAddress, setManualAddress] = useState("");

  // Saved letters
  const [savedLetters, setSavedLetters] = useState([]);
  const [showSavedDialog, setShowSavedDialog] = useState(false);
  const [letterName, setLetterName] = useState("");
  const [currentLetterId, setCurrentLetterId] = useState(null);
  const [showSaveToDocsDialog, setShowSaveToDocsDialog] = useState(false);
  const [showSaveToResidentDialog, setShowSaveToResidentDialog] = useState(false);
  const [assocLogoWidth, setAssocLogoWidth] = useState(200);
  const [letterDate, setLetterDate] = useState(new Date());
  const [useCompanySender, setUseCompanySender] = useState(false);
  const [companySettings, setCompanySettings] = useState({});

  const templates = useSavedFormTemplates("letter");

  const getFormData = () => ({ selectedAssociation, subject, certifiedMail, templateName, editorContent });
  const applyFormData = (d) => {
    if (d.selectedAssociation) setSelectedAssociation(d.selectedAssociation);
    if (d.subject !== undefined) setSubject(d.subject);
    if (d.certifiedMail !== undefined) setCertifiedMail(d.certifiedMail);
    if (d.templateName !== undefined) setTemplateName(d.templateName);
    if (d.editorContent !== undefined) setEditorContent(d.editorContent);
  };
  const handleSaveTemplate = () => templates.saveTemplate(templateName || subject || "Untitled Letter", getFormData(), selectedAssociation || null);
  const handleLoadTemplate = async (id) => { const r = await templates.loadTemplate(id); if (r?.form_data) applyFormData(r.form_data); };
  const handleNewTemplate = () => { templates.newTemplate(); setSelectedAssociation(""); setSelectedOwners([]); setSubject(""); setCertifiedMail(""); setTemplateName(""); setEditorContent(""); setManualRecipients([]); setCurrentLetterId(null); setLetterName(""); };

  // Insert page break handler
  const insertPageBreak = useCallback(() => {
    const quill = quillRef.current?.getEditor();
    if (!quill) return;
    const range = quill.getSelection(true);
    quill.insertText(range.index, "\n");
    quill.insertEmbed(range.index + 1, "pageBreak", true);
    quill.insertText(range.index + 2, "\n");
    quill.setSelection(range.index + 3);
  }, []);

  const quillModules = useMemo(() => ({
    toolbar: {
      container: [
        [{ font: ["", "arial", "times-new-roman", "georgia", "courier-new", "verdana", "trebuchet-ms"] }],
        [{ size: ["9px", "10px", "11px", "12px", false, "14px", "16px", "18px", "20px", "24px"] }],
        [{ header: [1, 2, 3, false] }],
        ["bold", "italic", "underline", "strike"],
        [{ color: [] }, { background: [] }],
        [{ list: "ordered" }, { list: "bullet" }],
        [{ align: [] }],
        [{ indent: "-1" }, { indent: "+1" }],
        ["pageBreak"],
        ["clean"],
      ],
      handlers: {
        pageBreak: function () {
          // Will be overridden after mount
        },
      },
    },
  }), []);

  // Attach the page break handler after Quill mounts
  useEffect(() => {
    const quill = quillRef.current?.getEditor();
    if (!quill) return;
    const toolbar = quill.getModule("toolbar");
    if (toolbar) {
      toolbar.addHandler("pageBreak", insertPageBreak);
    }
    // Style the page break button in the toolbar
    const btn = document.querySelector(".ql-pageBreak");
    if (btn) {
      btn.innerHTML = `<svg viewBox="0 0 18 18" width="18" height="18"><line x1="2" y1="9" x2="16" y2="9" stroke="currentColor" stroke-width="1.5" stroke-dasharray="3,2"/><line x1="2" y1="5" x2="16" y2="5" stroke="currentColor" stroke-width="0.5"/><line x1="2" y1="13" x2="16" y2="13" stroke="currentColor" stroke-width="0.5"/></svg>`;
      btn.title = "Insert Page Break";
    }
  }, [insertPageBreak]);

  useEffect(() => {
    supabase.from("associations").select("id, name").eq("status", "active").order("name").then(({ data }) => {
      if (data) setAssociations(data);
    });
    // Fetch company settings
    supabase.from("company_settings").select("key, value").in("key", [
      "company_name", "company_address", "company_phone", "company_email", "company_logo_url", "company_website"
    ]).then(({ data }) => {
      const settings = {};
      (data || []).forEach(r => { settings[r.key] = r.value; });
      setCompanySettings(settings);
    });
  }, []);

  useEffect(() => {
    if (!selectedAssociation) { setOwners([]); setAssociation(null); setCustomVars([]); return; }
    Promise.all([
      supabase.from("associations").select("*").eq("id", selectedAssociation).single(),
      supabase.from("owners").select("*, units(unit_number, account_number)").eq("status", "active").eq("association_id", selectedAssociation).order("last_name"),
      supabase.from("custom_variables").select("*").eq("association_id", selectedAssociation).order("variable_name"),
    ]).then(([assocRes, ownersRes, varsRes]) => {
      if (assocRes.data) setAssociation(assocRes.data);
      if (ownersRes.data) setOwners(ownersRes.data);
      setCustomVars(varsRes.data || []);
    });
  }, [selectedAssociation]);

  // Load saved letters
  const loadSavedLetters = async () => {
    const { data } = await supabase.from("saved_letters").select("*").order("updated_at", { ascending: false });
    if (data) setSavedLetters(data);
  };

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

  // Save letter to DB
  const handleSaveLetter = async () => {
    const name = letterName || subject || "Untitled Letter";
    const payload = {
      name,
      association_id: selectedAssociation || null,
      subject,
      certified_mail: certifiedMail,
      body_html: editorContent,
      recipients: selectedOwners,
      manual_recipients: manualRecipients,
      updated_at: new Date().toISOString(),
    };

    if (currentLetterId) {
      const { error } = await supabase.from("saved_letters").update(payload).eq("id", currentLetterId);
      if (error) { toast({ title: "Error saving letter", description: error.message, variant: "destructive" }); return; }
      toast({ title: "Letter updated" });
    } else {
      const { data, error } = await supabase.from("saved_letters").insert(payload).select("id").single();
      if (error) { toast({ title: "Error saving letter", description: error.message, variant: "destructive" }); return; }
      setCurrentLetterId(data.id);
      toast({ title: "Letter saved" });
    }
    loadSavedLetters();
  };

  // Load a saved letter
  const handleLoadLetter = (letter) => {
    setCurrentLetterId(letter.id);
    setLetterName(letter.name || "");
    setSubject(letter.subject || "");
    setCertifiedMail(letter.certified_mail || "");
    setEditorContent(letter.body_html || "");
    if (letter.association_id) setSelectedAssociation(letter.association_id);
    setSelectedOwners(Array.isArray(letter.recipients) ? letter.recipients : []);
    setManualRecipients(Array.isArray(letter.manual_recipients) ? letter.manual_recipients : []);
    setShowSavedDialog(false);
  };

  // Delete a saved letter
  const handleDeleteLetter = async (id) => {
    await supabase.from("saved_letters").delete().eq("id", id);
    if (currentLetterId === id) { setCurrentLetterId(null); setLetterName(""); }
    loadSavedLetters();
    toast({ title: "Letter deleted" });
  };

  // Manual recipients
  const addManualRecipient = () => {
    if (!manualName.trim()) return;
    setManualRecipients(prev => [...prev, { id: crypto.randomUUID(), name: manualName.trim(), address: manualAddress.trim() }]);
    setManualName("");
    setManualAddress("");
    setShowManualDialog(false);
  };

  const removeManualRecipient = (id) => {
    setManualRecipients(prev => prev.filter(r => r.id !== id));
  };

  const toggleOwner = (id) => {
    setSelectedOwners((prev) =>
      prev.includes(id) ? prev.filter((o) => o !== id) : [...prev, id]
    );
  };

  const filteredOwners = owners.filter((o) => {
    if (excludeElectronicConsent && o.electronic_consent) return false;
    const term = ownerSearch.toLowerCase();
    return `${o.first_name} ${o.last_name}`.toLowerCase().includes(term) ||
      o.property_address?.toLowerCase().includes(term) ||
      o.units?.unit_number?.toLowerCase().includes(term);
  });

  const allFilteredSelected = filteredOwners.length > 0 && filteredOwners.every(o => selectedOwners.includes(o.id));

  const toggleSelectAll = () => {
    if (allFilteredSelected) {
      const filteredIds = new Set(filteredOwners.map(o => o.id));
      setSelectedOwners(prev => prev.filter(id => !filteredIds.has(id)));
    } else {
      const filteredIds = filteredOwners.map(o => o.id);
      setSelectedOwners(prev => [...new Set([...prev, ...filteredIds])]);
    }
  };

  const generatePDF = async (returnBlob = false) => {
    const selected = owners.filter((o) => selectedOwners.includes(o.id) && !(excludeElectronicConsent && o.electronic_consent));
    if (selected.length === 0 && manualRecipients.length === 0) {
      toast({ title: "No recipients selected", variant: "destructive" });
      return;
    }

    // Pre-fetch logos as data URLs
    const assocLogoUrl = association?.logo_url || "";
    const { data: companyLogoRow } = await (supabase).from("company_settings").select("value").eq("key", "company_logo_url").maybeSingle();
    const companyLogoUrl = companyLogoRow?.value || "";
    const { data: companyAddrRow } = await supabase.from("company_settings").select("value").eq("key", "company_address").maybeSingle();
    const companyAddress = companyAddrRow?.value || "";

    const fetchDataUrl = async (url) => {
      if (!url) return null;
      if (url.startsWith("data:")) return url;
      try {
        const resp = await fetch(url, { mode: "cors" });
        const blob = await resp.blob();
        return new Promise((resolve) => {
          const reader = new FileReader();
          reader.onloadend = () => resolve(reader.result);
          reader.onerror = () => resolve(null);
          reader.readAsDataURL(blob);
        });
      } catch { return null; }
    };

    const [assocLogoData, companyLogoData] = await Promise.all([
      fetchDataUrl(assocLogoUrl),
      fetchDataUrl(companyLogoUrl),
    ]);

    const doc = new jsPDF({ unit: "pt", format: "letter" });
    const pageW = doc.internal.pageSize.getWidth();
    const pageH = doc.internal.pageSize.getHeight();

    const leftMargin = 36;  // 0.5 inch
    const rightMargin = 36; // 0.5 inch
    const contentLeft = leftMargin + 10;
    const returnAddressX = leftMargin + 2;
    const returnAddressY = 45;
    const recipientAddressY = 175;

    const footerBottomMargin = 40;
    const footerY = pageH - footerBottomMargin;
    const footerDividerY = footerY - 15;

    const getImageDims = (dataUrl, maxWidth) => {
      if (!dataUrl) return { w: 0, h: 0 };
      try {
        const props = doc.getImageProperties(dataUrl);
        const sourceWidth = props.width || props.pixelWidth || maxWidth;
        const sourceHeight = props.height || props.pixelHeight || maxWidth * 0.4;
        const w = Math.min(sourceWidth, maxWidth);
        const h = sourceWidth ? (sourceHeight / sourceWidth) * w : maxWidth * 0.4;
        return { w, h };
      } catch {
        return { w: maxWidth, h: maxWidth * 0.4 };
      }
    };

    const drawHeader = (showLogo = true) => {
      const logoData = useCompanySender ? companyLogoData : assocLogoData;
      if (showLogo && logoData) {
        try {
          const { w, h } = getImageDims(logoData, assocLogoWidth);
          doc.addImage(logoData, "PNG", pageW - rightMargin - w, 30, w, h);
        } catch { /* ignore */ }
      }
    };

    const drawFooter = () => {
      if (companyLogoData) {
        try {
          const { w, h } = getImageDims(companyLogoData, 75);
          const logoY = footerDividerY - h - 6;
          doc.addImage(companyLogoData, "PNG", (pageW - w) / 2, logoY, w, h);
        } catch { /* ignore */ }
      }

      doc.setDrawColor(200);
      doc.setLineDash([]);
      doc.setLineWidth(1);
      doc.line(leftMargin, footerDividerY, pageW - rightMargin, footerDividerY);

      const prevFont = doc.getFont();
      const prevFontSize = doc.getFontSize();
      const prevTextColor = doc.getTextColor();

      doc.setFont("helvetica", "normal");
      doc.setFontSize(7);
      doc.setTextColor(120);

      const footerWebsite = useCompanySender ? (companySettings.company_website || "") : (association?.website || "");
      const footerEmail = useCompanySender ? (companySettings.company_email || "") : (association?.email || "");
      const footerPhone = useCompanySender ? (companySettings.company_phone || "") : (association?.phone || "");
      const contactY = footerDividerY + 32;
      if (footerWebsite) doc.text(footerWebsite, leftMargin, contactY);
      if (footerEmail) doc.text(footerEmail, pageW / 2, contactY, { align: "center" });
      if (footerPhone) doc.text(footerPhone, pageW - rightMargin, contactY, { align: "right" });

      doc.setFont(prevFont.fontName, prevFont.fontStyle);
      doc.setFontSize(prevFontSize);
      doc.setTextColor(prevTextColor);
    };


    let letterCount = 0;

    // Pre-assign sign-up codes per recipient if {{signup_code}} or {{signup_url}} is used
    const needsSignupCode = /\{\{\s*signup_(code|url)\s*\}\}/i.test(editorContent || "");
    const ownerSignupCodes = {}; // ownerId -> code
    if (needsSignupCode && selectedOwnerObjsForCodes(owners, selectedOwners, excludeElectronicConsent).length > 0) {
      const targets = selectedOwnerObjsForCodes(owners, selectedOwners, excludeElectronicConsent);
      // Look up existing unredeemed, unexpired homeowner codes for these owners
      const ownerIds = targets.map(o => o.id);
      const { data: existing } = await supabase
        .from("signup_codes")
        .select("code, owner_id, expires_at, redeemed_at, role")
        .in("owner_id", ownerIds)
        .is("redeemed_at", null)
        .eq("role", "homeowner");
      const nowTs = Date.now();
      (existing || []).forEach(row => {
        if (!row.owner_id || ownerSignupCodes[row.owner_id]) return;
        if (row.expires_at && new Date(row.expires_at).getTime() < nowTs) return;
        ownerSignupCodes[row.owner_id] = row.code;
      });

      // Generate codes for owners who don't yet have one
      const toCreate = targets
        .filter(o => !ownerSignupCodes[o.id])
        .map(o => ({
          code: generateSignupCodeStr(),
          role: "homeowner",
          owner_id: o.id,
          association_id: o.association_id || selectedAssociation || null,
          note: "Auto-generated from letter mailing",
        }));
      if (toCreate.length > 0) {
        const { data: created, error: createErr } = await supabase
          .from("signup_codes")
          .insert(toCreate)
          .select("code, owner_id");
        if (createErr) {
          console.error("Failed to bulk-create signup codes for letter:", createErr);
          toast({ title: "Could not generate sign-up codes", description: createErr.message, variant: "destructive" });
        } else {
          (created || []).forEach(row => { if (row.owner_id) ownerSignupCodes[row.owner_id] = row.code; });
        }
      }
    }
    const signupBaseUrl = `${window.location.origin}/register/`;

    // Pre-generate all QR codes from the editor content (async, done once)
    const qrDataUrls = {};
    const qrMatches = [...(editorContent || "").matchAll(/\{\{qr:(.*?)\}\}/gi)];
    for (const match of qrMatches) {
      const url = match[1].trim();
      if (url && !qrDataUrls[url]) {
        try {
          qrDataUrls[url] = await QRCode.toDataURL(url, { width: 300, margin: 1 });
        } catch { qrDataUrls[url] = null; }
      }
    }

    // Helper to draw a single letter page
    const drawLetterPage = (recipientName, recipientAddr, senderInfo, signupCode = "") => {
      if (letterCount > 0) doc.addPage();
      letterCount += 1;

      drawHeader();
      drawFooter();

      doc.setFont("helvetica", "normal");
      doc.setFontSize(9);
      doc.setTextColor(0);

      let raY = returnAddressY;
      if (senderInfo) {
        doc.setFont("helvetica", "bold");
        doc.text(senderInfo.name || "", returnAddressX, raY);
        raY += 12;
        doc.setFont("helvetica", "normal");
        if (senderInfo.careOf) {
          doc.text(`c/o ${senderInfo.careOf}`, returnAddressX, raY);
          raY += 12;
        }
        if (senderInfo.address) {
          doc.text(senderInfo.address, returnAddressX, raY);
          raY += 12;
        }
        if (senderInfo.cityStateZip) {
          doc.text(senderInfo.cityStateZip, returnAddressX, raY);
          raY += 12;
        }
        if (senderInfo.phone) {
          raY += 4;
          doc.text(senderInfo.phone, returnAddressX, raY);
          raY += 11;
        }
        if (senderInfo.email) doc.text(senderInfo.email, returnAddressX, raY);
      }

      const dateStr = formatDateLongEST(letterDate || new Date());
      const dateY = raY > returnAddressY ? Math.max(raY, returnAddressY + 50) + 10 : returnAddressY + 60;
      doc.setFont("helvetica", "bold");
      doc.setFontSize(9);
      doc.setTextColor(0);
      doc.text(dateStr, pageW - rightMargin, dateY, { align: "right" });

      let certY = dateY;
      if (certifiedMail) {
        certY += 28;
        doc.setFont("helvetica", "bolditalic");
        doc.text("Sent via U.S. Certified Mail No.:", pageW - rightMargin, certY, { align: "right" });
        const txtWidth = doc.getTextWidth("Sent via U.S. Certified Mail No.:");
        doc.setLineWidth(0.5);
        doc.line(pageW - rightMargin - txtWidth, certY + 2, pageW - rightMargin, certY + 2);
        certY += 12;
        doc.setFont("helvetica", "normal");
        doc.text(certifiedMail, pageW - rightMargin, certY, { align: "right" });
      }

      let currentY = recipientAddressY;
      doc.setFont("helvetica", "normal");
      doc.setFontSize(9);
      doc.setTextColor(0);

      if (recipientName) {
        doc.text(recipientName, contentLeft, currentY);
        currentY += 14;
      }

      if (recipientAddr) {
        const addrLines = recipientAddr.split(/\n+/).map((line) => line.trim()).filter(Boolean);
        addrLines.forEach((line) => {
          doc.text(line, contentLeft, currentY);
          currentY += 14;
        });
      }

      // Resolve variables in HTML before parsing
      let resolvedHtml = (editorContent || "")
        .replace(/\{\{clientName\}\}/gi, association?.name || "")
        .replace(/\{\{AssociationName\}\}/gi, association?.name || "")
        .replace(/\{\{ownerName\}\}/gi, recipientName || "")
        .replace(/\{\{ownersNames\}\}/gi, recipientName || "")
        .replace(/\{\{Owners\}\}/gi, recipientName || "")
        .replace(/\{\{propertyAddress\}\}/gi, recipientAddr || "")
        .replace(/\{\{currentDate\}\}/gi, dateStr)
        .replace(/\{\{Date\}\}/gi, dateStr)
        .replace(/\{\{\s*signup_code\s*\}\}/gi, signupCode || "")
        .replace(/\{\{\s*signup_url\s*\}\}/gi, signupCode ? `${signupBaseUrl}${signupCode}` : "");

      // Resolve custom variables
      customVars.forEach((v) => {
        const varName = v.variable_name.replace(/[{}]/g, "");
        const regex = new RegExp(`\\{\\{${varName.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}\\}\\}`, "gi");
        resolvedHtml = resolvedHtml.replace(regex, v.default_value || "");
      });

      // Resolve QR code variables using pre-generated data URLs
      resolvedHtml = resolvedHtml.replace(/\{\{qr:(.*?)\}\}/gi, (_, url) => {
        const trimmed = url.trim();
        const dataUrl = qrDataUrls[trimmed];
        if (dataUrl) return `<img data-qr-url="${trimmed}" src="${dataUrl}" class="qr-code-inline" />`;
        return `[QR: ${trimmed}]`;
      });

      // Parse HTML into styled blocks
      const blocks = parseHtmlToSegments(resolvedHtml);

      let bodyY = Math.max(currentY + 36, 260);
      if (subject) {
        doc.setFont("helvetica", "bold");
        doc.setFontSize(9);
        doc.text(`Re: ${subject}`, contentLeft, bodyY);
        doc.setFont("helvetica", "normal");
        bodyY += 20;
      }

      const bodyMaxW = pageW - contentLeft - rightMargin;
      const maxBodyY = footerDividerY - 30;

      const ensureSpace = (needed) => {
        if (bodyY + needed > maxBodyY) {
          doc.addPage();
          drawHeader(false);
          drawFooter();
          bodyY = 72;
        }
      };

      const setSegmentStyle = (seg) => {
        const fontName = seg.font || "helvetica";
        let fontStyle = "normal";
        if (seg.bold && seg.italic) fontStyle = "bolditalic";
        else if (seg.bold) fontStyle = "bold";
        else if (seg.italic) fontStyle = "italic";
        doc.setFont(fontName, fontStyle);
        doc.setFontSize(seg.size || 9);
        if (seg.color) {
          doc.setTextColor(seg.color[0], seg.color[1], seg.color[2]);
        } else {
          doc.setTextColor(0);
        }
      };

      blocks.forEach((block) => {
        if (block.type === "pageBreak") {
          doc.addPage();
          drawHeader(false);
          drawFooter();
          bodyY = 72;
          return;
        }

        // QR code image block
        if (block.type === "qrImage" && block.src) {
          const qrSize = 80;
          ensureSpace(qrSize + 10);
          try {
            doc.addImage(block.src, "PNG", contentLeft, bodyY, qrSize, qrSize);
          } catch {}
          bodyY += qrSize + 4;
          doc.setFont("helvetica", "normal");
          doc.setFontSize(7);
          doc.setTextColor(100);
          doc.text(block.url || "", contentLeft, bodyY);
          doc.setTextColor(0);
          bodyY += 12;
          return;
        }

        // Table block — render using jspdf-autotable
        if (block.type === "table") {
          ensureSpace(30);
          const allRows = [...(block.headerRows || []), ...(block.bodyRows || [])];
          if (allRows.length === 0) return;

          const head = block.headerRows.length > 0
            ? block.headerRows.map(row => row.map(cell => cell.content))
            : [];
          const body = block.bodyRows.map(row => row.map(cell => cell.content));

          const tableTheme = block.showBorders !== false ? "grid" : "plain";

          doc.autoTable({
            startY: bodyY,
            head: head.length > 0 ? head : undefined,
            body: body,
            margin: { left: contentLeft, right: rightMargin },
            theme: tableTheme,
            styles: {
              font: "helvetica",
              fontSize: 8,
              cellPadding: 4,
              lineColor: [200, 200, 200],
              lineWidth: 0.5,
              textColor: [0, 0, 0],
            },
            headStyles: {
              fillColor: [241, 245, 249],
              textColor: [0, 0, 0],
              fontStyle: "bold",
            },
            alternateRowStyles: {
              fillColor: [255, 255, 255],
            },
            didDrawPage: () => {
              // Redraw header/footer on new pages from table overflow
              drawHeader(false);
              drawFooter();
            },
          });

          bodyY = doc.lastAutoTable.finalY + 10;
          return;
        }

        const segments = block.segments || [];
        if (segments.length === 0 || (segments.length === 1 && !segments[0].text?.trim())) {
          bodyY += 8;
          return;
        }

        const blockAlign = block.align || "left";
        const blockIndent = (block.indent || 0) * 20; // 20pt per indent level

        // Calculate list prefix
        let prefix = "";
        let prefixWidth = 0;
        if (block.listType) {
          const indentPx = (block.indent || 0) * 20;
          prefix = block.listType === "ordered" ? `${block.listIndex}. ` : "• ";
          doc.setFont("helvetica", "normal");
          doc.setFontSize(9);
          prefixWidth = doc.getTextWidth(prefix) + indentPx;
        }

        const fullText = segments.map(s => s.text || "").join("");
        if (!fullText.trim() && !block.listType) { bodyY += 8; return; }

        const maxSize = Math.max(...segments.map(s => s.size || 9));
        const lineHeight = maxSize * 1.5;

        const words = fullText.split(/(\s+)/);
        const lines = [];
        let currentLine = "";

        doc.setFont("helvetica", "normal");
        doc.setFontSize(maxSize);

        const indentOffset = block.listType ? 0 : blockIndent;
        const effectiveMaxW = bodyMaxW - prefixWidth - indentOffset;

        words.forEach((word) => {
          const testLine = currentLine + word;
          const testWidth = doc.getTextWidth(testLine);
          if (testWidth > effectiveMaxW && currentLine.trim()) {
            lines.push(currentLine);
            currentLine = word.trimStart();
          } else {
            currentLine = testLine;
          }
        });
        if (currentLine) lines.push(currentLine);

        lines.forEach((line, lineIdx) => {
          ensureSpace(lineHeight);

          const lineBaseX = contentLeft + prefixWidth + indentOffset;

          // Draw list prefix on first line
          if (lineIdx === 0 && block.listType) {
            doc.setFont("helvetica", "normal");
            doc.setFontSize(9);
            doc.setTextColor(0);
            doc.text(prefix, contentLeft + (block.indent || 0) * 20, bodyY);
          }

          const lineText = line;

          // Find the segment at this character offset
          let charOffset = 0;
          for (let li = 0; li < lineIdx; li++) {
            charOffset += lines[li].length;
          }
          let segCharCount = 0;
          let activeSeg = segments[0] || {};
          for (const seg of segments) {
            const segLen = (seg.text || "").length;
            if (segCharCount + segLen > charOffset) {
              activeSeg = seg;
              break;
            }
            segCharCount += segLen;
          }

          setSegmentStyle(activeSeg);

          // Calculate xPos based on alignment
          let xPos = lineBaseX;
          const lineW = doc.getTextWidth(lineText);
          const rightEdge = pageW - rightMargin;

          if (blockAlign === "center") {
            xPos = contentLeft + indentOffset + (effectiveMaxW - lineW) / 2;
          } else if (blockAlign === "right") {
            xPos = rightEdge - lineW;
          } else if (blockAlign === "justify" && lineIdx < lines.length - 1 && lineText.trim().includes(" ")) {
            // Justify: stretch words to fill line
            const trimmed = lineText.trim();
            const wordsInLine = trimmed.split(/\s+/);
            if (wordsInLine.length > 1) {
              const totalWordsWidth = wordsInLine.reduce((sum, w) => sum + doc.getTextWidth(w), 0);
              const extraSpace = (effectiveMaxW - totalWordsWidth) / (wordsInLine.length - 1);
              let wx = lineBaseX;
              wordsInLine.forEach((w, wi) => {
                doc.text(w, wx, bodyY);
                wx += doc.getTextWidth(w) + extraSpace;
              });
              // Skip normal text rendering for justified lines
              bodyY += lineHeight;
              return;
            }
          }

          doc.text(lineText, xPos, bodyY);

          // Underline
          if (activeSeg.underline) {
            const textW = doc.getTextWidth(lineText);
            doc.setLineWidth(0.5);
            doc.line(xPos, bodyY + 2, xPos + textW, bodyY + 2);
          }

          // Strikethrough
          if (activeSeg.strikethrough) {
            const textW = doc.getTextWidth(lineText);
            doc.setLineWidth(0.5);
            const strikeY = bodyY - (maxSize * 0.3);
            doc.line(xPos, strikeY, xPos + textW, strikeY);
          }

          bodyY += lineHeight;
        });
      });
    };

    // Build sender info
    let senderInfo = null;
    if (useCompanySender && companyAddress) {
      const compLines = companyAddress.split("\n").map(l => l.trim()).filter(Boolean);
      senderInfo = {
        name: companySettings.company_name || compLines[0] || "",
        careOf: null,
        address: compLines.length > 1 ? compLines.slice(companySettings.company_name ? 0 : 1).join(", ") : "",
        cityStateZip: "",
        phone: companySettings.company_phone || "",
        email: companySettings.company_email || "",
      };
      // If company_name is set separately, use full address lines
      if (companySettings.company_name) {
        senderInfo.address = compLines[0] || "";
        senderInfo.cityStateZip = compLines.slice(1).join(", ");
      }
    } else {
      const mailingAddr = association?.mailing_address || "";
      const mailingLines = mailingAddr ? (mailingAddr.includes('\n') 
        ? mailingAddr.split('\n').map(l => l.trim()).filter(l => l)
        : (() => {
            const csz = mailingAddr.match(/^(.+?),\s+([A-Za-z\s]+,\s*[A-Za-z]+\s+\d{5}(?:-\d{4})?)$/);
            return csz ? [csz[1].trim(), csz[2].trim()] : [mailingAddr];
          })()
      ) : [];
      senderInfo = association ? {
        name: association.name,
        careOf: association.care_of_address,
        address: mailingLines[0] || "",
        cityStateZip: mailingLines[1] || "",
        phone: association.phone,
        email: association.email,
      } : null;
    }

    // For manual recipients that use company address
    const companySender = companyAddress ? {
      name: companySettings.company_name || companyAddress.split("\n")[0] || "",
      address: companyAddress.split("\n").slice(1).join("\n"),
      cityStateZip: "",
    } : senderInfo;

    // Group selected owners by unit_id
    const unitGroups = new Map();
    let noUnitIdx = 0;
    const selectedOwnerObjs = owners.filter((o) => selectedOwners.includes(o.id) && !(excludeElectronicConsent && o.electronic_consent));
    selectedOwnerObjs.forEach((owner) => {
      const key = owner.unit_id || `__no_unit_${noUnitIdx++}`;
      if (!unitGroups.has(key)) unitGroups.set(key, []);
      unitGroups.get(key).push(owner);
    });

    for (const [, groupOwners] of unitGroups) {
      const names = groupOwners
        .map(o => `${o.first_name || ""} ${o.last_name || ""}`.trim() || o.owner_name || "")
        .filter(Boolean);
      const uniqueNames = [...new Set(names)];
      const ownerName = uniqueNames.length > 0 ? uniqueNames.join(", ") : "Owner";

      const addresses = [];
      const seenAddr = new Set();
      groupOwners.forEach((owner) => {
        const { full: primary } = formatMailingAddress(owner, ownerName);
        const addrKey = (owner.mailing_address || owner.property_address || owner.street_address || "").trim().toLowerCase();
        if (addrKey && !seenAddr.has(addrKey)) {
          seenAddr.add(addrKey);
          addresses.push({ addr: owner.mailing_address || owner.property_address || owner.street_address || "", showName: true });
        }
        if (owner.alternate_address_1?.trim() && !seenAddr.has(owner.alternate_address_1.trim().toLowerCase())) {
          seenAddr.add(owner.alternate_address_1.trim().toLowerCase());
          addresses.push({ addr: owner.alternate_address_1.trim(), showName: false });
        }
        if (owner.alternate_address_2?.trim() && !seenAddr.has(owner.alternate_address_2.trim().toLowerCase())) {
          seenAddr.add(owner.alternate_address_2.trim().toLowerCase());
          addresses.push({ addr: owner.alternate_address_2.trim(), showName: false });
        }
      });
      if (addresses.length === 0) addresses.push({ addr: "", showName: true });

      // Use the first owner in the unit-group's signup code (one code per unit/letter)
      const groupSignupCode = groupOwners.map(o => ownerSignupCodes[o.id]).find(Boolean) || "";
      addresses.forEach(({ addr, showName }) => {
        drawLetterPage(showName ? ownerName : ownerName, addr, senderInfo, groupSignupCode);
      });
    }

    // Manual recipients use company address as sender
    for (const mr of manualRecipients) {
      drawLetterPage(mr.name, mr.address, senderInfo || companySender);
    }

    if (returnBlob) {
      return doc.output("blob");
    }
    doc.save(`Letters_${toEST(new Date(), "yyyy-MM-dd")}.pdf`);
    toast({ title: `Generated ${letterCount} letter(s)` });
  };

  const totalRecipients = selectedOwners.length + manualRecipients.length;

  return (
    <div className="flex gap-6 p-6 h-full min-h-0">
      {/* Left Panel - Association & Recipients */}
      <div className="w-[280px] flex-shrink-0 flex flex-col gap-4 overflow-hidden">
        <div>
          <Label className="text-xs font-semibold uppercase tracking-wide text-muted-foreground">Association</Label>
          <Select value={selectedAssociation} onValueChange={setSelectedAssociation}>
            <SelectTrigger className="mt-1.5">
              <SelectValue placeholder="Select Association" />
            </SelectTrigger>
            <SelectContent>
              {associations.map((a) => (
                <SelectItem key={a.id} value={a.id}>{a.name}</SelectItem>
              ))}
            </SelectContent>
          </Select>
        </div>

        <div>
          <Label className="text-xs font-semibold uppercase tracking-wide text-muted-foreground">Letter Date</Label>
          <Popover>
            <PopoverTrigger asChild>
              <Button variant="outline" className={cn("w-full mt-1.5 justify-start text-left font-normal", !letterDate && "text-muted-foreground")}>
                <CalendarIcon className="mr-2 h-4 w-4" />
                {letterDate ? format(letterDate, "MMMM d, yyyy") : "Pick a date"}
              </Button>
            </PopoverTrigger>
            <PopoverContent className="w-auto p-0" align="start">
              <Calendar mode="single" selected={letterDate} onSelect={(d) => d && setLetterDate(d)} initialFocus className="p-3 pointer-events-auto" />
            </PopoverContent>
          </Popover>
        </div>

        <Separator />

        <div className="flex-1 min-h-0 flex flex-col">
          <div className="flex items-center justify-between mb-2">
            <Label className="text-xs font-semibold uppercase tracking-wide text-muted-foreground">Recipients</Label>
            <span className="text-[11px] text-muted-foreground">{totalRecipients} selected</span>
          </div>

          {/* Manual recipients section */}
          {manualRecipients.length > 0 && (
            <div className="mb-2 space-y-1">
              <div className="text-[10px] font-semibold uppercase text-muted-foreground">Manual Recipients</div>
              {manualRecipients.map((mr) => (
                <div key={mr.id} className="flex items-center gap-2 px-2.5 py-1.5 rounded bg-accent/50 text-xs">
                  <UserPlus className="h-3 w-3 text-primary flex-shrink-0" />
                  <div className="flex-1 min-w-0">
                    <div className="font-medium truncate">{mr.name}</div>
                    {mr.address && <div className="text-[10px] text-muted-foreground truncate">{mr.address.split("\n")[0]}</div>}
                  </div>
                  <button onClick={() => removeManualRecipient(mr.id)} className="text-muted-foreground hover:text-destructive">
                    <X className="h-3 w-3" />
                  </button>
                </div>
              ))}
            </div>
          )}

          <Dialog open={showManualDialog} onOpenChange={setShowManualDialog}>
            <DialogTrigger asChild>
              <Button variant="outline" size="sm" className="gap-1.5 text-xs mb-2 w-full">
                <UserPlus className="h-3.5 w-3.5" /> Add Manual Recipient
              </Button>
            </DialogTrigger>
            <DialogContent className="sm:max-w-[400px]">
              <DialogHeader>
                <DialogTitle>Add Manual Recipient</DialogTitle>
              </DialogHeader>
              <div className="space-y-3 pt-2">
                <div>
                  <Label className="text-xs">Recipient Name *</Label>
                  <Input placeholder="e.g. John Smith" value={manualName} onChange={(e) => setManualName(e.target.value)} className="mt-1" />
                </div>
                <div>
                  <Label className="text-xs">Mailing Address</Label>
                  <Textarea placeholder={"123 Main St\nCity, State 12345"} value={manualAddress} onChange={(e) => setManualAddress(e.target.value)} className="mt-1" rows={3} />
                </div>
                <Button onClick={addManualRecipient} disabled={!manualName.trim()} className="w-full">Add Recipient</Button>
              </div>
            </DialogContent>
          </Dialog>

          {selectedAssociation && (
            <>
              <div className="text-[10px] font-semibold uppercase text-muted-foreground mb-1">Homeowners</div>
              <div className="relative mb-2">
                <Search className="absolute left-2.5 top-1/2 -translate-y-1/2 h-3.5 w-3.5 text-muted-foreground" />
                <Input placeholder="Search Owner..." value={ownerSearch} onChange={(e) => setOwnerSearch(e.target.value)} className="pl-8 h-8 text-xs" />
              </div>
              <label className="flex items-center gap-2 px-2.5 py-1.5 rounded hover:bg-accent cursor-pointer text-xs mb-1">
                <Checkbox checked={excludeElectronicConsent} onCheckedChange={(v) => setExcludeElectronicConsent(!!v)} />
                <span className="text-muted-foreground">Exclude e-consent owners</span>
              </label>
              {filteredOwners.length > 0 && (
                <label className="flex items-center gap-2.5 px-2.5 py-1.5 rounded hover:bg-accent cursor-pointer text-xs border-b mb-1">
                  <Checkbox checked={allFilteredSelected} onCheckedChange={toggleSelectAll} />
                  <span className="font-semibold text-foreground">Select All ({filteredOwners.length})</span>
                </label>
              )}
            </>
          )}

          <ScrollArea className="flex-1">
            <div className="space-y-0.5 pr-2">
              {filteredOwners.map((owner) => (
                <label key={owner.id} className="flex items-center gap-2.5 px-2.5 py-2 rounded hover:bg-accent cursor-pointer text-xs">
                  <Checkbox checked={selectedOwners.includes(owner.id)} onCheckedChange={() => toggleOwner(owner.id)} />
                  <div className="flex-1 min-w-0">
                    <div className="font-medium text-foreground truncate">{owner.first_name} {owner.last_name}</div>
                    <div className="text-[11px] text-muted-foreground truncate">
                      {owner.units?.unit_number ? `Unit ${owner.units.unit_number} - ` : ""}{owner.property_address || "No address"}
                    </div>
                  </div>
                </label>
              ))}
              {!selectedAssociation && (
                <p className="text-xs text-muted-foreground text-center py-4">Select an association first</p>
              )}
              {selectedAssociation && filteredOwners.length === 0 && (
                <p className="text-xs text-muted-foreground text-center py-4">No owners found</p>
              )}
            </div>
          </ScrollArea>
        </div>

        <Separator />

        <div className="space-y-2">
          <div className="space-y-1">
            <Label className="text-xs">Subject Line (Re:)</Label>
            <Input placeholder="e.g. Notice of Delinquency" value={subject} onChange={(e) => setSubject(e.target.value)} className="text-sm" />
          </div>
        <div className="space-y-1">
            <Label className="text-xs">Certified Mail No. (Optional)</Label>
            <Input placeholder="e.g. 7021 1234 5678 9012" value={certifiedMail} onChange={(e) => setCertifiedMail(e.target.value)} className="text-sm" />
          </div>
          {/* Send from Company toggle */}
          <label className="flex items-center gap-2 px-2.5 py-2 rounded hover:bg-accent cursor-pointer text-xs border">
            <Checkbox checked={useCompanySender} onCheckedChange={(v) => setUseCompanySender(!!v)} />
            <Building2 className="h-3.5 w-3.5 text-primary" />
            <div>
              <span className="font-medium text-foreground">Send from Company</span>
              <p className="text-[10px] text-muted-foreground">Use company address & logo as sender</p>
            </div>
          </label>

          <div className="space-y-1">
            <div className="flex justify-between">
              <Label className="text-xs">{useCompanySender ? "Company" : "Association"} Logo Width</Label>
              <span className="text-[10px] text-muted-foreground">{assocLogoWidth}px</span>
            </div>
            <input
              type="range"
              min={50}
              max={400}
              step={10}
              value={assocLogoWidth}
              onChange={(e) => setAssocLogoWidth(Number(e.target.value))}
              className="w-full h-1.5 accent-primary"
            />
          </div>

          {/* QR Code hint */}
          <div className="rounded-md border border-border bg-muted/30 px-3 py-2">
            <p className="text-[10px] font-semibold uppercase text-muted-foreground mb-1">QR Code Variable</p>
            <p className="text-[11px] text-muted-foreground">
              Type <span className="font-mono text-xs font-semibold text-primary">{"{{qr:https://example.com}}"}</span> in the letter body to insert a QR code linking to any URL.
            </p>
          </div>

          {/* Sign-up code hint */}
          <div className="rounded-md border border-border bg-muted/30 px-3 py-2">
            <p className="text-[10px] font-semibold uppercase text-muted-foreground mb-1">Sign-up Code Variables</p>
            <p className="text-[11px] text-muted-foreground">
              Insert <span className="font-mono text-xs font-semibold text-primary">{"{{signup_code}}"}</span> or <span className="font-mono text-xs font-semibold text-primary">{"{{signup_url}}"}</span> in the body. A unique homeowner registration code will be auto-generated and assigned to each recipient.
            </p>
          </div>
        </div>
      </div>

      <Separator orientation="vertical" />

      {/* Right Panel - Action Bar + Preview */}
      <div className="flex-1 flex flex-col min-h-0">
        {/* Action Bar */}
        <div className="flex items-center gap-2 mb-4 flex-wrap">
          <Input placeholder="Letter Name..." value={letterName} onChange={(e) => setLetterName(e.target.value)} className="w-[160px] h-9 text-xs" />
          <Button variant="outline" size="sm" className="gap-1.5 text-xs h-9" onClick={handleNewTemplate}><Plus className="h-3.5 w-3.5" /> New</Button>

          {/* Saved Letters & Templates Dialog */}
          <Dialog open={showSavedDialog} onOpenChange={(open) => { if (open) loadSavedLetters(); setShowSavedDialog(open); }}>
            <DialogTrigger asChild>
              <Button variant="outline" size="sm" className="gap-1.5 text-xs h-9"><FolderOpen className="h-3.5 w-3.5" /> Open</Button>
            </DialogTrigger>
            <DialogContent className="sm:max-w-[500px]">
              <DialogHeader>
                <DialogTitle>Open</DialogTitle>
              </DialogHeader>
              <ScrollArea className="max-h-[500px]">
                {/* Saved Letters Section */}
                {savedLetters.length > 0 && (
                  <div className="mb-4">
                    <p className="text-xs font-semibold text-muted-foreground uppercase tracking-wider mb-2 px-1">Saved Letters</p>
                    <div className="space-y-1">
                      {savedLetters.map((l) => (
                        <div key={l.id} className="flex items-center gap-2 px-3 py-2.5 rounded hover:bg-accent cursor-pointer group" onClick={() => handleLoadLetter(l)}>
                          <FileText className="h-4 w-4 text-muted-foreground flex-shrink-0" />
                          <div className="flex-1 min-w-0">
                            <div className="text-sm font-medium truncate">{l.name}</div>
                            <div className="text-[11px] text-muted-foreground">{l.subject ? `Re: ${l.subject}` : "No subject"} · {format(new Date(l.updated_at), "MMM d, yyyy")}</div>
                          </div>
                          <button
                            onClick={(e) => { e.stopPropagation(); handleDeleteLetter(l.id); }}
                            className="opacity-0 group-hover:opacity-100 text-muted-foreground hover:text-destructive transition-opacity"
                          >
                            <Trash2 className="h-3.5 w-3.5" />
                          </button>
                        </div>
                      ))}
                    </div>
                  </div>
                )}

                {/* Templates Section */}
                {templates.savedTemplates.length > 0 && (
                  <div>
                    {savedLetters.length > 0 && <Separator className="my-3" />}
                    <p className="text-xs font-semibold text-muted-foreground uppercase tracking-wider mb-2 px-1">Templates</p>
                    <div className="space-y-1">
                      {templates.savedTemplates.map((t) => (
                        <div key={t.id} className="flex items-center gap-2 px-3 py-2.5 rounded hover:bg-accent cursor-pointer group" onClick={() => { handleLoadTemplate(t.id); setShowSavedDialog(false); }}>
                          <FileText className="h-4 w-4 text-primary/60 flex-shrink-0" />
                          <div className="flex-1 min-w-0">
                            <div className="text-sm font-medium truncate">{t.name}</div>
                            <div className="text-[11px] text-muted-foreground">
                              {t.associations?.name && <span className="text-primary font-medium">{t.associations.name} · </span>}
                              {format(new Date(t.updated_at), "MMM d, yyyy")}
                            </div>
                          </div>
                          <button
                            onClick={(e) => { e.stopPropagation(); templates.deleteTemplate(t.id); }}
                            className="opacity-0 group-hover:opacity-100 text-muted-foreground hover:text-destructive transition-opacity"
                          >
                            <Trash2 className="h-3.5 w-3.5" />
                          </button>
                        </div>
                      ))}
                    </div>
                  </div>
                )}

                {savedLetters.length === 0 && templates.savedTemplates.length === 0 && (
                  <p className="text-sm text-muted-foreground text-center py-8">No saved letters or templates yet</p>
                )}
              </ScrollArea>
            </DialogContent>
          </Dialog>

          <Button variant="outline" size="sm" className="gap-1.5 text-xs h-9" onClick={handleSaveTemplate}><Save className="h-3.5 w-3.5" /> Save Template</Button>
          <Button variant="outline" size="sm" className="gap-1.5 text-xs h-9 border-primary text-primary hover:bg-primary/5" onClick={handleSaveLetter}>
            <Save className="h-3.5 w-3.5" /> {currentLetterId ? "Update Letter" : "Save Letter"}
          </Button>
          <Button variant="outline" size="sm" className="gap-1.5 text-xs h-9" onClick={() => setShowSaveToDocsDialog(true)}>
            <FolderUp className="h-3.5 w-3.5" /> Save to Documents
          </Button>
          {(() => {
            const firstSelected = owners.find(o => selectedOwners.includes(o.id));
            const residentUnit = firstSelected?.units?.unit_number;
            if (!residentUnit || selectedOwners.length !== 1) return null;
            return (
              <Button
                variant="outline"
                size="sm"
                className="gap-1.5 text-xs h-9"
                onClick={() => setShowSaveToResidentDialog(true)}
                title={`Save to Resident Files / ${residentUnit}`}
              >
                <FolderUp className="h-3.5 w-3.5" /> Save to Resident File
              </Button>
            );
          })()}
          <Button size="sm" className="gap-1.5 text-xs h-9 ml-auto" onClick={() => generatePDF(false)}>
            <Download className="h-3.5 w-3.5" /> Download {totalRecipients} PDFs
          </Button>
        </div>

        {/* Letter Preview */}
        <div className="flex-1 border rounded-lg bg-card overflow-auto">
          <div className="max-w-[650px] mx-auto p-8 min-h-[800px] bg-background shadow-sm my-4">
            {/* Letterhead */}
            {(association || useCompanySender) && (
              <div className="mb-6 pb-4 border-b">
                <div className="flex justify-between items-start">
                  <div className="text-xs text-muted-foreground space-y-0.5">
                    {useCompanySender ? (
                      <>
                        <p className="font-semibold text-foreground text-sm">{companySettings.company_name || "Company Name"}</p>
                        {companySettings.company_address && companySettings.company_address.split("\n").map((line, i) => (
                          <p key={i}>{line}</p>
                        ))}
                        <div className="pt-1">
                          {companySettings.company_phone && <p>{companySettings.company_phone}</p>}
                          {companySettings.company_email && <p>{companySettings.company_email}</p>}
                        </div>
                      </>
                    ) : association && (
                      <>
                        <p className="font-semibold text-foreground text-sm">{association.name}</p>
                        {association.care_of_address && <p>c/o {association.care_of_address}</p>}
                        {association.address && <p>{association.address}</p>}
                        {(association.city || association.state) && (
                          <p>{association.city}{association.state && `, ${association.state}`} {association.zip}</p>
                        )}
                        <div className="pt-1">
                          {association.phone && <p>{association.phone}</p>}
                          {association.email && <p>{association.email}</p>}
                        </div>
                      </>
                    )}
                  </div>
                  <div className="text-right text-xs text-muted-foreground">
                    <p className="font-semibold text-foreground">{format(letterDate || new Date(), "MMMM d, yyyy")}</p>
                  </div>
                </div>
              </div>
            )}

            {/* Certified Mail */}
            {certifiedMail && (
              <div className="mb-4 text-right text-xs">
                <p className="font-bold italic underline text-foreground">Sent via U.S. Certified Mail No.:</p>
                <p className="text-muted-foreground">{certifiedMail}</p>
              </div>
            )}

            {/* Recipient placeholder */}
            <div className="mb-6 text-xs border border-dashed border-primary/30 rounded p-3 bg-primary/5">
              <p className="text-[10px] uppercase font-semibold text-primary mb-1">Window Envelope Area</p>
              <p className="text-foreground font-medium">
                {selectedOwners.length > 0
                  ? (() => {
                      const first = owners.find(o => o.id === selectedOwners[0]);
                      return first ? `${first.first_name} ${first.last_name}` : "Recipient Name";
                    })()
                  : manualRecipients.length > 0
                    ? manualRecipients[0].name
                    : "John & Jane Doe"
                }
              </p>
              <p className="text-muted-foreground">1234 Willow Creek Dr.</p>
              <p className="text-muted-foreground">Rockledge, FL 32955</p>
            </div>

            {/* Subject */}
            {subject && (
              <div className="mb-4">
                <p className="text-sm font-bold text-foreground">Re: {subject}</p>
              </div>
            )}

            {/* Rich Text Editor */}
            <ReactQuill
              ref={quillRef}
              theme="snow"
              value={editorContent}
              onChange={setEditorContent}
              modules={quillModules}
              placeholder="Type your letter content here..."
              className="letter-quill-editor"
            />
          </div>

          {/* Footer */}
          <div className="flex justify-between items-center px-8 pb-4 text-[10px] text-muted-foreground max-w-[650px] mx-auto">
            <span>{association?.name || ""}</span>
            <span>Confidential Information</span>
            <span>Page 1 of 1</span>
          </div>
        </div>
      </div>

      <SaveToDocumentsDialog
        open={showSaveToDocsDialog}
        onOpenChange={setShowSaveToDocsDialog}
        generatePdfBlob={() => generatePDF(true)}
        defaultTitle={subject || letterName || "Letter"}
        associationId={selectedAssociation}
        associations={associations}
      />

      <SaveToDocumentsDialog
        open={showSaveToResidentDialog}
        onOpenChange={setShowSaveToResidentDialog}
        generatePdfBlob={() => generatePDF(true)}
        defaultTitle={subject || letterName || "Letter"}
        associationId={selectedAssociation}
        associations={associations}
        residentFileMode
        unitId={(owners.find(o => selectedOwners.includes(o.id))?.unit_id) || ""}
        unitNumber={(owners.find(o => selectedOwners.includes(o.id))?.units?.unit_number) || ""}
      />
    </div>
  );
}
