'use client'

import { useMemo, useState, useEffect, useCallback, memo } from 'react'
import {
  DndContext,
  DragOverlay,
  PointerSensor,
  TouchSensor,
  closestCenter,
  useSensor,
  useSensors,
  type DragEndEvent,
  type DragStartEvent,
} from '@dnd-kit/core'
import {
  SortableContext,
  arrayMove,
  rectSortingStrategy,
  useSortable,
} from '@dnd-kit/sortable'
import { CSS } from '@dnd-kit/utilities'
import { useRouter } from 'next/navigation'
import { useSession } from 'next-auth/react'
import type { Note } from '@/lib/types'
import { NotesEditorialView } from '@/components/notes-editorial-view'
import type { NoteCollectionActions } from '@/lib/note-change-sync'
import { getNoteDisplayTitle, getNoteFeedImage, getNotePlainExcerpt, prepareNoteIllustrationForGrid } from '@/lib/note-preview'
import { sanitizeIllustrationSvg } from '@/lib/sanitize-content'
import { useLanguage } from '@/lib/i18n'
import { useNotebooks } from '@/context/notebooks-context'
import { useLabelsQuery } from '@/lib/query-hooks'
import { getAISettings } from '@/app/actions/ai-settings'
import { generateNoteIllustrationSvg } from '@/app/actions/note-illustration'
import { LabelBadge } from '@/components/label-badge'
import { formatAbsoluteDateLocalized } from '@/lib/utils/format-localized-date'
import { motion } from 'motion/react'
import { MoveToNotebookPicker } from '@/components/move-to-notebook-picker'
import { toast } from 'sonner'
import {
  Pin,
  FileText,
  ChevronUp,
  ChevronDown,
  Wind,
  Trash2,
  FolderOpen,
  Sparkles,
  Loader2,
  History,
} from 'lucide-react'
import { cn } from '@/lib/utils'
import { useHydrated } from '@/lib/use-hydrated'
import dynamic from 'next/dynamic'
const BrainstormConfirmDialog = dynamic(() => import('@/components/brainstorm/brainstorm-confirm-dialog').then(m => ({ default: m.BrainstormConfirmDialog })), { ssr: false })
import { formatDistanceToNow } from 'date-fns'
import { fr } from 'date-fns/locale/fr'
import { enUS } from 'date-fns/locale/en-US'

export type NotesLayoutMode = 'grid' | 'list' | 'table' | 'kanban' | 'gallery'
export type NotesClassicLayoutMode = 'grid' | 'list' | 'table'

export function isClassicLayoutMode(mode: NotesLayoutMode): mode is NotesClassicLayoutMode {
  return mode === 'grid' || mode === 'list' || mode === 'table'
}

function getNotebookColor(notebookId: string | null | undefined, name?: string) {
  const colors = [
    { bg: 'bg-[#A47148]/5 dark:bg-[#A47148]/10', border: 'border-[#A47148]/20', text: 'text-[#A47148]' },
    { bg: 'bg-emerald-500/5 dark:bg-emerald-500/10', border: 'border-emerald-500/15', text: 'text-emerald-600 dark:text-emerald-400' },
    { bg: 'bg-indigo-500/5 dark:bg-indigo-500/10', border: 'border-indigo-500/15', text: 'text-indigo-600 dark:text-indigo-400' },
    { bg: 'bg-blue-500/5 dark:bg-blue-500/10', border: 'border-blue-500/15', text: 'text-blue-600 dark:text-blue-400' },
    { bg: 'bg-amber-500/5 dark:bg-amber-500/10', border: 'border-amber-500/15', text: 'text-amber-600 dark:text-amber-400' },
    { bg: 'bg-rose-500/5 dark:bg-rose-500/10', border: 'border-rose-500/15', text: 'text-rose-600 dark:text-rose-400' },
  ]
  const key = name || notebookId || ''
  const idx = Math.abs(key.split('').reduce((acc, char) => acc + char.charCodeAt(0), 0)) % colors.length
  return colors[idx]
}

function NoteLabelsRow({
  labelNames,
  allLabels,
  max = 3,
}: {
  labelNames: string[] | null | undefined
  allLabels: { name: string; type?: 'ai' | 'user' }[]
  max?: number
}) {
  if (!labelNames?.length) return null
  return (
    <div className="flex items-center gap-1 flex-wrap">
      {labelNames.slice(0, max).map((labelName) => {
        const def = allLabels.find((l) => l.name === labelName)
        return (
          <LabelBadge key={labelName} label={labelName} type={def?.type} variant="default" />
        )
      })}
      {labelNames.length > max && (
        <span className="text-[8.5px] font-mono text-muted-foreground font-bold shrink-0 bg-muted/50 px-1 py-0.5 rounded">
          +{labelNames.length - max}
        </span>
      )}
    </div>
  )
}

function NoteGridIllustrationButton({
  busy,
  onClick,
  onDelete,
  hasIllustration,
  className,
}: {
  busy: boolean
  onClick: (e: React.MouseEvent) => void
  onDelete?: (e: React.MouseEvent) => void
  hasIllustration?: boolean
  className?: string
}) {
  const { t } = useLanguage()
  return (
    <div className={cn('absolute bottom-2 end-2 flex items-center gap-1.5 z-10', className)}>
      {hasIllustration && onDelete && (
        <button
          type="button"
          aria-label={t('notes.deleteIllustration') || 'Supprimer l\'illustration'}
          title={t('notes.deleteIllustration') || 'Supprimer l\'illustration'}
          className="flex h-8 w-8 items-center justify-center rounded-full border border-border bg-background/95 text-rose-400 hover:bg-rose-50 dark:hover:bg-rose-500/10 shadow-card-rest backdrop-blur-sm transition-colors"
          onClick={onDelete}
          disabled={busy}
        >
          <Trash2 className="h-3.5 w-3.5" />
        </button>
      )}
      <button
        type="button"
        aria-label={hasIllustration
          ? (t('notes.regenerateIllustration') || 'Regénérer l\'illustration')
          : (t('notes.generateIllustration') || 'Générer une illustration IA')}
        title={hasIllustration
          ? (t('notes.regenerateIllustration') || 'Regénérer')
          : (t('notes.generateIllustration') || 'Générer une illustration IA')}
        className="flex h-8 w-8 items-center justify-center rounded-full border border-border bg-background/95 text-foreground shadow-card-rest backdrop-blur-sm transition-colors hover:bg-accent"
        onClick={onClick}
        disabled={busy}
      >
        {busy ? <Loader2 className="h-3.5 w-3.5 animate-spin" /> : <Sparkles className="h-3.5 w-3.5 text-primary" />}
      </button>
    </div>
  )
}

function NoteGridThumbnail({
  note,
  aiIllustrationEnabled,
  onNoteIllustrationGenerated,
  onNoteIllustrationDeleted,
}: {
  note: Note
  aiIllustrationEnabled?: boolean
  onNoteIllustrationGenerated?: (noteId: string) => void | Promise<void>
  onNoteIllustrationDeleted?: (noteId: string) => void | Promise<void>
}) {
  const { t } = useLanguage()
  const [busy, setBusy] = useState(false)
  const img = getNoteFeedImage(note)

  const handleGenerateSvg = async (e: React.MouseEvent) => {
    e.stopPropagation()
    if (!aiIllustrationEnabled || busy) return
    setBusy(true)
    try {
      const res = await generateNoteIllustrationSvg(note.id, { skipRevalidation: true })
      if (!res.ok) {
        toast.error(res.error)
      } else {
        toast.success(t('notes.illustrationGenerated') || 'Illustration générée')
        await onNoteIllustrationGenerated?.(note.id)
      }
    } finally {
      setBusy(false)
    }
  }

  const handleDeleteSvg = async (e: React.MouseEvent) => {
    e.stopPropagation()
    if (busy) return
    setBusy(true)
    try {
      const { updateNote } = await import('@/app/actions/notes')
      await updateNote(note.id, { illustrationSvg: null })
      await onNoteIllustrationDeleted?.(note.id)
    } catch {
      toast.error('Erreur lors de la suppression')
    } finally {
      setBusy(false)
    }
  }

  const aiButtonClass = 'opacity-0 group-hover/card:opacity-100 focus-visible:opacity-100 transition-opacity duration-200'

  if (img) {
    return (
      <img
        src={img}
        alt=""
        className="w-full h-full object-cover mix-blend-multiply dark:mix-blend-normal opacity-85 grayscale contrast-115 group-hover/card:scale-105 group-hover/card:grayscale-0 group-hover/card:opacity-100 transition-all duration-500"
      />
    )
  }
  if (note.illustrationSvg) {
    return (
      <>
        <div
          className="absolute inset-0 w-full h-full overflow-hidden bg-[#F5F0E8] dark:bg-muted/30"
          dangerouslySetInnerHTML={{ __html: sanitizeIllustrationSvg(prepareNoteIllustrationForGrid(note.illustrationSvg)) }}
          aria-hidden
        />
        {aiIllustrationEnabled && (
          <NoteGridIllustrationButton
            busy={busy}
            onClick={handleGenerateSvg}
            onDelete={handleDeleteSvg}
            hasIllustration
            className={aiButtonClass}
          />
        )}
      </>
    )
  }
  return (
    <>
      <div className="w-full h-full flex items-center justify-center bg-muted/40 text-muted-foreground/50">
        <FileText size={28} strokeWidth={1.25} />
      </div>
      {aiIllustrationEnabled && (
        <NoteGridIllustrationButton
          busy={busy}
          onClick={handleGenerateSvg}
          className={aiButtonClass}
        />
      )}
    </>
  )
}


export type { NoteCollectionActions } from '@/lib/note-change-sync'

type NotesListViewsProps = {
  notes: Note[]
  pinnedNotes?: Note[]
  layoutMode: NotesLayoutMode
  onOpen: (note: Note, readOnly?: boolean) => void
  onOpenHistory?: (note: Note) => void
  notebookName?: string
  onGridReorder?: (orderedIds: string[]) => void | Promise<void>
} & Partial<NoteCollectionActions>

export function NotesListViews({
  notes,
  pinnedNotes = [],
  layoutMode,
  onOpen,
  onOpenHistory,
  notebookName,
  onTogglePin,
  onDeleteNote,
  onArchiveNote,
  onMoveToNotebook,
  onNotePatch,
  onNoteIllustrationGenerated,
  onGridReorder,
}: NotesListViewsProps) {
  const { t, language } = useLanguage()
  const { data: session } = useSession()
  const { notebooks } = useNotebooks()
  const { data: allLabels = [] } = useLabelsQuery()
  const [sortColumn, setSortColumn] = useState<'title' | 'notebook' | 'modified' | null>(null)
  const [sortDirection, setSortDirection] = useState<'asc' | 'desc' | null>(null)
  const [aiIllustrationEnabled, setAiIllustrationEnabled] = useState(false)

  useEffect(() => {
    if (!session?.user?.id) {
      setAiIllustrationEnabled(false)
      return
    }
    getAISettings(session.user.id)
      .then((s) => setAiIllustrationEnabled(s.paragraphRefactor !== false))
      .catch(() => setAiIllustrationEnabled(false))
  }, [session?.user?.id])

  const untitled = t('notes.untitled')
  const dateLocale = language === 'fr' ? fr : enUS

  const allDisplayNotes = useMemo(() => {
    const unpinned = notes.filter((n) => !n.isPinned)
    return [...pinnedNotes, ...unpinned]
  }, [notes, pinnedNotes])

  const handleSort = (field: 'title' | 'notebook' | 'modified') => {
    if (sortColumn !== field) {
      setSortColumn(field)
      setSortDirection('asc')
    } else if (sortDirection === 'asc') {
      setSortDirection('desc')
    } else {
      setSortColumn(null)
      setSortDirection(null)
    }
  }

  const sortedNotes = useMemo(() => {
    if (!sortColumn || !sortDirection) return allDisplayNotes
    const copy = [...allDisplayNotes]
    return copy.sort((a, b) => {
      let valA: string | number = ''
      let valB: string | number = ''
      if (sortColumn === 'title') {
        valA = getNoteDisplayTitle(a, untitled).toLowerCase()
        valB = getNoteDisplayTitle(b, untitled).toLowerCase()
      } else if (sortColumn === 'notebook') {
        valA = notebooks.find((nb) => nb.id === a.notebookId)?.name?.toLowerCase() || ''
        valB = notebooks.find((nb) => nb.id === b.notebookId)?.name?.toLowerCase() || ''
      } else {
        valA = new Date(a.updatedAt).getTime()
        valB = new Date(b.updatedAt).getTime()
      }
      if (valA < valB) return sortDirection === 'asc' ? -1 : 1
      if (valA > valB) return sortDirection === 'asc' ? 1 : -1
      return 0
    })
  }, [allDisplayNotes, sortColumn, sortDirection, notebooks, untitled])

  const SortIcon = ({ field }: { field: typeof sortColumn }) =>
    sortColumn === field ? (
      sortDirection === 'asc' ? <ChevronUp size={12} /> : <ChevronDown size={12} />
    ) : null

  if (layoutMode === 'grid') {
    return (
      <NotesMasonryGrid
        pinnedNotes={pinnedNotes}
        unpinnedNotes={notes.filter((n) => !n.isPinned)}
        untitled={untitled}
        allLabels={allLabels}
        notebooks={notebooks}
        aiIllustrationEnabled={aiIllustrationEnabled}
        onOpen={onOpen}
        onTogglePin={onTogglePin}
        onDeleteNote={onDeleteNote}
        onMoveToNotebook={onMoveToNotebook}
        onNoteIllustrationGenerated={onNoteIllustrationGenerated}
        onOpenHistory={onOpenHistory}
        onGridReorder={onGridReorder}
        pinnedLabel={t('notes.pinned')}
      />
    )
  }

  if (layoutMode === 'table') {
    return (
      <div className="max-w-6xl mx-auto">
        <div className="overflow-x-auto border border-border/40 rounded-2xl bg-card/30 shadow-sm">
          <table className="w-full text-left border-collapse min-w-[720px]">
            <thead>
              <tr className="border-b border-border/30">
                <th
                  className="w-[32%] px-4 py-3 text-[10px] uppercase tracking-widest font-black text-muted-foreground cursor-pointer hover:text-foreground"
                  onClick={() => handleSort('title')}
                >
                  <span className="inline-flex items-center gap-1">
                    {t('notes.tableTitle')} <SortIcon field="title" />
                  </span>
                </th>
                <th
                  className="w-[15%] px-4 py-3 text-[10px] uppercase tracking-widest font-black text-muted-foreground cursor-pointer hover:text-foreground"
                  onClick={() => handleSort('notebook')}
                >
                  <span className="inline-flex items-center gap-1">
                    {t('notes.tableNotebook')} <SortIcon field="notebook" />
                  </span>
                </th>
                <th className="w-[22%] px-4 py-3 text-[10px] uppercase tracking-widest font-black text-muted-foreground">
                  {t('notes.tableLabels')}
                </th>
                <th
                  className="w-[18%] px-4 py-3 text-[10px] uppercase tracking-widest font-black text-muted-foreground cursor-pointer hover:text-foreground"
                  onClick={() => handleSort('modified')}
                  aria-sort={sortColumn === 'modified' ? (sortDirection === 'asc' ? 'ascending' : 'descending') : 'none'}
                >
                  <span className="inline-flex items-center gap-1">
                    {t('notes.tableModified')} <SortIcon field="modified" />
                  </span>
                </th>
              </tr>
            </thead>
            <tbody className="divide-y divide-foreground/[0.03]">
              {sortedNotes.map((note) => {
                const title = getNoteDisplayTitle(note, untitled)
                const nb = notebooks.find((n) => n.id === note.notebookId)
                const nbColor = getNotebookColor(note.notebookId, nb?.name)
                return (
                  <tr
                    key={note.id}
                    onClick={() => onOpen(note)}
                    onKeyDown={(e) => { if (e.key === 'Enter') onOpen(note) }}
                    tabIndex={0}
                    role="button"
                    className="h-11 hover:bg-foreground/[0.02] cursor-pointer transition-colors group focus-visible:bg-foreground/[0.04]"
                  >
                    <td className="px-4 py-2 font-memento-serif text-[13px] font-medium truncate max-w-[280px]">
                      <span className="inline-flex items-center gap-2 truncate group-hover:text-brand-accent transition-colors">
                        {note.isPinned && <Pin size={11} className="text-amber-500 fill-amber-500 shrink-0" />}
                        {note.historyEnabled && <History size={11} className="text-emerald-500 shrink-0" />}
                        {title}
                      </span>
                    </td>
                    <td className="px-4 py-2">
                      {nb ? (
                        <span
                          className={cn(
                            'inline-block px-2 py-0.5 rounded-full text-[9px] font-bold tracking-wide border truncate max-w-[125px]',
                            nbColor.bg,
                            nbColor.border,
                            nbColor.text,
                          )}
                        >
                          {nb.name}
                        </span>
                      ) : (
                        <span className="text-muted-foreground text-[10px]">—</span>
                      )}
                    </td>
                    <td className="px-4 py-2">
                      <NoteLabelsRow labelNames={note.labels} allLabels={allLabels} max={3} />
                    </td>
                    <td className="px-4 py-2 text-[10.5px] font-mono text-muted-foreground whitespace-nowrap">
                      {formatDistanceToNow(new Date(note.updatedAt), { addSuffix: true, locale: dateLocale })}
                    </td>
                  </tr>
                )
              })}
            </tbody>
          </table>
        </div>
      </div>
    )
  }

  return (
    <div className="max-w-3xl space-y-16">
      {pinnedNotes.length > 0 && (
        <div className="mb-6">
          <h2 className="text-[10px] font-bold text-muted-foreground tracking-widest uppercase mb-4 px-2">
            {t('notes.pinned')}
          </h2>
          <NotesEditorialView
            notes={pinnedNotes}
            onOpen={onOpen}
            notebookName={notebookName}
            onOpenHistory={onOpenHistory}
            onTogglePin={onTogglePin}
            onDeleteNote={onDeleteNote}
            onArchiveNote={onArchiveNote}
            onMoveToNotebook={onMoveToNotebook}
            onNotePatch={onNotePatch}
            onNoteIllustrationGenerated={onNoteIllustrationGenerated}
          />
        </div>
      )}
      {notes.filter((n) => !n.isPinned).length > 0 && (
        <NotesEditorialView
          notes={notes.filter((n) => !n.isPinned)}
          onOpen={onOpen}
          notebookName={notebookName}
          onOpenHistory={onOpenHistory}
          onTogglePin={onTogglePin}
          onDeleteNote={onDeleteNote}
          onArchiveNote={onArchiveNote}
          onMoveToNotebook={onMoveToNotebook}
          onNotePatch={onNotePatch}
          onNoteIllustrationGenerated={onNoteIllustrationGenerated}
        />
      )}
    </div>
  )
}

function formatGridCardDate(date: Date | string, language: string): string {
  const d = typeof date === 'string' ? new Date(date) : date
  const locale = language === 'fr' ? fr : enUS
  if (language === 'fa') {
    return formatAbsoluteDateLocalized(d, language, 'd MMM yyyy', locale)
  }
  const month = d.toLocaleDateString('en-US', { month: 'short', timeZone: 'UTC' })
  const day = d.getUTCDate()
  const year = d.getUTCFullYear()
  return `${month.toUpperCase()} ${day}, ${year}`
}

type GridCardSharedProps = {
  note: Note
  index: number
  untitled: string
  allLabels: { name: string; type?: 'ai' | 'user' }[]
  notebooks: { id: string; name: string }[]
  aiIllustrationEnabled?: boolean
  onOpen: (note: Note) => void
  onTogglePin?: (note: Note) => void | Promise<void>
  onDeleteNote?: (note: Note) => void | Promise<void>
  onMoveToNotebook?: (note: Note, notebookId: string | null) => void | Promise<void>
  onNoteIllustrationGenerated?: (noteId: string) => void | Promise<void>
  onNoteIllustrationDeleted?: (noteId: string) => void | Promise<void>
  onOpenHistory?: (note: Note) => void
  isOverlay?: boolean
}

function NotesMasonryGrid({
  pinnedNotes,
  unpinnedNotes,
  pinnedLabel,
  onGridReorder,
  ...cardProps
}: {
  pinnedNotes: Note[]
  unpinnedNotes: Note[]
  pinnedLabel: string
  onGridReorder?: (orderedIds: string[]) => void | Promise<void>
} & Omit<GridCardSharedProps, 'note' | 'index' | 'isOverlay'>) {
  const [activeId, setActiveId] = useState<string | null>(null)

  const displayNotes = useMemo(
    () => [...pinnedNotes, ...unpinnedNotes],
    [pinnedNotes, unpinnedNotes],
  )

  const activeNote = useMemo(
    () => displayNotes.find((n) => n.id === activeId) ?? null,
    [displayNotes, activeId],
  )

  const sensors = useSensors(
    useSensor(PointerSensor, { activationConstraint: { distance: 8 } }),
    useSensor(TouchSensor, { activationConstraint: { delay: 200, tolerance: 8 } }),
  )

  const handleDragStart = useCallback((event: DragStartEvent) => {
    setActiveId(event.active.id as string)
  }, [])

  const handleDragEnd = useCallback(
    (event: DragEndEvent) => {
      const { active, over } = event
      setActiveId(null)
      if (!over || active.id === over.id || !onGridReorder) return

      const activeIdx = displayNotes.findIndex((n) => n.id === active.id)
      const overIdx = displayNotes.findIndex((n) => n.id === over.id)
      if (activeIdx === -1 || overIdx === -1) return

      const activeNoteItem = displayNotes[activeIdx]
      const overNoteItem = displayNotes[overIdx]
      if (activeNoteItem.isPinned !== overNoteItem.isPinned) return

      const reordered = arrayMove(displayNotes, activeIdx, overIdx)
      onGridReorder(reordered.map((n) => n.id))
    },
    [displayNotes, onGridReorder],
  )

  const sortEnabled = Boolean(onGridReorder)

  return (
    <DndContext
      id="notes-grid-masonry"
      sensors={sensors}
      collisionDetection={closestCenter}
      onDragStart={handleDragStart}
      onDragEnd={handleDragEnd}
    >
      <div className="max-w-6xl mx-auto space-y-8">
        {pinnedNotes.length > 0 && (
          <div>
            <h2 className="text-[10px] font-bold text-muted-foreground tracking-widest uppercase mb-4 px-1">
              {pinnedLabel}
            </h2>
            <NotesGridSection
              notes={pinnedNotes}
              sortEnabled={sortEnabled}
              {...cardProps}
            />
          </div>
        )}
        {unpinnedNotes.length > 0 && (
          <NotesGridSection
            notes={unpinnedNotes}
            sortEnabled={sortEnabled}
            indexOffset={pinnedNotes.length}
            {...cardProps}
          />
        )}
      </div>

      <DragOverlay dropAnimation={{ duration: 200, easing: 'ease' }}>
        {activeNote ? (
          <div className="cursor-grabbing shadow-2xl rounded-2xl opacity-95">
            <GridCard note={activeNote} index={0} isOverlay {...cardProps} />
          </div>
        ) : null}
      </DragOverlay>
    </DndContext>
  )
}

function NotesGridSection({
  notes,
  sortEnabled,
  indexOffset = 0,
  untitled,
  allLabels,
  notebooks,
  aiIllustrationEnabled,
  onOpen,
  onTogglePin,
  onDeleteNote,
  onMoveToNotebook,
  onNoteIllustrationGenerated,
  className,
}: Omit<GridCardSharedProps, 'note' | 'index' | 'isOverlay'> & {
  notes: Note[]
  sortEnabled?: boolean
  indexOffset?: number
  className?: string
}) {
  const ids = useMemo(() => notes.map((n) => n.id), [notes])

  const grid = (
    <div className={cn('grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6 items-stretch', className)}>
      {notes.map((note, index) =>
        sortEnabled ? (
          <SortableGridCard
            key={note.id}
            note={note}
            index={indexOffset + index}
            untitled={untitled}
            allLabels={allLabels}
            notebooks={notebooks}
            aiIllustrationEnabled={aiIllustrationEnabled}
            onOpen={onOpen}
            onTogglePin={onTogglePin}
            onDeleteNote={onDeleteNote}
            onMoveToNotebook={onMoveToNotebook}
            onNoteIllustrationGenerated={onNoteIllustrationGenerated}
          />
        ) : (
          <GridCard
            key={note.id}
            note={note}
            index={indexOffset + index}
            untitled={untitled}
            allLabels={allLabels}
            notebooks={notebooks}
            aiIllustrationEnabled={aiIllustrationEnabled}
            onOpen={onOpen}
            onTogglePin={onTogglePin}
            onDeleteNote={onDeleteNote}
            onMoveToNotebook={onMoveToNotebook}
            onNoteIllustrationGenerated={onNoteIllustrationGenerated}
          />
        ),
      )}
    </div>
  )

  if (!sortEnabled) return grid

  return (
    <SortableContext items={ids} strategy={rectSortingStrategy}>
      {grid}
    </SortableContext>
  )
}

const SortableGridCard = memo(function SortableGridCard(props: GridCardSharedProps) {
  const { attributes, listeners, setNodeRef, transform, transition, isDragging } = useSortable({
    id: props.note.id,
  })

  const style: React.CSSProperties = {
    transform: CSS.Transform.toString(transform),
    transition,
  }

  return (
    <div
      ref={setNodeRef}
      style={style}
      {...attributes}
      {...listeners}
      className={cn(
        'touch-none cursor-grab active:cursor-grabbing h-full',
        isDragging && 'opacity-40',
      )}
    >
      <GridCard {...props} />
    </div>
  )
})

const GridCard = memo(function GridCard({
  note,
  index,
  untitled,
  allLabels,
  notebooks,
  aiIllustrationEnabled,
  onOpen,
  onTogglePin,
  onDeleteNote,  onMoveToNotebook,
  onNoteIllustrationGenerated,
  onNoteIllustrationDeleted,
  onOpenHistory,
  isOverlay = false,
}: GridCardSharedProps) {
  const router = useRouter()
  const [brainstormNote, setBrainstormNote] = useState<Note | null>(null)

  const handleBrainstormConfirm = (notebookId: string | null) => {
    if (!brainstormNote) return
    const seed = `${brainstormNote.title || untitled}\n\n${getNotePlainExcerpt(brainstormNote, 200)}`.trim()
    const params = new URLSearchParams({ seed: seed.slice(0, 300), sourceNoteId: brainstormNote.id })
    if (notebookId) params.set('notebookId', notebookId)
    router.push(`/brainstorm?${params.toString()}`)
    setBrainstormNote(null)
  }
  const { t, language } = useLanguage()
  const hydrated = useHydrated()
  const title = getNoteDisplayTitle(note, untitled)
  const excerpt = getNotePlainExcerpt(note, 110)
  const formattedDate = formatGridCardDate(note.updatedAt, language)

  const handlePinClick = (e: React.MouseEvent) => {
    e.stopPropagation()
    onTogglePin?.(note)
  }

  const handleDeleteClick = (e: React.MouseEvent) => {
    e.stopPropagation()
    onDeleteNote?.(note)
  }

  const handleBrainstormClick = (e: React.MouseEvent) => {
    e.stopPropagation()
    setBrainstormNote(note)
  }

  return (
    <>
    <motion.div
      initial={isOverlay || !hydrated ? false : { opacity: 0, y: 15 }}
      animate={isOverlay ? undefined : { opacity: 1, y: 0 }}
      transition={isOverlay || !hydrated ? undefined : { delay: 0.04 * index, duration: 0.5 }}
      onClick={() => onOpen(note)}
      className="bg-card/60 border border-border/40 rounded-2xl overflow-hidden hover:shadow-md hover:border-brand-accent/30 transition-all duration-300 group/card cursor-pointer flex flex-col relative h-full"
    >
      <div className="aspect-[16/10] shrink-0 bg-muted/30 border-b border-border/20 overflow-hidden relative">
        <NoteGridThumbnail
          note={note}
          aiIllustrationEnabled={aiIllustrationEnabled}
          onNoteIllustrationGenerated={onNoteIllustrationGenerated}
          onNoteIllustrationDeleted={onNoteIllustrationDeleted}
        />
        {note.isPinned && (
          <div className="absolute top-3 start-3 bg-background/90 backdrop-blur-sm p-1.5 rounded-full shadow-sm border border-border/40 text-amber-500">
            <Pin size={11} className="fill-amber-500" />
          </div>
        )}
        {note.historyEnabled && (
          <div className="absolute top-3 end-3 bg-emerald-500/15 dark:bg-emerald-500/25 border border-emerald-500/20 p-1 rounded-full shadow-sm backdrop-blur-sm text-emerald-600 dark:text-emerald-400">
            <History size={11} />
          </div>
        )}
      </div>
      <div className="p-5 flex flex-col flex-1 min-h-[11.5rem]">
        <div className="space-y-2.5 flex-1">
          <div className="min-h-[1.125rem]">
            <NoteLabelsRow labelNames={note.labels} allLabels={allLabels} max={2} />
          </div>
          <h3 className="font-memento-serif text-base font-semibold text-foreground leading-snug truncate group-hover/card:text-brand-accent transition-colors">
            {title}
          </h3>
          <p className="text-xs text-muted-foreground leading-relaxed line-clamp-3 font-light min-h-[3.75rem]">
            {excerpt || '\u00A0'}
          </p>
        </div>
        <div className="flex items-center justify-between pt-3 mt-auto border-t border-foreground/[0.03] dark:border-white/[0.03] text-[9.5px] text-muted-foreground font-medium uppercase tracking-wider">
          <span>{formattedDate}</span>
          <div className="flex items-center gap-1 opacity-100 sm:opacity-0 sm:group-hover/card:opacity-100 transition-opacity">
            <button
              type="button"
              onClick={handleBrainstormClick}
              className="p-1 px-2 rounded-full hover:bg-brand-accent/10 text-brand-accent transition-all flex items-center gap-1 cursor-pointer"
              title={t('notes.brainstormThisIdea') || 'Brainstormer cette idée'}
              aria-label={t('notes.brainstormThisIdeaAria') || t('notes.brainstormThisIdea') || 'Brainstormer cette idée'}
            >
              <Wind size={11} />
            </button>
            <button
              type="button"
              onClick={handlePinClick}
              className="p-1 px-2 rounded-full hover:bg-muted text-foreground transition-all cursor-pointer"
              title={note.isPinned ? (t('notes.unpin') || 'Désépingler') : (t('notes.pin') || 'Épingler')}
              aria-label={note.isPinned ? (t('notes.unpin') || 'Désépingler') : (t('notes.pin') || 'Épingler')}
            >
              <Pin size={11} className={note.isPinned ? 'fill-amber-500 text-amber-500' : ''} />
            </button>
            {onMoveToNotebook && (
              <MoveToNotebookPicker
                notebooks={notebooks}
                currentNotebookId={note.notebookId}
                onSelect={(notebookId) => onMoveToNotebook(note, notebookId)}
                align="end"
                preferDropUp
              >
                <button
                  type="button"
                  className="p-1 px-2 rounded-full hover:bg-muted text-foreground transition-all cursor-pointer"
                  title={t('notebookSuggestion.moveToNotebook') || 'Déplacer vers…'}
                  aria-label={t('notebookSuggestion.moveToNotebook') || 'Déplacer vers…'}
                >
                  <FolderOpen size={11} />
                </button>
              </MoveToNotebookPicker>
            )}
            {onOpenHistory && (
              <button
                type="button"
                onClick={(e) => { e.stopPropagation(); onOpenHistory(note) }}
                className={cn(
                  "p-1 px-2 rounded-full transition-all cursor-pointer",
                  note.historyEnabled
                    ? "text-emerald-500 hover:bg-emerald-500/10"
                    : "text-muted-foreground/60 hover:bg-muted hover:text-foreground"
                )}
                title={note.historyEnabled ? (t('notes.history') || 'Historique') : (t('notes.enableHistory') || "Activer l'historique")}
                aria-label={note.historyEnabled ? (t('notes.history') || 'Historique') : (t('notes.enableHistory') || "Activer l'historique")}
              >
                <History size={11} />
              </button>
            )}
            <button
              type="button"
              onClick={handleDeleteClick}
              className="p-1 px-2 rounded-full hover:bg-rose-50 dark:hover:bg-rose-500/10 text-rose-500 transition-all cursor-pointer"
              title={t('notes.delete') || 'Supprimer'}
              aria-label={t('notes.delete') || 'Supprimer'}
            >
              <Trash2 size={11} />
            </button>
          </div>
        </div>
      </div>
    </motion.div>

      <BrainstormConfirmDialog
        open={brainstormNote !== null}
        onClose={() => setBrainstormNote(null)}
        onConfirm={(notebookId) => { if (brainstormNote) handleBrainstormConfirm(notebookId) }}
        note={brainstormNote}
      />
    </>
  )
})
