'use client'

import { useCallback, useEffect, useMemo, useRef, useState } from 'react'
import type { GridCell, GridResult, WordEntry, ColumnMeta } from '@/app/(admin)/ai/ocr-pipeline/types'
import { usePixelWordPositions } from './usePixelWordPositions'

const KLAUSUR_API = '/klausur-api'

interface LlmChange {
  row_index: number
  field: 'english' | 'german' | 'example'
  old: string
  new: string
}

interface StepLlmReviewProps {
  sessionId: string | null
  onNext: () => void
}

interface ReviewMeta {
  total_entries: number
  to_review: number
  skipped: number
  model: string
  skipped_indices?: number[]
}

interface StreamProgress {
  current: number
  total: number
}

const FIELD_LABELS: Record<string, string> = {
  english: 'EN',
  german: 'DE',
  example: 'Beispiel',
  source_page: 'Seite',
  marker: 'Marker',
  text: 'Text',
}

/** Map column type to WordEntry field name */
const COL_TYPE_TO_FIELD: Record<string, string> = {
  column_en: 'english',
  column_de: 'german',
  column_example: 'example',
  page_ref: 'source_page',
  column_marker: 'marker',
  column_text: 'text',
}

/** Column type → color class */
const COL_TYPE_COLOR: Record<string, string> = {
  column_en: 'text-blue-600 dark:text-blue-400',
  column_de: 'text-green-600 dark:text-green-400',
  column_example: 'text-orange-600 dark:text-orange-400',
  page_ref: 'text-cyan-600 dark:text-cyan-400',
  column_marker: 'text-gray-500 dark:text-gray-400',
  column_text: 'text-gray-700 dark:text-gray-300',
}

type RowStatus = 'pending' | 'active' | 'reviewed' | 'corrected' | 'skipped'

export function StepLlmReview({ sessionId, onNext }: StepLlmReviewProps) {
  // Core state
  const [status, setStatus] = useState<'idle' | 'loading' | 'ready' | 'running' | 'done' | 'error' | 'applied'>('idle')
  const [meta, setMeta] = useState<ReviewMeta | null>(null)
  const [changes, setChanges] = useState<LlmChange[]>([])
  const [progress, setProgress] = useState<StreamProgress | null>(null)
  const [totalDuration, setTotalDuration] = useState(0)
  const [error, setError] = useState('')
  const [accepted, setAccepted] = useState<Set<number>>(new Set())
  const [applying, setApplying] = useState(false)

  // Full vocab table state
  const [vocabEntries, setVocabEntries] = useState<WordEntry[]>([])
  const [columnsUsed, setColumnsUsed] = useState<ColumnMeta[]>([])
  const [activeRowIndices, setActiveRowIndices] = useState<Set<number>>(new Set())
  const [reviewedRows, setReviewedRows] = useState<Set<number>>(new Set())
  const [skippedRows, setSkippedRows] = useState<Set<number>>(new Set())
  const [correctedMap, setCorrectedMap] = useState<Map<number, LlmChange[]>>(new Map())

  // Image
  const [imageNaturalSize, setImageNaturalSize] = useState<{ w: number; h: number } | null>(null)

  // Overlay view state
  const [viewMode, setViewMode] = useState<'table' | 'overlay'>('table')
  const [fontScale, setFontScale] = useState(0.7)
  const [leftPaddingPct, setLeftPaddingPct] = useState(0)
  const [globalBold, setGlobalBold] = useState(false)
  const [cells, setCells] = useState<GridCell[]>([])
  const reconRef = useRef<HTMLDivElement>(null)
  const [reconWidth, setReconWidth] = useState(0)

  // Pixel-analysed word positions via shared hook
  const overlayImageUrl = sessionId
    ? `${KLAUSUR_API}/api/v1/ocr-pipeline/sessions/${sessionId}/image/cropped`
    : ''
  const cellWordPositions = usePixelWordPositions(overlayImageUrl, cells, viewMode === 'overlay')

  const tableRef = useRef<HTMLDivElement>(null)
  const activeRowRef = useRef<HTMLTableRowElement>(null)

  // Track reconstruction container width for font size calculation
  useEffect(() => {
    const el = reconRef.current
    if (!el) return
    const obs = new ResizeObserver(entries => {
      for (const entry of entries) setReconWidth(entry.contentRect.width)
    })
    obs.observe(el)
    return () => obs.disconnect()
  }, [viewMode])

  // Load session data on mount
  useEffect(() => {
    if (!sessionId) return
    loadSessionData()
  // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [sessionId])

  const loadSessionData = async () => {
    if (!sessionId) return
    setStatus('loading')
    try {
      const res = await fetch(`${KLAUSUR_API}/api/v1/ocr-pipeline/sessions/${sessionId}`)
      if (!res.ok) throw new Error(`HTTP ${res.status}`)
      const data = await res.json()

      const wordResult: GridResult | undefined = data.word_result
      if (!wordResult) {
        setError('Keine Worterkennungsdaten gefunden. Bitte zuerst Schritt 5 abschliessen.')
        setStatus('error')
        return
      }

      const entries = wordResult.vocab_entries || wordResult.entries || []
      setVocabEntries(entries)
      setColumnsUsed(wordResult.columns_used || [])
      setCells(wordResult.cells || [])

      // Check if LLM review was already run
      const llmReview = wordResult.llm_review
      if (llmReview && llmReview.changes) {
        const existingChanges: LlmChange[] = llmReview.changes as LlmChange[]
        setChanges(existingChanges)
        setTotalDuration(llmReview.duration_ms || 0)

        // Mark all rows as reviewed
        const allReviewed = new Set(entries.map((_: WordEntry, i: number) => i))
        setReviewedRows(allReviewed)

        // Build corrected map
        const cMap = new Map<number, LlmChange[]>()
        for (const c of existingChanges) {
          const existing = cMap.get(c.row_index) || []
          existing.push(c)
          cMap.set(c.row_index, existing)
        }
        setCorrectedMap(cMap)

        // Default: all accepted
        setAccepted(new Set(existingChanges.map((_: LlmChange, i: number) => i)))

        setMeta({
          total_entries: entries.length,
          to_review: llmReview.entries_corrected !== undefined ? entries.length : entries.length,
          skipped: 0,
          model: llmReview.model_used || 'unknown',
        })
        setStatus('done')
      } else {
        setStatus('ready')
      }
    } catch (e: unknown) {
      setError(e instanceof Error ? e.message : String(e))
      setStatus('error')
    }
  }

  const runReview = useCallback(async () => {
    if (!sessionId) return
    setStatus('running')
    setError('')
    setChanges([])
    setProgress(null)
    setMeta(null)
    setTotalDuration(0)
    setActiveRowIndices(new Set())
    setReviewedRows(new Set())
    setSkippedRows(new Set())
    setCorrectedMap(new Map())

    try {
      const res = await fetch(
        `${KLAUSUR_API}/api/v1/ocr-pipeline/sessions/${sessionId}/llm-review?stream=true`,
        { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({}) },
      )

      if (!res.ok) {
        const data = await res.json().catch(() => ({}))
        throw new Error(data.detail || `HTTP ${res.status}`)
      }

      const reader = res.body!.getReader()
      const decoder = new TextDecoder()
      let buffer = ''
      let allChanges: LlmChange[] = []
      let allReviewed = new Set<number>()
      let allSkipped = new Set<number>()
      let cMap = new Map<number, LlmChange[]>()

      while (true) {
        const { done, value } = await reader.read()
        if (done) break
        buffer += decoder.decode(value, { stream: true })

        while (buffer.includes('\n\n')) {
          const idx = buffer.indexOf('\n\n')
          const chunk = buffer.slice(0, idx).trim()
          buffer = buffer.slice(idx + 2)

          if (!chunk.startsWith('data: ')) continue
          const dataStr = chunk.slice(6)

          let event: any
          try { event = JSON.parse(dataStr) } catch { continue }

          if (event.type === 'meta') {
            setMeta({
              total_entries: event.total_entries,
              to_review: event.to_review,
              skipped: event.skipped,
              model: event.model,
              skipped_indices: event.skipped_indices,
            })
            // Mark skipped rows
            if (event.skipped_indices) {
              allSkipped = new Set(event.skipped_indices)
              setSkippedRows(allSkipped)
            }
          }

          if (event.type === 'batch') {
            const batchChanges: LlmChange[] = event.changes || []
            const batchRows: number[] = event.entries_reviewed || []

            // Update active rows (currently being reviewed)
            setActiveRowIndices(new Set(batchRows))

            // Accumulate changes
            allChanges = [...allChanges, ...batchChanges]
            setChanges(allChanges)
            setProgress(event.progress)

            // Update corrected map
            for (const c of batchChanges) {
              const existing = cMap.get(c.row_index) || []
              existing.push(c)
              cMap.set(c.row_index, [...existing])
            }
            setCorrectedMap(new Map(cMap))

            // Mark batch rows as reviewed
            for (const r of batchRows) {
              allReviewed.add(r)
            }
            setReviewedRows(new Set(allReviewed))

            // Scroll to active row in table
            setTimeout(() => {
              activeRowRef.current?.scrollIntoView({ behavior: 'smooth', block: 'center' })
            }, 50)
          }

          if (event.type === 'complete') {
            setActiveRowIndices(new Set())
            setTotalDuration(event.duration_ms)
            setAccepted(new Set(allChanges.map((_: LlmChange, i: number) => i)))
            // Mark all non-skipped as reviewed
            const allEntryIndices = vocabEntries.map((_: WordEntry, i: number) => i)
            for (const i of allEntryIndices) {
              if (!allSkipped.has(i)) allReviewed.add(i)
            }
            setReviewedRows(new Set(allReviewed))
            setStatus('done')
          }

          if (event.type === 'error') {
            throw new Error(event.detail || 'Unbekannter Fehler')
          }
        }
      }

      // If stream ended without complete event
      if (allChanges.length === 0) {
        setStatus('done')
      }
    } catch (e: unknown) {
      const msg = e instanceof Error ? e.message : String(e)
      setError(msg)
      setStatus('error')
    }
  }, [sessionId, vocabEntries])

  const toggleChange = (index: number) => {
    setAccepted(prev => {
      const next = new Set(prev)
      if (next.has(index)) next.delete(index)
      else next.add(index)
      return next
    })
  }

  const toggleAll = () => {
    if (accepted.size === changes.length) {
      setAccepted(new Set())
    } else {
      setAccepted(new Set(changes.map((_: LlmChange, i: number) => i)))
    }
  }

  const applyChanges = useCallback(async () => {
    if (!sessionId) return
    setApplying(true)
    try {
      const res = await fetch(`${KLAUSUR_API}/api/v1/ocr-pipeline/sessions/${sessionId}/llm-review/apply`, {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ accepted_indices: Array.from(accepted) }),
      })
      if (!res.ok) {
        const data = await res.json().catch(() => ({}))
        throw new Error(data.detail || `HTTP ${res.status}`)
      }
      setStatus('applied')
    } catch (e: unknown) {
      setError(e instanceof Error ? e.message : String(e))
    } finally {
      setApplying(false)
    }
  }, [sessionId, accepted])

  const getRowStatus = (rowIndex: number): RowStatus => {
    if (activeRowIndices.has(rowIndex)) return 'active'
    if (skippedRows.has(rowIndex)) return 'skipped'
    if (correctedMap.has(rowIndex)) return 'corrected'
    if (reviewedRows.has(rowIndex)) return 'reviewed'
    return 'pending'
  }

  const dewarpedUrl = sessionId
    ? `${KLAUSUR_API}/api/v1/ocr-pipeline/sessions/${sessionId}/image/cropped`
    : ''

  // Snap all cells in the same column to consistent x/w positions
  // Uses the median x and width per col_index so columns align vertically
  const colPositions = useMemo(() => {
    const byCol = new Map<number, { xs: number[]; ws: number[] }>()
    for (const cell of cells) {
      if (!cell.bbox_pct) continue
      const entry = byCol.get(cell.col_index) || { xs: [], ws: [] }
      entry.xs.push(cell.bbox_pct.x)
      entry.ws.push(cell.bbox_pct.w)
      byCol.set(cell.col_index, entry)
    }
    const result = new Map<number, { x: number; w: number }>()
    for (const [colIdx, { xs, ws }] of byCol) {
      xs.sort((a, b) => a - b)
      ws.sort((a, b) => a - b)
      const medianX = xs[Math.floor(xs.length / 2)]
      const medianW = ws[Math.floor(ws.length / 2)]
      result.set(colIdx, { x: medianX, w: medianW })
    }
    return result
  }, [cells])

  if (!sessionId) {
    return <div className="text-center py-12 text-gray-400">Bitte zuerst eine Session auswaehlen.</div>
  }

  // --- Loading session data ---
  if (status === 'loading' || status === 'idle') {
    return (
      <div className="flex items-center gap-3 justify-center py-12">
        <div className="animate-spin rounded-full h-5 w-5 border-b-2 border-teal-500" />
        <span className="text-gray-500">Session-Daten werden geladen...</span>
      </div>
    )
  }

  // --- Error ---
  if (status === 'error') {
    return (
      <div className="flex flex-col items-center justify-center py-12 text-center">
        <div className="text-5xl mb-4">⚠️</div>
        <h3 className="text-lg font-medium text-red-600 dark:text-red-400 mb-2">Fehler bei OCR-Zeichenkorrektur</h3>
        <p className="text-sm text-gray-500 dark:text-gray-400 max-w-lg mb-4">{error}</p>
        <div className="flex gap-3">
          <button onClick={() => { setError(''); loadSessionData() }}
            className="px-5 py-2 bg-teal-600 text-white rounded-lg hover:bg-teal-700 transition-colors text-sm">
            Erneut versuchen
          </button>
          <button onClick={onNext}
            className="px-5 py-2 bg-gray-200 dark:bg-gray-700 text-gray-700 dark:text-gray-300 rounded-lg hover:bg-gray-300 dark:hover:bg-gray-600 transition-colors text-sm">
            Ueberspringen →
          </button>
        </div>
      </div>
    )
  }

  // --- Applied ---
  if (status === 'applied') {
    return (
      <div className="flex flex-col items-center justify-center py-12 text-center">
        <div className="text-5xl mb-4">✅</div>
        <h3 className="text-lg font-medium text-gray-700 dark:text-gray-300 mb-2">Korrekturen uebernommen</h3>
        <p className="text-sm text-gray-500 dark:text-gray-400 mb-6">
          {accepted.size} von {changes.length} Korrekturen wurden angewendet.
        </p>
        <button onClick={onNext}
          className="px-6 py-2.5 bg-teal-600 text-white rounded-lg hover:bg-teal-700 transition-colors font-medium">
          Weiter →
        </button>
      </div>
    )
  }

  // Active entry for highlighting on image
  const activeEntry = vocabEntries.find((_: WordEntry, i: number) => activeRowIndices.has(i))

  const pct = progress ? Math.round((progress.current / progress.total) * 100) : 0

  /** Handle inline edit of a cell in the overlay */
  const handleCellEdit = (cellId: string, rowIndex: number, newText: string | null) => {
    if (newText === null) return
    setCells(prev => prev.map(c => c.cell_id === cellId ? { ...c, text: newText } : c))
    // Also update vocabEntries if this cell maps to a known field
    const cell = cells.find(c => c.cell_id === cellId)
    if (cell) {
      const field = COL_TYPE_TO_FIELD[cell.col_type]
      if (field) {
        setVocabEntries(prev => prev.map((e, i) =>
          i === rowIndex ? { ...e, [field]: newText } : e
        ))
      }
    }
  }

  // --- Ready / Running / Done: 2-column layout ---
  return (
    <div className="space-y-4">
      {/* Header */}
      <div className="flex items-center justify-between">
        <div>
          <h3 className="text-base font-medium text-gray-700 dark:text-gray-300">
            Schritt 6: Korrektur
          </h3>
          <p className="text-xs text-gray-400 mt-0.5">
            {status === 'ready' && `${vocabEntries.length} Eintraege bereit zur Pruefung`}
            {status === 'running' && meta && `${meta.model} · ${meta.to_review} zu pruefen, ${meta.skipped} uebersprungen`}
            {status === 'done' && (
              <>
                {changes.length} Korrektur{changes.length !== 1 ? 'en' : ''} gefunden
                {meta && <> · {meta.skipped} uebersprungen</>}
                {' '}· {totalDuration}ms · {meta?.model}
              </>
            )}
          </p>
        </div>
        <div className="flex items-center gap-2">
          {status === 'ready' && (
            <button onClick={runReview}
              className="px-5 py-2 bg-teal-600 text-white rounded-lg hover:bg-teal-700 transition-colors text-sm font-medium">
              Korrektur starten
            </button>
          )}
          {status === 'running' && (
            <div className="flex items-center gap-2 text-sm text-teal-600 dark:text-teal-400">
              <div className="animate-spin rounded-full h-4 w-4 border-b-2 border-teal-500" />
              {progress ? `${progress.current}/${progress.total}` : 'Startet...'}
            </div>
          )}
          {status === 'done' && changes.length > 0 && (
            <button onClick={toggleAll}
              className="text-xs px-3 py-1.5 border border-gray-300 dark:border-gray-600 rounded-lg hover:bg-gray-50 dark:hover:bg-gray-700 transition-colors text-gray-600 dark:text-gray-400">
              {accepted.size === changes.length ? 'Keine' : 'Alle'} auswaehlen
            </button>
          )}
        </div>
      </div>

      {/* Progress bar (while running) */}
      {status === 'running' && progress && (
        <div className="space-y-1">
          <div className="flex justify-between text-xs text-gray-400">
            <span>{progress.current} / {progress.total} Eintraege geprueft</span>
            <span>{pct}%</span>
          </div>
          <div className="w-full bg-gray-200 dark:bg-gray-700 rounded-full h-2">
            <div className="bg-teal-500 h-2 rounded-full transition-all duration-500" style={{ width: `${pct}%` }} />
          </div>
        </div>
      )}

      {/* View mode toggle */}
      <div className="flex items-center gap-1">
        <button
          onClick={() => setViewMode('table')}
          className={`px-3 py-1.5 text-xs rounded-l-lg border transition-colors ${
            viewMode === 'table'
              ? 'bg-teal-600 text-white border-teal-600'
              : 'bg-white dark:bg-gray-800 text-gray-600 dark:text-gray-400 border-gray-300 dark:border-gray-600 hover:bg-gray-50 dark:hover:bg-gray-700'
          }`}
        >
          Tabelle
        </button>
        <button
          onClick={() => setViewMode('overlay')}
          className={`px-3 py-1.5 text-xs rounded-r-lg border transition-colors ${
            viewMode === 'overlay'
              ? 'bg-teal-600 text-white border-teal-600'
              : 'bg-white dark:bg-gray-800 text-gray-600 dark:text-gray-400 border-gray-300 dark:border-gray-600 hover:bg-gray-50 dark:hover:bg-gray-700'
          }`}
        >
          Overlay
        </button>
      </div>

      {/* Overlay toolbar */}
      {viewMode === 'overlay' && (
        <div className="flex items-center gap-4 flex-wrap bg-gray-50 dark:bg-gray-800/50 rounded-lg px-3 py-2">
          <label className="flex items-center gap-2 text-xs text-gray-600 dark:text-gray-400">
            Schrift
            <input
              type="range" min={30} max={120} value={Math.round(fontScale * 100)}
              onChange={e => setFontScale(Number(e.target.value) / 100)}
              className="w-24 h-1 accent-teal-600"
            />
            <span className="w-8 text-right font-mono">{Math.round(fontScale * 100)}%</span>
          </label>
          <label className="flex items-center gap-2 text-xs text-gray-600 dark:text-gray-400">
            Einrueckung
            <input
              type="range" min={0} max={20} step={0.5} value={leftPaddingPct}
              onChange={e => setLeftPaddingPct(Number(e.target.value))}
              className="w-24 h-1 accent-teal-600"
            />
            <span className="w-8 text-right font-mono">{leftPaddingPct}%</span>
          </label>
          <button
            onClick={() => setGlobalBold(b => !b)}
            className={`px-2 py-1 text-xs rounded border transition-colors font-bold ${
              globalBold
                ? 'bg-teal-600 text-white border-teal-600'
                : 'bg-white dark:bg-gray-700 text-gray-600 dark:text-gray-400 border-gray-300 dark:border-gray-600'
            }`}
          >
            B
          </button>
        </div>
      )}

      {/* 2-column layout: Image + Table/Overlay */}
      <div className={`grid gap-4 ${viewMode === 'overlay' ? 'grid-cols-2' : 'grid-cols-3'}`}>
        {/* Left: Dewarped Image with highlight overlay */}
        <div className="col-span-1">
          <div className="text-xs font-medium text-gray-500 dark:text-gray-400 mb-1">
            Originalbild
          </div>
          <div className="border rounded-lg overflow-hidden dark:border-gray-700 bg-gray-50 dark:bg-gray-900 relative sticky top-4">
            {/* eslint-disable-next-line @next/next/no-img-element */}
            <img
              src={dewarpedUrl}
              alt="Dewarped"
              className="w-full h-auto"
              onLoad={(e) => {
                const img = e.target as HTMLImageElement
                setImageNaturalSize({ w: img.naturalWidth, h: img.naturalHeight })
              }}
            />
            {/* Highlight overlay for active row */}
            {activeEntry?.bbox && (
              <div
                className="absolute border-2 border-yellow-400 bg-yellow-400/20 pointer-events-none animate-pulse"
                style={{
                  left: `${activeEntry.bbox.x}%`,
                  top: `${activeEntry.bbox.y}%`,
                  width: `${activeEntry.bbox.w}%`,
                  height: `${activeEntry.bbox.h}%`,
                }}
              />
            )}
          </div>
        </div>

        {/* Right: Table or Overlay */}
        <div className={viewMode === 'table' ? 'col-span-2' : 'col-span-1'} ref={tableRef}>
          {viewMode === 'table' ? (
            <>
              <div className="text-xs font-medium text-gray-500 dark:text-gray-400 mb-1">
                {columnsUsed.length === 1 && columnsUsed[0]?.type === 'column_text' ? 'Tabelle' : 'Vokabeltabelle'} ({vocabEntries.length} Eintraege)
              </div>
              <div className="border border-gray-200 dark:border-gray-700 rounded-lg overflow-hidden">
                <div className="max-h-[70vh] overflow-y-auto">
                  <table className="w-full text-sm">
                    <thead className="sticky top-0 z-10">
                      <tr className="bg-gray-50 dark:bg-gray-800 border-b border-gray-200 dark:border-gray-700">
                        <th className="px-2 py-2 text-left text-gray-500 dark:text-gray-400 font-medium w-10">#</th>
                        {columnsUsed.length > 0 ? (
                          columnsUsed.map((col, i) => {
                            const field = COL_TYPE_TO_FIELD[col.type]
                            if (!field) return null
                            return (
                              <th key={i} className={`px-2 py-2 text-left font-medium ${COL_TYPE_COLOR[col.type] || 'text-gray-500 dark:text-gray-400'}`}>
                                {FIELD_LABELS[field] || field}
                              </th>
                            )
                          })
                        ) : (
                          <>
                            <th className="px-2 py-2 text-left text-gray-500 dark:text-gray-400 font-medium">EN</th>
                            <th className="px-2 py-2 text-left text-gray-500 dark:text-gray-400 font-medium">DE</th>
                            <th className="px-2 py-2 text-left text-gray-500 dark:text-gray-400 font-medium">Beispiel</th>
                          </>
                        )}
                        <th className="px-2 py-2 text-center text-gray-500 dark:text-gray-400 font-medium w-16">Status</th>
                      </tr>
                    </thead>
                    <tbody>
                      {vocabEntries.map((entry, idx) => {
                        const rowStatus = getRowStatus(idx)
                        const rowChanges = correctedMap.get(idx)

                        const rowBg = {
                          pending: '',
                          active: 'bg-yellow-50 dark:bg-yellow-900/20',
                          reviewed: '',
                          corrected: 'bg-teal-50/50 dark:bg-teal-900/10',
                          skipped: 'bg-gray-50 dark:bg-gray-800/50',
                        }[rowStatus]

                        return (
                          <tr
                            key={idx}
                            ref={rowStatus === 'active' ? activeRowRef : undefined}
                            className={`border-b border-gray-100 dark:border-gray-700/50 ${rowBg} ${
                              rowStatus === 'active' ? 'ring-1 ring-yellow-400 ring-inset' : ''
                            }`}
                          >
                            <td className="px-2 py-1.5 text-gray-400 font-mono text-xs">{idx}</td>
                            {columnsUsed.length > 0 ? (
                              columnsUsed.map((col, i) => {
                                const field = COL_TYPE_TO_FIELD[col.type]
                                if (!field) return null
                                const text = (entry as Record<string, unknown>)[field] as string || ''
                                return (
                                  <td key={i} className="px-2 py-1.5 text-xs">
                                    <CellContent text={text} field={field} rowChanges={rowChanges} />
                                  </td>
                                )
                              })
                            ) : (
                              <>
                                <td className="px-2 py-1.5">
                                  <CellContent text={entry.english} field="english" rowChanges={rowChanges} />
                                </td>
                                <td className="px-2 py-1.5">
                                  <CellContent text={entry.german} field="german" rowChanges={rowChanges} />
                                </td>
                                <td className="px-2 py-1.5 text-xs">
                                  <CellContent text={entry.example} field="example" rowChanges={rowChanges} />
                                </td>
                              </>
                            )}
                            <td className="px-2 py-1.5 text-center">
                              <StatusIcon status={rowStatus} />
                            </td>
                          </tr>
                        )
                      })}
                    </tbody>
                  </table>
                </div>
              </div>
            </>
          ) : (
            <>
              <div className="text-xs font-medium text-gray-500 dark:text-gray-400 mb-1">
                Text-Rekonstruktion ({cells.filter(c => c.text).length} Zellen)
              </div>
              <div className="border border-gray-200 dark:border-gray-700 rounded-lg overflow-hidden bg-white dark:bg-white">
                <div
                  ref={reconRef}
                  className="relative"
                  style={{
                    aspectRatio: imageNaturalSize ? `${imageNaturalSize.w} / ${imageNaturalSize.h}` : '3 / 4',
                  }}
                >
                  {cells.map(cell => {
                    if (!cell.bbox_pct || !cell.text) return null
                    const col = colPositions.get(cell.col_index)
                    const cellX = col?.x ?? cell.bbox_pct.x
                    const cellW = col?.w ?? cell.bbox_pct.w
                    const aspect = imageNaturalSize ? imageNaturalSize.h / imageNaturalSize.w : 4 / 3
                    const containerH = reconWidth * aspect
                    const cellHeightPx = containerH * (cell.bbox_pct.h / 100)

                    const wordPos = cellWordPositions.get(cell.cell_id)

                    // Pixel-analysed: render word-groups at detected positions
                    if (wordPos) {
                      return wordPos.map((wp, i) => {
                        // Auto font-size from pixel analysis, scaled by user slider
                        const autoFontPx = cellHeightPx * wp.fontRatio * fontScale
                        const fs = Math.max(6, autoFontPx)
                        return (
                          <span
                            key={`${cell.cell_id}_${i}`}
                            className="absolute leading-none pointer-events-none select-none"
                            style={{
                              left: `${wp.xPct}%`,
                              top: `${cell.bbox_pct.y}%`,
                              width: `${wp.wPct}%`,
                              height: `${cell.bbox_pct.h}%`,
                              fontSize: `${fs}px`,
                              fontWeight: globalBold ? 'bold' : (cell.is_bold ? 'bold' : 'normal'),
                              fontFamily: "'Liberation Sans', Arial, sans-serif",
                              display: 'flex',
                              alignItems: 'center',
                              whiteSpace: 'nowrap',
                              overflow: 'visible',
                              color: '#1a1a1a',
                            }}
                          >
                            {wp.text}
                          </span>
                        )
                      })
                    }

                    // Fallback: no pixel data — single span for entire cell
                    const fontSize = Math.max(6, cellHeightPx * fontScale)
                    return (
                      <span
                        key={cell.cell_id}
                        className="absolute leading-none pointer-events-none select-none"
                        style={{
                          left: `${cellX}%`,
                          top: `${cell.bbox_pct.y}%`,
                          width: `${cellW}%`,
                          height: `${cell.bbox_pct.h}%`,
                          fontSize: `${fontSize}px`,
                          fontWeight: globalBold ? 'bold' : (cell.is_bold ? 'bold' : 'normal'),
                          paddingLeft: `${leftPaddingPct}%`,
                          fontFamily: "'Liberation Sans', Arial, sans-serif",
                          display: 'flex',
                          alignItems: 'center',
                          whiteSpace: 'pre',
                          overflow: 'visible',
                          color: '#1a1a1a',
                        }}
                      >
                        {cell.text}
                      </span>
                    )
                  })}
                </div>
              </div>
            </>
          )}
        </div>
      </div>

      {/* Done state: summary + actions */}
      {status === 'done' && (
        <div className="space-y-4">
          {/* Summary */}
          <div className="bg-gray-50 dark:bg-gray-800/50 rounded-lg p-3 text-xs text-gray-500 dark:text-gray-400">
            {changes.length === 0 ? (
              <span>Keine Korrekturen noetig — alle Eintraege sind korrekt.</span>
            ) : (
              <span>
                {changes.length} Korrektur{changes.length !== 1 ? 'en' : ''} gefunden ·{' '}
                {accepted.size} ausgewaehlt ·{' '}
                {meta?.skipped || 0} uebersprungen (Lautschrift) ·{' '}
                {totalDuration}ms
              </span>
            )}
          </div>

          {/* Corrections detail list (if any) */}
          {changes.length > 0 && (
            <div className="border border-gray-200 dark:border-gray-700 rounded-lg overflow-hidden">
              <div className="bg-gray-50 dark:bg-gray-800 px-3 py-2 border-b border-gray-200 dark:border-gray-700">
                <span className="text-xs font-medium text-gray-600 dark:text-gray-400">
                  Korrekturvorschlaege ({accepted.size}/{changes.length} ausgewaehlt)
                </span>
              </div>
              <table className="w-full text-sm">
                <thead>
                  <tr className="bg-gray-50/50 dark:bg-gray-800/50 border-b border-gray-200 dark:border-gray-700">
                    <th className="w-10 px-3 py-1.5 text-center">
                      <input type="checkbox" checked={accepted.size === changes.length} onChange={toggleAll}
                        className="rounded border-gray-300 dark:border-gray-600" />
                    </th>
                    <th className="px-2 py-1.5 text-left text-gray-500 dark:text-gray-400 font-medium text-xs">Zeile</th>
                    <th className="px-2 py-1.5 text-left text-gray-500 dark:text-gray-400 font-medium text-xs">Feld</th>
                    <th className="px-2 py-1.5 text-left text-gray-500 dark:text-gray-400 font-medium text-xs">Vorher</th>
                    <th className="px-2 py-1.5 text-left text-gray-500 dark:text-gray-400 font-medium text-xs">Nachher</th>
                  </tr>
                </thead>
                <tbody>
                  {changes.map((change, idx) => (
                    <tr key={idx} className={`border-b border-gray-100 dark:border-gray-700/50 ${
                      accepted.has(idx) ? 'bg-teal-50/50 dark:bg-teal-900/10' : ''
                    }`}>
                      <td className="px-3 py-1.5 text-center">
                        <input type="checkbox" checked={accepted.has(idx)} onChange={() => toggleChange(idx)}
                          className="rounded border-gray-300 dark:border-gray-600" />
                      </td>
                      <td className="px-2 py-1.5 text-gray-500 dark:text-gray-400 font-mono text-xs">R{change.row_index}</td>
                      <td className="px-2 py-1.5">
                        <span className="text-xs px-1.5 py-0.5 rounded bg-gray-100 dark:bg-gray-700 text-gray-600 dark:text-gray-400">
                          {FIELD_LABELS[change.field] || change.field}
                        </span>
                      </td>
                      <td className="px-2 py-1.5"><span className="line-through text-red-500 dark:text-red-400 text-xs">{change.old}</span></td>
                      <td className="px-2 py-1.5"><span className="text-green-600 dark:text-green-400 font-medium text-xs">{change.new}</span></td>
                    </tr>
                  ))}
                </tbody>
              </table>
            </div>
          )}

          {/* Actions */}
          <div className="flex items-center justify-between pt-2">
            <p className="text-xs text-gray-400">
              {changes.length > 0 ? `${accepted.size} von ${changes.length} ausgewaehlt` : ''}
            </p>
            <div className="flex gap-3">
              {changes.length > 0 && (
                <button onClick={onNext}
                  className="px-4 py-2 text-sm border border-gray-300 dark:border-gray-600 rounded-lg hover:bg-gray-50 dark:hover:bg-gray-700 transition-colors text-gray-600 dark:text-gray-400">
                  Alle ablehnen
                </button>
              )}
              {changes.length > 0 ? (
                <button onClick={applyChanges} disabled={applying || accepted.size === 0}
                  className="px-5 py-2 text-sm bg-teal-600 text-white rounded-lg hover:bg-teal-700 disabled:opacity-50 disabled:cursor-not-allowed transition-colors font-medium">
                  {applying ? 'Wird uebernommen...' : `${accepted.size} Korrektur${accepted.size !== 1 ? 'en' : ''} uebernehmen`}
                </button>
              ) : (
                <button onClick={onNext}
                  className="px-6 py-2.5 bg-teal-600 text-white rounded-lg hover:bg-teal-700 transition-colors font-medium">
                  Weiter →
                </button>
              )}
            </div>
          </div>
        </div>
      )}
    </div>
  )
}

/** Cell content with inline diff for corrections */
function CellContent({ text, field, rowChanges }: {
  text: string
  field: string
  rowChanges?: LlmChange[]
}) {
  const change = rowChanges?.find(c => c.field === field)

  if (!text && !change) {
    return <span className="text-gray-300 dark:text-gray-600">&mdash;</span>
  }

  if (change) {
    return (
      <span>
        <span className="line-through text-red-400 dark:text-red-500 text-xs mr-1">{change.old}</span>
        <span className="text-green-600 dark:text-green-400 font-medium text-xs">{change.new}</span>
      </span>
    )
  }

  return <span className="text-gray-700 dark:text-gray-300 text-xs">{text}</span>
}

/** Status icon for each row */
function StatusIcon({ status }: { status: RowStatus }) {
  switch (status) {
    case 'pending':
      return <span className="text-gray-300 dark:text-gray-600 text-xs">—</span>
    case 'active':
      return (
        <span className="inline-block w-3 h-3 rounded-full bg-yellow-400 animate-pulse" title="Wird geprueft" />
      )
    case 'reviewed':
      return (
        <svg className="w-4 h-4 text-green-500 inline-block" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={2}>
          <path strokeLinecap="round" strokeLinejoin="round" d="M5 13l4 4L19 7" />
        </svg>
      )
    case 'corrected':
      return (
        <span className="inline-flex items-center px-1.5 py-0.5 rounded text-[10px] font-medium bg-teal-100 dark:bg-teal-900/30 text-teal-700 dark:text-teal-400">
          korr.
        </span>
      )
    case 'skipped':
      return (
        <span className="inline-flex items-center px-1.5 py-0.5 rounded text-[10px] font-medium bg-gray-100 dark:bg-gray-700 text-gray-500 dark:text-gray-400">
          skip
        </span>
      )
  }
}
