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

import { useEffect, useRef, useMemo, useCallback, memo, type ReactNode } from 'react'
import type {
  RecordJob,
  GalleryModelFlags,
  FinishedPostworkSummary,
} from '../../types'
import type { FinishedDownloadsTeaserState } from './teaserPlayback'
import {
  shouldObserveTeasers,
} from './teaserPlayback'
import FinishedDownloadsGalleryCard from './FinishedDownloadsGalleryCard'

type Props = {
  rows: RecordJob[]
  selectedKeys: Set<string>
  selectionModeActive: boolean
  onToggleSelected: (job: RecordJob) => void
  onSelectOnly: (job: RecordJob) => void
  isLoading?: boolean
  blurPreviews?: boolean
  durations: Record<string, number>
  postworkByFile: Record<string, FinishedPostworkSummary>
  getPostworkSummaryForOutput: (
    output: string | undefined,
    summaries: Record<string, FinishedPostworkSummary>
  ) => FinishedPostworkSummary | undefined
  teaserState: FinishedDownloadsTeaserState
  renderPostworkBadge?: (badge?: FinishedPostworkSummary) => ReactNode
  cardScale?: number

  handleDuration: (job: RecordJob, seconds: number) => void
  handleResolution: (job: RecordJob, w: number, h: number) => void
  resolutionLabelOf: (job: RecordJob) => string

  handleScrubberClickIndex: (job: RecordJob, segmentIndex: number, segmentCount: number) => void
  jobForDetails: (job: RecordJob) => RecordJob

  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

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

  registerTeaserHost: (key: string) => (el: HTMLDivElement | null) => void

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

  lower: (s: string) => string

  modelsByKey: Record<string, GalleryModelFlags>
  activeTagSet: Set<string>
  onHoverPreviewKeyChange?: (key: string | null) => void
  onToggleTagFilter: (tag: string) => void
  onToggleFavorite?: (job: RecordJob) => void | Promise<void>
  onToggleLike?: (job: RecordJob) => void | Promise<void>
  onToggleWatch?: (job: RecordJob) => void | Promise<void>
  onToggleHot: (job: RecordJob) => void | Promise<void>
  onSplit?: (job: RecordJob) => void | Promise<void>
  onAddToDownloads?: (job: RecordJob) => void | Promise<boolean>
  onRecommendedPageSizeChange?: (size: number) => void

  forcePreviewMuted?: boolean
  mobileTeaserAudioUnlocked?: boolean
  onUnlockMobileTeaserAudio?: () => void
  assetNonce?: number
  renderIntegrityBadge?: (job: RecordJob) => ReactNode
  renderRatingOverlay?: (job: RecordJob) => ReactNode
}

function snapTo(value: number, step: number, base = 0) {
  if (!Number.isFinite(value)) return base
  if (!Number.isFinite(step) || step <= 0) return value
  const snapped = Math.round((value - base) / step) * step + base
  return Number(snapped.toFixed(4))
}

function clamp(value: number, min: number, max: number) {
  return Math.max(min, Math.min(max, value))
}

function FinishedDownloadsGalleryView({
  rows,
  isLoading,
  blurPreviews,
  durations,
  postworkByFile,
  getPostworkSummaryForOutput,
  teaserState,
  renderPostworkBadge,
  cardScale,
  selectedKeys,
  selectionModeActive,
  onToggleSelected,
  onSelectOnly,
  handleDuration,
  handleResolution,
  resolutionLabelOf,
  handleScrubberClickIndex,
  jobForDetails,
  keyFor,
  baseName,
  modelNameFromOutput,
  runtimeOf,
  sizeBytesOf,
  formatBytes,
  deletingKeys,
  keepingKeys,
  removingKeys,
  registerTeaserHost,
  onHoverPreviewKeyChange,
  onOpenPlayer,
  deleteVideo,
  keepVideo,
  onToggleHot,
  lower,
  modelsByKey,
  activeTagSet,
  onToggleTagFilter,
  onToggleFavorite,
  onToggleLike,
  onToggleWatch,
  onSplit,
  onAddToDownloads,
  onRecommendedPageSizeChange,
  forcePreviewMuted,
  mobileTeaserAudioUnlocked,
  onUnlockMobileTeaserAudio,
  assetNonce,
  renderIntegrityBadge,
  renderRatingOverlay,
}: Props) {
  const observeTeasers = shouldObserveTeasers(teaserState.mode)

  const registerTeaserHostIfNeeded = useCallback(
    (key: string) => (el: HTMLDivElement | null) => {
      if (!observeTeasers) {
        registerTeaserHost(key)(null)
        return
      }
      registerTeaserHost(key)(el)
    },
    [registerTeaserHost, observeTeasers]
  )

  const effectiveScaleRaw =
    typeof cardScale === 'number'
      ? cardScale
      : 1

  const effectiveScale = clamp(effectiveScaleRaw, 0.8, 1.35)

  const layoutScale = useMemo(
    () => snapTo(effectiveScale, 0.05, 0.8),
    [effectiveScale]
  )

  const sizePreset = useMemo<'sm' | 'md' | 'lg' | 'xl'>(() => {
    if (layoutScale >= 1.14) return 'xl'
    if (layoutScale >= 0.99) return 'lg'
    if (layoutScale >= 0.88) return 'md'
    return 'sm'
  }, [layoutScale])

  const minCardWidth = useMemo(() => {
    return Math.round(270 * layoutScale)
  }, [layoutScale])

  const rootRef = useRef<HTMLDivElement | null>(null)
  const lastRecommendedPageSizeRef = useRef<number | null>(null)

  const recommendPageSize = useCallback(() => {
    if (!onRecommendedPageSizeChange) return

    const el = rootRef.current
    if (!el) return

    const containerWidth = el.clientWidth
    if (!Number.isFinite(containerWidth) || containerWidth <= 0) return

    const gapPx = 12 // entspricht Tailwind gap-3
    const columns = Math.max(
      1,
      Math.floor((containerWidth + gapPx) / (minCardWidth + gapPx))
    )

    // mobil etwas mehr vertikale Füllung, desktop wie bisher ca. 2 Reihen
    const rowsPerPage = containerWidth < 640 ? 3 : 2

    const nextPageSize = clamp(columns * rowsPerPage, rowsPerPage, 24)

    if (lastRecommendedPageSizeRef.current === nextPageSize) return
    lastRecommendedPageSizeRef.current = nextPageSize

    onRecommendedPageSizeChange(nextPageSize)
  }, [minCardWidth, onRecommendedPageSizeChange])

  useEffect(() => {
    recommendPageSize()

    const el = rootRef.current
    if (!el) return

    const ro = new ResizeObserver(() => {
      recommendPageSize()
    })

    ro.observe(el)
    window.addEventListener('resize', recommendPageSize)

    return () => {
      ro.disconnect()
      window.removeEventListener('resize', recommendPageSize)
    }
  }, [recommendPageSize])

  const contentPaddingClass =
    sizePreset === 'xl'
      ? 'px-4 py-3.5'
      : sizePreset === 'lg'
        ? 'px-3.5 py-3'
        : sizePreset === 'md'
          ? 'px-3 py-2.5'
          : 'px-2.5 py-2'

  const footerMinHeightClass =
    sizePreset === 'xl'
      ? 'min-h-[132px]'
      : sizePreset === 'lg'
        ? 'min-h-[124px]'
        : sizePreset === 'md'
          ? 'min-h-[118px]'
          : 'min-h-[110px]'

  const titleClass =
    sizePreset === 'xl'
      ? 'text-[15px]'
      : sizePreset === 'lg'
        ? 'text-sm'
        : sizePreset === 'md'
          ? 'text-[13px]'
          : 'text-xs'

  const sublineClass =
    sizePreset === 'xl'
      ? 'text-[13px]'
      : sizePreset === 'lg'
        ? 'text-xs'
        : 'text-[11px]'

  return (
    <div ref={rootRef} className="relative w-full">
      <div
        className="grid w-full justify-start gap-3"
        style={{
          gridTemplateColumns: `repeat(auto-fill, minmax(min(100%, ${minCardWidth}px), 1fr))`,
        }}
      >
        {rows.map((j) => {
          const k = keyFor(j)
          const durationForRow = durations[k]

          return (
            <div
              key={k}
              className="min-w-0 w-full"
            >
              <FinishedDownloadsGalleryCard
                job={j}
                selectedKeys={selectedKeys}
                selectionModeActive={selectionModeActive}
                rowKey={k}
                isPreviewActive={teaserState.activeKey === k || teaserState.hoverKey === k}
                forcePreviewMuted={forcePreviewMuted}
                blurPreviews={blurPreviews}
                postworkByFile={postworkByFile}
                getPostworkSummaryForOutput={getPostworkSummaryForOutput}
                renderPostworkBadge={renderPostworkBadge}
                durationSeconds={durationForRow}
                teaserState={teaserState}
                baseName={baseName}
                modelNameFromOutput={modelNameFromOutput}
                runtimeOf={runtimeOf}
                sizeBytesOf={sizeBytesOf}
                formatBytes={formatBytes}
                lower={lower}
                modelsByKey={modelsByKey}
                activeTagSet={activeTagSet}
                deletingKeys={deletingKeys}
                keepingKeys={keepingKeys}
                removingKeys={removingKeys}
                registerTeaserHost={registerTeaserHostIfNeeded}
                onToggleSelected={onToggleSelected}
                onSelectOnly={onSelectOnly}
                onHoverPreviewKeyChange={onHoverPreviewKeyChange}
                onToggleTagFilter={onToggleTagFilter}
                onOpenPlayer={onOpenPlayer}
                handleDuration={handleDuration}
                handleResolution={handleResolution}
                resolutionLabelOf={resolutionLabelOf}
                handleScrubberClickIndex={handleScrubberClickIndex}
                deleteVideo={deleteVideo}
                keepVideo={keepVideo}
                onToggleHot={onToggleHot}
                onToggleFavorite={onToggleFavorite}
                onToggleLike={onToggleLike}
                onToggleWatch={onToggleWatch}
                onSplit={onSplit}
                onAddToDownloads={onAddToDownloads}
                jobForDetails={jobForDetails}
                contentPaddingClass={contentPaddingClass}
                footerMinHeightClass={footerMinHeightClass}
                titleClass={titleClass}
                sublineClass={sublineClass}
                assetNonce={assetNonce ?? 0}
                renderIntegrityBadge={renderIntegrityBadge}
                renderRatingOverlay={renderRatingOverlay}
                mobileTeaserAudioUnlocked={mobileTeaserAudioUnlocked}
                onUnlockMobileTeaserAudio={onUnlockMobileTeaserAudio}
              />
            </div>
          )
        })}
      </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">
          <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>
  )
}

const MemoFinishedDownloadsGalleryView = memo(FinishedDownloadsGalleryView)
export default MemoFinishedDownloadsGalleryView