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

'use client'

import * as React from 'react'
import type { RecordJob } from '../../types'
import Modal from './Modal'
import Button from './Button'
import Checkbox from './Checkbox'

type Segment = {
  index: number
  label: string
  start: number
  end: number
  duration: number
  markerTime?: number
}

type Props = {
  open: boolean
  onClose: () => void
  job: RecordJob | null
  videoSrc: string
  timelinePreviewCount?: number
  onApply?: (payload: {
    job: RecordJob
    splits: number[]
    segments: Segment[]
  }) => void | Promise<void>
}

type PreviewSpriteMeta = {
  exists: boolean
  path?: string
  count?: number
  cols?: number
  rows?: number
  stepSeconds?: number
}

type TimelinePreviewTile = {
  index: number
  time: number
  label: string
}

type PreviewClipMap = {
  start: number
  dur: number
  cumStart: number
  cumEnd: number
}

function mapOptionalTime(
  value: unknown,
  timeMapper?: (timeSec: number) => number
): number | undefined {
  const n = Number(value)
  if (!Number.isFinite(n)) return undefined

  const mapped = timeMapper ? Number(timeMapper(n)) : n
  return Number.isFinite(mapped) ? mapped : undefined
}

function extractPreviewClipMap(data: any): PreviewClipMap[] | null {
  let raw =
    data?.meta?.preview?.clips ??
    data?.meta?.preview?.teaser?.clips ??
    data?.preview?.clips ??
    data?.preview?.teaser?.clips ??
    data?.previewClips ??
    data?.teaserClips ??
    null

  if (typeof raw === 'string') {
    try {
      raw = JSON.parse(raw)
    } catch {
      raw = null
    }
  }

  if (!Array.isArray(raw) || raw.length === 0) return null

  let cum = 0
  const out: PreviewClipMap[] = []

  for (const item of raw) {
    const start = Number(
      pickFirstDefined(
        item?.startSeconds,
        item?.StartSeconds,
        item?.start,
        item?.Start
      )
    )

    const dur = Number(
      pickFirstDefined(
        item?.durationSeconds,
        item?.DurationSeconds,
        item?.duration,
        item?.Duration
      )
    )

    if (!Number.isFinite(start) || start < 0) continue
    if (!Number.isFinite(dur) || dur <= 0) continue

    out.push({
      start,
      dur,
      cumStart: cum,
      cumEnd: cum + dur,
    })

    cum += dur
  }

  return out.length > 0 ? out : null
}

function mapPreviewTimeToVideoTime(
  timeSec: number,
  clipMap: PreviewClipMap[]
): number {
  if (!Number.isFinite(timeSec) || clipMap.length === 0) return timeSec

  if (timeSec <= 0) return clipMap[0].start

  const last = clipMap[clipMap.length - 1]
  if (timeSec >= last.cumEnd) {
    return last.start + last.dur
  }

  let lo = 0
  let hi = clipMap.length - 1

  while (lo <= hi) {
    const mid = (lo + hi) >> 1
    const clip = clipMap[mid]

    if (timeSec < clip.cumStart) {
      hi = mid - 1
    } else if (timeSec >= clip.cumEnd) {
      lo = mid + 1
    } else {
      return clip.start + (timeSec - clip.cumStart)
    }
  }

  const idx = clamp(lo, 0, clipMap.length - 1)
  return clipMap[idx].start
}

function maxAiRawTime(ai: any): number {
  if (!ai || typeof ai !== 'object') return 0

  let max = 0

  const collect = (v: unknown) => {
    const n = Number(v)
    if (Number.isFinite(n) && n > max) max = n
  }

  const hits = Array.isArray(ai?.hits ?? ai?.Hits) ? (ai?.hits ?? ai?.Hits) : []
  for (const item of hits) {
    collect(item?.time)
    collect(item?.Time)
    collect(item?.at)
    collect(item?.timestamp)
    collect(item?.start)
    collect(item?.Start)
    collect(item?.startSeconds)
    collect(item?.StartSeconds)
    collect(item?.end)
    collect(item?.End)
    collect(item?.endSeconds)
    collect(item?.EndSeconds)
  }

  const segments = Array.isArray(ai?.segments ?? ai?.Segments) ? (ai?.segments ?? ai?.Segments) : []
  for (const item of segments) {
    collect(item?.start)
    collect(item?.Start)
    collect(item?.startSeconds)
    collect(item?.StartSeconds)
    collect(item?.end)
    collect(item?.End)
    collect(item?.endSeconds)
    collect(item?.EndSeconds)
  }

  return max
}

function shouldMapAiPreviewTimes(
  ai: any,
  clipMap: PreviewClipMap[] | null,
  durationSec: number
): boolean {
  if (!clipMap || clipMap.length === 0) return false
  if (!Number.isFinite(durationSec) || durationSec <= 0) return false

  const previewDuration = clipMap[clipMap.length - 1]?.cumEnd ?? 0
  const aiMax = maxAiRawTime(ai)

  if (!Number.isFinite(previewDuration) || previewDuration <= 0) return false
  if (!Number.isFinite(aiMax) || aiMax <= 0) return false

  // Nur mappen, wenn AI-Zeiten klar auf die Preview-Zeitachse passen
  // und die Preview deutlich kürzer ist als das Vollvideo.
  return aiMax <= previewDuration * 1.05 && previewDuration < durationSec * 0.95
}

function frameIndexFromTime(
  timeSec: number,
  durationSec: number,
  meta: PreviewSpriteMeta
): number {
  const count = Math.max(1, Number(meta.count || 0))
  const stepSeconds = Number(meta.stepSeconds || 0)

  if (count <= 1) return 0

  const spriteCoverageSec =
    stepSeconds > 0
      ? stepSeconds * Math.max(1, count - 1)
      : 0

  const stepLooksUsable =
    stepSeconds > 0 &&
    durationSec > 0 &&
    spriteCoverageSec >= durationSec * 0.7 &&
    spriteCoverageSec <= durationSec * 1.3

  if (stepLooksUsable) {
    const idx = Math.round(timeSec / stepSeconds)
    return clamp(idx, 0, count - 1)
  }

  if (durationSec > 0) {
    const ratio = clamp(timeSec / durationSec, 0, 1)
    return clamp(Math.round(ratio * (count - 1)), 0, count - 1)
  }

  return 0
}

function buildTimelinePreviewTiles(
  durationSec: number,
  meta: PreviewSpriteMeta,
  visibleCount: number
): TimelinePreviewTile[] {
  const count = Math.max(1, Number(meta.count || 0))
  if (visibleCount <= 0 || count <= 0) return []

  const tiles = Array.from({ length: visibleCount }, (_, i) => {
    const ratio = visibleCount <= 1 ? 0 : i / (visibleCount - 1)
    const time = durationSec > 0 ? ratio * durationSec : 0

    return {
      index: frameIndexFromTime(time, durationSec, meta),
      time,
      label: formatTime(time),
    }
  })

  for (let i = 1; i < tiles.length; i++) {
    if (tiles[i].index < tiles[i - 1].index) {
      tiles[i].index = tiles[i - 1].index
    }
  }

  return tiles
}

function spriteSheetImageStyle(
  meta: PreviewSpriteMeta,
  frameIndex: number
): React.CSSProperties {
  const cols = Math.max(1, Number(meta.cols || 1))
  const rows = Math.max(1, Number(meta.rows || 1))
  const count = Math.max(1, Number(meta.count || cols * rows))

  const idx = clamp(frameIndex, 0, count - 1)
  const col = idx % cols
  const row = Math.floor(idx / cols)

  return {
    position: 'absolute',
    left: `${-col * 100}%`,
    top: `${-row * 100}%`,
    width: `${cols * 100}%`,
    height: `${rows * 100}%`,
    maxWidth: 'none',
    pointerEvents: 'none',
    userSelect: 'none',
    objectFit: 'fill',
  }
}

function spriteFrameImgSrc(meta: PreviewSpriteMeta): string {
  return String(meta.path || '').trim()
}

function cn(...parts: Array<string | false | null | undefined>) {
  return parts.filter(Boolean).join(' ')
}

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

function baseName(p: string) {
  return (p || '').replaceAll('\\', '/').trim().split('/').pop() || ''
}

function stripHotPrefix(name: string) {
  return name.startsWith('HOT ') ? name.slice(4) : name
}

function formatTime(totalSeconds: number): string {
  const s = Math.max(0, Math.floor(totalSeconds))
  const h = Math.floor(s / 3600)
  const m = Math.floor((s % 3600) / 60)
  const sec = s % 60

  if (h > 0) {
    return `${h}:${String(m).padStart(2, '0')}:${String(sec).padStart(2, '0')}`
  }
  return `${m}:${String(sec).padStart(2, '0')}`
}

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

function formatDateTime(v?: string | number | Date | null): string {
  if (!v) return '—'
  const d = v instanceof Date ? v : new Date(v)
  const t = d.getTime()
  if (!Number.isFinite(t)) return '—'
  return d.toLocaleString(undefined, {
    year: 'numeric',
    month: '2-digit',
    day: '2-digit',
    hour: '2-digit',
    minute: '2-digit',
  })
}

function pickNum(...vals: any[]): number | null {
  for (const v of vals) {
    const n = typeof v === 'string' ? Number(v) : v
    if (typeof n === 'number' && Number.isFinite(n) && n > 0) return n
  }
  return null
}

function formatFps(n?: number | null): string {
  if (!n || !Number.isFinite(n)) return '—'
  const digits = n >= 10 ? 0 : 2
  return `${n.toFixed(digits)} fps`
}

function formatResolution(h?: number | null, w?: number | null): string {
  if (w && h && Number.isFinite(w) && Number.isFinite(h)) return `${Math.round(w)}×${Math.round(h)}`
  if (h && Number.isFinite(h)) return `${Math.round(h)}p`
  return '—'
}

const reModel = /^(.*?)_\d{1,2}_\d{1,2}_\d{4}__\d{1,2}-\d{2}-\d{2}/

function modelNameFromOutput(output?: string) {
  const fileRaw = baseName(output || '')
  const file = stripHotPrefix(fileRaw)
  if (!file) return '—'

  const stem = file.replace(/\.[^.]+$/, '')
  const m = stem.match(reModel)
  if (m?.[1]) return m[1]

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

function parseDateFromOutput(output?: string): Date | null {
  const fileRaw = baseName(output || '')
  const file = stripHotPrefix(fileRaw)
  if (!file) return null

  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) return null

  const mm = Number(m[1])
  const dd = Number(m[2])
  const yyyy = Number(m[3])
  const hh = Number(m[4])
  const mi = Number(m[5])
  const ss = Number(m[6])

  if (![mm, dd, yyyy, hh, mi, ss].every((n) => Number.isFinite(n))) return null
  return new Date(yyyy, mm - 1, dd, hh, mi, ss)
}

function previewIdFromJob(job: RecordJob | null): string {
  if (!job) return ''
  const file = baseName(job.output || '')
  const stem = stripHotPrefix(file.replace(/\.[^.]+$/, '')).trim()
  return String(job.status).toLowerCase() === 'running' ? String(job.id || '') : stem || String(job.id || '')
}

function previewStillSrcFromJob(job: RecordJob | null): string {
  const id = previewIdFromJob(job)
  if (!id) return ''
  return `/api/preview?id=${encodeURIComponent(id)}&file=preview.jpg`
}

function previewSpritesSrcFromJob(job: RecordJob | null): string {
  const id = previewIdFromJob(job)
  if (!id) return ''
  return `/api/preview-sprite/${encodeURIComponent(id)}`
}

function buildSegmentPreviewSrc(job: RecordJob | null, seg: Segment): string {
  const id = previewIdFromJob(job)
  if (!id) return ''

  const t = Number(seg.start || 0)
  const safeTime = Math.max(0, Number.isFinite(t) ? t : 0)

  return `/api/preview?id=${encodeURIComponent(id)}&t=${encodeURIComponent(safeTime.toFixed(3))}`
}

const AUTO_SELECTED_AI_LABELS = new Set([
  'anus_exposed',
  'female_genitalia_exposed',
  'male_genitalia_exposed',
  'female_breast_exposed',
  'buttocks_exposed',
])

const AI_SEGMENT_PADDING_SECONDS = 3

function pickFirstDefined<T = any>(...vals: T[]): T | undefined {
  for (const v of vals) {
    if (v !== undefined && v !== null) return v
  }
  return undefined
}

function extractAiPayload(data: any): any {
  if (!data || typeof data !== 'object') return null

  const candidates = [
    data?.meta?.analysis?.ai,
    data?.meta?.analysis?.AI,
    data?.analysis?.ai,
    data?.analysis?.AI,
    data?.ai,
    data?.AI,
    data?.videoAI,
    data?.videoAi,
    data?.meta?.ai,
    data?.meta?.AI,
    data?.meta?.videoAI,
    data?.meta?.videoAi,
    data?.metaJson?.analysis?.ai,
    data?.metaJson?.ai,
    data?.metadata?.analysis?.ai,
    data?.metadata?.ai,
  ]

  for (const candidate of candidates) {
    if (candidate && typeof candidate === 'object') {
      return candidate
    }
  }

  return null
}

function extractPreviewSpriteMeta(data: any): PreviewSpriteMeta | null {
  const ps =
    data?.meta?.preview?.sprite ??
    data?.preview?.sprite ??
    data?.previewSprite ??
    null

  if (!ps || typeof ps !== 'object') return null

  const path = String(ps.path || '').trim()
  const count = Number(ps.count || 0)
  const cols = Number(ps.cols || 0)
  const rows = Number(ps.rows || 0)
  const stepSeconds = Number(ps.stepSeconds || 0)

  if (!path || count <= 0 || cols <= 0 || rows <= 0) {
    return null
  }

  return {
    exists: true,
    path,
    count,
    cols,
    rows,
    stepSeconds: Number.isFinite(stepSeconds) && stepSeconds > 0 ? stepSeconds : 0,
  }
}

function extractMetaDuration(data: any): number {
  const d = Number(
    data?.meta?.media?.durationSeconds ??
    data?.media?.durationSeconds ??
    data?.durationSeconds ??
    0
  )

  return Number.isFinite(d) && d > 0 ? d : 0
}

function normalizeAiHits(
  raw: unknown,
  timeMapper?: (timeSec: number) => number
): Array<{
  time: number
  label: string
  score?: number
  start?: number
  end?: number
}> {
  if (!Array.isArray(raw)) return []

  const out: Array<{
    time: number
    label: string
    score?: number
    start?: number
    end?: number
  }> = []

  for (const item of raw) {
    if (!item || typeof item !== 'object') continue

    const x = item as any

    const time = mapOptionalTime(
      pickFirstDefined(
        x.time,
        x.Time,
        x.at,
        x.timestamp,
      ),
      timeMapper
    )

    if (typeof time !== 'number' || !Number.isFinite(time) || time < 0) continue

    const label = String(
      pickFirstDefined(
        x.label,
        x.Label,
        x.name,
        'Treffer'
      ) || 'Treffer'
    ).trim()

    const scoreRaw = Number(
      pickFirstDefined(
        x.score,
        x.Score,
        x.confidence,
      )
    )

    const startRaw = mapOptionalTime(
      pickFirstDefined(
        x.start,
        x.Start,
        x.startSeconds,
        x.StartSeconds,
      ),
      timeMapper
    )

    const endRaw = mapOptionalTime(
      pickFirstDefined(
        x.end,
        x.End,
        x.endSeconds,
        x.EndSeconds,
      ),
      timeMapper
    )

    out.push({
      time,
      label,
      score: Number.isFinite(scoreRaw) ? scoreRaw : undefined,
      start: Number.isFinite(startRaw) ? startRaw : undefined,
      end: Number.isFinite(endRaw) ? endRaw : undefined,
    })
  }

  out.sort((a, b) => a.time - b.time)
  return out
}

function normalizeAiSegmentsFromMeta(
  raw: unknown,
  durationSec?: number,
  timeMapper?: (timeSec: number) => number
): Segment[] {
  if (!Array.isArray(raw)) return []

  const hasDuration =
    typeof durationSec === 'number' &&
    Number.isFinite(durationSec) &&
    durationSec > 0

  const out: Segment[] = []

  for (const item of raw) {
    if (!item || typeof item !== 'object') continue

    const x = item as any

    const mappedStart = mapOptionalTime(
      pickFirstDefined(
        x.startSeconds,
        x.StartSeconds,
        x.start,
        x.Start,
      ),
      timeMapper
    )

    const mappedEnd = mapOptionalTime(
      pickFirstDefined(
        x.endSeconds,
        x.EndSeconds,
        x.end,
        x.End,
      ),
      timeMapper
    )

    if (
      typeof mappedStart !== 'number' ||
      !Number.isFinite(mappedStart) ||
      typeof mappedEnd !== 'number' ||
      !Number.isFinite(mappedEnd)
    ) {
      continue
    }

    let startBase = mappedStart
    let endBase = mappedEnd

    if (endBase < startBase) {
      const tmp = startBase
      startBase = endBase
      endBase = tmp
    }

    const rawDuration = Math.max(0, endBase - startBase)
    const markerTime = startBase + rawDuration / 2

    let start = startBase - AI_SEGMENT_PADDING_SECONDS
    let end = endBase + AI_SEGMENT_PADDING_SECONDS

    if (hasDuration) {
      start = clamp(start, 0, durationSec!)
      end = clamp(end, 0, durationSec!)
    } else {
      start = Math.max(0, start)
      end = Math.max(0, end)
    }

    if (end <= start) continue

    const label = String(
      pickFirstDefined(
        x.label,
        x.Label,
        x.name,
        'Segment'
      ) || 'Segment'
    ).trim()

    out.push({
      index: out.length + 1,
      label,
      start,
      end,
      duration: Math.max(0, end - start),
      markerTime,
    })
  }

  out.sort((a, b) => {
    if ((a.markerTime ?? a.start) !== (b.markerTime ?? b.start)) {
      return (a.markerTime ?? a.start) - (b.markerTime ?? b.start)
    }
    if (a.start !== b.start) return a.start - b.start
    if (a.end !== b.end) return a.end - b.end
    return a.label.localeCompare(b.label)
  })

  return out.map((seg, idx) => ({
    ...seg,
    index: idx + 1,
  }))
}

function normalizeAiSegmentsFromHits(
  raw: unknown,
  durationSec?: number,
  timeMapper?: (timeSec: number) => number
): Segment[] {
  const hits = normalizeAiHits(raw, timeMapper)
  if (hits.length === 0) return []

  const hasDuration =
    typeof durationSec === 'number' &&
    Number.isFinite(durationSec) &&
    durationSec > 0

  const windows = hits
    .map((hit) => {
      const markerTime = Number(hit.time)

      if (!Number.isFinite(markerTime)) return null

      let start = markerTime - AI_SEGMENT_PADDING_SECONDS
      let end = markerTime + AI_SEGMENT_PADDING_SECONDS

      if (hasDuration) {
        start = clamp(start, 0, durationSec!)
        end = clamp(end, 0, durationSec!)
      } else {
        start = Math.max(0, start)
        end = Math.max(0, end)
      }

      if (end <= start) return null

      return {
        start,
        end,
        markerTime,
        labels: [String(hit.label || 'Segment').trim() || 'Segment'],
      }
    })
    .filter(Boolean) as Array<{
      start: number
      end: number
      markerTime: number
      labels: string[]
    }>

  if (windows.length === 0) return []

  windows.sort((a, b) => a.markerTime - b.markerTime)

  const merged: Array<{
    start: number
    end: number
    markerTime: number
    labels: string[]
  }> = []

  for (const win of windows) {
    const last = merged[merged.length - 1]

    if (!last || win.start > last.end) {
      merged.push({
        start: win.start,
        end: win.end,
        markerTime: win.markerTime,
        labels: [...win.labels],
      })
      continue
    }

    last.end = Math.max(last.end, win.end)
    for (const label of win.labels) {
      if (!last.labels.includes(label)) {
        last.labels.push(label)
      }
    }
  }

  return merged.map((seg, idx) => {
    const label =
      seg.labels.length <= 1
        ? seg.labels[0]
        : `${seg.labels[0]} +${seg.labels.length - 1}`

    return {
      index: idx + 1,
      label,
      start: seg.start,
      end: seg.end,
      duration: Math.max(0, seg.end - seg.start),
      markerTime: seg.markerTime,
    }
  })
}

function shouldAutoSelectAiHit(hit: {
  label: string
}): boolean {
  return AUTO_SELECTED_AI_LABELS.has(String(hit.label || '').trim().toLowerCase())
}

export default function VideoSplitModal({
  open,
  onClose,
  job,
  videoSrc,
  timelinePreviewCount = 10,
  onApply,
}: Props) {
  const videoRef = React.useRef<HTMLVideoElement | null>(null)
  const timelineRef = React.useRef<HTMLDivElement | null>(null)

  const [mediaDuration, setMediaDuration] = React.useState(0)
  const [duration, setDuration] = React.useState(0)
  const [currentTime, setCurrentTime] = React.useState(0)
  const [playing, setPlaying] = React.useState(false)
  const [splits, setSplits] = React.useState<number[]>([])
  const [busy, setBusy] = React.useState(false)
  const [aiError, setAiError] = React.useState('')
  const [spriteMeta, setSpriteMeta] = React.useState<PreviewSpriteMeta | null>(null)
  const [hoverRatio, setHoverRatio] = React.useState<number | null>(null)
  const [selectedSegmentIndices, setSelectedSegmentIndices] = React.useState<number[]>([])
  const [aiSegments, setAiSegments] = React.useState<Segment[]>([])
  const [aiHits, setAiHits] = React.useState<Array<{
    time: number
    label: string
    score?: number
    start?: number
    end?: number
  }>>([])

  React.useEffect(() => {
    if (!open) return
    setCurrentTime(0)
    setDuration(0)
    setMediaDuration(0)
    setPlaying(false)
    setSplits([])
    setSelectedSegmentIndices([])
    setAiSegments([])
    setAiHits([])
    setAiError('')
  }, [open, job?.id])

  React.useEffect(() => {
    const video = videoRef.current
    if (!video || !open) return

    const onLoaded = () => {
      const d = Number(video.duration || 0)
      setDuration(Number.isFinite(d) ? d : 0)
    }

    const onTime = () => {
      setCurrentTime(video.currentTime || 0)
    }

    const onPlay = () => setPlaying(true)
    const onPause = () => setPlaying(false)

    video.addEventListener('loadedmetadata', onLoaded)
    video.addEventListener('durationchange', onLoaded)
    video.addEventListener('timeupdate', onTime)
    video.addEventListener('play', onPlay)
    video.addEventListener('pause', onPause)

    return () => {
      video.removeEventListener('loadedmetadata', onLoaded)
      video.removeEventListener('durationchange', onLoaded)
      video.removeEventListener('timeupdate', onTime)
      video.removeEventListener('play', onPlay)
      video.removeEventListener('pause', onPause)
    }
  }, [open, videoSrc])

  React.useEffect(() => {
    if (!open || !job) {
      setSpriteMeta(null)
      setAiHits([])
      setAiSegments([])
      setMediaDuration(0)
      return
    }

    const file = baseName(job.output || '')
    if (!file) {
      setSpriteMeta(null)
      setAiHits([])
      setAiSegments([])
      setMediaDuration(0)
      setSplits([])
      return
    }

    let cancelled = false

    ;(async () => {
      try {
        const res = await fetch(`/api/record/done/meta?file=${encodeURIComponent(file)}`, {
          cache: 'no-store',
        })
        if (!res.ok) {
          if (!cancelled) {
            setSpriteMeta(null)
            setMediaDuration(0)
          }
          return
        }

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

        const ps = extractPreviewSpriteMeta(data)
        const ai = extractAiPayload(data)
        const clipMap = extractPreviewClipMap(data)

        const metaDuration = extractMetaDuration(data)
        const resolvedDuration = metaDuration > 0 ? metaDuration : 0

        const shouldMapPreviewAi =
          Boolean(shouldMapAiPreviewTimes(ai, clipMap, resolvedDuration) && clipMap)

        const mapAiTimes =
          shouldMapPreviewAi && clipMap
            ? (timeSec: number) => mapPreviewTimeToVideoTime(timeSec, clipMap)
            : undefined

        const metaAiHits = normalizeAiHits(
          ai?.hits ?? ai?.Hits ?? [],
          mapAiTimes
        )

        const metaAiSegmentsFromMeta = normalizeAiSegmentsFromMeta(
          ai?.segments ?? ai?.Segments ?? [],
          resolvedDuration,
          mapAiTimes
        )

        const metaAiSegments =
          metaAiSegmentsFromMeta.length > 0
            ? metaAiSegmentsFromMeta
            : normalizeAiSegmentsFromHits(
                ai?.hits ?? ai?.Hits ?? [],
                resolvedDuration,
                mapAiTimes
              )

        if (!cancelled) {
          setMediaDuration(metaDuration > 0 ? metaDuration : 0)
          setAiHits(metaAiHits)
          setAiSegments(metaAiSegments)

          if (metaAiSegments.length > 0) {
            setSplits([])
          }

          setSpriteMeta(ps)
        }
      } catch {
        if (!cancelled) {
          setSpriteMeta(null)
          setMediaDuration(0)
        }
      }
    })()

    return () => {
      cancelled = true
    }
  }, [open, job?.id, job?.output])

  const effectiveDuration = mediaDuration > 0 ? mediaDuration : duration

  const updateHoverFromClientX = React.useCallback((clientX: number) => {
    const el = timelineRef.current
    if (!el) return

    const rect = el.getBoundingClientRect()
    if (rect.width <= 0) return

    const ratio = clamp((clientX - rect.left) / rect.width, 0, 1)
    setHoverRatio(ratio)
  }, [])

  const onTimelinePointerMove = React.useCallback(
    (e: React.PointerEvent<HTMLDivElement>) => {
      updateHoverFromClientX(e.clientX)
    },
    [updateHoverFromClientX]
  )

  const onTimelinePointerEnter = React.useCallback(
    (e: React.PointerEvent<HTMLDivElement>) => {
      updateHoverFromClientX(e.clientX)
    },
    [updateHoverFromClientX]
  )

  const onTimelinePointerLeave = React.useCallback(() => {
    setHoverRatio(null)
  }, [])

  const seekTo = React.useCallback(
    (next: number) => {
      const video = videoRef.current
      if (!video) return
      const clamped = clamp(next, 0, Math.max(0, effectiveDuration || 0))
      video.currentTime = clamped
      setCurrentTime(clamped)
    },
    [effectiveDuration]
  )

  const togglePlayback = React.useCallback(async () => {
    const video = videoRef.current
    if (!video) return

    if (video.paused) {
      try {
        await video.play()
      } catch {
        // ignore
      }
    } else {
      video.pause()
    }
  }, [])

  const addSplitAt = React.useCallback(
    (rawTime: number) => {
      const d = effectiveDuration || 0
      if (!Number.isFinite(d) || d <= 0) return

      const t = clamp(rawTime, 0, d)

      if (t <= 0.2 || t >= d - 0.2) return

      setAiSegments([])
      setSplits((prev) => {
        const exists = prev.some((x) => Math.abs(x - t) < 0.2)
        if (exists) return prev
        return [...prev, t].sort((a, b) => a - b)
      })
    },
    [effectiveDuration]
  )

  const removeSplit = React.useCallback((time: number) => {
    setAiSegments([])
    setSplits((prev) => prev.filter((x) => x !== time))
  }, [])

  const clearSplits = React.useCallback(() => {
    setAiSegments([])
    setSplits([])
  }, [])

  const onTimelineClick = React.useCallback(
    (e: React.MouseEvent<HTMLDivElement>) => {
      if (!timelineRef.current || effectiveDuration <= 0) return

      const rect = timelineRef.current.getBoundingClientRect()
      const ratio = clamp((e.clientX - rect.left) / rect.width, 0, 1)

      setHoverRatio(ratio)

      const t = ratio * effectiveDuration
      seekTo(t)
    },
    [effectiveDuration, seekTo]
  )

  const manualSegments = React.useMemo<Segment[]>(() => {
    const points = [0, ...splits, effectiveDuration].filter((v) => Number.isFinite(v))
    const out: Segment[] = []

    for (let i = 0; i < points.length - 1; i++) {
      const start = points[i]
      const end = points[i + 1]
      const segDuration = Math.max(0, end - start)

      out.push({
        index: i + 1,
        label: `Segment ${i + 1}`,
        start,
        end,
        duration: segDuration,
      })
    }

    return out
  }, [splits, effectiveDuration])

  const segments = React.useMemo<Segment[]>(() => {
    if (aiSegments.length > 0) {
      return aiSegments.map((seg, idx) => ({
        ...seg,
        index: idx + 1,
      }))
    }

    return manualSegments
  }, [aiSegments, manualSegments])

  const timelinePreviewTiles = React.useMemo(() => {
    if (!spriteMeta?.exists || !spriteMeta.path) return []

    const count = Math.max(1, Number(spriteMeta.count || 0))
    const visibleCount = Math.min(Math.max(1, timelinePreviewCount), count)

    return buildTimelinePreviewTiles(effectiveDuration, spriteMeta, visibleCount)
  }, [spriteMeta, effectiveDuration, timelinePreviewCount])

  const selectedSegments = React.useMemo(() => {
    return segments.filter((seg) => selectedSegmentIndices.includes(seg.index))
  }, [segments, selectedSegmentIndices])

  React.useEffect(() => {
    if (segments.length === 0) {
      setSelectedSegmentIndices([])
      return
    }

    if (aiSegments.length === 0) {
      setSelectedSegmentIndices((prev) => {
        const valid = prev.filter((idx) => segments.some((seg) => seg.index === idx))
        if (valid.length > 0) return valid
        return segments.map((seg) => seg.index)
      })
      return
    }

    const autoMatchedSegmentIndices = segments
      .filter((seg) =>
        aiHits.some((hit) => {
          if (!shouldAutoSelectAiHit(hit)) return false

          const hitStart = hit.start ?? hit.time
          const hitEnd = hit.end ?? hit.time

          return hitEnd >= seg.start && hitStart <= seg.end
        })
      )
      .map((seg) => seg.index)

    if (autoMatchedSegmentIndices.length > 0) {
      setSelectedSegmentIndices(autoMatchedSegmentIndices)
      return
    }

    setSelectedSegmentIndices((prev) => {
      const valid = prev.filter((idx) => segments.some((seg) => seg.index === idx))
      if (valid.length > 0) return valid
      return segments.map((seg) => seg.index)
    })
  }, [segments, aiHits, aiSegments.length])

  const hoverTime = React.useMemo(() => {
    if (hoverRatio == null || effectiveDuration <= 0) return null
    return clamp(hoverRatio, 0, 1) * effectiveDuration
  }, [hoverRatio, effectiveDuration])

  const hoverFrameIndex = React.useMemo(() => {
    if (!spriteMeta?.exists || !spriteMeta.path || hoverTime == null) return null
    return frameIndexFromTime(hoverTime, effectiveDuration, spriteMeta)
  }, [spriteMeta, hoverTime, effectiveDuration])

  const toggleSegmentSelection = React.useCallback((segmentIndex: number) => {
    setSelectedSegmentIndices((prev) => {
      if (prev.includes(segmentIndex)) {
        return prev.filter((x) => x !== segmentIndex)
      }
      return [...prev, segmentIndex].sort((a, b) => a - b)
    })
  }, [])

  const selectAllSegments = React.useCallback(() => {
    setSelectedSegmentIndices(segments.map((seg) => seg.index))
  }, [segments])

  const clearSegmentSelection = React.useCallback(() => {
    setSelectedSegmentIndices([])
  }, [])

  const isSegmentSelected = React.useCallback(
    (segmentIndex: number) => selectedSegmentIndices.includes(segmentIndex),
    [selectedSegmentIndices]
  )

  const currentRatio =
    effectiveDuration > 0
      ? clamp(currentTime / effectiveDuration, 0, 1)
      : 0

  const title = job ? `Video schneiden – ${baseName(job.output || '')}` : 'Video schneiden'

  const previewStillSrc = React.useMemo(() => previewStillSrcFromJob(job), [job])
  const previewSpritesSrc = React.useMemo(() => previewSpritesSrcFromJob(job), [job])

  const anyJob = job as any
  const model = React.useMemo(() => modelNameFromOutput(job?.output), [job?.output])
  const file = React.useMemo(() => stripHotPrefix(baseName(job?.output || '')), [job?.output])

  const videoWidth = pickNum(anyJob?.videoWidth, anyJob?.width, anyJob?.meta?.width)
  const videoHeight = pickNum(anyJob?.videoHeight, anyJob?.height, anyJob?.meta?.height)
  const fps = pickNum(anyJob?.fps, anyJob?.frameRate, anyJob?.meta?.fps, anyJob?.meta?.frameRate)

  const resolutionLabel = formatResolution(videoHeight, videoWidth)
  const fpsLabel = formatFps(fps)
  const sizeLabel = formatBytes(
    pickNum(anyJob?.sizeBytes, anyJob?.fileSizeBytes, anyJob?.bytes, anyJob?.size)
  )

  const dateLabel = React.useMemo(() => {
    const fromName = parseDateFromOutput(job?.output)
    if (fromName) return formatDateTime(fromName)

    return formatDateTime(
      anyJob?.startedAt ??
        anyJob?.endedAt ??
        anyJob?.createdAt ??
        anyJob?.fileCreatedAt ??
        anyJob?.ctime ??
        null
    )
  }, [job?.output, anyJob?.startedAt, anyJob?.endedAt, anyJob?.createdAt, anyJob?.fileCreatedAt, anyJob?.ctime])

  return (
    <Modal
      open={open}
      onClose={onClose}
      title={title}
      width="max-w-[96vw]"
      footer={
        <>
          <Button variant="secondary" onClick={onClose} disabled={busy}>
            Schließen
          </Button>
          <Button
            variant="primary"
            disabled={!job || !videoSrc || busy || selectedSegments.length === 0}
            onClick={async () => {
              if (!job) return
              setBusy(true)
              try {
                await onApply?.({
                  job,
                  splits: aiSegments.length > 0 ? [] : splits,
                  segments: selectedSegments,
                })
                onClose()
              } finally {
                setBusy(false)
              }
            }}
          >
            Ausgewählte Segmente übernehmen
          </Button>
        </>
      }
    >
      <div className="p-4 sm:p-6">
        <div className="grid gap-4 xl:grid-cols-[300px_minmax(0,1fr)_320px]">
          <aside className="rounded-2xl border border-gray-200 bg-white p-4 shadow-sm dark:border-white/10 dark:bg-white/5">
            <div className="space-y-4">
              <div className="overflow-hidden rounded-xl bg-black ring-1 ring-black/10 dark:ring-white/10">
                {previewStillSrc ? (
                  <img
                    src={previewStillSrc}
                    alt=""
                    className="block aspect-video w-full object-contain bg-black"
                    draggable={false}
                  />
                ) : (
                  <div className="grid aspect-video place-items-center bg-black text-sm text-white/70">
                    Kein Preview verfügbar
                  </div>
                )}
              </div>

              <div className="space-y-1">
                <div className="truncate text-lg font-semibold text-gray-900 dark:text-white">{model}</div>
                <div className="break-all text-xs text-gray-500 dark:text-gray-400">{file || '—'}</div>
              </div>

              <div className="grid grid-cols-2 gap-x-3 gap-y-2 text-sm">
                <div className="text-gray-500 dark:text-gray-400">Status</div>
                <div className="font-medium text-gray-900 dark:text-white">{job?.status || '—'}</div>

                <div className="text-gray-500 dark:text-gray-400">Auflösung</div>
                <div className="font-medium text-gray-900 dark:text-white">{resolutionLabel}</div>

                <div className="text-gray-500 dark:text-gray-400">FPS</div>
                <div className="font-medium text-gray-900 dark:text-white">{fpsLabel}</div>

                <div className="text-gray-500 dark:text-gray-400">Laufzeit</div>
                <div className="font-medium text-gray-900 dark:text-white">
                  {effectiveDuration > 0 ? formatTime(effectiveDuration) : '—'}
                </div>

                <div className="text-gray-500 dark:text-gray-400">Größe</div>
                <div className="font-medium text-gray-900 dark:text-white">{sizeLabel}</div>

                <div className="text-gray-500 dark:text-gray-400">Datum</div>
                <div className="font-medium text-gray-900 dark:text-white">{dateLabel}</div>
              </div>
            </div>
          </aside>

          <section className="min-w-0 space-y-4">
            <div className="overflow-hidden rounded-xl bg-black ring-1 ring-black/10 dark:ring-white/10">
              {videoSrc ? (
                <video
                  ref={videoRef}
                  src={videoSrc}
                  controls
                  preload="metadata"
                  className="mx-auto block h-auto max-h-[46vh] w-full rounded-xl bg-black object-contain"
                />
              ) : (
                <div className="grid h-[320px] place-items-center text-sm text-white/70">
                  Kein Video verfügbar
                </div>
              )}
            </div>

            <div className="rounded-2xl border border-gray-200 bg-white p-4 shadow-sm dark:border-white/10 dark:bg-white/5">
              <div className="mb-3 flex flex-wrap items-center gap-2">
                <Button variant="secondary" onClick={() => seekTo(currentTime - 5)} disabled={!videoSrc}>
                  -5s
                </Button>
                <Button variant="primary" onClick={togglePlayback} disabled={!videoSrc}>
                  {playing ? 'Pause' : 'Play'}
                </Button>
                <Button variant="secondary" onClick={() => seekTo(currentTime + 5)} disabled={!videoSrc}>
                  +5s
                </Button>
                <Button variant="secondary" onClick={() => addSplitAt(currentTime)} disabled={!videoSrc || effectiveDuration <= 0}>
                  Split bei aktueller Zeit
                </Button>
                <Button variant="secondary" onClick={clearSplits} disabled={splits.length === 0}>
                  Alle Splits löschen
                </Button>

                <div className="ml-auto text-sm text-gray-600 dark:text-gray-300">
                  {formatTime(currentTime)} / {formatTime(effectiveDuration)}
                </div>
              </div>

              {aiError ? (
                <div className="mb-3 rounded-xl bg-red-50 px-3 py-2 text-sm text-red-700 ring-1 ring-red-200 dark:bg-red-500/10 dark:text-red-200 dark:ring-red-400/20">
                  {aiError}
                </div>
              ) : null}

              <div className="space-y-3">
                <div
                  ref={timelineRef}
                  className="relative h-24 cursor-pointer select-none rounded-xl ring-1 ring-gray-200 dark:ring-white/10"
                  onClick={onTimelineClick}
                  onPointerMove={onTimelinePointerMove}
                  onPointerEnter={onTimelinePointerEnter}
                  onPointerLeave={onTimelinePointerLeave}
                  onPointerDown={(e) => {
                    e.stopPropagation()
                  }}
                  title="Klicken zum Springen"
                >
                  {spriteMeta?.exists && spriteMeta.path && timelinePreviewTiles.length > 0 ? (
                    <div
                      className="absolute inset-0 grid gap-0 bg-black/20"
                      style={{
                        gridTemplateColumns: `repeat(${timelinePreviewTiles.length}, minmax(0, 1fr))`,
                      }}
                      aria-hidden="true"
                    >
                      {timelinePreviewTiles.map((tile, i) => (
                        <div
                          key={`${tile.index}-${i}-${tile.time}`}
                          className="relative h-full overflow-hidden"
                          title={tile.label}
                        >
                          <div className="absolute inset-0 overflow-hidden bg-black">
                            <img
                              src={spriteFrameImgSrc(spriteMeta)}
                              alt=""
                              aria-hidden="true"
                              draggable={false}
                              style={spriteSheetImageStyle(spriteMeta, tile.index)}
                            />
                          </div>
                          <div className="absolute inset-0 bg-black/10 dark:bg-black/20" />
                        </div>
                      ))}
                    </div>
                  ) : previewSpritesSrc ? (
                    <div
                      className="absolute inset-0 bg-left bg-no-repeat opacity-90"
                      style={{
                        backgroundImage: `url("${previewSpritesSrc}")`,
                        backgroundSize: 'auto 100%',
                      }}
                      aria-hidden="true"
                    />
                  ) : (
                    <div className="absolute inset-0 bg-gray-100 dark:bg-white/10" />
                  )}

                  {spriteMeta?.exists && spriteMeta.path && hoverFrameIndex != null && hoverTime != null ? (
                    <div
                      className="pointer-events-none absolute bottom-full z-40 mb-2"
                      style={{
                        left: `clamp(56px, ${(hoverRatio ?? 0) * 100}%, calc(100% - 56px))`,
                        transform: 'translateX(-50%)',
                      }}
                    >
                      <div className="overflow-hidden rounded-lg border border-white/15 bg-black shadow-2xl">
                        <div className="relative h-20 w-36 overflow-hidden bg-black">
                          <div className="absolute inset-0 overflow-hidden bg-black">
                            <img
                              src={spriteFrameImgSrc(spriteMeta)}
                              alt=""
                              aria-hidden="true"
                              draggable={false}
                              style={spriteSheetImageStyle(spriteMeta, hoverFrameIndex)}
                            />
                          </div>
                        </div>

                        <div className="bg-black/85 px-2 py-1 text-center text-[11px] font-medium text-white">
                          {formatTime(hoverTime)}
                        </div>
                      </div>

                      <div className="mx-auto -mt-1 h-2 w-2 rotate-45 border-b border-r border-white/15 bg-black" />
                    </div>
                  ) : null}

                  <div className="pointer-events-none absolute inset-0 bg-white/30 dark:bg-black/30" />

                  <div
                    className="pointer-events-none absolute inset-y-0 left-0 bg-indigo-500/20 dark:bg-indigo-400/20"
                    style={{ width: `${currentRatio * 100}%` }}
                  />

                  {segments.map((seg) => {
                    if (effectiveDuration <= 0) return null

                    const left = (seg.start / effectiveDuration) * 100
                    const width = ((seg.end - seg.start) / effectiveDuration) * 100
                    const markerTime = typeof seg.markerTime === 'number' ? seg.markerTime : seg.start
                    const markerLeft = (markerTime / effectiveDuration) * 100
                    const selected = isSegmentSelected(seg.index)

                    return (
                     <div
                        key={`segment-marker-${seg.index}`}
                        className="pointer-events-none absolute inset-0"
                        title={`${seg.label} · ${formatTime(seg.start)}–${formatTime(seg.end)}`}
                      >
                        {/* Gelber Hintergrund */}
                        <span
                          className={cn(
                            'absolute inset-y-0 rounded-sm',
                            selected ? 'bg-amber-400/20' : 'bg-amber-400/12'
                          )}
                          style={{
                            left: `${left}%`,
                            width: `${Math.max(width, 0.35)}%`,
                          }}
                        />

                        {/* Gelbe Linie */}
                        <span
                          className={cn(
                            'absolute inset-y-0 w-[2px] -translate-x-1/2',
                            selected ? 'bg-amber-400/80' : 'bg-amber-400/35'
                          )}
                          style={{ left: `${markerLeft}%` }}
                        />

                        {/* AI-Label */}
                        <span
                          className={cn(
                            'absolute top-1 -translate-x-1/2 rounded-full px-1.5 py-0.5 text-[10px] font-semibold',
                            selected
                              ? 'bg-amber-400/90 text-black shadow'
                              : 'bg-amber-400/18 text-black/50 shadow-none'
                          )}
                          style={{ left: `${markerLeft}%` }}
                        >
                          AI
                        </span>
                      </div>
                    )
                  })}

                  {aiSegments.length === 0 && splits.map((time) => {
                    const left = effectiveDuration > 0 ? (time / effectiveDuration) * 100 : 0
                    return (
                      <button
                        key={time}
                        type="button"
                        className="absolute top-0 h-full -translate-x-1/2"
                        style={{ left: `${left}%` }}
                        onClick={(e) => {
                          e.preventDefault()
                          e.stopPropagation()
                          seekTo(time)
                        }}
                        title={`Split ${formatTime(time)}`}
                      >
                        <span className="absolute inset-y-0 left-1/2 w-[2px] -translate-x-1/2 bg-rose-500" />
                        <span className="absolute left-1/2 top-1 -translate-x-1/2 rounded-full bg-rose-500 px-1.5 py-0.5 text-[10px] font-semibold text-white shadow">
                          |
                        </span>
                      </button>
                    )
                  })}

                  <div
                    className="pointer-events-none absolute top-0 h-full w-[2px] bg-indigo-600 dark:bg-indigo-300"
                    style={{ left: `${currentRatio * 100}%` }}
                  />

                  <div className="absolute inset-x-0 bottom-0 flex items-center justify-between bg-black/45 px-2 py-1 text-[11px] font-medium text-white">
                    <span>0:00</span>
                    <span>{formatTime(effectiveDuration / 2)}</span>
                    <span>{formatTime(effectiveDuration)}</span>
                  </div>
                </div>

                {aiSegments.length === 0 && (
                  <div className="flex flex-wrap gap-2">
                    {splits.length === 0 ? (
                      <div className="text-sm text-gray-500 dark:text-gray-400">
                        Noch keine Splits gesetzt.
                      </div>
                    ) : (
                      splits.map((time, idx) => (
                        <div
                          key={time}
                          className="inline-flex items-center gap-2 rounded-full bg-gray-100 px-3 py-1.5 text-sm ring-1 ring-gray-200 dark:bg-white/10 dark:ring-white/10"
                        >
                          <button
                            type="button"
                            className="font-medium text-gray-900 hover:underline dark:text-white"
                            onClick={() => seekTo(time)}
                          >
                            Split {idx + 1}: {formatTime(time)}
                          </button>
                          <button
                            type="button"
                            className="text-red-600 hover:underline dark:text-red-300"
                            onClick={() => removeSplit(time)}
                          >
                            entfernen
                          </button>
                        </div>
                      ))
                    )}
                  </div>
                )}
              </div>
            </div>
          </section>

          <aside className="rounded-2xl border border-gray-200 bg-white p-4 shadow-sm dark:border-white/10 dark:bg-white/5">
            <div className="mb-3 space-y-2">
              <div className="flex items-center justify-between gap-2">
                <div className="text-sm font-semibold text-gray-900 dark:text-white">
                  Segmente ({selectedSegments.length}/{segments.length})
                </div>

                {segments.length > 0 ? (
                  <div className="text-xs text-gray-500 dark:text-gray-400">
                    Nur ausgewählte Segmente werden gesplittet
                  </div>
                ) : null}
              </div>

              {segments.length > 0 ? (
                <div className="flex flex-wrap gap-2">
                  <Button variant="secondary" onClick={selectAllSegments}>
                    Alle wählen
                  </Button>
                  <Button variant="secondary" onClick={clearSegmentSelection}>
                    Auswahl aufheben
                  </Button>
                </div>
              ) : null}
            </div>

            <div className="max-h-[62vh] space-y-3 overflow-y-auto pr-1">
              {segments.map((seg) => {
                const selected = isSegmentSelected(seg.index)
                const previewSrc = buildSegmentPreviewSrc(job, seg)

                return (
                  <div
                    key={seg.index}
                    role="button"
                    tabIndex={0}
                    className={cn(
                      'group block w-full overflow-hidden rounded-2xl border text-left transition',
                      selected
                        ? 'border-indigo-400/60 bg-indigo-500/10 shadow-[0_0_0_1px_rgba(99,102,241,0.08)] dark:border-indigo-400/45'
                        : 'border-gray-200 bg-white hover:border-gray-300 dark:border-white/10 dark:bg-white/5 dark:hover:border-white/15'
                    )}
                    onClick={() => toggleSegmentSelection(seg.index)}
                    onKeyDown={(e) => {
                      if (e.key === 'Enter' || e.key === ' ') {
                        e.preventDefault()
                        toggleSegmentSelection(seg.index)
                      }
                    }}
                  >
                    <div className="relative aspect-video overflow-hidden">
                      {previewSrc ? (
                        <img
                          src={previewSrc}
                          alt={seg.label}
                          className="absolute inset-0 h-full w-full object-cover"
                          draggable={false}
                        />
                      ) : (
                        <div className="absolute inset-0 grid place-items-center bg-black text-xs text-white/70">
                          Kein Preview
                        </div>
                      )}

                      <div className="absolute inset-0 bg-gradient-to-t from-black/80 via-black/35 to-black/20" />

                      <div className="absolute inset-0 flex flex-col justify-between p-3">
                        <div className="flex items-start gap-2">
                          <Checkbox
                            checked={selected}
                            onChange={() => toggleSegmentSelection(seg.index)}
                            ariaLabel={`${seg.label.replaceAll('_', ' ')} auswählen`}
                            className="size-5"
                          />

                          <div className="min-w-0 flex-1">
                            <div
                              className="truncate text-sm font-semibold text-white"
                              style={{
                                textShadow:
                                  '0 1px 2px rgba(0,0,0,0.95), 0 0 6px rgba(0,0,0,0.85), 0 0 12px rgba(0,0,0,0.55)',
                              }}
                            >
                              {seg.label.replaceAll('_', ' ')}
                            </div>
                          </div>
                        </div>

                        <div className="flex justify-end">
                          <button
                            type="button"
                            className="rounded-full cursor-pointer select-none bg-black/45 px-2.5 py-1 text-[11px] font-medium text-white backdrop-blur-sm transition hover:bg-black/60"
                            onClick={(e) => {
                              e.preventDefault()
                              e.stopPropagation()
                              seekTo(seg.start)
                            }}
                            title={`Zur Position ${formatTime(seg.start)} springen`}
                          >
                            {formatTime(seg.start)} → {formatTime(seg.end)}
                          </button>
                        </div>
                      </div>
                    </div>
                  </div>
                )
              })}

              {segments.length === 0 ? (
                <div className="rounded-xl bg-gray-50 px-3 py-3 text-sm text-gray-500 ring-1 ring-gray-200 dark:bg-white/5 dark:text-gray-400 dark:ring-white/10">
                  Noch keine Segmente vorhanden.
                </div>
              ) : null}
            </div>
          </aside>
        </div>
      </div>
    </Modal>
  )
}