// frontend\src\components\ui\FinishedDownloadsTableView.tsx

'use client'

import { useState, useMemo, useCallback, type Dispatch, type ReactNode, type SetStateAction } from 'react'
import Table, { type Column, type SortState } from './Table'
import type {
  RecordJob,
  FinishedPostworkSummary,
  DoneSortMode,
  StoredModelFlags,
} from '../../types'
import FinishedVideoPreview from './FinishedVideoPreview'
import RecordJobActions from './RecordJobActions'
import TagOverflowRow from './TagOverflowRow'
import { isHotName, stripHotPrefix } from './hotName'
import { formatResolution } from './formatters'
import type { FinishedDownloadsTeaserState } from './teaserPlayback'
import {
  shouldAnimateTeaser,
  shouldEnableTeaserAudio,
} from './teaserPlayback'
import Checkbox from './Checkbox'
import { parseJobMeta } from './previewSprite'
import ModelGenderIcon from './ModelGenderIcon'
import { resolveFinishedModelFlags } from './finishedModelFlags'
import CountryFlag, { resolveFinishedCountry } from './CountryFlag'
import PreviewRatingOverlay from './PreviewRatingOverlay'

type Props = {
  rows: RecordJob[]
  isLoading?: boolean
  selectedKeys: Set<string>
  selectionModeActive: boolean
  onToggleSelected: (job: RecordJob) => void
  onToggleSelectAllPage: (checked: boolean) => void

  // helpers
  keyFor: (j: RecordJob) => string
  baseName: (p: string) => string
  lower: (s: string) => string
  modelNameFromOutput: (output?: string) => string
  runtimeOf: (job: RecordJob) => string
  sizeBytesOf: (job: RecordJob) => number | null
  formatBytes: (bytes?: number | null) => string
  resolutions: Record<string, { w: number; h: number }>
  durations: Record<string, number>
  postworkByFile: Record<string, FinishedPostworkSummary>
  getPostworkSummaryForOutput: (
    output: string | undefined,
    summaries: Record<string, FinishedPostworkSummary>
  ) => FinishedPostworkSummary | undefined
  jobForDetails: (job: RecordJob) => RecordJob
  renderPostworkBadge?: (badge?: FinishedPostworkSummary) => ReactNode

  // teaser/preview
  canHover: boolean
  teaserState: FinishedDownloadsTeaserState
  setHoverTeaserKey: Dispatch<SetStateAction<string | null>>
  registerTeaserHost: (key: string) => (el: HTMLDivElement | null) => void
  handleDuration: (job: RecordJob, seconds: number) => void
  handleResolution: (job: RecordJob, w: number, h: number) => void
  blurPreviews?: boolean
  assetNonce?: number

  // state/flags for actions row + rowClassName
  deletingKeys: Set<string>
  keepingKeys: Set<string>
  removingKeys: Set<string>

  modelsByKey: Record<string, StoredModelFlags>
  activeTagSet: Set<string>
  onToggleTagFilter: (tag: string) => void
  handleScrubberClickIndex: (job: RecordJob, segmentIndex: number, segmentCount: number) => void

  // actions
  onOpenPlayer: (job: RecordJob) => void
  onSortModeChange: (m: DoneSortMode) => void
  page: number
  onPageChange: (page: number) => void

  onToggleHot?: (job: RecordJob) => void | Promise<void>
  onToggleFavorite?: (job: RecordJob) => void | Promise<void>
  onToggleLike?: (job: RecordJob) => void | Promise<void>
  onToggleWatch?: (job: RecordJob) => void | Promise<void>
  onSplit?: (job: RecordJob) => void | Promise<void>
  onAddToDownloads?: (job: RecordJob) => void | Promise<boolean>

  deleteVideo: (job: RecordJob) => Promise<boolean>
  keepVideo: (job: RecordJob) => Promise<boolean>
  forcePreviewMuted?: boolean
  renderIntegrityBadge?: (job: RecordJob) => ReactNode
  renderRatingOverlay?: (job: RecordJob) => ReactNode
}

export default function FinishedDownloadsTableView({
  rows,
  isLoading,
  selectedKeys,
  selectionModeActive,
  onToggleSelected,
  onToggleSelectAllPage,
  keyFor,
  baseName,
  lower,
  modelNameFromOutput,
  runtimeOf,
  sizeBytesOf,
  formatBytes,
  resolutions,
  durations,
  postworkByFile,
  getPostworkSummaryForOutput,
  jobForDetails,
  renderPostworkBadge,

  canHover,
  teaserState,
  setHoverTeaserKey,
  registerTeaserHost,
  handleDuration,
  handleResolution,
  blurPreviews,
  assetNonce,

  deletingKeys,
  keepingKeys,
  removingKeys,

  modelsByKey,
  activeTagSet,
  onToggleTagFilter,

  onOpenPlayer,
  onSortModeChange,
  page,
  onPageChange,

  onToggleHot,
  onToggleFavorite,
  onToggleLike,
  onToggleWatch,
  onSplit,
  onAddToDownloads,

  deleteVideo,
  keepVideo,
  forcePreviewMuted,
  renderIntegrityBadge,
  renderRatingOverlay,
}: Props) {
  const [sort, setSort] = useState<SortState>(null)

  const runtimeSecondsForSort = useCallback((job: RecordJob) => {
    // 1) Prefer real video duration (ffprobe / backend)
    const sec = (job as any)?.meta?.file?.durationSeconds
    if (typeof sec === 'number' && Number.isFinite(sec) && sec > 0) return sec

    // 2) Fallback: endedAt-startedAt (only if plausible)
    const start = Date.parse(String((job as any)?.startedAt || ''))
    const end = Date.parse(String((job as any)?.endedAt || ''))
    if (Number.isFinite(start) && Number.isFinite(end) && end > start) {
      const diffSec = (end - start) / 1000
      if (diffSec >= 1 && diffSec <= 24 * 60 * 60) return diffSec
    }

    return Number.POSITIVE_INFINITY
  }, [])

  const parseTags = useCallback((raw?: string): string[] => {
    const s = String(raw ?? '').trim()
    if (!s) return []
    const parts = s
      .split(/[\n,;|]+/g)
      .map((p) => p.trim())
      .filter(Boolean)

    const seen = new Set<string>()
    const out: string[] = []
    for (const p of parts) {
      const k = p.toLowerCase()
      if (seen.has(k)) continue
      seen.add(k)
      out.push(p)
    }
    out.sort((a, b) => a.localeCompare(b, undefined, { sensitivity: 'base' }))
    return out
  }, [])

  const parseMeta = useCallback((j: RecordJob): any | null => {
    return parseJobMeta((j as any)?.meta)
  }, [])

  const previewSpriteInfoOf = useCallback(
    (j: RecordJob): { count: number; stepSeconds: number } | null => {
      const meta = parseMeta(j)
      let ps: any = meta?.previewSprite ?? meta?.preview?.sprite ?? null

      if (typeof ps === 'string') {
        try {
          ps = JSON.parse(ps)
        } catch {
          ps = null
        }
      }

      const count = Number(ps?.count)
      const stepSeconds = Number(ps?.stepSeconds)

      if (!Number.isFinite(count) || count < 2) return null

      return {
        count: Math.max(2, Math.floor(count)),
        stepSeconds: Number.isFinite(stepSeconds) && stepSeconds > 0 ? stepSeconds : 5,
      }
    },
    [parseMeta]
  )

  const [scrubActiveByKey, setScrubActiveByKey] = useState<Record<string, number | undefined>>({})

  const setScrubActiveIndex = useCallback((key: string, index: number | undefined) => {
    setScrubActiveByKey((prev) => {
      if (prev[key] === index) return prev
      return { ...prev, [key]: index }
    })
  }, [])

  const allSelectedOnPage =
    rows.length > 0 && rows.every((j) => selectedKeys.has(keyFor(j)))

  const someSelectedOnPage =
    rows.some((j) => selectedKeys.has(keyFor(j))) && !allSelectedOnPage

  const handlePrimaryAction = useCallback(
    (job: RecordJob) => {
      if (selectionModeActive) {
        onToggleSelected(job)
        return
      }

      onOpenPlayer(job)
    },
    [selectionModeActive, onToggleSelected, onOpenPlayer]
  )

  const columns = useMemo<Column<RecordJob>[]>(() => {
    return [
      {
        key: 'select',
        header: (
          <div
            className="flex items-center justify-center"
            onClick={(e) => e.stopPropagation()}
            onMouseDown={(e) => e.stopPropagation()}
          >
            <Checkbox
              checked={allSelectedOnPage}
              indeterminate={someSelectedOnPage}
              ariaLabel={
                allSelectedOnPage
                  ? 'Auswahl aufheben'
                  : 'Alle Downloads auf dieser Seite auswählen'
              }
              onChange={(checked) => onToggleSelectAllPage(checked)}
            />
          </div>
        ),
        widthClassName: 'w-[44px]',
        align: 'center',
        cell: (j) => {
          const checked = selectedKeys.has(keyFor(j))

          return (
            <div
              className="flex items-center justify-center"
              onClick={(e) => e.stopPropagation()}
              onMouseDown={(e) => e.stopPropagation()}
            >
              <Checkbox
                checked={checked}
                ariaLabel={`${checked ? 'Abwählen' : 'Auswählen'}: ${baseName(j.output || '') || 'Download'}`}
                onChange={() => onToggleSelected(j)}
              />
            </div>
          )
        },
      },
      {
        key: 'preview',
        header: 'Vorschau',
        widthClassName: 'w-[140px]',
        cell: (j) => {
          const k = keyFor(j)
          const isPreviewActive =
            teaserState.activeKey === k || teaserState.hoverKey === k
          const allowSound = shouldEnableTeaserAudio({
            state: teaserState,
            itemKey: k,
          })
          const previewMuted = forcePreviewMuted ? true : !allowSound
          const spriteInfo = previewSpriteInfoOf(j)
          const scrubActiveIndex = scrubActiveByKey[k]

          const fileRaw = baseName(j.output || '')
          const previewId = stripHotPrefix(fileRaw.replace(/\.[^.]+$/, '')).trim()

          const scrubTimeSec =
            spriteInfo && typeof scrubActiveIndex === 'number'
              ? Math.max(0, scrubActiveIndex * spriteInfo.stepSeconds)
              : undefined

          const scrubFrameSrc =
            previewId && typeof scrubTimeSec === 'number'
              ? `/api/preview?id=${encodeURIComponent(previewId)}&t=${scrubTimeSec.toFixed(3)}&v=${assetNonce ?? 0}`
              : ''

          return (
            <div
              ref={registerTeaserHost(k)}
              className="group relative py-1"
              onClick={(e) => {
                e.preventDefault()
                e.stopPropagation()
                handlePrimaryAction(j)
              }}
              onMouseDown={(e) => e.stopPropagation()}
              onMouseLeave={() => {
                setScrubActiveIndex(k, undefined)
              }}
            >
              <div className="relative w-28 h-16">
                <FinishedVideoPreview
                  job={jobForDetails(j)}
                  getFileName={baseName}
                  durationSeconds={durations[k] ?? (j as any)?.meta?.file?.durationSeconds}
                  muted={previewMuted}
                  popoverMuted={previewMuted}
                  onDuration={handleDuration}
                  onResolution={handleResolution}
                  className="w-28 h-16 rounded-md ring-1 ring-black/5 dark:ring-white/10"
                  showPopover={false}
                  blur={blurPreviews}
                  animated={shouldAnimateTeaser({
                    state: teaserState,
                    itemKey: k,
                  })}
                  animatedMode="teaser"
                  animatedTrigger="always"
                  assetNonce={assetNonce}
                  forceActive={isPreviewActive}
                  teaserPreloadEnabled={false}
                />

                <PreviewRatingOverlay>
                  {renderRatingOverlay ? renderRatingOverlay(j) : null}
                </PreviewRatingOverlay>

                {spriteInfo && typeof scrubActiveIndex === 'number' && scrubFrameSrc ? (
                  <img
                    src={scrubFrameSrc}
                    alt=""
                    aria-hidden="true"
                    className="pointer-events-none absolute inset-0 z-[15] h-16 w-28 rounded-md object-cover ring-1 ring-black/5 dark:ring-white/10"
                    draggable={false}
                  />
                ) : null}
              </div>
            </div>
          )
        },
      },
      {
        key: 'Model',
        header: 'Model',
        sortable: true,
        sortValue: (j) => {
          const fileRaw = baseName(j.output || '')
          const isHot = isHotName(fileRaw)
          const model = modelNameFromOutput(j.output)
          const file = stripHotPrefix(fileRaw)
          return `${model} ${isHot ? 'HOT' : ''} ${file}`.trim()
        },
        cell: (j) => {
          const fileRaw = baseName(j.output || '')
          const isHot = isHotName(fileRaw)
          const file = stripHotPrefix(fileRaw)
          const model = modelNameFromOutput(j.output)

          const { flags } = resolveFinishedModelFlags(
            j,
            modelsByKey,
            modelNameFromOutput,
            lower
          )

          const tags = parseTags(flags?.tags)

          const postworkBadge = getPostworkSummaryForOutput(j.output, postworkByFile)
          const showPostworkBadge = !!postworkBadge

          const country = resolveFinishedCountry(flags, j)

          return (
            <div className="min-w-0">
              <div className="flex items-center gap-2 min-w-0">
                <div className="min-w-0 flex items-center gap-1.5">
                  <div className="min-w-0 truncate text-sm font-semibold text-gray-900 dark:text-white">
                    {model}
                  </div>

                  <ModelGenderIcon
                    gender={flags?.gender}
                    className="shrink-0"
                  />

                  <CountryFlag
                    country={country}
                    size="sm"
                    className="shrink-0"
                  />
                </div>

                {(renderIntegrityBadge || (showPostworkBadge && renderPostworkBadge)) ? (
                  <div className="relative z-20 shrink-0 flex h-6 items-center gap-2">
                    {renderIntegrityBadge ? renderIntegrityBadge(j) : null}
                    {showPostworkBadge && renderPostworkBadge ? renderPostworkBadge(postworkBadge) : null}
                  </div>
                ) : null}
              </div>

              <div className="mt-0.5 min-w-0 text-xs text-gray-500 dark:text-gray-400">
                <div className="flex min-h-6 items-center gap-2 min-w-0">
                  <span className="truncate" title={file}>
                    {file || '—'}
                  </span>

                  {(() => {
                    const k = keyFor(j)
                    const res = resolutions[k] ?? null
                    const label = formatResolution(res)
                    if (!label) return null
                    return (
                      <span
                        className="shrink-0 rounded-md bg-gray-100 px-1.5 py-0.5 text-[11px] font-semibold text-gray-700 ring-1 ring-inset ring-gray-200
                                dark:bg-white/5 dark:text-gray-200 dark:ring-white/10"
                        title={res ? `${res.w}×${res.h}` : 'Auflösung'}
                      >
                        {label}
                      </span>
                    )
                  })()}

                  {isHot ? (
                    <span className="shrink-0 rounded-md bg-amber-500/15 px-1.5 py-0.5 text-[11px] font-semibold text-amber-800 dark:text-amber-300">
                      HOT
                    </span>
                  ) : null}
                </div>

                {tags.length > 0 ? (
                  <div className="mt-1 py-0.5 overflow-visible" onClick={(e) => e.stopPropagation()} onMouseDown={(e) => e.stopPropagation()}>
                    <TagOverflowRow
                      rowKey={keyFor(j)}
                      tags={tags}
                      activeTagSet={activeTagSet}
                      lower={lower}
                      onToggleTagFilter={onToggleTagFilter}
                      maxWidthClassName="max-w-[11rem]"
                    />
                  </div>
                ) : null}
              </div>
            </div>
          )
        },
      },
      {
        key: 'completedAt',
        header: 'Fertiggestellt am',
        sortable: true,
        widthClassName: 'w-[150px]',
        sortValue: (j) => {
          const t = Date.parse(String(j.endedAt || ''))
          return Number.isFinite(t) ? t : Number.NEGATIVE_INFINITY
        },
        cell: (j) => {
          const t = Date.parse(String(j.endedAt || ''))
          if (!Number.isFinite(t)) return <span className="text-xs text-gray-400">—</span>

          const d = new Date(t)
          const date = d.toLocaleDateString('de-DE', { day: '2-digit', month: '2-digit', year: 'numeric' })
          const time = d.toLocaleTimeString('de-DE', { hour: '2-digit', minute: '2-digit' })

          return (
            <time
              dateTime={d.toISOString()}
              title={`${date} ${time}`}
              className="tabular-nums whitespace-nowrap text-sm text-gray-900 dark:text-white"
            >
              <span className="font-medium">{date}</span>
              <span className="mx-1 text-gray-400 dark:text-gray-600">·</span>
              <span className="text-gray-600 dark:text-gray-300">{time}</span>
            </time>
          )
        },
      },
      {
        key: 'runtime',
        header: 'Dauer',
        align: 'right',
        sortable: true,
        sortValue: (j) => runtimeSecondsForSort(j),
        cell: (j) => <span className="font-medium text-gray-900 dark:text-white">{runtimeOf(j)}</span>,
      },
      {
        key: 'size',
        header: 'Größe',
        align: 'right',
        sortable: true,
        sortValue: (j) => {
          const s = sizeBytesOf(j)
          return typeof s === 'number' ? s : Number.NEGATIVE_INFINITY
        },
        cell: (j) => <span className="font-medium text-gray-900 dark:text-white">{formatBytes(sizeBytesOf(j))}</span>,
      },
      {
        key: 'actions',
        header: 'Aktionen',
        align: 'right',
        srOnlyHeader: true,
        cell: (j) => {
          const k = keyFor(j)
          const busy = deletingKeys.has(k) || keepingKeys.has(k) || removingKeys.has(k)

          const fileRaw = baseName(j.output || '')
          const isHot = isHotName(fileRaw)

          const { flags } = resolveFinishedModelFlags(
            j,
            modelsByKey,
            modelNameFromOutput,
            lower
          )

          const isFav = Boolean(flags?.favorite)
          const isLiked = flags?.liked === true
          const isWatching = Boolean(flags?.watching)

          return (
            <RecordJobActions
              job={jobForDetails(j)}
              variant="table"
              busy={busy}
              isHot={isHot}
              isFavorite={isFav}
              isLiked={isLiked}
              isWatching={isWatching}
              onToggleWatch={onToggleWatch}
              onToggleFavorite={onToggleFavorite}
              onToggleLike={onToggleLike}
              onToggleHot={onToggleHot}
              onKeep={keepVideo}
              onDelete={deleteVideo}
              onSplit={onSplit}
              onAddToDownloads={onAddToDownloads}
              order={['watch', 'favorite', 'like', 'hot', 'keep', 'delete', 'split', 'details', 'add', 'training']}
              className="flex items-center justify-end gap-1"
            />
          )
        },
      },
    ]
  }, [
    keyFor,
    baseName,
    durations,
    postworkByFile,
    registerTeaserHost,
    canHover,
    setHoverTeaserKey,
    handleDuration,
    handleResolution,
    blurPreviews,
    teaserState,
    assetNonce,
    lower,
    modelNameFromOutput,
    modelsByKey,
    activeTagSet,
    onToggleTagFilter,
    handlePrimaryAction,
    resolutions,
    deletingKeys,
    keepingKeys,
    removingKeys,
    previewSpriteInfoOf,
    scrubActiveByKey,
    setScrubActiveIndex,
    onToggleWatch,
    onToggleFavorite,
    onToggleLike,
    onToggleHot,
    keepVideo,
    deleteVideo,
    sizeBytesOf,
    formatBytes,
    runtimeOf,
    runtimeSecondsForSort,
    forcePreviewMuted,
    renderPostworkBadge,
    rows,
    selectedKeys,
    onToggleSelected,
    onToggleSelectAllPage,
    allSelectedOnPage,
    someSelectedOnPage,
    renderIntegrityBadge,
    renderRatingOverlay,
  ])

  const sortStateToMode = useCallback((s: SortState): DoneSortMode => {
    if (!s) return 'completed_desc'
    const anyS = s as any
    const key = String(anyS.key ?? anyS.columnKey ?? anyS.id ?? '')
    const dirRaw = String(anyS.dir ?? anyS.direction ?? anyS.order ?? '').toLowerCase()
    const asc = dirRaw === 'asc' || dirRaw === '1' || dirRaw === 'true'

    if (key === 'completedAt') return asc ? 'completed_asc' : 'completed_desc'
    if (key === 'runtime') return asc ? 'duration_asc' : 'duration_desc'
    if (key === 'size') return asc ? 'size_asc' : 'size_desc'
    if (key === 'Model') return asc ? 'file_asc' : 'file_desc'
    if (key === 'video') return asc ? 'file_asc' : 'file_desc'
    return asc ? 'completed_asc' : 'completed_desc'
  }, [])

  const handleSortChange = useCallback(
    (s: SortState) => {
      setSort(s) // Table Pfeil
      onSortModeChange(sortStateToMode(s))
      if (page !== 1) onPageChange(1)
    },
    [onSortModeChange, sortStateToMode, page, onPageChange]
  )

  const rowClassName = useCallback(
    (j: RecordJob) => {
      const k = keyFor(j)
      return [
        'transition-all duration-300',
        (deletingKeys.has(k) || removingKeys.has(k)) && 'bg-red-50/60 dark:bg-red-500/10 pointer-events-none',
        deletingKeys.has(k) && 'animate-pulse',
        (keepingKeys.has(k) || removingKeys.has(k)) && 'pointer-events-none',
        keepingKeys.has(k) && 'bg-emerald-50/60 dark:bg-emerald-500/10 animate-pulse',
        removingKeys.has(k) && 'opacity-0',
      ]
        .filter(Boolean)
        .join(' ')
    },
    [keyFor, deletingKeys, keepingKeys, removingKeys]
  )

  return (
    <div className="relative overflow-x-auto rounded-2xl border border-gray-200/80 bg-white/80 p-2 shadow-sm dark:border-white/10 dark:bg-transparent dark:p-0">
      <Table
        rows={rows}
        columns={columns}
        getRowKey={(j) => keyFor(j)}
        striped
        fullWidth
        stickyHeader
        compact={false}
        card
        sort={sort}
        onSortChange={handleSortChange}
        onRowClick={handlePrimaryAction}
        rowAttrs={(j) => ({
          'data-finished-hover-key': keyFor(j),
        })}
        rowClassName={rowClassName}
      />

      {isLoading && rows.length === 0 ? (
        <div className="absolute inset-0 z-20 grid place-items-center rounded-2xl bg-white/50 backdrop-blur-[2px] dark:bg-gray-950/40">
          <div className="flex items-center gap-3 rounded-lg border border-gray-200 bg-white/80 px-3 py-2 shadow-sm dark:border-white/10 dark:bg-gray-900/70">
            <div className="h-5 w-5 animate-spin rounded-full border-2 border-gray-300 border-t-transparent dark:border-white/20 dark:border-t-transparent" />
            <div className="text-sm font-medium text-gray-800 dark:text-gray-100">Lade…</div>
          </div>
        </div>
      ) : null}
    </div>
  )
}
