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

'use client'

import { useMemo, useState, useCallback, useEffect, useRef } from 'react'
import Table, { type Column } from './Table'
import Card from './Card'
import Button from './Button'
import ModelPreview from './ModelPreview'
import ModelGenderIcon from './ModelGenderIcon'
import type { RecordJob } from '../../types'
import ProgressBar from './ProgressBar'
import { PauseIcon, PlayIcon } from '@heroicons/react/24/solid'
import { useMediaQuery } from '../../lib/useMediaQuery'
import DownloadsCardRow, {
  PendingPreviewImage,
  type DownloadRow,
  type PendingWatchedRoom,
} from './DownloadsCardRow'
import SwipeActionRow from './SwipeActionRow'
import RecordJobActions from './RecordJobActions'
import LazyMountWhenVisible from './LazyMountWhenVisible'
import CountryFlag from './CountryFlag'

type AutostartState = {
  paused?: boolean
  pausedByUser?: boolean
  pausedByDisk?: boolean
}

type Props = {
  jobs: RecordJob[]
  pending?: PendingWatchedRoom[]
  autostartState?: AutostartState
  onRefreshAutostartState?: () => Promise<void> | void
  modelsByKey?: Record<
    string,
    {
      favorite?: boolean
      liked?: boolean | null
      watching?: boolean
      roomStatus?: string
      imageUrl?: string
      gender?: string | null
      country?: string | null
    }
  >
  roomStatusByModelKey?: Record<string, string>
  onOpenPlayer: (job: RecordJob) => void
  onStopJob: (id: string) => void | Promise<void>
  onStopAllJobs?: () => void | Promise<void>
  onRemoveQueuedPostworkJob?: (id: string) => void | Promise<void>
  blurPreviews?: boolean
  onToggleFavorite?: (job: RecordJob) => void | Promise<void>
  onToggleLike?: (job: RecordJob) => void | Promise<void>
  onToggleWatch?: (job: RecordJob) => void | Promise<void>
  onAddToDownloads?: (job: RecordJob) => boolean | void | Promise<boolean | void>
  onRemovePending?: (pending: PendingWatchedRoom) => void | Promise<void>

  enableConcurrentDownloadsLimit?: boolean
  maxConcurrentDownloads?: number
}

const countryOfJob = (
  job: RecordJob,
  modelsByKey?: Record<string, { country?: string | null }>
): string => {
  const anyJ = job as any
  const modelKey = modelKeyFromJob(job)

  return String(
    modelsByKey?.[modelKey]?.country ??
    anyJ?.country ??
    anyJ?.model?.country ??
    anyJ?.room?.country ??
    anyJ?.meta?.country ??
    anyJ?.meta?.model?.country ??
    ''
  ).trim()
}

const countryOfPending = (
  p: PendingWatchedRoom,
  modelsByKey?: Record<string, { country?: string | null }>
): string => {
  const anyP = p as any
  const pendingKey = String(anyP?.modelKey ?? pendingModelName(p)).trim().toLowerCase()

  return String(
    modelsByKey?.[pendingKey]?.country ??
    anyP?.country ??
    anyP?.room?.country ??
    ''
  ).trim()
}

const isChaturbateJob = (
  job: RecordJob,
  modelsByKey?: Record<string, { imageUrl?: string }>
): boolean => {
  const anyJ = job as any
  const src = String(anyJ?.sourceUrl ?? anyJ?.SourceURL ?? '').toLowerCase()
  const chat = String(anyJ?.modelChatRoomUrl ?? '').toLowerCase()
  const img = onlineImageUrlOfJob(job, modelsByKey).toLowerCase()

  return (
    src.includes('chaturbate.com') ||
    chat.includes('chaturbate.com') ||
    img.includes('highwebmedia') ||
    img.includes('chaturbate')
  )
}

const previewInitialSrcOfJob = (
  job: RecordJob,
  modelsByKey?: Record<string, { imageUrl?: string }>
): string => {
  if (!isChaturbateJob(job, modelsByKey)) return ''
  return onlineImageUrlOfJob(job, modelsByKey)
}

const pendingModelName = (p: PendingWatchedRoom) => {
  const anyP = p as any
  return (
    anyP.modelName ??
    anyP.model ??
    anyP.modelKey ??
    anyP.username ??
    anyP.name ??
    '—'
  )
}

const pendingUrl = (p: PendingWatchedRoom) => {
  const anyP = p as any
  return anyP.sourceUrl ?? anyP.url ?? anyP.roomUrl ?? ''
}

const pendingImageUrl = (p: PendingWatchedRoom) => {
  const anyP = p as any
  return String(anyP.imageUrl ?? anyP.image_url ?? '').trim()
}

const pendingRowKey = (p: PendingWatchedRoom) => {
  const anyP = p as any
  return String(anyP.key ?? anyP.id ?? pendingModelName(p))
}

const normalizeRoomStatus = (v: unknown): string => {
  const s = String(v ?? '').trim().toLowerCase()

  switch (s) {
    case 'public':
      return 'Public'
    case 'private':
      return 'Private'
    case 'hidden':
      return 'Hidden'
    case 'away':
      return 'Away'
    case 'offline':
      return 'Offline'
    default:
      return 'Unknown'
  }
}

const modelKeyFromJob = (job: RecordJob): string => {
  const j = job as any

  const rawUrl = String(j.sourceUrl ?? '').trim()
  if (rawUrl) {
    const m = rawUrl.match(/chaturbate\.com\/([^/?#]+)/i)
    if (m?.[1]) {
      try {
        return decodeURIComponent(m[1]).trim().toLowerCase()
      } catch {
        return String(m[1]).trim().toLowerCase()
      }
    }
  }

  return String(modelNameFromOutput(j.output || '')).trim().toLowerCase()
}

const onlineImageUrlOfJob = (
  job: RecordJob,
  modelsByKey?: Record<string, { imageUrl?: string }>
): string => {
  const anyJ = job as any

  const direct =
    String(
      anyJ?.modelImageUrl ??
      anyJ?.model?.imageUrl ??
      ''
    ).trim()

  if (direct) return direct

  const modelKey = modelKeyFromJob(job)
  if (!modelKey) return ''

  return String(modelsByKey?.[modelKey]?.imageUrl ?? '').trim()
}

const roomStatusOfJob = (
  job: RecordJob,
  roomStatusByModelKey?: Record<string, string>,
  modelsByKey?: Record<string, { roomStatus?: string }>
): string => {
  const j = job as any

  const rawFromJob =
    j.currentShow ??
    j.roomStatus ??
    j.modelStatus ??
    j.show ??
    j.statusText ??
    j.model?.currentShow ??
    j.model?.roomStatus ??
    j.model?.status ??
    j.room?.currentShow ??
    j.room?.status

  if (rawFromJob != null && String(rawFromJob).trim() !== '') {
    return normalizeRoomStatus(rawFromJob)
  }

  const modelKey = modelKeyFromJob(job)

  if (modelKey && roomStatusByModelKey?.[modelKey]) {
    return normalizeRoomStatus(roomStatusByModelKey[modelKey])
  }

  if (modelKey && modelsByKey?.[modelKey]?.roomStatus) {
    return normalizeRoomStatus(modelsByKey[modelKey].roomStatus)
  }

  return 'Unknown'
}

function effectiveRoomStatusOfJob(
  job: RecordJob,
  roomStatusByModelKey?: Record<string, string>,
  modelsByKey?: Record<string, { roomStatus?: string }>,
  growingByJobId?: Record<string, boolean>
): string {
  const raw = roomStatusOfJob(job, roomStatusByModelKey, modelsByKey)
  if (raw !== 'Unknown') return raw

  const anyJ = job as any
  const phaseLower = String(anyJ?.phase ?? '').trim().toLowerCase()
  const statusLower = String(job.status ?? '').trim().toLowerCase()

  const isActiveRecording =
    !job.endedAt &&
    statusLower === 'running' &&
    (phaseLower === '' || phaseLower === 'recording')

  if (isActiveRecording && growingByJobId?.[job.id]) {
    return 'Public'
  }

  return raw
}

const roomStatusTone = (status: string): string => {
  switch (status) {
    case 'Public':
      return 'bg-emerald-500/15 text-emerald-900 ring-emerald-500/30 dark:bg-emerald-400/10 dark:text-emerald-200 dark:ring-emerald-400/25'
    case 'Private':
      return 'bg-fuchsia-500/15 text-fuchsia-900 ring-fuchsia-500/30 dark:bg-fuchsia-400/10 dark:text-fuchsia-200 dark:ring-fuchsia-400/25'
    case 'Hidden':
      return 'bg-slate-500/15 text-slate-900 ring-slate-500/30 dark:bg-slate-400/10 dark:text-slate-200 dark:ring-slate-400/25'
    case 'Away':
      return 'bg-amber-500/15 text-amber-900 ring-amber-500/30 dark:bg-amber-400/10 dark:text-amber-200 dark:ring-amber-400/25'
    case 'Offline':
      return 'bg-gray-900/5 text-gray-800 ring-gray-900/10 dark:bg-white/10 dark:text-gray-200 dark:ring-white/10'
    case 'Unknown':
    default:
      return 'bg-gray-900/5 text-gray-800 ring-gray-900/10 dark:bg-white/10 dark:text-gray-200 dark:ring-white/10'
  }
}

const toMs = (v: unknown): number => {
  if (typeof v === 'number' && Number.isFinite(v)) {
    return v < 1_000_000_000_000 ? v * 1000 : v
  }
  if (typeof v === 'string') {
    const ms = Date.parse(v)
    return Number.isFinite(ms) ? ms : 0
  }
  if (v instanceof Date) return v.getTime()
  return 0
}

const addedAtMsOf = (r: DownloadRow): number => {
  if (r.kind === 'job') {
    const j = r.job as any
    return (
      toMs(j.addedAt) ||
      toMs(j.createdAt) ||
      toMs(j.enqueuedAt) ||
      toMs(j.queuedAt) ||
      toMs(j.requestedAt) ||
      toMs(j.startedAt) ||
      0
    )
  }

  const p = r.pending as any
  return (
    toMs(p.addedAt) ||
    toMs(p.createdAt) ||
    toMs(p.enqueuedAt) ||
    toMs(p.queuedAt) ||
    toMs(p.requestedAt) ||
    0
  )
}

const phaseLabel = (p?: string) => {
  switch ((p ?? '').toLowerCase()) {
    case 'stopping':
      return 'Stop wird angefordert…'
    case 'probe':
      return 'Analysiere Datei (Dauer/Streams)…'
    case 'remuxing':
      return 'Konvertiere Container zu MP4…'
    case 'moving':
      return 'Verschiebe nach Done…'
    case 'assets':
      return 'Erstelle Vorschau/Thumbnails…'
    case 'analyze':
      return 'AI analysiert Segmente…'
    case 'postwork':
      return 'Nacharbeiten laufen…'
    default:
      return ''
  }
}

function postWorkLabel(
  job: RecordJob,
  override?: { pos?: number; total?: number }
): string {
  const anyJ = job as any
  const pw = anyJ.postWork

  const effectiveState = getEffectivePostworkState(job)

  if (effectiveState === 'running') {
    const running = typeof pw?.running === 'number' && pw.running > 0 ? pw.running : 1
    const maxP = typeof pw?.maxParallel === 'number' ? pw.maxParallel : 0

    return maxP > 0
      ? `Nacharbeiten laufen… (${running}/${maxP} parallel)`
      : 'Nacharbeiten laufen…'
  }

  if (effectiveState === 'queued') {
    const posServer = typeof pw?.position === 'number' ? pw.position : 0
    const waitingServer = typeof pw?.waiting === 'number' ? pw.waiting : 0
    const totalServer = Math.max(waitingServer, posServer)

    const pos =
      typeof override?.pos === 'number' && Number.isFinite(override.pos) && override.pos > 0
        ? override.pos
        : posServer

    const total =
      typeof override?.total === 'number' && Number.isFinite(override.total) && override.total > 0
        ? override.total
        : totalServer

    return pos > 0 && total > 0
      ? `Warte auf Nacharbeiten… ${pos} / ${total}`
      : 'Warte auf Nacharbeiten…'
  }

  return 'Warte auf Nacharbeiten…'
}

function StatusCell({
  job,
  postworkInfo,
}: {
  job: RecordJob
  postworkInfo?: { pos?: number; total?: number }
}) {
  const anyJ = job as any
  const phaseRaw = String(anyJ?.phase ?? '').trim()
  const phase = phaseRaw.toLowerCase()
  const progress = Number(anyJ?.progress ?? 0)

  const isRecording = phase === 'recording'
  const isPwJob = isPostworkJob(job)
  const pwState = getEffectivePostworkState(job)

  let text = ''

  if (isRecording) {
    text = 'Recording läuft…'
  } else if (isPwJob) {
    if (phase === 'probe' || phase === 'remuxing' || phase === 'moving' || phase === 'assets') {
      text = phaseLabel(phaseRaw) || phaseRaw
    } else if (pwState === 'queued') {
      text = postWorkLabel(job, postworkInfo)
    } else if (pwState === 'running') {
      text = phaseLabel(phaseRaw) || postWorkLabel(job, postworkInfo)
    }
  }

  if (!text) {
    text = phaseRaw ? (phaseLabel(phaseRaw) || phaseRaw) : String(anyJ?.status ?? '').trim().toLowerCase()
  }

  const showBar =
    !isRecording &&
    Number.isFinite(progress) &&
    progress > 0 &&
    progress < 100

  const showIndeterminate =
    !isRecording &&
    !showBar &&
    Boolean(text) &&
    (!Number.isFinite(progress) || progress <= 0 || progress >= 100)

  return (
    <div className="min-w-0">
      {showBar ? (
        <ProgressBar
          label={text}
          value={Math.max(0, Math.min(100, progress))}
          showPercent
          size="sm"
          className="w-full min-w-0 sm:min-w-[220px]"
        />
      ) : showIndeterminate ? (
        <ProgressBar
          label={text}
          indeterminate
          size="sm"
          className="w-full min-w-0 sm:min-w-[220px]"
        />
      ) : (
        <div className="truncate">
          <span className="font-medium">{text}</span>
        </div>
      )}
    </div>
  )
}

const baseName = (p: string) =>
  (p || '').replaceAll('\\', '/').trim().split('/').pop() || ''

const modelNameFromOutput = (output?: string) => {
  const file = baseName(output || '')
  if (!file) return '—'
  const stem = file.replace(/\.[^.]+$/, '')

  const m = stem.match(/^(.*?)_\d{1,2}_\d{1,2}_\d{4}__\d{1,2}-\d{2}-\d{2}$/)
  if (m?.[1]) return m[1]

  const i = stem.lastIndexOf('_')
  return i > 0 ? stem.slice(0, i) : stem
}

const formatDuration = (ms: number): string => {
  if (!Number.isFinite(ms) || ms <= 0) return '—'
  const total = Math.floor(ms / 1000)
  const h = Math.floor(total / 3600)
  const m = Math.floor((total % 3600) / 60)
  const s = total % 60
  if (h > 0) return `${h}h ${m}m`
  if (m > 0) return `${m}m ${s}s`
  return `${s}s`
}

const runtimeOf = (j: RecordJob, nowMs: number) => {
  const anyJ = j as any

  const start =
    toMs(anyJ.startedAtMs) ||
    toMs(j.startedAt)

  if (!Number.isFinite(start) || start <= 0) return '—'

  const end =
    j.endedAt
      ? (toMs(anyJ.endedAtMs) || toMs(j.endedAt))
      : nowMs

  if (!Number.isFinite(end) || end <= 0) return '—'

  return formatDuration(end - start)
}

const sizeBytesOf = (job: RecordJob): number | null => {
  const anyJob = job as any
  const v = anyJob.sizeBytes ?? anyJob.fileSizeBytes ?? anyJob.bytes ?? anyJob.size ?? null
  return typeof v === 'number' && Number.isFinite(v) && v > 0 ? v : null
}

const formatBytes = (bytes: number | null): string => {
  if (!bytes || !Number.isFinite(bytes) || bytes <= 0) return '—'
  const units = ['B', 'KB', 'MB', 'GB', 'TB']
  let v = bytes
  let i = 0
  while (v >= 1024 && i < units.length - 1) {
    v /= 1024
    i++
  }
  const digits = i === 0 ? 0 : v >= 10 ? 1 : 2
  const s = v.toFixed(digits).replace(/\.0+$/, '')
  return `${s} ${units[i]}`
}

const isPostworkJob = (job: RecordJob): boolean => {
  const anyJ = job as any
  const phase = String(anyJ.phase ?? '').trim()
  const pw = anyJ.postWork
  const pwKey = String(anyJ.postWorkKey ?? '').trim()

  if (pwKey) return true
  if (pw && (pw.state === 'queued' || pw.state === 'running')) return true
  if (job.endedAt && phase) return true
  if (phase === 'postwork') return true

  return false
}

const getEffectivePostworkState = (job: RecordJob): 'running' | 'queued' | 'none' => {
  const anyJ = job as any
  const phase = String(anyJ.phase ?? '').trim().toLowerCase()
  const pw = anyJ.postWork
  const pwState = String(pw?.state ?? '').trim().toLowerCase()
  const hasPwKey = String(anyJ.postWorkKey ?? '').trim() !== ''

  if (!isPostworkJob(job)) return 'none'
  if (isTerminalStatus(anyJ?.status)) return 'none'

  if (pwState === 'queued') return 'queued'
  if (pwState === 'running') return 'running'

  if (
    phase === 'postwork' ||
    phase === 'probe' ||
    phase === 'remuxing' ||
    phase === 'moving' ||
    phase === 'assets'
  ) {
    return 'running'
  }

  if (
    typeof pw?.position === 'number' &&
    Number.isFinite(pw.position) &&
    pw.position > 0
  ) {
    return 'queued'
  }

  if (
    typeof pw?.running === 'number' &&
    Number.isFinite(pw.running) &&
    pw.running > 0 &&
    (!Number.isFinite(pw?.position) || pw.position <= 0)
  ) {
    return 'running'
  }

  if (hasPwKey) {
    return 'queued'
  }

  if (anyJ.endedAt || job.endedAt) return 'queued'

  return 'none'
}

const isTerminalStatus = (status?: unknown) => {
  const s = String(status ?? '').trim().toLowerCase()
  return (
    s === 'stopped' ||
    s === 'finished' ||
    s === 'failed' ||
    s === 'done' ||
    s === 'completed' ||
    s === 'canceled' ||
    s === 'cancelled'
  )
}

function DiskEmergencyBadge() {
  return (
    <span
      className="
        inline-flex items-center rounded-full px-2 py-0.5 text-[11px] font-semibold
        bg-red-100 text-red-800 ring-1 ring-red-200
        dark:bg-red-500/15 dark:text-red-200 dark:ring-red-400/25
      "
      title="Speicherplatz-Notbremse aktiv: Autostart gesperrt und Downloads wurden gestoppt"
    >
      Speicherplatz-Notbremse aktiv
    </span>
  )
}

export default function Downloads({
  jobs,
  pending = [],
  autostartState,
  onRefreshAutostartState,
  onOpenPlayer,
  onStopJob,
  onStopAllJobs,
  onToggleFavorite,
  onToggleLike,
  onToggleWatch,
  onAddToDownloads,
  onRemovePending,
  onRemoveQueuedPostworkJob,
  modelsByKey = {},
  roomStatusByModelKey = {},
  blurPreviews,
  enableConcurrentDownloadsLimit = false,
  maxConcurrentDownloads = 20,
}: Props) {
  const isDesktop = useMediaQuery('(min-width: 640px)', true)

  const [stopAllBusy, setStopAllBusy] = useState(false)

  const [watchedPaused, setWatchedPaused] = useState(false)
  const [, setWatchedPausedByUser] = useState(false)
  const [watchedPausedByDisk, setWatchedPausedByDisk] = useState(false)

  const watchedPausedRef = useRef<boolean | null>(null)
  const watchedPausedByUserRef = useRef<boolean | null>(null)
  const watchedPausedByDiskRef = useRef<boolean | null>(null)

  const [watchedBusy, setWatchedBusy] = useState(false)

  const refreshWatchedState = useCallback(async () => {
    try {
      await onRefreshAutostartState?.()
    } catch {
      // ignore
    }
  }, [onRefreshAutostartState])

  useEffect(() => {
    const nextPaused = Boolean(autostartState?.paused)
    const nextPausedByUser = Boolean(autostartState?.pausedByUser)
    const nextPausedByDisk = Boolean(autostartState?.pausedByDisk)

    const unchanged =
      watchedPausedRef.current === nextPaused &&
      watchedPausedByUserRef.current === nextPausedByUser &&
      watchedPausedByDiskRef.current === nextPausedByDisk

    if (unchanged) return

    watchedPausedRef.current = nextPaused
    watchedPausedByUserRef.current = nextPausedByUser
    watchedPausedByDiskRef.current = nextPausedByDisk

    setWatchedPaused(nextPaused)
    setWatchedPausedByUser(nextPausedByUser)
    setWatchedPausedByDisk(nextPausedByDisk)
  }, [autostartState])

  const pauseWatched = useCallback(async () => {
    if (watchedBusy || watchedPaused) return

    setWatchedBusy(true)
    try {
      const res = await fetch('/api/autostart/pause', { method: 'POST' })
      if (!res.ok) throw new Error(`HTTP ${res.status}`)
      await refreshWatchedState()
    } catch {
      // ignore
    } finally {
      setWatchedBusy(false)
    }
  }, [watchedBusy, watchedPaused, refreshWatchedState])

  const resumeWatched = useCallback(async () => {
    if (watchedBusy || !watchedPaused || watchedPausedByDisk) return

    setWatchedBusy(true)
    try {
      const res = await fetch('/api/autostart/resume', { method: 'POST' })
      if (!res.ok) throw new Error(`HTTP ${res.status}`)
      await refreshWatchedState()
    } catch {
      // ignore
    } finally {
      setWatchedBusy(false)
    }
  }, [watchedBusy, watchedPaused, watchedPausedByDisk, refreshWatchedState])

  const [stopRequestedIds, setStopRequestedIds] = useState<Record<string, true>>({})
  const [stopInitiatedIds, setStopInitiatedIds] = useState<Record<string, true>>({})

  const [removePendingRequestedKeys, setRemovePendingRequestedKeys] = useState<Record<string, true>>({})
  const [removeQueuedPostworkRequestedIds, setRemoveQueuedPostworkRequestedIds] = useState<Record<string, true>>({})

  const markStopRequested = useCallback((ids: string | string[]) => {
    const arr = Array.isArray(ids) ? ids : [ids]

    setStopRequestedIds((prev) => {
      const next = { ...prev }
      for (const id of arr) {
        if (id) next[id] = true
      }
      return next
    })

    setStopInitiatedIds((prev) => {
      const next = { ...prev }
      for (const id of arr) {
        if (id) next[id] = true
      }
      return next
    })
  }, [])

  const markRemovePendingRequested = useCallback((key: string) => {
    if (!key) return
    setRemovePendingRequestedKeys((prev) => ({ ...prev, [key]: true }))
  }, [])

  const clearRemovePendingRequested = useCallback((key: string) => {
    if (!key) return
    setRemovePendingRequestedKeys((prev) => {
      if (!prev[key]) return prev
      const next = { ...prev }
      delete next[key]
      return next
    })
  }, [])

  const markRemoveQueuedPostworkRequested = useCallback((id: string) => {
    if (!id) return
    setRemoveQueuedPostworkRequestedIds((prev) => ({ ...prev, [id]: true }))
  }, [])

  const clearRemoveQueuedPostworkRequested = useCallback((id: string) => {
    if (!id) return
    setRemoveQueuedPostworkRequestedIds((prev) => {
      if (!prev[id]) return prev
      const next = { ...prev }
      delete next[id]
      return next
    })
  }, [])

  useEffect(() => {
    setStopRequestedIds((prev) => {
      const keys = Object.keys(prev)
      if (keys.length === 0) return prev

      const next: Record<string, true> = {}
      for (const id of keys) {
        const j = jobs.find((x) => x.id === id)
        if (!j) continue

        const postworkState = getEffectivePostworkState(j)
        const isQueuedPostwork = postworkState === 'queued'
        if (isQueuedPostwork) {
          continue
        }

        const phaseLower = String((j as any).phase ?? '').trim().toLowerCase()
        const isBusyPhase = phaseLower !== '' && phaseLower !== 'recording'
        const isStopping = isBusyPhase || j.status !== 'running'

        if (isStopping) {
          next[id] = true
        }
      }
      return next
    })
  }, [jobs])

  useEffect(() => {
    setRemoveQueuedPostworkRequestedIds((prev) => {
      const keys = Object.keys(prev)
      if (keys.length === 0) return prev

      const liveIds = new Set(jobs.map((j) => String(j.id ?? '').trim()))
      const next: Record<string, true> = {}

      for (const id of keys) {
        if (liveIds.has(id)) next[id] = true
      }

      return Object.keys(next).length === keys.length ? prev : next
    })
  }, [jobs])

  useEffect(() => {
    setRemovePendingRequestedKeys((prev) => {
      const keys = Object.keys(prev)
      if (keys.length === 0) return prev

      const liveKeys = new Set(pending.map((p) => pendingRowKey(p)))
      const next: Record<string, true> = {}

      for (const key of keys) {
        if (liveKeys.has(key)) next[key] = true
      }

      return Object.keys(next).length === keys.length ? prev : next
    })
  }, [pending])

  const [nowMs, setNowMs] = useState(() => Date.now())

  const prevSizeBytesRef = useRef<Record<string, number>>({})
  const [growingByJobId, setGrowingByJobId] = useState<Record<string, boolean>>({})

  const hasActive = useMemo(() => {
    return jobs.some((j) => !j.endedAt && j.status === 'running')
  }, [jobs])

  const activeDownloadCount = useMemo(() => {
    return jobs.filter((j) => {
      if (isPostworkJob(j)) return false
      if ((j as any).endedAt) return false
      return String(j.status ?? '').trim().toLowerCase() === 'running'
    }).length
  }, [jobs])

  const concurrentLimitHintVisible = enableConcurrentDownloadsLimit
  const concurrentLimitReached =
    enableConcurrentDownloadsLimit && activeDownloadCount >= maxConcurrentDownloads

  useEffect(() => {
    setGrowingByJobId((prev) => {
      const next: Record<string, boolean> = { ...prev }
      const seen = new Set<string>()
      let changed = false

      for (const job of jobs) {
        const id = String(job.id ?? '').trim()
        if (!id) continue

        seen.add(id)

        const anyJ = job as any
        const phaseLower = String(anyJ?.phase ?? '').trim().toLowerCase()
        const statusLower = String(job.status ?? '').trim().toLowerCase()

        const isActiveRecording =
          !job.endedAt &&
          statusLower === 'running' &&
          (phaseLower === '' || phaseLower === 'recording')

        const currentSize = sizeBytesOf(job) ?? 0
        const prevSize = prevSizeBytesRef.current[id] ?? 0

        const grewNow = currentSize > prevSize
        const nextGrowing = isActiveRecording ? (prev[id] === true || grewNow) : false

        if (next[id] !== nextGrowing) {
          next[id] = nextGrowing
          changed = true
        }

        prevSizeBytesRef.current[id] = currentSize
      }

      for (const id of Object.keys(prevSizeBytesRef.current)) {
        if (!seen.has(id)) {
          delete prevSizeBytesRef.current[id]
        }
      }

      for (const id of Object.keys(next)) {
        if (!seen.has(id)) {
          delete next[id]
          changed = true
        }
      }

      return changed ? next : prev
    })
  }, [jobs, nowMs])

  const postworkQueueInfoById = useMemo(() => {
    const infoById = new Map<string, { pos: number; total: number }>()

    const enqueueMsOf = (job: RecordJob): number => {
      const anyJ = job as any
      const pw = anyJ.postWork
      return (
        toMs(pw?.enqueuedAt) ||
        toMs(anyJ.enqueuedAt) ||
        toMs(anyJ.queuedAt) ||
        toMs(anyJ.createdAt) ||
        toMs(anyJ.addedAt) ||
        toMs(job.endedAt) ||
        toMs(job.startedAt) ||
        0
      )
    }

    const runningSortMsOf = (job: RecordJob): number => {
      const anyJ = job as any
      return (
        toMs(anyJ.updatedAt) ||
        toMs(anyJ.phaseUpdatedAt) ||
        toMs(anyJ.progressUpdatedAt) ||
        toMs(anyJ.postWork?.updatedAt) ||
        enqueueMsOf(job)
      )
    }

    const running: RecordJob[] = []
    const queued: RecordJob[] = []

    for (const j of jobs) {
      if (!isPostworkJob(j)) continue
      if (isTerminalStatus((j as any)?.status)) continue

      const state = getEffectivePostworkState(j)

      if (state === 'running') {
        running.push(j)
      } else if (state === 'queued') {
        queued.push(j)
      }
    }

    running.sort((a, b) => runningSortMsOf(b) - runningSortMsOf(a))
    queued.sort((a, b) => enqueueMsOf(a) - enqueueMsOf(b))

    const runningCount = running.length
    const queuedCount = queued.length
    const total = runningCount + queuedCount

    for (let i = 0; i < running.length; i++) {
      const id = String((running[i] as any)?.id ?? '')
      if (!id) continue
      infoById.set(id, { pos: i + 1, total })
    }

    for (let i = 0; i < queued.length; i++) {
      const id = String((queued[i] as any)?.id ?? '')
      if (!id) continue
      infoById.set(id, { pos: i + 1, total: queuedCount })
    }

    return infoById
  }, [jobs])

  const postworkInfoOf = useCallback(
    (job: RecordJob) => {
      const id = String((job as any)?.id ?? '')
      return id ? postworkQueueInfoById.get(id) : undefined
    },
    [postworkQueueInfoById]
  )

  useEffect(() => {
    if (!hasActive) return
    const t = window.setInterval(() => setNowMs(Date.now()), 5000)
    return () => window.clearInterval(t)
  }, [hasActive])

  const stoppableIds = useMemo(() => {
    return jobs
      .filter((j) => {
        if (isPostworkJob(j)) return false
        if ((j as any).endedAt) return false

        const phase = String((j as any).phase ?? '').trim()
        const isStopRequested = Boolean(stopRequestedIds[j.id])
        const phaseLower = phase.trim().toLowerCase()
        const isBusyPhase = phaseLower !== '' && phaseLower !== 'recording'
        const isStopping = isBusyPhase || j.status !== 'running' || isStopRequested
        return !isStopping
      })
      .map((j) => j.id)
  }, [jobs, stopRequestedIds])

  const columns = useMemo<Column<DownloadRow>[]>(() => {
    return [
      {
        key: 'preview',
        header: 'Vorschau',
        widthClassName: 'w-[96px]',
        cell: (r) => {
          if (r.kind === 'job') {
            const j = r.job
            const directPreviewSrc = previewInitialSrcOfJob(j, modelsByKey)

            return (
              <div className="grid w-[96px] h-[60px] overflow-hidden rounded-md">
                <LazyMountWhenVisible
                  className="w-full h-full"
                  placeholder={<div className="h-full w-full bg-gray-100 dark:bg-white/10" />}
                >
                  <ModelPreview
                    jobId={j.id}
                    initialSrc={directPreviewSrc || undefined}
                    enableHoverLive={true}
                    blur={blurPreviews}
                    roomStatus={effectiveRoomStatusOfJob(
                      j,
                      roomStatusByModelKey,
                      modelsByKey,
                      growingByJobId
                    )}
                    fallbackSrc={`/api/preview?id=${encodeURIComponent(j.id)}&fallbackOnly=1`}
                    className="w-full h-full"
                  />
                </LazyMountWhenVisible>
              </div>
            )
          }

          const p = r.pending
          const name = pendingModelName(p)
          const img = pendingImageUrl(p)

          if (img) {
            return (
              <div className="grid w-[96px] h-[60px] overflow-hidden rounded-md bg-gray-100 ring-1 ring-black/5 dark:bg-white/10 dark:ring-white/10">
                <PendingPreviewImage
                  src={img}
                  alt={name}
                  blur={blurPreviews}
                  className="h-full w-full"
                />
              </div>
            )
          }

          return (
            <div className="h-[60px] w-[96px] rounded-md bg-gray-100 dark:bg-white/10 grid place-items-center text-sm text-gray-500">
              ⏳
            </div>
          )
        },
      },
      {
        key: 'model',
        header: 'Modelname',
        widthClassName: 'w-[170px]',
        cell: (r) => {
          if (r.kind === 'job') {
            const j = r.job
            const f = baseName(j.output || '')
            const name = modelNameFromOutput(j.output)
            const modelKey = modelKeyFromJob(j)
            const modelMeta = modelKey ? modelsByKey[modelKey] : undefined

            const roomStatus = effectiveRoomStatusOfJob(
              j,
              roomStatusByModelKey,
              modelsByKey,
              growingByJobId
            )

            return (
              <>
                <div className="flex items-center gap-2 min-w-0">
                  <span className="min-w-0 block max-w-[170px] truncate font-medium" title={name}>
                    {name}
                  </span>

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

                  <CountryFlag
                    country={countryOfJob(j, modelsByKey)}
                    size="sm"
                    className="shrink-0"
                  />

                  <span
                    className={[
                      'shrink-0 inline-flex items-center rounded-full px-2 py-0.5 text-[10px] font-semibold ring-1',
                      roomStatusTone(roomStatus),
                    ].join(' ')}
                    title={roomStatus}
                  >
                    {roomStatus}
                  </span>
                </div>
                <span className="block max-w-[220px] truncate" title={j.output}>
                  {f}
                </span>
                {j.sourceUrl ? (
                  <a
                    href={j.sourceUrl}
                    target="_blank"
                    rel="noreferrer"
                    title={j.sourceUrl}
                    className="block max-w-[260px] truncate text-indigo-600 dark:text-indigo-400 hover:underline"
                    onClick={(e) => e.stopPropagation()}
                  >
                    {j.sourceUrl}
                  </a>
                ) : (
                  <span className="block max-w-[260px] truncate text-gray-500 dark:text-gray-400">
                    —
                  </span>
                )}
              </>
            )
          }

          const p = r.pending
          const name = pendingModelName(p)
          const url = pendingUrl(p)

          const pendingKey = String((p as any)?.modelKey ?? name).trim().toLowerCase()
          const pendingFlags = pendingKey ? modelsByKey[pendingKey] : undefined

          return (
            <>
              <div className="flex min-w-0 items-center gap-2">
                <span className="block max-w-[170px] truncate font-medium" title={name}>
                  {name}
                </span>

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

                <CountryFlag
                  country={countryOfPending(p, modelsByKey)}
                  size="sm"
                  className="shrink-0"
                />
              </div>

              {url ? (
                <a
                  href={url}
                  target="_blank"
                  rel="noreferrer"
                  title={url}
                  className="block max-w-[260px] truncate text-indigo-600 dark:text-indigo-400 hover:underline"
                  onClick={(e) => e.stopPropagation()}
                >
                  {url}
                </a>
              ) : (
                <span className="block max-w-[260px] truncate text-gray-500 dark:text-gray-400">
                  —
                </span>
              )}
            </>
          )
        },
      },
      {
        key: 'status',
        header: 'Status',
        widthClassName: 'w-[260px] min-w-[240px]',
        cell: (r) => {
          if (r.kind === 'job') {
            const j = r.job
            return <StatusCell job={j} postworkInfo={postworkInfoOf(j)} />
          }

          const p = r.pending
          const show = normalizeRoomStatus(p.currentShow)

          return (
            <div className="min-w-0">
              <div className="truncate">
                <span className="font-medium">
                  {show}
                </span>
              </div>
            </div>
          )
        },
      },
      {
        key: 'runtime',
        header: 'Dauer',
        widthClassName: 'w-[90px]',
        cell: (r) => {
          if (r.kind === 'job') return runtimeOf(r.job, nowMs)
          return '—'
        },
      },
      {
        key: 'size',
        header: 'Größe',
        align: 'right',
        widthClassName: 'w-[110px]',
        cell: (r) => {
          if (r.kind === 'job') {
            return (
              <span className="tabular-nums text-sm text-gray-900 dark:text-white">
                {formatBytes(sizeBytesOf(r.job))}
              </span>
            )
          }
          return (
            <span className="tabular-nums text-sm text-gray-500 dark:text-gray-400">
              —
            </span>
          )
        },
      },
      {
        key: 'actions',
        header: 'Aktion',
        srOnlyHeader: true,
        align: 'right',
        widthClassName: 'w-[320px] min-w-[300px]',
        cell: (r) => {
          if (r.kind !== 'job') {
            const pendingKey = String(
              (r.pending as any)?.modelKey ??
              pendingModelName(r.pending)
            ).trim().toLowerCase()

            const pendingFlags = pendingKey ? modelsByKey[pendingKey] : undefined

            const pendingActionJob = {
              id: `pending:${pendingKey || pendingRowKey(r.pending)}`,
              output: `${pendingModelName(r.pending)}_pending.mp4`,
              sourceUrl: pendingUrl(r.pending),
              status: 'pending',
              endedAt: null,
            } as unknown as RecordJob

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

            const removeKey = pendingRowKey(r.pending)
            const isRemovingPending = Boolean(removePendingRequestedKeys[removeKey])

            return (
              <div
                className="flex items-center justify-end gap-2 pr-2"
                onClick={(e) => e.stopPropagation()}
                onMouseDown={(e) => e.stopPropagation()}
              >
                <RecordJobActions
                  job={pendingActionJob}
                  variant="table"
                  busy={false}
                  isFavorite={isFav}
                  isLiked={isLiked}
                  isWatching={isWatching}
                  onToggleFavorite={onToggleFavorite}
                  onToggleLike={onToggleLike}
                  onToggleWatch={onToggleWatch}
                  order={['watch', 'favorite', 'like', 'details']}
                  className="flex items-center gap-1"
                />

                <Button
                  size="sm"
                  variant="primary"
                  color="red"
                  disabled={isRemovingPending || !onRemovePending}
                  className="shrink-0"
                  onClick={async (e) => {
                    e.stopPropagation()
                    if (isRemovingPending) return

                    markRemovePendingRequested(removeKey)
                    try {
                      await onRemovePending?.(r.pending)
                    } catch {
                      clearRemovePendingRequested(removeKey)
                    }
                  }}
                >
                  {isRemovingPending ? 'Entferne…' : 'Entfernen'}
                </Button>
              </div>
            )
          }

          const j = r.job
          const phase = String((j as any).phase ?? '').trim()
          const isStopRequested = Boolean(stopRequestedIds[j.id])
          const phaseLower = phase.trim().toLowerCase()

          const postworkState = getEffectivePostworkState(j)
          const isQueuedPostwork = postworkState === 'queued'
          const isRemovingQueuedPostwork = Boolean(removeQueuedPostworkRequestedIds[j.id])

          const isBusyPhase = phaseLower !== '' && phaseLower !== 'recording'
          const isStopping = isBusyPhase || j.status !== 'running' || isStopRequested

          const buttonBusy = isQueuedPostwork ? isRemovingQueuedPostwork : isStopping
          const disableStopButton = isQueuedPostwork ? isRemovingQueuedPostwork : isStopping

          const key = modelKeyFromJob(j)
          const flags = key ? modelsByKey[key] : undefined
          const isFav = Boolean(flags?.favorite)
          const isLiked = flags?.liked === true
          const isWatching = Boolean(flags?.watching)

          return (
            <div
              className="flex flex-wrap items-center justify-end gap-2 pr-2"
              onClick={(e) => e.stopPropagation()}
              onMouseDown={(e) => e.stopPropagation()}
            >
              <RecordJobActions
                job={j}
                variant="table"
                busy={buttonBusy}
                isFavorite={isFav}
                isLiked={isLiked}
                isWatching={isWatching}
                onToggleFavorite={onToggleFavorite}
                onToggleLike={onToggleLike}
                onToggleWatch={onToggleWatch}
                order={['watch', 'favorite', 'like', 'details']}
                className="flex items-center gap-1"
              />

              <Button
                size="sm"
                variant="primary"
                color={isQueuedPostwork ? 'red' : 'indigo'}
                disabled={disableStopButton}
                className="shrink-0"
                onClick={async (e) => {
                  e.stopPropagation()

                  if (isQueuedPostwork) {
                    if (isRemovingQueuedPostwork) return
                    markRemoveQueuedPostworkRequested(j.id)
                    try {
                      await onRemoveQueuedPostworkJob?.(j.id)
                    } catch {
                      clearRemoveQueuedPostworkRequested(j.id)
                    }
                    return
                  }

                  if (isStopping) return
                  markStopRequested(j.id)
                  await onStopJob(j.id)
                }}
              >
                {isQueuedPostwork
                  ? (isRemovingQueuedPostwork ? 'Entferne…' : 'Entfernen')
                  : (isStopping ? 'Stoppe…' : 'Stoppen')}
              </Button>
            </div>
          )
        },
      },
    ]
  }, [
    blurPreviews,
    markStopRequested,
    modelsByKey,
    roomStatusByModelKey,
    growingByJobId,
    nowMs,
    onStopJob,
    onRemoveQueuedPostworkJob,
    onRemovePending,
    stopRequestedIds,
    stopInitiatedIds,
    postworkInfoOf,
    onAddToDownloads,
  ])

  const downloadJobRows = useMemo<DownloadRow[]>(() => {
  const seen = new Set<string>()

  const list = jobs
      .filter((j) => {
        if (isPostworkJob(j)) return false
        if (isTerminalStatus((j as any)?.status)) return false
        if (Boolean((j as any)?.endedAt)) return false

        const id = String((j as any)?.id ?? '').trim()
        if (!id) return false
        if (seen.has(id)) return false

        seen.add(id)
        return true
      })
      .map((job) => ({ kind: 'job', job }) as const)

    list.sort((a, b) => addedAtMsOf(b) - addedAtMsOf(a))
    return list
  }, [jobs])

  const postworkRows = useMemo<DownloadRow[]>(() => {
    const list = jobs
      .filter((j) => {
        if (!isPostworkJob(j)) return false
        if (isTerminalStatus((j as any)?.status)) return false
        return true
      })
      .map((job) => ({ kind: 'job', job }) as const)

    const stateRank = (j: RecordJob): number => {
      const state = getEffectivePostworkState(j)
      if (state === 'running') return 0
      if (state === 'queued') return 1
      return 2
    }

    const queuePosOf = (j: RecordJob): number => {
      const id = String((j as any)?.id ?? '')
      const info = id ? postworkQueueInfoById.get(id) : undefined
      return typeof info?.pos === 'number' && Number.isFinite(info.pos) ? info.pos : Number.MAX_SAFE_INTEGER
    }

    const runningSortMsOf = (j: RecordJob): number => {
      const anyJ = j as any
      return (
        toMs(anyJ.updatedAt) ||
        toMs(anyJ.phaseUpdatedAt) ||
        toMs(anyJ.progressUpdatedAt) ||
        toMs(anyJ.postWork?.updatedAt) ||
        addedAtMsOf({ kind: 'job', job: j })
      )
    }

    list.sort((a, b) => {
      const aj = a.job
      const bj = b.job

      const sr = stateRank(aj) - stateRank(bj)
      if (sr !== 0) return sr

      if (getEffectivePostworkState(aj) === 'running' && getEffectivePostworkState(bj) === 'running') {
        const ra = runningSortMsOf(aj)
        const rb = runningSortMsOf(bj)
        if (ra !== rb) return rb - ra
      }

      const pa = queuePosOf(aj)
      const pb = queuePosOf(bj)
      if (pa !== pb) return pa - pb

      return addedAtMsOf(a) - addedAtMsOf(b)
    })

    return list
  }, [jobs, postworkQueueInfoById])

  const pendingRows = useMemo<DownloadRow[]>(() => {
    const list = pending.map((p) => ({ kind: 'pending', pending: p }) as const)
    list.sort((a, b) => addedAtMsOf(b) - addedAtMsOf(a))
    return list
  }, [pending])

  const totalCount = downloadJobRows.length + postworkRows.length + pendingRows.length

  const stopAll = useCallback(async () => {
    if (stopAllBusy) return
    if (stoppableIds.length === 0) return

    setStopAllBusy(true)
    try {
      markStopRequested(stoppableIds)

      if (onStopAllJobs) {
        await onStopAllJobs()
      } else {
        await Promise.allSettled(stoppableIds.map((id) => Promise.resolve(onStopJob(id))))
      }
    } finally {
      setStopAllBusy(false)
    }
  }, [stopAllBusy, stoppableIds, markStopRequested, onStopAllJobs, onStopJob])

  const rowClassName = useCallback((r: DownloadRow) => {
    if (r.kind === 'pending') {
      const key = pendingRowKey(r.pending)
      const removing = Boolean(removePendingRequestedKeys[key])

      return [
        'transition-all duration-300',
        removing && 'bg-red-50/70 dark:bg-red-500/10 pointer-events-none animate-pulse',
      ]
        .filter(Boolean)
        .join(' ')
    }

    const j = r.job
    const stopRequested = Boolean(stopRequestedIds[j.id])
    const removingQueued = Boolean(removeQueuedPostworkRequestedIds[j.id])
    const isQueuedPostwork = getEffectivePostworkState(j) === 'queued'

    return [
      'transition-all duration-300',
      stopRequested && !isQueuedPostwork && 'bg-indigo-50/70 dark:bg-indigo-500/10 pointer-events-none animate-pulse',
      removingQueued && isQueuedPostwork && 'bg-red-50/70 dark:bg-red-500/10 pointer-events-none animate-pulse',
    ]
      .filter(Boolean)
      .join(' ')
  }, [removePendingRequestedKeys, stopRequestedIds, removeQueuedPostworkRequestedIds])

  return (
    <div className="grid gap-3">
      <div className="sticky top-[56px] z-20">
        <div
          className="
            rounded-xl border border-gray-200/70 bg-white/80 shadow-sm
            backdrop-blur supports-[backdrop-filter]:bg-white/60
            dark:border-white/10 dark:bg-gray-950/60 dark:supports-[backdrop-filter]:bg-gray-950/40
          "
        >
          <div className="p-3">
            {/* Mobile */}
            <div className="sm:hidden">
              <div className="flex items-center justify-between gap-2">
                <div className="min-w-0 flex items-center gap-2">
                  <div className="truncate text-sm font-semibold text-gray-900 dark:text-white">
                    Downloads
                  </div>
                  <span className="shrink-0 rounded-full bg-gray-100 px-2 py-0.5 text-xs font-medium text-gray-900 dark:bg-white/10 dark:text-gray-200">
                    {totalCount}
                  </span>
                </div>

                {concurrentLimitHintVisible ? (
                  <div
                    className={[
                      'shrink-0 rounded-full px-2 py-0.5 text-[11px] font-medium ring-1',
                      'max-w-[55%] truncate',
                      concurrentLimitReached
                        ? 'bg-amber-50 text-amber-900 ring-amber-200 dark:bg-amber-400/10 dark:text-amber-100 dark:ring-amber-400/20'
                        : 'bg-blue-50 text-blue-900 ring-blue-200 dark:bg-blue-400/10 dark:text-blue-100 dark:ring-blue-400/20',
                    ].join(' ')}
                    title={
                      concurrentLimitReached
                        ? `Downloads: ${activeDownloadCount}/${maxConcurrentDownloads} · Limit erreicht`
                        : `Downloads: ${activeDownloadCount}/${maxConcurrentDownloads}`
                    }
                  >
                    Downloads: {activeDownloadCount}/{maxConcurrentDownloads}
                  </div>
                ) : null}
              </div>

              <div className="mt-2 grid grid-cols-2 gap-2">
                <Button
                  size="sm"
                  variant={watchedPaused ? 'secondary' : 'primary'}
                  disabled={watchedBusy || watchedPausedByDisk}
                  onClick={(e) => {
                    e.preventDefault()
                    e.stopPropagation()
                    if (watchedPausedByDisk) return
                    void (watchedPaused ? resumeWatched() : pauseWatched())
                  }}
                  className="w-full justify-center"
                  title={
                    watchedPausedByDisk
                      ? 'Autostart durch Speicherplatz-Notbremse gesperrt'
                      : watchedPaused
                        ? 'Autostart fortsetzen'
                        : 'Autostart pausieren'
                  }
                  leadingIcon={
                    watchedPaused
                      ? <PauseIcon className="size-4 shrink-0" />
                      : <PlayIcon className="size-4 shrink-0" />
                  }
                >
                  Autostart
                </Button>

                <Button
                  size="sm"
                  variant="primary"
                  disabled={stopAllBusy || stoppableIds.length === 0}
                  onClick={(e) => {
                    e.preventDefault()
                    e.stopPropagation()
                    void stopAll()
                  }}
                  className="w-full justify-center"
                  title={stoppableIds.length === 0 ? 'Nichts zu stoppen' : 'Alle laufenden stoppen'}
                >
                  {stopAllBusy ? 'Stoppe…' : `Alle stoppen (${stoppableIds.length})`}
                </Button>
              </div>
            </div>

            {/* Desktop */}
            <div className="hidden sm:flex sm:items-center sm:justify-between sm:gap-2">
              <div className="min-w-0 flex items-center gap-2">
                <div className="truncate text-sm font-semibold text-gray-900 dark:text-white">
                  Downloads
                </div>
                <span className="shrink-0 rounded-full bg-gray-100 px-2 py-0.5 text-xs font-medium text-gray-900 dark:bg-white/10 dark:text-gray-200">
                  {totalCount}
                </span>
              </div>

              <div className="min-w-0 flex items-center gap-2">
                {concurrentLimitHintVisible ? (
                  <div
                    className={[
                      'flex items-center rounded-full px-2.5 py-1 text-xs font-medium ring-1 whitespace-nowrap',
                      concurrentLimitReached
                        ? 'bg-amber-50 text-amber-900 ring-amber-200 dark:bg-amber-400/10 dark:text-amber-100 dark:ring-amber-400/20'
                        : 'bg-blue-50 text-blue-900 ring-blue-200 dark:bg-blue-400/10 dark:text-blue-100 dark:ring-blue-400/20',
                    ].join(' ')}
                    title={
                      concurrentLimitReached
                        ? 'Maximale Anzahl gleichzeitiger Downloads erreicht'
                        : 'Begrenzung für gleichzeitige Downloads ist aktiv'
                    }
                  >
                    Downloads: {activeDownloadCount}/{maxConcurrentDownloads}
                    {concurrentLimitReached ? ' · Limit erreicht' : ''}
                  </div>
                ) : null}

                <Button
                  size="sm"
                  variant={watchedPaused ? 'secondary' : 'primary'}
                  disabled={watchedBusy || watchedPausedByDisk}
                  onClick={(e) => {
                    e.preventDefault()
                    e.stopPropagation()
                    if (watchedPausedByDisk) return
                    void (watchedPaused ? resumeWatched() : pauseWatched())
                  }}
                  title={
                    watchedPausedByDisk
                      ? 'Autostart durch Speicherplatz-Notbremse gesperrt'
                      : watchedPaused
                        ? 'Autostart fortsetzen'
                        : 'Autostart pausieren'
                  }
                  leadingIcon={
                    watchedPaused
                      ? <PauseIcon className="size-4 shrink-0" />
                      : <PlayIcon className="size-4 shrink-0" />
                  }
                >
                  Autostart
                </Button>

                <Button
                  size="sm"
                  variant="primary"
                  disabled={stopAllBusy || stoppableIds.length === 0}
                  onClick={(e) => {
                    e.preventDefault()
                    e.stopPropagation()
                    void stopAll()
                  }}
                  title={stoppableIds.length === 0 ? 'Nichts zu stoppen' : 'Alle laufenden stoppen'}
                >
                  {stopAllBusy ? 'Stoppe alle…' : `Alle stoppen (${stoppableIds.length})`}
                </Button>
              </div>
            </div>
          </div>
        </div>
      </div>

      {(downloadJobRows.length > 0 || postworkRows.length > 0 || pendingRows.length > 0) ? (
        <>
          {!isDesktop ? (
            <div className="mt-3 grid gap-4">
              {downloadJobRows.length > 0 ? (
                <>
                  <div className="flex flex-wrap items-center gap-2 text-xs font-semibold text-gray-700 dark:text-gray-200">
                    <span>Downloads ({downloadJobRows.length})</span>
                    {watchedPausedByDisk ? <DiskEmergencyBadge /> : null}
                  </div>
                  {downloadJobRows.map((r) => {
                    if (r.kind !== 'job') return null

                    const j = r.job
                    const phase = String((j as any).phase ?? '').trim().toLowerCase()
                    const isStopRequested = Boolean(stopRequestedIds[j.id])
                    const postworkState = getEffectivePostworkState(j)
                    const isQueuedPostwork = postworkState === 'queued'
                    const isBusyPhase = phase !== '' && phase !== 'recording'
                    const isStopping = isBusyPhase || j.status !== 'running' || isStopRequested

                    return (
                      <SwipeActionRow
                        key={`dl:${j.id}`}
                        actionLabel={isStopping ? 'Stoppe…' : 'Stoppen'}
                        actionColor="indigo"
                        disabled={isStopping || isQueuedPostwork}
                        onAction={async () => {
                          if (isStopping || isQueuedPostwork) return
                          markStopRequested(j.id)
                          await onStopJob(j.id)
                        }}
                      >
                        <div
                          className={[
                            'transition-all duration-300',
                            isStopping && 'pointer-events-none opacity-70 ring-1 ring-indigo-300 rounded-2xl animate-pulse bg-indigo-50/60 dark:bg-indigo-500/10 dark:ring-indigo-500/30',
                          ].filter(Boolean).join(' ')}
                        >
                          <DownloadsCardRow
                            r={r}
                            nowMs={nowMs}
                            blurPreviews={blurPreviews}
                            modelsByKey={modelsByKey}
                            roomStatusByModelKey={roomStatusByModelKey}
                            postworkInfoOf={postworkInfoOf}
                            stopRequestedIds={stopRequestedIds}
                            growingByJobId={growingByJobId}
                            markStopRequested={markStopRequested}
                            onOpenPlayer={onOpenPlayer}
                            onStopJob={onStopJob}
                            onRemoveQueuedPostworkJob={onRemoveQueuedPostworkJob}
                            onToggleFavorite={onToggleFavorite}
                            onToggleLike={onToggleLike}
                            onToggleWatch={onToggleWatch}
                          />
                        </div>
                      </SwipeActionRow>
                    )
                  })}
                </>
              ) : null}

              {pendingRows.length > 0 ? (
                <>
                  <div className="mt-2 text-xs font-semibold text-gray-700 dark:text-gray-200">
                    Wartend ({pendingRows.length})
                  </div>
                  {pendingRows.map((r) => {
                    if (r.kind !== 'pending') return null

                    const pKey = pendingRowKey(r.pending)
                    const isRemovingPending = Boolean(removePendingRequestedKeys[pKey])

                    return (
                      <SwipeActionRow
                        key={`pending:${pKey}`}
                        actionLabel={isRemovingPending ? 'Entferne…' : 'Entfernen'}
                        actionColor="red"
                        disabled={!onRemovePending || isRemovingPending}
                        onAction={async () => {
                          if (isRemovingPending) return
                          markRemovePendingRequested(pKey)
                          try {
                            await onRemovePending?.(r.pending)
                          } catch {
                            clearRemovePendingRequested(pKey)
                          }
                        }}
                      >
                        <div
                          className={[
                            'transition-all duration-300',
                            isRemovingPending && 'pointer-events-none opacity-70 ring-1 ring-red-300 rounded-2xl animate-pulse bg-red-50/60 dark:bg-red-500/10 dark:ring-red-500/30',
                          ].filter(Boolean).join(' ')}
                        >
                          <DownloadsCardRow
                            r={r}
                            nowMs={nowMs}
                            blurPreviews={blurPreviews}
                            modelsByKey={modelsByKey}
                            roomStatusByModelKey={roomStatusByModelKey}
                            postworkInfoOf={postworkInfoOf}
                            stopRequestedIds={stopRequestedIds}
                            growingByJobId={growingByJobId}
                            markStopRequested={markStopRequested}
                            onOpenPlayer={onOpenPlayer}
                            onStopJob={onStopJob}
                            onRemoveQueuedPostworkJob={onRemoveQueuedPostworkJob}
                            onToggleFavorite={onToggleFavorite}
                            onToggleLike={onToggleLike}
                            onToggleWatch={onToggleWatch}
                          />
                        </div>
                      </SwipeActionRow>
                    )
                  })}
                </>
              ) : null}

              {postworkRows.length > 0 ? (
                <>
                  <div className="mb-2 text-sm font-semibold text-gray-900 dark:text-white">
                    Nacharbeiten ({postworkRows.length})
                  </div>
                  {postworkRows.map((r) => {
                    if (r.kind !== 'job') return null

                    const j = r.job
                    const phase = String((j as any).phase ?? '').trim().toLowerCase()
                    const isStopRequested = Boolean(stopRequestedIds[j.id])

                    const postworkState = getEffectivePostworkState(j)
                    const isQueuedPostwork = postworkState === 'queued'

                    const isBusyPhase = phase !== '' && phase !== 'recording'
                    const isStopping = isBusyPhase || j.status !== 'running' || isStopRequested

                    const isRemovingQueuedPostwork = Boolean(removeQueuedPostworkRequestedIds[j.id])

                    const label = isQueuedPostwork
                      ? (isRemovingQueuedPostwork ? 'Entferne…' : 'Entfernen')
                      : (isStopping ? 'Stoppe…' : 'Stoppen')

                    const color = isQueuedPostwork ? 'red' : 'indigo'
                    const disabled = isQueuedPostwork ? isRemovingQueuedPostwork : isStopping

                    return (
                      <SwipeActionRow
                        key={`pw:${j.id}`}
                        actionLabel={label}
                        actionColor={color}
                        disabled={disabled}
                        onAction={async () => {
                          if (isQueuedPostwork) {
                            if (isRemovingQueuedPostwork) return
                            markRemoveQueuedPostworkRequested(j.id)
                            try {
                              await onRemoveQueuedPostworkJob?.(j.id)
                            } catch {
                              clearRemoveQueuedPostworkRequested(j.id)
                            }
                            return
                          }

                          if (isStopping) return
                          markStopRequested(j.id)
                          await onStopJob(j.id)
                        }}
                      >
                        <div
                          className={[
                            'transition-all duration-300',
                            isQueuedPostwork && isRemovingQueuedPostwork && 'pointer-events-none opacity-70 ring-1 ring-red-300 rounded-2xl animate-pulse bg-red-50/60 dark:bg-red-500/10 dark:ring-red-500/30',
                            !isQueuedPostwork && isStopping && 'pointer-events-none opacity-70 ring-1 ring-indigo-300 rounded-2xl animate-pulse bg-indigo-50/60 dark:bg-indigo-500/10 dark:ring-indigo-500/30',
                          ].filter(Boolean).join(' ')}
                        >
                          <DownloadsCardRow
                            r={r}
                            nowMs={nowMs}
                            blurPreviews={blurPreviews}
                            modelsByKey={modelsByKey}
                            roomStatusByModelKey={roomStatusByModelKey}
                            postworkInfoOf={postworkInfoOf}
                            stopRequestedIds={stopRequestedIds}
                            growingByJobId={growingByJobId}
                            markStopRequested={markStopRequested}
                            onOpenPlayer={onOpenPlayer}
                            onStopJob={onStopJob}
                            onRemoveQueuedPostworkJob={onRemoveQueuedPostworkJob}
                            onToggleFavorite={onToggleFavorite}
                            onToggleLike={onToggleLike}
                            onToggleWatch={onToggleWatch}
                          />
                        </div>
                      </SwipeActionRow>
                    )
                  })}
                </>
              ) : null}
            </div>
          ) : (
            <div className="mt-3 space-y-4">
              {downloadJobRows.length > 0 ? (
                <div className="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">
                  <div className="mb-2 flex flex-wrap items-center gap-2 rounded-xl border border-gray-200 bg-white px-3 py-2 text-sm font-semibold text-gray-900 shadow-sm dark:border-white/10 dark:bg-white/5 dark:text-white">
                    <span>Downloads ({downloadJobRows.length})</span>
                    {watchedPausedByDisk ? <DiskEmergencyBadge /> : null}
                  </div>
                  <Table
                    rows={downloadJobRows}
                    rowClassName={rowClassName}
                    columns={columns}
                    getRowKey={(r) => (r.kind === 'job' ? `dl:job:${r.job.id}` : `dl:pending:${pendingRowKey(r.pending)}`)}
                    striped
                    fullWidth
                    stickyHeader
                    compact={false}
                    card
                    onRowClick={(r) => {
                      if (r.kind === 'job') onOpenPlayer(r.job)
                    }}
                  />
                </div>
              ) : null}

              {postworkRows.length > 0 ? (
                <div className="overflow-x-auto rounded-2xl border border-amber-200/80 bg-white/80 p-2 shadow-sm dark:border-white/10 dark:bg-transparent dark:p-0">
                  <div className="mb-2 rounded-xl border border-amber-200 bg-amber-50 px-3 py-2 text-sm font-semibold text-amber-900 shadow-sm dark:border-white/10 dark:bg-white/5 dark:text-white">
                    Nacharbeiten ({postworkRows.length})
                  </div>
                  <Table
                    rows={postworkRows}
                    columns={columns}
                    getRowKey={(r) => (r.kind === 'job' ? `pw:job:${r.job.id}` : `pw:pending:${pendingRowKey(r.pending)}`)}
                    striped
                    fullWidth
                    stickyHeader
                    compact={false}
                    card
                    onRowClick={(r) => {
                      if (r.kind === 'job') onOpenPlayer(r.job)
                    }}
                  />
                </div>
              ) : null}

              {pendingRows.length > 0 ? (
                <div className="overflow-x-auto rounded-2xl border border-slate-200/80 bg-white/80 p-2 shadow-sm dark:border-white/10 dark:bg-transparent dark:p-0">
                  <div className="mb-2 rounded-xl border border-slate-200 bg-slate-50 px-3 py-2 text-sm font-semibold text-slate-800 shadow-sm dark:border-white/10 dark:bg-white/5 dark:text-white">
                    Wartend ({pendingRows.length})
                  </div>
                  <Table
                    rows={pendingRows}
                    columns={columns}
                    getRowKey={(r) => (r.kind === 'job' ? `wa:job:${r.job.id}` : `wa:pending:${pendingRowKey(r.pending)}`)}
                    striped
                    fullWidth
                    stickyHeader
                    compact={false}
                    card
                  />
                </div>
              ) : null}
            </div>
          )}
        </>
      ) : (
        <Card grayBody>
          <div className="flex items-center gap-3">
            <div className="grid h-10 w-10 place-items-center rounded-xl bg-white/70 ring-1 ring-gray-200 dark:bg-white/5 dark:ring-white/10">
              <span className="text-lg">⏸️</span>
            </div>
            <div className="min-w-0">
              <div className="text-sm font-medium text-gray-900 dark:text-white">
                Keine laufenden Downloads
              </div>
              <div className="text-xs text-gray-600 dark:text-gray-300">
                Starte oben eine URL – hier siehst du Live-Status und kannst Jobs stoppen.
              </div>
            </div>
          </div>
        </Card>
      )}
    </div>
  )
}