"use client";

import { useEffect, useRef, useState } from "react";
import {
  Dialog,
  DialogContent,
  DialogHeader,
  DialogTitle,
  DialogDescription,
  DialogFooter,
} from "@/app/shadcn_components/ui/dialog";
import { Button } from "@/app/shadcn_components/ui/button";
import {
  Select,
  SelectContent,
  SelectGroup,
  SelectItem,
  SelectLabel,
  SelectTrigger,
  SelectValue,
} from "@/app/shadcn_components/ui/select";
import { CheckCircle2, XCircle, Upload, Loader2, Clock, ChevronDown, ChevronRight, Info } from "lucide-react";
import { uploadFileToS3 } from "@/app/utils/s3";
import type { ClassifiedDeal, DocStatusMap } from "./types";
import { getRequiredDocs } from "@/app/lib/measureDocumentRequirements";
import { parseMeasures } from "@/app/lib/parseMeasures";
import {
  applyBulkDocType,
  selectAllUnclassified,
  getClassifiedCount,
  getUnclassifiedIds,
  uploadedCountForType,
} from "./classifyPhase";

// ── Types ─────────────────────────────────────────────────────────────────

type FileStatus = "queued" | "uploading" | "done" | "error";

type FileEntry = {
  id: string;            // local UUID for React key
  // One of these will be set:
  file?: File;           // for newly picked files
  existingS3Key?: string; // for pre-existing unclassified uploads
  // Display
  displayName: string;
  displaySize?: string;
  // Upload state
  status: FileStatus;
  errorMsg?: string;
  uploadedId?: string;   // DB record ID (set after recording or from existing)
  // Classification
  docType: string;
  measureName: string;
};

type Phase = "loading" | "measure-select" | "upload" | "classify";

type Props = {
  deal: ClassifiedDeal;
  portfolioId: string;
  onClose: () => void;
  docStatusMap?: DocStatusMap;
  approvedMeasures?: string[]; // if non-empty, used instead of proposedMeasures
};

// ── Constants ─────────────────────────────────────────────────────────────

const FILE_TYPE_OPTIONS: { value: string; label: string; group: string; hint?: string }[] = [
  // Photos
  { value: "pre_photo",           label: "Pre-Install Photos",                group: "Photos",             hint: "Required for ALL measures. Capture existing condition before any work begins." },
  { value: "mid_photo",           label: "Mid-Install Photos",                group: "Photos",             hint: "Required for ALL measures. Detailed photos showing all angles and areas during installation. Insufficient pictures will result in non-lodgement." },
  { value: "post_photo",          label: "Post-Install Photos",               group: "Photos",             hint: "Required for ALL measures. Confirm completed installation." },
  { value: "loft_hatch_photo",    label: "Loft Hatch & Draft Excluder Photos",group: "Photos",             hint: "Required for loft insulation. Must show loft hatch insulation, draft excluders, and hook & eye closing. Also include photos of insulation depth with a ruler showing thickness." },
  { value: "dmev_photos",         label: "DMEV Photos (Wetrooms)",            group: "Photos",             hint: "Clear photos of all Decentralised Mechanical Extract Ventilation units installed in wetrooms." },
  { value: "door_undercut_photos",label: "Door Undercut Photos",              group: "Photos",             hint: "Photos of all door undercuts to demonstrate compliant ventilation paths." },
  { value: "trickle_vent_photos", label: "Trickle Vent Photos",              group: "Photos",             hint: "Photos of all trickle vents located in windows." },
  // Pre-installation
  { value: "pre_installation_building_inspection", label: "PIBI / Tech Survey",                    group: "Pre-Installation", hint: "Pre-Installation Building Inspection — required per property and per measure." },
  { value: "point_of_work_risk_assessment",        label: "Point of Work Risk Assessment",          group: "Pre-Installation" },
  // Compliance & lodgement
  { value: "claim_of_compliance",           label: "DOCC 2030 (Claim of Compliance)",      group: "Compliance & Lodgement", hint: "Required per property and per measure for TrustMark lodgement under PAS 2030." },
  { value: "mcs_compliance_certificate",    label: "MCS Compliance Certificate",            group: "Compliance & Lodgement", hint: "Required for Solar PV and Air Source Heat Pump installations." },
  { value: "certificate_of_conformity",     label: "Certificate of Conformity",             group: "Compliance & Lodgement" },
  { value: "minor_works_electrical_certificate", label: "Minor Works Electrical Certificate", group: "Compliance & Lodgement" },
  { value: "trustmark_licence_numbers",     label: "TrustMark Licence Numbers",             group: "Compliance & Lodgement", hint: "All installer and subcontractor TrustMark licence numbers. Ensure all are accredited for the correct measures under PAS 2023." },
  { value: "operative_competency",          label: "Operative Competency",                  group: "Compliance & Lodgement", hint: "PAS 2030 installer accreditation and qualifications of individual workers, suitable for the measure(s) installed. Verify all installers/subcontractors are accredited under PAS 2023." },
  // Ventilation
  { value: "ventilation_assessment_checklist", label: "Ventilation Assessment Checklist", group: "Ventilation" },
  { value: "anemometer_readings",              label: "Anemometer Readings",              group: "Ventilation", hint: "Required for DMEV/ventilation measures to confirm airflow compliance." },
  { value: "commissioning_records",            label: "Commissioning Records",            group: "Ventilation", hint: "Tests, certifications and commissioning records for all systems installed." },
  { value: "part_f_ventilation_document",      label: "Approved Document Part F",         group: "Ventilation", hint: "Ventilation compliance document under Approved Document Part F." },
  // Handover & warranties
  { value: "handover_pack",        label: "Handover Pack",                      group: "Handover & Warranties" },
  { value: "workmanship_warranty", label: "Workmanship Warranty",               group: "Handover & Warranties", hint: "Required per property and per measure for TrustMark lodgement." },
  { value: "insurance_guarantee",  label: "Insurance Backed Guarantee (IBG)",   group: "Handover & Warranties", hint: "Required per property and per measure for TrustMark lodgement." },
  { value: "g98_notification",     label: "G98 / G99 Notification",             group: "Handover & Warranties", hint: "Required for Solar PV and other grid-connected installations." },
  // Qualifications & other
  { value: "installer_qualifications", label: "Installer Qualifications",       group: "Qualifications & Other" },
  { value: "installer_feedback",       label: "Installer Feedback",             group: "Qualifications & Other" },
  { value: "contractor_other",         label: "Other",                          group: "Qualifications & Other" },
];

const FILE_TYPE_GROUPS = [
  "Photos",
  "Pre-Installation",
  "Compliance & Lodgement",
  "Ventilation",
  "Handover & Warranties",
  "Qualifications & Other",
];

// ── PAS 2030/2035 requirements summary (for guidance panel) ───────────────

const PAS_REQUIREMENTS = [
  {
    heading: "Required for every property & measure",
    items: [
      "PIBI / Tech Survey (pre-installation building inspection)",
      "DOCC 2030 — Claim of Compliance (PAS 2030)",
      "Insurance Backed Guarantee (IBG)",
      "Workmanship Warranty",
      "Pre, mid, and post-install photos (all measures)",
    ],
  },
  {
    heading: "Additional for Solar PV & ASHP",
    items: [
      "MCS Compliance Certificate",
      "G98 / G99 Notification",
    ],
  },
  {
    heading: "Loft insulation",
    items: [
      "Loft hatch insulation, draft excluders, and hook & eye closing photos",
      "Photos of insulation depth with a ruler showing thickness",
    ],
  },
  {
    heading: "Ventilation measures",
    items: [
      "Clear DMEV photos in all wetrooms",
      "Anemometer readings",
      "Commissioning records",
      "Approved Document Part F ventilation document",
      "Door undercut photos",
      "Trickle vent photos",
    ],
  },
  {
    heading: "Installer / lodgement pack",
    items: [
      "TrustMark licence numbers for all installers & subcontractors (verify PAS 2023 accreditation)",
      "Operative Competency (PAS 2030 accreditation + individual worker qualifications)",
      "Minor Works Electrical Certificate (where applicable)",
    ],
  },
];

// ── Helpers ───────────────────────────────────────────────────────────────

function formatSize(bytes: number): string {
  if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(0)} KB`;
  return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
}

function contentTypeFor(ext: string): string {
  const e = ext.toLowerCase();
  if (e === "pdf") return "application/pdf";
  if (["jpg", "jpeg"].includes(e)) return "image/jpeg";
  if (e === "png") return "image/png";
  return "application/octet-stream";
}

function s3KeyBasename(key: string): string {
  return key.split("/").pop() ?? key;
}

async function getPresignedUrl(path: string, contentType: string): Promise<string> {
  const res = await fetch("/api/upload/retrofit-energy-assessments", {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify({ path, contentType, expiresInSeconds: 300 }),
  });
  if (!res.ok) throw new Error("Failed to get presigned URL");
  const { url } = await res.json();
  return url;
}

async function recordUpload(payload: {
  s3FileKey: string;
  s3FileBucket: string;
  uprn?: string;
  hubspotDealId?: string;
  landlordPropertyId?: string;
}): Promise<string> {
  const res = await fetch("/api/upload/contractor-install", {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify(payload),
  });
  if (!res.ok) throw new Error("Failed to record upload");
  const { id } = await res.json();
  return id;
}

async function saveClassifications(
  updates: { id: string; fileType: string; measureName?: string }[],
  hubspotDealId?: string,
  proposedMeasures?: string[],
): Promise<void> {
  const res = await fetch("/api/upload/contractor-install", {
    method: "PATCH",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify({ updates, hubspotDealId, proposedMeasures }),
  });
  if (!res.ok) throw new Error("Failed to save classifications");
}

// ── PAS guidance panel ────────────────────────────────────────────────────

function PasGuidancePanel() {
  const [open, setOpen] = useState(false);
  return (
    <div className="rounded-lg border border-blue-100 bg-blue-50/50">
      <button
        type="button"
        onClick={() => setOpen((v) => !v)}
        className="flex w-full items-center gap-2 px-3 py-2.5 text-left"
      >
        <Info className="h-3.5 w-3.5 text-blue-500 shrink-0" />
        <span className="text-xs font-medium text-blue-700 flex-1">
          PAS 2030/2035 document requirements
        </span>
        {open
          ? <ChevronDown className="h-3.5 w-3.5 text-blue-400 shrink-0" />
          : <ChevronRight className="h-3.5 w-3.5 text-blue-400 shrink-0" />
        }
      </button>
      {open && (
        <div className="px-3 pb-3 space-y-3 border-t border-blue-100">
          {PAS_REQUIREMENTS.map((section) => (
            <div key={section.heading}>
              <p className="text-[10px] font-bold uppercase tracking-wide text-blue-600 mt-2.5 mb-1">
                {section.heading}
              </p>
              <ul className="space-y-0.5">
                {section.items.map((item) => (
                  <li key={item} className="flex items-start gap-1.5 text-xs text-blue-800">
                    <span className="mt-1 h-1 w-1 rounded-full bg-blue-400 shrink-0" />
                    {item}
                  </li>
                ))}
              </ul>
            </div>
          ))}
          <p className="text-[10px] text-blue-500 mt-2 italic">
            Insufficient mid-install photos will result in the Retrofit Coordinator sending back for re-submission and non-lodgement.
          </p>
        </div>
      )}
    </div>
  );
}

// ── DocType select ────────────────────────────────────────────────────────

// ── DocType button grid — shown when a measure is selected ───────────────

function DocTypeButtonGrid({
  value,
  onChange,
  requiredDocs,
  uploadedDocs,
}: {
  value: string;
  onChange: (v: string) => void;
  requiredDocs: string[];
  uploadedDocs: string[];
}) {
  const [showOther, setShowOther] = useState(false);
  const requiredSet = new Set(requiredDocs);
  const isOtherSelected = value !== "" && !requiredSet.has(value);

  return (
    <div className="space-y-2">
      {/* Required doc type buttons */}
      <div className="flex flex-wrap gap-1.5">
        {requiredDocs.map((docType) => {
          const option = FILE_TYPE_OPTIONS.find((o) => o.value === docType);
          const label = option?.label ?? docType;
          const uploadedCount = uploadedCountForType(uploadedDocs, docType);
          const isSelected = value === docType;

          return (
            <button
              key={docType}
              type="button"
              onClick={() => { onChange(docType); setShowOther(false); }}
              title={uploadedCount > 0 ? `${label} — ${uploadedCount} already uploaded` : label}
              className={`inline-flex items-center gap-1.5 px-2.5 py-1.5 rounded-lg border text-xs font-medium transition-all duration-100 ${
                isSelected
                  ? "bg-brandblue text-white border-brandblue shadow-sm"
                  : "bg-white text-gray-700 border-gray-200 hover:border-brandblue/50 hover:bg-brandlightblue/10"
              }`}
            >
              {label}
              {uploadedCount > 0 && !isSelected && (
                <span className="inline-flex items-center justify-center h-4 min-w-4 px-1 rounded-full bg-gray-100 text-gray-500 text-[10px] font-semibold leading-none">
                  {uploadedCount}
                </span>
              )}
            </button>
          );
        })}

        {/* Other button */}
        <button
          type="button"
          onClick={() => setShowOther((v) => !v)}
          className={`inline-flex items-center gap-1 px-2.5 py-1.5 rounded-lg border text-xs font-medium transition-all duration-100 ${
            isOtherSelected || showOther
              ? "bg-gray-100 text-gray-700 border-gray-300"
              : "bg-white text-gray-400 border-gray-200 hover:border-gray-400 hover:text-gray-600"
          }`}
        >
          Other {showOther ? "▲" : "▼"}
        </button>
      </div>

      {/* Other: dropdown for non-required types */}
      {(showOther || isOtherSelected) && (
        <Select
          value={isOtherSelected ? value : "__unset__"}
          onValueChange={(v) => { onChange(v === "__unset__" ? "" : v); }}
        >
          <SelectTrigger className="h-8 text-xs w-full">
            <SelectValue placeholder="Select other type…" />
          </SelectTrigger>
          <SelectContent>
            <SelectItem value="__unset__" className="text-xs text-gray-400">Select other type…</SelectItem>
            {FILE_TYPE_GROUPS.map((group) => {
              const items = FILE_TYPE_OPTIONS.filter(
                (o) => o.group === group && !requiredSet.has(o.value),
              );
              if (!items.length) return null;
              return (
                <SelectGroup key={group}>
                  <SelectLabel className="text-[10px] font-semibold uppercase tracking-wide text-gray-400 px-2 py-1">
                    {group}
                  </SelectLabel>
                  {items.map((o) => (
                    <SelectItem key={o.value} value={o.value} className="text-xs">{o.label}</SelectItem>
                  ))}
                </SelectGroup>
              );
            })}
          </SelectContent>
        </Select>
      )}

      {/* Show hint for selected type */}
      {value && (() => {
        const hint = FILE_TYPE_OPTIONS.find((o) => o.value === value)?.hint;
        return hint ? <p className="text-[10px] text-blue-600 leading-snug">{hint}</p> : null;
      })()}
    </div>
  );
}

// ── DocType select — fallback when no measure selected ────────────────────

function DocTypeSelect({
  value,
  onChange,
}: {
  value: string;
  onChange: (v: string) => void;
}) {
  const selected = FILE_TYPE_OPTIONS.find((o) => o.value === value);

  return (
    <div className="space-y-1">
      <Select value={value || "__unset__"} onValueChange={(v) => onChange(v === "__unset__" ? "" : v)}>
        <SelectTrigger className="h-8 text-xs w-full">
          <SelectValue placeholder="Select type…" />
        </SelectTrigger>
        <SelectContent>
          <SelectItem value="__unset__" className="text-xs text-gray-400">Select type…</SelectItem>
          {FILE_TYPE_GROUPS.map((group) => {
            const items = FILE_TYPE_OPTIONS.filter((o) => o.group === group);
            if (!items.length) return null;
            return (
              <SelectGroup key={group}>
                <SelectLabel className="text-[10px] font-semibold uppercase tracking-wide text-gray-400 px-2 py-1">
                  {group}
                </SelectLabel>
                {items.map((o) => (
                  <SelectItem key={o.value} value={o.value} className="text-xs">{o.label}</SelectItem>
                ))}
              </SelectGroup>
            );
          })}
        </SelectContent>
      </Select>
      {selected?.hint && (
        <p className="text-[10px] text-blue-600 leading-snug px-0.5">{selected.hint}</p>
      )}
    </div>
  );
}

// ── Status icon ────────────────────────────────────────────────────────────

function StatusIcon({ status, isExisting, errorMsg }: { status: FileStatus; isExisting?: boolean; errorMsg?: string }) {
  if (isExisting) return <Clock className="h-4 w-4 text-amber-400 shrink-0" aria-label="Pending classification" />;
  if (status === "queued") return <span className="h-4 w-4 rounded-full border-2 border-gray-200 shrink-0 inline-block" />;
  if (status === "uploading") return <Loader2 className="h-4 w-4 animate-spin text-brandblue shrink-0" />;
  if (status === "done") return <CheckCircle2 className="h-4 w-4 text-emerald-500 shrink-0" />;
  return <span title={errorMsg}><XCircle className="h-4 w-4 text-red-400 shrink-0" /></span>;
}

// ── Main component ─────────────────────────────────────────────────────────

export default function ContractorUploadModal({ deal, portfolioId, onClose, docStatusMap, approvedMeasures }: Props) {
  // Use approved measures when available; fall back to all proposed measures
  const measures = (approvedMeasures && approvedMeasures.length > 0)
    ? approvedMeasures
    : parseMeasures(deal.proposedMeasures);
  const fileInputRef = useRef<HTMLInputElement>(null);
  const [isDragOver, setIsDragOver] = useState(false);
  const [queue, setQueue] = useState<FileEntry[]>([]);
  const [phase, setPhase] = useState<Phase>("loading");
  const [isUploading, setIsUploading] = useState(false);
  const [isSaving, setIsSaving] = useState(false);
  const [saveError, setSaveError] = useState<string | null>(null);
  // The measure selected in the measure-select phase (empty = "not measure-specific")
  const [selectedMeasure, setSelectedMeasure] = useState<string>("");
  // Bulk classify state
  const [selectedIds, setSelectedIds] = useState<Set<string>>(new Set());
  const [bulkDocType, setBulkDocType] = useState("");

  // ── Fetch existing unclassified files on mount ───────────────────────

  useEffect(() => {
    async function fetchExisting() {
      const uprnParam = deal.uprn;
      const propIdParam = deal.landlordPropertyId;
      if (!uprnParam && !propIdParam) {
        setPhase(measures.length > 0 ? "measure-select" : "upload");
        return;
      }

      try {
        const param = uprnParam
          ? `uprn=${encodeURIComponent(uprnParam)}`
          : `landlordPropertyId=${encodeURIComponent(propIdParam!)}`;
        const res = await fetch(`/api/live-tracking/property-documents?${param}`);
        if (!res.ok) throw new Error("fetch failed");
        const docs: { id: string; s3FileKey: string; docType: string | null; source: string | null }[] = await res.json();

        const unclassified = docs.filter(
          (d) => d.source === "contractor" && (d.docType === null || d.docType === "unknown"),
        );

        if (unclassified.length > 0) {
          const entries: FileEntry[] = unclassified.map((d) => ({
            id: crypto.randomUUID(),
            existingS3Key: d.s3FileKey,
            displayName: s3KeyBasename(d.s3FileKey),
            status: "done",
            uploadedId: d.id,
            docType: "",
            measureName: measures[0] ?? "",
          }));
          setQueue(entries);
          setPhase("classify");
        } else if (measures.length > 0) {
          setPhase("measure-select");
        } else {
          setPhase("upload");
        }
      } catch {
        // If fetch fails, just proceed to measure-select (or upload if no measures)
        setPhase(measures.length > 0 ? "measure-select" : "upload");
      }
    }

    fetchExisting();
  // eslint-disable-next-line react-hooks/exhaustive-deps
  }, []);

  // ── File selection ───────────────────────────────────────────────────

  function addFiles(files: FileList | File[]) {
    const newEntries: FileEntry[] = Array.from(files).map((f) => ({
      id: crypto.randomUUID(),
      file: f,
      displayName: f.name,
      displaySize: formatSize(f.size),
      status: "queued",
      docType: "",
      measureName: selectedMeasure,
    }));
    setQueue((prev) => [...prev, ...newEntries]);
  }

  function handleInputChange(e: React.ChangeEvent<HTMLInputElement>) {
    if (e.target.files?.length) addFiles(e.target.files);
    e.target.value = "";
  }

  function handleDrop(e: React.DragEvent) {
    e.preventDefault();
    setIsDragOver(false);
    if (e.dataTransfer.files?.length) addFiles(e.dataTransfer.files);
  }

  function removeFile(id: string) {
    setQueue((prev) => prev.filter((f) => f.id !== id));
  }

  // ── Phase 1: Upload new files ────────────────────────────────────────

  async function handleUpload() {
    const toUpload = queue.filter((f) => f.status === "queued");
    if (toUpload.length === 0) {
      // No new files to upload — go straight to classify for existing
      setPhase("classify");
      return;
    }
    if (isUploading) return;
    setIsUploading(true);

    setQueue((prev) =>
      prev.map((f) => f.status === "queued" ? { ...f, status: "uploading" } : f),
    );

    const uploadResults = await Promise.allSettled(
      toUpload.map(async (entry) => {
        const ext = (entry.file!.name.split(".").pop() ?? "bin").toLowerCase();
        const ct = contentTypeFor(ext);
        const timestamp = Date.now();
        const s3Key = `contractor-install/${deal.dealId}/unclassified/${timestamp}_${entry.id.slice(0, 8)}.${ext}`;

        const presignedUrl = await getPresignedUrl(s3Key, ct);
        await uploadFileToS3({ presignedUrl, file: entry.file!, contentType: ct });

        const urlObj = new URL(presignedUrl);
        const bucket = urlObj.hostname.split(".")[0];

        const uploadedId = await recordUpload({
          s3FileKey: s3Key,
          s3FileBucket: bucket,
          uprn: deal.uprn ?? undefined,
          hubspotDealId: deal.dealId,
          landlordPropertyId: deal.landlordPropertyId ?? undefined,
        });

        return { id: entry.id, uploadedId };
      }),
    );

    const resultMap = new Map(
      uploadResults.map((r, i) => [
        toUpload[i].id,
        r.status === "fulfilled" ? { ok: true, uploadedId: r.value.uploadedId } : { ok: false },
      ]),
    );

    setQueue((prev) =>
      prev.map((f) => {
        const r = resultMap.get(f.id);
        if (!r) return f;
        if (r.ok) return { ...f, status: "done", uploadedId: r.uploadedId };
        return { ...f, status: "error", errorMsg: "Upload failed" };
      }),
    );

    setIsUploading(false);
    setPhase("classify");
  }

  // ── Phase 2: Classify ────────────────────────────────────────────────

  function updateEntryField(id: string, field: "docType" | "measureName", value: string) {
    setQueue((prev) => prev.map((f) => (f.id === id ? { ...f, [field]: value } : f)));
  }

  const classifiableEntries = queue.filter((f) => f.status === "done" && f.uploadedId);
  const classifiedCount = getClassifiedCount(classifiableEntries);
  const unclassifiedCount = getUnclassifiedIds(classifiableEntries).length;

  function handleBulkApply() {
    if (!bulkDocType) return;
    setQueue((prev) => applyBulkDocType(prev, selectedIds, bulkDocType));
    setSelectedIds(new Set());
    setBulkDocType("");
  }

  function toggleSelectId(id: string) {
    setSelectedIds((prev) => {
      const next = new Set(prev);
      if (next.has(id)) next.delete(id); else next.add(id);
      return next;
    });
  }

  function handleSelectAllUnclassified() {
    setSelectedIds(selectAllUnclassified(classifiableEntries));
  }

  async function handleSaveClassifications() {
    setSaveError(null);
    setIsSaving(true);
    try {
      const toSave = classifiableEntries.filter((f) => f.docType !== "");
      await saveClassifications(
        toSave.map((f) => ({
          id: f.uploadedId!,
          fileType: f.docType,
          measureName: (f.measureName && f.measureName !== "__none__") ? f.measureName : undefined,
        })),
        deal.dealId,
        measures.length > 0 ? measures : undefined,
      );
      onClose();
    } catch {
      setSaveError("Failed to save classifications. Please try again.");
    } finally {
      setIsSaving(false);
    }
  }

  // ── Computed ─────────────────────────────────────────────────────────

  const newQueuedCount = queue.filter((f) => f.status === "queued").length;
  const existingCount = queue.filter((f) => f.existingS3Key && f.status === "done").length;
  const propertyLabel = deal.dealname ?? deal.landlordPropertyId ?? deal.dealId;

  // ── Render ───────────────────────────────────────────────────────────

  return (
    <Dialog open onOpenChange={onClose}>
      <DialogContent className="sm:max-w-4xl max-h-[92vh] flex flex-col overflow-hidden">
        <DialogHeader>
          <DialogTitle>
            {phase === "loading" ? "Loading…" :
             phase === "measure-select" ? "Select Measure" :
             phase === "upload" ? "Upload Documents" :
             "Classify Documents"}
          </DialogTitle>
          <DialogDescription>
            {phase === "loading" && "Checking for pending files…"}
            {phase === "measure-select" && (
              <>
                Which measure are you uploading documents for?{" "}
                <strong>{propertyLabel}</strong>
              </>
            )}
            {phase === "upload" && (
              <>
                {selectedMeasure
                  ? <>Uploading documents for <strong>{selectedMeasure}</strong> — <strong>{propertyLabel}</strong>.</>
                  : <>Upload install documents for <strong>{propertyLabel}</strong>.</>
                }
                {existingCount > 0 && ` ${existingCount} file${existingCount !== 1 ? "s" : ""} are pending classification.`}
              </>
            )}
            {phase === "classify" && (
              <>
                {classifiableEntries.length} file{classifiableEntries.length !== 1 ? "s" : ""} ready to classify.
                Select a document type for each, then save.
              </>
            )}
          </DialogDescription>
        </DialogHeader>

        <div className="flex-1 overflow-y-auto min-h-0 space-y-4 py-2">

          {/* ── Loading ── */}
          {phase === "loading" && (
            <div className="flex items-center justify-center py-8">
              <Loader2 className="h-5 w-5 animate-spin text-gray-400" />
            </div>
          )}

          {/* ── Measure Select ── */}
          {phase === "measure-select" && (() => {
            const docStatus = docStatusMap?.[deal.dealId];
            const measureProgressMap = new Map(
              (docStatus?.measureProgress ?? []).map((m) => [m.measureName, m]),
            );
            return (
              <div className="space-y-3">
                <p className="text-xs text-gray-500">
                  Select the measure you are uploading documents for. This helps track completion against required documents.
                </p>
                <div className="grid grid-cols-2 gap-2 sm:grid-cols-3">
                  {measures.map((measure) => {
                    const progress = measureProgressMap.get(measure);
                    const isComplete = progress?.isComplete ?? false;
                    const uploaded = progress?.uploadedCount ?? 0;
                    const required = progress?.requiredCount ?? getRequiredDocs(measure).length;
                    return (
                      <button
                        key={measure}
                        type="button"
                        onClick={() => { setSelectedMeasure(measure); setPhase("upload"); }}
                        className={`flex flex-col items-start gap-1 rounded-lg border px-3 py-2.5 text-left transition-colors hover:border-brandblue/40 hover:bg-brandlightblue/10 ${
                          isComplete
                            ? "border-emerald-200 bg-emerald-50/60"
                            : uploaded > 0
                              ? "border-amber-200 bg-amber-50/50"
                              : "border-gray-200 bg-white"
                        }`}
                      >
                        <span className="text-xs font-semibold text-gray-800 leading-tight">{measure}</span>
                        <span className={`text-[10px] font-medium ${isComplete ? "text-emerald-600" : uploaded > 0 ? "text-amber-600" : "text-gray-400"}`}>
                          {isComplete ? "✓ Complete" : `${uploaded} / ${required} docs`}
                        </span>
                      </button>
                    );
                  })}
                </div>
                <button
                  type="button"
                  onClick={() => { setSelectedMeasure(""); setPhase("upload"); }}
                  className="text-xs text-gray-400 hover:text-gray-600 underline underline-offset-2 mt-1"
                >
                  Not measure-specific / other
                </button>
              </div>
            );
          })()}

          {/* ── Phase 1: Upload ── */}
          {phase === "upload" && (
            <>
              {/* PAS guidance */}
              <PasGuidancePanel />

              {/* Existing unclassified banner */}
              {existingCount > 0 && (
                <div className="flex items-center gap-2 px-3 py-2.5 rounded-lg bg-amber-50 border border-amber-200 text-xs">
                  <Clock className="h-4 w-4 text-amber-500 shrink-0" />
                  <span className="text-amber-700">
                    <strong>{existingCount}</strong> previously uploaded file{existingCount !== 1 ? "s" : ""} {existingCount !== 1 ? "are" : "is"} waiting to be classified.
                    Add new files or go straight to classification.
                  </span>
                </div>
              )}

              {/* Drop zone */}
              <div
                className={`border-2 border-dashed rounded-xl p-8 text-center cursor-pointer transition-colors ${
                  isDragOver ? "border-brandblue bg-brandlightblue/20" : "border-gray-200 hover:border-brandblue/40 hover:bg-gray-50"
                }`}
                onClick={() => fileInputRef.current?.click()}
                onDragOver={(e) => { e.preventDefault(); setIsDragOver(true); }}
                onDragLeave={() => setIsDragOver(false)}
                onDrop={handleDrop}
              >
                <Upload className="h-6 w-6 text-gray-400 mx-auto mb-2" />
                <p className="text-sm font-medium text-gray-600">Drop files here or click to browse</p>
                <p className="text-xs text-gray-400 mt-1">PDF, JPG, PNG accepted · Multiple files OK</p>
                <input
                  ref={fileInputRef}
                  type="file"
                  multiple
                  accept=".pdf,.jpg,.jpeg,.png"
                  className="hidden"
                  onChange={handleInputChange}
                />
              </div>

              {/* New file queue */}
              {newQueuedCount > 0 && (
                <div className="space-y-1">
                  {queue.filter((f) => f.file).map((entry) => (
                    <div key={entry.id} className="flex items-center gap-3 px-3 py-2 rounded-lg bg-gray-50 border border-gray-100">
                      <div className="flex-1 min-w-0">
                        <p className="text-xs font-medium text-gray-700 truncate">{entry.displayName}</p>
                        {entry.displaySize && <p className="text-[10px] text-gray-400">{entry.displaySize}</p>}
                      </div>
                      <StatusIcon status={entry.status} />
                      {entry.status === "queued" && (
                        <button
                          onClick={() => removeFile(entry.id)}
                          className="text-gray-300 hover:text-gray-500 text-lg leading-none shrink-0"
                          aria-label="Remove"
                        >
                          ×
                        </button>
                      )}
                    </div>
                  ))}
                </div>
              )}
            </>
          )}

          {/* ── Phase 2: Classify ── */}
          {phase === "classify" && (() => {
            const docStatus = docStatusMap?.[deal.dealId];
            const measureProgressMap = new Map(
              (docStatus?.measureProgress ?? []).map((m) => [m.measureName, m]),
            );
            return (
            <div className="space-y-4">
              {/* PAS guidance */}
              <PasGuidancePanel />

              {/* Measure context banner */}
              {selectedMeasure && (
                <div className="flex items-center justify-between gap-2 px-3 py-2 rounded-lg bg-brandlightblue/20 border border-brandblue/20">
                  <div className="flex items-center gap-2">
                    <span className="text-xs font-semibold text-brandblue">{selectedMeasure}</span>
                    {(() => {
                      const mp = measureProgressMap.get(selectedMeasure);
                      if (!mp) return null;
                      return (
                        <span className={`text-[10px] font-medium px-1.5 py-0.5 rounded-full border ${
                          mp.isComplete ? "bg-emerald-50 text-emerald-700 border-emerald-200" : "bg-amber-50 text-amber-700 border-amber-200"
                        }`}>
                          {mp.uploadedCount}/{mp.requiredCount} docs uploaded
                        </span>
                      );
                    })()}
                  </div>
                  <button
                    type="button"
                    onClick={() => setPhase("measure-select")}
                    className="text-[10px] text-brandblue/60 hover:text-brandblue underline underline-offset-2"
                  >
                    Change
                  </button>
                </div>
              )}

              {/* Select-all row */}
              {classifiableEntries.length > 0 && (
                <div className="flex items-center gap-2 px-1">
                  <input
                    type="checkbox"
                    id="select-all-unclassified"
                    className="h-4 w-4 rounded border-gray-300 text-brandblue accent-brandblue cursor-pointer"
                    checked={selectedIds.size > 0 && selectedIds.size === selectAllUnclassified(classifiableEntries).size}
                    onChange={() => {
                      const allUnclassified = selectAllUnclassified(classifiableEntries);
                      setSelectedIds(allUnclassified.size === selectedIds.size ? new Set() : allUnclassified);
                    }}
                  />
                  <label htmlFor="select-all-unclassified" className="text-xs text-gray-500 cursor-pointer select-none">
                    Select all unclassified ({unclassifiedCount})
                  </label>
                </div>
              )}

              {/* File list with classification */}
              <div className="space-y-3">
                {classifiableEntries.map((entry) => {
                  const entryMeasure = entry.measureName && entry.measureName !== "__none__" ? entry.measureName : null;
                  const requiredDocs = entryMeasure ? getRequiredDocs(entryMeasure) : null;
                  const uploadedDocs = entryMeasure ? (measureProgressMap.get(entryMeasure)?.uploaded ?? []) : [];
                  const isChecked = selectedIds.has(entry.id);

                  return (
                    <div key={entry.id} className={`rounded-lg border bg-gray-50/50 p-3 space-y-2.5 transition-colors ${isChecked ? "border-brandblue/30 bg-brandlightblue/5" : "border-gray-100"}`}>
                      {/* File info row */}
                      <div className="flex items-center gap-2 min-w-0">
                        <input
                          type="checkbox"
                          className="h-4 w-4 rounded border-gray-300 text-brandblue accent-brandblue cursor-pointer shrink-0"
                          checked={isChecked}
                          onChange={() => toggleSelectId(entry.id)}
                        />
                        <StatusIcon status={entry.status} isExisting={!!entry.existingS3Key} />
                        <div className="min-w-0 flex-1">
                          <p className="text-xs font-medium text-gray-700 truncate">{entry.displayName}</p>
                          <p className="text-[10px] text-gray-400">
                            {entry.existingS3Key ? "Previously uploaded · " : ""}
                            {entry.displaySize ?? ""}
                            {entryMeasure && !selectedMeasure && (
                              <span className="ml-1 text-brandblue/70">{entryMeasure}</span>
                            )}
                          </p>
                        </div>
                        {/* Measure selector — only shown if no pre-selected measure */}
                        {!selectedMeasure && measures.length > 0 && (
                          <Select
                            value={entry.measureName || "__none__"}
                            onValueChange={(v) => updateEntryField(entry.id, "measureName", v === "__none__" ? "" : v)}
                          >
                            <SelectTrigger className="h-7 text-[10px] w-36 shrink-0">
                              <SelectValue placeholder="Measure…" />
                            </SelectTrigger>
                            <SelectContent>
                              <SelectItem value="__none__" className="text-xs text-gray-400">— None —</SelectItem>
                              {measures.map((m) => (
                                <SelectItem key={m} value={m} className="text-xs">{m}</SelectItem>
                              ))}
                            </SelectContent>
                          </Select>
                        )}
                      </div>

                      {/* Doc type selector */}
                      <div>
                        <p className="text-[10px] font-semibold text-gray-400 uppercase tracking-wide mb-1.5">
                          Document type <span className="text-red-400">*</span>
                        </p>
                        {requiredDocs ? (
                          <DocTypeButtonGrid
                            value={entry.docType}
                            onChange={(v) => updateEntryField(entry.id, "docType", v)}
                            requiredDocs={requiredDocs}
                            uploadedDocs={uploadedDocs}
                          />
                        ) : (
                          <DocTypeSelect
                            value={entry.docType}
                            onChange={(v) => updateEntryField(entry.id, "docType", v)}
                          />
                        )}
                      </div>
                    </div>
                  );
                })}
              </div>

              {/* Failed uploads (info only) */}
              {queue.filter((f) => f.status === "error").length > 0 && (
                <div className="p-3 rounded-lg bg-red-50 border border-red-200">
                  <p className="text-xs font-medium text-red-700 mb-1">
                    {queue.filter((f) => f.status === "error").length} file(s) failed and are excluded:
                  </p>
                  {queue.filter((f) => f.status === "error").map((f) => (
                    <p key={f.id} className="text-xs text-red-600">{f.displayName}</p>
                  ))}
                </div>
              )}

              {saveError && (
                <p className="text-sm text-red-600 bg-red-50 border border-red-200 rounded-md px-3 py-2">
                  {saveError}
                </p>
              )}
            </div>
            );
          })()}
        </div>

        {/* ── Bulk classify toolbar — shown when files are selected ── */}
        {phase === "classify" && selectedIds.size > 0 && (
          <div className="shrink-0 flex items-center gap-2 px-4 py-2.5 bg-brandlightblue/10 border-t border-brandblue/20">
            <span className="text-xs font-semibold text-brandblue whitespace-nowrap">
              {selectedIds.size} selected
            </span>
            <span className="text-xs text-gray-400">—</span>
            <span className="text-xs text-gray-600 whitespace-nowrap">Classify as:</span>
            <Select value={bulkDocType} onValueChange={setBulkDocType}>
              <SelectTrigger className="h-7 text-xs flex-1 max-w-56">
                <SelectValue placeholder="Choose type…" />
              </SelectTrigger>
              <SelectContent>
                {FILE_TYPE_GROUPS.map((group) => {
                  const items = FILE_TYPE_OPTIONS.filter((o) => o.group === group);
                  if (!items.length) return null;
                  return (
                    <SelectGroup key={group}>
                      <SelectLabel className="text-[10px] font-semibold uppercase tracking-wide text-gray-400 px-2 py-1">{group}</SelectLabel>
                      {items.map((o) => (
                        <SelectItem key={o.value} value={o.value} className="text-xs">{o.label}</SelectItem>
                      ))}
                    </SelectGroup>
                  );
                })}
              </SelectContent>
            </Select>
            <Button
              size="sm"
              className="h-7 text-xs bg-brandblue text-white shrink-0"
              disabled={!bulkDocType}
              onClick={handleBulkApply}
            >
              Apply
            </Button>
            <button
              type="button"
              className="text-xs text-gray-400 hover:text-gray-600 shrink-0 ml-1"
              onClick={() => setSelectedIds(new Set())}
            >
              Clear
            </button>
          </div>
        )}

        <DialogFooter className="pt-2 border-t border-gray-100 shrink-0">
          {phase === "loading" && (
            <Button variant="secondary" onClick={onClose}>Cancel</Button>
          )}

          {phase === "measure-select" && (
            <Button variant="secondary" onClick={onClose}>Cancel</Button>
          )}

          {phase === "upload" && (
            <>
              <Button variant="secondary" onClick={onClose} disabled={isUploading}>Cancel</Button>
              <Button
                onClick={handleUpload}
                disabled={isUploading || (newQueuedCount === 0 && existingCount === 0)}
                className="bg-brandblue text-white gap-1.5"
              >
                {isUploading ? (
                  <><Loader2 className="h-3.5 w-3.5 animate-spin" /> Uploading…</>
                ) : newQueuedCount > 0 ? (
                  <>Upload {newQueuedCount} file{newQueuedCount !== 1 ? "s" : ""} →</>
                ) : (
                  <>Classify {existingCount} pending file{existingCount !== 1 ? "s" : ""} →</>
                )}
              </Button>
            </>
          )}

          {phase === "classify" && (
            <>
              <Button variant="secondary" onClick={onClose} disabled={isSaving}>
                Skip for now
              </Button>
              <div className="flex flex-col items-end gap-0.5">
                <Button
                  onClick={handleSaveClassifications}
                  disabled={classifiedCount === 0 || isSaving}
                  className="bg-brandblue text-white gap-1.5"
                >
                  {isSaving ? (
                    <><Loader2 className="h-3.5 w-3.5 animate-spin" /> Saving…</>
                  ) : (
                    `Save ${classifiedCount} classified →`
                  )}
                </Button>
                {unclassifiedCount > 0 && (
                  <p className="text-[10px] text-gray-400">
                    {unclassifiedCount} unclassified — will stay in your queue
                  </p>
                )}
              </div>
            </>
          )}
        </DialogFooter>
      </DialogContent>
    </Dialog>
  );
}
