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 { 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 { 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 { MessageThread } from "@/components/patient-connection/message-thread";
import { getAppointmentTypeLabel } from "@/utils/appointmentTypeUtils";
function appointmentCardColor(apt) {
    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) {
    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"];
// helper (optional) – mirrors the query key structure
export const qkAppointmentsDay = (date) => [...QK_APPOINTMENTS_BASE, date];
export default function AppointmentsPage() {
    const { toast } = useToast();
    const { user } = useAuth();
    const [isAddModalOpen, setIsAddModalOpen] = useState(false);
    const [calendarOpen, setCalendarOpen] = useState(false);
    const [editingAppointment, setEditingAppointment] = useState(undefined);
    const [selectedDate, setSelectedDate] = useState(startOfToday());
    const [location] = useLocation();
    const [confirmDeleteState, setConfirmDeleteState] = useState({ 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({});
    const [selectedStaffColumns, setSelectedStaffColumns] = useState(new Set());
    const toggleStaffColumn = (staffId) => {
        setSelectedStaffColumns((prev) => {
            const next = new Set(prev);
            if (next.has(staffId))
                next.delete(staffId);
            else
                next.add(staffId);
            return next;
        });
    };
    const [selectedClaimColumns, setSelectedClaimColumns] = useState(new Set());
    const [isClaimingColumn, setIsClaimingColumn] = useState(false);
    const [selectedClaimAiColumns, setSelectedClaimAiColumns] = useState(new Set());
    const [isClaimingAiColumn, setIsClaimingAiColumn] = useState(false);
    const [autoClaimEnabled, setAutoClaimEnabled] = useState(false);
    const [autoClaimTime, setAutoClaimTime] = useState("19:00");
    const autoClaimFiredHourRef = useRef(null);
    const [aiClaimModalOpen, setAiClaimModalOpen] = useState(false);
    const [aiClaimQueue, setAiClaimQueue] = useState([]);
    const [aiClaimCurrentIndex, setAiClaimCurrentIndex] = useState(0);
    const [isAiClaimProcessing, setIsAiClaimProcessing] = useState(false);
    const [aiClaimCurrentData, setAiClaimCurrentData] = useState(null);
    const [needsAiClaimResume, setNeedsAiClaimResume] = useState(false);
    const [aiClaimCdtClarification, setAiClaimCdtClarification] = useState(null);
    const [selectedReminderColumns, setSelectedReminderColumns] = useState(new Set());
    const [isSendingReminders, setIsSendingReminders] = useState(false);
    const [reminderAiFollowUp, setReminderAiFollowUp] = useState(true);
    const [selectedRescheduleColumns, setSelectedRescheduleColumns] = useState(new Set());
    const [isSendingReschedule, setIsSendingReschedule] = useState(false);
    const [rescheduleAiFollowUp, setRescheduleAiFollowUp] = useState(true);
    const [selectedDownloadPdfColumns, setSelectedDownloadPdfColumns] = useState(new Set());
    const [isDownloadingClaimPdfs, setIsDownloadingClaimPdfs] = useState(false);
    const [columnLabels, setColumnLabels] = useState({});
    const [editingLabelStaffId, setEditingLabelStaffId] = useState(null);
    const [pendingOverride, setPendingOverride] = useState(null);
    const [newApptPrefill, setNewApptPrefill] = useState(null);
    const toggleReminderColumn = (staffId) => {
        setSelectedReminderColumns((prev) => {
            const next = new Set(prev);
            if (next.has(staffId))
                next.delete(staffId);
            else
                next.add(staffId);
            return next;
        });
    };
    const toggleClaimColumn = (staffId) => {
        setSelectedClaimColumns((prev) => {
            const next = new Set(prev);
            if (next.has(staffId))
                next.delete(staffId);
            else
                next.add(staffId);
            return next;
        });
    };
    const toggleClaimAiColumn = (staffId) => {
        setSelectedClaimAiColumns((prev) => {
            const next = new Set(prev);
            if (next.has(staffId))
                next.delete(staffId);
            else
                next.add(staffId);
            return next;
        });
    };
    const toggleRescheduleColumn = (staffId) => {
        setSelectedRescheduleColumns((prev) => {
            const next = new Set(prev);
            if (next.has(staffId))
                next.delete(staffId);
            else
                next.add(staffId);
            return next;
        });
    };
    const toggleDownloadPdfColumn = (staffId) => {
        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(null);
    const [selectProceduresAppointmentId, setSelectProceduresAppointmentId] = useState(null);
    // Chat popup state
    const [chatPatient, setChatPatient] = useState(null);
    const [chatAppointmentInfo, setChatAppointmentInfo] = useState(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);
    const { data: dayPayload = {
        appointments: [],
        patients: [],
    }, isLoading: isLoadingAppointments, refetch: refetchAppointments, } = useQuery({
        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({
        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 = [] } = useQuery({
        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") || "{}");
        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, value) => {
        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, staffIndex) => {
        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"];
        const dayName = dayNames[selectedDate.getDay()];
        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 = [];
    for (let hour = 8; hour <= 21; hour++) {
        for (let minute = 0; minute < 60; minute += 15) {
            const pad = (n) => 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;
            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) => {
            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) => {
            toast({
                title: "Error",
                description: `Failed to create appointment: ${error.message}`,
                variant: "destructive",
            });
        },
    });
    // Update appointment mutation
    const updateAppointmentMutation = useMutation({
        mutationFn: async ({ id, appointment, }) => {
            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) => {
            toast({
                title: "Error",
                description: `Failed to update appointment: ${error.message}`,
                variant: "destructive",
            });
        },
    });
    // Delete appointment mutation
    const deleteAppointmentMutation = useMutation({
        mutationFn: async (id) => {
            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) => {
            toast({
                title: "Error",
                description: `Failed to delete appointment: ${error.message}`,
                variant: "destructive",
            });
        },
    });
    // Handle appointment submission (create or update)
    const handleAppointmentSubmit = (appointmentData) => {
        // 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,
            });
        }
        else {
            // This is a new appointment
            if (user) {
                createAppointmentMutation.mutate({
                    ...updatedData,
                    userId: user.id,
                });
            }
        }
    };
    // Handle edit appointment
    const handleEditAppointment = (appointment) => {
        setEditingAppointment(appointment);
        setIsAddModalOpen(true);
    };
    // When user confirms delete in dialog
    const handleConfirmDelete = () => {
        if (!confirmDeleteState.appointmentId)
            return;
        deleteAppointmentMutation.mutate(confirmDeleteState.appointmentId);
    };
    const handleDeleteAppointment = (id) => {
        setConfirmDeleteState({
            open: true,
            appointmentId: id,
        });
    };
    // Process appointments for the scheduler view
    const processedAppointments = (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.eligibilityStatus;
        const patientStatus = patient?.status ?? "UNKNOWN";
        const patientInsuranceProvider = patient?.insuranceProvider ?? 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.hasProcedures,
            hasClaimWithNumber: !!apt.hasClaimWithNumber,
            notes: apt.notes ?? null,
            procedureCodes: apt.procedureCodes ?? [],
            movedByAi: !!apt.movedByAi,
            staffId,
            status: apt.status ?? null,
            date: formatLocalDate(apt.date),
            startTime: normalizedStart,
            endTime: normalizedEnd,
        };
        return processed;
    });
    // Compute how many 15-min slots an appointment occupies
    const getSlotSpan = (apt) => {
        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) => {
        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();
    (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, staffId) => {
        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, staffId, patientId) => {
        const startHour = parseInt(timeSlot.time.split(":")[0]);
        const startMinute = parseInt(timeSlot.time.split(":")[1]);
        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, newTimeSlot, newStaffId) => {
        const appointment = appointments.find((a) => a.id === appointmentId);
        if (!appointment)
            return;
        // Preserve existing duration when moving
        const apt = appointment;
        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]);
        const startMinute = parseInt(newTimeSlot.time.split(":")[1]);
        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 = {
            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) => {
        const apt = appointments.find((a) => a.id === appointmentId);
        if (!apt)
            return;
        const patient = patientsFromDay.find((p) => p.id === apt.patientId);
        if (!patient)
            return;
        const processed = processedAppointments.find((a) => a.id === appointmentId);
        setChatPatient(patient);
        if (processed) {
            setChatAppointmentInfo({
                date: typeof processed.date === "string" ? processed.date : formatLocalDate(processed.date),
                startTime: typeof processed.startTime === "string" ? processed.startTime.substring(0, 5) : "",
            });
        }
        else {
            setChatAppointmentInfo(undefined);
        }
    };
    // Function to display context menu
    const handleContextMenu = (e, appointmentId) => {
        // 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, }) {
        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(null);
        const currentSpan = getDisplaySpan(appointment);
        const SLOT_HEIGHT = 57; // h-14 = 56px + 1px border
        const handleResizeStart = useCallback((e) => {
            e.stopPropagation();
            e.preventDefault();
            resizingRef.current = true;
            startYRef.current = e.clientY;
            originalSpanRef.current = currentSpan;
            const onMouseMove = (ev) => {
                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) => {
                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);
                    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} 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, }) {
        const blocked = !isWithinOfficeHours(timeSlot.time, staffIndex);
        const [{ isOver, canDrop }, drop] = useDrop(() => ({
            accept: ItemTypes.APPOINTMENT,
            drop: (item) => {
                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} 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) => {
        const apt = appointments.find((a) => a.id === appointmentId);
        const patient = apt ? patientsFromDay.find((p) => p.id === apt.patientId) : null;
        const insuranceProvider = patient?.insuranceProvider ?? null;
        const p = insuranceProvider?.toLowerCase() ?? "";
        const isMassHealth = p.includes("masshealth");
        let autoCheck = "other-providers";
        if (isMassHealth) {
            const dob = patient?.dateOfBirth;
            let age = null;
            if (dob) {
                const dobDate = typeof dob === "string" ? new Date(dob) : dob;
                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) => {
        setLocation(`/claims?appointmentId=${appointmentId}`);
    };
    const handleSelectProcedures = (appointmentId) => {
        const appt = appointments.find((a) => a.id === appointmentId);
        if (!appt)
            return;
        setSelectProceduresAppointmentId(appointmentId);
        setSelectProceduresPatientId(appt.patientId);
        setIsSelectProceduresOpen(true);
    };
    const handlePayments = (appointmentId) => {
        setLocation(`/payments?appointmentId=${appointmentId}`);
    };
    const handleChartPlan = (appointmentId) => {
        console.log(`Viewing chart/treatment plan for appointment: ${appointmentId}`);
    };
    const handleClinicNotes = (appointmentId) => {
        console.log(`Opening clinic notes for appointment: ${appointmentId}`);
    };
    const handleManualConfirm = async (appointmentId) => {
        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) {
            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;
            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 = Array.isArray(body?.results) ? body.results : [];
            // Map appointmentId -> appointment so we can show human friendly toasts
            const appointmentMap = new Map();
            for (const a of appointments) {
                if (a && typeof a.id === "number")
                    appointmentMap.set(a.id, 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) {
            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;
            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 = Array.isArray(body?.results) ? body.results : [];
            const appointmentMap = new Map();
            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) {
            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) {
            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) {
            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) {
            toast({ title: "Download failed", description: err?.message ?? String(err), variant: "destructive" });
        }
        finally {
            setIsDownloadingClaimPdfs(false);
        }
    };
    // ── AI Claim Queue ─────────────────────────────────────────────────────────
    const processAiClaimAtIndex = async (queue, index) => {
        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);
        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}`,
                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 = data.actionData.unknownPhrases ?? [];
                const inputs = {};
                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 = data.actionData.unknownPhrases ?? [];
                const inputs = {};
                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"];
            const dayName = dayNames[selectedDate.getDay()];
            const fmt = (t) => {
                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) => { }} 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>);
}
