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

import * as React from 'react'
import Card from './Card'
import type { RecordJob } from '../../types'
import FinishedVideoPreview from './FinishedVideoPreview'
import SwipeCard, { type SwipeCardHandle } from './SwipeCard'
import {
  StarIcon as StarSolidIcon,
  HeartIcon as HeartSolidIcon,
  EyeIcon as EyeSolidIcon,
} from '@heroicons/react/24/solid'
import RecordJobActions from './RecordJobActions'
import TagOverflowRow from './TagOverflowRow'
import { isHotName, stripHotPrefix } from './hotName'
import { formatResolution } from './formatters'

type InlinePlayState = { key: string; nonce: number } | null

type Props = {
  rows: RecordJob[]
  isLoading?: boolean
  isSmall: boolean
  teaserPlayback: 'still' | 'hover' | 'all'
  teaserAudio?: boolean
  hoverTeaserKey?: string | null

  blurPreviews?: boolean
  durations: Record<string, number>
  teaserKey: string | null
  inlinePlay: InlinePlayState
  setInlinePlay: React.Dispatch<React.SetStateAction<InlinePlayState>>

  deletingKeys: Set<string>
  keepingKeys: Set<string>
  removingKeys: Set<string>

  swipeRefs: React.MutableRefObject<Map<string, SwipeCardHandle>>

  assetNonce?: number

  // helpers
  keyFor: (j: RecordJob) => string
  baseName: (p: string) => string
  modelNameFromOutput: (output?: string) => string
  runtimeOf: (job: RecordJob) => string
  sizeBytesOf: (job: RecordJob) => number | null
  formatBytes: (bytes?: number | null) => string
  lower: (s: string) => string

  // callbacks/actions
  onHoverPreviewKeyChange?: (key: string | null) => void
  onOpenPlayer: (job: RecordJob) => void
  openPlayer: (job: RecordJob) => void
  startInline: (key: string) => void
  tryAutoplayInline: (domId: string) => boolean
  registerTeaserHost: (key: string) => (el: HTMLDivElement | null) => void

  handleDuration: (job: RecordJob, seconds: number) => void

  deleteVideo: (job: RecordJob) => Promise<boolean>
  keepVideo: (job: RecordJob) => Promise<boolean>

  releasePlayingFile: (file: string, opts?: { close?: boolean }) => Promise<void>

  modelsByKey: Record<string, { favorite?: boolean; liked?: boolean | null; watching?: boolean | null; tags?: string }>
  activeTagSet: Set<string>
  onToggleTagFilter: (tag: string) => void

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

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

  const seen = new Set<string>()
  const out: string[] = []
  for (const p of parts) {
    const k = p.toLowerCase()
    if (seen.has(k)) continue
    seen.add(k)
    out.push(p)
  }
  return out
}

export default function FinishedDownloadsCardsView({
  rows,
  isSmall,
  isLoading,
  teaserPlayback,
  teaserAudio,
  hoverTeaserKey,
  blurPreviews,
  teaserKey,
  inlinePlay,
  setInlinePlay,

  deletingKeys,
  keepingKeys,
  removingKeys,

  swipeRefs,
  assetNonce,

  keyFor,
  baseName,
  modelNameFromOutput,
  runtimeOf,
  sizeBytesOf,
  formatBytes,
  lower,

  onHoverPreviewKeyChange,
  onOpenPlayer,
  openPlayer,
  startInline,
  tryAutoplayInline,
  registerTeaserHost,

  deleteVideo,
  keepVideo,

  modelsByKey,
  activeTagSet,
  onToggleTagFilter,

  onToggleHot,
  onToggleFavorite,
  onToggleLike,
  onToggleWatch,
}: Props) {
  // ✅ Auflösung als {w,h} aus meta.json bevorzugen
  const resolutionObjOf = React.useCallback((j: RecordJob): { w: number; h: number } | null => {
    const w =
      (typeof j.meta?.videoWidth === 'number' && Number.isFinite(j.meta.videoWidth) ? j.meta.videoWidth : 0) ||
      (typeof j.videoWidth === 'number' && Number.isFinite(j.videoWidth) ? j.videoWidth : 0)
    const h =
      (typeof j.meta?.videoHeight === 'number' && Number.isFinite(j.meta.videoHeight) ? j.meta.videoHeight : 0) ||
      (typeof j.videoHeight === 'number' && Number.isFinite(j.videoHeight) ? j.videoHeight : 0)

    if (w > 0 && h > 0) return { w, h }
    return null
  }, [])

  const metaChipCls = 'rounded bg-black/40 px-1.5 py-0.5 text-xs font-medium backdrop-blur-[2px]'

  return (
    <div className="relative">
      <div className="grid gap-3 sm:grid-cols-2 lg:grid-cols-3">
        {rows.map((j) => {
          const k = keyFor(j)
          const inlineActive = inlinePlay?.key === k
          const allowSound = Boolean(teaserAudio) && (inlineActive || hoverTeaserKey === k)
          const previewMuted = !allowSound
          const inlineNonce = inlineActive ? inlinePlay?.nonce ?? 0 : 0

          const busy = deletingKeys.has(k) || keepingKeys.has(k) || removingKeys.has(k)

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

          const flags = modelsByKey[lower(model)]
          const isFav = Boolean(flags?.favorite)
          const isLiked = flags?.liked === true
          const isWatching = Boolean(flags?.watching)

          const tags = parseTags(flags?.tags)

          const dur = runtimeOf(j)
          const size = formatBytes(sizeBytesOf(j))

          const resObj = resolutionObjOf(j)
          const resLabel = formatResolution(resObj)

          const inlineDomId = `inline-prev-${encodeURIComponent(k)}`

          // ✅ Shell an Gallery angelehnt
          const shellCls = [
            'group relative rounded-lg overflow-hidden outline-1 outline-black/5 dark:-outline-offset-1 dark:outline-white/10',
            'bg-white dark:bg-gray-900/40',
            'transition-all duration-200',
            !isSmall && 'hover:-translate-y-0.5 hover:shadow-md dark:hover:shadow-none',
            'focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-indigo-600 dark:focus-visible:outline-indigo-500',
            busy && 'pointer-events-none opacity-70',
            deletingKeys.has(k) && 'ring-1 ring-red-300 bg-red-50/60 dark:bg-red-500/10 dark:ring-red-500/30',
            keepingKeys.has(k) && 'ring-1 ring-emerald-300 bg-emerald-50/60 dark:bg-emerald-500/10 dark:ring-emerald-500/30',
            removingKeys.has(k) && 'opacity-0 translate-y-2 scale-[0.98]',
          ]
            .filter(Boolean)
            .join(' ')

          const cardInner = (
            <div
              role="button"
              tabIndex={0}
              className={shellCls}
              onClick={isSmall ? undefined : () => openPlayer(j)}
              onKeyDown={(e) => {
                if (e.key === 'Enter' || e.key === ' ') onOpenPlayer(j)
              }}
            >
              {/* Card shell keeps backgrounds consistent */}
              <Card noBodyPadding className="overflow-hidden bg-transparent">
                {/* Preview */}
                <div
                  id={inlineDomId}
                  ref={registerTeaserHost(k)}
                  className="relative aspect-video rounded-t-lg bg-black/5 dark:bg-white/5"
                  onMouseEnter={isSmall ? undefined : () => onHoverPreviewKeyChange?.(k)}
                  onMouseLeave={isSmall ? undefined : () => onHoverPreviewKeyChange?.(null)}
                  onClick={(e) => {
                    e.preventDefault()
                    e.stopPropagation()
                    if (isSmall) return
                    startInline(k)
                  }}
                >
                  {/* media */}
                  <div className="absolute inset-0">
                    <FinishedVideoPreview
                      job={j}
                      getFileName={baseName}
                      className="h-full w-full"
                      showPopover={false}
                      blur={isSmall ? false : inlineActive ? false : blurPreviews}
                      animated={teaserPlayback === 'all' ? true : teaserPlayback === 'hover' ? teaserKey === k : false}
                      animatedMode="teaser"
                      animatedTrigger="always"
                      inlineVideo={inlineActive ? 'always' : false}
                      inlineNonce={inlineNonce}
                      inlineControls={inlineActive}
                      inlineLoop={false}
                      muted={previewMuted}
                      popoverMuted={previewMuted}
                      assetNonce={assetNonce ?? 0}
                    />
                  </div>

                  {/* Actions top-right (wie Gallery: je nach Größe ausblenden) */}
                  <div className="absolute inset-x-2 top-2 z-10 flex justify-end" onClick={(e) => e.stopPropagation()}>
                    <RecordJobActions
                      job={j}
                      variant="overlay"
                      busy={busy}
                      collapseToMenu
                      isHot={isHot}
                      isFavorite={isFav}
                      isLiked={isLiked}
                      isWatching={isWatching}
                      onToggleWatch={onToggleWatch}
                      onToggleFavorite={onToggleFavorite}
                      onToggleLike={onToggleLike}
                      onToggleHot={onToggleHot}
                      onKeep={keepVideo}
                      onDelete={deleteVideo}
                      order={['watch', 'favorite', 'like', 'hot', 'keep', 'delete', 'details', 'add']}
                      className="w-full justify-end gap-1"
                    />
                  </div>

                  {/* Restart (wenn inline läuft) */}
                  {!isSmall && inlinePlay?.key === k ? (
                    <button
                      type="button"
                      className="absolute left-2 top-10 z-10 rounded-md bg-black/45 px-2 py-1 text-xs font-semibold text-white hover:bg-black/60"
                      onClick={(e) => {
                        e.preventDefault()
                        e.stopPropagation()
                        setInlinePlay((prev) => ({ key: k, nonce: prev?.key === k ? prev.nonce + 1 : 1 }))
                      }}
                      title="Von vorne starten"
                      aria-label="Von vorne starten"
                    >
                      ↻
                    </button>
                  ) : null}

                  {/* Bottom overlay (ohne Gradient) */}
                  <div
                    className="
                      pointer-events-none absolute inset-x-0 bottom-0
                      px-2 pb-2 pt-8 text-white
                    "
                  >
                    <div className="flex items-center justify-end gap-2">
                      <div className="shrink-0 flex items-center gap-1.5">
                        <span className={metaChipCls}>{dur}</span>
                        {resLabel ? (
                          <span className={metaChipCls} title={resObj ? `${resObj.w}×${resObj.h}` : 'Auflösung'}>
                            {resLabel}
                          </span>
                        ) : null}
                        <span className={metaChipCls}>{size}</span>
                      </div>
                    </div>
                  </div>
                </div>

                {/* Footer / Meta (wie Gallery strukturiert) */}
                <div className="relative min-h-[92px] overflow-hidden px-4 py-3 border-t border-black/5 dark:border-white/10 bg-white dark:bg-gray-900">
                  <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">{model}</div>

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

                        <span className="min-w-0 truncate text-xs text-gray-500 dark:text-gray-400">
                          {stripHotPrefix(fileRaw) || '—'}
                        </span>
                      </div>
                    </div>

                    <div className="shrink-0 flex items-center gap-1.5 pt-0.5">
                      {isWatching ? <EyeSolidIcon className="size-4 text-sky-600 dark:text-sky-300" /> : null}
                      {isLiked ? <HeartSolidIcon className="size-4 text-rose-600 dark:text-rose-300" /> : null}
                      {isFav ? <StarSolidIcon className="size-4 text-amber-600 dark:text-amber-300" /> : null}
                    </div>
                  </div>

                  {/* Tags */}
                  <div className="mt-2" onClick={(e) => e.stopPropagation()} onMouseDown={(e) => e.stopPropagation()}>
                    <TagOverflowRow
                      rowKey={k}
                      tags={tags}
                      activeTagSet={activeTagSet}
                      lower={lower}
                      onToggleTagFilter={onToggleTagFilter}
                    />
                  </div>
                </div>
              </Card>
            </div>
          )

          return isSmall ? (
            <SwipeCard
              ref={(h) => {
                if (h) swipeRefs.current.set(k, h)
                else swipeRefs.current.delete(k)
              }}
              key={k}
              enabled
              disabled={busy}
              ignoreFromBottomPx={110}
              doubleTapMs={360}
              doubleTapMaxMovePx={48}
              onDoubleTap={async () => {
                if (isHot) return
                await onToggleHot?.(j)
              }}
              onTap={() => {
                const domId = `inline-prev-${encodeURIComponent(k)}`
                startInline(k)
                requestAnimationFrame(() => {
                  if (!tryAutoplayInline(domId)) requestAnimationFrame(() => tryAutoplayInline(domId))
                })
              }}
              onSwipeLeft={() => deleteVideo(j)}
              onSwipeRight={() => keepVideo(j)}
            >
              {cardInner}
            </SwipeCard>
          ) : (
            <React.Fragment key={k}>{cardInner}</React.Fragment>
          )
        })}
      </div>

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