'use client'

import React, { useState, useEffect, useRef, useMemo } from 'react'
import { trpc } from '@/lib/trpc/client'
import { toast } from 'sonner'
import { cn } from '@/lib/utils'
import {
  DndContext,
  closestCenter,
  KeyboardSensor,
  PointerSensor,
  useSensor,
  useSensors,
  type DragEndEvent,
} from '@dnd-kit/core'
import {
  arrayMove,
  SortableContext,
  sortableKeyboardCoordinates,
  useSortable,
  verticalListSortingStrategy,
} from '@dnd-kit/sortable'
import { CSS } from '@dnd-kit/utilities'
import { AnimatePresence, motion } from 'motion/react'
import { Button } from '@/components/ui/button'
import { Badge } from '@/components/ui/badge'
import { Card, CardContent, CardHeader, CardTitle, CardDescription } from '@/components/ui/card'
import { Skeleton } from '@/components/ui/skeleton'
import {
  Sheet,
  SheetContent,
  SheetHeader,
  SheetTitle,
  SheetDescription,
} from '@/components/ui/sheet'
import { Input } from '@/components/ui/input'
import { Label } from '@/components/ui/label'
import { Textarea } from '@/components/ui/textarea'
import { Slider } from '@/components/ui/slider'
import { Switch } from '@/components/ui/switch'
import { ScoreExplainerDialog } from '@/components/shared/score-explainer-dialog'
import { csvCell } from '@/lib/csv'
import {
  Collapsible,
  CollapsibleContent,
  CollapsibleTrigger,
} from '@/components/ui/collapsible'
import {
  GripVertical,
  BarChart3,
  Calculator,
  Loader2,
  RefreshCw,
  Sparkles,
  ExternalLink,
  ChevronDown,
  Settings2,
  Download,
} from 'lucide-react'
import type { RankedProjectEntry } from '@/server/services/ai-ranking'
import { CountryDisplay } from '@/components/shared/country-display'

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

type RankingDashboardProps = {
  competitionId: string
  roundId: string
}

type ProjectInfo = {
  title: string
  teamName: string | null
  country: string | null
}

type JurorScore = {
  jurorName: string
  globalScore: number | null
  decision: boolean | null
}

type SortableProjectRowProps = {
  projectId: string
  currentRank: number
  entry: (RankedProjectEntry & { originalIndex?: number }) | undefined
  projectInfo: ProjectInfo | undefined
  jurorScores: JurorScore[] | undefined
  rawAverage: number | null
  balancedAverage: number | null
  rawPassRate: number | null
  balancedPassRate: number | null
  useBalanced: boolean
  useBalancedPassRate: boolean
  onSelect: () => void
  isSelected: boolean
  originalRank: number | undefined // from snapshotOrder — always in sync with localOrder
}

// ─── Sub-component: SortableProjectRow ────────────────────────────────────────

function SortableProjectRow({
  projectId,
  currentRank,
  entry,
  projectInfo,
  jurorScores,
  rawAverage,
  balancedAverage,
  rawPassRate,
  balancedPassRate,
  useBalanced,
  useBalancedPassRate,
  onSelect,
  isSelected,
  originalRank,
}: SortableProjectRowProps) {
  const {
    attributes,
    listeners,
    setNodeRef,
    transform,
    transition,
    isDragging,
  } = useSortable({ id: projectId })

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

  // isOverridden: admin drag-reordered this project from its original snapshot position.
  // Uses snapshotOrder (set in same effect as localOrder) so they are always in sync.
  const isOverridden = originalRank !== undefined && currentRank !== originalRank

  // Compute yes count from juror scores
  const yesCount = jurorScores?.filter((j) => j.decision === true).length ?? 0
  const totalJurors = jurorScores?.length ?? entry?.evaluatorCount ?? 0

  return (
    <div
      ref={setNodeRef}
      style={style}
      onClick={onSelect}
      className={cn(
        'flex items-center gap-3 rounded-lg border bg-card p-3 cursor-pointer transition-all hover:shadow-sm',
        isDragging && 'opacity-50 shadow-lg ring-2 ring-[#de0f1e]/30',
        isSelected && 'ring-2 ring-[#de0f1e]',
      )}
    >
      {/* Drag handle */}
      <button
        className="cursor-grab touch-none text-muted-foreground hover:text-foreground flex-shrink-0"
        onClick={(e) => e.stopPropagation()}
        {...attributes}
        {...listeners}
      >
        <GripVertical className="h-4 w-4" />
      </button>

      {/* Rank badge */}
      {isOverridden ? (
        <Badge className="flex-shrink-0 bg-amber-100 text-amber-700 hover:bg-amber-100 border-amber-200 text-xs font-semibold">
          #{currentRank} (override)
        </Badge>
      ) : (
        <Badge
          className="flex-shrink-0 text-xs font-semibold"
          style={{ backgroundColor: '#053d57', color: '#fefefe' }}
        >
          #{currentRank}
        </Badge>
      )}

      {/* Project info */}
      <div className="flex-1 min-w-0">
        <p className="text-sm font-medium truncate">
          {projectInfo?.title ?? `Project …${projectId.slice(-6)}`}
        </p>
        {(projectInfo?.teamName || projectInfo?.country) && (
          <p className="text-xs text-muted-foreground truncate">
            {projectInfo.teamName}
            {projectInfo.teamName && projectInfo.country ? ' · ' : ''}
            {projectInfo.country && <CountryDisplay country={projectInfo.country} />}
          </p>
        )}
      </div>

      {/* Juror scores + advance decision */}
      <div className="flex items-center gap-3 flex-shrink-0">
        {/* Individual juror score pills */}
        {jurorScores && jurorScores.length > 0 ? (
          <div className="flex items-center gap-1" title={jurorScores.map((j) => `${j.jurorName}: ${j.globalScore ?? '—'}/10`).join('\n')}>
            {jurorScores.map((j, i) => (
              <span
                key={i}
                className={cn(
                  'inline-flex items-center justify-center rounded-md px-1.5 py-0.5 text-xs font-medium border',
                  j.globalScore != null && j.globalScore >= 8
                    ? 'bg-emerald-50 text-emerald-700 border-emerald-200'
                    : j.globalScore != null && j.globalScore >= 6
                      ? 'bg-blue-50 text-blue-700 border-blue-200'
                      : j.globalScore != null && j.globalScore >= 4
                        ? 'bg-amber-50 text-amber-700 border-amber-200'
                        : 'bg-red-50 text-red-700 border-red-200',
                )}
                title={`${j.jurorName}: ${j.globalScore ?? '—'}/10`}
              >
                {j.globalScore ?? '—'}
              </span>
            ))}
          </div>
        ) : null}

        {/* Active score chip (balanced if toggle on, otherwise raw) */}
        {(() => {
          const active = useBalanced && balancedAverage != null ? balancedAverage : rawAverage
          if (active == null) return null
          const label = useBalanced && balancedAverage != null ? 'Bal' : 'Raw'
          return (
            <span
              className="inline-flex items-baseline gap-1 rounded-md border bg-muted/50 px-2 py-0.5 text-xs tabular-nums"
              title={`${label === 'Bal' ? 'Juror-balanced average' : 'Raw juror average'} (factored into rank)`}
            >
              <span className="text-[10px] uppercase tracking-wide text-muted-foreground">{label}</span>
              <span className="font-semibold">{active.toFixed(2)}</span>
            </span>
          )
        })()}

        {/* Active pass rate chip — only when advance votes factor into rank */}
        {useBalancedPassRate && (() => {
          const active = balancedPassRate ?? rawPassRate
          if (active == null) return null
          return (
            <span
              className="inline-flex items-baseline gap-1 rounded-md border bg-muted/50 px-2 py-0.5 text-xs tabular-nums"
              title="Harshness-corrected approval rate (factored into rank)"
            >
              <span className="text-[10px] uppercase tracking-wide text-muted-foreground">Yes%</span>
              <span className="font-semibold">{Math.round(active * 100)}%</span>
            </span>
          )
        })()}

        {/* Advance decision indicator */}
        <div className={cn(
          'inline-flex items-center gap-1 rounded-full px-2 py-0.5 text-xs font-medium',
          totalJurors === 0
            ? 'bg-gray-100 text-gray-500'
            : yesCount === totalJurors
              ? 'bg-emerald-100 text-emerald-700'
              : yesCount > 0
                ? 'bg-amber-100 text-amber-700'
                : 'bg-red-100 text-red-600',
        )}>
          {totalJurors > 0 ? (
            <>{yesCount}/{totalJurors} Yes</>
          ) : (
            <>0 jurors</>
          )}
        </div>
      </div>
    </div>
  )
}

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

export function RankingDashboard({ competitionId: _competitionId, roundId }: RankingDashboardProps) {
  // ─── State ────────────────────────────────────────────────────────────────
  const [selectedProjectId, setSelectedProjectId] = useState<string | null>(null)
  const [localOrder, setLocalOrder] = useState<Record<'STARTUP' | 'BUSINESS_CONCEPT', string[]>>({
    STARTUP: [],
    BUSINESS_CONCEPT: [],
  })
  // Track the original snapshot order (projectId → 1-based rank) for override detection.
  // Updated in the same effect as localOrder so they are always in sync.
  const [snapshotOrder, setSnapshotOrder] = useState<Record<string, number>>({})
  const initialized = useRef(false)
  const pendingReorderCount = useRef(0)

  // ─── Export state ──────────────────────────────────────────────────────────
  const [exportLoading, setExportLoading] = useState(false)

  // ─── Expandable review state ──────────────────────────────────────────────
  const [expandedReviews, setExpandedReviews] = useState<Set<string>>(new Set())

  // ─── Criteria weights state ────────────────────────────────────────────────
  const [weightsOpen, setWeightsOpen] = useState(false)
  const [localWeights, setLocalWeights] = useState<Record<string, number>>({})
  const [localCriteriaText, setLocalCriteriaText] = useState<string>('')
  const [localScoreWeight, setLocalScoreWeight] = useState(5)
  const [localPassRateWeight, setLocalPassRateWeight] = useState(5)
  const [useBalanced, setUseBalanced] = useState(true)
  const [useBalancedPassRate, setUseBalancedPassRate] = useState(true)
  const weightsInitialized = useRef(false)

  // ─── Sensors ──────────────────────────────────────────────────────────────
  const sensors = useSensors(
    useSensor(PointerSensor),
    useSensor(KeyboardSensor, { coordinateGetter: sortableKeyboardCoordinates }),
  )

  // ─── tRPC queries ─────────────────────────────────────────────────────────
  const { data: snapshots, isLoading: snapshotsLoading } = trpc.ranking.listSnapshots.useQuery(
    { roundId },
    // Poll every 3s so all admins see ranking progress/completion quickly
    { refetchInterval: 3_000 },
  )

  // Derive ranking-in-progress from server state (visible to ALL admins)
  const rankingInProgress = snapshots?.[0]?.status === 'RUNNING'

  // Find the latest COMPLETED snapshot (skip RUNNING/FAILED)
  const latestCompleted = snapshots?.find((s) => s.status === 'COMPLETED')
  const latestSnapshotId = latestCompleted?.id ?? null
  const latestSnapshot = latestCompleted ?? null

  const { data: snapshot, isLoading: snapshotLoading } = trpc.ranking.getSnapshot.useQuery(
    { snapshotId: latestSnapshotId! },
    { enabled: !!latestSnapshotId },
  )

  const { data: projectStates } = trpc.roundEngine.getProjectStates.useQuery(
    { roundId },
  )

  const { data: projectDetail, isLoading: detailLoading } = trpc.project.getFullDetail.useQuery(
    { id: selectedProjectId!, roundId },
    { enabled: !!selectedProjectId },
  )

  const { data: roundData } = trpc.round.getById.useQuery({ id: roundId })

  const { data: evalScores } = trpc.ranking.roundEvaluationScores.useQuery(
    { roundId },
    { refetchInterval: 30_000 },
  )

  const { data: evalForm } = trpc.evaluation.getStageForm.useQuery(
    { roundId },
  )

  // ─── tRPC mutations ───────────────────────────────────────────────────────
  const utils = trpc.useUtils()

  const saveReorderMutation = trpc.ranking.saveReorder.useMutation({
    onMutate: () => { pendingReorderCount.current++ },
    onSettled: () => { pendingReorderCount.current-- },
    onError: (err) => toast.error(`Failed to save order: ${err.message}`),
    // Do NOT invalidate getSnapshot — would reset localOrder
  })

  const clearReordersMutation = trpc.ranking.clearReorders.useMutation({
    onSuccess: () => {
      toast.success('Reverted to system-calculated order')
      initialized.current = false // allow re-init on next snapshot load
      void utils.ranking.getSnapshot.invalidate()
    },
    onError: (err) => toast.error(`Failed to reset: ${err.message}`),
  })

  const updateRoundMutation = trpc.round.update.useMutation({
    onSuccess: () => {
      toast.success('Ranking config saved')
      void utils.round.getById.invalidate({ id: roundId })
    },
    onError: (err) => toast.error(`Failed to save: ${err.message}`),
  })

  const triggerRankMutation = trpc.ranking.triggerAutoRank.useMutation({
    onSuccess: () => {
      toast.success('Ranking complete!')
      initialized.current = false // allow re-init on next snapshot load
      void utils.ranking.listSnapshots.invalidate({ roundId })
      void utils.ranking.getSnapshot.invalidate()
    },
    onError: (err) => {
      toast.error(err.message)
      void utils.ranking.listSnapshots.invalidate({ roundId })
    },
  })

  // ─── evalConfig (advancement counts from round config) ────────────────────
  const evalConfig = useMemo(() => {
    if (!roundData?.configJson) return null
    try {
      const config = roundData.configJson as Record<string, unknown>
      const advConfig = config.advancementConfig as Record<string, unknown> | undefined
      return {
        advanceMode: (config.advanceMode as string) ?? 'count',
        advanceScoreThreshold: (config.advanceScoreThreshold as number) ?? undefined,
        startupAdvanceCount: (advConfig?.startupCount ?? config.startupAdvanceCount ?? 0) as number,
        conceptAdvanceCount: (advConfig?.conceptCount ?? config.conceptAdvanceCount ?? 0) as number,
      }
    } catch { return null }
  }, [roundData])

  // ─── rankingMap (O(1) lookup) ──────────────────────────────────────────────
  const rankingMap = useMemo(() => {
    const map = new Map<string, RankedProjectEntry & { originalIndex: number }>()
    if (!snapshot) return map
    const startup = (snapshot.startupRankingJson ?? []) as unknown as RankedProjectEntry[]
    const concept = (snapshot.conceptRankingJson ?? []) as unknown as RankedProjectEntry[]
    startup.forEach((entry, i) => map.set(entry.projectId, { ...entry, originalIndex: i + 1 }))
    concept.forEach((entry, i) => map.set(entry.projectId, { ...entry, originalIndex: i + 1 }))
    return map
  }, [snapshot])

  // ─── projectInfoMap (O(1) lookup by projectId) ────────────────────────────
  const projectInfoMap = useMemo(() => {
    const map = new Map<string, ProjectInfo>()
    if (!projectStates) return map
    for (const ps of projectStates) {
      map.set(ps.project.id, {
        title: ps.project.title,
        teamName: ps.project.teamName,
        country: ps.project.country,
      })
    }
    return map
  }, [projectStates])

  // ─── localOrder init (once, with useRef guard) ────────────────────────────
  // Wait for evalScores too — the initial sort uses balanced (juror-corrected)
  // averages, so we can't initialize until those are loaded.
  useEffect(() => {
    if (!initialized.current && snapshot && evalScores) {
      const startup = (snapshot.startupRankingJson ?? []) as unknown as RankedProjectEntry[]
      const concept = (snapshot.conceptRankingJson ?? []) as unknown as RankedProjectEntry[]

      // Deduplicate ranking entries (AI may return duplicates) — keep first occurrence
      const dedup = (arr: RankedProjectEntry[]): RankedProjectEntry[] => {
        const seen = new Set<string>()
        return arr.filter((r) => {
          if (seen.has(r.projectId)) return false
          seen.add(r.projectId)
          return true
        })
      }
      const dedupedStartup = dedup(startup)
      const dedupedConcept = dedup(concept)

      // Composite ranking score. When the advance-vote toggle is OFF, pass
      // rate is not in the formula at all — pure score-based ranking. When ON,
      // the harshness-corrected (balanced) pass rate enters at passRateWeight.
      // The score toggle independently picks balanced vs raw average. Same
      // formula used by the live re-sort effect and the threshold cutoff.
      const compositeFor = (projectId: string, rawScoreFallback: number | null | undefined): number => {
        const b = evalScores.balanced[projectId]
        const score = useBalanced && b?.balancedAverage != null ? b.balancedAverage : (rawScoreFallback ?? null)
        const scoreUnit = score != null ? Math.max(0, Math.min(1, (score - 1) / 9)) : 0
        if (!useBalancedPassRate) return scoreUnit
        const passRate = b?.balancedPassRate ?? b?.rawPassRate ?? null
        const passUnit = passRate ?? 0
        const sW = localScoreWeight
        const pW = localPassRateWeight
        const totalW = sW + pW
        if (totalW <= 0) return scoreUnit
        return (sW * scoreUnit + pW * passUnit) / totalW
      }
      dedupedStartup.sort((a, b) =>
        compositeFor(b.projectId, b.avgGlobalScore) - compositeFor(a.projectId, a.avgGlobalScore)
        || b.compositeScore - a.compositeScore)
      dedupedConcept.sort((a, b) =>
        compositeFor(b.projectId, b.avgGlobalScore) - compositeFor(a.projectId, a.avgGlobalScore)
        || b.compositeScore - a.compositeScore)

      // Track original order for override detection (same effect = always in sync)
      const order: Record<string, number> = {}
      dedupedStartup.forEach((r, i) => { order[r.projectId] = i + 1 })
      dedupedConcept.forEach((r, i) => { order[r.projectId] = i + 1 })
      setSnapshotOrder(order)

      // Apply saved reorders so the ranking persists across all admin sessions.
      // reordersJson is append-only — the latest event per category is the current order.
      const reorders = (snapshot.reordersJson as Array<{
        category: 'STARTUP' | 'BUSINESS_CONCEPT'
        orderedProjectIds: string[]
      }> | null) ?? []

      const latestStartupReorder = [...reorders].reverse().find((r) => r.category === 'STARTUP')
      const latestConceptReorder = [...reorders].reverse().find((r) => r.category === 'BUSINESS_CONCEPT')

      // Deduplicate reorder IDs too, and filter out IDs not in the current snapshot
      const dedupIds = (ids: string[], validSet: Set<string>): string[] => {
        const seen = new Set<string>()
        return ids.filter((id) => {
          if (seen.has(id) || !validSet.has(id)) return false
          seen.add(id)
          return true
        })
      }
      const startupIdSet = new Set(dedupedStartup.map((r) => r.projectId))
      const conceptIdSet = new Set(dedupedConcept.map((r) => r.projectId))

      setLocalOrder({
        STARTUP: latestStartupReorder
          ? dedupIds(latestStartupReorder.orderedProjectIds, startupIdSet)
          : dedupedStartup.map((r) => r.projectId),
        BUSINESS_CONCEPT: latestConceptReorder
          ? dedupIds(latestConceptReorder.orderedProjectIds, conceptIdSet)
          : dedupedConcept.map((r) => r.projectId),
      })

      initialized.current = true
    }
  }, [snapshot, evalScores])

  // ─── Re-sort when toggle flips OR scores refetch (after init) ────────────
  // Only resorts when no server-side manual reorder is pinned for the snapshot;
  // persisted manual reorders always win regardless of the score being used.
  // The 30-second polling on roundEvaluationScores feeds this effect — when a
  // juror submits a new evaluation, the next refetch picks it up, balanced/raw
  // averages update, and the list re-sorts in place.
  useEffect(() => {
    if (!initialized.current || !snapshot || !evalScores) return
    const reorders = (snapshot.reordersJson as Array<{
      category: 'STARTUP' | 'BUSINESS_CONCEPT'
      orderedProjectIds: string[]
    }> | null) ?? []
    const hasManualReorder =
      reorders.some((r) => r.category === 'STARTUP') ||
      reorders.some((r) => r.category === 'BUSINESS_CONCEPT')
    if (hasManualReorder) return
    const startup = (snapshot.startupRankingJson ?? []) as unknown as RankedProjectEntry[]
    const concept = (snapshot.conceptRankingJson ?? []) as unknown as RankedProjectEntry[]
    const dedup = (arr: RankedProjectEntry[]): RankedProjectEntry[] => {
      const seen = new Set<string>()
      return arr.filter((r) => {
        if (seen.has(r.projectId)) return false
        seen.add(r.projectId)
        return true
      })
    }
    const compositeFor = (projectId: string, rawScoreFallback: number | null | undefined): number => {
      const b = evalScores.balanced[projectId]
      const score = useBalanced && b?.balancedAverage != null ? b.balancedAverage : (rawScoreFallback ?? null)
      const scoreUnit = score != null ? Math.max(0, Math.min(1, (score - 1) / 9)) : 0
      if (!useBalancedPassRate) return scoreUnit
      const passRate = b?.balancedPassRate ?? b?.rawPassRate ?? null
      const passUnit = passRate ?? 0
      const sW = localScoreWeight
      const pW = localPassRateWeight
      const totalW = sW + pW
      if (totalW <= 0) return scoreUnit
      return (sW * scoreUnit + pW * passUnit) / totalW
    }
    const sortedStartup = dedup(startup).sort((a, b) =>
      compositeFor(b.projectId, b.avgGlobalScore) - compositeFor(a.projectId, a.avgGlobalScore)
      || b.compositeScore - a.compositeScore)
    const sortedConcept = dedup(concept).sort((a, b) =>
      compositeFor(b.projectId, b.avgGlobalScore) - compositeFor(a.projectId, a.avgGlobalScore)
      || b.compositeScore - a.compositeScore)
    setLocalOrder({
      STARTUP: sortedStartup.map((r) => r.projectId),
      BUSINESS_CONCEPT: sortedConcept.map((r) => r.projectId),
    })
  }, [useBalanced, useBalancedPassRate, evalScores, snapshot, localScoreWeight, localPassRateWeight])

  // ─── numericCriteria from eval form ─────────────────────────────────────
  const numericCriteria = useMemo(() => {
    if (!evalForm?.criteriaJson) return []
    return (evalForm.criteriaJson as Array<{ id: string; label: string; type?: string; scale?: number | string }>)
      .filter((c) => !c.type || c.type === 'numeric')
  }, [evalForm])

  // ─── Init local weights + criteriaText from round config ──────────────────
  // useBalanced is hydrated on every roundData refetch (it has its own toggle
  // that persists immediately), so it sits outside the once-only guard.
  useEffect(() => {
    if (!roundData?.configJson) return
    const cfg = roundData.configJson as Record<string, unknown>
    setUseBalanced((cfg.useBalancedRanking as boolean | undefined) ?? true)
    setUseBalancedPassRate((cfg.useBalancedPassRate as boolean | undefined) ?? true)
    if (weightsInitialized.current) return
    const saved = (cfg.criteriaWeights ?? {}) as Record<string, number>
    setLocalWeights(saved)
    setLocalCriteriaText((cfg.rankingCriteria as string) ?? '')
    setLocalScoreWeight((cfg.scoreWeight as number) ?? 5)
    setLocalPassRateWeight((cfg.passRateWeight as number) ?? 5)
    weightsInitialized.current = true
  }, [roundData])

  // ─── Persist the balanced-ranking toggle immediately ─────────────────────
  const persistUseBalanced = (next: boolean) => {
    setUseBalanced(next)
    if (!roundData?.configJson) return
    const cfg = roundData.configJson as Record<string, unknown>
    updateRoundMutation.mutate({
      id: roundId,
      configJson: { ...cfg, useBalancedRanking: next },
    })
  }

  const persistUseBalancedPassRate = (next: boolean) => {
    setUseBalancedPassRate(next)
    if (!roundData?.configJson) return
    const cfg = roundData.configJson as Record<string, unknown>
    updateRoundMutation.mutate({
      id: roundId,
      configJson: { ...cfg, useBalancedPassRate: next },
    })
  }

  // ─── Save weights + criteria text to round config ─────────────────────────
  const saveRankingConfig = () => {
    if (!roundData?.configJson) return
    const cfg = roundData.configJson as Record<string, unknown>
    updateRoundMutation.mutate({
      id: roundId,
      configJson: {
        ...cfg,
        criteriaWeights: localWeights,
        rankingCriteria: localCriteriaText,
        scoreWeight: localScoreWeight,
        passRateWeight: localPassRateWeight,
      },
    })
  }

  // Derive ranking mode from criteria text
  const isFormulaMode = !localCriteriaText.trim()

  // ─── handleDragEnd ────────────────────────────────────────────────────────
  function handleDragEnd(category: 'STARTUP' | 'BUSINESS_CONCEPT', event: DragEndEvent) {
    const { active, over } = event
    if (!over || active.id === over.id) return
    setLocalOrder((prev) => {
      const ids = prev[category]
      const newIds = arrayMove(
        ids,
        ids.indexOf(active.id as string),
        ids.indexOf(over.id as string),
      )
      saveReorderMutation.mutate({
        snapshotId: latestSnapshotId!,
        category,
        orderedProjectIds: newIds,
      })
      return { ...prev, [category]: newIds }
    })
  }

  // ─── handleExport ──────────────────────────────────────────────────────────
  async function handleExportScores() {
    setExportLoading(true)
    try {
      const result = await utils.export.projectScores.fetch({ roundId })
      if (!result.data || result.data.length === 0) {
        toast.error('No data to export')
        return
      }
      const headers = result.columns
      const csvRows = [
        headers.map((h: string) => csvCell(h)).join(','),
        ...result.data.map((row: Record<string, unknown>) =>
          headers.map((h: string) => csvCell(row[h])).join(','),
        ),
      ]
      const blob = new Blob([csvRows.join('\n')], { type: 'text/csv;charset=utf-8;' })
      const url = URL.createObjectURL(blob)
      const a = document.createElement('a')
      a.href = url
      a.download = `round-scores-${roundId.slice(-8)}.csv`
      a.click()
      URL.revokeObjectURL(url)
      toast.success('CSV exported')
    } catch (err) {
      toast.error(`Export failed: ${err instanceof Error ? err.message : 'Unknown error'}`)
    } finally {
      setExportLoading(false)
    }
  }

  // ─── Loading state ────────────────────────────────────────────────────────
  if (snapshotsLoading || snapshotLoading) {
    return (
      <div className="space-y-4">
        <Skeleton className="h-24 w-full rounded-lg" />
        <Skeleton className="h-48 w-full rounded-lg" />
        <Skeleton className="h-48 w-full rounded-lg" />
      </div>
    )
  }

  // ─── Empty state ──────────────────────────────────────────────────────────
  if (!latestSnapshotId) {
    return (
      <div className="space-y-4">
        <Card>
          <CardContent className="flex flex-col items-center justify-center gap-4 py-12 text-center">
            {rankingInProgress ? (
              <>
                <Loader2 className="h-10 w-10 text-blue-500 animate-spin" />
                <div>
                  <p className="font-medium">Ranking in progress&hellip;</p>
                  <p className="mt-1 text-sm text-muted-foreground">
                    This may take a minute. You can continue working — results will appear automatically.
                  </p>
                </div>
                <div className="h-2 w-48 rounded-full bg-blue-100 overflow-hidden">
                  <div className="h-full w-full rounded-full bg-blue-500 animate-pulse" />
                </div>
              </>
            ) : (
              <>
                <BarChart3 className="h-10 w-10 text-muted-foreground" />
                <div>
                  <p className="font-medium">No ranking available yet</p>
                  <p className="mt-1 text-sm text-muted-foreground">
                    Run ranking from the Config tab to generate results, or trigger it now.
                  </p>
                </div>
                <Button
                  onClick={() => triggerRankMutation.mutate({ roundId })}
                  disabled={triggerRankMutation.isPending}
                >
                  {isFormulaMode ? (
                    <Calculator className="mr-2 h-4 w-4" />
                  ) : (
                    <Sparkles className="mr-2 h-4 w-4" />
                  )}
                  {isFormulaMode ? 'Run Ranking Now' : 'Run AI Ranking Now'}
                </Button>
              </>
            )}
          </CardContent>
        </Card>
      </div>
    )
  }

  // ─── Main content ─────────────────────────────────────────────────────────
  const categoryLabels: Record<'STARTUP' | 'BUSINESS_CONCEPT', string> = {
    STARTUP: 'Startups',
    BUSINESS_CONCEPT: 'Business Concepts',
  }

  return (
    <>
      <div className="space-y-6">
        {/* Header card */}
        <Card>
          <CardHeader className="flex flex-row items-start justify-between gap-4">
            <div className="flex-1 min-w-0">
              <CardTitle className="text-base">Latest Ranking Snapshot</CardTitle>
              {latestSnapshot && (
                <CardDescription className="mt-1 space-y-0.5">
                  <span>
                    Created{' '}
                    {new Date(latestSnapshot.createdAt).toLocaleString(undefined, {
                      dateStyle: 'medium',
                      timeStyle: 'short',
                    })}
                    {latestSnapshot.triggeredBy?.name && ` by ${latestSnapshot.triggeredBy.name}`}
                    {' · '}
                    {latestSnapshot.triggerType}
                  </span>
                  {latestSnapshot.criteriaText && (
                    <span className="block truncate text-xs">
                      Criteria: {latestSnapshot.criteriaText.slice(0, 120)}
                      {latestSnapshot.criteriaText.length > 120 ? '…' : ''}
                    </span>
                  )}
                </CardDescription>
              )}
            </div>
            <div className="flex items-center gap-2 flex-shrink-0">
              <Button
                size="sm"
                variant="outline"
                onClick={handleExportScores}
                disabled={exportLoading}
              >
                {exportLoading ? (
                  <Loader2 className="mr-2 h-4 w-4 animate-spin" />
                ) : (
                  <Download className="mr-2 h-4 w-4" />
                )}
                Export CSV
              </Button>
              <Button
                size="sm"
                variant="outline"
                onClick={() => triggerRankMutation.mutate({ roundId })}
                disabled={rankingInProgress}
              >
                {rankingInProgress ? (
                  <>
                    <Loader2 className="mr-2 h-4 w-4 animate-spin" />
                    Ranking&hellip;
                  </>
                ) : isFormulaMode ? (
                  <>
                    <Calculator className="mr-2 h-4 w-4" />
                    Run Ranking
                  </>
                ) : (
                  <>
                    <Sparkles className="mr-2 h-4 w-4" />
                    Run AI Ranking
                  </>
                )}
              </Button>
              {/* Advance Top N removed — use Finalization tab instead */}
            </div>
          </CardHeader>
        </Card>

        {/* Ranking display toggles — always visible, persist per round */}
        <Card>
          <CardContent className="py-3">
            <div className="grid gap-3 sm:grid-cols-2">
              <div className="flex items-center justify-between gap-4 rounded-lg border p-3">
                <div className="flex flex-col min-w-0">
                  <span className="text-sm font-medium">Balance juror grading style (score)</span>
                  <span className="text-xs text-muted-foreground">
                    Corrects for harshness on average scores. Off uses raw averages.
                  </span>
                </div>
                <Switch checked={useBalanced} onCheckedChange={persistUseBalanced} />
              </div>
              <div className="flex items-center justify-between gap-4 rounded-lg border p-3">
                <div className="flex flex-col min-w-0">
                  <span className="text-sm font-medium">Factor advance votes into ranking</span>
                  <span className="text-xs text-muted-foreground">
                    When on, yes/no votes (harshness-corrected) blend with the score via the
                    score/pass-rate weights. When off, the ranking is score-only.
                  </span>
                </div>
                <Switch checked={useBalancedPassRate} onCheckedChange={persistUseBalancedPassRate} />
              </div>
            </div>
          </CardContent>
        </Card>

        {/* Ranking Configuration: criteria text + weights */}
        <Collapsible open={weightsOpen} onOpenChange={setWeightsOpen}>
          <Card>
            <CollapsibleTrigger asChild>
              <CardHeader className="cursor-pointer flex flex-row items-center justify-between gap-4 hover:bg-muted/50 transition-colors">
                <div className="flex items-center gap-2">
                  <Settings2 className="h-4 w-4 text-muted-foreground" />
                  <div>
                    <CardTitle className="text-base">Ranking Configuration</CardTitle>
                    <CardDescription className="mt-0.5">Criteria text, per-criterion weights, and bias correction</CardDescription>
                  </div>
                </div>
                <ChevronDown className={cn('h-4 w-4 text-muted-foreground transition-transform', weightsOpen && 'rotate-180')} />
              </CardHeader>
            </CollapsibleTrigger>
            <CollapsibleContent>
              <CardContent className="space-y-5 pt-0">
                {/* Score vs Pass Rate weights */}
                <div className="space-y-3">
                  <div>
                    <Label>Formula Weights</Label>
                    <p className="text-xs text-muted-foreground">
                      Control the balance between evaluation scores and yes/no pass rate in the composite ranking
                    </p>
                  </div>
                  <div className="space-y-3">
                    <div className="flex items-center gap-4">
                      <span className="text-sm w-40 flex-shrink-0">Score Weight</span>
                      <Slider
                        min={0}
                        max={10}
                        step={1}
                        value={[localScoreWeight]}
                        onValueChange={([v]) => setLocalScoreWeight(v)}
                        className="flex-1"
                      />
                      <span className="text-sm font-mono w-6 text-right">{localScoreWeight}</span>
                    </div>
                    <div className="flex items-center gap-4">
                      <span className="text-sm w-40 flex-shrink-0">Pass Rate Weight</span>
                      <Slider
                        min={0}
                        max={10}
                        step={1}
                        value={[localPassRateWeight]}
                        onValueChange={([v]) => setLocalPassRateWeight(v)}
                        className="flex-1"
                      />
                      <span className="text-sm font-mono w-6 text-right">{localPassRateWeight}</span>
                    </div>
                  </div>
                </div>

                {/* Ranking criteria text (optional — triggers AI mode) */}
                <div className="space-y-2">
                  <Label htmlFor="rankingCriteria">AI Ranking Criteria (optional)</Label>
                  <p className="text-xs text-muted-foreground">
                    Optional: describe special ranking criteria for AI-assisted ranking.
                    Leave empty for formula-based ranking (faster, no AI cost).
                  </p>
                  <Textarea
                    id="rankingCriteria"
                    rows={3}
                    placeholder='E.g. "Prioritize innovation and ocean impact. Filter out projects with pass rate below 50%."'
                    value={localCriteriaText}
                    onChange={(e) => setLocalCriteriaText(e.target.value)}
                    className="resize-y"
                  />
                  {isFormulaMode ? (
                    <p className="text-xs text-emerald-600 flex items-center gap-1">
                      <Calculator className="h-3 w-3" /> Formula mode — ranking uses weights only, no AI calls
                    </p>
                  ) : (
                    <p className="text-xs text-amber-600 flex items-center gap-1">
                      <Sparkles className="h-3 w-3" /> AI mode — criteria will be parsed and used for ranking (uses API credits)
                    </p>
                  )}
                </div>

                {/* Per-criterion weights */}
                {numericCriteria.length > 0 && (
                  <div className="space-y-3">
                    <div>
                      <Label>Criteria Weights</Label>
                      <p className="text-xs text-muted-foreground">
                        Set relative importance of each evaluation criterion (0 = ignore, 10 = highest priority)
                      </p>
                    </div>
                    <div className="space-y-3">
                      {numericCriteria.map((c) => (
                        <div key={c.id} className="flex items-center gap-4">
                          <span className="text-sm w-40 truncate flex-shrink-0" title={c.label}>{c.label}</span>
                          <Slider
                            min={0}
                            max={10}
                            step={1}
                            value={[localWeights[c.id] ?? 1]}
                            onValueChange={([v]) => setLocalWeights((prev) => ({ ...prev, [c.id]: v }))}
                            className="flex-1"
                          />
                          <span className="text-sm font-mono w-6 text-right">{localWeights[c.id] ?? 1}</span>
                        </div>
                      ))}
                    </div>
                  </div>
                )}

                <div className="flex items-center gap-2 pt-2 border-t">
                  <Button
                    size="sm"
                    onClick={saveRankingConfig}
                    disabled={updateRoundMutation.isPending}
                  >
                    {updateRoundMutation.isPending ? <Loader2 className="h-4 w-4 mr-2 animate-spin" /> : null}
                    Save Configuration
                  </Button>
                  <p className="text-xs text-muted-foreground">
                    Weights are applied when ranking is run. Z-score normalization corrects for juror bias automatically.
                  </p>
                </div>
              </CardContent>
            </CollapsibleContent>
          </Card>
        </Collapsible>

        {/* Ranking in-progress banner */}
        {rankingInProgress && (
          <Card className="border-blue-200 bg-blue-50">
            <CardContent className="flex items-center gap-3 py-4">
              <Loader2 className="h-5 w-5 animate-spin text-blue-600 flex-shrink-0" />
              <div className="flex-1 min-w-0">
                <p className="text-sm font-medium text-blue-900">
                  Ranking in progress&hellip;
                </p>
                <p className="text-xs text-blue-700">
                  This may take a minute. You can continue working — results will appear automatically.
                </p>
              </div>
              <div className="h-1.5 w-32 rounded-full bg-blue-200 overflow-hidden flex-shrink-0">
                <div className="h-full w-full rounded-full bg-blue-500 animate-pulse" />
              </div>
            </CardContent>
          </Card>
        )}

        {/* Per-category sections */}
        {(['STARTUP', 'BUSINESS_CONCEPT'] as const).map((category) => (
          <Card key={category}>
            <CardHeader className="flex-row items-center justify-between space-y-0">
              <CardTitle className="text-sm font-semibold uppercase tracking-wide text-muted-foreground">
                {categoryLabels[category]}
                {evalConfig && evalConfig.advanceMode === 'threshold' && evalConfig.advanceScoreThreshold != null ? (
                  <span className="ml-2 text-xs font-normal normal-case">
                    (Score &ge; {evalConfig.advanceScoreThreshold} advance)
                  </span>
                ) : evalConfig && (category === 'STARTUP' ? evalConfig.startupAdvanceCount : evalConfig.conceptAdvanceCount) > 0 ? (
                  <span className="ml-2 text-xs font-normal normal-case">
                    (Top {category === 'STARTUP' ? evalConfig.startupAdvanceCount : evalConfig.conceptAdvanceCount} advance)
                  </span>
                ) : null}
              </CardTitle>
              {(() => {
                const reorders = (snapshot?.reordersJson as Array<{
                  category: 'STARTUP' | 'BUSINESS_CONCEPT'
                }> | null) ?? []
                const hasManualOrder = reorders.some((r) => r.category === category)
                if (!hasManualOrder || !latestSnapshotId) return null
                return (
                  <Button
                    variant="outline"
                    size="sm"
                    className="h-7 text-xs"
                    disabled={clearReordersMutation.isPending}
                    onClick={() => clearReordersMutation.mutate({ snapshotId: latestSnapshotId, category })}
                    title="Discard manual drag-reorder for this category and revert to system-calculated order"
                  >
                    <RefreshCw className="h-3 w-3 mr-1" />
                    Reset to system order
                  </Button>
                )
              })()}
            </CardHeader>
            <CardContent>
              {localOrder[category].length === 0 ? (
                <p className="text-sm text-muted-foreground">
                  No {category === 'STARTUP' ? 'startup' : 'business concept'} projects ranked.
                </p>
              ) : (() => {
                // Precompute cutoff index so it only shows ONCE
                const isThresholdMode = evalConfig?.advanceMode === 'threshold' && evalConfig.advanceScoreThreshold != null
                const advanceCount = isThresholdMode ? 0 : (category === 'STARTUP'
                  ? (evalConfig?.startupAdvanceCount ?? 0)
                  : (evalConfig?.conceptAdvanceCount ?? 0))
                const threshold = evalConfig?.advanceScoreThreshold ?? 0

                // Effective ranking score for the threshold cutoff. Mirrors
                // the composite formula used by the sort. When the advance-vote
                // toggle is OFF, threshold compares against the score directly
                // (back in 1-10 space) so the cutoff matches admin intent.
                const effectiveScore = (id: string) => {
                  const e = rankingMap.get(id)
                  const b = evalScores?.balanced[id]
                  const score = useBalanced && b?.balancedAverage != null ? b.balancedAverage : (e?.avgGlobalScore ?? null)
                  if (!useBalancedPassRate) return score ?? 0
                  const scoreUnit = score != null ? Math.max(0, Math.min(1, (score - 1) / 9)) : 0
                  const passRate = b?.balancedPassRate ?? b?.rawPassRate ?? null
                  const passUnit = passRate ?? 0
                  const sW = localScoreWeight
                  const pW = localPassRateWeight
                  const totalW = sW + pW
                  const composite = totalW <= 0 ? scoreUnit : (sW * scoreUnit + pW * passUnit) / totalW
                  return composite * 9 + 1
                }

                let cutoffIndex = -1
                if (isThresholdMode) {
                  // Find the FIRST project that does NOT meet the threshold — cutoff goes before it.
                  const firstFailIdx = localOrder[category].findIndex((id) => effectiveScore(id) < threshold)
                  if (firstFailIdx === -1) {
                    // All meet threshold — cutoff after the last one
                    cutoffIndex = localOrder[category].length - 1
                  } else if (firstFailIdx > 0) {
                    cutoffIndex = firstFailIdx - 1
                  }
                } else if (advanceCount > 0) {
                  cutoffIndex = advanceCount - 1
                }

                // Check if admin has reordered this category
                const reorders = (snapshot?.reordersJson as Array<{
                  category: 'STARTUP' | 'BUSINESS_CONCEPT'
                  orderedProjectIds: string[]
                }> | null) ?? []
                const hasReorders = reorders.some((r) => r.category === category)

                return (
                <DndContext
                  sensors={sensors}
                  collisionDetection={closestCenter}
                  onDragEnd={(event) => handleDragEnd(category, event)}
                >
                  <SortableContext
                    items={localOrder[category]}
                    strategy={verticalListSortingStrategy}
                  >
                    <AnimatePresence initial={false}>
                      <div className="space-y-2">
                        {localOrder[category].map((projectId, index) => {
                          const projectScore = effectiveScore(projectId)
                          const isAdvancing = isThresholdMode
                            ? projectScore >= threshold
                            : (advanceCount > 0 && index < advanceCount)
                          const isCutoffRow = cutoffIndex >= 0 && index === cutoffIndex

                          return (
                            <React.Fragment key={projectId}>
                              <motion.div
                                layout
                                initial={{ opacity: 0, y: 20 }}
                                animate={{ opacity: 1, y: 0 }}
                                exit={{ opacity: 0, y: -20 }}
                                className={isAdvancing
                                  ? 'rounded-lg bg-emerald-50 border-l-4 border-emerald-400'
                                  : ''}
                              >
                                <SortableProjectRow
                                  projectId={projectId}
                                  currentRank={index + 1}
                                  entry={rankingMap.get(projectId)}
                                  projectInfo={projectInfoMap.get(projectId)}
                                  jurorScores={evalScores?.byProject[projectId]}
                                  rawAverage={evalScores?.balanced[projectId]?.rawAverage ?? null}
                                  balancedAverage={evalScores?.balanced[projectId]?.balancedAverage ?? null}
                                  rawPassRate={evalScores?.balanced[projectId]?.rawPassRate ?? null}
                                  balancedPassRate={evalScores?.balanced[projectId]?.balancedPassRate ?? null}
                                  useBalanced={useBalanced}
                                  useBalancedPassRate={useBalancedPassRate}
                                  onSelect={() => setSelectedProjectId(projectId)}
                                  isSelected={selectedProjectId === projectId}
                                  originalRank={hasReorders ? snapshotOrder[projectId] : undefined}
                                />
                              </motion.div>
                              {isCutoffRow && (
                                <div className="flex items-center gap-2 py-1">
                                  <div className="flex-1 border-t-2 border-dashed border-emerald-400/60" />
                                  <span className="text-xs font-medium text-emerald-600 whitespace-nowrap">
                                    Advancement cutoff — {isThresholdMode ? `Score ≥ ${threshold}` : `Top ${advanceCount}`}
                                  </span>
                                  <div className="flex-1 border-t-2 border-dashed border-emerald-400/60" />
                                </div>
                              )}
                            </React.Fragment>
                          )
                        })}
                      </div>
                    </AnimatePresence>
                  </SortableContext>
                </DndContext>
                )
              })()}
            </CardContent>
          </Card>
        ))}
      </div>

      {/* Advance dialog removed — use Finalization tab */}

      {/* Side panel Sheet */}
      <Sheet
        open={!!selectedProjectId}
        onOpenChange={(open) => {
          if (!open) setSelectedProjectId(null)
        }}
      >
        <SheetContent className="w-[480px] sm:max-w-[480px] overflow-y-auto">
          <SheetHeader>
            <SheetTitle>{projectDetail?.project.title ?? 'Project Details'}</SheetTitle>
            <SheetDescription>
              {selectedProjectId ? `ID: …${selectedProjectId.slice(-8)}` : ''}
            </SheetDescription>
            <div className="flex flex-wrap items-center gap-2 mt-1">
              {projectDetail?.project.country && (
                <Badge variant="outline" className="gap-1 text-xs">
                  <CountryDisplay country={projectDetail.project.country} />
                </Badge>
              )}
              {projectDetail?.project.teamName && (
                <Badge variant="outline" className="gap-1 text-xs">
                  {projectDetail.project.teamName}
                </Badge>
              )}
            </div>
            {selectedProjectId && (
              <a
                href={`/admin/projects/${selectedProjectId}`}
                target="_blank"
                rel="noopener noreferrer"
                className="inline-flex items-center gap-1.5 text-sm text-primary hover:underline mt-1"
              >
                <ExternalLink className="h-3.5 w-3.5" />
                View Project Page
              </a>
            )}
          </SheetHeader>

          {detailLoading ? (
            <div className="mt-6 space-y-3">
              <Skeleton className="h-16 w-full" />
              <Skeleton className="h-24 w-full" />
              <Skeleton className="h-24 w-full" />
            </div>
          ) : projectDetail ? (
            <div className="mt-6 space-y-6">
              {/* Project description (collapsible) */}
              {projectDetail.project.description && (
                <Collapsible>
                  <CollapsibleTrigger className="flex w-full items-center justify-between rounded-lg border p-3 text-left hover:bg-muted/50 transition-colors">
                    <span className="text-sm font-medium">Description</span>
                    <ChevronDown className="h-4 w-4 transition-transform data-[state=open]:rotate-180" />
                  </CollapsibleTrigger>
                  <CollapsibleContent className="mt-1 rounded-lg border bg-muted/30 p-3 text-sm whitespace-pre-wrap">
                    {projectDetail.project.description}
                  </CollapsibleContent>
                </Collapsible>
              )}

              {/* Stats summary: combined Avg card with Raw + Balanced side-by-side */}
              {projectDetail.stats && (() => {
                const raw = selectedProjectId
                  ? evalScores?.balanced[selectedProjectId]?.rawAverage ?? null
                  : null
                const balanced = selectedProjectId
                  ? evalScores?.balanced[selectedProjectId]?.balancedAverage ?? null
                  : null
                return (
                  <div className="space-y-3">
                    <div className="rounded-lg border p-3">
                      <p className="text-xs text-muted-foreground mb-2">Avg Score</p>
                      <div className="flex items-baseline gap-4 flex-wrap">
                        <div className={`flex items-baseline gap-1 ${useBalanced ? 'text-muted-foreground' : 'font-semibold'}`}>
                          <span className="text-xs">Raw</span>
                          <span className="text-lg tabular-nums">{raw != null ? raw.toFixed(2) : '—'}</span>
                          {!useBalanced && <span className="ml-1 text-[10px] text-muted-foreground">← used for ranking</span>}
                        </div>
                        <div className={`flex items-baseline gap-1 ${useBalanced ? 'font-semibold' : 'text-muted-foreground'}`}>
                          <span className="text-xs">Balanced</span>
                          <span className="text-lg tabular-nums">{balanced != null ? balanced.toFixed(2) : '—'}</span>
                          {useBalanced && <span className="ml-1 text-[10px] text-muted-foreground">← used for ranking</span>}
                        </div>
                      </div>
                      <div className="mt-2 flex justify-end">
                        <ScoreExplainerDialog />
                      </div>
                    </div>
                    <div className="grid grid-cols-2 gap-3">
                      <div className="rounded-lg border p-3 text-center">
                        <p className="text-xs text-muted-foreground">Pass Rate</p>
                        <p className="mt-1 text-lg font-semibold">
                          {projectDetail.stats.totalEvaluations > 0
                            ? `${Math.round((projectDetail.stats.yesVotes / projectDetail.stats.totalEvaluations) * 100)}%`
                            : '—'}
                        </p>
                      </div>
                      <div className="rounded-lg border p-3 text-center">
                        <p className="text-xs text-muted-foreground">Evaluators</p>
                        <p className="mt-1 text-lg font-semibold">
                          {projectDetail.stats.totalEvaluations}
                        </p>
                      </div>
                    </div>
                  </div>
                )
              })()}

              {/* Per-juror evaluations */}
              <div>
                <h4 className="mb-3 text-sm font-semibold">Juror Evaluations</h4>
                {(() => {
                  const submitted = projectDetail.assignments.filter(
                    (a) => a.evaluation?.status === 'SUBMITTED' && a.round.id === roundId,
                  )
                  if (submitted.length === 0) {
                    return (
                      <p className="text-sm text-muted-foreground">
                        No submitted evaluations for this round.
                      </p>
                    )
                  }
                  return (
                    <div className="space-y-3">
                      {submitted.map((a) => {
                        const isExpanded = expandedReviews.has(a.id)
                        return (
                          <div
                            key={a.id}
                            className="rounded-lg border p-3 cursor-pointer hover:bg-muted/50 transition-colors"
                            onClick={() => setExpandedReviews(prev => {
                              const next = new Set(prev)
                              next.has(a.id) ? next.delete(a.id) : next.add(a.id)
                              return next
                            })}
                          >
                            <div className="flex items-center justify-between">
                              <span className="font-medium text-sm">{a.user?.name ?? a.user?.email ?? 'Unknown'}</span>
                              <div className="flex items-center gap-2">
                                {a.evaluation?.decision != null && (
                                  <Badge
                                    variant={a.evaluation.decision ? 'default' : 'destructive'}
                                    className={a.evaluation.decision ? 'bg-emerald-100 text-emerald-700 hover:bg-emerald-100' : ''}
                                  >
                                    {a.evaluation.decision ? 'Yes' : 'No'}
                                  </Badge>
                                )}
                                <Badge variant="outline">Score: {a.evaluation?.globalScore?.toFixed(1) ?? '—'}</Badge>
                                {useBalanced && (() => {
                                  const userId = a.user?.id
                                  const score = a.evaluation?.globalScore
                                  if (!userId || score == null) return null
                                  const stats = evalScores?.jurorStats?.[userId]
                                  const overallMean = evalScores?.overallMean
                                  const overallStddev = evalScores?.overallStddev
                                  if (!stats || overallMean == null || overallStddev == null || overallStddev === 0) return null
                                  const z = stats.stddev > 0
                                    ? (score - stats.mean) / stats.stddev
                                    : (score - overallMean) / overallStddev
                                  const contributesAs = overallMean + z * overallStddev
                                  return (
                                    <span
                                      className="text-xs text-muted-foreground"
                                      title={`Their typical score in this round; rescaled contribution after juror balancing`}
                                      onClick={(e) => e.stopPropagation()}
                                    >
                                      typical {stats.mean.toFixed(2)} → contributes {contributesAs.toFixed(2)}
                                    </span>
                                  )
                                })()}
                              </div>
                            </div>
                            {isExpanded && (
                              <div className="mt-2 space-y-2 border-t pt-2">
                                {/* Per-criterion scores */}
                                {(() => {
                                  const scores = a.evaluation?.criterionScoresJson as Record<string, unknown> | null
                                  if (!scores || !evalForm?.criteriaJson) return null
                                  const criteria = evalForm.criteriaJson as Array<{
                                    id: string
                                    label: string
                                    type?: string
                                    trueLabel?: string
                                    falseLabel?: string
                                    scale?: number | string
                                  }>
                                  const rendered = criteria
                                    .map((c) => {
                                      const v = scores[c.id]
                                      if (v == null || v === '') return null
                                      let display: string
                                      if (typeof v === 'boolean') {
                                        display = v ? (c.trueLabel ?? 'Yes') : (c.falseLabel ?? 'No')
                                      } else if (typeof v === 'number') {
                                        display = String(v)
                                      } else {
                                        display = String(v)
                                      }
                                      return { label: c.label, display, type: c.type ?? 'numeric' }
                                    })
                                    .filter((x): x is { label: string; display: string; type: string } => x != null)
                                  if (rendered.length === 0) return null
                                  return (
                                    <div className="space-y-1">
                                      {rendered.map((c, i) => (
                                        <div key={i} className="flex items-start justify-between gap-3 text-xs">
                                          <span className="text-muted-foreground flex-1">{c.label}</span>
                                          <span className="font-medium tabular-nums">{c.display}</span>
                                        </div>
                                      ))}
                                    </div>
                                  )
                                })()}

                                {/* Feedback text */}
                                {a.evaluation?.feedbackText && (
                                  <p className="text-sm text-muted-foreground whitespace-pre-wrap">
                                    {a.evaluation.feedbackText}
                                  </p>
                                )}
                              </div>
                            )}
                          </div>
                        )
                      })}
                    </div>
                  )
                })()}
              </div>
            </div>
          ) : null}
        </SheetContent>
      </Sheet>
    </>
  )
}
