import { useState, useEffect, useRef, useCallback } from "react";
import { useQuery, useMutation } from "@tanstack/react-query";
import { addDays, addWeeks, startOfToday, addMinutes } from "date-fns";
import {
  parseLocalDate,
  formatLocalDate,
  formatLocalTime,
  formatDateToHumanReadable,
} from "@/utils/dateUtils";
import { AddAppointmentModal } from "@/components/appointments/add-appointment-modal";
import type { NewAppointmentPrefill } from "@/components/appointments/appointment-form";
import { Button } from "@/components/ui/button";
import {
  Calendar as CalendarIcon,
  Plus,
  ChevronLeft,
  ChevronRight,
  ChevronsLeft,
  ChevronsRight,
  Move,
  Trash2,
  CreditCard,
  ClipboardList,
  StickyNote,
  Shield,
  FileCheck,
  LoaderCircleIcon,
  Stethoscope,
  Download,
  MessageSquare,
  Clock,
  ExternalLink,
  Bot,
  UserCheck,
} from "lucide-react";
import { useToast } from "@/hooks/use-toast";
import { Calendar } from "@/components/ui/calendar";
import { apiRequest, queryClient } from "@/lib/queryClient";
import { useAuth } from "@/hooks/use-auth";
import { DndProvider, useDrag, useDrop } from "react-dnd";
import { HTML5Backend } from "react-dnd-html5-backend";
import { Menu, Item, useContextMenu } from "react-contexify";
import "react-contexify/ReactContexify.css";
import { useLocation } from "wouter";
import { DeleteConfirmationDialog } from "@/components/ui/deleteDialog";
import {
  Appointment,
  InsertAppointment,
  Patient,
  PatientStatus,
  UpdateAppointment,
  UpdatePatient,
} from "@repo/db/types";
import { ClaimForm } from "@/components/claims/claim-form";
import {
  Popover,
  PopoverContent,
  PopoverTrigger,
} from "@/components/ui/popover";
import { Label } from "@/components/ui/label";
import { Switch } from "@/components/ui/switch";
import {
  Select,
  SelectContent,
  SelectItem,
  SelectTrigger,
  SelectValue,
} from "@/components/ui/select";
import { useAppDispatch, useAppSelector } from "@/redux/hooks";
import {
  clearTaskStatus,
  setTaskStatus,
} from "@/redux/slices/seleniumTaskSlice";
import { SeleniumTaskBanner } from "@/components/ui/selenium-task-banner";
import { PatientStatusBadge } from "@/components/appointments/patient-status-badge";
import type { OfficeHoursData } from "@/components/settings/office-hours-card";
import { MessageThread } from "@/components/patient-connection/message-thread";
import { getAppointmentTypeLabel } from "@/utils/appointmentTypeUtils";

// Define types for scheduling
interface TimeSlot {
  time: string;
  displayTime: string;
}

interface Staff {
  id: string;
  name: string;
  role: "doctor" | "hygienist";
  color: string;
}

interface ScheduledAppointment {
  id?: number;
  patientId: number;
  patientName: string;
  eligibilityStatus: PatientStatus;
  patientStatus: PatientStatus;
  patientInsuranceProvider: string | null;
  hasProcedures: boolean;
  hasClaimWithNumber: boolean;
  movedByAi?: boolean;
  staffId: number;
  date: string | Date;
  startTime: string | Date;
  endTime: string | Date;
  status: string | null;
  type: string;
  notes?: string | null;
  procedureCodes?: string[];
}

function appointmentCardColor(apt: ScheduledAppointment): string {
  if (apt.hasClaimWithNumber) return "bg-gray-500 text-white";
  if (apt.hasProcedures) return "bg-blue-500 text-white";
  return "bg-slate-100 text-gray-700 border-slate-300";
}

function resolveAppointmentBadgeStatus(apt: ScheduledAppointment): PatientStatus {
  if (apt.eligibilityStatus === "ACTIVE") return "ACTIVE";
  if (apt.eligibilityStatus === "INACTIVE") return "INACTIVE";
  if (apt.eligibilityStatus === "UNKNOWN") {
    if (apt.patientStatus === "ACTIVE") return "ACTIVE";
    if (apt.patientStatus === "INACTIVE") return "INACTIVE";
  }
  return apt.eligibilityStatus ?? "UNKNOWN";
}

// Define a unique ID for the appointment context menu
const APPOINTMENT_CONTEXT_MENU_ID = "appointment-context-menu";

// 🔑 exported base key
export const QK_APPOINTMENTS_BASE = ["appointments", "day"] as const;
// helper (optional) – mirrors the query key structure
export const qkAppointmentsDay = (date: string) =>
  [...QK_APPOINTMENTS_BASE, date] as const;

export default function AppointmentsPage() {
  const { toast } = useToast();
  const { user } = useAuth();
  const [isAddModalOpen, setIsAddModalOpen] = useState(false);
  const [calendarOpen, setCalendarOpen] = useState(false);
  const [editingAppointment, setEditingAppointment] = useState<
    Appointment | undefined
  >(undefined);
  const [selectedDate, setSelectedDate] = useState<Date>(startOfToday());
  const [location] = useLocation();
  const [confirmDeleteState, setConfirmDeleteState] = useState<{
    open: boolean;
    appointmentId?: number;
  }>({ open: false });
  const dispatch = useAppDispatch();
  const batchTask = useAppSelector(
    (state) => state.seleniumTasks.eligibilityBatchCheck
  );
  const claimBatchTask = useAppSelector(
    (state) => state.seleniumTasks.claimBatchCheck
  );
  const [isCheckingAllElig, setIsCheckingAllElig] = useState(false);
  const [processedAppointmentIds, setProcessedAppointmentIds] = useState<
    Record<number, boolean>
  >({});
  const [selectedStaffColumns, setSelectedStaffColumns] = useState<Set<number>>(new Set());

  const toggleStaffColumn = (staffId: number) => {
    setSelectedStaffColumns((prev) => {
      const next = new Set(prev);
      if (next.has(staffId)) next.delete(staffId);
      else next.add(staffId);
      return next;
    });
  };

  const [selectedClaimColumns, setSelectedClaimColumns] = useState<Set<number>>(new Set());
  const [isClaimingColumn, setIsClaimingColumn] = useState(false);
  const [selectedClaimAiColumns, setSelectedClaimAiColumns] = useState<Set<number>>(new Set());
  const [isClaimingAiColumn, setIsClaimingAiColumn] = useState(false);
  const [autoClaimEnabled, setAutoClaimEnabled] = useState(false);
  const [autoClaimTime, setAutoClaimTime] = useState("19:00");
  const autoClaimFiredHourRef = useRef<number | null>(null);
  const [aiClaimModalOpen, setAiClaimModalOpen] = useState(false);
  const [aiClaimQueue, setAiClaimQueue] = useState<ScheduledAppointment[]>([]);
  const [aiClaimCurrentIndex, setAiClaimCurrentIndex] = useState(0);
  const [isAiClaimProcessing, setIsAiClaimProcessing] = useState(false);
  const [aiClaimCurrentData, setAiClaimCurrentData] = useState<{
    matchedCodes: Array<{ code: string; description: string; toothNumber?: string; toothSurface?: string }>;
    siteKey: string;
    serviceDate: string;
    appointmentId: number;
    patientName: string;
    notes: string;
    reply: string;
  } | null>(null);
  const [needsAiClaimResume, setNeedsAiClaimResume] = useState(false);
  const [aiClaimCdtClarification, setAiClaimCdtClarification] = useState<{
    unknownPhrases: string[];
    codeInputs: Record<string, string>;
    originalMessage: string;
    aptDate: string;
    appointmentId: number;
    patientName: string;
    notes: string;
    matchedSoFar: Array<{ code: string; description: string }>;
  } | null>(null);
  const [selectedReminderColumns, setSelectedReminderColumns] = useState<Set<number>>(new Set());
  const [isSendingReminders, setIsSendingReminders] = useState(false);
  const [reminderAiFollowUp, setReminderAiFollowUp] = useState(true);
  const [selectedRescheduleColumns, setSelectedRescheduleColumns] = useState<Set<number>>(new Set());
  const [isSendingReschedule, setIsSendingReschedule] = useState(false);
  const [rescheduleAiFollowUp, setRescheduleAiFollowUp] = useState(true);
  const [selectedDownloadPdfColumns, setSelectedDownloadPdfColumns] = useState<Set<number>>(new Set());
  const [isDownloadingClaimPdfs, setIsDownloadingClaimPdfs] = useState(false);
  const [columnLabels, setColumnLabels] = useState<Record<string, string>>({});
  const [editingLabelStaffId, setEditingLabelStaffId] = useState<string | null>(null);
  const [pendingOverride, setPendingOverride] = useState<{
    type: "move" | "create";
    appointmentId?: number;
    timeSlot: TimeSlot;
    staffId: number;
  } | null>(null);
  const [newApptPrefill, setNewApptPrefill] = useState<NewAppointmentPrefill | null>(null);

  const toggleReminderColumn = (staffId: number) => {
    setSelectedReminderColumns((prev) => {
      const next = new Set(prev);
      if (next.has(staffId)) next.delete(staffId);
      else next.add(staffId);
      return next;
    });
  };

  const toggleClaimColumn = (staffId: number) => {
    setSelectedClaimColumns((prev) => {
      const next = new Set(prev);
      if (next.has(staffId)) next.delete(staffId);
      else next.add(staffId);
      return next;
    });
  };

  const toggleClaimAiColumn = (staffId: number) => {
    setSelectedClaimAiColumns((prev) => {
      const next = new Set(prev);
      if (next.has(staffId)) next.delete(staffId);
      else next.add(staffId);
      return next;
    });
  };

  const toggleRescheduleColumn = (staffId: number) => {
    setSelectedRescheduleColumns((prev) => {
      const next = new Set(prev);
      if (next.has(staffId)) next.delete(staffId);
      else next.add(staffId);
      return next;
    });
  };

  const toggleDownloadPdfColumn = (staffId: number) => {
    setSelectedDownloadPdfColumns((prev) => {
      const next = new Set(prev);
      if (next.has(staffId)) next.delete(staffId);
      else next.add(staffId);
      return next;
    });
  };

  const [, setLocation] = useLocation();

  // Select Procedures modal state (opened inline, no navigation)
  const [isSelectProceduresOpen, setIsSelectProceduresOpen] = useState(false);
  const [selectProceduresPatientId, setSelectProceduresPatientId] = useState<number | null>(null);
  const [selectProceduresAppointmentId, setSelectProceduresAppointmentId] = useState<number | null>(null);

  // Chat popup state
  const [chatPatient, setChatPatient] = useState<Patient | null>(null);
  const [chatAppointmentInfo, setChatAppointmentInfo] = useState<{ date: string; startTime: string } | undefined>(undefined);

  // Create context menu hook
  const { show } = useContextMenu({
    id: APPOINTMENT_CONTEXT_MENU_ID,
  });

  // ----------------------
  // Day-level fetch: appointments + patients for selectedDate (lightweight)
  // ----------------------
  const formattedSelectedDate = formatLocalDate(selectedDate);
  type DayPayload = { appointments: Appointment[]; patients: Patient[] };
  const {
    data: dayPayload = {
      appointments: [] as Appointment[],
      patients: [] as Patient[],
    },
    isLoading: isLoadingAppointments,
    refetch: refetchAppointments,
  } = useQuery<
    DayPayload,
    Error,
    DayPayload,
    readonly [string, string, string]
  >({
    queryKey: qkAppointmentsDay(formattedSelectedDate),
    queryFn: async () => {
      const res = await apiRequest(
        "GET",
        `/api/appointments/day?date=${formattedSelectedDate}`
      );
      if (!res.ok) {
        throw new Error("Failed to load appointments for date");
      }
      return res.json();
    },
    enabled: !!user && !!formattedSelectedDate,
    // placeholderData: keepPreviousData,
  });

  const appointments = dayPayload.appointments ?? [];
  const patientsFromDay = dayPayload.patients ?? [];

  // Office hours (used to enforce scheduling rules)
  const { data: officeHours } = useQuery<OfficeHoursData | null>({
    queryKey: ["/api/office-hours"],
    queryFn: async () => {
      const res = await apiRequest("GET", "/api/office-hours");
      if (!res.ok) return null;
      return res.json();
    },
    enabled: !!user,
  });

  // Staff memebers
  const { data: staffMembersRaw = [] as Staff[] } = useQuery<Staff[]>({
    queryKey: ["/api/staffs/"],
    queryFn: async () => {
      const res = await apiRequest("GET", "/api/staffs/");
      return res.json();
    },
    enabled: !!user,
  });

  const colors = [
    "bg-sky-500", // light blue
    "bg-teal-500", // teal
    "bg-indigo-500", // soft indigo
    "bg-rose-400", // muted rose
    "bg-amber-400", // muted amber
    "bg-orange-500", // softer warm orange
  ];

  // Assign colors cycling through the list
  const staffMembers = staffMembersRaw.map((staff, index) => ({
    ...staff,

    color: colors[index % colors.length] || "bg-gray-400",
  }));

  // Initialize column labels from localStorage; default A, B, C… for new staff
  useEffect(() => {
    if (!staffMembersRaw.length) return;
    const stored = JSON.parse(
      localStorage.getItem("scheduleColumnLabels") || "{}"
    ) as Record<string, string>;
    let changed = false;
    staffMembersRaw.forEach((staff, index) => {
      if (!(String(staff.id) in stored)) {
        stored[String(staff.id)] = String.fromCharCode(65 + index);
        changed = true;
      }
    });
    if (changed) localStorage.setItem("scheduleColumnLabels", JSON.stringify(stored));
    setColumnLabels(stored);
  }, [staffMembersRaw]);

  const handleLabelSave = (staffId: string, value: string) => {
    const trimmed = value.trim() || String.fromCharCode(65 + staffMembersRaw.findIndex((s) => String(s.id) === staffId));
    setColumnLabels((prev) => {
      const updated = { ...prev, [staffId]: trimmed };
      localStorage.setItem("scheduleColumnLabels", JSON.stringify(updated));
      return updated;
    });
    setEditingLabelStaffId(null);
  };

  // Returns true when a time slot is within the configured office hours for that staff group
  const isWithinOfficeHours = (timeStr: string, staffIndex: number): boolean => {
    if (!officeHours) return true; // no config = unrestricted
    // If today is an override date, all slots are open
    const todayYMD = selectedDate.toLocaleDateString("en-CA");
    if (officeHours.overrideDates?.includes(todayYMD)) return true;
    const dayNames = ["sunday", "monday", "tuesday", "wednesday", "thursday", "friday", "saturday"] as const;
    const dayName = dayNames[selectedDate.getDay()] as keyof typeof officeHours.doctors;
    const group = staffIndex <= 2 ? officeHours.doctors : officeHours.hygienists;
    const dayHours = group[dayName];
    if (!dayHours || !dayHours.enabled) return false;
    return (
      (timeStr >= dayHours.amStart && timeStr < dayHours.amEnd) ||
      (timeStr >= dayHours.pmStart && timeStr < dayHours.pmEnd)
    );
  };

  // Generate time slots from 8:00 AM to 9:00 PM in 15-minute increments
  const timeSlots: TimeSlot[] = [];
  for (let hour = 8; hour <= 21; hour++) {
    for (let minute = 0; minute < 60; minute += 15) {
      const pad = (n: number) => n.toString().padStart(2, "0");
      const timeStr = `${pad(hour)}:${pad(minute)}`;

      // Only allow start times up to 21:00 (9 PM)
      if (timeStr > "21:00") continue;

      const hour12 = hour > 12 ? hour - 12 : hour;
      const period = hour >= 12 ? "PM" : "AM";
      const displayTime = `${hour12}:${pad(minute)} ${period}`;
      timeSlots.push({ time: timeStr, displayTime });
    }
  }

  // Check for newPatient parameter in URL
  useEffect(() => {
    const params = new URLSearchParams(window.location.search);
    const newPatientId = params.get("newPatient");

    if (newPatientId) {
      const patientId = parseInt(newPatientId);
      const firstStaff = staffMembers.length > 0 ? staffMembers[0] : undefined;
      const staffId = firstStaff ? Number(firstStaff.id) : 1;
      const defaultTimeSlot = timeSlots.find((slot) => slot.time === "09:00") || timeSlots[0];
      if (!defaultTimeSlot) {
        toast({ title: "Unable to schedule", description: "No available time slots.", variant: "destructive" });
        return;
      }

      handleCreateAppointmentAtSlot(defaultTimeSlot, staffId, patientId);

      params.delete("newPatient");
      const newSearch = params.toString();
      const newUrl = `${window.location.pathname}${newSearch ? `?${newSearch}` : ""}${window.location.hash || ""}`;
      window.history.replaceState({}, "", newUrl);
    }
  }, [location]);

  // On mount: detect if we should resume an AI claim queue after returning from claims page
  useEffect(() => {
    try {
      const raw = sessionStorage.getItem("ai_claim_queue");
      if (!raw) return;
      const parsed = JSON.parse(raw);
      if (parsed?.pendingResume && Array.isArray(parsed.appointments) && parsed.appointments.length > 0) {
        setNeedsAiClaimResume(true);
      }
    } catch {
      sessionStorage.removeItem("ai_claim_queue");
    }
  }, []);

  // When appointments finish loading and a resume is pending, reopen the modal
  useEffect(() => {
    if (!needsAiClaimResume || isLoadingAppointments) return;
    setNeedsAiClaimResume(false);
    try {
      const raw = sessionStorage.getItem("ai_claim_queue");
      if (!raw) return;
      const parsed = JSON.parse(raw);
      const queue = parsed.appointments as ScheduledAppointment[];
      if (!queue?.length) { sessionStorage.removeItem("ai_claim_queue"); return; }
      sessionStorage.setItem("ai_claim_queue", JSON.stringify({ ...parsed, pendingResume: false }));
      setAiClaimQueue(queue);
      setAiClaimCurrentIndex(0);
      setAiClaimModalOpen(true);
      processAiClaimAtIndex(queue, 0);
    } catch {
      sessionStorage.removeItem("ai_claim_queue");
    }
  }, [needsAiClaimResume, isLoadingAppointments]);

  // Auto-claim: fire handleClaimForColumnWithAi at the configured hour each day
  useEffect(() => {
    if (!autoClaimEnabled || !autoClaimTime) return;
    const [targetHour] = autoClaimTime.split(":").map(Number);
    const interval = setInterval(() => {
      const now = new Date();
      const currentHour = now.getHours();
      const currentMinute = now.getMinutes();
      if (currentHour === targetHour && currentMinute === 0) {
        if (autoClaimFiredHourRef.current !== currentHour) {
          autoClaimFiredHourRef.current = currentHour;
          handleClaimForColumnWithAi();
        }
      } else if (autoClaimFiredHourRef.current === currentHour && currentMinute !== 0) {
        autoClaimFiredHourRef.current = null;
      }
    }, 30_000);
    return () => clearInterval(interval);
  }, [autoClaimEnabled, autoClaimTime, selectedClaimAiColumns]);

  // Create/upsert appointment mutation
  const createAppointmentMutation = useMutation({
    mutationFn: async (appointment: InsertAppointment) => {
      const res = await apiRequest(
        "POST",
        "/api/appointments/upsert",
        appointment
      );
      const body = await res.json();
      if (!res.ok) throw new Error(body?.message || "Failed to create appointment");
      return body;
    },
    onSuccess: (appointment) => {
      toast({
        title: "Appointment Scheduled",
        description: appointment.message || "Appointment created successfully.",
      });
      // Invalidate both appointments and patients queries
      queryClient.invalidateQueries({
        queryKey: qkAppointmentsDay(formattedSelectedDate),
      });
      setIsAddModalOpen(false);
    },
    onError: (error: Error) => {
      toast({
        title: "Error",
        description: `Failed to create appointment: ${error.message}`,
        variant: "destructive",
      });
    },
  });

  // Update appointment mutation
  const updateAppointmentMutation = useMutation({
    mutationFn: async ({
      id,
      appointment,
    }: {
      id: number;
      appointment: UpdateAppointment;
    }) => {
      const res = await apiRequest(
        "PUT",
        `/api/appointments/${id}`,
        appointment
      );
      return await res.json();
    },
    onSuccess: (appointment) => {
      toast({
        title: "Appointment Scheduled",
        description: appointment.message || "Appointment updated successfully.",
      });
      queryClient.invalidateQueries({
        queryKey: qkAppointmentsDay(formattedSelectedDate),
      });
      setEditingAppointment(undefined);
      setIsAddModalOpen(false);
    },
    onError: (error: Error) => {
      toast({
        title: "Error",
        description: `Failed to update appointment: ${error.message}`,
        variant: "destructive",
      });
    },
  });

  // Delete appointment mutation
  const deleteAppointmentMutation = useMutation({
    mutationFn: async (id: number) => {
      await apiRequest("DELETE", `/api/appointments/${id}`);
    },
    onSuccess: () => {
      toast({
        title: "Success",
        description: "Appointment deleted successfully.",
      });
      // Invalidate both appointments and patients queries
      queryClient.invalidateQueries({
        queryKey: qkAppointmentsDay(formattedSelectedDate),
      });
      setConfirmDeleteState({ open: false });
    },
    onError: (error: Error) => {
      toast({
        title: "Error",
        description: `Failed to delete appointment: ${error.message}`,
        variant: "destructive",
      });
    },
  });

  // Handle appointment submission (create or update)
  const handleAppointmentSubmit = (
    appointmentData: InsertAppointment | UpdateAppointment
  ) => {
    // Converts local date to exact UTC date with no offset issues
    const rawDate = parseLocalDate(appointmentData.date);

    const updatedData = {
      ...appointmentData,
      date: formatLocalDate(rawDate),
    };

    // Check if we're editing an existing appointment with a valid ID
    if (
      editingAppointment &&
      "id" in editingAppointment &&
      typeof editingAppointment.id === "number"
    ) {
      updateAppointmentMutation.mutate({
        id: editingAppointment.id,
        appointment: updatedData as unknown as UpdateAppointment,
      });
    } else {
      // This is a new appointment
      if (user) {
        createAppointmentMutation.mutate({
          ...(updatedData as unknown as InsertAppointment),
          userId: user.id,
        });
      }
    }
  };

  // Handle edit appointment
  const handleEditAppointment = (appointment: Appointment) => {
    setEditingAppointment(appointment);
    setIsAddModalOpen(true);
  };

  // When user confirms delete in dialog
  const handleConfirmDelete = () => {
    if (!confirmDeleteState.appointmentId) return;
    deleteAppointmentMutation.mutate(confirmDeleteState.appointmentId);
  };

  const handleDeleteAppointment = (id: number) => {
    setConfirmDeleteState({
      open: true,
      appointmentId: id,
    });
  };

  // Process appointments for the scheduler view
  const processedAppointments: ScheduledAppointment[] = (
    appointments ?? []
  ).map((apt) => {
    // Find patient name
    const patient = patientsFromDay.find((p) => p.id === apt.patientId);

    const patientName = patient
      ? `${patient.firstName} ${patient.lastName}`
      : "Unknown Patient";

    const eligibilityStatus = (apt as any).eligibilityStatus as PatientStatus;
    const patientStatus = (patient as any)?.status as PatientStatus ?? "UNKNOWN";
    const patientInsuranceProvider = (patient as any)?.insuranceProvider as string | null ?? null;

    const staffId = Number(apt.staffId ?? 1);

    const normalizedStart =
      typeof apt.startTime === "string"
        ? apt.startTime.substring(0, 5)
        : formatLocalTime(apt.startTime);
    const normalizedEnd =
      typeof apt.endTime === "string"
        ? apt.endTime.substring(0, 5)
        : formatLocalTime(apt.endTime);

    const processed = {
      ...apt,
      patientName,
      eligibilityStatus,
      patientStatus,
      patientInsuranceProvider,
      hasProcedures: !!(apt as any).hasProcedures,
      hasClaimWithNumber: !!(apt as any).hasClaimWithNumber,
      notes: (apt as any).notes ?? null,
      procedureCodes: (apt as any).procedureCodes ?? [],
      movedByAi: !!(apt as any).movedByAi,
      staffId,
      status: apt.status ?? null,
      date: formatLocalDate(apt.date),
      startTime: normalizedStart,
      endTime: normalizedEnd,
    } as ScheduledAppointment;

    return processed;
  });

  // Compute how many 15-min slots an appointment occupies
  const getSlotSpan = (apt: ScheduledAppointment): number => {
    const startStr = (typeof apt.startTime === "string" ? apt.startTime : formatLocalTime(apt.startTime)).substring(0, 5);
    const endStr = (typeof apt.endTime === "string" ? apt.endTime : formatLocalTime(apt.endTime)).substring(0, 5);
    const startParts = startStr.split(":");
    const endParts = endStr.split(":");
    const startH = parseInt(startParts[0] ?? "0", 10);
    const startM = parseInt(startParts[1] ?? "0", 10);
    const endH = parseInt(endParts[0] ?? "0", 10);
    const endM = parseInt(endParts[1] ?? "0", 10);
    const diff = (endH * 60 + endM) - (startH * 60 + startM);
    return Math.max(1, Math.round(diff / 15));
  };

  // Compute display span — same as getSlotSpan but truncated if a later appointment in the
  // same staff column starts within this appointment's time range (overlap case).
  const getDisplaySpan = (apt: ScheduledAppointment): number => {
    const fullSpan = getSlotSpan(apt);
    const startStr = (typeof apt.startTime === "string" ? apt.startTime : formatLocalTime(apt.startTime)).substring(0, 5);
    const [startH, startM] = startStr.split(":").map(Number);
    const startMinutes = (startH ?? 0) * 60 + (startM ?? 0);

    const nextOverlap = (processedAppointments ?? [])
      .filter((other) => {
        if (other.id === apt.id || other.staffId !== apt.staffId) return false;
        const otherStart = (typeof other.startTime === "string" ? other.startTime : formatLocalTime(other.startTime)).substring(0, 5);
        const [oh, om] = otherStart.split(":").map(Number);
        const otherMin = (oh ?? 0) * 60 + (om ?? 0);
        return otherMin > startMinutes && otherMin < startMinutes + fullSpan * 15;
      })
      .sort((a, b) => {
        const aStart = (typeof a.startTime === "string" ? a.startTime : formatLocalTime(a.startTime)).substring(0, 5);
        const bStart = (typeof b.startTime === "string" ? b.startTime : formatLocalTime(b.startTime)).substring(0, 5);
        return aStart.localeCompare(bStart);
      })[0];

    if (!nextOverlap) return fullSpan;

    const nextStart = (typeof nextOverlap.startTime === "string" ? nextOverlap.startTime : formatLocalTime(nextOverlap.startTime)).substring(0, 5);
    const [nh, nm] = nextStart.split(":").map(Number);
    const nextMin = (nh ?? 0) * 60 + (nm ?? 0);
    return Math.max(1, Math.round((nextMin - startMinutes) / 15));
  };

  // Slots that are "continued" rows of a multi-slot appointment (should not render a td)
  const coveredSlots = new Set<string>();
  (processedAppointments ?? []).forEach((apt) => {
    const span = getDisplaySpan(apt);
    if (span <= 1) return;
    const startStr = (typeof apt.startTime === "string" ? apt.startTime : formatLocalTime(apt.startTime)).substring(0, 5);
    const [startH, startM] = startStr.split(":").map(Number);
    for (let i = 1; i < span; i++) {
      const totalMin = (startH ?? 0) * 60 + (startM ?? 0) + i * 15;
      const h = Math.floor(totalMin / 60).toString().padStart(2, "0");
      const m = (totalMin % 60).toString().padStart(2, "0");
      coveredSlots.add(`${h}:${m}-${apt.staffId}`);
    }
  });

  // Check if appointment exists at a specific time slot and staff
  const getAppointmentAtSlot = (timeSlot: TimeSlot, staffId: number) => {
    if (!processedAppointments || processedAppointments.length === 0)
      return undefined;

    // In appointments for a given time slot, we'll just display the first one
    // In a real application, you might want to show multiple or stack them
    const appointmentsAtSlot = processedAppointments.filter((apt) => {
      const dbTime =
        typeof apt.startTime === "string" ? apt.startTime.substring(0, 5) : "";
      const timeMatches = dbTime === timeSlot.time;
      const staffMatches = apt.staffId === staffId;
      return timeMatches && staffMatches;
    });

    return appointmentsAtSlot.length > 0 ? appointmentsAtSlot[0] : undefined;
  };

  const isLoading =
    isLoadingAppointments ||
    createAppointmentMutation.isPending ||
    updateAppointmentMutation.isPending ||
    deleteAppointmentMutation.isPending;

  // Define drag item types
  const ItemTypes = {
    APPOINTMENT: "appointment",
  };

  // Handle creating a new appointment at a specific time slot and for a specific staff member
  const handleCreateAppointmentAtSlot = (
    timeSlot: TimeSlot,
    staffId: number,
    patientId?: number
  ) => {
    const startHour = parseInt(timeSlot.time.split(":")[0] as string);
    const startMinute = parseInt(timeSlot.time.split(":")[1] as string);
    const startDate = parseLocalDate(selectedDate);
    startDate.setHours(startHour, startMinute, 0);

    const endDate = addMinutes(startDate, 30);
    const endTime = `${endDate.getHours().toString().padStart(2, "0")}:${endDate.getMinutes().toString().padStart(2, "0")}`;

    const staff = staffMembers.find((s) => Number(s.id) === Number(staffId));

    setNewApptPrefill({
      staffId: Number(staffId),
      date: formatLocalDate(selectedDate),
      startTime: timeSlot.time,
      endTime,
      type: staff?.role === "doctor" ? "checkup" : "cleaning",
      patientId,
    });
    setEditingAppointment(undefined);
    setIsAddModalOpen(true);
  };

  // Handle moving an appointment to a new time slot and staff
  const handleMoveAppointment = (
    appointmentId: number,
    newTimeSlot: TimeSlot,
    newStaffId: number
  ) => {
    const appointment = appointments.find((a) => a.id === appointmentId);
    if (!appointment) return;

    // Preserve existing duration when moving
    const apt = appointment as any;
    const oldStart = (typeof apt.startTime === "string" ? apt.startTime : formatLocalTime(apt.startTime)).substring(0, 5);
    const oldEnd = (typeof apt.endTime === "string" ? apt.endTime : formatLocalTime(apt.endTime)).substring(0, 5);
    const [osH, osM] = oldStart.split(":").map(Number);
    const [oeH, oeM] = oldEnd.split(":").map(Number);
    const durationMinutes = ((oeH ?? 0) * 60 + (oeM ?? 0)) - ((osH ?? 0) * 60 + (osM ?? 0));
    const effectiveDuration = durationMinutes > 0 ? durationMinutes : 30;

    const startHour = parseInt(newTimeSlot.time.split(":")[0] as string);
    const startMinute = parseInt(newTimeSlot.time.split(":")[1] as string);
    const startDate = parseLocalDate(selectedDate);
    startDate.setHours(startHour, startMinute, 0);

    const endDate = addMinutes(startDate, effectiveDuration);
    const endTime = `${endDate.getHours().toString().padStart(2, "0")}:${endDate.getMinutes().toString().padStart(2, "0")}`;

    // Find staff member
    const staff = staffMembers.find((s) => Number(s.id) === newStaffId);

    const updatedAppointment: UpdateAppointment = {
      patientId: apt.patientId,
      userId: apt.userId,
      staffId: newStaffId,
      title: apt.title,
      date: apt.date,
      type: apt.type,
      status: apt.status ?? undefined,
      startTime: newTimeSlot.time,
      endTime: endTime,
      notes: apt.notes ?? undefined,
    };
    // Call update mutation
    updateAppointmentMutation.mutate({
      id: appointmentId,
      appointment: updatedAppointment,
    });
  };

  // Open chat window for the patient linked to this appointment
  const handleChat = (appointmentId: number) => {
    const apt = appointments.find((a) => a.id === appointmentId);
    if (!apt) return;
    const patient = patientsFromDay.find((p) => p.id === (apt as any).patientId);
    if (!patient) return;
    const processed = processedAppointments.find((a) => a.id === appointmentId);
    setChatPatient(patient as Patient);
    if (processed) {
      setChatAppointmentInfo({
        date: typeof processed.date === "string" ? processed.date : formatLocalDate(processed.date as Date),
        startTime: typeof processed.startTime === "string" ? processed.startTime.substring(0, 5) : "",
      });
    } else {
      setChatAppointmentInfo(undefined);
    }
  };

  // Function to display context menu
  const handleContextMenu = (e: React.MouseEvent, appointmentId: number) => {
    // Prevent the default browser context menu
    e.preventDefault();

    // Show our custom context menu with appointment ID as data
    show({
      event: e,
      props: {
        appointmentId,
      },
    });
  };

  // Create a draggable appointment component
  function DraggableAppointment({
    appointment,
    staff,
  }: {
    appointment: ScheduledAppointment;
    staff: Staff;
  }) {
    const [{ isDragging }, drag] = useDrag(() => ({
      type: ItemTypes.APPOINTMENT,
      item: { id: appointment.id },
      collect: (monitor) => ({
        isDragging: !!monitor.isDragging(),
      }),
    }));

    const resizingRef = useRef(false);
    const startYRef = useRef(0);
    const originalSpanRef = useRef(1);
    const [resizeSpan, setResizeSpan] = useState<number | null>(null);

    const currentSpan = getDisplaySpan(appointment);
    const SLOT_HEIGHT = 57; // h-14 = 56px + 1px border

    const handleResizeStart = useCallback((e: React.MouseEvent) => {
      e.stopPropagation();
      e.preventDefault();
      resizingRef.current = true;
      startYRef.current = e.clientY;
      originalSpanRef.current = currentSpan;

      const onMouseMove = (ev: MouseEvent) => {
        if (!resizingRef.current) return;
        const deltaY = ev.clientY - startYRef.current;
        const slotDelta = Math.round(deltaY / SLOT_HEIGHT);
        const newSpan = Math.max(1, originalSpanRef.current + slotDelta);
        setResizeSpan(newSpan);
      };

      const onMouseUp = (ev: MouseEvent) => {
        if (!resizingRef.current) return;
        resizingRef.current = false;
        document.removeEventListener("mousemove", onMouseMove);
        document.removeEventListener("mouseup", onMouseUp);
        document.body.style.cursor = "";
        document.body.style.userSelect = "";

        const deltaY = ev.clientY - startYRef.current;
        const slotDelta = Math.round(deltaY / SLOT_HEIGHT);
        const newSpan = Math.max(1, originalSpanRef.current + slotDelta);
        setResizeSpan(null);

        if (newSpan !== originalSpanRef.current && appointment.id) {
          const startStr = (typeof appointment.startTime === "string" ? appointment.startTime : formatLocalTime(appointment.startTime)).substring(0, 5);
          const [sH, sM] = startStr.split(":").map(Number);
          const newEndMinutes = ((sH ?? 0) * 60 + (sM ?? 0)) + newSpan * 15;
          const endH = Math.floor(newEndMinutes / 60).toString().padStart(2, "0");
          const endM = (newEndMinutes % 60).toString().padStart(2, "0");
          const newEndTime = `${endH}:${endM}`;

          const fullApt = appointments.find((a) => a.id === appointment.id) as any;
          if (fullApt) {
            updateAppointmentMutation.mutate({
              id: appointment.id,
              appointment: {
                patientId: fullApt.patientId,
                userId: fullApt.userId,
                staffId: fullApt.staffId ?? appointment.staffId,
                title: fullApt.title,
                date: fullApt.date,
                type: fullApt.type,
                status: fullApt.status ?? undefined,
                startTime: startStr,
                endTime: newEndTime,
                notes: fullApt.notes ?? undefined,
              },
            });
          }
        }
      };

      document.body.style.cursor = "ns-resize";
      document.body.style.userSelect = "none";
      document.addEventListener("mousemove", onMouseMove);
      document.addEventListener("mouseup", onMouseUp);
    }, [appointment, currentSpan]);

    const displayHeight = resizeSpan !== null
      ? `${resizeSpan * SLOT_HEIGHT - 2}px`
      : undefined;

    return (
      <div
        ref={drag as unknown as React.RefObject<HTMLDivElement>}
        className={`${appointmentCardColor(appointment)} border shadow-md rounded p-1 text-xs overflow-visible cursor-move relative ${
          isDragging ? "opacity-50" : "opacity-100"
        }`}
        style={{
          fontWeight: 500,
          height: displayHeight ?? "100%",
          ...(resizeSpan !== null ? { zIndex: 20 } : {}),
        }}
        onClick={(e) => {
          if (!isDragging) {
            const fullAppointment = appointments.find(
              (a) => a.id === appointment.id
            );
            if (fullAppointment) {
              e.stopPropagation();
              handleEditAppointment(fullAppointment);
            }
          }
        }}
        onContextMenu={(e) => handleContextMenu(e, appointment.id ?? 0)}
      >
        <PatientStatusBadge
          status={resolveAppointmentBadgeStatus(appointment)}
          className="pointer-events-auto"
          size={30}
        />

        <div className="font-bold truncate flex items-center gap-1">
          <Move className="h-3 w-3" />
          {appointment.patientName}
          {appointment.movedByAi && (
            <span
              className="ml-1 inline-flex items-center gap-0.5 rounded bg-teal-600 px-1 py-0.5 text-[10px] font-semibold text-white leading-none"
              title="Rescheduled by AI — pending human confirmation"
            >
              <Bot className="h-2.5 w-2.5" />
              AI
            </span>
          )}
        </div>
        <div className="truncate text-[11px]">
          {getAppointmentTypeLabel(appointment.type)}
        </div>
        {appointment.procedureCodes && appointment.procedureCodes.length > 0 && (
          <div className="truncate text-[10px] opacity-80">
            {appointment.procedureCodes.join(", ")}
          </div>
        )}
        {appointment.notes && (
          <div className="truncate text-[10px] opacity-90 font-semibold">
            {appointment.notes}
          </div>
        )}
        {/* Resize handle at the bottom */}
        <div
          className="absolute bottom-0 left-0 right-0 h-2 cursor-ns-resize flex items-center justify-center group hover:bg-black/10 rounded-b"
          onMouseDown={handleResizeStart}
          onClick={(e) => e.stopPropagation()}
        >
          <div className="w-8 h-0.5 bg-current opacity-30 group-hover:opacity-70 rounded" />
        </div>
      </div>
    );
  }

  // Create a drop target for appointments
  function DroppableTimeSlot({
    timeSlot,
    staffId,
    staffIndex,
    appointment,
    staff,
    rowSpan = 1,
  }: {
    timeSlot: TimeSlot;
    staffId: number;
    staffIndex: number;
    appointment: ScheduledAppointment | undefined;
    staff: Staff;
    rowSpan?: number;
  }) {
    const blocked = !isWithinOfficeHours(timeSlot.time, staffIndex);

    const [{ isOver, canDrop }, drop] = useDrop(() => ({
      accept: ItemTypes.APPOINTMENT,
      drop: (item: { id: number }) => {
        if (blocked) {
          // Store plain data — never store callbacks in state across re-renders
          setPendingOverride({ type: "move", appointmentId: item.id, timeSlot, staffId });
        } else {
          handleMoveAppointment(item.id, timeSlot, staffId);
        }
      },
      canDrop: () => !appointment,
      collect: (monitor) => ({
        isOver: !!monitor.isOver(),
        canDrop: !!monitor.canDrop(),
      }),
    }));

    const handleClickEmpty = () => {
      if (blocked) {
        setPendingOverride({ type: "create", timeSlot, staffId });
      } else {
        handleCreateAppointmentAtSlot(timeSlot, staffId);
      }
    };

    return (
      <td
        ref={drop as unknown as React.RefObject<HTMLTableCellElement>}
        key={`${timeSlot.time}-${staffId}`}
        className={`px-1 py-1 border relative h-14 ${
          isOver && canDrop ? "bg-green-100" : blocked ? "bg-gray-100" : ""
        }`}
        rowSpan={rowSpan > 1 ? rowSpan : undefined}
        title={blocked ? "Outside office hours — click to override" : undefined}
      >
        {appointment ? (
          <DraggableAppointment appointment={appointment} staff={staff} />
        ) : (
          <button
            className={`w-full h-full rounded flex items-center justify-center ${
              isOver && canDrop
                ? "bg-green-100"
                : blocked
                ? "text-gray-300 hover:bg-gray-200"
                : "text-gray-400 hover:bg-gray-100"
            }`}
            onClick={handleClickEmpty}
          >
            <Plus className="h-4 w-4" />
          </button>
        )}
      </td>
    );
  }

  // -------------------
  // appointment page — update these handlers
  const handleCheckEligibility = (appointmentId: number) => {
    const apt = appointments.find((a) => a.id === appointmentId);
    const patient = apt ? patientsFromDay.find((p) => p.id === apt.patientId) : null;
    const insuranceProvider = (patient as any)?.insuranceProvider as string | null ?? null;
    const p = insuranceProvider?.toLowerCase() ?? "";
    const isMassHealth = p.includes("masshealth");

    let autoCheck = "other-providers";

    if (isMassHealth) {
      const dob = (patient as any)?.dateOfBirth;
      let age: number | null = null;
      if (dob) {
        const dobDate = typeof dob === "string" ? new Date(dob) : dob as Date;
        const today = new Date();
        age = today.getFullYear() - dobDate.getFullYear();
        const m = today.getMonth() - dobDate.getMonth();
        if (m < 0 || (m === 0 && today.getDate() < dobDate.getDate())) age--;
      }
      autoCheck = age !== null && age >= 21 ? "mh-history" : "cmsp";
    } else if (p.includes("delta dental ma")) {
      autoCheck = "ddma";
    } else if (p.includes("delta dental ins")) {
      autoCheck = "delta-ins";
    } else if (p.includes("united healthcare sco") || p.includes("united sco")) {
      autoCheck = "united-sco";
    } else if (p.includes("tufts") || p.includes("dentaquest")) {
      autoCheck = "tufts-sco";
    } else if (p.includes("commonwealth care alliance") || p.includes("cca")) {
      autoCheck = "cca";
    }

    if (autoCheck === "other-providers") {
      setLocation(`/insurance-status?appointmentId=${appointmentId}&scrollTo=other-providers`);
    } else {
      setLocation(`/insurance-status?appointmentId=${appointmentId}&autoCheck=${autoCheck}`);
    }
  };

  const handleClaimsPreAuth = (appointmentId: number) => {
    setLocation(`/claims?appointmentId=${appointmentId}`);
  };

  const handleSelectProcedures = (appointmentId: number) => {
    const appt = appointments.find((a) => a.id === appointmentId);
    if (!appt) return;
    setSelectProceduresAppointmentId(appointmentId);
    setSelectProceduresPatientId(appt.patientId);
    setIsSelectProceduresOpen(true);
  };

  const handlePayments = (appointmentId: number) => {
    setLocation(`/payments?appointmentId=${appointmentId}`);
  };

  const handleChartPlan = (appointmentId: number) => {
    console.log(
      `Viewing chart/treatment plan for appointment: ${appointmentId}`
    );
  };

  const handleClinicNotes = (appointmentId: number) => {
    console.log(`Opening clinic notes for appointment: ${appointmentId}`);
  };

  const handleManualConfirm = async (appointmentId: number) => {
    try {
      await apiRequest("PATCH", `/api/appointments/${appointmentId}/confirm`);
      queryClient.invalidateQueries({ queryKey: qkAppointmentsDay(formattedSelectedDate) });
      toast({ title: "Confirmed", description: "AI-moved label removed. Appointment manually confirmed." });
    } catch (err: any) {
      toast({ title: "Error", description: err?.message ?? "Failed to confirm appointment.", variant: "destructive" });
    }
  };

  const handleCheckAllEligibilities = async () => {
    if (!user) {
      toast({
        title: "Unauthorized",
        description: "Please login to perform this action.",
        variant: "destructive",
      });
      return;
    }

    const dateParam = formattedSelectedDate; // existing variable in your component

    const staffIdsParam = `&staffIds=${Array.from(selectedStaffColumns).join(",")}`;

    // Start: set redux task status (visible globally)
    dispatch(
      setTaskStatus({
        key: "eligibilityBatchCheck",
        status: "pending",
        message: `Checking eligibility for appointments on ${dateParam}...`,
      })
    );

    setIsCheckingAllElig(true);
    setProcessedAppointmentIds({});

    try {
      const res = await apiRequest(
        "POST",
        `/api/insurance-status/appointments/check-all-eligibilities?date=${dateParam}${staffIdsParam}`,
        {}
      );

      // read body for all cases so we can show per-appointment info
      let body: any;
      try {
        body = await res.json();
      } catch (e) {
        body = null;
      }

      if (!res.ok) {
        const errMsg = body?.error ?? `Server error ${res.status}`;
        // global error
        dispatch(
          setTaskStatus({
            key: "eligibilityBatchCheck",
            status: "error",
            message: `Batch eligibility failed: ${errMsg}`,
          })
        );
        toast({
          title: "Batch check failed",
          description: errMsg,
          variant: "destructive",
        });
        return;
      }

      const results: any[] = Array.isArray(body?.results) ? body.results : [];

      // Map appointmentId -> appointment so we can show human friendly toasts
      const appointmentMap = new Map<number, Appointment>();
      for (const a of appointments) {
        if (a && typeof a.id === "number")
          appointmentMap.set(a.id as number, a);
      }

      // Counters for summary
      let successCount = 0;
      let skippedCount = 0;
      let warningCount = 0;

      // Show toast for each skipped appointment (error) and for warnings.
      for (const r of results) {
        const aptId = Number(r.appointmentId);
        const apt = appointmentMap.get(aptId);
        const patientName = apt
          ? patientsFromDay.find((p) => p.id === apt.patientId)
            ? `${patientsFromDay.find((p) => p.id === apt.patientId)!.firstName ?? ""} ${patientsFromDay.find((p) => p.id === apt.patientId)!.lastName ?? ""}`.trim()
            : `patient#${apt.patientId ?? "?"}`
          : `appointment#${aptId}`;

        const aptTime = apt ? `${apt.date ?? ""} ${apt.startTime ?? ""}` : "";

        if (r.error) {
          skippedCount++;
          toast({
            title: `Skipped: ${patientName}`,
            description: `${aptTime} — ${r.error}`,
            variant: "destructive",
          });
          console.warn("[batch skipped]", aptId, r.error);
        } else if (r.warning) {
          warningCount++;
          toast({
            title: `Warning: ${patientName}`,
            description: `${aptTime} — ${r.warning}`,
            variant: "destructive",
          });
          console.info("[batch warning]", aptId, r.warning);
        } else if (r.processed) {
          successCount++;
          // optional: show small non-intrusive toast or nothing for each success.
          // comment-in to notify successes (may create many toasts):
          // toast({ title: `Processed: ${patientName}`, description: `${aptTime}`, variant: "default" });
        } else {
          // fallback: treat as skipped
          skippedCount++;
          toast({
            title: `Skipped: ${patientName}`,
            description: `${aptTime} — Unknown reason`,
            variant: "destructive",
          });
        }
      }

      // Invalidate queries so UI repaints with updated patient statuses
      queryClient.invalidateQueries({
        queryKey: qkAppointmentsDay(formattedSelectedDate),
      });

      // global success status (summary)
      dispatch(
        setTaskStatus({
          key: "eligibilityBatchCheck",
          status: skippedCount > 0 ? "error" : "success",
          message: `Batch processed ${results.length} appointments — success: ${successCount}, warnings: ${warningCount}, skipped: ${skippedCount}.`,
        })
      );

      // also show final toast summary
      toast({
        title: "Batch complete",
        description: `Processed ${results.length} appointments — success: ${successCount}, warnings: ${warningCount}, skipped: ${skippedCount}.`,
        variant: skippedCount > 0 ? "destructive" : "default",
      });
    } catch (err: any) {
      console.error("[check-all-eligibilities] error", err);
      dispatch(
        setTaskStatus({
          key: "eligibilityBatchCheck",
          status: "error",
          message: `Batch eligibility error: ${err?.message ?? String(err)}`,
        })
      );
      toast({
        title: "Batch check failed",
        description: err?.message ?? String(err),
        variant: "destructive",
      });
    } finally {
      setIsCheckingAllElig(false);
      // intentionally do not clear task status here so banner persists until user dismisses it
    }
  };

  const handleClaimForColumn = async () => {
    if (!user || selectedClaimColumns.size === 0) return;

    const staffIdsParam = Array.from(selectedClaimColumns).join(",");

    dispatch(
      setTaskStatus({
        key: "claimBatchCheck",
        status: "pending",
        message: `Submitting claims for selected columns on ${formattedSelectedDate}...`,
      })
    );
    setIsClaimingColumn(true);

    try {
      const res = await apiRequest(
        "POST",
        `/api/claims/batch-column?date=${formattedSelectedDate}&staffIds=${staffIdsParam}`,
        {}
      );

      let body: any;
      try { body = await res.json(); } catch { body = null; }

      if (!res.ok) {
        const errMsg = body?.error ?? `Server error ${res.status}`;
        dispatch(setTaskStatus({ key: "claimBatchCheck", status: "error", message: `Batch claim failed: ${errMsg}` }));
        toast({ title: "Batch claim failed", description: errMsg, variant: "destructive" });
        return;
      }

      const results: any[] = Array.isArray(body?.results) ? body.results : [];
      const appointmentMap = new Map<number, Appointment>();
      for (const a of appointments) {
        if (a && typeof a.id === "number") appointmentMap.set(a.id, a);
      }

      let queued = 0, skippedNoProcedures = 0, skippedAlreadyClaimed = 0, errCount = 0;

      for (const r of results) {
        const aptId = Number(r.appointmentId);
        const apt = appointmentMap.get(aptId);
        const patientName = apt
          ? patientsFromDay.find((p) => p.id === apt.patientId)
            ? `${patientsFromDay.find((p) => p.id === apt.patientId)!.firstName ?? ""} ${patientsFromDay.find((p) => p.id === apt.patientId)!.lastName ?? ""}`.trim()
            : `patient#${apt.patientId}`
          : `appointment#${aptId}`;

        if (r.skipped && r.error === "Already claimed") {
          skippedAlreadyClaimed++;
        } else if (r.skipped) {
          skippedNoProcedures++;
        } else if (r.error) {
          errCount++;
          toast({ title: `Skipped: ${patientName}`, description: r.error, variant: "destructive" });
        } else if (r.processed) {
          queued++;
        }
      }

      queryClient.invalidateQueries({ queryKey: qkAppointmentsDay(formattedSelectedDate) });

      dispatch(
        setTaskStatus({
          key: "claimBatchCheck",
          status: errCount > 0 ? "error" : "success",
          message: `Claims queued: ${queued}, already claimed: ${skippedAlreadyClaimed}, no procedures: ${skippedNoProcedures}, errors: ${errCount}.`,
        })
      );
      toast({
        title: "Claim batch queued",
        description: `Queued: ${queued}, already claimed: ${skippedAlreadyClaimed}, no procedures: ${skippedNoProcedures}, errors: ${errCount}.`,
        variant: errCount > 0 ? "destructive" : "default",
      });
    } catch (err: any) {
      dispatch(setTaskStatus({ key: "claimBatchCheck", status: "error", message: `Batch claim error: ${err?.message ?? String(err)}` }));
      toast({ title: "Batch claim failed", description: err?.message ?? String(err), variant: "destructive" });
    } finally {
      setIsClaimingColumn(false);
    }
  };

  const handleSendRemindersForColumn = async () => {
    if (!user || selectedReminderColumns.size === 0) return;
    setIsSendingReminders(true);
    try {
      const res = await apiRequest("POST", "/api/twilio/send-reminders-batch", {
        date: formattedSelectedDate,
        staffIds: Array.from(selectedReminderColumns),
        aiFollowUp: reminderAiFollowUp,
      });
      const { sent, skipped } = await res.json();
      toast({
        title: "Text Reminders Sent",
        description: `Sent ${sent} reminder${sent !== 1 ? "s" : ""}${skipped > 0 ? `, skipped ${skipped} (no phone)` : ""}.`,
      });
    } catch (err: any) {
      toast({ title: "Failed to Send Reminders", description: err?.message ?? String(err), variant: "destructive" });
    } finally {
      setIsSendingReminders(false);
    }
  };

  const handleSendRescheduleForColumn = async () => {
    if (!user || selectedRescheduleColumns.size === 0) return;
    setIsSendingReschedule(true);
    try {
      const res = await apiRequest("POST", "/api/twilio/send-reschedule-batch", {
        date: formattedSelectedDate,
        staffIds: Array.from(selectedRescheduleColumns),
        aiFollowUp: rescheduleAiFollowUp,
      });
      const { sent, skipped } = await res.json();
      toast({
        title: "Reschedule Messages Sent",
        description: `Sent ${sent} message${sent !== 1 ? "s" : ""}${skipped > 0 ? `, skipped ${skipped} (no phone)` : ""}.`,
      });
    } catch (err: any) {
      toast({ title: "Failed to Send Reschedule Messages", description: err?.message ?? String(err), variant: "destructive" });
    } finally {
      setIsSendingReschedule(false);
    }
  };

  const handleDownloadClaimPdfs = async () => {
    if (!user || selectedDownloadPdfColumns.size === 0) return;
    const staffIdsParam = Array.from(selectedDownloadPdfColumns).join(",");
    setIsDownloadingClaimPdfs(true);
    try {
      const res = await apiRequest(
        "GET",
        `/api/claims/batch-pdf?date=${formattedSelectedDate}&staffIds=${staffIdsParam}`
      );
      if (!res.ok) {
        let errMsg = `Server error ${res.status}`;
        try { const body = await res.json(); errMsg = body?.error ?? errMsg; } catch { /* ignore */ }
        toast({ title: "Download failed", description: errMsg, variant: "destructive" });
        return;
      }
      const blob = await res.blob();
      const url = URL.createObjectURL(blob);
      const a = document.createElement("a");
      a.href = url;
      a.download = `claims_${formattedSelectedDate}.zip`;
      document.body.appendChild(a);
      a.click();
      document.body.removeChild(a);
      URL.revokeObjectURL(url);
      toast({ title: "Download started", description: `Claim PDFs for ${formattedSelectedDate} saved to your Downloads folder.` });
    } catch (err: any) {
      toast({ title: "Download failed", description: err?.message ?? String(err), variant: "destructive" });
    } finally {
      setIsDownloadingClaimPdfs(false);
    }
  };

  // ── AI Claim Queue ─────────────────────────────────────────────────────────

  const processAiClaimAtIndex = async (queue: ScheduledAppointment[], index: number) => {
    if (index >= queue.length) {
      setAiClaimModalOpen(false);
      sessionStorage.removeItem("ai_claim_queue");
      toast({ title: "All Done", description: `Finished processing all ${queue.length} appointment${queue.length !== 1 ? "s" : ""}.` });
      return;
    }
    const apt = queue[index]!;
    const aptDate = typeof apt.date === "string" ? apt.date : formatLocalDate(apt.date as Date);
    if (!apt.notes?.trim()) {
      setIsAiClaimProcessing(false);
      setAiClaimCurrentData({
        matchedCodes: [],
        siteKey: "",
        serviceDate: aptDate,
        appointmentId: Number(apt.id),
        patientName: apt.patientName,
        notes: "",
        reply: "No notes on this appointment.",
      });
      return;
    }
    setIsAiClaimProcessing(true);
    setAiClaimCurrentData(null);
    try {
      const res = await apiRequest("POST", "/api/ai/internal-chat", {
        message: `claim ${apt.notes} for ${apt.patientName} today`,
        clientDate: aptDate,
      });
      const data = await res.json();
      if ((data.action === "claim_only_ready" || data.action === "check_and_claim_ready") && data.actionData) {
        setAiClaimCurrentData({
          matchedCodes: data.actionData.matchedCodes ?? [],
          siteKey: data.actionData.siteKey ?? "",
          serviceDate: data.actionData.serviceDate ?? aptDate,
          appointmentId: Number(apt.id),
          patientName: apt.patientName,
          notes: apt.notes ?? "",
          reply: data.reply ?? "",
        });
      } else if (data.action === "need_cdt_clarification" && data.actionData) {
        const phrases: string[] = data.actionData.unknownPhrases ?? [];
        const inputs: Record<string, string> = {};
        for (const p of phrases) inputs[p] = "";
        setAiClaimCdtClarification({
          unknownPhrases: phrases,
          codeInputs: inputs,
          originalMessage: `claim ${apt.notes} for ${apt.patientName}`,
          aptDate,
          appointmentId: Number(apt.id),
          patientName: apt.patientName,
          notes: apt.notes ?? "",
          matchedSoFar: data.actionData.matchedSoFar ?? [],
        });
      } else {
        setAiClaimCurrentData({
          matchedCodes: [],
          siteKey: "",
          serviceDate: aptDate,
          appointmentId: Number(apt.id),
          patientName: apt.patientName,
          notes: apt.notes ?? "",
          reply: data.reply ?? "Could not interpret notes.",
        });
      }
    } catch {
      setAiClaimCurrentData({
        matchedCodes: [],
        siteKey: "",
        serviceDate: aptDate,
        appointmentId: Number(apt.id),
        patientName: apt.patientName,
        notes: apt.notes ?? "",
        reply: "Error contacting AI.",
      });
    } finally {
      setIsAiClaimProcessing(false);
    }
  };

  const handleClaimForColumnWithAi = async () => {
    if (selectedClaimAiColumns.size === 0) return;
    const unclaimed = processedAppointments.filter(
      (apt) => selectedClaimAiColumns.has(apt.staffId) && !apt.hasClaimWithNumber
    );
    if (!unclaimed.length) {
      toast({ title: "No unclaimed appointments", description: "All appointments in the selected columns are already claimed." });
      return;
    }
    sessionStorage.setItem("ai_claim_queue", JSON.stringify({
      appointments: unclaimed,
      date: formattedSelectedDate,
      pendingResume: false,
    }));
    setAiClaimQueue(unclaimed);
    setAiClaimCurrentIndex(0);
    setAiClaimModalOpen(true);
    await processAiClaimAtIndex(unclaimed, 0);
  };

  const handleAiClaimConfirm = () => {
    if (!aiClaimCurrentData) return;
    const { matchedCodes, siteKey, serviceDate, appointmentId } = aiClaimCurrentData;
    // Include current patient in queue; claims-page advances it only on successful submit.
    const fromCurrent = aiClaimQueue.slice(aiClaimCurrentIndex);
    if (fromCurrent.length > 0) {
      sessionStorage.setItem("ai_claim_queue", JSON.stringify({
        appointments: fromCurrent,
        date: formattedSelectedDate,
        pendingResume: true,
      }));
    } else {
      sessionStorage.removeItem("ai_claim_queue");
    }
    sessionStorage.setItem("chatbot_claim_prefill", JSON.stringify({
      codes: matchedCodes,
      siteKey,
      serviceDate,
      autoSubmit: true,
    }));
    setAiClaimModalOpen(false);
    setLocation(`/claims?appointmentId=${appointmentId}`);
  };

  const handleAiClaimSkip = async () => {
    setAiClaimCdtClarification(null);
    const nextIndex = aiClaimCurrentIndex + 1;
    setAiClaimCurrentIndex(nextIndex);
    await processAiClaimAtIndex(aiClaimQueue, nextIndex);
  };

  const handleAiClaimCdtSubmit = async () => {
    if (!aiClaimCdtClarification) return;
    const { codeInputs, originalMessage, aptDate, appointmentId, patientName, notes, matchedSoFar } = aiClaimCdtClarification;
    setAiClaimCdtClarification(null);
    setIsAiClaimProcessing(true);
    setAiClaimCurrentData(null);
    try {
      for (const [phrase, code] of Object.entries(codeInputs)) {
        await apiRequest("POST", "/api/ai/cdt-aliases/add", { phrase, cdtCode: code.trim() });
      }
      const res = await apiRequest("POST", "/api/ai/internal-chat", {
        message: originalMessage,
        clientDate: aptDate,
      });
      const data = await res.json();
      if ((data.action === "claim_only_ready" || data.action === "check_and_claim_ready") && data.actionData) {
        setAiClaimCurrentData({
          matchedCodes: data.actionData.matchedCodes ?? [],
          siteKey: data.actionData.siteKey ?? "",
          serviceDate: data.actionData.serviceDate ?? aptDate,
          appointmentId,
          patientName,
          notes,
          reply: data.reply ?? "",
        });
      } else if (data.action === "need_cdt_clarification" && data.actionData) {
        // Still has unknowns — loop again
        const phrases: string[] = data.actionData.unknownPhrases ?? [];
        const inputs: Record<string, string> = {};
        for (const p of phrases) inputs[p] = "";
        setAiClaimCdtClarification({
          unknownPhrases: phrases,
          codeInputs: inputs,
          originalMessage,
          aptDate,
          appointmentId,
          patientName,
          notes,
          matchedSoFar: data.actionData.matchedSoFar ?? matchedSoFar,
        });
      } else {
        setAiClaimCurrentData({
          matchedCodes: matchedSoFar.map((c) => ({ ...c, toothNumber: undefined, toothSurface: undefined })),
          siteKey: "",
          serviceDate: aptDate,
          appointmentId,
          patientName,
          notes,
          reply: data.reply ?? "Could not fully interpret notes.",
        });
      }
    } catch {
      setAiClaimCurrentData({
        matchedCodes: matchedSoFar.map((c) => ({ ...c, toothNumber: undefined, toothSurface: undefined })),
        siteKey: "",
        serviceDate: aptDate,
        appointmentId,
        patientName,
        notes,
        reply: "Error retrying after alias save.",
      });
    } finally {
      setIsAiClaimProcessing(false);
    }
  };

  return (
    <div>
      <SeleniumTaskBanner
        status={batchTask.status}
        message={batchTask.message}
        show={batchTask.show}
        onClear={() => dispatch(clearTaskStatus("eligibilityBatchCheck"))}
      />
      <SeleniumTaskBanner
        status={claimBatchTask.status}
        message={claimBatchTask.message}
        show={claimBatchTask.show}
        onClear={() => dispatch(clearTaskStatus("claimBatchCheck"))}
      />

      <div className="container mx-auto">
        <div className="flex justify-between items-center mb-6">
          <div>
            <h1 className="text-3xl font-bold tracking-tight">
              Appointment Schedule
            </h1>
            <p className="text-muted-foreground">
              View and manage the dental practice schedule
            </p>
          </div>

          <div className="flex items-center gap-3 flex-wrap">
            <Button
              onClick={() => {
                setEditingAppointment(undefined);
                setIsAddModalOpen(true);
              }}
              disabled={isLoading}
            >
              <Plus className="h-4 w-4" />
              New Appointment
            </Button>

            {/* Check Eligibility for Column section */}
            <div className="flex items-center gap-2 border rounded-md px-3 py-2 bg-white shadow-sm">
              <Button
                onClick={() => handleCheckAllEligibilities()}
                disabled={isLoading || isCheckingAllElig || selectedStaffColumns.size === 0}
                size="sm"
              >
                {isCheckingAllElig ? (
                  <>
                    <LoaderCircleIcon className="h-4 w-4 mr-1 animate-spin" />
                    Checking...
                  </>
                ) : (
                  <>
                    <Shield className="h-4 w-4 mr-1" />
                    Check Eligibility for Column
                  </>
                )}
              </Button>
              {staffMembers.map((staff, index) => (
                <label
                  key={staff.id}
                  className="flex items-center gap-1 cursor-pointer select-none"
                >
                  <input
                    type="checkbox"
                    className="w-4 h-4 rounded border-gray-400 accent-teal-600"
                    checked={selectedStaffColumns.has(Number(staff.id))}
                    onChange={() => toggleStaffColumn(Number(staff.id))}
                  />
                  <span className="text-sm font-medium">
                    {columnLabels[String(staff.id)] ?? String.fromCharCode(65 + index)}
                  </span>
                </label>
              ))}
            </div>

            {/* Claim for Column section */}
            <div className="flex items-center gap-2 border rounded-md px-3 py-2 bg-white shadow-sm">
              <Button
                onClick={() => handleClaimForColumn()}
                disabled={isLoading || isClaimingColumn || selectedClaimColumns.size === 0}
                size="sm"
              >
                {isClaimingColumn ? (
                  <>
                    <LoaderCircleIcon className="h-4 w-4 mr-1 animate-spin" />
                    Submitting...
                  </>
                ) : (
                  <>
                    <FileCheck className="h-4 w-4 mr-1" />
                    Claim for Column
                  </>
                )}
              </Button>
              {staffMembers.map((staff, index) => (
                <label
                  key={staff.id}
                  className="flex items-center gap-1 cursor-pointer select-none"
                >
                  <input
                    type="checkbox"
                    className="w-4 h-4 rounded border-gray-400 accent-teal-600"
                    checked={selectedClaimColumns.has(Number(staff.id))}
                    onChange={() => toggleClaimColumn(Number(staff.id))}
                  />
                  <span className="text-sm font-medium">
                    {columnLabels[String(staff.id)] ?? String.fromCharCode(65 + index)}
                  </span>
                </label>
              ))}
            </div>

            {/* Claim for Column with AI section */}
            <div className="flex flex-wrap items-center gap-2 border rounded-md px-3 py-2 bg-white shadow-sm">
              <Button
                onClick={() => handleClaimForColumnWithAi()}
                disabled={isLoading || isClaimingAiColumn || selectedClaimAiColumns.size === 0}
                size="sm"
              >
                {isClaimingAiColumn ? (
                  <>
                    <LoaderCircleIcon className="h-4 w-4 mr-1 animate-spin" />
                    Submitting...
                  </>
                ) : (
                  <>
                    <Bot className="h-4 w-4 mr-1" />
                    Claim for Column with AI
                  </>
                )}
              </Button>
              {staffMembers.map((staff, index) => (
                <label
                  key={staff.id}
                  className="flex items-center gap-1 cursor-pointer select-none"
                >
                  <input
                    type="checkbox"
                    className="w-4 h-4 rounded border-gray-400 accent-teal-600"
                    checked={selectedClaimAiColumns.has(Number(staff.id))}
                    onChange={() => toggleClaimAiColumn(Number(staff.id))}
                  />
                  <span className="text-sm font-medium">
                    {columnLabels[String(staff.id)] ?? String.fromCharCode(65 + index)}
                  </span>
                </label>
              ))}
              <div className="flex items-center gap-2 border-l pl-3 ml-1">
                <Switch
                  id="auto-claim-toggle"
                  checked={autoClaimEnabled}
                  onCheckedChange={setAutoClaimEnabled}
                />
                <Label htmlFor="auto-claim-toggle" className="text-sm font-medium cursor-pointer whitespace-nowrap">
                  Auto Claim
                </Label>
                {autoClaimEnabled && (
                  <Select value={autoClaimTime} onValueChange={setAutoClaimTime}>
                    <SelectTrigger className="h-8 w-28 text-xs">
                      <SelectValue />
                    </SelectTrigger>
                    <SelectContent>
                      {Array.from({ length: 24 }, (_, h) => {
                        const value = `${String(h).padStart(2, "0")}:00`;
                        const label = h === 0 ? "12 AM" : h < 12 ? `${h} AM` : h === 12 ? "12 PM" : `${h - 12} PM`;
                        return (
                          <SelectItem key={value} value={value}>
                            {label}
                          </SelectItem>
                        );
                      })}
                    </SelectContent>
                  </Select>
                )}
              </div>
            </div>

            {/* Text Reminder for Column section */}
            <div className="flex items-center gap-2 border rounded-md px-3 py-2 bg-white shadow-sm">
              <Button
                onClick={() => handleSendRemindersForColumn()}
                disabled={isLoading || isSendingReminders || selectedReminderColumns.size === 0}
                size="sm"
              >
                {isSendingReminders ? (
                  <>
                    <LoaderCircleIcon className="h-4 w-4 mr-1 animate-spin" />
                    Sending...
                  </>
                ) : (
                  <>
                    <MessageSquare className="h-4 w-4 mr-1" />
                    Text Reminder for Column
                  </>
                )}
              </Button>
              {staffMembers.map((staff, index) => (
                <label
                  key={staff.id}
                  className="flex items-center gap-1 cursor-pointer select-none"
                >
                  <input
                    type="checkbox"
                    className="w-4 h-4 rounded border-gray-400 accent-teal-600"
                    checked={selectedReminderColumns.has(Number(staff.id))}
                    onChange={() => toggleReminderColumn(Number(staff.id))}
                  />
                  <span className="text-sm font-medium">
                    {columnLabels[String(staff.id)] ?? String.fromCharCode(65 + index)}
                  </span>
                </label>
              ))}
              <div className="flex items-center gap-1.5 ml-2 pl-2 border-l border-gray-200">
                <button
                  type="button"
                  role="switch"
                  aria-checked={reminderAiFollowUp}
                  onClick={() => setReminderAiFollowUp((v) => !v)}
                  className={`relative inline-flex h-5 w-9 shrink-0 cursor-pointer rounded-full border-2 border-transparent transition-colors focus:outline-none ${reminderAiFollowUp ? "bg-teal-600" : "bg-gray-300"}`}
                >
                  <span
                    className={`pointer-events-none inline-block h-4 w-4 rounded-full bg-white shadow transform transition-transform ${reminderAiFollowUp ? "translate-x-4" : "translate-x-0"}`}
                  />
                </button>
                <span className="text-xs text-gray-600 whitespace-nowrap">AI follow up</span>
              </div>
            </div>

            {/* Reschedule for Column section */}
            <div className="flex items-center gap-2 border rounded-md px-3 py-2 bg-white shadow-sm">
              <Button
                onClick={() => handleSendRescheduleForColumn()}
                disabled={isLoading || isSendingReschedule || selectedRescheduleColumns.size === 0}
                size="sm"
              >
                {isSendingReschedule ? (
                  <>
                    <LoaderCircleIcon className="h-4 w-4 mr-1 animate-spin" />
                    Sending...
                  </>
                ) : (
                  <>
                    <MessageSquare className="h-4 w-4 mr-1" />
                    Reschedule for Column
                  </>
                )}
              </Button>
              {staffMembers.map((staff, index) => (
                <label
                  key={staff.id}
                  className="flex items-center gap-1 cursor-pointer select-none"
                >
                  <input
                    type="checkbox"
                    className="w-4 h-4 rounded border-gray-400 accent-teal-600"
                    checked={selectedRescheduleColumns.has(Number(staff.id))}
                    onChange={() => toggleRescheduleColumn(Number(staff.id))}
                  />
                  <span className="text-sm font-medium">
                    {columnLabels[String(staff.id)] ?? String.fromCharCode(65 + index)}
                  </span>
                </label>
              ))}
              <div className="flex items-center gap-1.5 ml-2 pl-2 border-l border-gray-200">
                <button
                  type="button"
                  role="switch"
                  aria-checked={rescheduleAiFollowUp}
                  onClick={() => setRescheduleAiFollowUp((v) => !v)}
                  className={`relative inline-flex h-5 w-9 shrink-0 cursor-pointer rounded-full border-2 border-transparent transition-colors focus:outline-none ${rescheduleAiFollowUp ? "bg-teal-600" : "bg-gray-300"}`}
                >
                  <span
                    className={`pointer-events-none inline-block h-4 w-4 rounded-full bg-white shadow transform transition-transform ${rescheduleAiFollowUp ? "translate-x-4" : "translate-x-0"}`}
                  />
                </button>
                <span className="text-xs text-gray-600 whitespace-nowrap">AI follow up</span>
              </div>
            </div>

            {/* Download Claim PDF for Column section */}
            <div className="flex items-center gap-2 border rounded-md px-3 py-2 bg-white shadow-sm">
              <Button
                onClick={() => handleDownloadClaimPdfs()}
                disabled={isLoading || isDownloadingClaimPdfs || selectedDownloadPdfColumns.size === 0}
                size="sm"
              >
                {isDownloadingClaimPdfs ? (
                  <>
                    <LoaderCircleIcon className="h-4 w-4 mr-1 animate-spin" />
                    Downloading...
                  </>
                ) : (
                  <>
                    <Download className="h-4 w-4 mr-1" />
                    Download Claim PDF for Column
                  </>
                )}
              </Button>
              {staffMembers.map((staff, index) => (
                <label
                  key={staff.id}
                  className="flex items-center gap-1 cursor-pointer select-none"
                >
                  <input
                    type="checkbox"
                    className="w-4 h-4 rounded border-gray-400 accent-teal-600"
                    checked={selectedDownloadPdfColumns.has(Number(staff.id))}
                    onChange={() => toggleDownloadPdfColumn(Number(staff.id))}
                  />
                  <span className="text-sm font-medium">
                    {columnLabels[String(staff.id)] ?? String.fromCharCode(65 + index)}
                  </span>
                </label>
              ))}
            </div>
          </div>
        </div>

        {/* Context Menu */}
        <Menu id={APPOINTMENT_CONTEXT_MENU_ID} animation="fade">
          <Item
            onClick={({ props }) => {
              const fullAppointment = appointments.find(
                (a) => a.id === props.appointmentId
              );
              if (fullAppointment) {
                handleEditAppointment(fullAppointment);
              }
            }}
          >
            <span className="flex items-center gap-2">
              <CalendarIcon className="h-4 w-4" />
              Edit Appointment
            </span>
          </Item>
          <Item
            onClick={({ props }) =>
              handleDeleteAppointment(props.appointmentId)
            }
          >
            <span className="flex items-center gap-2 text-red-600">
              <Trash2 className="h-4 w-4" />
              Delete Appointment
            </span>
          </Item>

          {/* Select Procedures */}
          <Item
            onClick={({ props }) => handleSelectProcedures(props.appointmentId)}
          >
            <span className="flex items-center gap-2 text-purple-600">
              <Stethoscope className="h-4 w-4" />
              Select Procedures
            </span>
          </Item>

          {/* Check Eligibility */}
          <Item
            onClick={({ props }) => handleCheckEligibility(props.appointmentId)}
          >
            <span className="flex items-center gap-2">
              <Shield className="h-4 w-4" />
              Check Eligibility
            </span>
          </Item>

          {/* Claims / PreAuth */}
          <Item
            onClick={({ props }) => handleClaimsPreAuth(props.appointmentId)}
          >
            <span className="flex items-center gap-2">
              <FileCheck className="h-4 w-4" />
              Claims/PreAuth
            </span>
          </Item>

          {/* Payments */}
          <Item onClick={({ props }) => handlePayments(props.appointmentId)}>
            <span className="flex items-center gap-2 text-green-600">
              <CreditCard className="h-4 w-4" />
              Payments
            </span>
          </Item>

          {/* Chart / Treatment Plan */}
          <Item onClick={({ props }) => handleChartPlan(props.appointmentId)}>
            <span className="flex items-center gap-2">
              <ClipboardList className="h-4 w-4" />
              Chart / Treatment Plan
            </span>
          </Item>

          {/* Clinic Notes */}
          <Item onClick={({ props }) => handleClinicNotes(props.appointmentId)}>
            <span className="flex items-center gap-2 text-yellow-600">
              <StickyNote className="h-4 w-4" />
              Clinic Notes
            </span>
          </Item>

          {/* Chat */}
          <Item onClick={({ props }) => handleChat(props.appointmentId)}>
            <span className="flex items-center gap-2 text-blue-600">
              <MessageSquare className="h-4 w-4" />
              Chat
            </span>
          </Item>

          {/* Manually Confirmed — only relevant for AI-moved appointments */}
          <Item onClick={({ props }) => handleManualConfirm(props.appointmentId)}>
            <span className="flex items-center gap-2 text-teal-700">
              <UserCheck className="h-4 w-4" />
              Manually Confirmed
            </span>
          </Item>
        </Menu>

        {/* Main Content  */}
        <div className="flex flex-col lg:flex-row gap-6">
          {/* Schedule Grid */}
          <div className="w-full overflow-x-auto bg-white rounded-md shadow">
            <div className="p-4 border-b">
              <div className="flex items-center justify-between">
                <div className="flex items-center space-x-1">
                  {/* << prev week */}
                  <Button
                    variant="outline"
                    size="icon"
                    title="Previous week"
                    onClick={() => setSelectedDate(addWeeks(selectedDate, -1))}
                  >
                    <ChevronsLeft className="h-4 w-4" />
                  </Button>
                  {/* < prev day */}
                  <Button
                    variant="outline"
                    size="icon"
                    title="Previous day"
                    onClick={() => setSelectedDate(addDays(selectedDate, -1))}
                  >
                    <ChevronLeft className="h-4 w-4" />
                  </Button>
                  {/* today circle */}
                  <Button
                    variant="outline"
                    title="Go to today"
                    onClick={() => setSelectedDate(startOfToday())}
                    className="w-auto px-3 font-semibold text-sm"
                  >
                    {selectedDate.toLocaleDateString('en-US', { weekday: 'long' })}&nbsp;·&nbsp;{`${String(selectedDate.getMonth() + 1).padStart(2, '0')}-${String(selectedDate.getDate()).padStart(2, '0')}-${selectedDate.getFullYear()}`}
                  </Button>
                  {/* > next day */}
                  <Button
                    variant="outline"
                    size="icon"
                    title="Next day"
                    onClick={() => setSelectedDate(addDays(selectedDate, 1))}
                  >
                    <ChevronRight className="h-4 w-4" />
                  </Button>
                  {/* >> next week */}
                  <Button
                    variant="outline"
                    size="icon"
                    title="Next week"
                    onClick={() => setSelectedDate(addWeeks(selectedDate, 1))}
                  >
                    <ChevronsRight className="h-4 w-4" />
                  </Button>
                </div>

                {/* Top button with popover calendar */}
                <div className="flex items-center gap-2">
                  <Label className="hidden sm:flex">Selected</Label>
                  <Popover open={calendarOpen} onOpenChange={setCalendarOpen}>
                    <PopoverTrigger asChild>
                      <Button
                        variant="outline"
                        className="w-[160px] justify-start text-left font-normal"
                      >
                        <CalendarIcon className="mr-2 h-4 w-4" />
                        {selectedDate
                          ? formatDateToHumanReadable(selectedDate)
                          : "Pick a date"}
                      </Button>
                    </PopoverTrigger>

                    <PopoverContent className="w-auto">
                      <Calendar
                        mode="single"
                        selected={selectedDate}
                        onSelect={(date) => {
                          if (date) setSelectedDate(date);
                        }}
                        onClose={() => setCalendarOpen(false)}
                      />
                    </PopoverContent>
                  </Popover>
                </div>
              </div>
            </div>

            {/* Office Hours Summary */}
            {(() => {
              const dayNames = ["sunday","monday","tuesday","wednesday","thursday","friday","saturday"] as const;
              const dayName = dayNames[selectedDate.getDay()]!;
              const fmt = (t: string) => {
                const [hh, mm] = t.split(":").map(Number);
                const period = (hh ?? 0) >= 12 ? "PM" : "AM";
                const h12 = (hh ?? 0) > 12 ? (hh ?? 0) - 12 : (hh ?? 0) === 0 ? 12 : (hh ?? 0);
                return `${h12}:${String(mm ?? 0).padStart(2,"0")} ${period}`;
              };
              const doctorHours  = officeHours?.doctors?.[dayName];
              const hygHours     = officeHours?.hygienists?.[dayName];
              const isOverride   = officeHours?.overrideDates?.includes(selectedDate.toLocaleDateString("en-CA"));

              return (
                <div className="px-4 pb-3 flex items-center gap-4 flex-wrap text-xs text-gray-500 border-b">
                  <div className="flex items-center gap-1.5 font-medium text-gray-700">
                    <Clock className="h-3.5 w-3.5 text-teal-600" />
                    <span>Office Hours</span>
                    <button onClick={() => setLocation("/settings/officehours")} className="ml-1 text-teal-600 hover:text-teal-700" title="Edit office hours">
                      <ExternalLink className="h-3 w-3" />
                    </button>
                  </div>
                  {!officeHours ? (
                    <span className="italic text-gray-400">Not configured — <button onClick={() => setLocation("/settings/officehours")} className="text-teal-600 underline">set up office hours</button></span>
                  ) : isOverride ? (
                    <span className="text-teal-600 font-medium">Override active — all slots open today</span>
                  ) : (
                    <>
                      <span>
                        <span className="font-medium text-gray-600">Doctors (A–C):</span>{" "}
                        {doctorHours?.enabled
                          ? `${fmt(doctorHours.amStart)}–${fmt(doctorHours.amEnd)}, ${fmt(doctorHours.pmStart)}–${fmt(doctorHours.pmEnd)}`
                          : <span className="text-gray-400">Closed</span>}
                      </span>
                      <span>
                        <span className="font-medium text-gray-600">Hygienists (D–F):</span>{" "}
                        {hygHours?.enabled
                          ? `${fmt(hygHours.amStart)}–${fmt(hygHours.amEnd)}, ${fmt(hygHours.pmStart)}–${fmt(hygHours.pmEnd)}`
                          : <span className="text-gray-400">Closed</span>}
                      </span>
                    </>
                  )}
                </div>
              );
            })()}

            {/* Schedule Grid with Drag and Drop */}
            <DndProvider backend={HTML5Backend}>
              <div className="overflow-x-auto">
                <table className="w-full border-collapse min-w-[800px]">
                  <thead>
                    <tr>
                      <th className="p-2 border bg-gray-50 w-[100px]">Time</th>
                      {staffMembers.map((staff, index) => (
                        <th
                          key={staff.id}
                          className={`p-2 border bg-gray-50 ${staff.role === "doctor" ? "font-bold" : ""}`}
                        >
                          {editingLabelStaffId === String(staff.id) ? (
                            <input
                              className="w-16 text-center border rounded text-sm font-bold px-1"
                              defaultValue={columnLabels[String(staff.id)] ?? String.fromCharCode(65 + index)}
                              onBlur={(e) => handleLabelSave(String(staff.id), e.target.value)}
                              onKeyDown={(e) => {
                                if (e.key === "Enter") handleLabelSave(String(staff.id), e.currentTarget.value);
                                if (e.key === "Escape") setEditingLabelStaffId(null);
                              }}
                              autoFocus
                            />
                          ) : (
                            <div
                              className="font-bold text-sm cursor-pointer hover:bg-gray-200 rounded px-1 inline-block"
                              title="Click to rename"
                              onClick={() => setEditingLabelStaffId(String(staff.id))}
                            >
                              {columnLabels[String(staff.id)] ?? String.fromCharCode(65 + index)}
                            </div>
                          )}
                          <div className="text-xs text-gray-500">{staff.name}</div>
                          <div className="text-xs text-gray-400">{staff.role}</div>
                        </th>
                      ))}
                    </tr>
                  </thead>
                  <tbody>
                    {timeSlots.map((timeSlot) => (
                      <tr key={timeSlot.time}>
                        <td className="border px-2 py-1 text-xs text-gray-600 font-medium">
                          {timeSlot.displayTime}
                        </td>
                        {staffMembers.map((staff, staffIndex) => {
                          if (coveredSlots.has(`${timeSlot.time}-${staff.id}`)) return null;
                          const apt = getAppointmentAtSlot(timeSlot, Number(staff.id));
                          const span = apt ? getDisplaySpan(apt) : 1;
                          return (
                            <DroppableTimeSlot
                              key={`${timeSlot.time}-${staff.id}`}
                              timeSlot={timeSlot}
                              staffId={Number(staff.id)}
                              staffIndex={staffIndex}
                              appointment={apt}
                              staff={staff}
                              rowSpan={span}
                            />
                          );
                        })}
                      </tr>
                    ))}
                  </tbody>
                </table>
              </div>
            </DndProvider>
          </div>
        </div>
      </div>

      {/* Add/Edit Appointment Modal */}
      <AddAppointmentModal
        open={isAddModalOpen}
        onOpenChange={(open) => {
          setIsAddModalOpen(open);
          if (!open) setNewApptPrefill(null);
        }}
        onSubmit={handleAppointmentSubmit}
        isLoading={
          createAppointmentMutation.isPending ||
          updateAppointmentMutation.isPending
        }
        appointment={editingAppointment}
        prefillData={editingAppointment ? null : newApptPrefill}
        onDelete={handleDeleteAppointment}
      />

      <DeleteConfirmationDialog
        isOpen={confirmDeleteState.open}
        onConfirm={handleConfirmDelete}
        onCancel={() => setConfirmDeleteState({ open: false })}
        entityName={String(confirmDeleteState.appointmentId)}
      />

      {/* Outside-office-hours override dialog */}
      {pendingOverride && (
        <div className="fixed inset-0 bg-black/40 flex items-center justify-center z-50">
          <div className="bg-white rounded-lg shadow-lg p-6 max-w-sm w-full mx-4">
            <h3 className="text-base font-semibold mb-2">Outside Office Hours</h3>
            <p className="text-sm text-gray-600 mb-5">
              This time slot is outside the configured office hours. Do you want to schedule here anyway?
            </p>
            <div className="flex justify-end gap-3">
              <button
                className="px-4 py-2 text-sm border rounded hover:bg-gray-50"
                onClick={() => setPendingOverride(null)}
              >
                Cancel
              </button>
              <button
                className="px-4 py-2 text-sm bg-teal-600 text-white rounded hover:bg-teal-700"
                onClick={() => {
                  if (pendingOverride.type === "move" && pendingOverride.appointmentId != null) {
                    handleMoveAppointment(pendingOverride.appointmentId, pendingOverride.timeSlot, pendingOverride.staffId);
                  } else {
                    handleCreateAppointmentAtSlot(pendingOverride.timeSlot, pendingOverride.staffId);
                  }
                  setPendingOverride(null);
                }}
              >
                Schedule Anyway
              </button>
            </div>
          </div>
        </div>
      )}

      {/* Chat popup */}
      {chatPatient && (
        <div className="fixed inset-0 bg-black/40 flex items-center justify-center z-50">
          <div className="bg-white rounded-lg shadow-xl w-full max-w-lg h-[600px] flex flex-col overflow-hidden">
            <MessageThread
              patient={chatPatient}
              appointmentInfo={chatAppointmentInfo}
              onBack={() => { setChatPatient(null); setChatAppointmentInfo(undefined); }}
            />
          </div>
        </div>
      )}

      {/* Select Procedures modal — stays on appointments page */}
      {isSelectProceduresOpen && selectProceduresPatientId !== null && (
        <ClaimForm
          patientId={selectProceduresPatientId}
          appointmentId={selectProceduresAppointmentId ?? undefined}
          proceduresOnly={true}
          onClose={() => {
            setIsSelectProceduresOpen(false);
            setSelectProceduresPatientId(null);
            setSelectProceduresAppointmentId(null);
          }}
          onSubmit={async () => { throw new Error("not used in proceduresOnly mode"); }}
          onHandleAppointmentSubmit={async () => 0}
          onHandleUpdatePatient={(_patient: UpdatePatient & { id: number }) => {}}
          onHandleForMHSeleniumClaim={() => {}}
          onHandleForMHSeleniumClaimPreAuth={() => {}}
          onHandleForCCASeleniumClaim={() => {}}
          onHandleForCCASeleniumPreAuth={() => {}}
          onHandleForUnitedDHSeleniumPreAuth={() => {}}
          onHandleForDDMASeleniumClaim={() => {}}
          onHandleForUnitedDHSeleniumClaim={() => {}}
          onHandleForTuftsSCOSeleniumPreAuth={() => {}}
          onHandleForTuftsSCOSeleniumClaim={() => {}}
        />
      )}

      {/* AI Claim Queue Modal */}
      {aiClaimModalOpen && (
        <div className="fixed inset-0 bg-black/40 flex items-center justify-center z-50">
          <div className="bg-white rounded-lg shadow-xl w-full max-w-md mx-4">
            <div className="p-4 border-b flex items-center justify-between">
              <h3 className="font-semibold text-base flex items-center gap-2">
                <Bot className="h-4 w-4 text-teal-600" />
                AI Claim Queue
              </h3>
              <span className="text-xs text-gray-500 font-medium">
                {aiClaimCurrentIndex + 1} / {aiClaimQueue.length}
              </span>
            </div>
            <div className="p-4">
              {isAiClaimProcessing ? (
                <div className="flex items-center gap-2 text-sm text-gray-500 py-4">
                  <LoaderCircleIcon className="h-4 w-4 animate-spin text-teal-600" />
                  Interpreting notes with AI...
                </div>
              ) : aiClaimCdtClarification ? (
                <div className="space-y-3">
                  <div>
                    <p className="text-sm font-semibold">{aiClaimCdtClarification.patientName}</p>
                    <p className="text-xs text-gray-500 mt-0.5">
                      Notes: <span className="italic">{aiClaimCdtClarification.notes}</span>
                    </p>
                  </div>
                  <p className="text-xs font-medium text-amber-700">
                    Unknown term{aiClaimCdtClarification.unknownPhrases.length > 1 ? "s" : ""} — enter CDT code{aiClaimCdtClarification.unknownPhrases.length > 1 ? "s" : ""}:
                  </p>
                  {aiClaimCdtClarification.matchedSoFar.length > 0 && (
                    <div className="bg-teal-50 border border-teal-200 rounded p-2 space-y-0.5">
                      <p className="text-[10px] text-teal-700 font-medium uppercase tracking-wide">Already matched</p>
                      {aiClaimCdtClarification.matchedSoFar.map((c) => (
                        <p key={c.code} className="text-xs">
                          <span className="font-semibold text-teal-800">{c.code}</span>
                          <span className="text-gray-600"> — {c.description}</span>
                        </p>
                      ))}
                    </div>
                  )}
                  <div className="space-y-2">
                    {aiClaimCdtClarification.unknownPhrases.map((phrase) => (
                      <div key={phrase} className="flex items-center gap-2">
                        <span className="text-xs text-gray-700 font-medium shrink-0">"{phrase}" →</span>
                        <input
                          type="text"
                          placeholder="D0272"
                          value={aiClaimCdtClarification.codeInputs[phrase] ?? ""}
                          onChange={(e) =>
                            setAiClaimCdtClarification((prev) =>
                              prev ? { ...prev, codeInputs: { ...prev.codeInputs, [phrase]: e.target.value.toUpperCase() } } : prev
                            )
                          }
                          className="flex-1 rounded border border-amber-300 bg-white px-2 py-1 text-xs focus:outline-none focus:ring-1 focus:ring-amber-400"
                        />
                      </div>
                    ))}
                  </div>
                  <div className="flex gap-2 pt-1">
                    <button
                      className="flex-1 flex items-center justify-center gap-1 text-xs h-8 px-3 rounded bg-amber-600 hover:bg-amber-700 text-white font-medium transition-colors disabled:opacity-50"
                      disabled={Object.values(aiClaimCdtClarification.codeInputs).some((v) => !v.trim())}
                      onClick={handleAiClaimCdtSubmit}
                    >
                      Save &amp; Retry
                    </button>
                    <button
                      className="flex items-center justify-center gap-1 text-xs h-8 px-3 rounded border border-gray-300 hover:bg-gray-50 text-gray-700 font-medium transition-colors"
                      onClick={() => { setAiClaimCdtClarification(null); handleAiClaimSkip(); }}
                    >
                      Skip
                    </button>
                    <button
                      className="flex items-center justify-center gap-1 text-xs h-8 px-3 rounded hover:bg-gray-100 text-gray-500 transition-colors"
                      onClick={() => { setAiClaimCdtClarification(null); setAiClaimModalOpen(false); sessionStorage.removeItem("ai_claim_queue"); }}
                    >
                      Cancel All
                    </button>
                  </div>
                </div>
              ) : aiClaimCurrentData ? (
                <div className="space-y-3">
                  <div>
                    <p className="text-sm font-semibold">{aiClaimCurrentData.patientName}</p>
                    {aiClaimCurrentData.notes && (
                      <p className="text-xs text-gray-500 mt-0.5">
                        Notes: <span className="italic">{aiClaimCurrentData.notes}</span>
                      </p>
                    )}
                  </div>
                  <p className="text-xs text-gray-600">{aiClaimCurrentData.reply}</p>
                  {aiClaimCurrentData.matchedCodes.length > 0 ? (
                    <div className="bg-teal-50 border border-teal-200 rounded p-2 space-y-1">
                      {aiClaimCurrentData.matchedCodes.map((c) => (
                        <p key={c.code} className="text-xs">
                          <span className="font-semibold text-teal-800">{c.code}</span>
                          <span className="text-gray-600"> — {c.description}</span>
                          {c.toothNumber && <span className="text-gray-500"> (#{c.toothNumber})</span>}
                        </p>
                      ))}
                    </div>
                  ) : (
                    <p className="text-xs text-amber-600 bg-amber-50 rounded p-2">
                      No procedures could be matched from notes. Skip this appointment or cancel.
                    </p>
                  )}
                  <div className="flex gap-2 pt-1">
                    {aiClaimCurrentData.matchedCodes.length > 0 && (
                      <button
                        className="flex-1 flex items-center justify-center gap-1 text-xs h-8 px-3 rounded bg-teal-600 hover:bg-teal-700 text-white font-medium transition-colors"
                        onClick={handleAiClaimConfirm}
                      >
                        <FileCheck className="h-3.5 w-3.5" />
                        Confirm &amp; Claim
                      </button>
                    )}
                    <button
                      className="flex items-center justify-center gap-1 text-xs h-8 px-3 rounded border border-gray-300 hover:bg-gray-50 text-gray-700 font-medium transition-colors"
                      onClick={handleAiClaimSkip}
                    >
                      Skip
                    </button>
                    <button
                      className="flex items-center justify-center gap-1 text-xs h-8 px-3 rounded hover:bg-gray-100 text-gray-500 transition-colors"
                      onClick={() => {
                        setAiClaimModalOpen(false);
                        sessionStorage.removeItem("ai_claim_queue");
                      }}
                    >
                      Cancel All
                    </button>
                  </div>
                </div>
              ) : null}
            </div>
          </div>
        </div>
      )}
    </div>
  );
}
