import { useEffect, useState, useMemo, useCallback } from "react";
import { useSearchParams } from "react-router-dom";
import { supabase } from "@/integrations/supabase/client";
import { logStatusChange, logNoticeGenerated } from "@/lib/violationTimelineLogger";
import RecordImportButton from "@/components/RecordImportButton";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { Badge } from "@/components/ui/badge";
import { Card, CardContent } from "@/components/ui/card";
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
import { Tabs, TabsList, TabsTrigger } from "@/components/ui/tabs";
import { useToast } from "@/hooks/use-toast";
import {
  Plus, Search, AlertTriangle, FileText, Upload, FileDown,
  Grid3X3, List, LayoutGrid, CheckSquare, Settings,
  Calendar as CalendarIcon, Shield, Mail
} from "lucide-react";
import type { Tables } from "@/integrations/supabase/types";
import { format } from "date-fns";
import { parseLocalDate } from "@/lib/dateUtils";
import ViolationCard from "@/components/ViolationCard";
import { ViolationTableView } from "@/components/ViolationTableView";
import ViolationDetailsDialog from "@/components/ViolationDetailsDialog";
import ViolationDialog from "@/components/ViolationDialog";
import ViolationExportDialog from "@/components/ViolationExportDialog";
import ViolationStatusExportDialog from "@/components/ViolationStatusExportDialog";
import ViolationTimelineImportDialog from "@/components/ViolationTimelineImportDialog";
import { ViolationNoticeProgressionReport } from "@/components/ViolationNoticeProgressionReport";
import ViolationTypeManager from "@/components/ViolationTypeManager";
import ViolationBulkActions from "@/components/ViolationBulkActions";
import ViolationNoticeForm from "@/components/forms/ViolationNoticeForm";
import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogDescription, DialogFooter } from "@/components/ui/dialog";
import { sendEmailViaSMTP } from "@/lib/sendEmailViaSMTP";
import { generateViolationNoticeEmailHtml } from "@/lib/violationNoticeEmailHtml";
import { Checkbox } from "@/components/ui/checkbox";

type ViolationRow = Tables<"violations"> & {
  units?: { unit_number: string } | null;
  associations?: { name: string } | null;
  owners?: { first_name: string; last_name: string; property_address: string; email?: string; electronic_consent?: boolean } | null;
  allOwnerNames?: string;
};

const STATUS_CONFIG: Record<string, { label: string; cls: string }> = {
  all: { label: "All", cls: "" },
  open: { label: "Open", cls: "cc-badge-danger" },
  resolved: { label: "Resolved", cls: "cc-badge-success" },
};

const STAGE_ORDER = ["First Notice", "Second Notice", "Third & Final Notice"];

const getNextStage = (current: string | null): string | null => {
  const idx = STAGE_ORDER.indexOf(current || "");
  if (idx < 0) return "Second Notice";
  if (idx < STAGE_ORDER.length - 1) return STAGE_ORDER[idx + 1];
  return null; // already at max
};


export default function ViolationsPage({ boardAssociationIds }: { boardAssociationIds?: string[] } = {}) {
  const [searchParams] = useSearchParams();
  const isBoardView = !!boardAssociationIds?.length;
  const [violations, setViolations] = useState<ViolationRow[]>([]);
  const [associations, setAssociations] = useState<Tables<"associations">[]>([]);
  const [loading, setLoading] = useState(true);
  const [search, setSearch] = useState("");
  const [clientFilter, setClientFilter] = useState("all");
  const [startDate, setStartDate] = useState("");
  const [endDate, setEndDate] = useState("");
  const [viewMode, setViewMode] = useState<"grid" | "table" | "card">("card");
  const [groupBy, setGroupBy] = useState<"status" | "stage" | "timeline">("status");
  const [statusTab, setStatusTab] = useState("open");
  const [showSelection, setShowSelection] = useState(false);
  const [selectedIds, setSelectedIds] = useState<string[]>([]);

  // Dialog states
  const [logDialogOpen, setLogDialogOpen] = useState(false);
  const [detailsDialogOpen, setDetailsDialogOpen] = useState(false);
  const [exportDialogOpen, setExportDialogOpen] = useState(false);
  const [statusExportDialogOpen, setStatusExportDialogOpen] = useState(false);
  const [importDialogOpen, setImportDialogOpen] = useState(false);
  const [noticeReportOpen, setNoticeReportOpen] = useState(false);
  const [typesDialogOpen, setTypesDialogOpen] = useState(false);
  const [selectedViolation, setSelectedViolation] = useState<ViolationRow | null>(null);
  const [editingViolation, setEditingViolation] = useState<ViolationRow | null>(null);
  const [regenerateNoticeViolation, setRegenerateNoticeViolation] = useState<ViolationRow | null>(null);
  const [generateNoticeViolations, setGenerateNoticeViolations] = useState<ViolationRow[] | null>(null);
  const [emailNoticeDialogOpen, setEmailNoticeDialogOpen] = useState(false);
  const [emailNoticeTargets, setEmailNoticeTargets] = useState<ViolationRow[]>([]);
  const [emailNoticeSending, setEmailNoticeSending] = useState(false);
  const [emailSenders, setEmailSenders] = useState<any[]>([]);
  const [emailSenderId, setEmailSenderId] = useState("");
  const [emailNoticeSelected, setEmailNoticeSelected] = useState<Record<string, boolean>>({});

  const { toast } = useToast();

  useEffect(() => {
    const associationId = searchParams.get("associationId");
    const ownerId = searchParams.get("ownerId");
    const name = searchParams.get("name");
    if (associationId) setClientFilter(associationId);
    if (name) setSearch(name);
    if (searchParams.get("new") === "1" && associationId && ownerId && !isBoardView) {
      setEditingViolation(null);
      setLogDialogOpen(true);
    }
  }, [searchParams, isBoardView]);

  const fetchSenders = useCallback(async () => {
    const { data: result } = await supabase.functions.invoke("send-smtp-email", { body: { action: "list_senders" } });
    const available = result?.senders || [];
    setEmailSenders(available);
    if (available.length > 0 && !emailSenderId) setEmailSenderId(available[0].id);
  }, [emailSenderId]);

  const handleOpenEmailNotice = (targets: ViolationRow[]) => {
    const eligible = targets.filter(v => v.owners?.electronic_consent && v.owners?.email);
    if (eligible.length === 0) {
      toast({ title: "No eligible owners", description: "None of the selected violations have owners with electronic consent and a valid email.", variant: "destructive" });
      return;
    }
    setEmailNoticeTargets(eligible);
    const selected: Record<string, boolean> = {};
    eligible.forEach(v => { selected[v.id] = true; });
    setEmailNoticeSelected(selected);
    setEmailNoticeDialogOpen(true);
    fetchSenders();
  };

  const handleSendEmailNotices = async () => {
    const toSend = emailNoticeTargets.filter(v => emailNoticeSelected[v.id]);
    if (toSend.length === 0 || !emailSenderId) return;
    setEmailNoticeSending(true);
    let successCount = 0;
    let failCount = 0;
    const failErrors: string[] = [];

    // Gather unique association IDs to fetch details
    const assocIds = [...new Set(toSend.map(v => v.association_id).filter(Boolean))];
    const assocMap: Record<string, any> = {};
    if (assocIds.length > 0) {
      const { data: assocData } = await supabase
        .from("associations")
        .select("id, name, address, city, state, zip, phone, email, website, care_of_address")
        .in("id", assocIds);
      (assocData || []).forEach(a => { assocMap[a.id] = a; });
    }

    // Fetch violation types for citation / requested action
    const vtIds = [...new Set(toSend.map(v => v.violation_type).filter(Boolean))];
    const vtMap: Record<string, any> = {};
    if (vtIds.length > 0) {
      const { data: vtData } = await supabase
        .from("violation_types")
        .select("id, category, article_section, citation, requested_action")
        .in("category", vtIds);
      (vtData || []).forEach(vt => { vtMap[vt.category] = vt; });
    }

    for (const v of toSend) {
      const ownerName = `${v.owners?.first_name || ""} ${v.owners?.last_name || ""}`.trim() || "Owner";
      const assoc = assocMap[v.association_id] || {};
      const assocName = assoc.name || v.associations?.name || "Your Association";
      const vt = vtMap[v.violation_type || ""] || {};

      const assocAddr = [assoc.address, [assoc.city, assoc.state, assoc.zip].filter(Boolean).join(", ")].filter(Boolean).join(", ");

      // Collect photo URLs
      let photos: string[] = [];
      if (Array.isArray(v.photo_urls) && v.photo_urls.length > 0) {
        photos = (v.photo_urls as string[]).slice(0, 3);
      } else if (v.photo_url) {
        photos = [v.photo_url];
      }

      const htmlBody = generateViolationNoticeEmailHtml({
        ownerName,
        associationName: assocName,
        managementCompany: "Avria Community Management",
        associationAddress: assocAddr || undefined,
        managementPhone: assoc.phone || undefined,
        managementEmail: assoc.email || assoc.contact_email || undefined,
        managementWebsite: assoc.website || undefined,
        propertyAddress: v.address || v.owners?.property_address || "N/A",
        violationType: v.violation_type || v.title || "General",
        noticeLevel: v.stage || v.notice_level || "First Notice",
        articleSection: vt.article_section || undefined,
        citationText: vt.citation || undefined,
        requestedAction: vt.requested_action || v.description || undefined,
        violationDate: v.violation_date
          ? format(parseLocalDate(v.violation_date), "MMMM d, yyyy")
          : format(new Date(v.created_at), "MMMM d, yyyy"),
        dueDate: v.due_date ? format(parseLocalDate(v.due_date), "MMMM d, yyyy") : undefined,
        description: v.description || undefined,
        notes: v.notes || undefined,
        photoUrls: photos.length > 0 ? photos : undefined,
        violationId: v.id,
        certifiedMail: (v as any).certified_mail || undefined,
      });

      const result = await sendEmailViaSMTP({
        sender_id: emailSenderId,
        recipient_email: v.owners!.email!,
        subject: `Violation Notice — ${v.stage || v.notice_level || "First Notice"} — ${v.violation_type || v.title || "General"} — ${v.address || v.owners?.property_address || ""}`,
        htmlBody,
        body: htmlBody,
      });
      if (result.success) {
        successCount++;
        logNoticeGenerated(v.id, { noticeLevel: v.stage || v.notice_level || "First Notice", action: "email_sent" });
      } else {
        failCount++;
        const errMsg = (result as any)?.error || "Unknown error";
        console.error(`[ViolationsPage] Email failed for ${v.owners?.email}:`, errMsg, result);
        failErrors.push(`${v.owners?.email}: ${errMsg}`);
      }
    }
    setEmailNoticeSending(false);
    setEmailNoticeDialogOpen(false);
    toast({
      title: `Email notices sent`,
      description: `${successCount} sent successfully${failCount > 0 ? `, ${failCount} failed. ${failErrors[0] || ""}` : ""}`,
      variant: failCount > 0 ? "destructive" : "default",
    });
  };

  const fetchData = useCallback(async () => {
    setLoading(true);
    let violQuery = supabase
      .from("violations")
      .select("*, units(unit_number), associations(name), owners(first_name, last_name, property_address, email, electronic_consent, status)")
      .order("created_at", { ascending: true });
    let assocQuery = supabase.from("associations").select("*").eq("status", "active").order("name");
    
    if (isBoardView) {
      violQuery = violQuery.in("association_id", boardAssociationIds!);
      assocQuery = assocQuery.in("id", boardAssociationIds!);
    }
    
    const [violRes, assocRes] = await Promise.all([violQuery, assocQuery]);
    
    let enrichedViolations = ((violRes.data || []) as ViolationRow[]).map(v => {
      // Null out archived owners from the join so they don't appear in UI/PDF
      if ((v.owners as any)?.status === 'archived') return { ...v, owners: null };
      return v;
    });
    
    // Fetch all owners to resolve co-owners per unit/address
    if (enrichedViolations.length > 0) {
      const assocIds = [...new Set(enrichedViolations.map(v => v.association_id).filter(Boolean))];
      if (assocIds.length > 0) {
        const { data: allOwners } = await supabase
          .from("owners")
          .select("id, first_name, last_name, property_address, unit_id, association_id, status")
          .in("association_id", assocIds)
          .neq("status", "archived");
        
        if (allOwners && allOwners.length > 0) {
          const normalize = (addr: string | null) => (addr || '').toLowerCase().replace(/[.,#\-\s]+/g, ' ').trim();
          
          enrichedViolations = enrichedViolations.map(v => {
            let coOwners: typeof allOwners = [];
            // Match by unit_id first
            if (v.unit_id) {
              coOwners = allOwners.filter(o => o.unit_id === v.unit_id && o.association_id === v.association_id);
            }
            // Fallback to address match
            if (coOwners.length === 0 && v.address) {
              const normAddr = normalize(v.address);
              coOwners = allOwners.filter(o => o.association_id === v.association_id && normalize(o.property_address) === normAddr);
            }
            // Fallback to single linked owner
            if (coOwners.length === 0 && v.owner_id) {
              const single = allOwners.find(o => o.id === v.owner_id);
              if (single) coOwners = [single];
            }
            
            if (coOwners.length > 0) {
              const names = coOwners
                .map(o => `${o.first_name || ''} ${o.last_name || ''}`.trim())
                .filter(Boolean);
              const unique = [...new Set(names)];
              let combined = 'Unknown Owner';
              if (unique.length === 1) combined = unique[0];
              else if (unique.length === 2) combined = `${unique[0]} & ${unique[1]}`;
              else if (unique.length > 2) combined = unique.slice(0, -1).join(', ') + ' & ' + unique[unique.length - 1];
              return { ...v, allOwnerNames: combined };
            }
            return v;
          });
        }
      }
    }
    
    if (enrichedViolations) setViolations(enrichedViolations);
    if (assocRes.data) setAssociations(assocRes.data);
    setLoading(false);
  }, [isBoardView, boardAssociationIds]);

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

  const filtered = useMemo(() => {
    return violations.filter((v) => {
      const matchesSearch = !search ||
        (v.title?.toLowerCase().includes(search.toLowerCase())) ||
        (v.violation_type?.toLowerCase().includes(search.toLowerCase())) ||
        (v.address?.toLowerCase().includes(search.toLowerCase())) ||
        ((v.owners as any)?.property_address?.toLowerCase().includes(search.toLowerCase()));
      const matchesClient = clientFilter === "all" || v.association_id === clientFilter;
      const matchesStart = !startDate || new Date(v.created_at) >= new Date(startDate);
      const matchesEnd = !endDate || new Date(v.created_at) <= new Date(endDate + "T23:59:59");
      const vStatus = v.status?.toLowerCase();
      const matchesStatus =
        statusTab === "all" ||
        (statusTab === "open" ? (vStatus === "open" || vStatus === "escalated" || vStatus === "recommended_for_fining") : vStatus === statusTab);
      return matchesSearch && matchesClient && matchesStart && matchesEnd && matchesStatus;
    });
  }, [violations, search, clientFilter, startDate, endDate, statusTab]);

  const statusCounts = useMemo(() => {
    const base = violations.filter((v) => {
      const matchesClient = clientFilter === "all" || v.association_id === clientFilter;
      const matchesStart = !startDate || new Date(v.created_at) >= new Date(startDate);
      const matchesEnd = !endDate || new Date(v.created_at) <= new Date(endDate + "T23:59:59");
      const matchesSearch = !search ||
        (v.title?.toLowerCase().includes(search.toLowerCase())) ||
        (v.violation_type?.toLowerCase().includes(search.toLowerCase())) ||
        (v.address?.toLowerCase().includes(search.toLowerCase()));
      return matchesClient && matchesStart && matchesEnd && matchesSearch;
    });
    return {
      all: base.length,
      open: base.filter(v => v.status === "open" || v.status === "escalated" || v.status === "recommended_for_fining").length,
      resolved: base.filter(v => v.status === "resolved").length,
    };
  }, [violations, clientFilter, startDate, endDate, search]);

  const groupedByStage = useMemo(() => {
    const groups: Record<string, ViolationRow[]> = {};
    filtered.forEach((v) => {
      const stage = v.stage || v.notice_level || "Unassigned";
      if (!groups[stage]) groups[stage] = [];
      groups[stage].push(v);
    });
    return groups;
  }, [filtered]);

  const handleViewDetails = (v: ViolationRow) => {
    setSelectedViolation(v);
    setDetailsDialogOpen(true);
  };

  const handleEdit = () => {
    setEditingViolation(selectedViolation);
    setDetailsDialogOpen(false);
    setLogDialogOpen(true);
  };

  const handleStatusChange = async (id: string, status: string) => {
    const currentViolation = violations.find((v: any) => v.id === id);
    const previousStatus = currentViolation?.status || null;
    
    // If "fined" is passed, mark as "closed" instead
    const normalizedStatus = status.toLowerCase() === "fined" ? "closed" : status;
    
    // If "escalated" is passed, bump stage instead of changing status
    if (status.toLowerCase() === "escalated") {
      const nextStage = getNextStage(currentViolation?.stage || currentViolation?.notice_level || null);
      if (!nextStage) {
        toast({ title: "Already at maximum stage", description: "This violation is already at Third & Final Notice.", variant: "destructive" });
        return;
      }
      const { error } = await supabase.from("violations").update({ stage: nextStage, notice_level: nextStage, status: "open" }).eq("id", id);
      if (error) throw error;
      await logStatusChange(id, currentViolation?.stage || "Unknown", nextStage);
      toast({ title: "Violation Escalated", description: `Moved to ${nextStage}.` });
      fetchData();
      return;
    }
    const { error } = await supabase.from("violations").update({ status: normalizedStatus }).eq("id", id);
    if (error) throw error;
    await logStatusChange(id, previousStatus, normalizedStatus);
    fetchData();
  };

  const toggleSelect = (id: string) => {
    setSelectedIds(prev => prev.includes(id) ? prev.filter(x => x !== id) : [...prev, id]);
  };

  const toggleSelectAll = () => {
    if (selectedIds.length === filtered.length) setSelectedIds([]);
    else setSelectedIds(filtered.map(v => v.id));
  };

  return (
    <div className="max-w-[1440px] mx-auto p-5 space-y-5">
      {/* Header */}
      <div className="flex flex-col sm:flex-row items-start sm:items-center justify-between gap-3">
        <div>
          <h1 className="text-[22px] font-semibold tracking-tight text-foreground flex items-center gap-2">
            <Shield className="w-5 h-5 text-primary" /> Violations
          </h1>
          <p className="text-[13px] text-muted-foreground mt-0.5">Manage compliance violations across all communities.</p>
        </div>
        <div className="flex items-center gap-1.5 flex-wrap">
          {isBoardView ? (
            <>
              <Button variant="outline" size="sm" className="h-8 text-[12px] gap-1.5" onClick={() => setExportDialogOpen(true)}>
                <FileDown className="w-3.5 h-3.5" /> Export
              </Button>
              <Button variant="outline" size="sm" className="h-8 text-[12px] gap-1.5" onClick={() => setStatusExportDialogOpen(true)}>
                <FileDown className="w-3.5 h-3.5" /> Export by Status
              </Button>
            </>
          ) : (
            <>
              <RecordImportButton
                title="Import Violations"
                description="Upload a CSV file to import violation records."
                expectedColumns={[
                  { key: "address", label: "Address", aliases: ["property_address", "property_unit", "location", "street_address", "street"] },
                  { key: "association", label: "Association Name", aliases: ["association_name", "community", "property_name", "hoa_name"] },
                  { key: "violation_type", label: "Violation Type", aliases: ["type", "category", "violation_category"] },
                  { key: "status", label: "Status", aliases: ["violation_status"] },
                  { key: "photo_url", label: "Photo URL" },
                  { key: "notes", label: "Notes", aliases: ["owner_response", "comments", "remarks"] },
                  { key: "created_at", label: "Created At", aliases: ["create_date", "created_date"] },
                  { key: "violation_date", label: "Violation Date", aliases: ["date_issued", "date", "issued_date", "notice_date"] },
                  { key: "due_date", label: "Due Date", aliases: ["deadline", "response_due", "compliance_date"] },
                  { key: "priority", label: "Priority" },
                  { key: "tags", label: "Tags", aliases: ["labels", "keywords", "violation_tags"] },
                  { key: "notice_level", label: "Notice Level", aliases: ["notice_stage", "escalation_level"] },
                  { key: "notice_history", label: "Notice History (JSON)" },
                  { key: "stage", label: "Stage" },
                  { key: "timeline_entries", label: "Timeline Entries (JSON)" },
                  { key: "photo_urls", label: "Photo URLs", aliases: ["photos", "images", "image_urls"] },
                  { key: "description", label: "Description", aliases: ["details", "summary"] },
                  { key: "created_by", label: "Created By (user ID)" },
                  { key: "assigned_to", label: "Assigned To" },
                  { key: "property_id", label: "Owner ID (property_id)" },
                  { key: "unit_id", label: "Unit ID" },
                  { key: "id", label: "Violation ID", aliases: ["violation_id", "record_id"] },
                ]}
                onImport={async (rows) => {
                  const [{ data: allAssocs }, { data: allOwners }] = await Promise.all([
                    supabase.from("associations").select("id, name"),
                    supabase.from("owners").select("id, property_address, first_name, last_name").eq("status", "active"),
                  ]);
                  const assocMap = new Map((allAssocs || []).map(a => [a.name.toLowerCase().trim(), a.id]));
                  const normalizeAddr = (s: string) => s.toLowerCase().replace(/[.,#\-]/g, '').replace(/\s+/g, ' ').trim();
                  const ownerByAddress = new Map<string, string>();
                  for (const o of allOwners || []) {
                    if (o.property_address) ownerByAddress.set(normalizeAddr(o.property_address), o.id);
                  }
                  const { data: existingTypes } = await supabase.from("violation_types").select("category, association_id");
                  const typeSet = new Set((existingTypes || []).map(t => `${t.association_id}::${t.category.toLowerCase().trim()}`));
                  const typesToCreate: { association_id: string; category: string }[] = [];
                  let successCount = 0;
                  const errors: string[] = [];
                  for (const row of rows) {
                    const address = row.address?.trim();
                    if (!address) { errors.push("Skipped row: missing address"); continue; }
                    const assocName = row.association?.toLowerCase().trim();
                    const associationId = assocMap.get(assocName || "");
                    if (!associationId) { errors.push(`${address}: association "${row.association}" not found`); continue; }
                    let ownerId: string | null = row.property_id?.trim() || null;
                    if (!ownerId) { ownerId = ownerByAddress.get(normalizeAddr(address)) || null; }
                    let unitId: string | null = row.unit_id?.trim() || null;
                    const rawStatus = row.status?.toLowerCase().trim() || "open";
                    const validStatuses = ['open', 'pending', 'resolved', 'escalated', 'closed', 'fined'];
                    const status = validStatuses.includes(rawStatus) ? rawStatus : 'open';
                    const rawPriority = row.priority?.toLowerCase().trim() || null;
                    const validPriorities = ['low', 'medium', 'high', 'critical'];
                    const priority = rawPriority && validPriorities.includes(rawPriority) ? rawPriority : null;
                    let noticeHistory = null;
                    if (row.notice_history) { try { noticeHistory = JSON.parse(row.notice_history); } catch { noticeHistory = null; } }
                    let timelineEntries = null;
                    if (row.timeline_entries) { try { timelineEntries = JSON.parse(row.timeline_entries); } catch { timelineEntries = null; } }
                    const tags = row.tags ? row.tags.split(",").map((t: string) => t.trim()).filter(Boolean) : [];
                    let photoUrls: string[] | null = null;
                    if (row.photo_urls) { photoUrls = row.photo_urls.split(/[,;]\s*/).map((u: string) => u.trim()).filter(Boolean); }
                    else if (row.photo_url) { photoUrls = row.photo_url.split(/[,;]\s*/).map((u: string) => u.trim()).filter(Boolean); }
                    if (tags) { for (const tag of tags) { const key = `${associationId}::${tag.toLowerCase()}`; if (!typeSet.has(key)) { typeSet.add(key); typesToCreate.push({ association_id: associationId, category: tag }); } } }
                    try {
                      const record: any = {
                        title: row.violation_type?.trim() || address, association_id: associationId,
                        violation_type: row.violation_type?.trim() || null, address, description: row.description?.trim() || null,
                        status, priority, stage: row.stage?.trim() || null,
                        notice_level: row.notice_level?.trim() || row.stage?.trim() || null,
                        violation_date: row.violation_date?.trim() || null, due_date: row.due_date?.trim() || null,
                        owner_id: ownerId, unit_id: unitId, notes: row.notes?.trim() || null,
                        photo_url: row.photo_url?.trim() || null, photo_urls: photoUrls,
                        assigned_to: row.assigned_to?.trim() || null, tags, notice_history: noticeHistory,
                        timeline_entries: timelineEntries, created_by: row.created_by?.trim() || null,
                        ...(row.created_at?.trim() ? { created_at: row.created_at.trim() } : {}),
                        ...(row.updated_at?.trim() ? { updated_at: row.updated_at.trim() } : {}),
                      };
                      if (row.id?.trim()) record.id = row.id.trim();
                      const { error: insertErr } = await supabase.from("violations").upsert(record, { onConflict: "id" });
                      if (insertErr) throw insertErr;
                      successCount++;
                    } catch (err: any) { errors.push(`${address}: ${err.message}`); }
                  }
                  if (typesToCreate.length > 0) { await supabase.from("violation_types").insert(typesToCreate); }
                  if (errors.length) { toast({ title: `${successCount} imported, ${errors.length} failed`, description: errors.slice(0, 3).join("\n"), variant: "destructive" }); }
                  else { toast({ title: `${successCount} violation(s) imported successfully` }); }
                  fetchData();
                }}
                label="Import CSV"
              />
              <Button variant="outline" size="sm" className="h-8 text-[12px] gap-1.5" onClick={async () => {
                const targets = selectedIds.length > 0
                  ? violations.filter(v => selectedIds.includes(v.id))
                  : filtered;
                if (targets.length === 0) {
                  toast({ title: "No violations to generate notices for", variant: "destructive" });
                  return;
                }
                // Enrich with violation_types data
                const vtCategories = [...new Set(targets.map(v => v.violation_type).filter(Boolean))];
                const vtMap: Record<string, any> = {};
                if (vtCategories.length > 0) {
                  const { data: vtData } = await supabase
                    .from("violation_types")
                    .select("category, article_section, citation, requested_action")
                    .in("category", vtCategories);
                  (vtData || []).forEach(vt => { vtMap[vt.category] = vt; });
                }
                const enriched = targets.map(v => {
                  const vt = vtMap[v.violation_type || ""];
                  if (!vt) return v;
                  const history = (v as any).notice_history || [];
                  const latestHistory = history.length > 0 ? history[history.length - 1] : {};
                  return {
                    ...v,
                    article: latestHistory.article_section || vt.article_section || (v as any).article,
                    citation: latestHistory.citation || vt.citation || (v as any).citation,
                    description: v.description || vt.requested_action || latestHistory.requested_action,
                  };
                });
                setGenerateNoticeViolations(enriched as ViolationRow[]);
              }}>
                <FileText className="w-3.5 h-3.5" /> Generate Notice{selectedIds.length > 0 ? ` (${selectedIds.length})` : ""}
              </Button>
              {(() => {
                const targets = selectedIds.length > 0
                  ? violations.filter(v => selectedIds.includes(v.id))
                  : filtered;
                const eligibleCount = targets.filter(v => v.owners?.electronic_consent && v.owners?.email).length;
                return (
                  <Button
                    variant="outline"
                    size="sm"
                    className="h-8 text-[12px] gap-1.5"
                    disabled={eligibleCount === 0}
                    title={eligibleCount === 0 ? "No owners with electronic consent and email" : undefined}
                    onClick={() => handleOpenEmailNotice(targets)}
                  >
                    <Mail className="w-3.5 h-3.5" /> Email Notice{selectedIds.length > 0 ? ` (${eligibleCount} eligible)` : ""}
                  </Button>
                );
              })()}
              <Button variant="outline" size="sm" className="h-8 text-[12px] gap-1.5" onClick={() => setNoticeReportOpen(true)}>
                <FileText className="w-3.5 h-3.5" /> Notice Reports
              </Button>
              <Button variant="outline" size="sm" className="h-8 text-[12px] gap-1.5" onClick={() => setImportDialogOpen(true)}>
                <Upload className="w-3.5 h-3.5" /> Import Timeline
              </Button>
              <Button variant="outline" size="sm" className="h-8 text-[12px] gap-1.5" onClick={() => setExportDialogOpen(true)}>
                <FileDown className="w-3.5 h-3.5" /> Export
              </Button>
              <Button size="sm" className="h-8 text-[12px] gap-1.5" onClick={() => { setEditingViolation(null); setLogDialogOpen(true); }}>
                <Plus className="w-3.5 h-3.5" /> Create Violation
              </Button>
            </>
          )}
        </div>
      </div>

      {/* Filters */}
      <Card className="border shadow-sm">
        <CardContent className="p-3">
          <div className="flex flex-col lg:flex-row gap-2.5 items-end">
            <div className="flex-1 grid grid-cols-1 sm:grid-cols-5 gap-2.5">
              {/* Search */}
              <div className="sm:col-span-2">
                <label className="text-[11px] font-semibold uppercase tracking-wider text-muted-foreground mb-1 block">Search</label>
                <div className="relative">
                  <Search className="absolute left-2.5 top-1/2 -translate-y-1/2 h-3.5 w-3.5 text-muted-foreground/40" />
                  <Input placeholder="Owner, address, type…" value={search} onChange={e => setSearch(e.target.value)} className="h-8 pl-8 text-[13px] bg-muted/50 border-transparent focus:border-border focus:bg-card" />
                </div>
              </div>
              {/* Property / Association */}
              <div>
                <label className="text-[11px] font-semibold uppercase tracking-wider text-muted-foreground mb-1 block">Property</label>
                <Select value={clientFilter} onValueChange={setClientFilter}>
                  <SelectTrigger className="h-8 text-[13px]"><SelectValue placeholder="All" /></SelectTrigger>
                  <SelectContent>
                    <SelectItem value="all">All Properties</SelectItem>
                    {associations.map(a => (
                      <SelectItem key={a.id} value={a.id}>{a.name}</SelectItem>
                    ))}
                  </SelectContent>
                </Select>
              </div>
              {/* Start Date */}
              <div>
                <label className="text-[11px] font-semibold uppercase tracking-wider text-muted-foreground mb-1 block">From</label>
                <Input type="date" value={startDate} onChange={e => setStartDate(e.target.value)} className="h-8 text-[13px]" />
              </div>
              {/* End Date */}
              <div>
                <label className="text-[11px] font-semibold uppercase tracking-wider text-muted-foreground mb-1 block">To</label>
                <Input type="date" value={endDate} onChange={e => setEndDate(e.target.value)} className="h-8 text-[13px]" />
              </div>
            </div>
            {/* View controls */}
            <div className="flex items-center gap-1.5 shrink-0">
              <div className="flex border rounded-lg overflow-hidden">
                <Button variant={viewMode === "table" ? "default" : "ghost"} size="icon" className="rounded-none h-8 w-8" onClick={() => setViewMode("table")}>
                  <List className="w-3.5 h-3.5" />
                </Button>
                <Button variant={viewMode === "grid" ? "default" : "ghost"} size="icon" className="rounded-none h-8 w-8" onClick={() => setViewMode("grid")}>
                  <Grid3X3 className="w-3.5 h-3.5" />
                </Button>
                <Button variant={viewMode === "card" ? "default" : "ghost"} size="icon" className="rounded-none h-8 w-8" onClick={() => setViewMode("card")}>
                  <LayoutGrid className="w-3.5 h-3.5" />
                </Button>
              </div>
              {!isBoardView && (
                <Button variant={showSelection ? "default" : "outline"} size="sm" className="h-8 text-[12px] gap-1.5" onClick={() => { setShowSelection(!showSelection); setSelectedIds([]); }}>
                  <CheckSquare className="w-3.5 h-3.5" /> Select
                </Button>
              )}
              {!isBoardView && (
                <Button variant="outline" size="sm" className="h-8 text-[12px] gap-1.5" onClick={() => setTypesDialogOpen(!typesDialogOpen)}>
                  <Settings className="w-3.5 h-3.5" /> Types
                </Button>
              )}
            </div>
          </div>
        </CardContent>
      </Card>

      {/* Group-by & Status tabs */}
      <div className="flex flex-col sm:flex-row items-start sm:items-center justify-between gap-3">
        <Tabs value={groupBy} onValueChange={(v) => setGroupBy(v as any)} className="w-auto">
          <TabsList className="bg-muted/50 h-8">
            <TabsTrigger value="status" className="text-[12px] h-6">By Status</TabsTrigger>
            <TabsTrigger value="stage" className="text-[12px] h-6">By Stage</TabsTrigger>
            <TabsTrigger value="timeline" className="text-[12px] h-6 gap-1">
              <CalendarIcon className="w-3 h-3" /> Timeline
            </TabsTrigger>
          </TabsList>
        </Tabs>
        <Button variant="outline" size="sm" className="h-7 text-[11px] gap-1.5" onClick={() => setStatusExportDialogOpen(true)}>
          <FileDown className="w-3 h-3" /> Export by Status
        </Button>
      </div>

      {/* Status sub-tabs */}
      {groupBy === "status" && (
        <div className="flex flex-wrap gap-1">
          {(Object.keys(STATUS_CONFIG) as Array<keyof typeof STATUS_CONFIG>).map(status => {
            const count = statusCounts[status as keyof typeof statusCounts] ?? 0;
            if (status !== "all" && count === 0) return null;
            const config = STATUS_CONFIG[status];
            return (
              <Button
                key={status}
                variant="ghost"
                size="sm"
                className={`h-7 text-[12px] gap-1.5 rounded-lg ${statusTab === status ? "bg-muted font-semibold" : ""}`}
                onClick={() => setStatusTab(status as string)}
              >
                {config.label}
                <Badge variant="secondary" className="text-2xs px-1.5 h-4 min-w-[18px]">{count}</Badge>
              </Button>
            );
          })}
        </div>
      )}

      {/* Select All bar + Bulk Actions */}
      {showSelection && (
        <div className="flex items-center gap-3 px-1">
          <Checkbox
            checked={filtered.length > 0 && selectedIds.length === filtered.length}
            onCheckedChange={toggleSelectAll}
            disabled={filtered.length === 0}
          />
          <span className="text-[12px] text-muted-foreground">
            {selectedIds.length === 0
              ? `Select all (${filtered.length})`
              : `${selectedIds.length} of ${filtered.length} selected`}
          </span>
          {selectedIds.length > 0 && selectedIds.length < filtered.length && (
            <Button variant="link" size="sm" className="h-auto p-0 text-[12px]" onClick={() => setSelectedIds(filtered.map(v => v.id))}>
              Select all {filtered.length}
            </Button>
          )}
          {selectedIds.length > 0 && (
            <Button variant="link" size="sm" className="h-auto p-0 text-[12px] text-muted-foreground" onClick={() => setSelectedIds([])}>
              Clear
            </Button>
          )}
        </div>
      )}

      {!isBoardView && selectedIds.length > 0 && (() => {
        let bulkAssocId: string | undefined;
        if (clientFilter !== "all") {
          bulkAssocId = clientFilter;
        } else {
          const assocIds = new Set(
            violations.filter(v => selectedIds.includes(v.id)).map(v => v.association_id).filter(Boolean)
          );
          if (assocIds.size === 1) bulkAssocId = Array.from(assocIds)[0] as string;
        }
        return (
          <ViolationBulkActions
            selectedIds={selectedIds}
            onClear={() => setSelectedIds([])}
            onComplete={() => { setSelectedIds([]); fetchData(); }}
            associationId={bulkAssocId}
          />
        );
      })()}

      {/* Content */}
      {loading ? (
        <div className="flex justify-center py-16">
          <div className="h-6 w-6 animate-spin rounded-full border-2 border-primary border-t-transparent" />
        </div>
      ) : filtered.length === 0 ? (
        <Card className="border shadow-sm">
          <CardContent className="py-16 text-center">
            <AlertTriangle className="mx-auto h-10 w-10 mb-3 text-muted-foreground/30" />
            <p className="text-[13px] text-muted-foreground">{search || clientFilter !== "all" || statusTab !== "all" ? "No violations match your filters." : "No violations recorded yet."}</p>
            {!isBoardView && (
              <Button size="sm" className="mt-4 h-8 text-[12px] gap-1.5" onClick={() => { setEditingViolation(null); setLogDialogOpen(true); }}>
                <Plus className="w-3.5 h-3.5" /> Create First Violation
              </Button>
            )}
          </CardContent>
        </Card>
      ) : groupBy === "stage" ? (
        <div className="space-y-6">
          {Object.entries(groupedByStage).map(([stage, items]) => (
            <div key={stage}>
              <h3 className="text-[13px] font-semibold text-muted-foreground uppercase tracking-wider mb-2 flex items-center gap-2">
                {stage} <Badge variant="secondary" className="text-2xs">{items.length}</Badge>
              </h3>
              {viewMode === "table" ? (
                <ViolationTableView
                  violations={items}
                  selectedIds={selectedIds}
                  onToggleSelect={toggleSelect}
                  onToggleSelectAll={toggleSelectAll}
                  onViewDetails={handleViewDetails}
                  onRefresh={fetchData}
                />
              ) : (
                <div className={`grid gap-3 ${viewMode === "card" ? "grid-cols-1 sm:grid-cols-2" : "grid-cols-1 sm:grid-cols-2 lg:grid-cols-3"}`}>
                  {items.map(v => (
                    <div key={v.id} onClick={() => handleViewDetails(v)} className="cursor-pointer">
                      <ViolationCard
                        violation={v}
                        onStatusChange={fetchData}
                        isSelected={selectedIds.includes(v.id)}
                        onSelect={(id: string, checked: boolean) => toggleSelect(id)}
                        showSelection={showSelection}
                        canEdit={!isBoardView}
                      />
                    </div>
                  ))}
                </div>
              )}
            </div>
          ))}
        </div>
      ) : groupBy === "timeline" ? (
        <div className="space-y-2">
          {filtered.map(v => (
            <Card key={v.id} className="border shadow-sm cc-card-hover cursor-pointer" onClick={() => handleViewDetails(v)}>
              <CardContent className="py-3 px-4 flex items-center gap-4">
                <div className="text-center shrink-0 w-14">
                  <div className="text-[18px] font-semibold text-foreground leading-none">{format(new Date(v.created_at), "dd")}</div>
                  <div className="text-[11px] text-muted-foreground mt-0.5">{format(new Date(v.created_at), "MMM yyyy")}</div>
                </div>
                <div className="h-8 w-px bg-border" />
                <div className="flex-1 min-w-0">
                  <div className="flex items-center gap-2 mb-0.5">
                    <span className="text-[13px] font-medium text-foreground truncate">{v.violation_type || v.title}</span>
                    <span className={`inline-flex items-center rounded-full px-2 py-0.5 text-2xs font-medium border capitalize ${STATUS_CONFIG[v.status || ""]?.cls || "cc-badge-neutral"}`}>
                      {STATUS_CONFIG[v.status || ""]?.label || v.status}
                    </span>
                    {v.stage && <span className="inline-flex items-center rounded-full px-2 py-0.5 text-2xs font-medium border cc-badge-neutral">{v.stage}</span>}
                  </div>
                  <p className="text-[12px] text-muted-foreground truncate">{(v.owners as any)?.property_address || v.address || "No address"}</p>
                </div>
                {v.due_date && (
                  <div className="text-right shrink-0">
                    <p className="text-[11px] text-muted-foreground">Due</p>
                    <p className="text-[13px] font-medium">{format(parseLocalDate(v.due_date), "MMM d, yyyy")}</p>
                  </div>
                )}
              </CardContent>
            </Card>
          ))}
        </div>
      ) : viewMode === "table" ? (
        <ViolationTableView
          violations={filtered}
          selectedIds={selectedIds}
          onToggleSelect={toggleSelect}
          onToggleSelectAll={toggleSelectAll}
          onViewDetails={handleViewDetails}
          onRefresh={fetchData}
        />
      ) : (
        <div className={`grid gap-3 ${viewMode === "card" ? "grid-cols-1 sm:grid-cols-2" : "grid-cols-1 sm:grid-cols-2 lg:grid-cols-3"}`}>
          {filtered.map(v => (
            <div key={v.id} onClick={() => handleViewDetails(v)} className="cursor-pointer">
              <ViolationCard
                violation={v}
                onStatusChange={fetchData}
                isSelected={selectedIds.includes(v.id)}
                onSelect={(id: string, checked: boolean) => toggleSelect(id)}
                showSelection={showSelection}
                canEdit={!isBoardView}
              />
            </div>
          ))}
        </div>
      )}

      {/* Types Manager (inline expandable) */}
      {typesDialogOpen && clientFilter !== "all" && (
        <Card className="border shadow-sm">
          <CardContent className="pt-4">
            <ViolationTypeManager associationId={clientFilter} />
          </CardContent>
        </Card>
      )}
      {typesDialogOpen && clientFilter === "all" && (
        <Card className="border shadow-sm">
          <CardContent className="py-8 text-center text-[13px] text-muted-foreground">
            Please select a specific property to manage violation types.
          </CardContent>
        </Card>
      )}

      {/* Dialogs */}
      {!isBoardView && (
        <ViolationDialog
          open={logDialogOpen}
          onOpenChange={setLogDialogOpen}
          violation={editingViolation}
          onSuccess={() => { fetchData(); setEditingViolation(null); }}
          associationId={clientFilter !== "all" ? clientFilter : undefined}
        />
      )}

      <ViolationDetailsDialog
        open={detailsDialogOpen}
        onOpenChange={setDetailsDialogOpen}
        violation={selectedViolation}
        onEdit={isBoardView ? undefined : handleEdit}
        onUpdate={fetchData}
        onStatusChange={isBoardView ? undefined : handleStatusChange}
        onRegenerateNotice={isBoardView ? undefined : (v: ViolationRow) => setRegenerateNoticeViolation(v)}
        onEmailNotice={isBoardView ? undefined : (v: ViolationRow) => handleOpenEmailNotice([v])}
      />

      <ViolationExportDialog
        open={exportDialogOpen}
        onOpenChange={setExportDialogOpen}
        violations={filtered}
      />

      <ViolationStatusExportDialog
        open={statusExportDialogOpen}
        onOpenChange={setStatusExportDialogOpen}
        violations={filtered}
        preselectedStatus={statusTab !== "all" ? statusTab : "all"}
      />

      <ViolationTimelineImportDialog
        open={importDialogOpen}
        onOpenChange={setImportDialogOpen}
        onSuccess={fetchData}
      />

      {/* Regenerate Notice Dialog */}
      <Dialog open={!!regenerateNoticeViolation} onOpenChange={(open) => { if (!open) setRegenerateNoticeViolation(null); }}>
        <DialogContent className="max-w-5xl w-[95vw] max-h-[90vh] overflow-y-auto p-0">
          {regenerateNoticeViolation && (
            <ViolationNoticeForm
              initialViolations={[regenerateNoticeViolation]}
              onClose={() => setRegenerateNoticeViolation(null)}
            />
          )}
        </DialogContent>
      </Dialog>

      {/* Generate Notice Dialog */}
      <Dialog open={!!generateNoticeViolations} onOpenChange={(open) => { if (!open) setGenerateNoticeViolations(null); }}>
        <DialogContent className="max-w-5xl w-[95vw] max-h-[90vh] overflow-y-auto p-0">
          {generateNoticeViolations && (
            <ViolationNoticeForm
              initialViolations={generateNoticeViolations}
              onClose={() => setGenerateNoticeViolations(null)}
            />
          )}
        </DialogContent>
      </Dialog>

      {/* Email Notice Dialog */}
      <Dialog open={emailNoticeDialogOpen} onOpenChange={(open) => { if (!open) setEmailNoticeDialogOpen(false); }}>
        <DialogContent className="max-w-lg">
          <DialogHeader>
            <DialogTitle className="flex items-center gap-2"><Mail className="w-5 h-5 text-primary" /> Email Violation Notices</DialogTitle>
            <DialogDescription>Send violation notices via email to owners with electronic consent.</DialogDescription>
          </DialogHeader>
          <div className="space-y-4 py-2">
            {emailSenders.length > 0 && (
              <div>
                <label className="text-[12px] font-semibold text-muted-foreground mb-1 block">Send From</label>
                <Select value={emailSenderId} onValueChange={setEmailSenderId}>
                  <SelectTrigger className="h-9 text-[13px]"><SelectValue placeholder="Select sender" /></SelectTrigger>
                  <SelectContent>
                    {emailSenders.map((s: any) => (
                      <SelectItem key={s.id} value={s.id}>{s.sender_name || s.email_address} ({s.email_address})</SelectItem>
                    ))}
                  </SelectContent>
                </Select>
              </div>
            )}
            {emailSenders.length === 0 && (
              <p className="text-sm text-destructive">No email senders configured. Please set up SMTP senders first.</p>
            )}
            <div>
              <label className="text-[12px] font-semibold text-muted-foreground mb-1 block">
                Recipients ({emailNoticeTargets.filter(v => emailNoticeSelected[v.id]).length} of {emailNoticeTargets.length} eligible)
              </label>
              <div className="max-h-[300px] overflow-y-auto border rounded-md divide-y">
                {emailNoticeTargets.map(v => {
                  const ownerName = `${v.owners?.first_name || ""} ${v.owners?.last_name || ""}`.trim();
                  return (
                    <label key={v.id} className="flex items-center gap-3 p-3 hover:bg-muted/50 cursor-pointer">
                      <Checkbox
                        checked={!!emailNoticeSelected[v.id]}
                        onCheckedChange={(checked) => setEmailNoticeSelected(prev => ({ ...prev, [v.id]: !!checked }))}
                      />
                      <div className="flex-1 min-w-0">
                        <p className="text-[13px] font-medium truncate">{ownerName || "Unknown"}</p>
                        <p className="text-[11px] text-muted-foreground truncate">{v.owners?.email} — {v.violation_type || v.title || "General"}</p>
                        <p className="text-[11px] text-muted-foreground truncate">{v.address || v.owners?.property_address || ""}</p>
                      </div>
                      <Badge variant="outline" className="text-[10px] shrink-0">E-Consent ✓</Badge>
                    </label>
                  );
                })}
              </div>
            </div>
          </div>
          <DialogFooter>
            <Button variant="outline" onClick={() => setEmailNoticeDialogOpen(false)}>Cancel</Button>
            <Button
              onClick={handleSendEmailNotices}
              disabled={emailNoticeSending || emailSenders.length === 0 || emailNoticeTargets.filter(v => emailNoticeSelected[v.id]).length === 0}
              className="gap-1.5"
            >
              {emailNoticeSending ? "Sending…" : <><Mail className="w-4 h-4" /> Send {emailNoticeTargets.filter(v => emailNoticeSelected[v.id]).length} Notice{emailNoticeTargets.filter(v => emailNoticeSelected[v.id]).length !== 1 ? "s" : ""}</>}
            </Button>
          </DialogFooter>
        </DialogContent>
      </Dialog>
    </div>
  );
}
