// frontend\src\components\ui\Downloads.tsx
'use client'

import { useMemo, useState, useCallback, useEffect } from 'react'
import Table, { type Column } from './Table'
import Card from './Card'
import Button from './Button'
import ModelPreview from './ModelPreview'
import type { RecordJob } from '../../types'
import ProgressBar from './ProgressBar'
import RecordJobActions from './RecordJobActions'
import { PauseIcon, PlayIcon } from '@heroicons/react/24/solid'
import { subscribeSSE } from '../../lib/sseSingleton'

type PendingWatchedRoom = WaitingModelRow & {
  currentShow: string // public / private / hidden / away / unknown
}

type WaitingModelRow = {
  id: string
  modelKey: string
  url: string
  imageUrl?: string
  currentShow?: string // public / private / hidden / away / unknown
}

type AutostartState = { paused?: boolean }

type Props = {
  jobs: RecordJob[]
  pending?: PendingWatchedRoom[]
  modelsByKey?: Record<string, { favorite?: boolean; liked?: boolean | null; watching?: boolean }>
  onOpenPlayer: (job: RecordJob) => void
  onStopJob: (id: string) => void
  blurPreviews?: boolean
  onToggleFavorite?: (job: RecordJob) => void | Promise<void>
  onToggleLike?: (job: RecordJob) => void | Promise<void>
  onToggleWatch?: (job: RecordJob) => void | Promise<void>
}

type DownloadRow =
  | { kind: 'job'; job: RecordJob }
  | { kind: 'pending'; pending: PendingWatchedRoom }

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 toMs = (v: unknown): number => {
  if (typeof v === 'number' && Number.isFinite(v)) {
    // Heuristik: 10-stellige Unix-Sekunden -> ms
    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) || // Fallback
      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) {
    case 'stopping':
      return 'Stop wird angefordert…'
    case 'remuxing':
      return 'Remux zu MP4…'
    case 'moving':
      return 'Verschiebe nach Done…'
    case 'assets':
      return 'Erstelle Vorschau…'
    default:
      return ''
  }
}

async function apiJSON<T>(url: string, init?: RequestInit): Promise<T> {
  const res = await fetch(url, init)
  if (!res.ok) {
    const text = await res.text().catch(() => '')
    throw new Error(text || `HTTP ${res.status}`)
  }
  return res.json() as Promise<T>
}

function StatusCell({ job }: { job: RecordJob }) {
  const phaseRaw = String((job as any)?.phase ?? '').trim()
  const progress = Number((job as any)?.progress ?? 0)

  const phaseText = phaseRaw ? (phaseLabel(phaseRaw) || phaseRaw) : ''
  const text = phaseText || String((job as any)?.status ?? '').trim().toLowerCase()

  // ✅ ProgressBar unabhängig vom Text
  // ✅ determinate nur wenn sinnvoll (0..100)
  const showBar = Number.isFinite(progress) && progress > 0 && progress < 100

  // ✅ wenn wir in einer Phase sind, aber noch kein Progress da ist -> indeterminate
  const showIndeterminate =
    !showBar &&
    Boolean(phaseRaw) &&
    (!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>
  )
}

function DownloadsCardRow({
  r,
  nowMs,
  blurPreviews,
  modelsByKey,
  stopRequestedIds,
  markStopRequested,
  onOpenPlayer,
  onStopJob,
  onToggleFavorite,
  onToggleLike,
  onToggleWatch,
}: {
  r: DownloadRow
  nowMs: number
  blurPreviews?: boolean
  modelsByKey: Record<string, { favorite?: boolean; liked?: boolean | null; watching?: boolean }>
  stopRequestedIds: Record<string, true>
  markStopRequested: (ids: string | string[]) => void
  onOpenPlayer: (job: RecordJob) => void
  onStopJob: (id: string) => void
  onToggleFavorite?: (job: RecordJob) => void | Promise<void>
  onToggleLike?: (job: RecordJob) => void | Promise<void>
  onToggleWatch?: (job: RecordJob) => void | Promise<void>
}) {
  // ---------- Pending ----------
  if (r.kind === 'pending') {
    const p = r.pending
    const name = pendingModelName(p)
    const url = pendingUrl(p)
    const show = (p.currentShow || 'unknown').toLowerCase()

    return (
      <div
        className="
          relative overflow-hidden rounded-2xl border border-white/40 bg-white/35 shadow-sm
          backdrop-blur-xl supports-[backdrop-filter]:bg-white/25
          ring-1 ring-black/5
          transition-all hover:-translate-y-0.5 hover:shadow-md active:translate-y-0
          dark:border-white/10 dark:bg-gray-950/35 dark:supports-[backdrop-filter]:bg-gray-950/25
        "
      >
        {/* subtle gradient */}
        <div className="pointer-events-none absolute inset-0 bg-gradient-to-br from-white/70 via-white/20 to-white/60 dark:from-white/10 dark:via-transparent dark:to-white/5" />

        <div className="relative p-3">
          <div className="flex items-start justify-between gap-2">
            <div className="min-w-0">
              <div className="truncate text-sm font-semibold text-gray-900 dark:text-white" title={name}>
                {name}
              </div>

              <div className="mt-1 flex flex-wrap items-center gap-2 text-xs text-gray-600 dark:text-gray-300">
                <span className="inline-flex items-center gap-1 rounded-full bg-gray-900/5 px-2 py-0.5 font-medium dark:bg-white/10">
                  Wartend
                </span>
                <span className="inline-flex items-center rounded-full bg-gray-900/5 px-2 py-0.5 text-[11px] font-medium uppercase tracking-wide text-gray-700 dark:bg-white/10 dark:text-gray-200">
                  {show}
                </span>
              </div>
            </div>

            <span className="shrink-0 rounded-full bg-gray-900/5 px-2.5 py-1 text-xs font-medium text-gray-800 dark:bg-white/10 dark:text-gray-200">
              ⏳
            </span>
          </div>

          <div className="mt-3">
            {(() => {
              const img = pendingImageUrl(p)
              return (
                <div className="relative aspect-[16/9] w-full overflow-hidden rounded-xl bg-gray-100 ring-1 ring-black/5 dark:bg-white/10 dark:ring-white/10">
                  {img ? (
                    <img
                      src={img}
                      alt={name}
                      className={[
                        "h-full w-full object-cover",
                        blurPreviews ? "blur-md" : "",
                      ].join(" ")}
                      loading="lazy"
                      referrerPolicy="no-referrer"
                      onError={(e) => {
                        ;(e.currentTarget as HTMLImageElement).style.display = "none"
                      }}
                    />
                  ) : (
                    <div className="grid h-full w-full place-items-center">
                      <div className="text-sm text-gray-500 dark:text-gray-300">Waiting…</div>
                    </div>
                  )}
                </div>
              )
            })()}
          </div>

          {url ? (
            <a
              href={url}
              target="_blank"
              rel="noreferrer"
              className="mt-3 block truncate text-xs text-indigo-600 hover:underline dark:text-indigo-400"
              onClick={(e) => e.stopPropagation()}
              title={url}
            >
              {url}
            </a>
          ) : null}
        </div>
      </div>
    )
  }

  // ---------- Job ----------
  const j = r.job
  const name = modelNameFromOutput(j.output)
  const file = baseName(j.output || '')

  const phase = String((j as any).phase ?? '').trim()
  const phaseText = phase ? (phaseLabel(phase) || phase) : ''

  const isStopRequested = Boolean(stopRequestedIds[j.id])          // nur UI-zwischenzustand
  const rawStatus = String(j.status ?? '').toLowerCase()

  const isStopping = Boolean(phase) || rawStatus !== 'running' || isStopRequested

  // ✅ Badge hinter Modelname: IMMER Backend-Status
  const statusText = rawStatus || 'unknown'

  // ✅ Progressbar Label: Phase (gemappt), fallback auf Status
  const progressLabel = phaseText || statusText

  const progress = Number((j as any).progress ?? 0)
  const showBar = Number.isFinite(progress) && progress > 0 && progress < 100
  const showIndeterminate =
    !showBar &&
    Boolean(phase) &&
    (!Number.isFinite(progress) || progress <= 0 || progress >= 100)

  const key = name && name !== '—' ? name.toLowerCase() : ''
  const flags = key ? modelsByKey[key] : undefined
  const isFav = Boolean(flags?.favorite)
  const isLiked = flags?.liked === true
  const isWatching = Boolean(flags?.watching)

  return (
    <div
      className="
        group relative overflow-hidden rounded-2xl border border-white/40 bg-white/35 shadow-sm
        backdrop-blur-xl supports-[backdrop-filter]:bg-white/25
        ring-1 ring-black/5
        transition-all hover:-translate-y-0.5 hover:shadow-md active:translate-y-0
        dark:border-white/10 dark:bg-gray-950/35 dark:supports-[backdrop-filter]:bg-gray-950/25
      "
      onClick={() => onOpenPlayer(j)}
      role="button"
      tabIndex={0}
      onKeyDown={(e) => {
        if (e.key === 'Enter' || e.key === ' ') onOpenPlayer(j)
      }}
    >
      {/* subtle gradient */}
      <div className="pointer-events-none absolute inset-0 bg-gradient-to-br from-white/70 via-white/20 to-white/60 dark:from-white/10 dark:via-transparent dark:to-white/5" />
      <div className="pointer-events-none absolute -inset-10 opacity-0 blur-2xl transition-opacity duration-300 group-hover:opacity-100 bg-white/40 dark:bg-white/10" />

      <div className="relative p-3">
        {/* Header: kleines Preview links + Infos rechts */}
        <div className="flex items-start gap-3">
          {/* Thumbnail */}
          <div
            className="
              relative
              shrink-0 overflow-hidden rounded-lg
              w-[112px] h-[64px]
              bg-gray-100 ring-1 ring-black/5
              dark:bg-white/10 dark:ring-white/10
            "
          >
            <ModelPreview
              jobId={j.id}
              blur={blurPreviews}
              alignStartAt={j.startedAt}
              alignEndAt={j.endedAt ?? null}
              alignEveryMs={10_000}
              fastRetryMs={1000}
              fastRetryMax={25}
              fastRetryWindowMs={60_000}
              className="w-full h-full"
            />
          </div>

          {/* Infos */}
          <div className="min-w-0 flex-1">
            <div className="flex items-start justify-between gap-2">
              <div className="min-w-0">
                <div className="flex items-center gap-2 min-w-0">
                  <div className="truncate text-base font-semibold text-gray-900 dark:text-white" title={name}>
                    {name}
                  </div>

                  <span
                    className={[
                      'shrink-0 inline-flex items-center rounded-full px-2 py-0.5 text-[10px] font-semibold',
                      'bg-gray-900/5 text-gray-800 dark:bg-white/10 dark:text-gray-200',
                      isStopping ? 'ring-1 ring-amber-500/30' : 'ring-1 ring-emerald-500/25',
                    ].join(' ')}
                    title={statusText}
                  >
                    {statusText}
                  </span>
                </div>
                <div className="mt-0.5 truncate text-xs text-gray-600 dark:text-gray-300" title={j.output}>
                  {file || '—'}
                </div>
              </div>

              <div className="shrink-0 flex flex-col items-end gap-1">
                <span className="rounded-full bg-gray-900/5 px-2 py-0.5 text-[11px] font-medium text-gray-900 dark:bg-white/10 dark:text-gray-200">
                  {runtimeOf(j, nowMs)}
                </span>
                <span className="rounded-full bg-gray-900/5 px-2 py-0.5 text-[11px] font-medium text-gray-900 dark:bg-white/10 dark:text-gray-200">
                  {formatBytes(sizeBytesOf(j))}
                </span>
              </div>
            </div>

            {j.sourceUrl ? (
              <a
                href={j.sourceUrl}
                target="_blank"
                rel="noreferrer"
                className="mt-1 block truncate text-xs text-indigo-600 hover:underline dark:text-indigo-400"
                onClick={(e) => e.stopPropagation()}
                title={j.sourceUrl}
              >
                {j.sourceUrl}
              </a>
            ) : null}
          </div>
        </div>

        {(showBar || showIndeterminate) ? (
          <div className="mt-3">
            {showBar ? (
              <ProgressBar
                label={progressLabel}
                value={Math.max(0, Math.min(100, progress))}
                showPercent
                size="sm"
                className="w-full"
              />
            ) : (
              <ProgressBar
                label={progressLabel}
                indeterminate
                size="sm"
                className="w-full"
              />
            )}
          </div>
        ) : null}

        {/* Footer */}
        <div className="mt-3 flex items-center justify-between gap-2 border-t border-white/30 pt-3 dark:border-white/10">
          <div
            className="flex items-center gap-1"
            onClick={(e) => e.stopPropagation()}
            onMouseDown={(e) => e.stopPropagation()}
          >
            <RecordJobActions
              job={j}
              variant="table"
              busy={isStopping}
              isFavorite={isFav}
              isLiked={isLiked}
              isWatching={isWatching}
              showHot={false}
              showKeep={false}
              showDelete={false}
              showFavorite
              showLike
              onToggleFavorite={onToggleFavorite}
              onToggleLike={onToggleLike}
              onToggleWatch={onToggleWatch}
              order={['watch', 'favorite', 'like', 'details']}
              className="flex items-center gap-1"
            />
          </div>

          <Button
            size="sm"
            variant="primary"
            disabled={isStopping}
            className="shrink-0"
            onClick={(e) => {
              e.stopPropagation()
              if (isStopping) return
              markStopRequested(j.id)
              onStopJob(j.id)
            }}
          >
            {isStopping ? 'Stoppe…' : 'Stop'}
          </Button>
        </div>
      </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(/\.[^.]+$/, '')

  // <model>_MM_DD_YYYY__HH-MM-SS
  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 start = Date.parse(String(j.startedAt || ''))
  if (!Number.isFinite(start)) return '—'
  const end = j.endedAt ? Date.parse(String(j.endedAt)) : nowMs
  if (!Number.isFinite(end)) 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]}`
}


export default function Downloads({ jobs, pending = [], onOpenPlayer, onStopJob, onToggleFavorite, onToggleLike, onToggleWatch, modelsByKey = {}, blurPreviews }: Props) {

  const [stopAllBusy, setStopAllBusy] = useState(false)

  const [watchedPaused, setWatchedPaused] = useState(false)
  const [watchedBusy, setWatchedBusy] = useState(false)

  const refreshWatchedState = useCallback(async () => {
    try {
      const s = await apiJSON<AutostartState>('/api/autostart/state', { cache: 'no-store' as any })
      setWatchedPaused(Boolean(s?.paused))
    } catch {
      // wenn Endpoint (noch) nicht da ist: nichts kaputt machen
    }
  }, [])

  useEffect(() => {
    // initial: einmal fetchen (schneller first paint)
    void refreshWatchedState()

    // danach: Stream (Singleton)
    const unsub = subscribeSSE<AutostartState>(
      '/api/autostart/state/stream',
      'autostart',
      (data) => setWatchedPaused(Boolean((data as any)?.paused))
    )

    return () => {
      unsub()
    }
  }, [refreshWatchedState])

  const pauseWatched = useCallback(async () => {
    if (watchedBusy || watchedPaused) return
    setWatchedBusy(true)
    try {
      await fetch('/api/autostart/pause', { method: 'POST' })
      setWatchedPaused(true)
    } catch {
      // ignore
    } finally {
      setWatchedBusy(false)
    }
  }, [watchedBusy, watchedPaused])

  const resumeWatched = useCallback(async () => {
    if (watchedBusy || !watchedPaused) return
    setWatchedBusy(true)
    try {
      await fetch('/api/autostart/resume', { method: 'POST' })
      setWatchedPaused(false)
    } catch {
      // ignore
    } finally {
      setWatchedBusy(false)
    }
  }, [watchedBusy, watchedPaused])

  // ✅ Merkt sich: für diese Jobs wurde "Stop" bereits angefordert (z.B. via "Alle stoppen")
  const [stopRequestedIds, setStopRequestedIds] = useState<Record<string, true>>({})

  // ✅ Merkt sich dauerhaft: Stop wurde vom User ausgelöst (damit wir später "Stopped" anzeigen können)
  const [stopInitiatedIds, setStopInitiatedIds] = useState<Record<string, true>>({})

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

    // UI-Zwischenzustand ("Stoppe…")
    setStopRequestedIds((prev) => {
      const next = { ...prev }
      for (const id of arr) {
        if (id) next[id] = true
      }
      return next
    })

    // dauerhaft merken: User hat Stop ausgelöst
    setStopInitiatedIds((prev) => {
      const next = { ...prev }
      for (const id of arr) {
        if (id) next[id] = true
      }
      return next
    })
  }, [])

  // Cleanup: sobald Job nicht mehr "running ohne phase" ist, brauchen wir das Flag nicht mehr
  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 phase = String((j as any).phase ?? '').trim()
        const isStopping = Boolean(phase) || j.status !== 'running'
        if (!isStopping) next[id] = true
      }
      return next
    })
  }, [jobs])


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

  const hasActive = useMemo(() => {
    // tickt solange mind. ein Job noch nicht beendet ist
    return jobs.some((j) => !j.endedAt && j.status === 'running')
  }, [jobs])

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

  const stoppableIds = useMemo(() => {
    return jobs
      .filter((j) => {
        const phase = String((j as any).phase ?? '').trim()
        const isStopRequested = Boolean(stopRequestedIds[j.id])
        const isStopping = Boolean(phase) || 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
            return (
              <div className="grid w-[96px] h-[60px] overflow-hidden rounded-md">
                <ModelPreview
                  jobId={j.id}
                  blur={blurPreviews}
                  alignStartAt={j.startedAt}
                  alignEndAt={j.endedAt ?? null}
                  alignEveryMs={10_000}
                  fastRetryMs={1000}
                  fastRetryMax={25}
                  fastRetryWindowMs={60_000}
                  className="w-full h-full"
                />
              </div>
            )
          }

          // pending: kein jobId → Platzhalter
          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">
                <img
                  src={img}
                  alt={name}
                  className={[
                    "h-full w-full object-cover",
                    blurPreviews ? "blur-md" : "",
                  ].join(" ")}
                  loading="lazy"
                  referrerPolicy="no-referrer"
                  onError={(e) => {
                    ;(e.currentTarget as HTMLImageElement).style.display = "none"
                  }}
                />
              </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 phase = String((j as any).phase ?? '').trim()

            const isStopRequested = Boolean(stopRequestedIds[j.id])
            const stopInitiated = Boolean(stopInitiatedIds[j.id])
            const rawStatus = String(j.status ?? '').toLowerCase()
            const isStoppedFinal = rawStatus === 'stopped' || (stopInitiated && Boolean(j.endedAt))

            const isStopping = Boolean(phase) || rawStatus !== 'running' || isStopRequested
            const statusText = rawStatus || 'unknown'

            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>
                  <span
                    className={[
                      'shrink-0 inline-flex items-center rounded-full px-2 py-0.5 text-[10px] font-semibold ring-1',
                      // Status-Farben
                      isStopping
                        ? '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'
                        : j.status === 'finished'
                          ? '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'
                          : j.status === 'failed'
                            ? 'bg-red-500/15 text-red-900 ring-red-500/30 dark:bg-red-400/10 dark:text-red-200 dark:ring-red-400/25'
                            : isStoppedFinal
                              ? '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'
                              : 'bg-gray-900/5 text-gray-800 ring-gray-900/10 dark:bg-white/10 dark:text-gray-200 dark:ring-white/10',
                    ].join(' ')}
                    title={statusText}
                  >
                    {statusText}
                  </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)

          return (
            <>
              <span className="block max-w-[170px] truncate font-medium" title={name}>
                {name}
              </span>
              {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} />
          }

          const p = r.pending
          const show = (p.currentShow || 'unknown').toLowerCase()

          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) => {
          // actions nur für echte Jobs (Stop/Icons). Pending ist “read-only” in der Tabelle.
          if (r.kind !== 'job') {
            return <div className="pr-2 text-right text-sm text-gray-500 dark:text-gray-400">—</div>
          }

          const j = r.job
          const phase = String((j as any).phase ?? '').trim()
          const isStopRequested = Boolean(stopRequestedIds[j.id])
          const isStopping = Boolean(phase) || j.status !== 'running' || isStopRequested

          const key = modelNameFromOutput(j.output || '')
          const flags = key && key !== '—' ? modelsByKey[key.toLowerCase()] : 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">
              <RecordJobActions
                job={j}
                variant="table"
                busy={isStopping}
                isFavorite={isFav}
                isLiked={isLiked}
                isWatching={isWatching}
                showHot={false}
                showKeep={false}
                showDelete={false}
                showFavorite
                showLike
                onToggleFavorite={onToggleFavorite}
                onToggleLike={onToggleLike}
                onToggleWatch={onToggleWatch}
                order={['watch', 'favorite', 'like', 'details']}
                className="flex items-center gap-1"
              />

              {(() => {
                const phase = String((j as any).phase ?? '').trim()
                const isStopRequested = Boolean(stopRequestedIds[j.id])
                const isStopping = Boolean(phase) || j.status !== 'running' || isStopRequested

                return (
                  <Button
                    size="sm"
                    variant="primary"
                    disabled={isStopping}
                    className="shrink-0"
                    onClick={(e) => {
                      e.stopPropagation()
                      if (isStopping) return
                      markStopRequested(j.id)
                      onStopJob(j.id)
                    }}
                  >
                    {isStopping ? 'Stoppe…' : 'Stop'}
                  </Button>
                )
              })()}
            </div>
          )
        },
      },
    ]
  }, [blurPreviews, markStopRequested, modelsByKey, nowMs, onStopJob, onToggleFavorite, onToggleLike, onToggleWatch, stopRequestedIds, stopInitiatedIds])

  const hasAnyPending = pending.length > 0
  const hasJobs = jobs.length > 0

  const rows = useMemo<DownloadRow[]>(() => {
    const list: DownloadRow[] = [
      ...jobs.map((job) => ({ kind: 'job', job }) as const),
      ...pending.map((p) => ({ kind: 'pending', pending: p }) as const),
    ]

    // ✅ Neueste zuerst (Hinzugefügt am DESC)
    list.sort((a, b) => addedAtMsOf(b) - addedAtMsOf(a))
    return list
  }, [jobs, pending])

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

    setStopAllBusy(true)
    try {
      // UI sofort auf "Stoppe…" stellen
      markStopRequested(stoppableIds)

      // Stop anfordern (parallel)
      await Promise.allSettled(stoppableIds.map((id) => Promise.resolve(onStopJob(id))))
    } finally {
      setStopAllBusy(false)
    }
  }, [stopAllBusy, stoppableIds, markStopRequested, onStopJob])

  return (
    <div className="grid gap-3">
      {(hasAnyPending || hasJobs) ? (
        <>
          {/* Toolbar (sticky) wie FinishedDownloads */}
          <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="flex items-center justify-between gap-2 p-3">
                {/* Title + Count */}
                <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">
                    {rows.length}
                  </span>
                </div>

                {/* Actions + View */}
                <div className="shrink-0 flex items-center gap-2">
                  <Button
                    size="sm"
                    variant={watchedPaused ? 'secondary' : 'primary'}
                    disabled={watchedBusy}
                    onClick={(e) => {
                      e.preventDefault()
                      e.stopPropagation()
                      void (watchedPaused ? resumeWatched() : pauseWatched())
                    }}
                    className="hidden sm:inline-flex"
                    title={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="hidden sm:inline-flex"
                    title={stoppableIds.length === 0 ? 'Nichts zu stoppen' : 'Alle laufenden stoppen'}
                  >
                    {stopAllBusy ? 'Stoppe alle…' : `Alle stoppen (${stoppableIds.length})`}
                  </Button>
                </div>
              </div>
            </div>
          </div>

          {/* Content */}
          {/* Mobile: Cards */}
          <div className="mt-3 grid gap-4 sm:hidden">
            {rows.map((r) => (
              <DownloadsCardRow
                key={r.kind === 'job' ? `job:${r.job.id}` : `pending:${pendingRowKey(r.pending)}`}
                r={r}
                nowMs={nowMs}
                blurPreviews={blurPreviews}
                modelsByKey={modelsByKey}
                stopRequestedIds={stopRequestedIds}
                markStopRequested={markStopRequested}
                onOpenPlayer={onOpenPlayer}
                onStopJob={onStopJob}
                onToggleFavorite={onToggleFavorite}
                onToggleLike={onToggleLike}
                onToggleWatch={onToggleWatch}
              />
            ))}
          </div>

          {/* Tablet/Desktop: Tabelle */}
          <div className="mt-3 hidden sm:block overflow-x-auto">
            <Table
              rows={rows}
              columns={columns}
              getRowKey={(r) => (r.kind === 'job' ? `job:${r.job.id}` : `pending:${pendingRowKey(r.pending)}`)}
              striped
              fullWidth
              stickyHeader
              compact={false}
              card
              onRowClick={(r) => {
                if (r.kind === 'job') onOpenPlayer(r.job)
              }}
            />
          </div>
        </>
      ) : null}

      {!hasAnyPending && !hasJobs ? (
        <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>
      ) : null}
    </div>
  )
}
