"use client";

import { useState, useEffect, useCallback, useMemo, useRef } from "react";
import { createPortal } from "react-dom";
import { formatDate, formatMoney } from "~/lib/format.js";
import type { Project, ColumnDef } from "@capakraken/shared";
import { ProjectStatus, PROJECT_COLUMNS, BlueprintTarget } from "@capakraken/shared";
import Link from "next/link";
import Image from "next/image";
import { clsx } from "clsx";
import { motion } from "framer-motion";
import { trpc } from "~/lib/trpc/client.js";
import { generateCsv, downloadCsv } from "~/lib/csv-export.js";
import { ProjectModal } from "~/components/projects/ProjectModal.js";
import { ProjectWizard } from "~/components/projects/ProjectWizard.js";
import { useSelection } from "~/hooks/useSelection.js";
import { BatchActionBar } from "~/components/ui/BatchActionBar.js";
import { ConfirmDialog } from "~/components/ui/ConfirmDialog.js";
import { FilterBar } from "~/components/ui/FilterBar.js";
import { FilterChips } from "~/components/ui/FilterChips.js";
import { ColumnTogglePanel } from "~/components/ui/ColumnTogglePanel.js";
import { InfiniteScrollSentinel } from "~/components/ui/InfiniteScrollSentinel.js";
import { usePermissions } from "~/hooks/usePermissions.js";
import { useColumnConfig } from "~/hooks/useColumnConfig.js";
import { useTableSort } from "~/hooks/useTableSort.js";
import { useViewPrefs } from "~/hooks/useViewPrefs.js";
import { useRowOrder } from "~/hooks/useRowOrder.js";
import { SortableColumnHeader } from "~/components/ui/SortableColumnHeader.js";
import { DraggableTableRow } from "~/components/ui/DraggableTableRow.js";
import { ShoringBadge } from "~/components/projects/ShoringIndicator.js";
import { SuccessToast } from "~/components/ui/SuccessToast.js";

import { PROJECT_STATUS_BADGE as STATUS_COLORS, ORDER_TYPE_BADGE as ORDER_TYPE_COLORS } from "~/lib/status-styles.js";

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

const ALL_STATUSES = [
  { value: "DRAFT", label: "Draft" },
  { value: "ACTIVE", label: "Active" },
  { value: "ON_HOLD", label: "On Hold" },
  { value: "COMPLETED", label: "Completed" },
  { value: "CANCELLED", label: "Cancelled" },
] as const;

const ALL_ORDER_TYPES = [
  { value: "BD", label: "BD" },
  { value: "CHARGEABLE", label: "Chargeable" },
  { value: "INTERNAL", label: "Internal" },
  { value: "OVERHEAD", label: "Overhead" },
] as const;

// ─── Sub-components ───────────────────────────────────────────────────────────

function BudgetBar({ utilizationPercent, budgetCents }: { utilizationPercent: number; budgetCents: number }) {
  if (budgetCents === 0) {
    return <div className="text-xs text-gray-400">No budget</div>;
  }
  const cappedPercent = Math.min(utilizationPercent, 100);
  let barColor = "bg-green-500";
  if (utilizationPercent > 95) barColor = "bg-red-500";
  else if (utilizationPercent > 85) barColor = "bg-orange-500";
  else if (utilizationPercent > 70) barColor = "bg-yellow-500";

  return (
    <div className="min-w-[104px] space-y-1">
      <div className="h-2 w-full overflow-hidden rounded-full bg-gray-200/80 dark:bg-gray-700/80">
        <div className={clsx("h-full rounded-full transition-all duration-700 ease-out", barColor)} style={{ width: `${cappedPercent}%` }} />
      </div>
      <div className="text-xs text-gray-500">{utilizationPercent.toFixed(0)}% used</div>
    </div>
  );
}

function StatusDropdown({ project, isOpen, onOpen, onClose }: { project: ProjectRow; isOpen: boolean; onOpen: () => void; onClose: () => void }) {
  const utils = trpc.useUtils();
  const triggerRef = useRef<HTMLButtonElement>(null);
  const panelRef = useRef<HTMLDivElement>(null);
  const [pos, setPos] = useState({ top: 0, left: 0 });

  const updateStatus = trpc.project.updateStatus.useMutation({
    onSuccess: async () => {
      await utils.project.listWithCosts.invalidate();
      onClose();
    },
  });

  // Position the portal dropdown below the trigger button
  useEffect(() => {
    if (!isOpen || !triggerRef.current) return;
    const rect = triggerRef.current.getBoundingClientRect();
    setPos({ top: rect.bottom + 4, left: rect.left });
  }, [isOpen]);

  useEffect(() => {
    if (!isOpen) return;
    function handleOutsideClick(e: MouseEvent) {
      const target = e.target as Node;
      if (triggerRef.current?.contains(target)) return;
      if (panelRef.current?.contains(target)) return;
      onClose();
    }
    document.addEventListener("mousedown", handleOutsideClick);
    return () => document.removeEventListener("mousedown", handleOutsideClick);
  }, [isOpen, onClose]);

  return (
    <>
      <button
        ref={triggerRef}
        type="button"
        onClick={(e) => { e.stopPropagation(); isOpen ? onClose() : onOpen(); }}
        className={clsx(
          "inline-flex cursor-pointer items-center gap-1 rounded-full border px-2.5 py-1 text-xs font-medium transition",
          STATUS_COLORS[project.status] ?? "bg-gray-100 text-gray-700",
        )}
        title="Click to change status"
      >
        {project.status}
        <svg className="w-2.5 h-2.5 opacity-60" fill="none" stroke="currentColor" viewBox="0 0 24 24">
          <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M19 9l-7 7-7-7" />
        </svg>
      </button>
      {isOpen && createPortal(
        <motion.div
          ref={panelRef}
          initial={{ opacity: 0, scaleY: 0.9 }}
          animate={{ opacity: 1, scaleY: 1 }}
          transition={{ duration: 0.12, ease: "easeOut" }}
          className="fixed z-[9999] min-w-[160px] rounded-2xl border border-gray-200 bg-white p-2 shadow-xl dark:border-gray-700 dark:bg-gray-900 origin-top"
          style={{ top: pos.top, left: pos.left }}
        >
          {ALL_STATUSES.map((s) => (
            <button
              key={s.value}
              type="button"
              disabled={s.value === project.status || updateStatus.isPending}
              onClick={(e) => { e.stopPropagation(); updateStatus.mutate({ id: project.id, status: s.value as never }); }}
              className={clsx(
                "w-full rounded-xl px-3 py-2 text-left text-xs transition",
                s.value === project.status
                  ? "cursor-default font-semibold text-gray-400"
                  : "cursor-pointer text-gray-700 hover:bg-gray-50 dark:text-gray-200 dark:hover:bg-gray-800",
              )}
            >
              <span className={clsx("inline-block px-1.5 py-0.5 rounded-full", STATUS_COLORS[s.value] ?? "bg-gray-100 text-gray-700")}>
                {s.label}
              </span>
            </button>
          ))}
        </motion.div>,
        document.body,
      )}
    </>
  );
}

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

interface ProjectRow {
  id: string;
  shortCode: string;
  name: string;
  status: string;
  orderType: string;
  startDate: string | Date;
  endDate: string | Date;
  budgetCents: number;
  winProbability: number;
  totalCostCents: number;
  totalPersonDays: number;
  utilizationPercent: number;
  dynamicFields?: Record<string, unknown> | null;
  coverImageUrl?: string | null;
  color?: string | null;
}

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

export function ProjectsClient() {
  const [search, setSearch] = useState("");
  const [statusFilter, setStatusFilter] = useState<string>("");
  const [orderTypeFilter, setOrderTypeFilter] = useState<string>("");
  const [modalOpen, setModalOpen] = useState(false);
  const [wizardOpen, setWizardOpen] = useState(false);
  const [editingProject, setEditingProject] = useState<Project | null>(null);
  const [successToast, setSuccessToast] = useState<string | null>(null);
  const [openStatusProjectId, setOpenStatusProjectId] = useState<string | null>(null);
  const [batchStatusPicker, setBatchStatusPicker] = useState(false);
  const [confirmBatchStatus, setConfirmBatchStatus] = useState<{ ids: string[]; status: string } | null>(null);
  const [confirmBatchDelete, setConfirmBatchDelete] = useState<string[] | null>(null);

  const selection = useSelection();
  const utils = trpc.useUtils();
  const { canViewCosts } = usePermissions();

  const batchUpdateStatus = trpc.project.batchUpdateStatus.useMutation({
    onSuccess: async () => {
      await utils.project.listWithCosts.invalidate();
      selection.clear();
    },
  });

  const batchDeleteMutation = trpc.project.batchDelete.useMutation({
    onSuccess: async () => {
      await utils.project.listWithCosts.invalidate();
      selection.clear();
    },
  });

  // ─── Favorites ──────────────────────────────────────────────────────────
  const { data: favoriteIds } = trpc.user.getFavoriteProjectIds.useQuery(undefined, { staleTime: 30_000 });
  const favSet = useMemo(() => new Set(favoriteIds ?? []), [favoriteIds]);
  const toggleFavMutation = trpc.user.toggleFavoriteProject.useMutation({
    onMutate: async ({ projectId }) => {
      // Cancel any outgoing refetches so they don't overwrite optimistic update
      await utils.user.getFavoriteProjectIds.cancel();
      // Snapshot previous value
      const previous = utils.user.getFavoriteProjectIds.getData();
      // Optimistically update the cache
      const current = previous ?? [];
      const next = current.includes(projectId)
        ? current.filter((id: string) => id !== projectId)
        : [...current, projectId];
      utils.user.getFavoriteProjectIds.setData(undefined, next);
      return { previous };
    },
    onError: (_err, _vars, context) => {
      // Rollback on error
      if (context?.previous !== undefined) {
        utils.user.getFavoriteProjectIds.setData(undefined, context.previous);
      }
    },
    onSettled: () => {
      void utils.user.getFavoriteProjectIds.invalidate();
    },
  });

  // ─── Custom field columns from global blueprints ──────────────────────────
  const { data: globalFieldDefs } = trpc.blueprint.getGlobalFieldDefs.useQuery(
    { target: BlueprintTarget.PROJECT },
    { staleTime: 300_000 },
  );
  const customColumns = useMemo<ColumnDef[]>(
    () =>
      (globalFieldDefs ?? [])
        .filter((f) => f.showInList)
        .map((f) => ({
          key: `custom_${f.key}`,
          label: f.label,
          defaultVisible: false,
          hideable: true,
          isCustom: true,
          fieldType: f.type as string,
        })),
    [globalFieldDefs],
  );

  // ─── Column visibility ────────────────────────────────────────────────────
  // Filter out budget column if user cannot view costs
  const baseColumns = useMemo<ColumnDef[]>(
    () => (canViewCosts ? PROJECT_COLUMNS : PROJECT_COLUMNS.filter((c) => c.key !== "budget")),
    [canViewCosts],
  );

  const { allColumns, visibleColumns, visibleKeys, setVisible } = useColumnConfig(
    "projects",
    baseColumns,
    customColumns,
  );
  const defaultKeys = useMemo(
    () => baseColumns.filter((c) => c.defaultVisible).map((c) => c.key),
    [baseColumns],
  );

  // ─── Infinite query (cursor-based) ────────────────────────────────────────
  const {
    data,
    isLoading,
    isFetchingNextPage,
    fetchNextPage,
    hasNextPage,
  // Keep this boundary shallow; full TRPC inference here can trip TS depth limits.
  } = (trpc.project.listWithCosts.useInfiniteQuery as any)(
    {
      search: search || undefined,
      status: (statusFilter as ProjectStatus) || undefined,
      limit: 50,
    },
    {
      getNextPageParam: (lastPage: { nextCursor?: string | null }) => lastPage.nextCursor ?? undefined,
      initialCursor: undefined,
      placeholderData: (prev: { pages: { projects: ProjectRow[]; nextCursor?: string | null }[] } | undefined) => prev,
      staleTime: 15_000,
    },
  ) as {
    data:
      | {
          pages: { projects: ProjectRow[]; nextCursor?: string | null }[];
        }
      | undefined;
    isLoading: boolean;
    isFetchingNextPage: boolean;
    fetchNextPage: () => Promise<unknown>;
    hasNextPage: boolean | undefined;
  };

  const allProjects = useMemo(
    () => (data?.pages.flatMap((p) => p.projects) ?? []) as unknown as ProjectRow[],
    [data],
  );

  // Client-side orderType filter
  const filteredProjects = useMemo(
    () => (orderTypeFilter ? allProjects.filter((p) => p.orderType === orderTypeFilter) : allProjects),
    [allProjects, orderTypeFilter],
  );

  // ─── Sort + row order ─────────────────────────────────────────────────────
  const viewPrefs = useViewPrefs("projects");
  const { sorted, sortField, sortDir, toggle, reset } = useTableSort(filteredProjects, {
    initialField: viewPrefs.savedSort?.field ?? null,
    initialDir: viewPrefs.savedSort?.dir ?? null,
    onSortChange: (field, dir) => {
      viewPrefs.setSavedSort(field && dir ? { field, dir } : null);
    },
  });
  const { orderedRows: projects, reorder, isCustomOrder, resetOrder } = useRowOrder(
    sorted,
    viewPrefs,
    sortField,
    reset,
  );
  const rowDragRef = useRef<string | null>(null);

  const projectIds = projects.map((p) => p.id);

  useEffect(() => {
    selection.clear();
  // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [search, statusFilter, orderTypeFilter]);

  const handleFetchNext = useCallback(() => {
    if (hasNextPage && !isFetchingNextPage) void fetchNextPage();
  }, [hasNextPage, isFetchingNextPage, fetchNextPage]);

  function openNewModal() { setEditingProject(null); setModalOpen(true); }
  function openEditModal(project: Project) { setEditingProject(project); setModalOpen(true); }
  function closeModal() { setModalOpen(false); setEditingProject(null); }
  function clearAll() { setSearch(""); setStatusFilter(""); setOrderTypeFilter(""); }

  const exportSelectedCsv = useCallback(() => {
    const selected = projects.filter((p) => selection.selectedIds.has(p.id));
    if (selected.length === 0) return;
    const csv = generateCsv(selected, [
      { header: "Short Code", accessor: (p) => p.shortCode },
      { header: "Name", accessor: (p) => p.name },
      { header: "Status", accessor: (p) => p.status },
      { header: "Order Type", accessor: (p) => p.orderType },
      { header: "Start Date", accessor: (p) => formatDate(p.startDate) },
      { header: "End Date", accessor: (p) => formatDate(p.endDate) },
      { header: "Budget (cents)", accessor: (p) => p.budgetCents },
      { header: "Win Probability", accessor: (p) => p.winProbability },
      { header: "Total Cost (cents)", accessor: (p) => p.totalCostCents },
      { header: "Person Days", accessor: (p) => p.totalPersonDays },
      { header: "Utilization %", accessor: (p) => p.utilizationPercent },
    ]);
    downloadCsv(csv, `projects-export-${new Date().toISOString().slice(0, 10)}.csv`);
  }, [projects, selection.selectedIds]);

  const chips = [
    ...(search ? [{ label: `Search: "${search}"`, onRemove: () => setSearch("") }] : []),
    ...(statusFilter ? [{ label: `Status: ${statusFilter}`, onRemove: () => setStatusFilter("") }] : []),
    ...(orderTypeFilter ? [{ label: `Type: ${orderTypeFilter}`, onRemove: () => setOrderTypeFilter("") }] : []),
  ];

  // ─── Cell renderer ────────────────────────────────────────────────────────
  function renderCell(col: ColumnDef, project: ProjectRow) {
    const dynFields = (project.dynamicFields ?? {}) as Record<string, unknown>;

    if (col.isCustom) {
      const fieldKey = col.key.replace(/^custom_/, "");
      const val = dynFields[fieldKey];
      return <td key={col.key} className="px-4 py-3 text-sm text-gray-600 dark:text-gray-300">{val != null ? String(val) : "—"}</td>;
    }

    switch (col.key) {
      case "shortCode":
        return <td key={col.key} className="px-4 py-3 text-sm font-mono font-medium text-gray-900 dark:text-gray-100">{project.shortCode}</td>;
      case "name":
        return (
          <td key={col.key} className="max-w-xs truncate px-4 py-3 text-sm font-medium text-gray-900 dark:text-gray-100">
            <Link href={`/projects/${project.id}`} className="inline-flex items-center gap-2 transition hover:text-brand-600 hover:underline">
              {project.coverImageUrl ? (
                <Image src={project.coverImageUrl} alt={project.name} width={24} height={24} className="h-6 w-6 flex-shrink-0 rounded object-cover" unoptimized={project.coverImageUrl.startsWith("data:")} />
              ) : (
                <span
                  className="flex h-6 w-6 flex-shrink-0 items-center justify-center rounded text-[9px] font-bold opacity-60"
                  style={{
                    backgroundColor: (project.color ?? "#6366f1") + "22",
                    color: project.color ?? "#6366f1",
                  }}
                >
                  {project.name.split(/\s+/).map((w) => w[0]).filter(Boolean).slice(0, 2).join("").toUpperCase()}
                </span>
              )}
              <span className="truncate">{project.name}</span>
            </Link>
          </td>
        );
      case "status":
        return (
          <td key={col.key} className="px-4 py-3">
            <StatusDropdown
              project={project}
              isOpen={openStatusProjectId === project.id}
              onOpen={() => setOpenStatusProjectId(project.id)}
              onClose={() => setOpenStatusProjectId(null)}
            />
          </td>
        );
      case "orderType":
        return (
          <td key={col.key} className="px-4 py-3">
            <span className={`inline-block px-2 py-0.5 text-xs rounded-full ${ORDER_TYPE_COLORS[project.orderType] ?? ""}`}>
              {project.orderType}
            </span>
          </td>
        );
      case "dates":
        return (
          <td key={col.key} className="whitespace-nowrap px-4 py-3 text-xs text-gray-500 dark:text-gray-400">
            {formatDate(project.startDate)} – {formatDate(project.endDate)}
          </td>
        );
      case "budget":
        return (
          <td key={col.key} className="px-4 py-3 min-w-[120px]">
            <div className="mb-0.5 text-sm text-gray-900 dark:text-gray-100">
              {formatMoney(project.budgetCents)}
            </div>
            <BudgetBar utilizationPercent={project.utilizationPercent ?? 0} budgetCents={project.budgetCents} />
          </td>
        );
      case "allocations":
        return (
          <td key={col.key} className="px-4 py-3 text-right text-sm">
            {project.totalPersonDays > 0 ? (
              <span className="inline-flex items-center gap-1 rounded-full bg-brand-50 px-2 py-0.5 text-xs font-medium text-brand-700 dark:bg-brand-900/30 dark:text-brand-300">
                <svg className="h-3 w-3 opacity-60" fill="none" stroke="currentColor" viewBox="0 0 24 24">
                  <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M17 20h5v-2a3 3 0 00-5.356-1.857M17 20H7m10 0v-2c0-.656-.126-1.283-.356-1.857M7 20H2v-2a3 3 0 015.356-1.857M7 20v-2c0-.656.126-1.283.356-1.857m0 0a5.002 5.002 0 019.288 0M15 7a3 3 0 11-6 0 3 3 0 016 0z" />
                </svg>
                {project.totalPersonDays}d
              </span>
            ) : (
              <span className="text-gray-400 dark:text-gray-500">—</span>
            )}
          </td>
        );
      case "shoring":
        return (
          <td key={col.key} className="px-4 py-3 text-center">
            <ShoringBadge projectId={project.id} />
          </td>
        );
      case "responsible":
        return <td key={col.key} className="px-4 py-3 text-sm text-gray-500 dark:text-gray-400">—</td>;
      default:
        return <td key={col.key} className="px-4 py-3 text-sm text-gray-600 dark:text-gray-300">—</td>;
    }
  }

  // ─── Header renderer ──────────────────────────────────────────────────────
  const SORTABLE_PROJECT_COLS = new Set(["shortCode", "name", "status", "orderType", "dates", "budget", "allocations"]);
  function renderHeader(col: ColumnDef) {
    if (SORTABLE_PROJECT_COLS.has(col.key)) {
      return (
        <SortableColumnHeader
          key={col.key}
          label={col.label}
          field={col.key}
          sortField={sortField}
          sortDir={sortDir}
          onSort={toggle}
        />
      );
    }
    return (
      <th key={col.key} className="px-4 py-3 text-left text-xs font-medium uppercase tracking-wider text-gray-500">
        {col.label}
      </th>
    );
  }

  return (
    <div className="app-page space-y-5">
      <div className="app-page-header gap-4">
        <div>
          <h1 className="app-page-title">Projects</h1>
          {!isLoading && (
            <p className="app-page-subtitle mt-1">
              {projects.length} project{projects.length !== 1 ? "s" : ""}
              {hasNextPage ? "+" : ""}
            </p>
          )}
        </div>
        <div className="flex items-center gap-2">
          <button
            type="button"
            onClick={() => setWizardOpen(true)}
            className="inline-flex items-center gap-2 rounded-xl bg-brand-600 px-4 py-2.5 text-sm font-semibold text-white transition hover:bg-brand-700"
          >
            <svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
              <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9.663 17h4.673M12 3v1m6.364 1.636l-.707.707M21 12h-1M4 12H3m3.343-5.657l-.707-.707m2.828 9.9a5 5 0 117.072 0l-.548.547A3.374 3.374 0 0014 18.469V19a2 2 0 11-4 0v-.531c0-.895-.356-1.754-.988-2.386l-.548-.547z" />
            </svg>
            New Project Wizard
          </button>
          <button
            type="button"
            onClick={openNewModal}
            className="inline-flex items-center gap-2 rounded-xl border border-gray-300 bg-white px-4 py-2.5 text-sm font-medium text-gray-700 transition hover:border-gray-400 hover:bg-gray-50 dark:border-gray-600 dark:bg-gray-900 dark:text-gray-200 dark:hover:bg-gray-800"
          >
            <svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
              <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 4v16m8-8H4" />
            </svg>
            Quick Add
          </button>
        </div>
      </div>

      <FilterBar>
        <input
          type="search"
          placeholder="Search projects..."
          value={search}
          onChange={(e) => setSearch(e.target.value)}
          className="app-input max-w-xs"
        />
        <select
          value={statusFilter}
          onChange={(e) => setStatusFilter(e.target.value)}
          className="app-select"
        >
          <option value="">All Statuses</option>
          {ALL_STATUSES.map((s) => (
            <option key={s.value} value={s.value}>{s.label}</option>
          ))}
        </select>
        <select
          value={orderTypeFilter}
          onChange={(e) => setOrderTypeFilter(e.target.value)}
          className="app-select"
        >
          <option value="">All Types</option>
          {ALL_ORDER_TYPES.map((t) => (
            <option key={t.value} value={t.value}>{t.label}</option>
          ))}
        </select>
        <ColumnTogglePanel
          allColumns={allColumns}
          visibleKeys={visibleKeys}
          onSetVisible={setVisible}
          defaultKeys={defaultKeys}
        />
        {isCustomOrder && (
          <button
            type="button"
            onClick={resetOrder}
            className="whitespace-nowrap text-xs text-gray-500 underline transition hover:text-gray-700 dark:hover:text-gray-200"
            title="Clear manual row order"
          >
            Reset order
          </button>
        )}
      </FilterBar>

      {chips.length > 0 && (
        <div className="mb-3">
          <FilterChips chips={chips} onClearAll={clearAll} />
        </div>
      )}

      <div className="app-data-table">
        {isLoading ? (
          <div className="py-16 text-center text-sm text-gray-500 shimmer-skeleton">Loading projects…</div>
        ) : (
          <>
            <div className="overflow-x-auto">
              <table className="w-full">
                <thead className="border-b border-gray-200 dark:border-gray-700">
                  <tr>
                    {/* Drag handle column */}
                    <th className="w-8 px-2" />
                    <th className="w-8 px-2" />
                    <th className="px-4 py-3 w-10">
                      <input
                        type="checkbox"
                        checked={selection.isAllSelected(projectIds)}
                        ref={(el) => {
                          if (el) el.indeterminate = selection.isIndeterminate(projectIds);
                        }}
                        onChange={() => selection.toggleAll(projectIds)}
                        className="rounded border-gray-300 dark:border-gray-600"
                      />
                    </th>
                    {visibleColumns.map(renderHeader)}
                    <th className="px-4 py-3 text-right text-xs font-medium text-gray-500 uppercase tracking-wider">
                      Actions
                    </th>
                  </tr>
                </thead>
                <tbody className="divide-y divide-gray-100 dark:divide-gray-800">
                  {projects.map((project, index) => {
                    const isSelected = selection.selectedIds.has(project.id);
                    return (
                      <DraggableTableRow
                        key={project.id}
                        id={project.id}
                        dragRef={rowDragRef}
                        onDrop={(draggedId) => reorder(draggedId, project.id)}
                        className={`table-row-hover hover-lift hover:bg-gray-50/80 dark:hover:bg-gray-900/60 animate-row-enter ${isSelected ? "bg-brand-50 dark:bg-brand-900/20" : ""}`}
                        style={{ animationDelay: `${Math.min(index * 15, 300)}ms` }}
                      >
                        <td className="px-2 py-3 w-8">
                          <button
                            type="button"
                            onClick={(e) => { e.stopPropagation(); e.preventDefault(); toggleFavMutation.mutate({ projectId: project.id }); }}
                            className={`text-sm transition-colors ${favSet.has(project.id) ? "text-amber-500 hover:text-amber-600" : "text-gray-300 hover:text-amber-400 dark:text-gray-600 dark:hover:text-amber-500"}`}
                            title={favSet.has(project.id) ? "Remove from favorites" : "Add to favorites"}
                          >
                            {favSet.has(project.id) ? "★" : "☆"}
                          </button>
                        </td>
                        <td className="px-4 py-3">
                          <input
                            type="checkbox"
                            checked={isSelected}
                            onChange={() => selection.toggle(project.id)}
                            className="rounded border-gray-300 dark:border-gray-600"
                          />
                        </td>
                        {visibleColumns.map((col) => renderCell(col, project))}
                        <td className="px-4 py-3">
                          <div className="flex items-center justify-end gap-2">
                            <button
                              type="button"
                              onClick={() => openEditModal(project as unknown as Project)}
                              className="link-hover-underline text-xs font-medium text-gray-600 transition-colors hover:text-gray-900 dark:text-gray-300 dark:hover:text-gray-100"
                            >
                              Edit
                            </button>
                            <Link href={`/projects/${project.id}`} className="link-hover-underline text-xs font-medium text-blue-600 hover:text-blue-800 dark:text-blue-300 dark:hover:text-blue-200">
                              View →
                            </Link>
                          </div>
                        </td>
                      </DraggableTableRow>
                    );
                  })}
                </tbody>
              </table>
            </div>

            {projects.length === 0 && (
              <div className="py-14 text-center text-sm text-gray-500">
                No projects found.{" "}
                <button type="button" onClick={openNewModal} className="text-brand-600 hover:underline font-medium">
                  Create your first project.
                </button>
              </div>
            )}

            <InfiniteScrollSentinel
              onVisible={handleFetchNext}
              isLoading={isFetchingNextPage}
            />
          </>
        )}
      </div>

      {/* Batch Status Picker */}
      {batchStatusPicker && (
        <div className="fixed inset-0 bg-black/30 z-50 flex items-center justify-center p-4" onClick={() => setBatchStatusPicker(false)}>
          <div className="min-w-[220px] rounded-2xl bg-white p-5 shadow-2xl dark:bg-gray-900" onClick={(e) => e.stopPropagation()}>
            <h3 className="mb-3 text-sm font-semibold text-gray-900 dark:text-gray-100">Set status for {selection.count} projects</h3>
            <div className="flex flex-col gap-1">
              {ALL_STATUSES.map((s) => (
                <button
                  key={s.value}
                  type="button"
                  onClick={() => {
                    setConfirmBatchStatus({ ids: selection.selectedArray, status: s.value });
                    setBatchStatusPicker(false);
                  }}
                  className="w-full rounded-xl px-3 py-2 text-left text-sm transition-colors hover:bg-gray-50 dark:hover:bg-gray-800"
                >
                  <span className={clsx("inline-block px-2 py-0.5 text-xs rounded-full", STATUS_COLORS[s.value])}>
                    {s.label}
                  </span>
                </button>
              ))}
            </div>
          </div>
        </div>
      )}

      {/* Confirm batch status change */}
      {confirmBatchStatus && (
        <ConfirmDialog
          title="Update Project Status"
          message={`Set ${confirmBatchStatus.ids.length} project${confirmBatchStatus.ids.length !== 1 ? "s" : ""} to "${confirmBatchStatus.status}"?`}
          confirmLabel="Update"
          onConfirm={() => {
            if (confirmBatchStatus) {
              batchUpdateStatus.mutate({ ids: confirmBatchStatus.ids, status: confirmBatchStatus.status as never });
            }
            setConfirmBatchStatus(null);
          }}
          onCancel={() => setConfirmBatchStatus(null)}
        />
      )}

      {/* Confirm batch delete */}
      {confirmBatchDelete && (
        <ConfirmDialog
          title="Delete Projects"
          message={`Permanently delete ${confirmBatchDelete.length} project${confirmBatchDelete.length !== 1 ? "s" : ""}? This will also remove all associated allocations and demands. This action cannot be undone.`}
          confirmLabel="Delete All"
          variant="danger"
          onConfirm={() => {
            batchDeleteMutation.mutate({ ids: confirmBatchDelete });
            setConfirmBatchDelete(null);
          }}
          onCancel={() => setConfirmBatchDelete(null)}
        />
      )}

      {/* Batch Action Bar */}
      <BatchActionBar
        count={selection.count}
        onClear={selection.clear}
        actions={[
          { label: "Export Selected", onClick: exportSelectedCsv },
          { label: "Set Status...", onClick: () => setBatchStatusPicker(true) },
          {
            label: `Delete (${selection.count})`,
            variant: "danger" as const,
            onClick: () => setConfirmBatchDelete(selection.selectedArray),
            disabled: batchDeleteMutation.isPending,
          },
        ]}
      />

      {/* Modal */}
      {modalOpen && (
        <ProjectModal
          project={editingProject}
          onClose={closeModal}
          onSuccess={(name) =>
            setSuccessToast(
              editingProject
                ? `Project "${name}" updated successfully.`
                : `Project "${name}" created successfully.`,
            )
          }
        />
      )}

      {/* Wizard */}
      <ProjectWizard
        open={wizardOpen}
        onClose={() => setWizardOpen(false)}
        onSuccess={(shortCode, name) =>
          setSuccessToast(`Project "${shortCode} — ${name}" created successfully.`)
        }
      />

      <SuccessToast
        show={successToast !== null}
        message={successToast ?? ""}
        onDone={() => setSuccessToast(null)}
      />
    </div>
  );
}
