/**
 * Reno System – Income Sources Module
 * Phases 1–8: Income tracking, sources, dashboard, reports, audit logs
 * Pattern: MUI + Axios + TypeScript (matches attoDeskApi codebase)
 *
 * MOBILE RESPONSIVENESS PASS
 * ---------------------------------------------------------------------------
 * This revision makes the module usable end-to-end on phone-width viewports
 * (< 600px). Summary of changes vs. the previous version:
 *
 *  1. Added `useMediaQuery` / `useTheme` to detect mobile viewports (isXs).
 *  2. Page header + search/add-income row now wrap instead of overflowing;
 *     the search field is full-width on mobile instead of a fixed 320px.
 *  3. Tabs are now `variant="scrollable"` with mobile scroll buttons so three
 *     tabs + icons never get clipped or squeezed.
 *  4. The Transactions table becomes a stacked card list on mobile (xs) and
 *     stays a normal dense table on sm+ — instead of forcing users to
 *     side-scroll through 8 columns on a phone.
 *  5. The Manage Sources table becomes a stacked card list on mobile too,
 *     with the same drag-and-drop reordering (dnd-kit's PointerSensor
 *     supports touch out of the box).
 *  6. Fixed pixel heights (table max-height, file viewer image/iframe) are
 *     now responsive (`{ xs: ..., sm: ... }`) instead of one fixed value
 *     that could dominate a short mobile viewport.
 *  7. Dialogs use `fullScreen` on mobile so forms with many fields (Add/Edit
 *     Income, Audit Log) don't feel cramped inside a small modal.
 *  8. Long text (filenames, notes) truncates responsively rather than with
 *     a fixed max-width that was tuned for desktop.
 *
 * (Original backend / migration / route notes retained below for reference.)
 *
 * CHANGE NOTE: Attachment field on the Income form now supports MULTIPLE files.
 * Backend impact:
 *   - `property_incomes` table: `attachment` (single varchar) should become a
 *     related `property_income_attachments` table (id, property_income_id FK,
 *     file_path, original_name, created_at) OR a JSON array column if you want
 *     to keep it simple. The sample below assumes a JSON array column named
 *     `attachments` for minimal migration effort:
 *
 *     php artisan make:migration add_attachments_to_property_incomes_table
 *     -> $table->json('attachments')->nullable()->after('attachment');
 *
 *   - PropertyIncomeController@store / update should loop over
 *     $request->file('attachments') (plural, array) and store each file,
 *     saving the resulting paths as a JSON array.
 *
 * =============================================================================
 * LARAVEL MIGRATION SUMMARY (run these in order)
 * =============================================================================
 *
 * 1. php artisan make:migration create_income_sources_table
 *    Fields: id, name, description, is_active (bool default true),
 *            sort_order (int default 0), created_at, updated_at
 *
 * 2. php artisan make:migration create_property_incomes_table
 *    Fields: id, property_id (FK), income_source_id (FK),
 *            tenant_id (nullable FK), booking_id (nullable),
 *            owner_id (nullable FK), amount (decimal 12,2),
 *            income_date (date), payment_method (varchar),
 *            reference_number (nullable varchar), description (text nullable),
 *            attachments (json nullable), status (enum: created,verified,cancelled default created),
 *            created_by (FK users), updated_by (FK users nullable),
 *            created_at, updated_at
 *
 * 3. php artisan make:migration create_income_audit_logs_table
 *    Fields: id, income_id (FK property_incomes), user_id (FK),
 *            action (varchar), previous_value (json nullable),
 *            new_value (json nullable), created_at, updated_at
 *
 * =============================================================================
 * LARAVEL API ROUTES (add to routes/api.php)
 * =============================================================================
 *
 * // Income Sources (admin managed)
 * Route::get('/income-sources',               [IncomeSourceController::class, 'index']);
 * Route::post('/income-sources',              [IncomeSourceController::class, 'store']);
 * Route::put('/income-sources/{id}',          [IncomeSourceController::class, 'update']);
 * Route::delete('/income-sources/{id}',       [IncomeSourceController::class, 'destroy']);
 * Route::post('/income-sources/reorder',      [IncomeSourceController::class, 'reorder']); // NEW
 *
 * // Property Incomes
 * Route::get('/property-incomes/{propertyId}',       [PropertyIncomeController::class, 'index']);
 * Route::post('/property-incomes',                   [PropertyIncomeController::class, 'store']);
 * Route::put('/property-incomes/{id}',               [PropertyIncomeController::class, 'update']);
 * Route::put('/property-incomes/{id}/verify',        [PropertyIncomeController::class, 'verify']);
 * Route::put('/property-incomes/{id}/cancel',        [PropertyIncomeController::class, 'cancel']);
 * Route::delete('/property-incomes/{id}',            [PropertyIncomeController::class, 'destroy']);
 *
 * // Reports
 * Route::get('/property-incomes/{propertyId}/report', [PropertyIncomeController::class, 'report']);
 * Route::get('/property-incomes/{propertyId}/export', [PropertyIncomeController::class, 'export']);
 *
 * // Audit logs
 * Route::get('/income-audit-logs/{incomeId}',        [IncomeAuditController::class, 'index']);
 *
 * =============================================================================
 * NEW LARAVEL REORDER METHOD (add to IncomeSourceController)
 * =============================================================================
 *
 * public function reorder(Request $request)
 * {
 *     foreach ($request->order as $item) {
 *         IncomeSource::where('id', $item['id'])->update(['sort_order' => $item['sort_order']]);
 *     }
 *     return response()->json(['success' => true]);
 * }
 *
 * =============================================================================
 * INSTALL DND KIT (run once)
 * =============================================================================
 *
 * npm install @dnd-kit/core @dnd-kit/sortable @dnd-kit/utilities
 *
 * =============================================================================
 * SEEDER (run in DatabaseSeeder or standalone)
 * =============================================================================
 *
 * $defaultSources = [
 *   'Rent Income','Short Term Rental Income','Room Rental Income',
 *   'Parking Income','Storage Income','Laundry Income',
 *   'Utility Recovery','Maintenance Recovery','Damage Recovery',
 *   'Cleaning Fee','Late Fee','Insurance Claim',
 *   'Government Rebate','Other Income',
 * ];
 * foreach ($defaultSources as $i => $name) {
 *   IncomeSource::create(['name'=>$name,'sort_order'=>$i,'is_active'=>true]);
 * }
 */

import React, { useEffect, useState, useMemo } from "react";
import {
  Grid, Card, CardContent, Typography, Box, Button, TextField,
  MenuItem, InputAdornment, Table, TableBody, TableCell,
  TableContainer, TableHead, TableRow, Dialog, DialogActions,
  DialogContent, DialogTitle, Chip, CircularProgress, Tabs, Tab,
  IconButton, Divider, Paper, Stack, useMediaQuery, useTheme,
} from "@mui/material";
import { useParams, useNavigate } from "react-router-dom";
import axios from "axios";
import { CiSearch } from "react-icons/ci";
import EditSharpIcon from "@mui/icons-material/EditSharp";
import DeleteOutlineSharpIcon from "@mui/icons-material/DeleteOutlineSharp";
import AddCircleOutlineIcon from "@mui/icons-material/AddCircleOutline";
import VerifiedIcon from "@mui/icons-material/Verified";
import CancelIcon from "@mui/icons-material/Cancel";
import HistoryIcon from "@mui/icons-material/History";
import FileDownloadIcon from "@mui/icons-material/FileDownload";
import AttachFileIcon from "@mui/icons-material/AttachFile";
import CloseIcon from "@mui/icons-material/Close";
import SaveAltIcon from "@mui/icons-material/SaveAlt";
import TrendingUpIcon from "@mui/icons-material/TrendingUp";
import AccountBalanceWalletIcon from "@mui/icons-material/AccountBalanceWallet";
import BarChartIcon from "@mui/icons-material/BarChart";
import SettingsIcon from "@mui/icons-material/Settings";
import DragIndicatorIcon from "@mui/icons-material/DragIndicator";
import Highcharts from "highcharts";
import HighchartsReact from "highcharts-react-official";
import {
  DndContext,
  closestCenter,
  KeyboardSensor,
  PointerSensor,
  useSensor,
  useSensors,
  DragEndEvent,
} from "@dnd-kit/core";
import {
  arrayMove,
  SortableContext,
  sortableKeyboardCoordinates,
  useSortable,
  verticalListSortingStrategy,
} from "@dnd-kit/sortable";
import { CSS } from "@dnd-kit/utilities";
import { useNotifier } from "../../Core/Notifier";

// ---------------------------------------------------------------------------
// Types
// ---------------------------------------------------------------------------

interface IncomeSource {
  id: number;
  name: string;
  description: string;
  is_active: boolean;
  sort_order: number;
}

interface PropertyIncome {
  id: number;
  property_id: number;
  income_source_id: number;
  income_source?: IncomeSource;
  tenant_id?: number;
  booking_id?: string;
  owner_id?: number;
  amount: number;
  income_date: string;
  payment_method: string;
  reference_number?: string;
  description?: string;
  // Multiple attachments. Kept `attachment` around for backward
  // compatibility with older records that only have a single file.
  attachment?: string;
  attachments?: string[];
  status: "created" | "verified" | "cancelled";
  created_by: number;
  created_at: string;
}

interface AuditLog {
  id: number;
  income_id: number;
  user_id: number;
  action: string;
  previous_value?: Record<string, unknown>;
  new_value?: Record<string, unknown>;
  created_at: string;
  username?: string;
  user?: { username: string };
}

interface ReportFilter {
  start_date: string;
  end_date: string;
  income_source_id: string;
  payment_method: string;
  status: string;
}

// ---------------------------------------------------------------------------
// Constants
// ---------------------------------------------------------------------------

const PAYMENT_METHODS = [
  "Cash", "Cheque", "Credit Card", "Debit Card",
  "E-Transfer", "Bank Transfer", "Online Banking",
  "Pre-Authorization", "Other",
];

const STATUS_COLORS: Record<string, "success" | "warning" | "error" | "default"> = {
  verified: "success",
  created: "warning",
  cancelled: "error",
};

// Guardrails for multi-file uploads
const MAX_ATTACHMENTS = 5;
const MAX_ATTACHMENT_SIZE_MB = 10;

const INCOME_FORM_INIT = {
  income_source_id: "",
  amount: "",
  income_date: new Date().toLocaleDateString("en-CA", { timeZone: "America/Toronto" }),
  payment_method: "",
  reference_number: "",
  description: "",
  attachments: [] as File[],
};

// ---------------------------------------------------------------------------
// Helpers
// ---------------------------------------------------------------------------

const fmtCurrency = (n: number | string) =>
  `$${Number(n).toLocaleString("en-CA", { minimumFractionDigits: 2, maximumFractionDigits: 2 })}`;

const today = () => new Date().toLocaleDateString("en-CA", { timeZone: "America/Toronto" });

const firstDayOfMonth = () => {
  const d = new Date();
  return new Date(d.getFullYear(), d.getMonth(), 1).toLocaleDateString("en-CA");
};

const fmtFileSize = (bytes: number) => {
  if (bytes < 1024) return `${bytes} B`;
  if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
  return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
};

// Normalize an income record's attachments into a single string[] regardless
// of whether the backend returns the legacy single `attachment` field or the
// new `attachments` array.
const getAttachmentList = (income: PropertyIncome): string[] => {
  if (income.attachments && income.attachments.length) return income.attachments;
  if (income.attachment) return [income.attachment];
  return [];
};

// ---------------------------------------------------------------------------
// SortableSourceRow – drag-and-drop row for Manage Sources tab (desktop table)
// ---------------------------------------------------------------------------

interface SortableSourceRowProps {
  source: IncomeSource;
  onEdit: (src: IncomeSource) => void;
  onToggleActive: (src: IncomeSource) => void;
  onDelete: (id: number) => void;
}

const SortableSourceRow: React.FC<SortableSourceRowProps> = ({
  source, onEdit, onToggleActive, onDelete,
}) => {
  const {
    attributes,
    listeners,
    setNodeRef,
    transform,
    transition,
    isDragging,
  } = useSortable({ id: source.id });

  const style: React.CSSProperties = {
    transform: CSS.Transform.toString(transform),
    transition,
    opacity: isDragging ? 0.5 : source.is_active ? 1 : 0.5,
    backgroundColor: isDragging ? "#f0f4ff" : undefined,
  };

  return (
    <TableRow ref={setNodeRef} style={style} hover>
      {/* Drag handle */}
      <TableCell sx={{ width: 40, pr: 0, pl: 1 }}>
        <Box
          {...attributes}
          {...listeners}
          sx={{
            cursor: isDragging ? "grabbing" : "grab",
            color: "text.disabled",
            display: "flex",
            alignItems: "center",
            justifyContent: "center",
            "&:hover": { color: "text.secondary" },
          }}
          title="Drag to reorder"
        >
          <DragIndicatorIcon fontSize="small" />
        </Box>
      </TableCell>
      <TableCell sx={{ fontWeight: 500 }}>{source.name}</TableCell>
      <TableCell sx={{ color: "text.secondary", fontSize: "0.8rem" }}>
        {source.description || "—"}
      </TableCell>
      <TableCell>
        <Chip
          label={source.is_active ? "Active" : "Inactive"}
          color={source.is_active ? "success" : "default"}
          size="small"
          sx={{ borderRadius: 1, fontSize: "0.7rem" }}
        />
      </TableCell>
      <TableCell>
        <Box display="flex" gap={0.5}>
          <IconButton size="small" onClick={() => onEdit(source)} title="Edit">
            <EditSharpIcon fontSize="small" />
          </IconButton>
          <Button
            size="small"
            variant="outlined"
            color={source.is_active ? "warning" : "success"}
            onClick={() => onToggleActive(source)}
            sx={{ fontSize: "0.7rem", py: 0.25, px: 1 }}
          >
            {source.is_active ? "Deactivate" : "Activate"}
          </Button>
          <IconButton
            size="small"
            color="error"
            onClick={() => onDelete(source.id)}
            title="Delete (only if no transactions)"
          >
            <DeleteOutlineSharpIcon fontSize="small" />
          </IconButton>
        </Box>
      </TableCell>
    </TableRow>
  );
};

// ---------------------------------------------------------------------------
// SortableSourceCard – drag-and-drop card for Manage Sources tab (mobile)
// ---------------------------------------------------------------------------

const SortableSourceCard: React.FC<SortableSourceRowProps> = ({
  source, onEdit, onToggleActive, onDelete,
}) => {
  const {
    attributes, listeners, setNodeRef, transform, transition, isDragging,
  } = useSortable({ id: source.id });

  const style: React.CSSProperties = {
    transform: CSS.Transform.toString(transform),
    transition,
    opacity: isDragging ? 0.6 : source.is_active ? 1 : 0.6,
  };

  return (
    <Paper
      ref={setNodeRef}
      style={style}
      variant="outlined"
      sx={{ p: 1.5, borderRadius: 2, backgroundColor: isDragging ? "#f0f4ff" : "background.paper" }}
    >
      <Box display="flex" alignItems="flex-start" gap={1}>
        <Box
          {...attributes}
          {...listeners}
          sx={{
            cursor: isDragging ? "grabbing" : "grab",
            color: "text.disabled",
            pt: 0.5,
            touchAction: "none",
          }}
          title="Drag to reorder"
        >
          <DragIndicatorIcon fontSize="small" />
        </Box>
        <Box flex={1} minWidth={0}>
          <Box display="flex" alignItems="center" justifyContent="space-between" gap={1}>
            <Typography variant="body2" fontWeight={600} noWrap>
              {source.name}
            </Typography>
            <Chip
              label={source.is_active ? "Active" : "Inactive"}
              color={source.is_active ? "success" : "default"}
              size="small"
              sx={{ borderRadius: 1, fontSize: "0.65rem", flexShrink: 0 }}
            />
          </Box>
          {source.description && (
            <Typography variant="caption" color="text.secondary" display="block" sx={{ mt: 0.25 }}>
              {source.description}
            </Typography>
          )}
          <Box display="flex" gap={0.5} mt={1} flexWrap="wrap">
            <Button
              size="small"
              variant="outlined"
              startIcon={<EditSharpIcon fontSize="small" />}
              onClick={() => onEdit(source)}
              sx={{ fontSize: "0.7rem", py: 0.25 }}
            >
              Edit
            </Button>
            <Button
              size="small"
              variant="outlined"
              color={source.is_active ? "warning" : "success"}
              onClick={() => onToggleActive(source)}
              sx={{ fontSize: "0.7rem", py: 0.25 }}
            >
              {source.is_active ? "Deactivate" : "Activate"}
            </Button>
            <IconButton size="small" color="error" onClick={() => onDelete(source.id)} title="Delete">
              <DeleteOutlineSharpIcon fontSize="small" />
            </IconButton>
          </Box>
        </Box>
      </Box>
    </Paper>
  );
};

// ---------------------------------------------------------------------------
// Main Component
// ---------------------------------------------------------------------------

const IncomeModule: React.FC = () => {
  const { propertyId } = useParams<{ propertyId: string }>();
  const navigate = useNavigate();
  const API_URL = process.env.REACT_APP_API_URL;
  const LARAVEL_API_URL = process.env.REACT_APP_LARAVEL_API;

  // Responsive helpers
  const theme = useTheme();
  const isXs = useMediaQuery(theme.breakpoints.down("sm")); // < 600px, phones
  const isSm = useMediaQuery(theme.breakpoints.down("md")); // < 900px, phones + small tablets

  // Tab state
  const [activeTab, setActiveTab] = useState(0);

  // Data state
  const [incomes, setIncomes] = useState<PropertyIncome[]>([]);
  const [sources, setSources] = useState<IncomeSource[]>([]);
  const [auditLogs, setAuditLogs] = useState<AuditLog[]>([]);
  const [loading, setLoading] = useState(false);

  // Search & filter
  const [searchQuery, setSearchQuery] = useState("");
  const [reportFilter, setReportFilter] = useState<ReportFilter>({
    start_date: firstDayOfMonth(),
    end_date: today(),
    income_source_id: "",
    payment_method: "",
    status: "",
  });

  // Add/Edit income dialog
  const [incomeDialogOpen, setIncomeDialogOpen] = useState(false);
  const [editingIncome, setEditingIncome] = useState<PropertyIncome | null>(null);
  const [incomeForm, setIncomeForm] = useState({ ...INCOME_FORM_INIT });
  const [formErrors, setFormErrors] = useState<Record<string, string>>({});
  // Existing attachments on the record being edited (kept separately from
  // newly-chosen files so the user can remove already-saved files too)
  const [existingAttachments, setExistingAttachments] = useState<string[]>([]);
  const [removedExistingAttachments, setRemovedExistingAttachments] = useState<string[]>([]);

  // Source management dialog — sort_order removed (managed by drag)
  const [sourceDialogOpen, setSourceDialogOpen] = useState(false);
  const [editingSource, setEditingSource] = useState<IncomeSource | null>(null);
  const [sourceForm, setSourceForm] = useState({ name: "", description: "" });

  // Audit log dialog
  const [auditDialogOpen, setAuditDialogOpen] = useState(false);
  const [auditIncomeId, setAuditIncomeId] = useState<number | null>(null);

  // File viewer
  const [fileViewerOpen, setFileViewerOpen] = useState(false);
  const [fileViewerUrl, setFileViewerUrl] = useState("");
  // Viewer supports paging through every attachment on a record, not just
  // the first one.
  const [fileViewerList, setFileViewerList] = useState<string[]>([]);
  const [fileViewerIndex, setFileViewerIndex] = useState(0);

  const { showErrorMessage: showError, showMessage } = useNotifier();

  // ---------------------------------------------------------------------------
  // DnD sensors
  // ---------------------------------------------------------------------------

  const sensors = useSensors(
    useSensor(PointerSensor, { activationConstraint: { distance: 4 } }),
    useSensor(KeyboardSensor, { coordinateGetter: sortableKeyboardCoordinates })
  );

  // ---------------------------------------------------------------------------
  // Fetch helpers
  // ---------------------------------------------------------------------------

  const fetchIncomes = async () => {
    setLoading(true);
    try {
      const res = await axios.get(`${API_URL}/property-incomes/${propertyId}`, {
        headers: { Authorization: `Bearer ${localStorage.getItem("token")}` },
      });
      setIncomes(res.data);
    } catch { showError("Failed to load income records."); }
    finally { setLoading(false); }
  };

  const fetchSources = async () => {
    try {
      const res = await axios.get(`${API_URL}/income-sources`, {
        headers: { Authorization: `Bearer ${localStorage.getItem("token")}` },
      });
      setSources(res.data);
    } catch { showError("Failed to load income sources."); }
  };

  const fetchAuditLogs = async (incomeId: number) => {
    try {
      const res = await axios.get(`${API_URL}/income-audit-logs/${incomeId}`, {
        headers: { Authorization: `Bearer ${localStorage.getItem("token")}` },
      });
      setAuditLogs(res.data);
    } catch { showError("Failed to load audit logs."); }
  };

  useEffect(() => {
    fetchIncomes();
    fetchSources();
  }, [propertyId]);

  // ---------------------------------------------------------------------------
  // Derived / computed data
  // ---------------------------------------------------------------------------

  const activeSources = useMemo(() => sources.filter((s) => s.is_active), [sources]);

  const sourceMap = useMemo(
    () => sources.reduce((m, s) => { m[s.id] = s.name; return m; }, {} as Record<number, string>),
    [sources]
  );

  const filteredIncomes = useMemo(() => {
    return incomes.filter((inc) => {
      const q = searchQuery.toLowerCase();
      return (
        (sourceMap[inc.income_source_id] || "").toLowerCase().includes(q) ||
        (inc.description || "").toLowerCase().includes(q) ||
        (inc.reference_number || "").toLowerCase().includes(q)
      );
    });
  }, [incomes, searchQuery, sourceMap]);

  const sortedFilteredIncomes = useMemo(
    () => [...filteredIncomes].sort(
      (a, b) => new Date(b.income_date).getTime() - new Date(a.income_date).getTime()
    ),
    [filteredIncomes]
  );

  // Dashboard metrics
  const totalIncome = useMemo(() =>
    incomes.filter(i => i.status !== "cancelled").reduce((s, i) => s + Number(i.amount), 0), [incomes]);

  const verifiedIncome = useMemo(() =>
    incomes.filter(i => i.status === "verified").reduce((s, i) => s + Number(i.amount), 0), [incomes]);

  const pendingIncome = useMemo(() =>
    incomes.filter(i => i.status === "created").reduce((s, i) => s + Number(i.amount), 0), [incomes]);

  // Income by source (for bar chart)
  const incomeBySource = useMemo(() => {
    const map: Record<string, number> = {};
    incomes.filter(i => i.status !== "cancelled").forEach(i => {
      const name = sourceMap[i.income_source_id] || "Unknown";
      map[name] = (map[name] || 0) + Number(i.amount);
    });
    return map;
  }, [incomes, sourceMap]);

  // Monthly trend (current year)
  const monthlyTrend = useMemo(() => {
    const arr = Array(12).fill(0);
    incomes.filter(i => i.status !== "cancelled").forEach(i => {
      const month = new Date(i.income_date).getMonth();
      arr[month] += Number(i.amount);
    });
    return arr.map(v => parseFloat(v.toFixed(2)));
  }, [incomes]);

  // ---------------------------------------------------------------------------
  // Drag-and-drop reorder handler
  // ---------------------------------------------------------------------------

  const handleDragEnd = async (event: DragEndEvent) => {
    const { active, over } = event;
    if (!over || active.id === over.id) return;

    const oldIndex = sources.findIndex((s) => s.id === active.id);
    const newIndex = sources.findIndex((s) => s.id === over.id);
    const reordered = arrayMove(sources, oldIndex, newIndex).map((s, i) => ({
      ...s,
      sort_order: i,
    }));

    setSources(reordered);

    try {
      await axios.post(
        `${API_URL}/income-sources/reorder`,
        { order: reordered.map((s) => ({ id: s.id, sort_order: s.sort_order })) },
        { headers: { Authorization: `Bearer ${localStorage.getItem("token")}` } }
      );
      showMessage("Source order updated successfully.");
    } catch {
      showError("Failed to save new order.");
      fetchSources();
    }
  };

  // ---------------------------------------------------------------------------
  // Income CRUD
  // ---------------------------------------------------------------------------

  const validateIncomeForm = () => {
    const errs: Record<string, string> = {};
    if (!incomeForm.income_source_id) errs.income_source_id = "Income source is required.";
    if (!incomeForm.amount || Number(incomeForm.amount) <= 0) errs.amount = "Valid amount is required.";
    if (!incomeForm.income_date) errs.income_date = "Date is required.";
    if (!incomeForm.payment_method) errs.payment_method = "Payment method is required.";
    setFormErrors(errs);
    return Object.keys(errs).length === 0;
  };

  // Handle selecting one or more files, merging with any already chosen,
  // enforcing a max count and per-file size limit.
  const handleAttachmentsSelected = (files: FileList | null) => {
    if (!files || files.length === 0) return;
    const incoming = Array.from(files);

    const oversized = incoming.filter((f) => f.size > MAX_ATTACHMENT_SIZE_MB * 1024 * 1024);
    if (oversized.length) {
      showError(`${oversized.length} file(s) exceed the ${MAX_ATTACHMENT_SIZE_MB}MB limit and were skipped.`);
    }

    const valid = incoming.filter((f) => f.size <= MAX_ATTACHMENT_SIZE_MB * 1024 * 1024);

    setIncomeForm((prev) => {
      const combined = [...prev.attachments, ...valid];
      const existingCount = existingAttachments.length - removedExistingAttachments.length;
      if (combined.length + existingCount > MAX_ATTACHMENTS) {
        showError(`You can attach up to ${MAX_ATTACHMENTS} files.`);
        return { ...prev, attachments: combined.slice(0, MAX_ATTACHMENTS - existingCount) };
      }
      return { ...prev, attachments: combined };
    });
  };

  const removeNewAttachment = (index: number) => {
    setIncomeForm((prev) => ({
      ...prev,
      attachments: prev.attachments.filter((_, i) => i !== index),
    }));
  };

  const removeExistingAttachment = (path: string) => {
    setRemovedExistingAttachments((prev) => [...prev, path]);
  };

  const handleSaveIncome = async () => {
    if (!validateIncomeForm()) return;

    const formData = new FormData();
    formData.append("property_id", propertyId || "");
    formData.append("income_source_id", incomeForm.income_source_id);
    formData.append("amount", incomeForm.amount);
    formData.append("income_date", incomeForm.income_date);
    formData.append("payment_method", incomeForm.payment_method);
    formData.append("reference_number", incomeForm.reference_number);
    formData.append("description", incomeForm.description);

    // Append every newly-selected file under the same array key so Laravel
    // receives them as $request->file('attachments') (an array).
    incomeForm.attachments.forEach((file) => {
      formData.append("attachments[]", file);
    });

    // Tell the backend which previously-saved attachments to remove.
    if (removedExistingAttachments.length) {
      removedExistingAttachments.forEach((path) => {
        formData.append("removed_attachments[]", path);
      });
    }

    try {
      if (editingIncome) {
        await axios.post(`${API_URL}/property-incomes/${editingIncome.id}?_method=PUT`, formData, {
          headers: {
            Authorization: `Bearer ${localStorage.getItem("token")}`,
            "Content-Type": "multipart/form-data",
          },
        });
        showMessage("Income updated successfully.");
      } else {
        await axios.post(`${API_URL}/property-incomes`, formData, {
          headers: {
            Authorization: `Bearer ${localStorage.getItem("token")}`,
            "Content-Type": "multipart/form-data",
          },
        });
        showMessage("Income recorded successfully.");
      }
      setIncomeDialogOpen(false);
      resetIncomeForm();
      fetchIncomes();
    } catch { showError("Failed to save income record."); }
  };

  const handleVerify = async (id: number) => {
    try {
      await axios.put(`${API_URL}/property-incomes/${id}/verify`, {}, {
        headers: { Authorization: `Bearer ${localStorage.getItem("token")}` },
      });
      showMessage("Income verified.");
      fetchIncomes();
    } catch { showError("Failed to verify income."); }
  };

  const handleCancel = async (id: number) => {
    if (!window.confirm("Cancel this income entry?")) return;
    try {
      await axios.put(`${API_URL}/property-incomes/${id}/cancel`, {}, {
        headers: { Authorization: `Bearer ${localStorage.getItem("token")}` },
      });
      showMessage("Income cancelled.");
      fetchIncomes();
    } catch { showError("Failed to cancel income."); }
  };

  const handleDelete = async (id: number) => {
    if (!window.confirm("Permanently delete this income record?")) return;
    try {
      await axios.delete(`${API_URL}/property-incomes/${id}`, {
        headers: { Authorization: `Bearer ${localStorage.getItem("token")}` },
      });
      showMessage("Deleted successfully.");
      fetchIncomes();
    } catch { showError("Failed to delete income."); }
  };

  const openAddIncome = () => {
    resetIncomeForm();
    setEditingIncome(null);
    setIncomeDialogOpen(true);
  };

  const openEditIncome = (income: PropertyIncome) => {
    setEditingIncome(income);
    setIncomeForm({
      income_source_id: String(income.income_source_id),
      amount: String(income.amount),
      income_date: income.income_date,
      payment_method: income.payment_method,
      reference_number: income.reference_number || "",
      description: income.description || "",
      attachments: [],
    });
    setExistingAttachments(getAttachmentList(income));
    setRemovedExistingAttachments([]);
    setIncomeDialogOpen(true);
  };

  const resetIncomeForm = () => {
    setIncomeForm({ ...INCOME_FORM_INIT });
    setFormErrors({});
    setExistingAttachments([]);
    setRemovedExistingAttachments([]);
  };

  // ---------------------------------------------------------------------------
  // Source management CRUD
  // ---------------------------------------------------------------------------

  const handleSaveSource = async () => {
    if (!sourceForm.name.trim()) { showError("Source name is required."); return; }
    try {
      if (editingSource) {
        await axios.put(
          `${API_URL}/income-sources/${editingSource.id}`,
          { ...sourceForm, is_active: editingSource.is_active, sort_order: editingSource.sort_order },
          { headers: { Authorization: `Bearer ${localStorage.getItem("token")}` } }
        );
        showMessage("Source updated.");
      } else {
        await axios.post(
          `${API_URL}/income-sources`,
          { ...sourceForm, is_active: true, sort_order: sources.length }, // append at end
          { headers: { Authorization: `Bearer ${localStorage.getItem("token")}` } }
        );
        showMessage("Source created.");
      }
      setSourceDialogOpen(false);
      setSourceForm({ name: "", description: "" });
      setEditingSource(null);
      fetchSources();
    } catch { showError("Failed to save source."); }
  };

  const handleToggleSourceActive = async (source: IncomeSource) => {
    try {
      await axios.put(
        `${API_URL}/income-sources/${source.id}`,
        { ...source, is_active: !source.is_active },
        { headers: { Authorization: `Bearer ${localStorage.getItem("token")}` } }
      );
      showMessage(source.is_active ? "Source deactivated." : "Source activated.");
      fetchSources();
    } catch { showError("Failed to update source."); }
  };

  const handleDeleteSource = async (id: number) => {
    if (!window.confirm("Delete this income source? This will fail if transactions exist.")) return;
    try {
      await axios.delete(`${API_URL}/income-sources/${id}`, {
        headers: { Authorization: `Bearer ${localStorage.getItem("token")}` },
      });
      showMessage("Source deleted.");
      fetchSources();
    } catch { showError("Cannot delete: transactions exist for this source."); }
  };

  // ---------------------------------------------------------------------------
  // Export
  // ---------------------------------------------------------------------------

  const handleExport = async () => {
    try {
      const params = new URLSearchParams(reportFilter as unknown as Record<string, string>);
      const res = await axios.get(
        `${API_URL}/property-incomes/${propertyId}/report?${params}`,
        {
          headers: { Authorization: `Bearer ${localStorage.getItem("token")}` },
          responseType: "blob",
        }
      );
      const url = window.URL.createObjectURL(new Blob([res.data]));
      const link = document.createElement("a");
      link.href = url;
      link.setAttribute("download", `income_report_property_${propertyId}.csv`);
      document.body.appendChild(link);
      link.click();
      link.remove();
    } catch { showError("Export failed."); }
  };

  // ---------------------------------------------------------------------------
  // Charts
  // ---------------------------------------------------------------------------

  const monthlyChartOptions = {
    chart: { type: "area", height: isXs ? 220 : 280 },
    title: { text: "Monthly Income Trend", style: { fontSize: isXs ? "14px" : "18px" } },
    xAxis: {
      categories: ["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"],
      labels: { style: { fontSize: isXs ? "9px" : "11px" } },
    },
    yAxis: { title: { text: "Income ($)" } },
    credits: { enabled: false },
    plotOptions: { area: { fillOpacity: 0.3 } },
    series: [{ name: "Income", data: monthlyTrend, color: "#4CAF50" }],
  };

  const sourceChartOptions = {
    chart: { type: "bar", height: isXs ? 220 : 280 },
    title: { text: "Income by Source", style: { fontSize: isXs ? "14px" : "18px" } },
    xAxis: {
      categories: Object.keys(incomeBySource),
      labels: { style: { fontSize: isXs ? "9px" : "11px" } },
    },
    yAxis: { title: { text: "Amount ($)" } },
    credits: { enabled: false },
    series: [{ name: "Total", data: Object.values(incomeBySource), color: "#3f51b5" }],
  };

  // ---------------------------------------------------------------------------
  // Render helpers
  // ---------------------------------------------------------------------------

  // Open the file viewer for a given attachment list, starting at
  // `startIndex`. Used everywhere attachments are clicked so the person can
  // page through every file on a record, not just the first.
  const openFileViewer = (paths: string[], startIndex = 0) => {
    if (!paths.length) return;
    setFileViewerList(paths);
    setFileViewerIndex(startIndex);
    setFileViewerUrl(`${LARAVEL_API_URL}/${paths[startIndex]}`);
    setFileViewerOpen(true);
  };

  const goToFileViewerIndex = (index: number) => {
    if (index < 0 || index >= fileViewerList.length) return;
    setFileViewerIndex(index);
    setFileViewerUrl(`${LARAVEL_API_URL}/${fileViewerList[index]}`);
  };

  const renderStatusChip = (status: string) => (
    <Chip
      label={status.charAt(0).toUpperCase() + status.slice(1)}
      color={STATUS_COLORS[status] || "default"}
      size="small"
      sx={{ borderRadius: 1, fontWeight: 600, fontSize: "0.7rem" }}
    />
  );

  // Visible existing attachments (those the user hasn't marked for removal)
  const visibleExistingAttachments = existingAttachments.filter(
    (path) => !removedExistingAttachments.includes(path)
  );
  const attachmentSlotsUsed = visibleExistingAttachments.length + incomeForm.attachments.length;
  const attachmentSlotsRemaining = Math.max(0, MAX_ATTACHMENTS - attachmentSlotsUsed);

  // Shared action buttons for a single income record — used by both the
  // desktop table row and the mobile card so behaviour stays in sync.
  const renderIncomeActions = (inc: PropertyIncome, attachmentList: string[]) => (
    <Box display="flex" gap={0.5} flexWrap="wrap">
      {attachmentList.length > 0 && (
        attachmentList.length === 1 ? (
          <IconButton size="small" color="info" onClick={() => openFileViewer(attachmentList, 0)} title="View attachment">
            <AttachFileIcon fontSize="small" />
          </IconButton>
        ) : (
          <Chip
            icon={<AttachFileIcon fontSize="small" />}
            label={attachmentList.length}
            size="small"
            clickable
            color="info"
            variant="outlined"
            onClick={() => openFileViewer(attachmentList, 0)}
            title={`${attachmentList.length} attachments — click to browse`}
            sx={{ fontSize: "0.7rem" }}
          />
        )
      )}
      {inc.status === "created" && (
        <IconButton size="small" color="success" onClick={() => handleVerify(inc.id)} title="Verify">
          <VerifiedIcon fontSize="small" />
        </IconButton>
      )}
      {inc.status !== "cancelled" && (
        <IconButton size="small" color="primary" onClick={() => openEditIncome(inc)} title="Edit">
          <EditSharpIcon fontSize="small" />
        </IconButton>
      )}
      {inc.status === "created" && (
        <IconButton size="small" color="warning" onClick={() => handleCancel(inc.id)} title="Cancel">
          <CancelIcon fontSize="small" />
        </IconButton>
      )}
      <IconButton
        size="small"
        color="inherit"
        onClick={() => {
          setAuditIncomeId(inc.id);
          fetchAuditLogs(inc.id);
          setAuditDialogOpen(true);
        }}
        title="Audit log"
      >
        <HistoryIcon fontSize="small" />
      </IconButton>
      <IconButton size="small" color="error" onClick={() => handleDelete(inc.id)} title="Delete">
        <DeleteOutlineSharpIcon fontSize="small" />
      </IconButton>
    </Box>
  );

  // ---------------------------------------------------------------------------
  // JSX
  // ---------------------------------------------------------------------------

  return (
    <>
      {/* ── Page Header ─────────────────────────────────────────────────────── */}
      <Grid container spacing={3} sx={{ mt: 1, mb: 2 }}>
        <Grid item xs={12}>
          <Box
            display="flex"
            alignItems={{ xs: "stretch", sm: "center" }}
            justifyContent="space-between"
            flexDirection={{ xs: "column", sm: "row" }}
            gap={1.5}
          >
            <Typography variant="h5" fontWeight={700}>
              Income Management
            </Typography>
            <Button
              variant="contained"
              startIcon={<AddCircleOutlineIcon />}
              onClick={openAddIncome}
              fullWidth={isXs}
              sx={{ borderRadius: 2 }}
            >
              Add Income
            </Button>
          </Box>
        </Grid>
      </Grid>

      {/* ── KPI Cards ───────────────────────────────────────────────────────── */}
      <Grid container spacing={{ xs: 2, sm: 3 }} sx={{ mb: 3 }}>
        {[
          { label: "Total Income", value: totalIncome, icon: <AccountBalanceWalletIcon />, color: "#4CAF50" },
          { label: "Verified", value: verifiedIncome, icon: <VerifiedIcon />, color: "#2196F3" },
          { label: "Pending Verification", value: pendingIncome, icon: <TrendingUpIcon />, color: "#FF9800" },
        ].map((kpi) => (
          <Grid item xs={12} sm={4} key={kpi.label}>
            <Card elevation={3}>
              <CardContent>
                <Box display="flex" alignItems="center" gap={1} mb={1}>
                  <Box sx={{ color: kpi.color }}>{kpi.icon}</Box>
                  <Typography variant="body2" color="textSecondary">{kpi.label}</Typography>
                </Box>
                <Typography variant="h4" fontWeight={700} sx={{ color: kpi.color }}>
                  {fmtCurrency(kpi.value)}
                </Typography>
              </CardContent>
            </Card>
          </Grid>
        ))}
      </Grid>

      {/* ── Charts ──────────────────────────────────────────────────────────── */}
      <Grid container spacing={{ xs: 2, sm: 3 }} sx={{ mb: 3 }}>
        <Grid item xs={12} md={6}>
          <Card>
            <CardContent sx={{ px: { xs: 1, sm: 2 } }}>
              <HighchartsReact highcharts={Highcharts} options={monthlyChartOptions} />
            </CardContent>
          </Card>
        </Grid>
        <Grid item xs={12} md={6}>
          <Card>
            <CardContent sx={{ px: { xs: 1, sm: 2 } }}>
              <HighchartsReact highcharts={Highcharts} options={sourceChartOptions} />
            </CardContent>
          </Card>
        </Grid>
      </Grid>

      {/* ── Tabs: Transactions | Reports | Sources ───────────────────────────── */}
      <Card elevation={2}>
        <Tabs
          value={activeTab}
          onChange={(_, v) => setActiveTab(v)}
          variant="scrollable"
          scrollButtons="auto"
          allowScrollButtonsMobile
          sx={{ borderBottom: 1, borderColor: "divider", px: { xs: 0.5, sm: 2 }, pt: 1 }}
        >
          <Tab label="Transactions" icon={<AccountBalanceWalletIcon fontSize="small" />} iconPosition="start" sx={{ minHeight: 44 }} />
          <Tab label="Reports" icon={<BarChartIcon fontSize="small" />} iconPosition="start" sx={{ minHeight: 44 }} />
          <Tab label="Manage Sources" icon={<SettingsIcon fontSize="small" />} iconPosition="start" sx={{ minHeight: 44 }} />
        </Tabs>

        {/* ── Tab 0: Transactions ─────────────────────────────────────────── */}
        {activeTab === 0 && (
          <Box p={{ xs: 1.5, sm: 2 }}>
            <Box
              display="flex"
              flexDirection={{ xs: "column", sm: "row" }}
              justifyContent="space-between"
              alignItems={{ xs: "stretch", sm: "center" }}
              gap={1.5}
              mb={2}
            >
              <TextField
                size="small"
                placeholder="Search by source, notes, reference..."
                value={searchQuery}
                onChange={(e) => setSearchQuery(e.target.value)}
                InputProps={{
                  startAdornment: (
                    <InputAdornment position="start">
                      <CiSearch />
                    </InputAdornment>
                  ),
                }}
                sx={{ width: { xs: "100%", sm: 320 } }}
              />
              <Button
                variant="contained"
                startIcon={<AddCircleOutlineIcon />}
                onClick={openAddIncome}
                size="small"
                fullWidth={isXs}
              >
                Add Income
              </Button>
            </Box>

            {loading ? (
              <Box display="flex" justifyContent="center" py={4}>
                <CircularProgress size={24} />
              </Box>
            ) : sortedFilteredIncomes.length === 0 ? (
              <Box sx={{ textAlign: "center", color: "text.secondary", py: 4 }}>
                No income records found. Click "Add Income" to get started.
              </Box>
            ) : isXs ? (
              // ── Mobile: stacked cards instead of an 8-column table ───────
              <Stack spacing={1.5}>
                {sortedFilteredIncomes.map((inc) => {
                  const attachmentList = getAttachmentList(inc);
                  return (
                    <Paper key={inc.id} variant="outlined" sx={{ p: 1.5, borderRadius: 2 }}>
                      <Box display="flex" justifyContent="space-between" alignItems="flex-start" gap={1}>
                        <Box minWidth={0}>
                          <Chip
                            label={sourceMap[inc.income_source_id] || "Unknown"}
                            size="small"
                            variant="outlined"
                            sx={{ fontSize: "0.7rem", mb: 0.5 }}
                          />
                          <Typography variant="caption" color="text.secondary" display="block">
                            {inc.income_date} · {inc.payment_method}
                          </Typography>
                        </Box>
                        <Box sx={{ textAlign: "right", flexShrink: 0 }}>
                          <Typography variant="subtitle1" fontWeight={700} sx={{ color: "#2e7d32" }}>
                            {fmtCurrency(inc.amount)}
                          </Typography>
                          {renderStatusChip(inc.status)}
                        </Box>
                      </Box>

                      {inc.reference_number && (
                        <Typography variant="caption" color="text.secondary" display="block" mt={0.5}>
                          Ref: {inc.reference_number}
                        </Typography>
                      )}
                      {inc.description && (
                        <Typography variant="body2" mt={0.5} sx={{ overflowWrap: "break-word" }}>
                          {inc.description}
                        </Typography>
                      )}

                      <Divider sx={{ my: 1 }} />
                      {renderIncomeActions(inc, attachmentList)}
                    </Paper>
                  );
                })}
              </Stack>
            ) : (
              // ── Tablet / desktop: dense table with horizontal scroll ─────
              <TableContainer sx={{ maxHeight: { sm: 420, md: 480 } }}>
                <Table size="small" stickyHeader>
                  <TableHead>
                    <TableRow>
                      {["Date","Source","Amount","Payment Method","Reference","Notes","Status","Actions"].map((h) => (
                        <TableCell key={h} sx={{ fontWeight: 700, backgroundColor: "#f5f5f5" }}>{h}</TableCell>
                      ))}
                    </TableRow>
                  </TableHead>
                  <TableBody>
                    {sortedFilteredIncomes.map((inc) => {
                      const attachmentList = getAttachmentList(inc);
                      return (
                        <TableRow key={inc.id} hover>
                          <TableCell>{inc.income_date}</TableCell>
                          <TableCell>
                            <Chip
                              label={sourceMap[inc.income_source_id] || "Unknown"}
                              size="small"
                              variant="outlined"
                              sx={{ fontSize: "0.7rem" }}
                            />
                          </TableCell>
                          <TableCell sx={{ fontWeight: 600, color: "#2e7d32" }}>
                            {fmtCurrency(inc.amount)}
                          </TableCell>
                          <TableCell>{inc.payment_method}</TableCell>
                          <TableCell sx={{ fontSize: "0.75rem", color: "text.secondary" }}>
                            {inc.reference_number || "—"}
                          </TableCell>
                          <TableCell sx={{ maxWidth: { sm: 120, md: 180 }, overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" }}>
                            {inc.description || "—"}
                          </TableCell>
                          <TableCell>{renderStatusChip(inc.status)}</TableCell>
                          <TableCell>{renderIncomeActions(inc, attachmentList)}</TableCell>
                        </TableRow>
                      );
                    })}
                  </TableBody>
                </Table>
              </TableContainer>
            )}

            {/* Summary row */}
            {sortedFilteredIncomes.length > 0 && (
              <Box
                display="flex"
                flexDirection={{ xs: "column", sm: "row" }}
                justifyContent="flex-end"
                alignItems={{ xs: "flex-start", sm: "center" }}
                gap={{ xs: 0.25, sm: 0 }}
                mt={1.5}
                px={1}
              >
                <Typography variant="body2" color="textSecondary" mr={{ xs: 0, sm: 2 }}>
                  {sortedFilteredIncomes.length} records
                </Typography>
                <Typography variant="body2" fontWeight={700}>
                  Total: {fmtCurrency(
                    sortedFilteredIncomes.filter(i => i.status !== "cancelled")
                      .reduce((s, i) => s + Number(i.amount), 0)
                  )}
                </Typography>
              </Box>
            )}
          </Box>
        )}

        {/* ── Tab 1: Reports ─────────────────────────────────────────────── */}
        {activeTab === 1 && (
          <Box p={{ xs: 1.5, sm: 2 }}>
            <Typography variant="subtitle1" fontWeight={600} mb={2}>Filter Report</Typography>
            <Grid container spacing={2} mb={3}>
              <Grid item xs={12} sm={6} md={2}>
                <TextField
                  fullWidth size="small" label="Start Date" type="date"
                  InputLabelProps={{ shrink: true }}
                  value={reportFilter.start_date}
                  onChange={(e) => setReportFilter({ ...reportFilter, start_date: e.target.value })}
                />
              </Grid>
              <Grid item xs={12} sm={6} md={2}>
                <TextField
                  fullWidth size="small" label="End Date" type="date"
                  InputLabelProps={{ shrink: true }}
                  value={reportFilter.end_date}
                  onChange={(e) => setReportFilter({ ...reportFilter, end_date: e.target.value })}
                />
              </Grid>
              <Grid item xs={12} sm={6} md={2}>
                <TextField
                  select fullWidth size="small" label="Income Source"
                  value={reportFilter.income_source_id}
                  onChange={(e) => setReportFilter({ ...reportFilter, income_source_id: e.target.value })}
                >
                  <MenuItem value="">All Sources</MenuItem>
                  {sources.map((s) => <MenuItem key={s.id} value={s.id}>{s.name}</MenuItem>)}
                </TextField>
              </Grid>
              <Grid item xs={12} sm={6} md={2}>
                <TextField
                  select fullWidth size="small" label="Payment Method"
                  value={reportFilter.payment_method}
                  onChange={(e) => setReportFilter({ ...reportFilter, payment_method: e.target.value })}
                >
                  <MenuItem value="">All Methods</MenuItem>
                  {PAYMENT_METHODS.map((m) => <MenuItem key={m} value={m}>{m}</MenuItem>)}
                </TextField>
              </Grid>
              <Grid item xs={12} sm={6} md={2}>
                <TextField
                  select fullWidth size="small" label="Status"
                  value={reportFilter.status}
                  onChange={(e) => setReportFilter({ ...reportFilter, status: e.target.value })}
                >
                  <MenuItem value="">All Status</MenuItem>
                  <MenuItem value="created">Created</MenuItem>
                  <MenuItem value="verified">Verified</MenuItem>
                  <MenuItem value="cancelled">Cancelled</MenuItem>
                </TextField>
              </Grid>
              <Grid item xs={12} sm={6} md={2}>
                <Button
                  fullWidth variant="contained" startIcon={<FileDownloadIcon />}
                  onClick={handleExport} sx={{ height: "40px" }}
                >
                  Export CSV
                </Button>
              </Grid>
            </Grid>

            {/* Income breakdown by source */}
            <Typography variant="subtitle1" fontWeight={600} mb={1}>Income Breakdown by Source</Typography>
            <TableContainer component={Paper} variant="outlined" sx={{ overflowX: "auto" }}>
              <Table size="small">
                <TableHead>
                  <TableRow>
                    <TableCell sx={{ fontWeight: 700, backgroundColor: "#f5f5f5" }}>Income Source</TableCell>
                    <TableCell sx={{ fontWeight: 700, backgroundColor: "#f5f5f5" }}>Count</TableCell>
                    <TableCell sx={{ fontWeight: 700, backgroundColor: "#f5f5f5" }}>Total Amount</TableCell>
                    <TableCell sx={{ fontWeight: 700, backgroundColor: "#f5f5f5" }}>% of Total</TableCell>
                  </TableRow>
                </TableHead>
                <TableBody>
                  {Object.entries(incomeBySource)
                    .sort(([, a], [, b]) => b - a)
                    .map(([sourceName, amount]) => {
                      const count = incomes.filter(
                        i => sourceMap[i.income_source_id] === sourceName && i.status !== "cancelled"
                      ).length;
                      const pct = totalIncome > 0 ? ((amount / totalIncome) * 100).toFixed(1) : "0.0";
                      return (
                        <TableRow key={sourceName} hover>
                          <TableCell>{sourceName}</TableCell>
                          <TableCell>{count}</TableCell>
                          <TableCell sx={{ fontWeight: 600, color: "#2e7d32" }}>{fmtCurrency(amount)}</TableCell>
                          <TableCell>
                            <Box display="flex" alignItems="center" gap={1}>
                              <Box
                                sx={{
                                  height: 6, borderRadius: 3, backgroundColor: "#4CAF50",
                                  width: `${Math.min(Number(pct), 100)}%`, minWidth: 4, maxWidth: 100,
                                }}
                              />
                              <Typography variant="caption">{pct}%</Typography>
                            </Box>
                          </TableCell>
                        </TableRow>
                      );
                    })}
                  <TableRow>
                    <TableCell colSpan={2} sx={{ fontWeight: 700 }}>Grand Total</TableCell>
                    <TableCell sx={{ fontWeight: 700, color: "#2e7d32" }}>{fmtCurrency(totalIncome)}</TableCell>
                    <TableCell sx={{ fontWeight: 700 }}>100%</TableCell>
                  </TableRow>
                </TableBody>
              </Table>
            </TableContainer>
          </Box>
        )}

        {/* ── Tab 2: Manage Sources (with drag-and-drop) ──────────────────── */}
        {activeTab === 2 && (
          <Box p={{ xs: 1.5, sm: 2 }}>
            <Box display="flex" justifyContent="space-between" alignItems="flex-start" mb={2} gap={1.5} flexWrap="wrap">
              <Typography variant="subtitle1" fontWeight={600}>Income Sources</Typography>
              <Button
                variant="contained"
                size="small"
                startIcon={<AddCircleOutlineIcon />}
                fullWidth={isXs}
                onClick={() => {
                  setEditingSource(null);
                  setSourceForm({ name: "", description: "" });
                  setSourceDialogOpen(true);
                }}
              >
                Add Source
              </Button>
            </Box>

            {isXs ? (
              // ── Mobile: stacked draggable cards instead of a wide table ──
              <DndContext sensors={sensors} collisionDetection={closestCenter} onDragEnd={handleDragEnd}>
                <SortableContext items={sources.map((s) => s.id)} strategy={verticalListSortingStrategy}>
                  <Stack spacing={1}>
                    {[...sources]
                      .sort((a, b) => a.sort_order - b.sort_order)
                      .map((src) => (
                        <SortableSourceCard
                          key={src.id}
                          source={src}
                          onEdit={(s) => {
                            setEditingSource(s);
                            setSourceForm({ name: s.name, description: s.description });
                            setSourceDialogOpen(true);
                          }}
                          onToggleActive={handleToggleSourceActive}
                          onDelete={handleDeleteSource}
                        />
                      ))}
                  </Stack>
                </SortableContext>
              </DndContext>
            ) : (
              <TableContainer component={Paper} variant="outlined" sx={{ overflowX: "auto" }}>
                <Table size="small">
                  <TableHead>
                    <TableRow>
                      <TableCell sx={{ fontWeight: 700, backgroundColor: "#f5f5f5", width: 40 }} />
                      {["Name", "Description", "Status", "Actions"].map((h) => (
                        <TableCell key={h} sx={{ fontWeight: 700, backgroundColor: "#f5f5f5" }}>{h}</TableCell>
                      ))}
                    </TableRow>
                  </TableHead>
                  <TableBody>
                    <DndContext sensors={sensors} collisionDetection={closestCenter} onDragEnd={handleDragEnd}>
                      <SortableContext items={sources.map((s) => s.id)} strategy={verticalListSortingStrategy}>
                        {[...sources]
                          .sort((a, b) => a.sort_order - b.sort_order)
                          .map((src) => (
                            <SortableSourceRow
                              key={src.id}
                              source={src}
                              onEdit={(s) => {
                                setEditingSource(s);
                                setSourceForm({ name: s.name, description: s.description });
                                setSourceDialogOpen(true);
                              }}
                              onToggleActive={handleToggleSourceActive}
                              onDelete={handleDeleteSource}
                            />
                          ))}
                      </SortableContext>
                    </DndContext>
                  </TableBody>
                </Table>
              </TableContainer>
            )}
          </Box>
        )}
      </Card>

      {/* ────────────────────────────────────────────────────────────────────── */}
      {/* Add / Edit Income Dialog                                              */}
      {/* ────────────────────────────────────────────────────────────────────── */}
      <Dialog
        open={incomeDialogOpen}
        onClose={() => setIncomeDialogOpen(false)}
        maxWidth="sm"
        fullWidth
        fullScreen={isXs}
      >
        <DialogTitle>
          {editingIncome ? "Edit Income Record" : "Add Income Record"}
          <IconButton
            onClick={() => setIncomeDialogOpen(false)}
            sx={{ position: "absolute", right: 8, top: 8 }}
          >
            <CloseIcon />
          </IconButton>
        </DialogTitle>
        <DialogContent dividers>
          <Grid container spacing={2} mt={0.5}>
            <Grid item xs={12}>
              <TextField
                select fullWidth size="small" label="Income Source *"
                value={incomeForm.income_source_id}
                onChange={(e) => setIncomeForm({ ...incomeForm, income_source_id: e.target.value })}
                error={!!formErrors.income_source_id}
                helperText={formErrors.income_source_id}
              >
                <MenuItem value="">Select income source</MenuItem>
                {activeSources
                  .sort((a, b) => a.sort_order - b.sort_order)
                  .map((s) => <MenuItem key={s.id} value={s.id}>{s.name}</MenuItem>)}
              </TextField>
            </Grid>

            <Grid item xs={12} sm={6}>
              <TextField
                fullWidth size="small" label="Amount *" type="number"
                value={incomeForm.amount}
                onChange={(e) => setIncomeForm({ ...incomeForm, amount: e.target.value })}
                error={!!formErrors.amount}
                helperText={formErrors.amount}
                InputProps={{ startAdornment: <InputAdornment position="start">$</InputAdornment> }}
              />
            </Grid>

            <Grid item xs={12} sm={6}>
              <TextField
                fullWidth size="small" label="Income Date *" type="date"
                InputLabelProps={{ shrink: true }}
                value={incomeForm.income_date}
                onChange={(e) => setIncomeForm({ ...incomeForm, income_date: e.target.value })}
                error={!!formErrors.income_date}
                helperText={formErrors.income_date}
              />
            </Grid>

            <Grid item xs={12} sm={6}>
              <TextField
                select fullWidth size="small" label="Payment Method *"
                value={incomeForm.payment_method}
                onChange={(e) => setIncomeForm({ ...incomeForm, payment_method: e.target.value })}
                error={!!formErrors.payment_method}
                helperText={formErrors.payment_method}
              >
                <MenuItem value="">Select method</MenuItem>
                {PAYMENT_METHODS.map((m) => <MenuItem key={m} value={m}>{m}</MenuItem>)}
              </TextField>
            </Grid>

            <Grid item xs={12} sm={6}>
              <TextField
                fullWidth size="small" label="Reference Number"
                value={incomeForm.reference_number}
                onChange={(e) => setIncomeForm({ ...incomeForm, reference_number: e.target.value })}
                placeholder="Invoice #, Booking ID, Cheque #..."
              />
            </Grid>

            <Grid item xs={12}>
              <TextField
                fullWidth size="small" label="Reason / Notes"
                multiline rows={3}
                value={incomeForm.description}
                onChange={(e) => setIncomeForm({ ...incomeForm, description: e.target.value })}
                placeholder="Additional details about this income..."
              />
            </Grid>

            {/* ── Attachments (multi-file) ─────────────────────────────────── */}
            <Grid item xs={12}>
              <Box
                display="flex"
                flexDirection={{ xs: "column", sm: "row" }}
                alignItems={{ xs: "flex-start", sm: "center" }}
                justifyContent="space-between"
                gap={0.5}
                mb={0.5}
              >
                <Typography variant="caption" color="textSecondary" display="block">
                  Attachments (Invoice, Receipt, Booking Confirmation)
                </Typography>
                <Typography variant="caption" color="textSecondary">
                  {attachmentSlotsUsed}/{MAX_ATTACHMENTS} files
                </Typography>
              </Box>

              <Button
                variant="outlined"
                component="label"
                startIcon={<AttachFileIcon />}
                size="small"
                fullWidth={isXs}
                disabled={attachmentSlotsRemaining === 0}
              >
                Choose Files
                <input
                  type="file"
                  hidden
                  multiple
                  accept="image/*,application/pdf"
                  onChange={(e) => {
                    handleAttachmentsSelected(e.target.files);
                    // allow re-selecting the same file again later
                    e.target.value = "";
                  }}
                />
              </Button>
              {attachmentSlotsRemaining === 0 && (
                <Typography variant="caption" color="error" display="block" mt={0.5}>
                  Maximum of {MAX_ATTACHMENTS} attachments reached.
                </Typography>
              )}

              {/* Existing attachments (edit mode) */}
              {visibleExistingAttachments.length > 0 && (
                <Stack spacing={0.5} mt={1}>
                  {visibleExistingAttachments.map((path) => (
                    <Box
                      key={path}
                      display="flex" alignItems="center" justifyContent="space-between"
                      sx={{ px: 1, py: 0.5, borderRadius: 1, backgroundColor: "#f5f5f5", gap: 1 }}
                    >
                      <Box
                        display="flex" alignItems="center" gap={0.5}
                        sx={{ cursor: "pointer", overflow: "hidden", minWidth: 0 }}
                        onClick={() => openFileViewer(visibleExistingAttachments, visibleExistingAttachments.indexOf(path))}
                      >
                        <AttachFileIcon fontSize="small" sx={{ color: "text.secondary", flexShrink: 0 }} />
                        <Typography variant="caption" noWrap sx={{ maxWidth: { xs: 180, sm: 260 } }}>
                          {path.split("/").pop()}
                        </Typography>
                      </Box>
                      <IconButton size="small" onClick={() => removeExistingAttachment(path)} title="Remove">
                        <CloseIcon fontSize="small" />
                      </IconButton>
                    </Box>
                  ))}
                </Stack>
              )}

              {/* Newly selected files (not yet uploaded) */}
              {incomeForm.attachments.length > 0 && (
                <Stack spacing={0.5} mt={1}>
                  {incomeForm.attachments.map((file, idx) => (
                    <Box
                      key={`${file.name}-${idx}`}
                      display="flex" alignItems="center" justifyContent="space-between"
                      sx={{ px: 1, py: 0.5, borderRadius: 1, backgroundColor: "#e8f5e9", gap: 1 }}
                    >
                      <Box display="flex" alignItems="center" gap={0.5} sx={{ overflow: "hidden", minWidth: 0 }}>
                        <AttachFileIcon fontSize="small" sx={{ color: "#2e7d32", flexShrink: 0 }} />
                        <Typography variant="caption" noWrap sx={{ maxWidth: { xs: 140, sm: 220 } }}>
                          {file.name}
                        </Typography>
                        <Typography variant="caption" color="textSecondary" sx={{ flexShrink: 0 }}>
                          ({fmtFileSize(file.size)})
                        </Typography>
                      </Box>
                      <IconButton size="small" onClick={() => removeNewAttachment(idx)} title="Remove">
                        <CloseIcon fontSize="small" />
                      </IconButton>
                    </Box>
                  ))}
                </Stack>
              )}
            </Grid>
          </Grid>
        </DialogContent>
        <DialogActions sx={{ px: 3, py: 2, gap: 1, flexDirection: { xs: "column-reverse", sm: "row" } }}>
          <Button
            variant="outlined"
            startIcon={<CloseIcon />}
            onClick={() => setIncomeDialogOpen(false)}
            fullWidth={isXs}
          >
            Cancel
          </Button>
          <Button
            variant="contained"
            startIcon={<SaveAltIcon />}
            onClick={handleSaveIncome}
            fullWidth={isXs}
          >
            {editingIncome ? "Update" : "Save Income"}
          </Button>
        </DialogActions>
      </Dialog>

      {/* ────────────────────────────────────────────────────────────────────── */}
      {/* Source Dialog (sort_order field removed)                              */}
      {/* ────────────────────────────────────────────────────────────────────── */}
      <Dialog open={sourceDialogOpen} onClose={() => setSourceDialogOpen(false)} maxWidth="xs" fullWidth>
        <DialogTitle>
          {editingSource ? "Edit Income Source" : "Add Income Source"}
          <IconButton onClick={() => setSourceDialogOpen(false)} sx={{ position: "absolute", right: 8, top: 8 }}>
            <CloseIcon />
          </IconButton>
        </DialogTitle>
        <DialogContent dividers>
          <Grid container spacing={2} mt={0.5}>
            <Grid item xs={12}>
              <TextField
                fullWidth size="small" label="Source Name *"
                value={sourceForm.name}
                onChange={(e) => setSourceForm({ ...sourceForm, name: e.target.value })}
              />
            </Grid>
            <Grid item xs={12}>
              <TextField
                fullWidth size="small" label="Description"
                multiline rows={2}
                value={sourceForm.description}
                onChange={(e) => setSourceForm({ ...sourceForm, description: e.target.value })}
              />
            </Grid>
          </Grid>
        </DialogContent>
        <DialogActions sx={{ px: 3, py: 2, gap: 1, flexDirection: { xs: "column-reverse", sm: "row" } }}>
          <Button variant="outlined" startIcon={<CloseIcon />} onClick={() => setSourceDialogOpen(false)} fullWidth={isXs}>Cancel</Button>
          <Button variant="contained" startIcon={<SaveAltIcon />} onClick={handleSaveSource} fullWidth={isXs}>Save</Button>
        </DialogActions>
      </Dialog>

      {/* ────────────────────────────────────────────────────────────────────── */}
      {/* Audit Log Dialog                                                       */}
      {/* ────────────────────────────────────────────────────────────────────── */}
      <Dialog
        open={auditDialogOpen}
        onClose={() => setAuditDialogOpen(false)}
        maxWidth="md"
        fullWidth
        fullScreen={isXs}
      >
        <DialogTitle sx={{ pb: 1 }}>
          <Box display="flex" alignItems="center" gap={1} flexWrap="wrap">
            <HistoryIcon sx={{ color: "#3f51b5" }} />
            <Typography variant="h6" fontWeight={700}>Audit Log</Typography>
            <Chip
              label={(() => {
                const inc = incomes.find(i => i.id === auditIncomeId);
                return inc ? sourceMap[inc.income_source_id] : `Income #${auditIncomeId}`;
              })()}
              size="small"
              sx={{ ml: 1, backgroundColor: "#e8eaf6", color: "#3f51b5", fontWeight: 600 }}
            />
          </Box>
          <IconButton onClick={() => setAuditDialogOpen(false)} sx={{ position: "absolute", right: 8, top: 8 }}>
            <CloseIcon />
          </IconButton>
        </DialogTitle>

        <Divider />

        <DialogContent sx={{ p: { xs: 1.5, sm: 3 }, backgroundColor: "#f8f9fa" }}>
          {auditLogs.length === 0 ? (
            <Box display="flex" flexDirection="column" alignItems="center" py={5} gap={1}>
              <HistoryIcon sx={{ fontSize: 48, color: "#ccc" }} />
              <Typography color="textSecondary">No audit log entries found.</Typography>
            </Box>
          ) : (
            <Box sx={{ position: "relative" }}>
              <Box sx={{
                position: "absolute", left: 19, top: 0, bottom: 0,
                width: 2, backgroundColor: "#e0e0e0", zIndex: 0,
                display: { xs: "none", sm: "block" },
              }} />

              <Stack spacing={2}>
                {auditLogs.map((log) => {
                  const actionColors: Record<string, { bg: string; color: string; border: string }> = {
                    created:  { bg: "#e8f5e9", color: "#2e7d32", border: "#a5d6a7" },
                    verified: { bg: "#e3f2fd", color: "#1565c0", border: "#90caf9" },
                    updated:  { bg: "#fff8e1", color: "#e65100", border: "#ffcc80" },
                    cancelled:{ bg: "#fce4ec", color: "#c62828", border: "#ef9a9a" },
                  };
                  const colors = actionColors[log.action] || { bg: "#f3e5f5", color: "#6a1b9a", border: "#ce93d8" };

                  let prevVal: Record<string, unknown> | null = null;
                  let newVal: Record<string, unknown> | null = null;
                  try { prevVal = typeof log.previous_value === "string" ? JSON.parse(log.previous_value) : log.previous_value; } catch {}
                  try { newVal = typeof log.new_value === "string" ? JSON.parse(log.new_value) : log.new_value; } catch {}

                  const excludeFields = ["created_at", "updated_at", "updated_by", "_method", "property_id", "income_source_id"];
                  const friendlyLabel: Record<string, string> = {
                    property_id:       "Property",
                    income_source:     "Income Source",
                    income_source_id:  "Income Source",
                    amount:            "Amount",
                    income_date:       "Date",
                    payment_method:    "Payment Method",
                    reference_number:  "Reference",
                    description:       "Notes",
                    status:            "Status",
                    attachment:        "Attachment",
                    attachments:       "Attachments",
                  };

                  return (
                    <Box key={log.id} sx={{ display: "flex", gap: { xs: 1, sm: 2 }, position: "relative", zIndex: 1 }}>
                      <Box sx={{
                        width: { xs: 32, sm: 40 }, height: { xs: 32, sm: 40 }, borderRadius: "50%", flexShrink: 0,
                        backgroundColor: colors.bg, border: `2px solid ${colors.border}`,
                        display: "flex", alignItems: "center", justifyContent: "center",
                      }}>
                        {log.action === "created"   && <AddCircleOutlineIcon sx={{ fontSize: 18, color: colors.color }} />}
                        {log.action === "verified"  && <VerifiedIcon sx={{ fontSize: 18, color: colors.color }} />}
                        {log.action === "updated"   && <EditSharpIcon sx={{ fontSize: 18, color: colors.color }} />}
                        {log.action === "cancelled" && <CancelIcon sx={{ fontSize: 18, color: colors.color }} />}
                        {!["created","verified","updated","cancelled"].includes(log.action) &&
                          <HistoryIcon sx={{ fontSize: 18, color: colors.color }} />}
                      </Box>

                      <Paper elevation={1} sx={{
                        flex: 1, borderRadius: 2, overflow: "hidden", minWidth: 0,
                        border: `1px solid ${colors.border}`,
                      }}>
                        <Box sx={{
                          px: { xs: 1.5, sm: 2 }, py: 1, backgroundColor: colors.bg,
                          display: "flex", justifyContent: "space-between", alignItems: { xs: "flex-start", sm: "center" },
                          flexDirection: { xs: "column", sm: "row" }, gap: 0.5,
                        }}>
                          <Box display="flex" alignItems="center" gap={1}>
                            <Chip
                              label={log.action.toUpperCase()}
                              size="small"
                              sx={{ backgroundColor: colors.color, color: "#fff", fontWeight: 700, fontSize: "0.65rem", height: 20 }}
                            />
                            <Typography variant="body2" fontWeight={600} sx={{ color: colors.color }}>
                              {log.action === "created"   && "Record Created"}
                              {log.action === "verified"  && "Income Verified"}
                              {log.action === "updated"   && "Record Updated"}
                              {log.action === "cancelled" && "Income Cancelled"}
                              {!["created","verified","updated","cancelled"].includes(log.action) && "Action Performed"}
                            </Typography>
                          </Box>
                          <Box display="flex" alignItems="center" gap={1} flexWrap="wrap">
                            <Typography variant="caption" color="textSecondary">
                              👤 {log.username || `User #${log.user_id}`}
                            </Typography>
                            <Typography variant="caption" color="textSecondary" sx={{ display: { xs: "none", sm: "inline" } }}>·</Typography>
                            <Typography variant="caption" color="textSecondary">
                              🕐 {new Date(log.created_at + "Z").toLocaleString()}
                            </Typography>
                          </Box>
                        </Box>

                        <Box sx={{ px: { xs: 1.5, sm: 2 }, py: 1.5 }}>
                          {log.action === "created" && newVal && (
                            <Grid container spacing={1}>
                              {Object.entries(newVal)
                                .filter(([k]) => !excludeFields.includes(k))
                                .map(([key, val]) => (
                                  <Grid item xs={12} sm={6} key={key}>
                                    <Box display="flex" gap={0.5}>
                                      <Typography variant="caption" color="textSecondary" sx={{ minWidth: 110 }}>
                                        {friendlyLabel[key] || key}:
                                      </Typography>
                                      <Typography variant="caption" fontWeight={600}>
                                        {key === "amount"
                                          ? `$${Number(val).toFixed(2)}`
                                          : key === "attachments" && Array.isArray(val)
                                            ? `${val.length} file(s)`
                                            : String(val || "—")}
                                      </Typography>
                                    </Box>
                                  </Grid>
                                ))}
                            </Grid>
                          )}

                          {(log.action === "verified" || log.action === "cancelled") && (
                            <Box display="flex" alignItems="center" gap={2} flexWrap="wrap">
                              <Box sx={{ px: 1.5, py: 0.5, borderRadius: 1, backgroundColor: "#fce4ec" }}>
                                <Typography variant="caption" fontWeight={600} color="#c62828">
                                  {prevVal?.status ? String(prevVal.status).toUpperCase() : "—"}
                                </Typography>
                              </Box>
                              <Typography variant="caption" color="textSecondary">→</Typography>
                              <Box sx={{ px: 1.5, py: 0.5, borderRadius: 1, backgroundColor: colors.bg }}>
                                <Typography variant="caption" fontWeight={600} sx={{ color: colors.color }}>
                                  {newVal?.status ? String(newVal.status).toUpperCase() : "—"}
                                </Typography>
                              </Box>
                            </Box>
                          )}

                          {log.action === "updated" && prevVal && newVal && (
                            <Grid container spacing={1}>
                              {Object.entries(newVal)
                                .filter(([k]) => !excludeFields.includes(k) && String(newVal?.[k]) !== String(prevVal?.[k]))
                                .map(([key, val]) => (
                                  <Grid item xs={12} key={key}>
                                    <Box display="flex" alignItems="center" gap={1} flexWrap="wrap">
                                      <Typography variant="caption" color="textSecondary" sx={{ minWidth: 110 }}>
                                        {friendlyLabel[key] || key}:
                                      </Typography>
                                      <Box sx={{ px: 1, py: 0.25, borderRadius: 1, backgroundColor: "#fce4ec" }}>
                                        <Typography variant="caption" sx={{ color: "#c62828", textDecoration: "line-through" }}>
                                          {String(prevVal?.[key] || "—")}
                                        </Typography>
                                      </Box>
                                      <Typography variant="caption">→</Typography>
                                      <Box sx={{ px: 1, py: 0.25, borderRadius: 1, backgroundColor: "#e8f5e9" }}>
                                        <Typography variant="caption" sx={{ color: "#2e7d32", fontWeight: 600 }}>
                                          {String(val || "—")}
                                        </Typography>
                                      </Box>
                                    </Box>
                                  </Grid>
                                ))}
                            </Grid>
                          )}
                        </Box>
                      </Paper>
                    </Box>
                  );
                })}
              </Stack>
            </Box>
          )}
        </DialogContent>

        <Divider />
        <DialogActions sx={{ px: 3, py: 2 }}>
          <Typography variant="caption" color="textSecondary" sx={{ flex: 1 }}>
            {auditLogs.length} {auditLogs.length === 1 ? "entry" : "entries"} total
          </Typography>
          <Button variant="outlined" onClick={() => setAuditDialogOpen(false)}>Close</Button>
        </DialogActions>
      </Dialog>

      {/* ────────────────────────────────────────────────────────────────────── */}
      {/* File Viewer Dialog                                                     */}
      {/* ────────────────────────────────────────────────────────────────────── */}
      <Dialog
        open={fileViewerOpen}
        onClose={() => setFileViewerOpen(false)}
        maxWidth="md"
        fullWidth
        fullScreen={isXs}
      >
        <DialogTitle>
          <Box display="flex" alignItems="center" gap={1}>
            <span>Attachment</span>
            {fileViewerList.length > 1 && (
              <Chip
                size="small"
                label={`${fileViewerIndex + 1} of ${fileViewerList.length}`}
                sx={{ fontSize: "0.7rem" }}
              />
            )}
          </Box>
          <IconButton onClick={() => setFileViewerOpen(false)} sx={{ position: "absolute", right: 8, top: 8 }}>
            <CloseIcon />
          </IconButton>
        </DialogTitle>
        <DialogContent>
          <Box sx={{ position: "relative" }}>
            {fileViewerList.length > 1 && (
              <IconButton
                onClick={() => goToFileViewerIndex(fileViewerIndex - 1)}
                disabled={fileViewerIndex === 0}
                sx={{
                  position: "absolute", left: 4, top: "50%", transform: "translateY(-50%)",
                  zIndex: 1, backgroundColor: "rgba(255,255,255,0.85)",
                  "&:hover": { backgroundColor: "rgba(255,255,255,1)" },
                }}
                title="Previous attachment"
              >
                ‹
              </IconButton>
            )}

            {fileViewerUrl.toLowerCase().endsWith(".pdf") ? (
              <iframe
                src={fileViewerUrl}
                title="PDF"
                style={{ width: "100%", height: isXs ? "70vh" : 500, border: "none" }}
              />
            ) : (
              <img
                src={fileViewerUrl}
                alt="Attachment"
                style={{ width: "100%", maxHeight: isXs ? "60vh" : 500, objectFit: "contain" }}
              />
            )}

            {fileViewerList.length > 1 && (
              <IconButton
                onClick={() => goToFileViewerIndex(fileViewerIndex + 1)}
                disabled={fileViewerIndex === fileViewerList.length - 1}
                sx={{
                  position: "absolute", right: 4, top: "50%", transform: "translateY(-50%)",
                  zIndex: 1, backgroundColor: "rgba(255,255,255,0.85)",
                  "&:hover": { backgroundColor: "rgba(255,255,255,1)" },
                }}
                title="Next attachment"
              >
                ›
              </IconButton>
            )}
          </Box>

          {/* Thumbnail strip for quick jumping between files */}
          {fileViewerList.length > 1 && (
            <Box display="flex" gap={1} mt={1.5} sx={{ overflowX: "auto", pb: 0.5 }}>
              {fileViewerList.map((path, idx) => (
                <Chip
                  key={path}
                  size="small"
                  variant={idx === fileViewerIndex ? "filled" : "outlined"}
                  color={idx === fileViewerIndex ? "primary" : "default"}
                  icon={<AttachFileIcon fontSize="small" />}
                  label={path.split("/").pop()}
                  onClick={() => goToFileViewerIndex(idx)}
                  sx={{ fontSize: "0.65rem", flexShrink: 0, maxWidth: 200 }}
                />
              ))}
            </Box>
          )}
        </DialogContent>
        <DialogActions sx={{ px: 3, py: 2 }}>
          <Button variant="outlined" onClick={() => setFileViewerOpen(false)} fullWidth={isXs}>Close</Button>
        </DialogActions>
      </Dialog>
    </>
  );
};

export default IncomeModule;