import { useQuery, useQueryClient } from "@tanstack/react-query";
import { useMemo, useRef, useState } from "react";
import { accounting } from "@/lib/accountingClient";
import { supabase } from "@/integrations/supabase/client";
import { useCompanyId } from "./lib/useCompanyId";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogTrigger, DialogFooter } from "@/components/ui/dialog";
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
import { Table, TableHeader, TableRow, TableHead, TableBody, TableCell } from "@/components/ui/table";
import { Plus, Trash2, Search, Receipt, Upload, Sparkles, FileText, X, AlertCircle, Printer, Loader2, Pencil } from "lucide-react";
import { toast } from "sonner";
import { money, fmtDate } from "./lib/format";
import { StatusBadge } from "./components/StatusBadge";
import { EmptyState } from "./components/EmptyState";
import { TableSkeleton } from "./components/TableSkeleton";
import { parseBill } from "./lib/parseBill";
import { cn } from "@/lib/utils";
import { Switch } from "@/components/ui/switch";
import { Textarea } from "@/components/ui/textarea";
import { generateCheckPDF } from "./lib/checkPdf";

type Item = { description: string; quantity: number; rate: number; account_id?: string | null };

function deriveStatus(b: any): string {
  if (b.status === "void" || b.status === "draft") return b.status;
  const paid = Number(b.paid_amount ?? 0);
  const total = Number(b.total ?? 0);
  if (paid >= total && total > 0) return "paid";
  if (paid > 0 && paid < total) return "partially_paid";
  if (b.due_date && new Date(b.due_date) < new Date() && paid < total) return "overdue";
  return "open";
}

const ACCEPT = "application/pdf,image/jpeg,image/jpg,image/png,image/heic,image/heif";
const HL = "bg-sky-50 border-sky-300";
const ERR = "bg-red-50 border-red-400";

export default function AccountingBillsPage() {
  const { companyId, loading: companyLoading, error: companyError, associationId, associationName } = useCompanyId();
  const cid = companyId ?? "";
  const cur = "USD";
  const qc = useQueryClient();
  const parseFn = parseBill;
  const [open, setOpen] = useState(false);
  const [editId, setEditId] = useState<string | null>(null);
  const [search, setSearch] = useState("");
  const [statusFilter, setStatusFilter] = useState<string>("all");
  const [autoOnly, setAutoOnly] = useState(false);

  const [vendorId, setVendorId] = useState("");
  const [number, setNumber] = useState("");
  const [issueDate, setIssueDate] = useState(new Date().toLocaleDateString("en-CA", { timeZone: "America/New_York" }));
  const [dueDate, setDueDate] = useState("");
  const [taxPct, setTaxPct] = useState(0);
  const [notes, setNotes] = useState("");
  const [items, setItems] = useState<Item[]>([{ description: "", quantity: 1, rate: 0, account_id: null }]);

  // Upload + parse state
  const [file, setFile] = useState<File | null>(null);
  const [filePreview, setFilePreview] = useState<string | null>(null);
  const [uploadedUrl, setUploadedUrl] = useState<string | null>(null);
  const [parsing, setParsing] = useState(false);
  const [aiFilled, setAiFilled] = useState<Set<string>>(new Set()); // field keys that were AI-filled
  const [missingFields, setMissingFields] = useState<Set<string>>(new Set());
  const [dragOver, setDragOver] = useState(false);
  const [pageDragOver, setPageDragOver] = useState(false);
  const fileRef = useRef<HTMLInputElement>(null);

  const { data: bills = [], isLoading } = useQuery({
    queryKey: ["bills", cid],
    enabled: !!cid,
    queryFn: async () => (await accounting.from("bills").select("*, vendors(name,address)").eq("company_id", cid).order("issue_date", { ascending: false })).data ?? [],
  });
  // Single vendor roster = public.vendors, scoped to this company's association.
  // The chosen public vendor is mapped to its accounting.vendors row on save.
  const { data: vendors = [] } = useQuery({
    queryKey: ["vendors-lookup", associationId],
    enabled: !!associationId,
    queryFn: async () => (await supabase.from("vendors")
      .select("id,name")
      .eq("is_active", true)
      .or(`association_id.eq.${associationId},association_ids.cs.{${associationId}}`)
      .order("name")).data ?? [],
  });
  const { data: expenseAccounts = [] } = useQuery({
    queryKey: ["expense-accounts", cid],
    enabled: !!cid,
    queryFn: async () =>
      (await accounting.from("accounts").select("id,name,code,type").eq("company_id", cid).eq("type", "expense").eq("is_archived", false).order("code")).data ?? [],
  });

  const enriched = useMemo(
    () => (bills as any[]).map((b: any) => ({ ...b, derivedStatus: deriveStatus(b), balance: Math.max(0, Number(b.total) - Number(b.paid_amount ?? 0)) })),
    [bills]
  );
  const filtered = enriched.filter((b: any) => {
    if (statusFilter !== "all" && b.derivedStatus !== statusFilter) return false;
    if (autoOnly && !b.auto_created) return false;
    if (search && !`${b.number} ${b.vendors?.name ?? ""}`.toLowerCase().includes(search.toLowerCase())) return false;
    return true;
  });

  const aging = useMemo(() => {
    const buckets = { current: 0, "0-30": 0, "31-60": 0, "60+": 0 };
    const today = new Date();
    enriched.forEach((b: any) => {
      if (b.derivedStatus === "paid" || b.derivedStatus === "void" || b.derivedStatus === "draft") return;
      if (!b.due_date) { buckets.current += b.balance; return; }
      const days = Math.floor((today.getTime() - new Date(b.due_date).getTime()) / 86400000);
      if (days < 0) buckets.current += b.balance;
      else if (days <= 30) buckets["0-30"] += b.balance;
      else if (days <= 60) buckets["31-60"] += b.balance;
      else buckets["60+"] += b.balance;
    });
    return buckets;
  }, [enriched]);

  const totalDue = aging.current + aging["0-30"] + aging["31-60"] + aging["60+"];

  const subtotal = useMemo(() => items.reduce((s, i) => s + Number(i.quantity) * Number(i.rate), 0), [items]);
  const tax = +(subtotal * (taxPct / 100)).toFixed(2);
  const total = +(subtotal + tax).toFixed(2);

  const resetForm = () => {
    setEditId(null);
    setVendorId(""); setNumber(""); setDueDate(""); setTaxPct(0); setNotes("");
    setItems([{ description: "", quantity: 1, rate: 0, account_id: null }]);
    setFile(null); setFilePreview(null); setUploadedUrl(null);
    setAiFilled(new Set()); setMissingFields(new Set());
  };

  const openEdit = async (b: any) => {
    setEditId(b.id);
    // The dropdown is keyed by public vendor id; map the stored accounting
    // vendor back to its source public vendor when one exists.
    let pubVendorId = "";
    if (b.vendor_id) {
      const { data: av } = await accounting.from("vendors").select("external_source, external_id").eq("id", b.vendor_id).maybeSingle();
      if (av?.external_source === "acmacc_vendor" && av?.external_id) pubVendorId = String(av.external_id);
    }
    setVendorId(pubVendorId);
    setNumber(b.number ?? "");
    setIssueDate(b.issue_date ?? issueDate);
    setDueDate(b.due_date ?? "");
    const sub = Number(b.subtotal || 0);
    setTaxPct(sub > 0 ? +((Number(b.tax || 0) / sub) * 100).toFixed(4) : 0);
    setNotes(b.notes ?? "");
    setUploadedUrl(b.attachment_url ?? null);
    setFile(null); setFilePreview(null);
    setAiFilled(new Set()); setMissingFields(new Set());
    const { data: its } = await accounting.from("bill_items").select("*").eq("bill_id", b.id);
    setItems(its && its.length
      ? its.map((i: any) => ({ description: i.description ?? "", quantity: Number(i.quantity ?? 1), rate: Number(i.rate ?? 0), account_id: i.account_id ?? null }))
      : [{ description: "", quantity: 1, rate: 0, account_id: null }]);
    setOpen(true);
  };

  const handleFile = (f: File) => {
    setFile(f);
    setUploadedUrl(null);
    if (f.type.startsWith("image/")) {
      setFilePreview(URL.createObjectURL(f));
    } else {
      setFilePreview(null);
    }
    // Open dialog immediately and auto-parse
    setOpen(true);
    runParse(f);
  };

  const uploadFileObj = async (f: File): Promise<string | null> => {
    if (!cid) return null;
    const ext = f.name.split(".").pop() || "bin";
    const path = `${cid}/${Date.now()}-${Math.random().toString(36).slice(2, 8)}.${ext}`;
    const { error } = await supabase.storage.from("bill-attachments").upload(path, f, {
      contentType: f.type || "application/octet-stream",
      upsert: false,
    });
    if (error) { toast.error(`Upload failed: ${error.message}`); return null; }
    const { data } = supabase.storage.from("bill-attachments").getPublicUrl(path);
    setUploadedUrl(data.publicUrl);
    return data.publicUrl;
  };

  const matchAccount = (desc: string): string | null => {
    if (!desc || (expenseAccounts as any[]).length === 0) return null;
    const d = desc.toLowerCase();
    let best: { id: string; score: number } | null = null;
    for (const a of expenseAccounts as any[]) {
      const name = String(a.name).toLowerCase();
      let score = 0;
      if (d.includes(name) || name.includes(d)) score = 100;
      else {
        const words = name.split(/\s+/).filter((w) => w.length > 3);
        score = words.reduce((s, w) => (d.includes(w) ? s + 10 : s), 0);
      }
      if (score > 0 && (!best || score > best.score)) best = { id: a.id, score };
    }
    return best?.id ?? null;
  };

  const runParse = async (f: File) => {
    setParsing(true);
    try {
      const url = await uploadFileObj(f);
      if (!url) { setParsing(false); return; }

      const parsed = await parseFn({ data: { fileUrl: url, mimeType: f.type || "application/octet-stream" } });

      const filled = new Set<string>();
      const missing = new Set<string>();

      if (parsed.bill_number) { setNumber(String(parsed.bill_number)); filled.add("number"); } else missing.add("number");
      if (parsed.bill_date) { setIssueDate(String(parsed.bill_date).slice(0, 10)); filled.add("issueDate"); }
      if (parsed.due_date) { setDueDate(String(parsed.due_date).slice(0, 10)); filled.add("dueDate"); }
      if (parsed.notes) { setNotes(String(parsed.notes)); filled.add("notes"); }

      // Vendor match
      if (parsed.vendor_name) {
        const vn = String(parsed.vendor_name).toLowerCase();
        const v = (vendors as any[]).find((x) => String(x.name).toLowerCase() === vn || String(x.name).toLowerCase().includes(vn) || vn.includes(String(x.name).toLowerCase()));
        if (v) { setVendorId(v.id); filled.add("vendor"); }
        else filled.add("vendor_unmatched");
      }

      // Line items
      if (Array.isArray(parsed.line_items) && parsed.line_items.length > 0) {
        const newItems: Item[] = parsed.line_items.map((li: any) => {
          const desc = String(li.description ?? "");
          const qty = Number(li.quantity ?? 1) || 1;
          const rate = Number(li.unit_price ?? li.amount ?? 0) || 0;
          return { description: desc, quantity: qty, rate, account_id: matchAccount(desc) };
        });
        setItems(newItems);
        filled.add("items");
      }

      // Tax % from subtotal/tax
      const sub = Number(parsed.subtotal ?? 0);
      const taxAmt = Number(parsed.tax_amount ?? 0);
      if (sub > 0 && taxAmt > 0) { setTaxPct(+((taxAmt / sub) * 100).toFixed(2)); filled.add("tax"); }

      setAiFilled(filled);
      setMissingFields(missing);
      toast.success("Bill parsed — please review highlighted fields.");
    } catch (e: any) {
      toast.error(e?.message ?? "Failed to parse bill");
    } finally {
      setParsing(false);
    }
  };

  const save = async () => {
    if (!number.trim()) return toast.error("Bill number required");
    let attachmentUrl = uploadedUrl;
    if (file && !attachmentUrl) attachmentUrl = await uploadFileObj(file);

    // Resolve the chosen public vendor to its accounting.vendors row (find-or-create).
    let acctVendorId: string | null = null;
    if (vendorId) {
      const { data: mapped, error: mapErr } = await supabase.rpc("ensure_accounting_vendor", {
        _association_id: associationId,
        _public_vendor_id: vendorId,
      });
      if (mapErr) return toast.error(mapErr.message);
      acctVendorId = (mapped as string) ?? null;
    }

    const itemRows = (billId: string) => items.map(i => ({
      bill_id: billId, description: i.description, quantity: i.quantity, rate: i.rate,
      amount: +(Number(i.quantity) * Number(i.rate)).toFixed(2),
      account_id: i.account_id || null,
    }));

    if (editId) {
      const { error } = await accounting.from("bills").update({
        vendor_id: acctVendorId, number,
        issue_date: issueDate, due_date: dueDate || null,
        subtotal, tax, total,
        notes: notes || null,
        attachment_url: attachmentUrl,
      }).eq("id", editId);
      if (error) return toast.error(error.message);
      await accounting.from("bill_items").delete().eq("bill_id", editId);
      await accounting.from("bill_items").insert(itemRows(editId));
      toast.success("Bill updated");
    } else {
      const { data: bill, error } = await accounting.from("bills").insert({
        company_id: cid, vendor_id: acctVendorId, number,
        issue_date: issueDate, due_date: dueDate || null,
        subtotal, tax, total, status: "open",
        notes: notes || null,
        attachment_url: attachmentUrl,
      }).select().single();
      if (error || !bill) return toast.error(error?.message ?? "Failed");
      await accounting.from("bill_items").insert(itemRows(bill.id));
      toast.success("Bill recorded");
    }
    setOpen(false);
    resetForm();
    qc.invalidateQueries({ queryKey: ["bills", cid] });
  };

  // Payment dialog state

  const [payBill, setPayBill] = useState<any | null>(null);
  const [payAmount, setPayAmount] = useState(0);
  const [payDate, setPayDate] = useState(new Date().toLocaleDateString("en-CA", { timeZone: "America/New_York" }));
  const [payMethod, setPayMethod] = useState<"check" | "ach" | "wire" | "credit_card" | "cash" | "other">("check");
  const [payAccountId, setPayAccountId] = useState("");
  const [payReference, setPayReference] = useState("");
  const [payMemo, setPayMemo] = useState("");
  const [printCheck, setPrintCheck] = useState(true);
  const [paying, setPaying] = useState(false);

  const { data: bankAccounts = [] } = useQuery({
    queryKey: ["bank-accounts", cid],
    enabled: !!cid,
    queryFn: async () =>
      // Payment accounts: banks plus equity accounts (e.g. reserve components
      // paid directly from equity). Archived accounts never appear.
      (await accounting.from("accounts").select("id,name,code,balance,is_bank,type").eq("company_id", cid).or("is_bank.eq.true,type.eq.equity").eq("is_archived", false).order("code")).data ?? [],
  });

  const openPayment = async (b: any) => {
    setPayBill(b);
    setPayAmount(b.balance);
    setPayDate(new Date().toLocaleDateString("en-CA", { timeZone: "America/New_York" }));
    setPayMethod("check");
    setPayMemo("");
    setPrintCheck(true);
    const def = (bankAccounts as any[])[0]?.id ?? "";
    setPayAccountId(def);
    const { data: cs } = await accounting.from("check_settings").select("next_check_number").eq("company_id", cid).maybeSingle();
    setPayReference(String(cs?.next_check_number ?? 1001));
  };

  const onMethodChange = async (m: typeof payMethod) => {
    setPayMethod(m);
    if (m === "check") {
      setPrintCheck(true);
      const { data: cs } = await accounting.from("check_settings").select("next_check_number").eq("company_id", cid).maybeSingle();
      setPayReference(String(cs?.next_check_number ?? 1001));
    } else {
      setPrintCheck(false);
      setPayReference("");
    }
  };

  const printCheckPDF = async (checkNumber: number, vendorName: string, amount: number, date: string, memo: string, bankAccount: any) => {
    const { data: cs } = await accounting.from("check_settings").select("*").eq("company_id", cid).maybeSingle();
    const { data: billItems } = await accounting.from("bill_items").select("description,amount").eq("bill_id", payBill?.id ?? "");
    const vendorAddress = payBill?.vendors?.address ?? undefined;

    // Return-address (payer) block comes from the company check layout the user
    // configured in Settings → Check Layout. Without this, checks print no
    // return address.
    const { data: layout } = await supabase
      .from("company_check_layouts")
      .select("payer_name, payer_address, show_payer_block")
      .maybeSingle();
    // Fall back to the association's General Settings name/address (accounting.companies).
    const { data: companyInfo } = await accounting.from("companies").select("name, address").eq("id", cid).maybeSingle();
    const payerName = (layout?.payer_name || "").trim() || ((companyInfo as any)?.name ?? "");
    const showPayer = layout?.show_payer_block !== false;
    const returnAddress = showPayer ? (layout?.payer_address || (companyInfo as any)?.address || undefined) : undefined;

    const dataUrl = generateCheckPDF(
      [{
        companyName: (payerName || associationName) ?? "Company",
        companyAddress: returnAddress,
        bankName: cs?.bank_name ?? bankAccount?.name ?? undefined,
        bankAddress: cs?.bank_address ?? undefined,
        routingNumber: cs?.routing_number ?? undefined,
        accountNumber: cs?.account_number ?? undefined,
        fractionalRouting: (cs as any)?.fractional_routing ?? undefined,
        checkNumber,
        date: fmtDate(date),
        payee: vendorName,
        payeeAddress: vendorAddress,
        amount,
        memo: memo || undefined,
        lineItems: (billItems ?? []).map((i: any) => ({ description: i.description, amount: Number(i.amount) })),
        printSignature: cs?.print_signature ?? false,
        signatureDataUrl: cs?.signature_url ?? undefined,
      }],
      {
        style: (cs?.default_style as any) ?? "voucher",
        position: (cs?.default_position as any) ?? "top",
        fontSize: (cs?.font_size as any) ?? "medium",
        offsetX: (cs as any)?.offset_x ?? 0,
        offsetY: (cs as any)?.offset_y ?? 0,
        micrOffsetY: (cs as any)?.micr_offset_y ?? 0,
        micrGap1: (cs as any)?.micr_gap_1 ?? 1,
        micrGap2: (cs as any)?.micr_gap_2 ?? 1,
        fieldPositions: (cs as any)?.field_positions ?? {},
      }
    );
    const w = window.open("");
    if (w) {
      w.document.write(`<iframe src="${dataUrl}" style="border:0;width:100%;height:100vh" onload="this.contentWindow.print()"></iframe>`);
    }
  };

  const savePayment = async () => {
    if (!payBill || !payAccountId) return toast.error("Payment account required");
    if (!payAmount || payAmount <= 0) return toast.error("Invalid amount");
    setPaying(true);
    try {
      const bankAccount = (bankAccounts as any[]).find((a) => a.id === payAccountId);
      const vendorName = payBill.vendors?.name ?? "Vendor";
      const refLabel = payReference || payMethod.toUpperCase();

      // Expense category name(s) from the bill — shown on the payment line in the
      // transaction journal for visibility (display only; does not affect the GL).
      const { data: payItems } = await accounting
        .from("bill_items").select("account_id").eq("bill_id", payBill.id).not("account_id", "is", null);
      const payAcctIds = Array.from(new Set((payItems ?? []).map((i: any) => i.account_id)));
      let categoryLabel = "Bill Payment";
      if (payAcctIds.length) {
        const { data: payAccs } = await accounting.from("accounts").select("name").in("id", payAcctIds);
        const names = (payAccs ?? []).map((a: any) => a.name).filter(Boolean);
        if (names.length) categoryLabel = names.join(", ");
      }

      // 1) Bank ledger transaction — debit = money OUT of the bank (payment to vendor).
      // A bill payment must clear Accounts Payable, NOT re-hit the expense account:
      // the expense was already recognized when the bill was entered (accrual).
      // coa_account_id stays null with vendor_id set, so post_transaction_gl posts
      // Dr Accounts Payable / Cr Bank (clears the payable, no second P&L hit). The
      // expense category is stored in `category` for the journal view only.
      await accounting.from("transactions").insert({
        company_id: cid,
        account_id: payAccountId,
        date: payDate,
        type: "debit",
        amount: payAmount,
        description: `Bill Payment · ${vendorName} · Bill ${payBill.number}`,
        category: categoryLabel,
        reference: refLabel,
        coa_account_id: null,                  // → posts against Accounts Payable (via vendor)
        vendor_id: payBill.vendor_id ?? null,  // required so the GL clears A/P
        bill_id: payBill.id,                   // links the payment to the bill it settles
      });

      // 2) Bank balance auto-updated by DB trigger trg_sync_account_balance

      // 3) Update bill paid amount (partial payments leave the bill partially_paid)
      const newPaid = Number(payBill.paid_amount ?? 0) + Number(payAmount);
      await accounting.from("bills").update({ paid_amount: newPaid, status: newPaid >= Number(payBill.total) - 0.005 ? "paid" : "partially_paid" }).eq("id", payBill.id);

      // 4) If check + print: insert check record, print, mark printed, bump next #
      if (payMethod === "check") {
        const checkNumber = parseInt(payReference, 10) || 1001;
        const { data: chk } = await accounting.from("checks").insert({
          company_id: cid,
          bank_account_id: payAccountId,
          check_number: checkNumber,
          date: payDate,
          payee_vendor_id: payBill.vendor_id,
          payee_name: vendorName,
          amount: payAmount,
          memo: payMemo || `Bill ${payBill.number}`,
          source_bill_id: payBill.id,
          status: printCheck ? "printed" : "unprinted",
          printed_at: printCheck ? new Date().toISOString() : null,
        }).select().single();

        // bump next check number
        const { data: csRow } = await accounting.from("check_settings").select("id,next_check_number").eq("company_id", cid).maybeSingle();
        if (csRow) {
          await accounting.from("check_settings").update({ next_check_number: Math.max(csRow.next_check_number, checkNumber + 1) }).eq("id", csRow.id);
        } else {
          await accounting.from("check_settings").insert({ company_id: cid, next_check_number: checkNumber + 1 });
        }

        if (printCheck && chk) {
          printCheckPDF(checkNumber, vendorName, payAmount, payDate, payMemo, bankAccount);
        }
      }

      toast.success("Payment recorded");
      setPayBill(null);
      qc.invalidateQueries({ queryKey: ["bills", cid] });
      qc.invalidateQueries({ queryKey: ["bank-accounts", cid] });
      qc.invalidateQueries({ queryKey: ["transactions", cid] });
      qc.invalidateQueries({ queryKey: ["accounts", cid] });
    } catch (e: any) {
      toast.error(e?.message ?? "Failed to record payment");
    } finally {
      setPaying(false);
    }
  };

  const remove = async (id: string) => {
    await accounting.from("bills").delete().eq("id", id);
    qc.invalidateQueries({ queryKey: ["bills", cid] });
  };

  const hl = (key: string) => cn(
    aiFilled.has(key) && HL,
    missingFields.has(key) && ERR,
  );

  if (!associationId) return <p className="text-sm text-muted-foreground">Select an association.</p>;
  if (companyLoading) return <div className="flex justify-center py-12"><Loader2 className="h-6 w-6 animate-spin text-muted-foreground" /></div>;
  if (companyError || !companyId) return <p className="text-sm text-muted-foreground text-center py-12">{companyError || "Accounting setup is not ready."}</p>;

  return (
    <div
      className="space-y-6"
      onDragOver={(e) => { e.preventDefault(); setPageDragOver(true); }}
      onDragLeave={(e) => { if (!e.currentTarget.contains(e.relatedTarget as Node)) setPageDragOver(false); }}
      onDrop={(e) => {
        e.preventDefault();
        setPageDragOver(false);
        const f = e.dataTransfer.files?.[0];
        if (f) handleFile(f);
      }}
    >
      {/* Full-page drop overlay */}
      {pageDragOver && (
        <div className="fixed inset-0 z-50 flex items-center justify-center bg-primary/10 pointer-events-none">
          <div className="rounded-2xl border-4 border-dashed border-primary bg-white shadow-2xl px-12 py-10 text-center">
            <Upload className="mx-auto h-14 w-14 text-primary mb-4" />
            <div className="text-xl font-semibold">Drop bill to parse</div>
            <div className="text-sm text-muted-foreground mt-1">PDF, JPG, PNG, HEIC accepted</div>
          </div>
        </div>
      )}
      <div className="flex items-center justify-between">
        <h1 className="text-2xl font-semibold">Bills</h1>
        <Dialog open={open} onOpenChange={(o) => { setOpen(o); if (!o) resetForm(); }}>
          <DialogTrigger asChild><Button><Plus className="mr-1 h-4 w-4" /> New Bill</Button></DialogTrigger>
          <DialogContent className="max-w-4xl max-h-[90vh] overflow-y-auto">
            <DialogHeader><DialogTitle>{editId ? "Edit bill" : "New bill"}</DialogTitle></DialogHeader>

            {aiFilled.size > 0 && (
              <div className="rounded-md border border-amber-300 bg-amber-50 text-amber-900 px-3 py-2 text-sm flex items-start gap-2">
                <AlertCircle className="h-4 w-4 mt-0.5 shrink-0" />
                <span>AI has filled in this bill — please review all fields before saving.</span>
              </div>
            )}

            <div className="grid grid-cols-1 md:grid-cols-3 gap-4">
              {/* Form */}
              <div className="md:col-span-2 space-y-4">
                {/* Upload zone */}
                <div
                  onDragOver={(e) => { e.preventDefault(); setDragOver(true); }}
                  onDragLeave={() => setDragOver(false)}
                  onDrop={(e) => {
                    e.preventDefault(); setDragOver(false);
                    const f = e.dataTransfer.files?.[0]; if (f) handleFile(f);
                  }}
                  className={cn(
                    "rounded-lg border-2 border-dashed p-4 text-center text-sm cursor-pointer transition-colors",
                    dragOver ? "border-primary bg-primary/5" : "border-border hover:bg-muted/50"
                  )}
                  onClick={() => fileRef.current?.click()}
                >
                  <input
                    ref={fileRef} type="file" accept={ACCEPT} className="hidden"
                    onChange={(e) => { const f = e.target.files?.[0]; if (f) handleFile(f); }}
                  />
                  <Upload className="mx-auto h-5 w-5 text-muted-foreground" />
                  <div className="mt-1 font-medium">
                    {parsing ? "Parsing with AI…" : file ? file.name : "Upload & Parse Bill"}
                  </div>
                  <div className="text-xs text-muted-foreground">
                    {parsing ? "Extracting fields from your bill…" : "Drag & drop or click — PDF, JPG, PNG, HEIC · auto-parses on drop"}
                  </div>
                  {file && !parsing && (
                    <div className="mt-2 flex justify-center gap-2" onClick={(e) => e.stopPropagation()}>
                      <Button size="sm" variant="outline" onClick={() => runParse(file)}>
                        <Sparkles className="mr-1 h-3.5 w-3.5" /> Re-parse
                      </Button>
                      <Button size="sm" variant="ghost" onClick={() => { setFile(null); setFilePreview(null); setUploadedUrl(null); setAiFilled(new Set()); setMissingFields(new Set()); }}>
                        <X className="h-3.5 w-3.5" />
                      </Button>
                    </div>
                  )}
                </div>

                <div className="grid grid-cols-2 gap-3">
                  <div>
                    <Label>Vendor</Label>
                    <Select value={vendorId} onValueChange={setVendorId}>
                      <SelectTrigger className={hl("vendor") || (aiFilled.has("vendor_unmatched") ? ERR : "")}>
                        <SelectValue placeholder={aiFilled.has("vendor_unmatched") ? "Could not match — pick vendor" : "Select vendor"} />
                      </SelectTrigger>
                      <SelectContent>{(vendors as any[]).map((v: any) => <SelectItem key={v.id} value={v.id}>{v.name}</SelectItem>)}</SelectContent>
                    </Select>
                  </div>
                  <div>
                    <Label>Bill #{missingFields.has("number") && <span className="text-red-600 text-xs ml-1">Could not detect — enter manually</span>}</Label>
                    <Input maxLength={40} value={number} onChange={(e) => setNumber(e.target.value)} className={hl("number")} />
                  </div>
                  <div>
                    <Label>Issue date</Label>
                    <Input type="date" value={issueDate} onChange={(e) => setIssueDate(e.target.value)} className={hl("issueDate")} />
                  </div>
                  <div>
                    <Label>Due date</Label>
                    <Input type="date" value={dueDate} onChange={(e) => setDueDate(e.target.value)} className={hl("dueDate")} />
                  </div>
                </div>

                <div>
                  <Label>Line items</Label>
                  {items.map((it, idx) => (
                    <div key={idx} className={cn("mt-2 grid grid-cols-12 gap-2 p-2 rounded", aiFilled.has("items") && "bg-sky-50/60")}>
                      <Input className="col-span-5" placeholder="Description" maxLength={200} value={it.description}
                        onChange={(e) => { const a = [...items]; a[idx] = { ...it, description: e.target.value }; setItems(a); }} />
                      <Input className="col-span-1" type="number" min={0} step="0.01" value={it.quantity}
                        onChange={(e) => { const a = [...items]; a[idx] = { ...it, quantity: Number(e.target.value) }; setItems(a); }} />
                      <Input className="col-span-2" type="number" min={0} step="0.01" value={it.rate}
                        onChange={(e) => { const a = [...items]; a[idx] = { ...it, rate: Number(e.target.value) }; setItems(a); }} />
                      <Select value={it.account_id ?? ""} onValueChange={(v) => { const a = [...items]; a[idx] = { ...it, account_id: v || null }; setItems(a); }}>
                        <SelectTrigger className="col-span-3 text-xs"><SelectValue placeholder="Account" /></SelectTrigger>
                        <SelectContent>
                          {(expenseAccounts as any[]).map((a) => (
                            <SelectItem key={a.id} value={a.id}>{a.code ? `${a.code} · ` : ""}{a.name}</SelectItem>
                          ))}
                        </SelectContent>
                      </Select>
                      <Button className="col-span-1" variant="ghost" size="icon" onClick={() => setItems(items.filter((_, i) => i !== idx))}><Trash2 className="h-4 w-4" /></Button>
                    </div>
                  ))}
                  <Button className="mt-2" variant="outline" size="sm" onClick={() => setItems([...items, { description: "", quantity: 1, rate: 0, account_id: null }])}><Plus className="mr-1 h-3 w-3" /> Add item</Button>
                </div>

                <div className="grid grid-cols-3 gap-3">
                  <div>
                    <Label>Tax %</Label>
                    <Input type="number" min={0} value={taxPct} onChange={(e) => setTaxPct(Number(e.target.value))} className={hl("tax")} />
                  </div>
                  <div className="col-span-2 text-right text-sm">
                    <div>Subtotal: <b>{money(subtotal, cur)}</b></div>
                    <div>Tax: <b>{money(tax, cur)}</b></div>
                    <div className="text-lg">Total: <b>{money(total, cur)}</b></div>
                  </div>
                </div>

                {(notes || aiFilled.has("notes")) && (
                  <div>
                    <Label>Notes</Label>
                    <Input value={notes} onChange={(e) => setNotes(e.target.value)} className={hl("notes")} />
                  </div>
                )}
              </div>

              {/* Preview pane */}
              <div className="md:col-span-1">
                <Label>Attachment</Label>
                <div className="mt-1 rounded-md border bg-muted/30 p-2 min-h-[200px] flex items-center justify-center">
                  {filePreview ? (
                    <img src={filePreview} alt="Bill preview" className="max-h-[400px] w-full object-contain rounded" />
                  ) : file ? (
                    <div className="text-center text-sm text-muted-foreground">
                      <FileText className="mx-auto h-8 w-8 mb-2" />
                      <div className="font-medium">{file.name}</div>
                      <div className="text-xs">{(file.size / 1024).toFixed(1)} KB</div>
                    </div>
                  ) : (
                    <div className="text-xs text-muted-foreground text-center">No file uploaded</div>
                  )}
                </div>
              </div>
            </div>

            <DialogFooter><Button onClick={save}>Save bill</Button></DialogFooter>
          </DialogContent>
        </Dialog>
      </div>

      {/* Aging summary */}
      <Card>
        <CardHeader className="pb-3">
          <CardTitle className="text-base">Payables aging · {money(totalDue, cur)} due</CardTitle>
        </CardHeader>
        <CardContent>
          <div className="grid grid-cols-2 gap-4 md:grid-cols-4">
            {[
              { label: "Not yet due", value: aging.current, tone: "bg-slate-50 text-slate-700 border-slate-200" },
              { label: "0 – 30 days", value: aging["0-30"], tone: "bg-amber-50 text-amber-700 border-amber-200" },
              { label: "31 – 60 days", value: aging["31-60"], tone: "bg-orange-50 text-orange-700 border-orange-200" },
              { label: "60+ days", value: aging["60+"], tone: "bg-red-50 text-red-700 border-red-200" },
            ].map((b) => (
              <div key={b.label} className={`rounded-lg border p-4 ${b.tone}`}>
                <div className="text-xs font-medium uppercase tracking-wide opacity-75">{b.label}</div>
                <div className="mt-2 text-2xl font-semibold">{money(b.value, cur)}</div>
              </div>
            ))}
          </div>
        </CardContent>
      </Card>

      <div className="flex flex-wrap items-center gap-3">
        <div className="relative flex-1 min-w-[220px]">
          <Search className="pointer-events-none absolute left-3 top-1/2 h-4 w-4 -translate-y-1/2 text-muted-foreground" />
          <Input className="pl-9" placeholder="Search bill # or vendor" value={search} onChange={(e) => setSearch(e.target.value)} />
        </div>
        <Select value={statusFilter} onValueChange={setStatusFilter}>
          <SelectTrigger className="w-44"><SelectValue /></SelectTrigger>
          <SelectContent>
            <SelectItem value="all">All statuses</SelectItem>
            <SelectItem value="open">Open</SelectItem>
            <SelectItem value="overdue">Overdue</SelectItem>
            <SelectItem value="partially_paid">Partially Paid</SelectItem>
            <SelectItem value="paid">Paid</SelectItem>
          </SelectContent>
        </Select>
        <label className="inline-flex items-center gap-2 text-sm">
          <input type="checkbox" checked={autoOnly} onChange={(e) => setAutoOnly(e.target.checked)} className="rounded border-border" />
          Show Auto-Created Bills
        </label>
      </div>

      <Card>
        <CardHeader><CardTitle>All bills</CardTitle></CardHeader>
        <CardContent>
          <Table>
            <TableHeader>
              <TableRow>
                <TableHead>Bill #</TableHead>
                <TableHead>Vendor</TableHead>
                <TableHead>Issued</TableHead>
                <TableHead>Due</TableHead>
                <TableHead className="text-right">Total</TableHead>
                <TableHead className="text-right">Balance</TableHead>
                <TableHead>Status</TableHead>
                <TableHead></TableHead>
              </TableRow>
            </TableHeader>
            <TableBody>
              {isLoading && <TableSkeleton columns={8} />}
              {!isLoading && filtered.map((b: any) => (
                <TableRow key={b.id}>
                  <TableCell className="font-medium">
                    {b.attachment_url ? (
                      <a href={b.attachment_url} target="_blank" rel="noopener noreferrer" className="inline-flex items-center gap-1 hover:underline">
                        <FileText className="h-3 w-3" /> {b.number}
                      </a>
                    ) : b.number}
                    {b.auto_created && <span className="ml-2 inline-flex items-center rounded bg-amber-100 text-amber-700 px-1.5 py-0.5 text-[10px] font-semibold">Auto</span>}
                  </TableCell>
                  <TableCell>{b.vendors?.name ?? "—"}</TableCell>
                  <TableCell>{fmtDate(b.issue_date)}</TableCell>
                  <TableCell>{fmtDate(b.due_date)}</TableCell>
                  <TableCell className="text-right">{money(b.total, cur)}</TableCell>
                  <TableCell className="text-right">{money(b.balance, cur)}</TableCell>
                  <TableCell><StatusBadge status={b.derivedStatus.replace("_", " ")} /></TableCell>
                  <TableCell className="flex gap-1">
                    {b.derivedStatus !== "paid" && b.derivedStatus !== "void" && (
                      <Button size="sm" variant="outline" onClick={() => openPayment(b)}>Record payment</Button>

                    )}
                    {b.derivedStatus !== "void" && (
                      <Button size="icon" variant="ghost" onClick={() => openEdit(b)} title="Edit bill"><Pencil className="h-4 w-4" /></Button>
                    )}
                    <Button size="icon" variant="ghost" onClick={() => remove(b.id)}><Trash2 className="h-4 w-4" /></Button>
                  </TableCell>
                </TableRow>
              ))}
              {!isLoading && filtered.length === 0 && (
                <TableRow className="hover:bg-transparent"><TableCell colSpan={8} className="p-0">
                  <EmptyState icon={Receipt} title="No bills match" description="Record a bill from a vendor to track what you owe." />
                </TableCell></TableRow>
              )}
            </TableBody>
          </Table>
        </CardContent>
      </Card>

      {/* Payment dialog */}
      <Dialog open={!!payBill} onOpenChange={(o) => !o && setPayBill(null)}>
        <DialogContent className="max-w-lg">
          <DialogHeader>
            <DialogTitle>Record payment {payBill ? `· Bill ${payBill.number}` : ""}</DialogTitle>
          </DialogHeader>
          {payBill && (
            <div className="space-y-3">
              <div className="text-sm text-muted-foreground">
                Vendor: <b>{payBill.vendors?.name ?? "—"}</b> · Balance:{" "}
                <b>{money(payBill.balance, cur)}</b>
              </div>
              <div className="grid grid-cols-2 gap-3">
                <div>
                  <Label>Payment date</Label>
                  <Input type="date" value={payDate} onChange={(e) => setPayDate(e.target.value)} />
                </div>
                <div>
                  <Label>Amount</Label>
                  <Input type="number" min={0} step="0.01" value={payAmount} onChange={(e) => setPayAmount(Number(e.target.value))} />
                </div>
                <div>
                  <Label>Payment method</Label>
                  <Select value={payMethod} onValueChange={(v) => onMethodChange(v as any)}>
                    <SelectTrigger><SelectValue /></SelectTrigger>
                    <SelectContent>
                      <SelectItem value="check">Check</SelectItem>
                      <SelectItem value="ach">ACH</SelectItem>
                      <SelectItem value="wire">Wire</SelectItem>
                      <SelectItem value="credit_card">Credit Card</SelectItem>
                      <SelectItem value="cash">Cash</SelectItem>
                      <SelectItem value="other">Other</SelectItem>
                    </SelectContent>
                  </Select>
                </div>
                <div>
                  <Label>Payment account</Label>
                  <Select value={payAccountId} onValueChange={setPayAccountId}>
                    <SelectTrigger><SelectValue placeholder="Select account" /></SelectTrigger>
                    <SelectContent>
                      {(bankAccounts as any[]).map((a) => (
                        <SelectItem key={a.id} value={a.id}>{a.code ? `${a.code} · ` : ""}{a.name}</SelectItem>
                      ))}
                    </SelectContent>
                  </Select>
                </div>
                <div className="col-span-2">
                  <Label>{payMethod === "check" ? "Check number" : "Reference"}</Label>
                  <Input value={payReference} onChange={(e) => setPayReference(e.target.value)} maxLength={40} />
                </div>
                <div className="col-span-2">
                  <Label>Memo</Label>
                  <Textarea rows={2} value={payMemo} onChange={(e) => setPayMemo(e.target.value)} maxLength={200} />
                </div>
                {payMethod === "check" && (
                  <div className="col-span-2 flex items-center justify-between rounded-md border p-3 bg-muted/30">
                    <div className="flex items-center gap-2">
                      <Printer className="h-4 w-4" />
                      <div>
                        <div className="text-sm font-medium">Print check after saving</div>
                        <div className="text-xs text-muted-foreground">Opens print dialog & marks as printed</div>
                      </div>
                    </div>
                    <Switch checked={printCheck} onCheckedChange={setPrintCheck} />
                  </div>
                )}
              </div>
            </div>
          )}
          <DialogFooter>
            <Button variant="outline" onClick={() => setPayBill(null)}>Cancel</Button>
            <Button onClick={savePayment} disabled={paying}>
              {paying ? "Saving…" : payMethod === "check" && printCheck ? "Save & Print" : "Save payment"}
            </Button>
          </DialogFooter>
        </DialogContent>
      </Dialog>
    </div>
  );
}
