/**
 * Recollections list page: table or cards view with search, sort, pagination, actions.
 * Sort by last edited (default), name, or created; configurable page size; export toast; undo delete.
 */

import React, { useCallback, useMemo, useState } from 'react'
import { useNavigate } from 'react-router-dom'
import {
  Table,
  TableBody,
  TableCell,
  TableHead,
  TableHeader,
  TableRow,
} from '@/components/ui/table'
import { Button } from '@/components/ui/button'
import {
  DropdownMenu,
  DropdownMenuContent,
  DropdownMenuItem,
  DropdownMenuSeparator,
  DropdownMenuTrigger,
} from '@/components/ui/dropdown-menu'
import {
  Dialog,
  DialogContent,
  DialogDescription,
  DialogFooter,
  DialogHeader,
  DialogTitle,
} from '@/components/ui/dialog'
import { Input } from '@/components/ui/input'
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select'
import { ToggleGroup, ToggleGroupItem } from '@/components/ui/toggle-group'
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '@/components/ui/tooltip'
import {
  ChevronLeft,
  ChevronRight,
  ChevronsLeft,
  ChevronsRight,
  MoreHorizontal,
  Pencil,
  Trash2,
  Download,
  FolderOpen,
  Search,
  Plus,
  LayoutGrid,
  List,
  Network,
  ArrowUpDown,
  ArrowUp,
  ArrowDown,
  X,
  Copy,
} from 'lucide-react'
import { Checkbox } from '@/components/ui/checkbox'
import { usePlatform } from '@/app/kosmos/KosmosContext'
import { getRecollectionIcon } from '@/lib/iconMap'
import {
  loadGraphFromStorage,
  saveGraphToStorage,
  removeGraphFromStorage,
  RECOLLECTION_FILE_EXT,
  RECOLLECTION_VERSION,
} from './state/recollectionGraphStorage'
import { getLogosContent, setLogosContent } from './state/recollectionStore'
import { toast } from 'sonner'
import { NewRecollectionDialog } from '@/app/kosmos/NewRecollectionDialog'
import { RecollectionsPageBackground } from './layout/RecollectionsPageBackground'
import { RenameRecollectionDialog } from './layout/RenameRecollectionDialog'
import type { Recollection } from '@/app/kosmos/types'

const PAGE_SIZE_OPTIONS = [10, 25, 50] as const
type SortKey = 'lastEdited' | 'name' | 'created'
type SortDir = 'asc' | 'desc'

function formatDate(ts: number) {
  return new Date(ts).toLocaleString(undefined, {
    year: 'numeric',
    month: 'short',
    day: 'numeric',
    hour: '2-digit',
    minute: '2-digit',
  })
}

function getRelativeTime(ts: number): string {
  const d = new Date(ts)
  const now = Date.now()
  const diff = now - d.getTime()
  const sec = Math.floor(diff / 1000)
  const min = Math.floor(sec / 60)
  const hour = Math.floor(min / 60)
  const day = Math.floor(hour / 24)
  if (day > 7) return formatDate(ts)
  if (day >= 1) return `${day} day${day === 1 ? '' : 's'} ago`
  if (hour >= 1) return `${hour} hour${hour === 1 ? '' : 's'} ago`
  if (min >= 1) return `${min} min ago`
  return 'Just now'
}

function getGraphCounts(recollectionId: string): { nodes: number; edges: number } {
  const stored = loadGraphFromStorage(recollectionId)
  if (!stored) return { nodes: 0, edges: 0 }
  return {
    nodes: Array.isArray(stored.nodes) ? stored.nodes.length : 0,
    edges: Array.isArray(stored.edges) ? stored.edges.length : 0,
  }
}

type NodeLike = { id: string; position?: { x: number; y: number } }
type EdgeLike = { id?: string; source: string; target: string }

/** Shared grid style for empty thumbnail, non-empty thumbnail SVG, and New recollection placeholder. */
const THUMBNAIL_GRID = {
  baseFill: 'hsl(var(--muted) / 0.4)',
  dotFill: 'hsl(var(--muted-foreground) / 0.06)',
  size: 8,
} as const

const thumbnailGridStyle: React.CSSProperties = {
  backgroundColor: THUMBNAIL_GRID.baseFill,
  backgroundImage: `radial-gradient(circle, ${THUMBNAIL_GRID.dotFill} 1px, transparent 1px)`,
  backgroundSize: `${THUMBNAIL_GRID.size}px ${THUMBNAIL_GRID.size}px`,
}

/** Renders a minimal SVG preview of the graph from storage, or a placeholder. */
function GraphThumbnail({ recollectionId, className }: { recollectionId: string; className?: string }) {
  const stored = loadGraphFromStorage(recollectionId)
  const nodes = (stored?.nodes ?? []) as NodeLike[]
  const edges = (stored?.edges ?? []) as EdgeLike[]
  const withPos = nodes.filter((n) => n.position && typeof n.position.x === 'number' && typeof n.position.y === 'number')

  const dotGridPatternId = `dotgrid-${recollectionId.replace(/\W/g, '-')}`

  if (withPos.length === 0) {
    return (
      <div
        className={`flex h-full w-full flex-col items-center justify-center gap-1 text-muted-foreground ${className ?? ''}`}
        style={thumbnailGridStyle}
        aria-hidden
      >
        <Network className="size-8" />
        <span className="text-[10px]">No graph</span>
      </div>
    )
  }

  const padding = 12
  const viewBoxW = 200
  const viewBoxH = 120
  const xs = withPos.map((n) => n.position!.x)
  const ys = withPos.map((n) => n.position!.y)
  const minX = Math.min(...xs)
  const maxX = Math.max(...xs)
  const minY = Math.min(...ys)
  const maxY = Math.max(...ys)
  const w = Math.max(maxX - minX, 1)
  const h = Math.max(maxY - minY, 1)
  const scale = Math.min((viewBoxW - padding * 2) / w, (viewBoxH - padding * 2) / h, 8)
  const graphW = w * scale
  const graphH = h * scale
  const ox = (viewBoxW - graphW) / 2 - minX * scale
  const oy = (viewBoxH - graphH) / 2 - minY * scale

  const nodeById = new Map(withPos.map((n) => [n.id, n]))
  const validEdges = edges.filter((e) => nodeById.has(e.source) && nodeById.has(e.target))

  return (
    <svg
      className={`h-full w-full ${className ?? ''}`}
      viewBox="0 0 200 120"
      preserveAspectRatio="xMidYMid meet"
      aria-hidden
    >
      <defs>
        <pattern id={dotGridPatternId} width={THUMBNAIL_GRID.size} height={THUMBNAIL_GRID.size} patternUnits="userSpaceOnUse">
          <circle cx={THUMBNAIL_GRID.size / 2} cy={THUMBNAIL_GRID.size / 2} r={1} fill={THUMBNAIL_GRID.dotFill} />
        </pattern>
      </defs>
      <rect width={200} height={120} fill={THUMBNAIL_GRID.baseFill} />
      <rect width={200} height={120} fill={`url(#${dotGridPatternId})`} />
      {validEdges.slice(0, 50).map((e, i) => {
        const a = nodeById.get(e.source)!.position!
        const b = nodeById.get(e.target)!.position!
        return (
          <line
            key={e.id ?? `e-${i}`}
            x1={ox + a.x * scale}
            y1={oy + a.y * scale}
            x2={ox + b.x * scale}
            y2={oy + b.y * scale}
            stroke="hsl(var(--muted-foreground) / 0.4)"
            strokeWidth={1.5}
          />
        )
      })}
      {withPos.slice(0, 80).map((n) => (
        <circle
          key={n.id}
          cx={ox + n.position!.x * scale}
          cy={oy + n.position!.y * scale}
          r={3}
          fill="hsl(var(--primary))"
        />
      ))}
    </svg>
  )
}

type RecollectionActionsMenuProps = {
  recollection: Recollection
  onOpen: (id: string) => void
  onRenameOpen: (recollection: Recollection) => void
  onDuplicateOpen: (recollection: Recollection) => void
  onExport: (recollection: Recollection) => void
  onDeleteOpen: (recollection: Recollection) => void
  trigger: React.ReactNode
}

function RecollectionActionsMenu({
  recollection,
  onOpen,
  onRenameOpen,
  onDuplicateOpen,
  onExport,
  onDeleteOpen,
  trigger,
}: RecollectionActionsMenuProps) {
  return (
    <DropdownMenu>
      <DropdownMenuTrigger asChild>{trigger}</DropdownMenuTrigger>
      <DropdownMenuContent align="end">
        <DropdownMenuItem onClick={() => onOpen(recollection.id)}>
          <FolderOpen className="size-4" />
          Open
        </DropdownMenuItem>
        <DropdownMenuItem onClick={() => onRenameOpen(recollection)}>
          <Pencil className="size-4" />
          Rename
        </DropdownMenuItem>
        <DropdownMenuItem onClick={() => onDuplicateOpen(recollection)}>
          <Copy className="size-4" />
          Duplicate
        </DropdownMenuItem>
        <DropdownMenuItem onClick={() => onExport(recollection)}>
          <Download className="size-4" />
          Export
        </DropdownMenuItem>
        <DropdownMenuSeparator />
        <DropdownMenuItem className="text-destructive focus:text-destructive" onClick={() => onDeleteOpen(recollection)}>
          <Trash2 className="size-4" />
          Delete
        </DropdownMenuItem>
      </DropdownMenuContent>
    </DropdownMenu>
  )
}

export type ViewMode = 'table' | 'cards'

export function RecollectionsPage() {
  const { orderedRecollections, deleteRecollection, renameRecollection, createRecollection, restoreRecollection } = usePlatform()
  const navigate = useNavigate()
  const [viewMode, setViewMode] = useState<ViewMode>('cards')
  const [search, setSearch] = useState('')
  const [pageSize, setPageSize] = useState<number>(10)
  const [page, setPage] = useState(0)
  const [sortKey, setSortKey] = useState<SortKey>('lastEdited')
  const [sortDir, setSortDir] = useState<SortDir>('desc')
  const [renameTarget, setRenameTarget] = useState<Recollection | null>(null)
  const [deleteTarget, setDeleteTarget] = useState<Recollection | null>(null)
  const [duplicateTarget, setDuplicateTarget] = useState<Recollection | null>(null)
  const [duplicateName, setDuplicateName] = useState('')
  const duplicateInputRef = React.useRef<HTMLInputElement>(null)
  const [selectedIds, setSelectedIds] = useState<Set<string>>(new Set())
  const [bulkDeleteTargets, setBulkDeleteTargets] = useState<Recollection[] | null>(null)

  const filtered = useMemo(() => {
    const q = search.trim().toLowerCase()
    if (!q) return orderedRecollections
    return orderedRecollections.filter((p) => p.name.toLowerCase().includes(q))
  }, [orderedRecollections, search])

  const sorted = useMemo(() => {
    const arr = [...filtered]
    const mult = sortDir === 'asc' ? 1 : -1
    arr.sort((a, b) => {
      if (sortKey === 'name') {
        return mult * a.name.localeCompare(b.name)
      }
      if (sortKey === 'created') {
        return mult * (a.createdAt - b.createdAt)
      }
      return mult * ((a.lastEditedAt ?? a.createdAt) - (b.lastEditedAt ?? b.createdAt))
    })
    return arr
  }, [filtered, sortKey, sortDir])

  const totalPages = Math.max(1, pageSize === -1 ? 1 : Math.ceil(sorted.length / pageSize))
  const start = pageSize === -1 ? 0 : page * pageSize
  const pageItems = pageSize === -1 ? sorted : sorted.slice(start, start + pageSize)

  const handleSort = (key: SortKey) => {
    if (sortKey === key) setSortDir((d) => (d === 'asc' ? 'desc' : 'asc'))
    else {
      setSortKey(key)
      setSortDir(key === 'name' ? 'asc' : 'desc')
    }
    setPage(0)
  }

  const handleOpen = useCallback(
    (recollectionId: string) => {
      navigate(`/recollections/${recollectionId}`)
    },
    [navigate]
  )

  const handleRenameOpen = useCallback((recollection: Recollection) => {
    setRenameTarget(recollection)
  }, [])

  React.useEffect(() => {
    if (duplicateTarget) {
      const t = setTimeout(() => duplicateInputRef.current?.focus(), 0)
      return () => clearTimeout(t)
    }
  }, [duplicateTarget])

  const handleDeleteOpen = useCallback((recollection: Recollection) => {
    setDeleteTarget(recollection)
  }, [])

  const handleDeleteConfirm = useCallback(() => {
    if (!deleteTarget) return
    const recollection = deleteTarget
    const graphSnapshot = loadGraphFromStorage(recollection.id)
    const snapshot =
      graphSnapshot && (graphSnapshot.nodes.length > 0 || graphSnapshot.edges.length > 0)
        ? { nodes: graphSnapshot.nodes, edges: graphSnapshot.edges }
        : null
    removeGraphFromStorage(recollection.id)
    deleteRecollection(recollection.id)
    setDeleteTarget(null)
    navigate('/recollections', { replace: true })
    toast(`"${recollection.name}" deleted`, {
      action: {
        label: 'Undo',
        onClick: () => restoreRecollection(recollection, snapshot),
      },
      duration: 8000,
    })
  }, [deleteTarget, deleteRecollection, navigate, restoreRecollection])

  const handleExport = useCallback(
    (recollection: Recollection) => {
      const stored = loadGraphFromStorage(recollection.id)
      const state = stored
        ? { version: RECOLLECTION_VERSION, nodes: stored.nodes, edges: stored.edges }
        : { version: RECOLLECTION_VERSION, nodes: [], edges: [] }
      const blob = new Blob([JSON.stringify(state, null, 2)], { type: 'application/json' })
      const url = URL.createObjectURL(blob)
      const a = document.createElement('a')
      const filename = `${recollection.name.replace(/[^\w.-]/g, '_')}${RECOLLECTION_FILE_EXT}`
      a.href = url
      a.download = filename
      a.click()
      URL.revokeObjectURL(url)
      toast.success(`Exported as ${filename}`)
    },
    []
  )

  const handleCreateRecollection = useCallback(
    (recollection: Recollection) => {
      createRecollection(recollection)
      navigate(`/recollections/${recollection.id}`)
    },
    [createRecollection, navigate]
  )

  const handleDuplicateOpen = useCallback((recollection: Recollection) => {
    setDuplicateTarget(recollection)
    setDuplicateName(`${recollection.name} (copy)`)
  }, [])

  const handleDuplicateConfirm = useCallback(() => {
    if (!duplicateTarget || !duplicateName.trim()) return
    const name = duplicateName.trim()
    const newId = `recollection_${Date.now()}_${Math.random().toString(36).slice(2, 6)}`
    const now = Date.now()
    const newRecollection: Recollection = {
      id: newId,
      name,
      iconId: duplicateTarget.iconId,
      createdAt: now,
      lastEditedAt: now,
    }
    createRecollection(newRecollection)
    const graph = loadGraphFromStorage(duplicateTarget.id)
    if (graph && (graph.nodes.length > 0 || graph.edges.length > 0)) {
      saveGraphToStorage(newId, { version: RECOLLECTION_VERSION, nodes: graph.nodes, edges: graph.edges })
    }
    const logosContent = getLogosContent(duplicateTarget.id)
    if (logosContent && logosContent.length > 0) {
      setLogosContent(newId, logosContent)
    }
    toast.success('Recollection duplicated')
    setDuplicateTarget(null)
    setDuplicateName('')
  }, [duplicateTarget, duplicateName, createRecollection])

  const allOnPageSelected = pageItems.length > 0 && pageItems.every((p) => selectedIds.has(p.id))
  const someOnPageSelected = pageItems.some((p) => selectedIds.has(p.id))

  const toggleSelection = useCallback((id: string) => {
    setSelectedIds((prev) => {
      const next = new Set(prev)
      if (next.has(id)) next.delete(id)
      else next.add(id)
      return next
    })
  }, [])

  const toggleSelectAll = useCallback(() => {
    if (allOnPageSelected) {
      setSelectedIds((prev) => {
        const next = new Set(prev)
        pageItems.forEach((p) => next.delete(p.id))
        return next
      })
    } else {
      setSelectedIds((prev) => {
        const next = new Set(prev)
        pageItems.forEach((p) => next.add(p.id))
        return next
      })
    }
  }, [allOnPageSelected, pageItems])

  const clearSelection = useCallback(() => setSelectedIds(new Set()), [])

  const handleBulkDeleteOpen = useCallback(() => {
    const toDelete = sorted.filter((p) => selectedIds.has(p.id))
    if (toDelete.length > 0) setBulkDeleteTargets(toDelete)
  }, [sorted, selectedIds])

  const handleBulkDeleteConfirm = useCallback(() => {
    if (!bulkDeleteTargets || bulkDeleteTargets.length === 0) return
    const count = bulkDeleteTargets.length
    bulkDeleteTargets.forEach((recollection) => {
      removeGraphFromStorage(recollection.id)
      deleteRecollection(recollection.id)
    })
    setBulkDeleteTargets(null)
    setSelectedIds(new Set())
    navigate('/recollections', { replace: true })
    toast(`${count} recollection${count === 1 ? '' : 's'} deleted`)
  }, [bulkDeleteTargets, deleteRecollection, navigate])

  const handleBulkExport = useCallback(() => {
    const toExport = sorted.filter((p) => selectedIds.has(p.id))
    toExport.forEach((recollection) => {
      const stored = loadGraphFromStorage(recollection.id)
      const state = stored
        ? { version: RECOLLECTION_VERSION, nodes: stored.nodes, edges: stored.edges }
        : { version: RECOLLECTION_VERSION, nodes: [], edges: [] }
      const blob = new Blob([JSON.stringify(state, null, 2)], { type: 'application/json' })
      const url = URL.createObjectURL(blob)
      const a = document.createElement('a')
      const filename = `${recollection.name.replace(/[^\w.-]/g, '_')}${RECOLLECTION_FILE_EXT}`
      a.href = url
      a.download = filename
      a.click()
      URL.revokeObjectURL(url)
    })
    toast.success(`Exported ${toExport.length} recollection${toExport.length === 1 ? '' : 's'}`)
  }, [sorted, selectedIds])

  const SortIcon = ({ columnKey }: { columnKey: SortKey }) => {
    if (sortKey !== columnKey) return <ArrowUpDown className="size-3.5 opacity-50" />
    return sortDir === 'asc' ? <ArrowUp className="size-3.5" /> : <ArrowDown className="size-3.5" />
  }

  return (
    <div className="relative flex flex-1 flex-col min-h-0">
      <RecollectionsPageBackground className="absolute inset-0 pointer-events-none" />
      <div className="relative flex min-h-0 flex-1 flex-col gap-4 p-4 overflow-auto">
      <TooltipProvider>
        <div className="flex flex-wrap items-center gap-3">
          <h1 className="p-3 scroll-m-20 text-4xl font-extrabold tracking-tight text-balance font-serif">
            Hi, Demiurge
          </h1>
        </div>
        <div className="flex flex-col gap-4 rounded-lg border bg-card p-4 text-card-foreground shadow-sm">
          <div className="flex flex-wrap items-center gap-3">
            <div className="relative w-64 shrink-0">
              <Search className="absolute left-2.5 top-1/2 size-4 -translate-y-1/2 text-muted-foreground pointer-events-none" />
              <Input
                placeholder="Search recollections"
                value={search}
                onChange={(e) => {
                  setSearch(e.target.value)
                  setPage(0)
                }}
                className="h-9 w-full pl-8"
                aria-label="Search recollections by name"
              />
            </div>
            <div className="ml-auto flex h-9 items-center gap-2">
              <Select
                value={pageSize === -1 ? 'all' : String(pageSize)}
                onValueChange={(v) => {
                  setPageSize(v === 'all' ? -1 : Number(v))
                  setPage(0)
                }}
              >
                <SelectTrigger className="h-9 w-[120px]">
                  <SelectValue placeholder="Page size" />
                </SelectTrigger>
                <SelectContent>
                  {PAGE_SIZE_OPTIONS.map((n) => (
                    <SelectItem key={n} value={String(n)}>
                      {n} per page
                    </SelectItem>
                  ))}
                  <SelectItem value="all">All</SelectItem>
                </SelectContent>
              </Select>
              <ToggleGroup
                type="single"
                value={viewMode}
                onValueChange={(v) => v && setViewMode(v as ViewMode)}
                aria-label="View mode"
                variant="outline"
                size="sm"
                className="gap-0 rounded-md p-0.5 [&>button]:rounded-none [&>button:first-child]:rounded-l-md [&>button:last-child]:rounded-r-md [&>button:not(:first-child)]:border-l-0"
              >
                <ToggleGroupItem value="cards" aria-label="Cards view" className="px-2.5">
                  <LayoutGrid className="size-4" />
                </ToggleGroupItem>
                <ToggleGroupItem value="table" aria-label="Table view" className="px-2.5">
                  <List className="size-4" />
                </ToggleGroupItem>
              </ToggleGroup>
            </div>
          </div>

          {sorted.length === 0 ? (
            <div className="grid gap-3 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4">
              <NewRecollectionDialog
                onCreate={handleCreateRecollection}
                existingNames={orderedRecollections.map((p) => p.name)}
                trigger={
                  <div
                    className="flex cursor-pointer flex-col overflow-hidden rounded-lg border border-dashed border-muted-foreground/30 bg-muted/20 shadow-sm transition-colors hover:border-muted-foreground/50 hover:bg-muted/30"
                    role="button"
                    tabIndex={0}
                    onKeyDown={(e) => {
                      if (e.key === 'Enter' || e.key === ' ') {
                        e.preventDefault()
                          ; (e.currentTarget as HTMLElement).click()
                      }
                    }}
                    aria-label="Create new recollection"
                  >
                    <div className="flex aspect-video w-full shrink-0 items-center justify-center" style={thumbnailGridStyle}>
                      <Plus className="size-12 text-muted-foreground" />
                    </div>
                    <div className="flex flex-1 flex-col gap-1 p-3">
                      <span className="font-medium text-muted-foreground">New recollection</span>
                      <span className="text-xs text-muted-foreground">
                        {search.trim() ? 'No recollections match your search.' : 'Create a new recollection'}
                      </span>
                    </div>
                  </div>
                }
              />
            </div>
          ) : viewMode === 'table' ? (
            <>
              <div className="overflow-auto rounded-md border">
                {(selectedIds.size > 0) && (
                  <div className="flex flex-wrap items-center gap-2 border-b bg-muted/60 px-3 py-2">
                    <span className="text-sm font-medium tabular-nums">
                      {selectedIds.size} selected
                    </span>
                    <div className="flex items-center gap-1">
                      <Button variant="outline" size="sm" onClick={handleBulkExport} className="h-8 gap-1.5 px-2.5">
                        <Download className="size-3.5" />
                        Export
                      </Button>
                      <Button variant="outline" size="sm" className="h-8 gap-1.5 px-2.5 text-destructive hover:text-destructive" onClick={handleBulkDeleteOpen}>
                        <Trash2 className="size-3.5" />
                        Delete
                      </Button>
                    </div>
                    <Button variant="ghost" size="sm" onClick={clearSelection} className="ml-auto h-8 gap-1.5 px-2.5">
                      <X className="size-3.5" />
                      Clear
                    </Button>
                  </div>
                )}
                <Table className="table-fixed">
                  <TableHeader className="sticky top-0 z-10 bg-card">
                    <TableRow className="hover:bg-transparent border-b h-8">
                      <TableHead className="w-10 px-2 py-1.5 text-xs [&:has([role=checkbox])]:pr-0">
                        <Checkbox
                          checked={pageItems.length === 0 ? false : allOnPageSelected ? true : someOnPageSelected ? 'indeterminate' : false}
                          onCheckedChange={() => toggleSelectAll()}
                          aria-label="Select all on page"
                        />
                      </TableHead>
                      <TableHead className="w-[35%] min-w-[100px] h-8 px-2 py-1.5 text-xs">
                        <button
                          type="button"
                          className="flex items-center gap-1 font-medium hover:underline"
                          onClick={() => handleSort('name')}
                          aria-sort={sortKey === 'name' ? (sortDir === 'asc' ? 'ascending' : 'descending') : undefined}
                        >
                          Name
                          <SortIcon columnKey="name" />
                        </button>
                      </TableHead>
                      <TableHead className="hidden sm:table-cell w-[120px] min-w-[100px] h-8 px-2 py-1.5 text-xs">
                        <button
                          type="button"
                          className="flex items-center gap-1 font-medium hover:underline"
                          onClick={() => handleSort('created')}
                          aria-sort={sortKey === 'created' ? (sortDir === 'asc' ? 'ascending' : 'descending') : undefined}
                        >
                          Created
                          <SortIcon columnKey="created" />
                        </button>
                      </TableHead>
                      <TableHead className="hidden md:table-cell w-[100px] min-w-[80px] h-8 px-2 py-1.5 text-xs">Size</TableHead>
                      <TableHead className="w-[115px] min-w-[90px] h-8 px-2 py-1.5 text-xs">
                        <button
                          type="button"
                          className="flex items-center gap-1 font-medium hover:underline"
                          onClick={() => handleSort('lastEdited')}
                          aria-sort={sortKey === 'lastEdited' ? (sortDir === 'asc' ? 'ascending' : 'descending') : undefined}
                        >
                          Last edited
                          <SortIcon columnKey="lastEdited" />
                        </button>
                      </TableHead>
                      <TableHead className="sticky right-0 z-10 w-12 min-w-12 bg-card h-8 px-1 py-1.5 text-xs font-medium" />
                    </TableRow>
                  </TableHeader>
                  <TableBody className="text-xs">
                    {pageItems.map((recollection) => {
                      const Icon = getRecollectionIcon(recollection.iconId)
                      const counts = getGraphCounts(recollection.id)
                      const lastEdited = recollection.lastEditedAt ?? recollection.createdAt
                      const isSelected = selectedIds.has(recollection.id)
                      return (
                        <TableRow
                          key={recollection.id}
                          className={`group cursor-pointer hover:bg-muted/50 h-8 ${isSelected ? 'bg-muted/70' : ''}`}
                          onClick={() => handleOpen(recollection.id)}
                          onKeyDown={(e) => {
                            if (e.key === 'Enter' || e.key === ' ') {
                              e.preventDefault()
                              handleOpen(recollection.id)
                            }
                          }}
                          tabIndex={0}
                          role="button"
                          aria-label={`Open ${recollection.name}`}
                        >
                          <TableCell className="px-2 py-1.5 w-10 [&:has([role=checkbox])]:pr-0" onClick={(e) => e.stopPropagation()}>
                            <Checkbox
                              checked={isSelected}
                              onCheckedChange={() => toggleSelection(recollection.id)}
                              aria-label={`Select ${recollection.name}`}
                            />
                          </TableCell>
                          <TableCell className="px-2 py-1.5 min-w-0">
                            <div className="flex items-center gap-1.5 min-w-0">
                              <Icon className="size-3.5 shrink-0 text-muted-foreground" />
                              <span className="font-medium truncate">{recollection.name}</span>
                            </div>
                          </TableCell>
                          <TableCell className="hidden sm:table-cell text-muted-foreground px-2 py-1.5 w-[120px] min-w-[100px]">
                            <span className="truncate block" title={formatDate(recollection.createdAt)}>
                              {formatDate(recollection.createdAt)}
                            </span>
                          </TableCell>
                          <TableCell className="hidden md:table-cell text-muted-foreground px-2 py-1.5 w-[100px] min-w-[80px]" title={`${counts.nodes} nodes, ${counts.edges} edges`}>
                            {counts.nodes}n, {counts.edges}e
                          </TableCell>
                          <TableCell className="text-muted-foreground px-2 py-1.5 w-[115px] min-w-[90px]">
                            <Tooltip>
                              <TooltipTrigger asChild>
                                <span className="truncate block">{getRelativeTime(lastEdited)}</span>
                              </TooltipTrigger>
                              <TooltipContent>{formatDate(lastEdited)}</TooltipContent>
                            </Tooltip>
                          </TableCell>
                          <TableCell
                            className={`sticky right-0 w-12 min-w-12 px-1 py-1.5 ${isSelected ? 'bg-muted/70' : 'bg-card group-hover:bg-muted/50'}`}
                            onClick={(e) => e.stopPropagation()}
                          >
                            <RecollectionActionsMenu
                              recollection={recollection}
                              onOpen={handleOpen}
                              onRenameOpen={handleRenameOpen}
                              onDuplicateOpen={handleDuplicateOpen}
                              onExport={handleExport}
                              onDeleteOpen={handleDeleteOpen}
                              trigger={
                                <Button variant="ghost" size="icon" className="size-7" aria-label={`Actions for ${recollection.name}`}>
                                  <MoreHorizontal className="size-4" />
                                </Button>
                              }
                            />
                          </TableCell>
                        </TableRow>
                      )
                    })}
                  </TableBody>
                </Table>
              </div>

              {totalPages > 1 && (
                <div className="flex flex-wrap items-center justify-between gap-2 border-t pt-4">
                  <p className="text-sm text-muted-foreground">
                    Page {page + 1} of {totalPages}
                    {pageSize !== -1 && ` · ${sorted.length} recollections`}
                  </p>
                  <div className="flex gap-1">
                    <Button
                      variant="outline"
                      size="sm"
                      onClick={() => setPage(0)}
                      disabled={page === 0}
                      aria-label="First page"
                    >
                      <ChevronsLeft className="size-4" />
                    </Button>
                    <Button
                      variant="outline"
                      size="sm"
                      onClick={() => setPage((p) => Math.max(0, p - 1))}
                      disabled={page === 0}
                      aria-label="Previous page"
                    >
                      <ChevronLeft className="size-4" />
                    </Button>
                    <Button
                      variant="outline"
                      size="sm"
                      onClick={() => setPage((p) => Math.min(totalPages - 1, p + 1))}
                      disabled={page >= totalPages - 1}
                      aria-label="Next page"
                    >
                      <ChevronRight className="size-4" />
                    </Button>
                    <Button
                      variant="outline"
                      size="sm"
                      onClick={() => setPage(totalPages - 1)}
                      disabled={page >= totalPages - 1}
                      aria-label="Last page"
                    >
                      <ChevronsRight className="size-4" />
                    </Button>
                  </div>
                </div>
              )}
            </>
          ) : (
            <>
              <div className="grid gap-3 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4">
                <NewRecollectionDialog
                  onCreate={handleCreateRecollection}
                  existingNames={orderedRecollections.map((p) => p.name)}
                  trigger={
                    <div
                      className="flex cursor-pointer flex-col overflow-hidden rounded-lg border border-dashed border-muted-foreground/30 bg-muted/20 shadow-sm transition-colors hover:border-muted-foreground/50 hover:bg-muted/30"
                      role="button"
                      tabIndex={0}
                      onKeyDown={(e) => {
                        if (e.key === 'Enter' || e.key === ' ') {
                          e.preventDefault()
                            ; (e.currentTarget as HTMLElement).click()
                        }
                      }}
                      aria-label="Create new recollection"
                    >
                      <div className="flex aspect-video w-full shrink-0 items-center justify-center" style={thumbnailGridStyle}>
                        <Plus className="size-12 text-muted-foreground" />
                      </div>
                      <div className="flex flex-1 flex-col gap-1 p-3">
                        <span className="font-medium text-muted-foreground">New recollection</span>
                        <span className="text-xs text-muted-foreground">Create a new recollection</span>
                      </div>
                    </div>
                  }
                />
                {pageItems.map((recollection) => {
                  const Icon = getRecollectionIcon(recollection.iconId)
                  const lastEdited = recollection.lastEditedAt ?? recollection.createdAt
                  return (
                    <div
                      key={recollection.id}
                      className="group flex cursor-pointer flex-col overflow-hidden rounded-lg border bg-card text-card-foreground shadow-sm transition-shadow hover:shadow-md"
                      onClick={() => handleOpen(recollection.id)}
                      onKeyDown={(e) => {
                        if (e.key === 'Enter' || e.key === ' ') {
                          e.preventDefault()
                          handleOpen(recollection.id)
                        }
                      }}
                      role="button"
                      tabIndex={0}
                      aria-label={`Open ${recollection.name}`}
                    >
                      <div className="relative aspect-video w-full shrink-0 overflow-hidden bg-muted">
                        <GraphThumbnail recollectionId={recollection.id} className="h-full w-full object-cover" />
                        <div
                          className="absolute right-1.5 top-1.5 z-10 opacity-0 transition-opacity group-hover:opacity-100 focus-within:opacity-100"
                          onClick={(e) => e.stopPropagation()}
                        >
                          <RecollectionActionsMenu
                            recollection={recollection}
                            onOpen={handleOpen}
                            onRenameOpen={handleRenameOpen}
                            onDuplicateOpen={handleDuplicateOpen}
                            onExport={handleExport}
                            onDeleteOpen={handleDeleteOpen}
                            trigger={
                              <Button
                                variant="secondary"
                                size="icon"
                                className="size-7 rounded-full shadow-sm"
                                aria-label={`Actions for ${recollection.name}`}
                                onClick={(e) => e.stopPropagation()}
                              >
                                <MoreHorizontal className="size-4" />
                              </Button>
                            }
                          />
                        </div>
                      </div>
                      <div className="flex flex-1 flex-col gap-1 p-3">
                        <div className="flex items-center gap-2">
                          <Icon className="size-4 shrink-0 text-muted-foreground" />
                          <span className="truncate font-medium font-serif">{recollection.name}</span>
                        </div>
                        <Tooltip>
                          <TooltipTrigger asChild>
                            <span className="text-xs text-muted-foreground">{getRelativeTime(lastEdited)}</span>
                          </TooltipTrigger>
                          <TooltipContent>{formatDate(lastEdited)}</TooltipContent>
                        </Tooltip>
                      </div>
                    </div>
                  )
                })}
              </div>
              {totalPages > 1 && (
                <div className="flex flex-wrap items-center justify-between gap-2 border-t pt-4">
                  <p className="text-sm text-muted-foreground">
                    Page {page + 1} of {totalPages}
                    {pageSize !== -1 && ` · ${sorted.length} recollections`}
                  </p>
                  <div className="flex gap-1">
                    <Button
                      variant="outline"
                      size="sm"
                      onClick={() => setPage(0)}
                      disabled={page === 0}
                      aria-label="First page"
                    >
                      <ChevronsLeft className="size-4" />
                    </Button>
                    <Button
                      variant="outline"
                      size="sm"
                      onClick={() => setPage((p) => Math.max(0, p - 1))}
                      disabled={page === 0}
                      aria-label="Previous page"
                    >
                      <ChevronLeft className="size-4" />
                    </Button>
                    <Button
                      variant="outline"
                      size="sm"
                      onClick={() => setPage((p) => Math.min(totalPages - 1, p + 1))}
                      disabled={page >= totalPages - 1}
                      aria-label="Next page"
                    >
                      <ChevronRight className="size-4" />
                    </Button>
                    <Button
                      variant="outline"
                      size="sm"
                      onClick={() => setPage(totalPages - 1)}
                      disabled={page >= totalPages - 1}
                      aria-label="Last page"
                    >
                      <ChevronsRight className="size-4" />
                    </Button>
                  </div>
                </div>
              )}
            </>
          )}
        </div>

        <RenameRecollectionDialog
          open={!!renameTarget}
          onOpenChange={(open) => !open && setRenameTarget(null)}
          recollectionId={renameTarget?.id ?? ''}
          initialName={renameTarget?.name ?? ''}
          recollections={sorted}
          onRename={(id, newName) => {
            renameRecollection(id, newName)
            setRenameTarget(null)
          }}
        />

        {/* Duplicate dialog */}
        <Dialog open={!!duplicateTarget} onOpenChange={(open) => { if (!open) { setDuplicateTarget(null); setDuplicateName('') } }}>
          <DialogContent onCloseAutoFocus={(e) => e.preventDefault()}>
            <DialogHeader>
              <DialogTitle>Duplicate recollection</DialogTitle>
              <DialogDescription>Enter a name for the duplicate recollection.</DialogDescription>
            </DialogHeader>
            <Input
              ref={duplicateInputRef}
              value={duplicateName}
              onChange={(e) => setDuplicateName(e.target.value)}
              onKeyDown={(e) => {
                if (e.key === 'Enter') handleDuplicateConfirm()
                if (e.key === 'Escape') setDuplicateTarget(null)
              }}
              placeholder="Recollection name"
              aria-label="Duplicate recollection name"
            />
            {duplicateTarget && duplicateName.trim() && orderedRecollections.some((p) => p.name.toLowerCase() === duplicateName.trim().toLowerCase()) && (
              <p className="text-xs text-amber-600 dark:text-amber-500">An recollection with this name already exists.</p>
            )}
            <DialogFooter>
              <Button variant="outline" onClick={() => { setDuplicateTarget(null); setDuplicateName('') }}>
                Cancel
              </Button>
              <Button onClick={handleDuplicateConfirm} disabled={!duplicateName.trim()}>
                Duplicate
              </Button>
            </DialogFooter>
          </DialogContent>
        </Dialog>

        {/* Delete confirmation */}
        <Dialog open={!!deleteTarget} onOpenChange={(open) => !open && setDeleteTarget(null)}>
          <DialogContent>
            <DialogHeader>
              <DialogTitle>Delete recollection</DialogTitle>
              <DialogDescription>
                {deleteTarget
                  ? `Are you sure you want to delete "${deleteTarget.name}"? You can undo this from the notification.`
                  : ''}
              </DialogDescription>
            </DialogHeader>
            <DialogFooter>
              <Button variant="outline" onClick={() => setDeleteTarget(null)}>
                Cancel
              </Button>
              <Button variant="destructive" onClick={handleDeleteConfirm}>
                Delete
              </Button>
            </DialogFooter>
          </DialogContent>
        </Dialog>

        {/* Bulk delete confirmation */}
        <Dialog open={bulkDeleteTargets !== null && bulkDeleteTargets.length > 0} onOpenChange={(open) => !open && setBulkDeleteTargets(null)}>
          <DialogContent>
            <DialogHeader>
              <DialogTitle>Delete {bulkDeleteTargets?.length ?? 0} recollections</DialogTitle>
              <DialogDescription>
                Are you sure you want to delete these recollections? This cannot be undone.
              </DialogDescription>
            </DialogHeader>
            <DialogFooter>
              <Button variant="outline" onClick={() => setBulkDeleteTargets(null)}>
                Cancel
              </Button>
              <Button variant="destructive" onClick={handleBulkDeleteConfirm}>
                Delete all
              </Button>
            </DialogFooter>
          </DialogContent>
        </Dialog>
      </TooltipProvider>
      </div>
    </div>
  )
}
