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

'use client'

import {
  useEffect,
  useRef,
  useState,
  useMemo,
  useCallback,
  useDeferredValue,
  type ReactNode,
  type MouseEvent as ReactMouseEvent,
} from 'react'
import clsx from 'clsx'
import Card from './Card'
import Button from './Button'
import ButtonGroup from './ButtonGroup'
import Table, { type Column } from './Table'
import Modal from './Modal'
import Pagination from './Pagination'
import TagBadge from './TagBadge'
import RecordJobActions from './RecordJobActions'
import type { RecordJob } from '../../types'
import TextInput from './TextInput'
import ModelGenderIcon from './ModelGenderIcon'
import CountryFlag from './CountryFlag'
import {
  ArrowTopRightOnSquareIcon,
  EyeIcon as EyeIconOutline,
  EyeSlashIcon,
  FilmIcon,
  HeartIcon as HeartIconOutline,
  Squares2X2Icon,
  StarIcon as StarIconOutline,
  TableCellsIcon,
  TagIcon,
} from '@heroicons/react/24/outline'
import {
  EyeIcon as EyeIconSolid,
  HeartIcon as HeartIconSolid,
  StarIcon as StarIconSolid,
} from '@heroicons/react/24/solid'
import TagFilterBar from './TagFilterBar'
import { parseTagList, lowerTag } from './tagUtils'

type ParsedModel = {
  input: string
  isUrl: boolean
  host?: string
  path?: string
  modelKey: string
}

type ImportKind = 'liked' | 'favorite'
type ModelsViewMode = 'table' | 'gallery'

type GallerySortMode =
  | 'created_desc'
  | 'created_asc'
  | 'model_asc'
  | 'model_desc'
  | 'videos_desc'
  | 'videos_asc'
  | 'tags_desc'
  | 'tags_asc'

export type StoredModel = {
  id: string
  input: string
  isUrl: boolean
  host?: string
  path?: string
  modelKey: string
  tags?: string
  lastStream?: string

  // ✅ Profile image Felder vom Backend
  profileImageUrl?: string
  profileImageCached?: string
  profileImageUpdatedAt?: string

  gender?: string | null
  country?: string | null

  watching: boolean
  favorite: boolean
  hot: boolean
  keep: boolean
  liked?: boolean | null
  createdAt: string
  updatedAt: string
}

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>
}

const badge = (on: boolean, label: string) => (
  <span
    className={clsx(
      'inline-flex items-center rounded-md px-2 py-0.5 text-xs',
      on
        ? 'bg-indigo-50 text-indigo-700 dark:bg-indigo-500/10 dark:text-indigo-200'
        : 'bg-gray-50 text-gray-600 dark:bg-white/5 dark:text-gray-300'
    )}
  >
    {label}
  </span>
)

/** Erlaubt nur http(s) URLs. Optional: ohne Scheme -> https:// */
function normalizeHttpUrl(raw: string): string | null {
  let v = (raw ?? '').trim()
  if (!v) return null

  if (!/^https?:\/\//i.test(v)) v = `https://${v}`

  try {
    const u = new URL(v)
    if (u.protocol !== 'http:' && u.protocol !== 'https:') return null
    return u.toString()
  } catch {
    return null
  }
}

function splitTags(raw?: string): string[] {
  return parseTagList(raw)
}

function canonicalHost(raw?: string): string {
  return String(raw ?? '')
    .trim()
    .toLowerCase()
    .replace(/^www\./, '')
}

function baseNameFromPath(p: string): string {
  const n = String(p || '').replaceAll('\\', '/')
  const parts = n.split('/')
  return parts[parts.length - 1] || ''
}

function stripHotPrefixLocal(file: string): string {
  const f = String(file || '')
  return f.toUpperCase().startsWith('HOT ') ? f.slice(4).trim() : f
}

// exakt wie in FinishedDownloads / backend: <modelKey>_MM_DD_YYYY__HH-MM-SS.ext
function modelKeyFromOutput(output?: string): string {
  const fileRaw = baseNameFromPath(output || '')
  const file = stripHotPrefixLocal(fileRaw)
  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
}

function modelHref(m: StoredModel): string | null {
  if (m.isUrl && /^https?:\/\//i.test(String(m.input ?? ''))) {
    return String(m.input)
  }

  const host = canonicalHost(m.host)
  const key = String(m.modelKey ?? '').trim()
  if (!host || !key) return null

  if (host.includes('chaturbate.com') || host.includes('chaturbate')) {
    return `https://chaturbate.com/${encodeURIComponent(key)}/`
  }

  if (host.includes('myfreecams.com') || host.includes('myfreecams')) {
    return `https://www.myfreecams.com/#${encodeURIComponent(key)}`
  }

  return null
}

function modelProfileImageSrc(m: StoredModel): string | null {
  const cached = String(m.profileImageCached ?? '').trim()
  if (cached) return cached

  const remote = String(m.profileImageUrl ?? '').trim()
  if (/^https?:\/\//i.test(remote)) return remote

  return null
}

function IconToggle({
  title,
  active,
  hiddenUntilHover,
  onClick,
  icon,
}: {
  title: string
  active?: boolean
  hiddenUntilHover?: boolean
  onClick: (e: ReactMouseEvent<HTMLButtonElement>) => void
  icon: ReactNode
}) {
  return (
    <span
      className={clsx(
        hiddenUntilHover && !active
          ? 'opacity-0 pointer-events-none group-hover:opacity-100 group-hover:pointer-events-auto transition-opacity'
          : 'opacity-100'
      )}
    >
      <Button
        variant={active ? 'soft' : 'secondary'}
        size="xs"
        className={clsx(
          'px-2 py-1 leading-none',
          active ? '' : 'bg-transparent shadow-none hover:bg-gray-50 dark:hover:bg-white/10'
        )}
        title={title}
        onClick={onClick}
      >
        <span className="text-base leading-none">{icon}</span>
      </Button>
    </span>
  )
}

function normalizeSortValue(v: any): { isNull: boolean; kind: 'number' | 'string'; value: number | string } {
  if (v === null || v === undefined) return { isNull: true, kind: 'string', value: '' }
  if (v instanceof Date) return { isNull: false, kind: 'number', value: v.getTime() }
  if (typeof v === 'number') return { isNull: false, kind: 'number', value: v }
  if (typeof v === 'boolean') return { isNull: false, kind: 'number', value: v ? 1 : 0 }
  if (typeof v === 'bigint') return { isNull: false, kind: 'number', value: Number(v) }
  return { isNull: false, kind: 'string', value: String(v).toLocaleLowerCase() }
}

function gallerySortModeFromSort(
  sort: { key: string; direction: 'asc' | 'desc' } | null
): GallerySortMode {
  if (!sort) return 'created_desc'

  if (sort.key === 'createdAt') return sort.direction === 'asc' ? 'created_asc' : 'created_desc'
  if (sort.key === 'model') return sort.direction === 'asc' ? 'model_asc' : 'model_desc'
  if (sort.key === 'videos') return sort.direction === 'asc' ? 'videos_asc' : 'videos_desc'
  if (sort.key === 'tags') return sort.direction === 'asc' ? 'tags_asc' : 'tags_desc'

  return 'created_desc'
}

function sortFromGallerySortMode(mode: GallerySortMode): { key: string; direction: 'asc' | 'desc' } {
  switch (mode) {
    case 'created_asc':
      return { key: 'createdAt', direction: 'asc' }
    case 'created_desc':
      return { key: 'createdAt', direction: 'desc' }

    case 'model_asc':
      return { key: 'model', direction: 'asc' }
    case 'model_desc':
      return { key: 'model', direction: 'desc' }

    case 'videos_asc':
      return { key: 'videos', direction: 'asc' }
    case 'videos_desc':
      return { key: 'videos', direction: 'desc' }

    case 'tags_asc':
      return { key: 'tags', direction: 'asc' }
    case 'tags_desc':
      return { key: 'tags', direction: 'desc' }

    default:
      return { key: 'createdAt', direction: 'desc' }
  }
}

function GridIcon() {
  return <Squares2X2Icon className="size-4" />
}

function TableIcon() {
  return <TableCellsIcon className="size-4" />
}

type ModelsTabProps = {
  onAddToDownloads?: (job: RecordJob) => void | Promise<boolean>
  onModelsCountChange?: (count: number) => void
}

export default function ModelsTab({ onAddToDownloads, onModelsCountChange }: ModelsTabProps) {
  const [models, setModels] = useState<StoredModel[]>([])
  const modelsLoadedRef = useRef(false)
  const flagsInFlightRef = useRef<Record<string, true>>({})

  const [loading, setLoading] = useState(false)
  const [err, setErr] = useState<string | null>(null)

  const [q, setQ] = useState('')
  const [page, setPage] = useState(1)
  const pageSize = 10

  const [viewMode, setViewMode] = useState<ModelsViewMode>('gallery')

  const [tagFilter, setTagFilter] = useState<string[]>([])

  type WatchFilterMode = 'all' | 'watched' | 'unwatched'
  type StatusFilterKey = 'favorite' | 'liked'

  const [watchFilter, setWatchFilter] = useState<WatchFilterMode>('all')
  const [statusFilter, setStatusFilter] = useState<StatusFilterKey[]>([])

  const activeStatusSet = useMemo(
    () => new Set(statusFilter),
    [statusFilter]
  )

  const toggleStatusFilter = useCallback((key: StatusFilterKey) => {
    setStatusFilter((prev) =>
      prev.includes(key)
        ? prev.filter((x) => x !== key)
        : [...prev, key]
    )
  }, [])

  const clearStatusFilter = useCallback(() => {
    setWatchFilter('all')
    setStatusFilter([])
  }, [])

  const [videoCounts, setVideoCounts] = useState<Record<string, number>>({})
  const [videoCountsLoading, setVideoCountsLoading] = useState(false)

  const [sort, setSort] = useState<{ key: string; direction: 'asc' | 'desc' } | null>({
    key: 'createdAt',
    direction: 'desc',
  })

  const refreshVideoCounts = useCallback(async () => {
    setVideoCountsLoading(true)
    try {
      const res = await fetch('/api/record/done/model-counts?includeKeep=1', { cache: 'no-store' as any })
      if (!res.ok) return

      const data = await res.json().catch(() => null)
      const rawCounts = data?.counts && typeof data.counts === 'object' ? data.counts : data

      const counts: Record<string, number> = {}

      for (const [key, value] of Object.entries(rawCounts ?? {})) {
        const mk = String(key || '').trim().toLowerCase()
        if (!mk || mk === '—') continue
        const k = mk.trim().toLowerCase()
        if (!k) continue
        const n = typeof value === 'number' ? value : Number(value)
        counts[k] = Number.isFinite(n) && n > 0 ? n : 0
      }

      setVideoCounts(counts)
    } finally {
      setVideoCountsLoading(false)
    }
  }, [])

  const activeTagSet = useMemo(() => new Set(tagFilter.map(lowerTag)), [tagFilter])

  const allKnownTags = useMemo(() => {
    const seen = new Set<string>()
    const out: string[] = []

    for (const m of models) {
      for (const tag of splitTags(m.tags)) {
        const k = lowerTag(tag)
        if (!k || seen.has(k)) continue
        seen.add(k)
        out.push(tag)
      }
    }

    out.sort((a, b) => a.localeCompare(b, undefined, { sensitivity: 'base' }))
    return out
  }, [models])

  const toggleTagFilter = useCallback((tag: string) => {
    const k = tag.toLowerCase()
    setTagFilter((prev) => {
      const has = prev.some((t) => t.toLowerCase() === k)
      return has ? prev.filter((t) => t.toLowerCase() !== k) : [...prev, tag]
    })
  }, [])

  const clearTagFilter = useCallback(() => setTagFilter([]), [])

  useEffect(() => {
    const onSet = (ev: Event) => {
      const e = ev as CustomEvent<{ tags?: string[] }>
      const tags = Array.isArray(e.detail?.tags) ? e.detail.tags : []
      setTagFilter(tags)
    }

    window.addEventListener('models:set-tag-filter', onSet as any)
    return () => window.removeEventListener('models:set-tag-filter', onSet as any)
  }, [])

  useEffect(() => {
    try {
      const raw = localStorage.getItem('models_pendingTags')
      if (!raw) return
      const tags = JSON.parse(raw)
      if (Array.isArray(tags)) setTagFilter(tags)
      localStorage.removeItem('models_pendingTags')
    } catch {
      // ignore
    }
  }, [])

  useEffect(() => {
    try {
      const saved = localStorage.getItem('models_viewMode')
      if (saved === 'table' || saved === 'gallery') setViewMode(saved)
    } catch {
      // ignore
    }
  }, [])

  useEffect(() => {
    try {
      localStorage.setItem('models_viewMode', viewMode)
    } catch {
      // ignore
    }
  }, [viewMode])

  const [input, setInput] = useState('')
  const [parsed, setParsed] = useState<ParsedModel | null>(null)
  const [parseError, setParseError] = useState<string | null>(null)
  const [adding, setAdding] = useState(false)

  const [importOpen, setImportOpen] = useState(false)
  const [importFile, setImportFile] = useState<File | null>(null)
  const [importMsg, setImportMsg] = useState<string | null>(null)

  const [importKind, setImportKind] = useState<ImportKind>('favorite')
  const [importing, setImporting] = useState(false)
  const [importErr, setImportErr] = useState<string | null>(null)

  async function doImport() {
    if (!importFile) return
    setImporting(true)
    setImportMsg(null)
    setErr(null)
    try {
      const fd = new FormData()
      fd.append('file', importFile)
      fd.append('kind', importKind)

      const res = await fetch('/api/models/import', { method: 'POST', body: fd })
      if (!res.ok) throw new Error(await res.text())
      const data = await res.json()
      setImportMsg(`✅ Import: ${data.inserted} neu, ${data.updated} aktualisiert, ${data.skipped} übersprungen`)
      setImportOpen(false)
      setImportFile(null)
      await refresh()
      window.dispatchEvent(new Event('models-changed'))
    } catch (e: any) {
      setImportErr(e?.message ?? String(e))
    } finally {
      setImporting(false)
    }
  }

  function jobForDetails(m: StoredModel): RecordJob {
    const href = modelHref(m) ?? undefined
    const modelKey = modelKeyFromOutput(`${m.modelKey}_01_01_2000__00-00-00.mp4`) || m.modelKey
    return {
      output: `${modelKey}_01_01_2000__00-00-00.mp4`,
      sourceUrl: href,
    } as any
  }

  const openImport = () => {
    setImportErr(null)
    setImportMsg(null)
    setImportFile(null)
    setImportKind('favorite')
    setImportOpen(true)
  }

  const refresh = useCallback(async () => {
    setLoading(true)
    setErr(null)
    try {
      const res = await fetch('/api/models', { cache: 'no-store' as any })
      if (!res.ok) throw new Error(await res.text().catch(() => `HTTP ${res.status}`))

      const data = await res.json().catch(() => null)

      // ✅ akzeptiere beide Formen: Array ODER { items: [...] }
      const list: StoredModel[] = Array.isArray(data?.items)
        ? (data.items as StoredModel[])
        : Array.isArray(data)
          ? (data as StoredModel[])
          : []

      setModels(list)
      modelsLoadedRef.current = true
      onModelsCountChange?.(list.length)
      void refreshVideoCounts()
    } catch (e: any) {
      setErr(e?.message ?? String(e))
    } finally {
      setLoading(false)
    }
  }, [onModelsCountChange, refreshVideoCounts])

  useEffect(() => {
    void refresh()
  }, [refresh])

  useEffect(() => {
    if (!modelsLoadedRef.current) return
    onModelsCountChange?.(models.length)
  }, [models.length, onModelsCountChange])

  useEffect(() => {
    const onChanged = (ev: Event) => {
      const e = ev as CustomEvent<any>
      const detail = e?.detail ?? {}

      if (detail?.model) {
        const updated = detail.model as StoredModel
        setModels((prev) => {
          const idx = prev.findIndex((m) => m.id === updated.id)
          if (idx === -1) return [updated, ...prev]
          const next = prev.slice()
          next[idx] = updated
          return next
        })
        return
      }

      if (detail?.removed && detail?.id) {
        const rid = String(detail.id)
        setModels((prev) => prev.filter((m) => m.id !== rid))
        return
      }

      void refresh()
    }

    window.addEventListener('models-changed', onChanged as any)
    return () => window.removeEventListener('models-changed', onChanged as any)
  }, [refresh])

  useEffect(() => {
    const onDbChanged = () => {
      setPage(1)
      modelsLoadedRef.current = false
      setModels([])
      void refresh()
    }

    window.addEventListener('models-db-changed', onDbChanged as any)
    return () => window.removeEventListener('models-db-changed', onDbChanged as any)
  }, [refresh])

  useEffect(() => {
    const raw = input.trim()
    if (!raw) {
      setParsed(null)
      setParseError(null)
      return
    }

    const normalized = normalizeHttpUrl(raw)
    if (!normalized) {
      setParsed(null)
      setParseError('Bitte nur gültige http(s) URLs einfügen (keine Modelnamen).')
      return
    }

    const t = window.setTimeout(async () => {
      try {
        const p = await apiJSON<ParsedModel>('/api/models/parse', {
          method: 'POST',
          headers: { 'Content-Type': 'application/json' },
          body: JSON.stringify({ input: normalized }),
        })

        if (!p?.isUrl) {
          setParsed(null)
          setParseError('Bitte nur URLs einfügen (keine Modelnamen).')
          return
        }

        setParsed(p)
        setParseError(null)
      } catch (e: any) {
        setParsed(null)
        setParseError(e?.message ?? String(e))
      }
    }, 300)

    return () => window.clearTimeout(t)
  }, [input])

  const modelsWithHay = useMemo(() => {
    return models.map((m) => ({
      m,
      hay: `${m.modelKey} ${m.host ?? ''} ${m.input ?? ''} ${m.tags ?? ''}`.toLowerCase(),
    }))
  }, [models])

  const deferredQ = useDeferredValue(q)

  async function patch(id: string, body: any) {
    setErr(null)

    if (flagsInFlightRef.current[id]) return
    flagsInFlightRef.current[id] = true

    const prevModel = models.find((m) => m.id === id) ?? null
    if (prevModel) {
      const optimistic: StoredModel = { ...prevModel, ...body }

      if (typeof body?.watched === 'boolean') optimistic.watching = body.watched
      if (body?.favorite === true) optimistic.liked = false
      if (body?.liked === true) optimistic.favorite = false

      setModels((prev) => prev.map((m) => (m.id === id ? optimistic : m)))
      window.dispatchEvent(new CustomEvent('models-changed', { detail: { model: optimistic } }))
    }

    try {
      const res = await fetch('/api/models/flags', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ id, ...body }),
      })

      if (res.status === 204) {
        setModels((prev) => prev.filter((m) => m.id !== id))

        if (prevModel) {
          window.dispatchEvent(
            new CustomEvent('models-changed', {
              detail: { removed: true, id: prevModel.id, modelKey: prevModel.modelKey },
            })
          )
        } else {
          window.dispatchEvent(new CustomEvent('models-changed', { detail: { removed: true, id } }))
        }
        return
      }

      if (!res.ok) {
        const text = await res.text().catch(() => '')
        throw new Error(text || `HTTP ${res.status}`)
      }

      const updated = (await res.json()) as StoredModel

      setModels((prev) => {
        const idx = prev.findIndex((m) => m.id === updated.id)
        if (idx === -1) return prev
        const next = prev.slice()
        next[idx] = updated
        return next
      })

      window.dispatchEvent(new CustomEvent('models-changed', { detail: { model: updated } }))
    } catch (e: any) {
      if (prevModel) {
        setModels((prev) => prev.map((m) => (m.id === id ? prevModel : m)))
        window.dispatchEvent(new CustomEvent('models-changed', { detail: { model: prevModel } }))
      }
      setErr(e?.message ?? String(e))
    } finally {
      delete flagsInFlightRef.current[id]
    }
  }

  const columns = useMemo<Column<StoredModel>[]>(() => {
    return [
      {
        key: 'statusAll',
        header: 'Status',
        align: 'center',
        cell: (m) => {
          const liked = m.liked === true
          const fav = m.favorite === true
          const watch = m.watching === true
          const hideUntilHover = !watch && !fav && !liked

          return (
            <div className="group flex items-center justify-center gap-1 w-[110px]">
              <span
                className={clsx(
                  hideUntilHover && !watch
                    ? 'opacity-0 pointer-events-none group-hover:opacity-100 group-hover:pointer-events-auto transition-opacity'
                    : 'opacity-100'
                )}
              >
                <Button
                  variant={watch ? 'soft' : 'secondary'}
                  size="xs"
                  className={clsx(
                    'px-2 py-1 leading-none',
                    !watch && 'bg-transparent shadow-none hover:bg-gray-50 dark:hover:bg-white/10'
                  )}
                  title={watch ? 'Nicht mehr beobachten' : 'Beobachten'}
                  onClick={(e) => {
                    e.stopPropagation()
                    patch(m.id, { watched: !watch })
                  }}
                >
                  {watch ? (
                    <EyeIconSolid className="size-5 text-indigo-600 dark:text-indigo-400" />
                  ) : (
                    <EyeIconOutline className="size-5 text-gray-400 dark:text-gray-500" />
                  )}
                </Button>
              </span>

              <IconToggle
                title={fav ? 'Favorit entfernen' : 'Als Favorit markieren'}
                active={fav}
                hiddenUntilHover={hideUntilHover}
                onClick={(e) => {
                  e.stopPropagation()
                  if (fav) patch(m.id, { favorite: false })
                  else patch(m.id, { favorite: true, liked: false })
                }}
                icon={
                  fav ? (
                    <StarIconSolid className="size-5 text-amber-500" />
                  ) : (
                    <StarIconOutline className="size-5 text-gray-400 dark:text-gray-500" />
                  )
                }
              />

              <IconToggle
                title={liked ? 'Gefällt mir entfernen' : 'Gefällt mir'}
                active={liked}
                hiddenUntilHover={hideUntilHover}
                onClick={(e) => {
                  e.stopPropagation()
                  if (liked) patch(m.id, { liked: false })
                  else patch(m.id, { liked: true, favorite: false })
                }}
                icon={
                  liked ? (
                    <HeartIconSolid className="size-5 text-rose-500" />
                  ) : (
                    <HeartIconOutline className="size-5 text-gray-400 dark:text-gray-500" />
                  )
                }
              />
            </div>
          )
        },
      },
      {
        key: 'model',
        header: 'Model',
        sortable: true,
        sortValue: (m) => (m.modelKey || '').toLowerCase(),
        cell: (m) => {
          const href = modelHref(m)
          const imgSrc = modelProfileImageSrc(m)

          return (
            <div className="min-w-0">
              <div className="flex items-start gap-2 min-w-0">
                <div className="shrink-0 mt-0.5">
                  {imgSrc ? (
                    <img
                      src={imgSrc}
                      alt={m.modelKey}
                      loading="lazy"
                      className="w-10 h-10 rounded-full object-cover bg-gray-100 dark:bg-white/10 ring-1 ring-gray-200 dark:ring-white/10"
                      onClick={(e) => {
                        e.stopPropagation()
                        if (href) window.open(href, '_blank', 'noreferrer')
                      }}
                      onError={(e) => {
                        e.currentTarget.style.display = 'none'
                      }}
                    />
                  ) : (
                    <div className="w-10 h-10 rounded-full bg-gray-100 dark:bg-white/10 ring-1 ring-gray-200 dark:ring-white/10 flex items-center justify-center text-xs text-gray-500 dark:text-gray-400">
                      {String(m.modelKey || '?').slice(0, 1).toUpperCase()}
                    </div>
                  )}
                </div>

                <div className="min-w-0 flex-1">
                  <div className="flex items-center gap-2 min-w-0">
                    <div className="flex items-center gap-1.5 min-w-0">
                      <div className="font-medium truncate">{m.modelKey}</div>

                      {href ? (
                        <span
                          className="shrink-0 text-xs text-gray-400 dark:text-gray-500"
                          title={href}
                          onClick={(e) => {
                            e.stopPropagation()
                            window.open(href, '_blank', 'noreferrer')
                          }}
                        >
                          <ArrowTopRightOnSquareIcon className="size-4" />
                        </span>
                      ) : null}

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

                  <div className="text-xs text-gray-500 dark:text-gray-400 truncate">
                    {m.host ?? '—'}
                  </div>

                  {href ? (
                    <div className="text-xs text-indigo-600 dark:text-indigo-400 truncate max-w-[520px]">
                      {href}
                    </div>
                  ) : null}
                </div>
              </div>
            </div>
          )
        },
      },
      {
        key: 'videos',
        header: 'Videos',
        sortable: true,
        sortValue: (m) => {
          const k = String(m.modelKey || '').trim().toLowerCase()
          const n = videoCounts[k]
          return typeof n === 'number' ? n : 0
        },
        align: 'right',
        cell: (m) => {
          const k = String(m.modelKey || '').trim().toLowerCase()
          const n = videoCounts[k]
          return (
            <span className="tabular-nums text-sm text-gray-900 dark:text-white w-[64px] inline-block text-right">
              {typeof n === 'number' ? n : videoCountsLoading ? '…' : 0}
            </span>
          )
        },
      },
      {
        key: 'tags',
        header: 'Tags',
        sortable: true,
        sortValue: (m) => splitTags(m.tags).length,
        cell: (m) => {
          const tags = splitTags(m.tags)
          const shown = tags.slice(0, 6)
          const rest = tags.length - shown.length
          const full = tags.join(', ')

          return (
            <div className="flex flex-wrap gap-2 max-w-[520px]" title={full || undefined}>
              {m.hot ? badge(true, '🔥 HOT') : null}
              {m.keep ? badge(true, '📌 Behalten') : null}

              {shown.map((t) => (
                <TagBadge
                  key={t}
                  tag={t}
                  title={t}
                  active={activeTagSet.has(t.toLowerCase())}
                  onClick={toggleTagFilter}
                />
              ))}

              {rest > 0 ? <TagBadge title={full}>+{rest}</TagBadge> : null}

              {!m.hot && !m.keep && tags.length === 0 ? (
                <span className="text-xs text-gray-400 dark:text-gray-500">—</span>
              ) : null}
            </div>
          )
        },
      },
      {
        key: 'createdAt',
        header: 'Hinzugefügt',
        sortable: true,
        sortValue: (m) => {
          const t = Date.parse(String(m.createdAt ?? ''))
          return Number.isFinite(t) ? t : 0
        },
        widthClassName: 'w-[160px]',
        cell: (m) => {
          const ts = Date.parse(String(m.createdAt ?? ''))
          if (!Number.isFinite(ts) || ts <= 0) {
            return <span className="text-xs text-gray-400 dark:text-gray-500">—</span>
          }

          const d = new Date(ts)
          return (
            <div className="text-xs text-gray-700 dark:text-gray-300 leading-tight">
              <div className="tabular-nums">
                {d.toLocaleDateString('de-DE')}
              </div>
              <div className="tabular-nums text-gray-500 dark:text-gray-400">
                {d.toLocaleTimeString('de-DE', { hour: '2-digit', minute: '2-digit' })}
              </div>
            </div>
          )
        },
      },
      {
        key: 'actions',
        header: '',
        align: 'right',
        cell: (m) => (
          <div className="flex justify-end w-[92px]">
            <RecordJobActions
              job={jobForDetails(m)}
              variant="table"
              onAddToDownloads={onAddToDownloads}
              order={['add', 'details']}
              className="flex items-center gap-2"
            />
          </div>
        ),
      },
    ]
  }, [activeTagSet, toggleTagFilter, videoCounts, videoCountsLoading])

  const filtered = useMemo(() => {
    const needle = deferredQ.trim().toLowerCase()

    let base = !needle
      ? models
      : modelsWithHay.filter((x) => x.hay.includes(needle)).map((x) => x.m)

    // Watch-Filter: Alle / Watched / Unwatched
    if (watchFilter === 'watched') {
      base = base.filter((m) => m.watching === true)
    } else if (watchFilter === 'unwatched') {
      base = base.filter((m) => m.watching !== true)
    }

    // Favorite / Liked: OR-Logik
    if (activeStatusSet.size > 0) {
      base = base.filter((m) => {
        const fav = m.favorite === true
        const liked = m.liked === true

        return (
          (activeStatusSet.has('favorite') && fav) ||
          (activeStatusSet.has('liked') && liked)
        )
      })
    }

    if (activeTagSet.size === 0) return base

    return base.filter((m) => {
      const tags = splitTags(m.tags)
      if (tags.length === 0) return false
      const have = new Set(tags.map((t) => t.toLowerCase()))
      for (const t of activeTagSet) {
        if (!have.has(t)) return false
      }
      return true
    })
  }, [models, modelsWithHay, deferredQ, watchFilter, activeStatusSet, activeTagSet])

  const sortedAll = useMemo(() => {
    if (!sort) return filtered

    const col = columns.find((c) => c.key === sort.key)
    if (!col) return filtered

    const dirMul = sort.direction === 'asc' ? 1 : -1
    const decorated = filtered.map((r, i) => ({ r, i }))

    decorated.sort((x, y) => {
      let res = 0

      if (col.sortFn) {
        res = col.sortFn(x.r, y.r)
      } else {
        const ax = col.sortValue ? col.sortValue(x.r) : (x.r as any)?.[col.key]
        const by = col.sortValue ? col.sortValue(y.r) : (y.r as any)?.[col.key]

        const na = normalizeSortValue(ax)
        const nb = normalizeSortValue(by)

        if (na.isNull && !nb.isNull) res = 1
        else if (!na.isNull && nb.isNull) res = -1
        else if (na.kind === 'number' && nb.kind === 'number') res = na.value < nb.value ? -1 : na.value > nb.value ? 1 : 0
        else res = String(na.value).localeCompare(String(nb.value), undefined, { numeric: true })
      }

      if (res === 0) return x.i - y.i
      return res * dirMul
    })

    return decorated.map((d) => d.r)
  }, [filtered, sort, columns])

  useEffect(() => {
    setPage(1)
  }, [q, tagFilter, watchFilter, statusFilter, viewMode, sort])

  const totalItems = sortedAll.length
  const totalPages = useMemo(() => Math.max(1, Math.ceil(totalItems / pageSize)), [totalItems, pageSize])

  useEffect(() => {
    if (page > totalPages) setPage(totalPages)
  }, [page, totalPages])

  const pageRows = useMemo(() => {
    const start = (page - 1) * pageSize
    return sortedAll.slice(start, start + pageSize)
  }, [sortedAll, page, pageSize])

  const upsertFromParsed = async () => {
    if (!parsed) return
    if (!parsed.isUrl) {
      setParseError('Bitte nur URLs einfügen (keine Modelnamen).')
      return
    }

    setAdding(true)
    setErr(null)
    try {
      const saved = await apiJSON<StoredModel>('/api/models/upsert', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify(parsed),
      })
      setModels((prev) => {
        const rest = prev.filter((x) => x.id !== saved.id)
        return [saved, ...rest]
      })
      setInput('')
      setParsed(null)
      window.dispatchEvent(new CustomEvent('models-changed', { detail: { model: saved } }))
    } catch (e: any) {
      setErr(e?.message ?? String(e))
    } finally {
      setAdding(false)
    }
  }

  return (
    <div className="space-y-4">
      <Card
        header={<div className="text-sm font-medium text-gray-900 dark:text-white">Model hinzufügen</div>}
        grayBody
      >
        <div className="grid gap-2">
          <div className="flex flex-col sm:flex-row gap-2">
            <TextInput
              value={input}
              size='sm'
              onChange={(e) => setInput(e.target.value)}
              placeholder="https://…"
              className="flex-1"
            />
            <Button
              className="px-3 py-2 text-sm"
              onClick={upsertFromParsed}
              disabled={!parsed || adding}
              title={!parsed ? 'Bitte gültige URL einfügen' : 'In Models speichern'}
            >
              Hinzufügen
            </Button>
            <Button className="px-3 py-2 text-sm" onClick={refresh} disabled={loading}>
              Aktualisieren
            </Button>
          </div>

          {parseError ? (
            <div className="text-xs text-red-600 dark:text-red-300">{parseError}</div>
          ) : parsed ? (
            <div className="text-xs text-gray-600 dark:text-gray-300">
              Gefunden: <span className="font-medium">{parsed.modelKey}</span>
              {parsed.host ? <span className="opacity-70"> • {parsed.host}</span> : null}
            </div>
          ) : null}

          {err ? <div className="text-xs text-red-600 dark:text-red-300">{err}</div> : null}
        </div>
      </Card>

      <Card
        header={
          <div className="space-y-2">
            <div className="grid gap-2 sm:flex sm:items-center sm:justify-between">
              <div className="flex items-center gap-2 min-w-0">
                <div className="text-sm font-medium text-gray-900 dark:text-white whitespace-nowrap">
                  Models <span className="text-gray-500 dark:text-gray-400">({filtered.length})</span>
                </div>

                {/* Mobile */}
                <div className="sm:hidden shrink-0">
                  <Button variant="secondary" size="md" onClick={openImport}>
                    Import
                  </Button>
                </div>

                {/* Desktop */}
                <div className="hidden sm:block shrink-0">
                  <Button variant="secondary" size="md" onClick={openImport}>
                    Importieren
                  </Button>
                </div>
              </div>

              <div className="w-full sm:w-auto min-w-0">
                {/* Mobile Layout: 2 Zeilen */}
                <div className="sm:hidden grid gap-2">
                  {/* Zeile 1: View + Sort */}
                  <div className="grid grid-cols-[auto,minmax(0,1fr)] gap-2 items-center min-w-0">
                    <div className="shrink-0">
                      <ButtonGroup
                        ariaLabel="Ansicht umschalten"
                        size="lg"
                        className="flex w-full [&>button]:flex-1 [&>button]:min-w-0"
                        value={viewMode}
                        onChange={(id) => {
                          if (id === 'table' || id === 'gallery') setViewMode(id)
                        }}
                        items={[
                          {
                            id: 'gallery',
                            label: 'Gallery',
                            icon: <GridIcon />,
                          },
                          {
                            id: 'table',
                            label: 'Tabelle',
                            icon: <TableIcon />,
                          },
                        ]}
                      />
                    </div>

                    <div className="min-w-0">
                      {viewMode === 'gallery' ? (
                        <>
                          <label className="sr-only" htmlFor="models-gallery-sort-mobile">
                            Sortierung
                          </label>
                          <select
                            id="models-gallery-sort-mobile"
                            value={gallerySortModeFromSort(sort)}
                            onChange={(e) => {
                              const next = sortFromGallerySortMode(e.target.value as GallerySortMode)
                              setSort(next)
                            }}
                            className="
                              h-9 w-full min-w-0
                              rounded-md px-2 text-sm
                              bg-white text-gray-900 shadow-sm ring-1 ring-gray-200
                              focus:outline-none focus:ring-2 focus:ring-indigo-500
                              dark:bg-white/10 dark:text-white dark:ring-white/10 dark:[color-scheme:dark]
                            "
                          >
                            <option value="created_desc">Hinzugefügt ↓</option>
                            <option value="created_asc">Hinzugefügt ↑</option>
                            <option value="model_asc">Model A→Z</option>
                            <option value="model_desc">Model Z→A</option>
                            <option value="videos_desc">Videos ↓</option>
                            <option value="videos_asc">Videos ↑</option>
                            <option value="tags_desc">Tags ↓</option>
                            <option value="tags_asc">Tags ↑</option>
                          </select>
                        </>
                      ) : (
                        <div
                          aria-hidden="true"
                          className="
                            h-9 w-full min-w-0
                            rounded-md px-3 text-sm
                            inline-flex items-center
                            bg-gray-50 text-gray-400 ring-1 ring-gray-200
                            dark:bg-white/5 dark:text-white/30 dark:ring-white/10
                            select-none
                          "
                          title="Sortierung in der Tabellenansicht über Spaltenkopf"
                        >
                          Sortierung
                        </div>
                      )}
                    </div>
                  </div>

                  {/* Zeile 2: Suche volle Breite */}
                  <TextInput
                    value={q}
                    size='sm'
                    onChange={(e) => setQ(e.target.value)}
                    placeholder="Suchen…"
                  />
                </div>

                {/* Desktop Layout: wie bisher */}
                <div className="hidden sm:flex items-center gap-2 min-w-0 sm:justify-end">
                  {viewMode === 'gallery' ? (
                    <div className="shrink-0">
                      <label className="sr-only" htmlFor="models-gallery-sort">
                        Sortierung
                      </label>
                      <select
                        id="models-gallery-sort"
                        value={gallerySortModeFromSort(sort)}
                        onChange={(e) => {
                          const next = sortFromGallerySortMode(e.target.value as GallerySortMode)
                          setSort(next)
                        }}
                        className="
                          h-9 w-auto
                          rounded-md px-3 text-sm
                          bg-white text-gray-900 shadow-sm ring-1 ring-gray-200
                          focus:outline-none focus:ring-2 focus:ring-indigo-500
                          dark:bg-white/10 dark:text-white dark:ring-white/10 dark:[color-scheme:dark]
                        "
                      >
                        <option value="created_desc">Hinzugefügt ↓</option>
                        <option value="created_asc">Hinzugefügt ↑</option>
                        <option value="model_asc">Model A→Z</option>
                        <option value="model_desc">Model Z→A</option>
                        <option value="videos_desc">Videos ↓</option>
                        <option value="videos_asc">Videos ↑</option>
                        <option value="tags_desc">Tags ↓</option>
                        <option value="tags_asc">Tags ↑</option>
                      </select>
                    </div>
                  ) : null}

                  <div className="shrink-0">
                    <ButtonGroup
                      ariaLabel="Ansicht umschalten"
                      size="lg"
                      value={viewMode}
                      onChange={(id) => {
                        if (id === 'table' || id === 'gallery') setViewMode(id)
                      }}
                      items={[
                        {
                          id: 'gallery',
                          label: 'Gallery',
                          icon: <GridIcon />,
                        },
                        {
                          id: 'table',
                          label: 'Tabelle',
                          icon: <TableIcon />,
                        },
                      ]}
                    />
                  </div>

                  <TextInput
                    value={q}
                    size='sm'
                    onChange={(e) => setQ(e.target.value)}
                    placeholder="Suchen…"
                    className="sm:w-[260px]"
                  />
                </div>
              </div>
            </div>

            <div className="flex flex-wrap items-center gap-2">
              <span className="text-xs font-medium text-gray-500 dark:text-gray-400">
                Status-Filter:
              </span>

              <div className="flex flex-wrap items-center gap-2">
                <ButtonGroup
                  ariaLabel="Watched-Filter"
                  size="sm"
                  value={watchFilter}
                  onChange={(id) => {
                    if (id === 'all' || id === 'watched' || id === 'unwatched') {
                      setWatchFilter(id)
                    }
                  }}
                  items={[
                    {
                      id: 'all',
                      label: 'Alle',
                    },
                    {
                      id: 'watched',
                      label: 'Watched',
                      icon: <EyeIconSolid className="size-4" />,
                    },
                    {
                      id: 'unwatched',
                      label: 'Unwatched',
                      icon: <EyeSlashIcon className="size-4" />,
                    },
                  ]}
                />

                <Button
                  size="sm"
                  color="amber"
                  variant={activeStatusSet.has('favorite') ? 'soft' : 'secondary'}
                  onClick={() => toggleStatusFilter('favorite')}
                  title="Favoriten filtern"
                  leadingIcon={
                    activeStatusSet.has('favorite') ? (
                      <StarIconSolid className="size-4" />
                    ) : (
                      <StarIconOutline className="size-4" />
                    )
                  }
                >
                  Favorited
                </Button>

                <Button
                  size="sm"
                  color="red"
                  variant={activeStatusSet.has('liked') ? 'soft' : 'secondary'}
                  onClick={() => toggleStatusFilter('liked')}
                  title="Liked Models filtern"
                  leadingIcon={
                    activeStatusSet.has('liked') ? (
                      <HeartIconSolid className="size-4" />
                    ) : (
                      <HeartIconOutline className="size-4" />
                    )
                  }
                >
                  Liked
                </Button>

                {watchFilter !== 'all' || statusFilter.length > 0 ? (
                  <Button
                    size="md"
                    variant="soft"
                    onClick={clearStatusFilter}
                    className="text-xs font-medium text-gray-600 hover:underline dark:text-gray-300"
                  >
                    Zurücksetzen
                  </Button>
                ) : null}
              </div>
            </div>

            {(allKnownTags.length > 0 || tagFilter.length > 0) ? (
              <div className="flex flex-wrap items-start gap-2">
                <span className="pt-1 text-xs font-medium text-gray-500 dark:text-gray-400">
                  Tag-Filter:
                </span>

                <div className="min-w-0 flex-1">
                  <TagFilterBar
                    activeTags={tagFilter}
                    allTags={allKnownTags}
                    onAddTag={(tag) => {
                      setTagFilter((prev) =>
                        prev.some((t) => lowerTag(t) === lowerTag(tag)) ? prev : [...prev, tag]
                      )
                    }}
                    onRemoveTag={(tag) => {
                      setTagFilter((prev) => prev.filter((t) => lowerTag(t) !== lowerTag(tag)))
                    }}
                    onClear={clearTagFilter}
                  />
                </div>
              </div>
            ) : null}
          </div>
        }
        noBodyPadding
      >
        {viewMode === 'table' ? (
          <>
            <div className="overflow-x-auto">
              <div className="min-w-[980px]">
                <Table
                  rows={pageRows}
                  columns={columns}
                  getRowKey={(m) => m.id}
                  striped
                  compact
                  fullWidth
                  stickyHeader
                  sort={sort}
                  onSortChange={(next) => setSort(next)}
                  onRowClick={(m) => {
                    const href = modelHref(m)
                    if (href) window.open(href, '_blank', 'noreferrer')
                  }}
                />
              </div>
            </div>

            <Pagination page={page} pageSize={pageSize} totalItems={totalItems} onPageChange={setPage} />
          </>
        ) : (
          <>
            <div className="p-2 sm:p-3">
              {pageRows.length === 0 ? (
                <div className="rounded-lg border border-dashed border-gray-200 p-8 text-center text-sm text-gray-500 dark:border-white/10 dark:text-gray-400">
                  Keine Models gefunden.
                </div>
              ) : (
                <div className="grid grid-cols-2 gap-2 md:grid-cols-3 lg:grid-cols-5">
                  {pageRows.map((m) => {
                    const href = modelHref(m)
                    const imgSrc = modelProfileImageSrc(m)
                    const tags = splitTags(m.tags)
                    const shownTags = tags.slice(0, 3)
                    const liked = m.liked === true
                    const fav = m.favorite === true
                    const watch = m.watching === true
                    const hideUntilHover = !watch && !fav && !liked
                    const videoCount = videoCounts[String(m.modelKey || '').trim().toLowerCase()] ?? 0
                    const modelName = String(m.modelKey || '?')
                    const len = modelName.length

                    return (
                      <div
                        key={m.id}
                        className="group flex h-full flex-col overflow-hidden rounded-xl border border-gray-200 bg-white shadow-sm transition hover:-translate-y-0.5 hover:shadow-md dark:border-white/10 dark:bg-slate-900/80"
                      >
                        <div
                          className="relative cursor-pointer bg-gray-100 dark:bg-slate-950"
                          onClick={() => {
                            if (href) window.open(href, '_blank', 'noreferrer')
                          }}
                        >
                          {/* Bildbereich ähnlich Screenshot */}
                          <div className="relative aspect-[3/4] w-full overflow-hidden bg-gradient-to-br from-gray-100 to-slate-200 dark:from-[#12202c] dark:to-[#0b1620]">
                            {imgSrc ? (
                              <img
                                src={imgSrc}
                                alt={m.modelKey}
                                loading="lazy"
                                className="absolute inset-0 h-full w-full object-cover transition-transform duration-300 group-hover:scale-[1.02]"
                                onError={(e) => {
                                  e.currentTarget.style.display = 'none'
                                  const fallback = e.currentTarget.nextElementSibling as HTMLElement | null
                                  if (fallback) fallback.style.display = 'flex'
                                }}
                              />
                            ) : null}

                            {/* Fallback-Name im Bild */}
                            <div
                              className={clsx(
                                'absolute inset-0 items-center justify-center px-3 text-center font-semibold leading-tight text-slate-600 dark:text-slate-500',
                                imgSrc ? 'hidden' : 'flex'
                              )}
                              style={{
                                display: imgSrc ? 'none' : 'flex',
                                fontSize:
                                  len <= 10
                                    ? 'clamp(1rem, 1.4vw + 0.4rem, 2.1rem)'
                                    : len <= 18
                                      ? 'clamp(0.95rem, 1.15vw + 0.3rem, 1.7rem)'
                                      : 'clamp(0.8rem, 0.9vw + 0.25rem, 1.25rem)',
                                lineHeight: 1.1,
                                overflowWrap: 'anywhere',
                                wordBreak: 'break-word',
                              }}
                              title={modelName}
                            >
                              {modelName}
                            </div>

                            {/* dunkler Verlauf unten für bessere Lesbarkeit */}
                            <div className="pointer-events-none absolute inset-x-0 bottom-0 h-20 bg-gradient-to-t from-black/55 via-black/15 to-transparent dark:from-black/70 dark:via-black/20" />

                            {/* Modelname im Bild unten links (mit Safe-Area rechts für Stats) */}
                            <div className="pointer-events-none absolute inset-x-0 bottom-0 p-2.5 pr-18 sm:pr-2.5">
                              <div className="flex items-center gap-1.5 min-w-0">
                                <div
                                  className="truncate text-sm font-semibold text-white drop-shadow-[0_1px_2px_rgba(0,0,0,0.9)]"
                                  title={m.modelKey}
                                >
                                  {m.modelKey}
                                </div>

                                <ModelGenderIcon
                                  gender={m.gender}
                                  className="shrink-0 text-white"
                                />

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

                              {m.host ? (
                                <div className="truncate text-[11px] text-white/70 drop-shadow-[0_1px_2px_rgba(0,0,0,0.9)]">
                                  {m.host}
                                </div>
                              ) : null}
                            </div>

                            {/* Stats im Bild (unten rechts, untereinander, mit Icons) */}
                            <div className="pointer-events-none absolute bottom-2 right-2 z-10 flex flex-col items-end gap-1">
                              <span
                                className="inline-flex items-center gap-1 px-0.5 py-0 text-[10px] font-semibold text-white/95 tabular-nums"
                                title="Videos"
                                aria-hidden="true"
                              >
                                <FilmIcon className="size-3.5 drop-shadow-[0_1px_2px_rgba(0,0,0,0.9)]" />
                                <span className="drop-shadow-[0_1px_2px_rgba(0,0,0,0.9)]">
                                  {videoCountsLoading ? '…' : videoCount}
                                </span>
                              </span>

                              <span
                                className="inline-flex items-center gap-1 px-0.5 py-0 text-[10px] font-semibold text-white/95 tabular-nums"
                                title="Tags"
                                aria-hidden="true"
                              >
                                <TagIcon className="size-3.5 drop-shadow-[0_1px_2px_rgba(0,0,0,0.9)]" />
                                <span className="drop-shadow-[0_1px_2px_rgba(0,0,0,0.9)]">
                                  {tags.length}
                                </span>
                              </span>
                            </div>

                            {/* oben links: Record Actions Overlay */}
                            <div
                              className={clsx(
                                'absolute left-2 z-10 flex items-center gap-1',
                                (m.hot || m.keep) ? 'top-12' : 'top-2'
                              )}
                              onClick={(e) => e.stopPropagation()}
                            >
                              <RecordJobActions
                                job={jobForDetails(m)}
                                variant="table"
                                onAddToDownloads={onAddToDownloads}
                                order={['add', 'details']}
                                className="
                                  flex items-center gap-1
                                  [&_button]:h-7 [&_button]:w-7 [&_button]:min-w-0 [&_button]:p-0
                                  [&_button]:bg-transparent [&_button]:shadow-none
                                  [&_button:hover]:bg-white/20 dark:[&_button:hover]:bg-white/10
                                  [&_button]:border-0
                                  [&_button_svg]:drop-shadow-[0_1px_2px_rgba(0,0,0,0.9)]
                                "
                              />
                            </div>

                            {/* oben rechts: Status-Icons (nur Anzeige, ohne Hintergrund) */}
                            <div className="absolute right-2 top-2 flex items-center gap-1.5 pointer-events-none select-none">
                              {watch ? (
                                <span
                                  className="inline-flex items-center justify-center text-indigo-300 drop-shadow-[0_1px_2px_rgba(0,0,0,0.9)]"
                                  title="Beobachtet"
                                  aria-hidden="true"
                                >
                                  <EyeIconSolid className="size-5" />
                                </span>
                              ) : null}

                              {fav ? (
                                <span
                                  className="inline-flex items-center justify-center text-amber-300 drop-shadow-[0_1px_2px_rgba(0,0,0,0.9)]"
                                  title="Favorit"
                                  aria-hidden="true"
                                >
                                  <StarIconSolid className="size-5" />
                                </span>
                              ) : null}

                              {liked ? (
                                <span
                                  className="inline-flex items-center justify-center text-rose-300 drop-shadow-[0_1px_2px_rgba(0,0,0,0.9)]"
                                  title="Gefällt mir"
                                  aria-hidden="true"
                                >
                                  <HeartIconSolid className="size-5" />
                                </span>
                              ) : null}
                            </div>

                            {/* HOT / KEEP links oben */}
                            {(m.hot || m.keep) && (
                              <div className="absolute left-2 top-2 flex flex-col gap-1">
                                {m.hot ? (
                                  <span className="rounded bg-amber-500/90 px-1.5 py-0.5 text-[10px] font-semibold text-white drop-shadow-[0_1px_2px_rgba(0,0,0,0.9)]">
                                    HOT
                                  </span>
                                ) : null}
                                {m.keep ? (
                                  <span className="rounded bg-indigo-500/90 px-1.5 py-0.5 text-[10px] font-semibold text-white drop-shadow-[0_1px_2px_rgba(0,0,0,0.9)]">
                                    KEEP
                                  </span>
                                ) : null}
                              </div>
                            )}
                          </div>
                        </div>

                        {/* Footer */}
                        <div className="flex-1 rounded-b-xl border-t border-gray-200 bg-gray-50 px-2.5 py-2 dark:border-white/5 dark:bg-slate-800/70">
                          {/* Mobile: kompakter, aber nicht gequetscht */}
                          <div className="sm:hidden">
                            {/* Zeile 1: Actions als Touch-freundliche 3er-Reihe */}
                            <div
                              className="grid grid-cols-3 gap-1.5"
                              onClick={(e) => e.stopPropagation()}
                            >
                              <Button
                                variant={watch ? 'soft' : 'secondary'}
                                size="xs"
                                className={clsx(
                                  'h-8 min-w-0 px-0 shadow-none',
                                  watch
                                    ? 'bg-indigo-50 text-indigo-700 ring-1 ring-indigo-200 shadow-xs hover:bg-indigo-100 dark:bg-indigo-500/20 dark:text-indigo-200 dark:ring-0 dark:shadow-none dark:hover:bg-indigo-500/30'
                                    : 'bg-white text-gray-700 ring-1 ring-gray-200 shadow-none hover:bg-gray-100 dark:bg-white/5 dark:text-slate-200 dark:ring-0 dark:hover:bg-white/10'
                                )}
                                title={watch ? 'Nicht mehr beobachten' : 'Beobachten'}
                                onClick={(e) => {
                                  e.stopPropagation()
                                  patch(m.id, { watched: !watch })
                                }}
                              >
                                <span className="inline-flex items-center justify-center gap-1">
                                  {watch ? (
                                    <EyeIconSolid className="size-5 text-indigo-600 dark:text-indigo-300" />
                                  ) : (
                                    <EyeIconOutline className="size-5 text-gray-500 dark:text-slate-300" />
                                  )}
                                </span>
                              </Button>

                              <Button
                                variant={fav ? 'soft' : 'secondary'}
                                size="xs"
                                className={clsx(
                                  'h-8 min-w-0 px-0 shadow-none',
                                  fav
                                    ? 'bg-amber-50 text-amber-800 ring-1 ring-amber-200 shadow-xs hover:bg-amber-100 dark:bg-amber-500/20 dark:text-amber-200 dark:ring-0 dark:shadow-none dark:hover:bg-amber-500/30'
                                    : 'bg-white text-gray-700 ring-1 ring-gray-200 shadow-none hover:bg-gray-100 dark:bg-white/5 dark:text-slate-200 dark:ring-0 dark:hover:bg-white/10'
                                )}
                                title={fav ? 'Favorit entfernen' : 'Als Favorit markieren'}
                                onClick={(e) => {
                                  e.stopPropagation()
                                  if (fav) patch(m.id, { favorite: false })
                                  else patch(m.id, { favorite: true, liked: false })
                                }}
                              >
                                <span className="inline-flex items-center justify-center gap-1">
                                  {fav ? (
                                    <StarIconSolid className="size-5 text-amber-600 dark:text-amber-300" />
                                  ) : (
                                    <StarIconOutline className="size-5 text-gray-500 dark:text-slate-300" />
                                  )}
                                </span>
                              </Button>

                              <Button
                                variant={liked ? 'soft' : 'secondary'}
                                size="xs"
                                className={clsx(
                                  'h-8 min-w-0 px-0 shadow-none',
                                  liked
                                    ? 'bg-rose-50 text-rose-700 ring-1 ring-rose-200 shadow-xs hover:bg-rose-100 dark:bg-rose-500/20 dark:text-rose-200 dark:ring-0 dark:shadow-none dark:hover:bg-rose-500/30'
                                    : 'bg-white text-gray-700 ring-1 ring-gray-200 shadow-none hover:bg-gray-100 dark:bg-white/5 dark:text-slate-200 dark:ring-0 dark:hover:bg-white/10'
                                )}
                                title={liked ? 'Gefällt mir entfernen' : 'Gefällt mir'}
                                onClick={(e) => {
                                  e.stopPropagation()
                                  if (liked) patch(m.id, { liked: false })
                                  else patch(m.id, { liked: true, favorite: false })
                                }}
                              >
                                <span className="inline-flex items-center justify-center gap-1">
                                  {liked ? (
                                    <HeartIconSolid className="size-5 text-rose-600 dark:text-rose-300" />
                                  ) : (
                                    <HeartIconOutline className="size-5 text-gray-500 dark:text-slate-300" />
                                  )}
                                </span>
                              </Button>
                            </div>

                            {/* Zeile 2: Tags */}
                            <div className="mt-2 min-h-[24px] flex flex-wrap items-start gap-1.5">
                              {shownTags.length > 0 ? (
                                shownTags.map((t) => (
                                  <TagBadge
                                    key={`${m.id}:${t}`}
                                    tag={t}
                                    title={t}
                                    active={activeTagSet.has(t.toLowerCase())}
                                    onClick={toggleTagFilter}
                                  />
                                ))
                              ) : (
                                <span className="opacity-0 select-none pointer-events-none text-[11px]">placeholder</span>
                              )}
                            </div>
                          </div>

                          {/* Desktop: ohne Footer-Stats (wie mobile Buttons) */}
                          <div className="hidden sm:block">
                            {/* Zeile 1: Actions rechts, Style wie mobile */}
                            <div onClick={(e) => e.stopPropagation()}>
                              <div className="grid grid-cols-3 gap-1.5 w-full">
                                <Button
                                  variant={watch ? 'soft' : 'secondary'}
                                  size="xs"
                                  className={clsx(
                                    'h-8 min-w-0 px-0 shadow-none',
                                    hideUntilHover && !watch
                                      ? 'opacity-0 pointer-events-none group-hover:opacity-100 group-hover:pointer-events-auto transition-opacity'
                                      : 'opacity-100',
                                    watch
                                      ? 'bg-indigo-50 text-indigo-700 ring-1 ring-indigo-200 shadow-xs hover:bg-indigo-100 dark:bg-indigo-500/20 dark:text-indigo-200 dark:ring-0 dark:shadow-none dark:hover:bg-indigo-500/30'
                                      : 'bg-white text-gray-700 ring-1 ring-gray-200 shadow-none hover:bg-gray-100 dark:bg-white/5 dark:text-slate-200 dark:ring-0 dark:hover:bg-white/10'
                                  )}
                                  title={watch ? 'Nicht mehr beobachten' : 'Beobachten'}
                                  onClick={(e) => {
                                    e.stopPropagation()
                                    patch(m.id, { watched: !watch })
                                  }}
                                >
                                  <span className="inline-flex items-center justify-center gap-1">
                                    <span className={clsx('text-xl leading-none', watch ? 'text-indigo-600 dark:text-indigo-300' : 'text-gray-500 dark:text-slate-300')}>
                                      👁
                                    </span>
                                  </span>
                                </Button>

                                <Button
                                  variant={fav ? 'soft' : 'secondary'}
                                  size="xs"
                                  className={clsx(
                                    'h-8 min-w-0 px-0 shadow-none',
                                    hideUntilHover && !fav
                                      ? 'opacity-0 pointer-events-none group-hover:opacity-100 group-hover:pointer-events-auto transition-opacity'
                                      : 'opacity-100',
                                    fav
                                      ? 'bg-amber-50 text-amber-800 ring-1 ring-amber-200 shadow-xs hover:bg-amber-100 dark:bg-amber-500/20 dark:text-amber-200 dark:ring-0 dark:shadow-none dark:hover:bg-amber-500/30'
                                      : 'bg-white text-gray-700 ring-1 ring-gray-200 shadow-none hover:bg-gray-100 dark:bg-white/5 dark:text-slate-200 dark:ring-0 dark:hover:bg-white/10'
                                  )}
                                  title={fav ? 'Favorit entfernen' : 'Als Favorit markieren'}
                                  onClick={(e) => {
                                    e.stopPropagation()
                                    if (fav) patch(m.id, { favorite: false })
                                    else patch(m.id, { favorite: true, liked: false })
                                  }}
                                >
                                  <span className="inline-flex items-center justify-center gap-1">
                                    <span className={clsx('text-xl leading-none', fav ? 'text-amber-600 dark:text-amber-300' : 'text-gray-500 dark:text-slate-300')}>
                                      ★
                                    </span>
                                  </span>
                                </Button>

                                <Button
                                  variant={liked ? 'soft' : 'secondary'}
                                  size="xs"
                                  className={clsx(
                                    'h-8 min-w-0 px-0 shadow-none',
                                    hideUntilHover && !liked
                                      ? 'opacity-0 pointer-events-none group-hover:opacity-100 group-hover:pointer-events-auto transition-opacity'
                                      : 'opacity-100',
                                    liked
                                      ? 'bg-rose-50 text-rose-700 ring-1 ring-rose-200 shadow-xs hover:bg-rose-100 dark:bg-rose-500/20 dark:text-rose-200 dark:ring-0 dark:shadow-none dark:hover:bg-rose-500/30'
                                      : 'bg-white text-gray-700 ring-1 ring-gray-200 shadow-none hover:bg-gray-100 dark:bg-white/5 dark:text-slate-200 dark:ring-0 dark:hover:bg-white/10'
                                  )}
                                  title={liked ? 'Gefällt mir entfernen' : 'Gefällt mir'}
                                  onClick={(e) => {
                                    e.stopPropagation()
                                    if (liked) patch(m.id, { liked: false })
                                    else patch(m.id, { liked: true, favorite: false })
                                  }}
                                >
                                  <span className="inline-flex items-center justify-center gap-1">
                                    <span className={clsx('text-xl leading-none', liked ? 'text-rose-600 dark:text-rose-300' : 'text-gray-500 dark:text-slate-300')}>
                                      ♥
                                    </span>
                                  </span>
                                </Button>
                              </div>
                            </div>

                            {/* Zeile 2: Tags (immer vorhanden für gleiche Kartenhöhe) */}
                            <div className="mt-2 min-h-[24px] flex flex-wrap items-start gap-1.5">
                              {shownTags.length > 0 ? (
                                shownTags.map((t) => (
                                  <TagBadge
                                    key={`${m.id}:${t}`}
                                    tag={t}
                                    title={t}
                                    active={activeTagSet.has(t.toLowerCase())}
                                    onClick={toggleTagFilter}
                                  />
                                ))
                              ) : (
                                <span className="opacity-0 select-none pointer-events-none text-[11px]">placeholder</span>
                              )}
                            </div>
                          </div>
                        </div>
                      </div>
                    )
                  })}
                </div>
              )}
            </div>

            <Pagination page={page} pageSize={pageSize} totalItems={totalItems} onPageChange={setPage} />
          </>
        )}
      </Card>

      <Modal
        open={importOpen}
        onClose={() => !importing && setImportOpen(false)}
        title="Models importieren"
        footer={
          <>
            <Button variant="secondary" onClick={() => setImportOpen(false)} disabled={importing}>
              Abbrechen
            </Button>
            <Button
              variant="primary"
              onClick={doImport}
              isLoading={importing}
              disabled={!importFile || importing}
            >
              Import starten
            </Button>
          </>
        }
      >
        <div className="space-y-3">
          <div className="text-sm text-gray-700 dark:text-gray-300">
            Wähle eine CSV-Datei zum Import aus.
          </div>

          <div className="space-y-2">
            <div className="text-sm font-medium text-gray-900 dark:text-white">Import-Typ</div>

            <div className="flex flex-col gap-2 sm:flex-row sm:gap-4">
              <label className="inline-flex items-center gap-2 text-sm text-gray-700 dark:text-gray-300">
                <input
                  type="radio"
                  name="import-kind"
                  value="favorite"
                  checked={importKind === 'favorite'}
                  onChange={() => setImportKind('favorite')}
                  disabled={importing}
                />
                Favoriten (★)
              </label>

              <label className="inline-flex items-center gap-2 text-sm text-gray-700 dark:text-gray-300">
                <input
                  type="radio"
                  name="import-kind"
                  value="liked"
                  checked={importKind === 'liked'}
                  onChange={() => setImportKind('liked')}
                  disabled={importing}
                />
                Gefällt mir (♥)
              </label>
            </div>
          </div>

          <input
            type="file"
            accept=".csv,text/csv"
            onChange={(e) => {
              const f = e.target.files?.[0] ?? null
              setImportErr(null)
              setImportFile(f)
            }}
            className="block w-full text-sm text-gray-700 file:mr-4 file:rounded-md file:border-0 file:bg-gray-100 file:px-3 file:py-2 file:text-sm file:font-semibold file:text-gray-900 hover:file:bg-gray-200 dark:text-gray-200 dark:file:bg-white/10 dark:file:text-white dark:hover:file:bg-white/20"
          />

          {importFile ? (
            <div className="text-xs text-gray-600 dark:text-gray-400">
              Ausgewählt: <span className="font-medium">{importFile.name}</span>
            </div>
          ) : null}

          {importErr ? (
            <div className="text-xs text-red-600 dark:text-red-300">{importErr}</div>
          ) : null}

          {importMsg ? (
            <div className="rounded-md bg-green-50 px-3 py-2 text-xs text-green-700 dark:bg-green-500/10 dark:text-green-200">
              {importMsg}
            </div>
          ) : null}
        </div>
      </Modal>
    </div>
  )
}
