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

'use client'

import * as React from 'react'
import { TrashIcon, BookmarkSquareIcon } from '@heroicons/react/24/outline'

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

export type SwipeAction = {
  label: React.ReactNode
  className?: string
}

export type SwipeCardProps = {
  children: React.ReactNode

  /** Swipe an/aus (z.B. nur mobile view) */
  enabled?: boolean
  /** blockiert Swipe + Tap */
  disabled?: boolean

  /** Tap ohne Swipe (z.B. Player öffnen) */
  onTap?: () => void

  /**
   * Rückgabe:
   * - true/void => Aktion erfolgreich, Karte fliegt raus (translate offscreen)
   * - false => Aktion fehlgeschlagen => Karte snappt zurück
   */
  onSwipeLeft: () => boolean | void | Promise<boolean | void>
  onSwipeRight: () => boolean | void | Promise<boolean | void>

  /** optionales Styling am äußeren Wrapper */
  className?: string

  /** Action-Bereiche */
  leftAction?: SwipeAction   // standard: Behalten
  rightAction?: SwipeAction  // standard: Löschen

  /** Ab welcher Strecke wird ausgelöst? */
  thresholdPx?: number
  thresholdRatio?: number // Anteil der Kartenbreite, z.B. 0.35

  /** Animation timings */
  snapMs?: number
  commitMs?: number
    /**
   * Swipe soll NICHT starten, wenn der Pointer im unteren Bereich startet.
   * Praktisch für native Video-Controls (Progressbar) beim Inline-Playback.
   * Beispiel: 72 (px) = unterste 72px sind "swipe-frei".
   */
  ignoreFromBottomPx?: number

  /**
   * Optional: CSS-Selector, bei dem Swipe-Start komplett ignoriert wird.
   * (z.B. setze data-swipe-ignore auf Elemente, die eigene Gesten haben)
   */
  ignoreSelector?: string

    /**
   * Optional: CSS-Selector, bei dem ein "Tap" NICHT onTap() auslösen soll.
   * (z.B. Buttons/Inputs innerhalb der Karte)
   */
  tapIgnoreSelector?: string


}

export type SwipeCardHandle = {
  swipeLeft: (opts?: { runAction?: boolean }) => Promise<boolean>
  swipeRight: (opts?: { runAction?: boolean }) => Promise<boolean>
  reset: () => void
}

const SwipeCard = React.forwardRef<SwipeCardHandle, SwipeCardProps>(function SwipeCard(
  {
    children,
    enabled = true,
    disabled = false,
    onTap,
    onSwipeLeft,
    onSwipeRight,
    className,
    leftAction = {
      label: (
        <span className="inline-flex flex-col items-center gap-1 font-semibold leading-tight">
          <BookmarkSquareIcon className="h-6 w-6" aria-hidden="true" />
          <span>Behalten</span>
        </span>
      ),
      className: 'bg-emerald-500/20 text-emerald-800 dark:bg-emerald-500/15 dark:text-emerald-300',
    },
    rightAction = {
      label: (
        <span className="inline-flex flex-col items-center gap-1 font-semibold leading-tight">
          <TrashIcon className="h-6 w-6" aria-hidden="true" />
          <span>Löschen</span>
        </span>
      ),
      className: 'bg-red-500/20 text-red-800 dark:bg-red-500/15 dark:text-red-300',
    },
    //thresholdPx = 120,
    thresholdPx = 180,
    //thresholdRatio = 0.35,
    thresholdRatio = 0.1,
    ignoreFromBottomPx = 72,
    ignoreSelector = '[data-swipe-ignore]',
    snapMs = 180,
    commitMs = 180,
    tapIgnoreSelector = 'button,a,input,textarea,select,video[controls],video[controls] *,[data-tap-ignore]',
  },
  ref
) {

  const cardRef = React.useRef<HTMLDivElement | null>(null)

    // ✅ Perf: dx pro Frame updaten (statt pro Pointer-Move)
  const dxRef = React.useRef(0)
  const rafRef = React.useRef<number | null>(null)

  // ✅ Perf: Threshold einmal pro PointerDown berechnen (kein offsetWidth pro Move)
  const thresholdRef = React.useRef(0)

  const pointer = React.useRef<{
    id: number | null
    x: number
    y: number
    dragging: boolean
    captured: boolean
    tapIgnored: boolean
  }>({ id: null, x: 0, y: 0, dragging: false, captured: false, tapIgnored: false })

  const [dx, setDx] = React.useState(0)
  const [armedDir, setArmedDir] = React.useState<null | 'left' | 'right'>(null)
  const [animMs, setAnimMs] = React.useState<number>(0)

  const reset = React.useCallback(() => {
    // ✅ rAF cleanup
    if (rafRef.current != null) {
      cancelAnimationFrame(rafRef.current)
      rafRef.current = null
    }
    dxRef.current = 0

    setAnimMs(snapMs)
    setDx(0)
    setArmedDir(null)
    window.setTimeout(() => setAnimMs(0), snapMs)
  }, [snapMs])

  const commit = React.useCallback(
    async (dir: 'left' | 'right', runAction: boolean) => {

        if (rafRef.current != null) {
          cancelAnimationFrame(rafRef.current)
          rafRef.current = null
        }

        const el = cardRef.current
        const w = el?.offsetWidth || 360

        // rausfliegen lassen
        setAnimMs(commitMs)
        setArmedDir(dir === 'right' ? 'right' : 'left')
        const outDx = dir === 'right' ? w + 40 : -(w + 40)
        dxRef.current = outDx
        setDx(outDx)

        let ok: boolean | void = true
        if (runAction) {
        try {
            ok = dir === 'right' ? await onSwipeRight() : await onSwipeLeft()
        } catch {
            ok = false
        }
        }

        // wenn Aktion fehlschlägt => zurücksnappen
        if (ok === false) {
        setAnimMs(snapMs)
        setArmedDir(null)
        setDx(0)
        window.setTimeout(() => setAnimMs(0), snapMs)
        return false
        }

        return true
    },
    [commitMs, onSwipeLeft, onSwipeRight, snapMs]
  )

  React.useImperativeHandle(
    ref,
    () => ({
        swipeLeft: (opts) => commit('left', opts?.runAction ?? true),
        swipeRight: (opts) => commit('right', opts?.runAction ?? true),
        reset: () => reset(),
    }),
    [commit, reset]
  )

  return (
    <div className={cn('relative overflow-hidden rounded-lg', className)}>
      {/* Background actions (100% je Richtung, animiert) */}
      <div className="absolute inset-0 pointer-events-none overflow-hidden rounded-lg">
        <div
          className={cn(
            'absolute inset-0 transition-opacity duration-200 ease-out',
            dx === 0 ? 'opacity-0' : 'opacity-100',
            dx > 0 ? leftAction.className : rightAction.className
          )}
        />

        <div
          className={cn(
            'absolute inset-0 flex items-center transition-all duration-200 ease-out'
          )}
          style={{
            transform: `translateX(${Math.max(-24, Math.min(24, dx / 8))}px)`,
            opacity: dx === 0 ? 0 : 1,
            justifyContent: dx > 0 ? 'flex-start' : 'flex-end',
            paddingLeft: dx > 0 ? 16 : 0,
            paddingRight: dx > 0 ? 0 : 16,
          }}
        >
          {dx > 0 ? leftAction.label : rightAction.label}
        </div>
      </div>

      {/* Foreground (moves) */}
      <div
        ref={cardRef}
        className="relative"
        style={{
          // ✅ iOS Fix: kein transform im Idle-Zustand, sonst sind Video-Controls oft nicht tappbar
          transform: dx !== 0 ? `translate3d(${dx}px,0,0)` : undefined,
          transition: animMs ? `transform ${animMs}ms ease` : undefined,
          touchAction: 'pan-y',
          willChange: dx !== 0 ? 'transform' : undefined,
        }}
        onPointerDown={(e) => {
          if (!enabled || disabled) return

          // ✅ 1) Ignoriere Start auf "No-swipe"-Elementen
          const target = e.target as HTMLElement | null
          const tapIgnored = Boolean(tapIgnoreSelector && target?.closest?.(tapIgnoreSelector))

          if (ignoreSelector && target?.closest?.(ignoreSelector)) return

          const root = e.currentTarget as HTMLElement
          const videos = Array.from(root.querySelectorAll('video')) as HTMLVideoElement[]
          const ctlVideo = videos.find((v) => v.controls)

          if (ctlVideo) {
            const vr = ctlVideo.getBoundingClientRect()

            const inVideo =
              e.clientX >= vr.left &&
              e.clientX <= vr.right &&
              e.clientY >= vr.top &&
              e.clientY <= vr.bottom

            if (inVideo) {
              // unten frei für Timeline/Scrub (iPhone braucht meist etwas mehr)
              const fromBottomVideo = vr.bottom - e.clientY
              const scrubZonePx = 72
              if (fromBottomVideo <= scrubZonePx) return

              // Swipe nur aus den Seitenrändern
              const edgeZonePx = 64
              const xFromLeft = e.clientX - vr.left
              const xFromRight = vr.right - e.clientX
              const inEdge = xFromLeft <= edgeZonePx || xFromRight <= edgeZonePx

              if (!inEdge) return
            }
          }

          // ✅ 3) Optional: generelle Card-Bottom-Sperre (bei dir in CardsView auf 0 lassen)
          const rect = (e.currentTarget as HTMLElement).getBoundingClientRect()
          const fromBottom = rect.bottom - e.clientY
          if (ignoreFromBottomPx && fromBottom <= ignoreFromBottomPx) return

          pointer.current = {
            id: e.pointerId,
            x: e.clientX,
            y: e.clientY,
            dragging: false,
            captured: false,
            tapIgnored, // ✅ WICHTIG: nicht "false"
          }

          // ✅ Perf: pro Gesture einmal Threshold berechnen
          const el = cardRef.current
          const w = el?.offsetWidth || 360
          thresholdRef.current = Math.min(thresholdPx, w * thresholdRatio)

          // ✅ dxRef reset (neue Gesture)
          dxRef.current = 0

        }}

        onPointerMove={(e) => {
          if (!enabled || disabled) return
          if (pointer.current.id !== e.pointerId) return

          const ddx = e.clientX - pointer.current.x
          const ddy = e.clientY - pointer.current.y

          // Erst entscheiden ob wir überhaupt draggen
          if (!pointer.current.dragging) {
            // wenn Nutzer vertikal scrollt -> abbrechen, NICHT hijacken
            if (Math.abs(ddy) > Math.abs(ddx) && Math.abs(ddy) > 8) {
              pointer.current.id = null
              return
            }

            // "Dead zone" bis wirklich horizontal gedrückt wird
            if (Math.abs(ddx) < 12) return

            // ✅ jetzt erst beginnen wir zu swipen
            pointer.current.dragging = true

            // ✅ Anim nur 1x beim Drag-Start deaktivieren
            setAnimMs(0)

            // ✅ Pointer-Capture erst JETZT (nicht bei pointerdown)
            try {
              ;(e.currentTarget as HTMLElement).setPointerCapture(e.pointerId)
              pointer.current.captured = true
            } catch {
              pointer.current.captured = false
            }
          }

          // ✅ dx nur pro Frame in React-State schreiben
          dxRef.current = ddx

          if (rafRef.current == null) {
            rafRef.current = requestAnimationFrame(() => {
              rafRef.current = null
              setDx(dxRef.current)
            })
          }

          // ✅ armedDir nur updaten wenn geändert
          const threshold = thresholdRef.current
          const nextDir = ddx > threshold ? 'right' : ddx < -threshold ? 'left' : null
          setArmedDir((prev) => (prev === nextDir ? prev : nextDir))
        }}

        onPointerUp={(e) => {
          if (!enabled || disabled) return
          if (pointer.current.id !== e.pointerId) return

          const threshold = thresholdRef.current || Math.min(thresholdPx, (cardRef.current?.offsetWidth || 360) * thresholdRatio)

          const wasDragging = pointer.current.dragging
          const wasCaptured = pointer.current.captured
          const wasTapIgnored = pointer.current.tapIgnored

          pointer.current.id = null
          pointer.current.dragging = false
          pointer.current.captured = false

          // Capture sauber lösen (falls gesetzt)
          if (wasCaptured) {
            try {
              ;(e.currentTarget as HTMLElement).releasePointerCapture(e.pointerId)
            } catch {}
          }

          if (!wasDragging) {
            // ✅ Wichtig: Wenn Tap auf Video/Controls (tapIgnored), NICHT resetten
            // sonst “stiehlt” SwipeCard den Tap (iOS besonders empfindlich).
            if (wasTapIgnored) {
              setAnimMs(0)
              setDx(0)
              setArmedDir(null)
              return
            }

            reset()
            onTap?.()
            return
          }

          const finalDx = dxRef.current

          // rAF cleanup
          if (rafRef.current != null) {
            cancelAnimationFrame(rafRef.current)
            rafRef.current = null
          }

          if (finalDx > threshold) {
            void commit('right', true)
          } else if (finalDx < -threshold) {
            void commit('left', true)
          } else {
            reset()
          }

          dxRef.current = 0

        }}
        onPointerCancel={(e) => {
          if (!enabled || disabled) return
          if (pointer.current.captured && pointer.current.id != null) {
            try {
              ;(e.currentTarget as HTMLElement).releasePointerCapture(pointer.current.id)
            } catch {}
          }
          pointer.current = { id: null, x: 0, y: 0, dragging: false, captured: false, tapIgnored: false }
          if (rafRef.current != null) {
            cancelAnimationFrame(rafRef.current)
            rafRef.current = null
          }
          dxRef.current = 0

          reset()
        }}
      >
        <div className="relative">
          <div className="relative z-10">{children}</div>

          {/* ✅ Overlay liegt ÜBER dem Inhalt */}
          <div
            className={cn(
              'absolute inset-0 z-20 pointer-events-none transition-opacity duration-150 rounded-lg',
              armedDir === 'right' && 'bg-emerald-500/20 opacity-100',
              armedDir === 'left' && 'bg-red-500/20 opacity-100',
              armedDir === null && 'opacity-0'
            )}
          />
        </div>
      </div>
    </div>
  )
})

export default SwipeCard