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

'use client'

import * as React from 'react'
import { FireIcon as FireSolidIcon } from '@heroicons/react/24/solid'
import { createRoot } from 'react-dom/client'

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

function getGlobalFxLayer(): HTMLDivElement | null {
  if (typeof document === 'undefined') return null

  const ID = '__swipecard_hot_fx_layer__'
  let el = document.getElementById(ID) as HTMLDivElement | null

  if (!el) {
    el = document.createElement('div')
    el.id = ID
    el.style.position = 'fixed'
    el.style.inset = '0'
    el.style.pointerEvents = 'none'
    el.style.zIndex = '2147483647'
    // optional, aber nice:
    ;(el.style as any).contain = 'layout style paint'
    document.body.appendChild(el)
  }

  return el
}


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

    /** Doppeltippen (z.B. HOT togglen). Rückgabe false => Aktion fehlgeschlagen */
  onDoubleTap?: () => boolean | void | Promise<boolean | void>

  /** Wohin soll die Flamme fliegen? (Element innerhalb der Card) */
  hotTargetSelector?: string

  /** Double-Tap Zeitfenster */
  doubleTapMs?: number

  /** Max. Bewegung zwischen taps (px) */
  doubleTapMaxMovePx?: number
}

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,
    thresholdPx = 140,
    thresholdRatio = 0.28,
    ignoreFromBottomPx = 72,
    ignoreSelector = '[data-swipe-ignore]',
    snapMs = 180,
    commitMs = 180,
    tapIgnoreSelector = 'button,a,input,textarea,select,video[controls],video[controls] *,[data-tap-ignore]',
    onDoubleTap,
    hotTargetSelector = '[data-hot-target]',
    doubleTapMs = 360,
    doubleTapMaxMovePx = 48,
  },
  ref
) {
  const cardRef = React.useRef<HTMLDivElement | null>(null)

  const doubleTapBusyRef = React.useRef(false)

  // ✅ 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 outerRef = React.useRef<HTMLDivElement | null>(null)

  const tapTimerRef = React.useRef<number | null>(null)
  const lastTapRef = React.useRef<{ t: number; x: number; y: number } | null>(null)

  const pointer = React.useRef<{
    id: number | null
    x: number
    y: number
    dragging: boolean
    captured: boolean
    tapIgnored: boolean
    noSwipe: boolean
  }>({ id: null, x: 0, y: 0, dragging: false, captured: false, tapIgnored: false, noSwipe: 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
    // ✅ TouchAction zurücksetzen (falls während Drag auf none gestellt)
    if (cardRef.current) cardRef.current.style.touchAction = 'pan-y'

    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
      }

      // ✅ TouchAction zurücksetzen (Sicherheit)
      if (cardRef.current) cardRef.current.style.touchAction = 'pan-y'

      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]
  )

  const clearTapTimer = React.useCallback(() => {
    if (tapTimerRef.current != null) {
      window.clearTimeout(tapTimerRef.current)
      tapTimerRef.current = null
    }
  }, [])

  const softResetForTap = React.useCallback(() => {
    if (rafRef.current != null) {
      cancelAnimationFrame(rafRef.current)
      rafRef.current = null
    }
    dxRef.current = 0
    setAnimMs(0)
    setDx(0)
    setArmedDir(null)
    try {
      const el = cardRef.current
      if (el) el.style.touchAction = 'pan-y'
    } catch {}
  }, [])

  const runHotFx = React.useCallback(
    (clientX?: number, clientY?: number) => {
      const outer = outerRef.current
      const card = cardRef.current
      if (!outer || !card) return

      const layer = getGlobalFxLayer()
      if (!layer) return

      // ✅ Start im Viewport (da wo getippt wurde, fallback: Mitte)
      let startX = typeof clientX === 'number' ? clientX : window.innerWidth / 2
      let startY = typeof clientY === 'number' ? clientY : window.innerHeight / 2

      // Ziel: HOT Button (falls gefunden) – ebenfalls im Viewport
      const targetEl = hotTargetSelector
        ? ((outerRef.current?.querySelector(hotTargetSelector) as HTMLElement | null) ??
          (card.querySelector(hotTargetSelector) as HTMLElement | null))
        : null

      let endX = startX
      let endY = startY
      if (targetEl) {
        const tr = targetEl.getBoundingClientRect()
        endX = tr.left + tr.width / 2
        endY = tr.top + tr.height / 2
      }

      const dx = endX - startX
      const dy = endY - startY

      // Flame node
      const flame = document.createElement('div')
      flame.style.position = 'absolute'
      flame.style.left = `${startX}px`
      flame.style.top = `${startY}px`
      flame.style.transform = 'translate(-50%, -50%)'
      flame.style.pointerEvents = 'none'
      flame.style.willChange = 'transform, opacity'
      flame.style.zIndex = '2147483647'
      flame.style.lineHeight = '1'
      flame.style.userSelect = 'none'
      flame.style.filter = 'drop-shadow(0 10px 16px rgba(0,0,0,0.22))'

      // ✅ Heroicon per React-Root rendern (Client-sicher)
      const inner = document.createElement('div')
      inner.style.width = '30px'
      inner.style.height = '30px'
      inner.style.color = '#f59e0b' // amber
      // optional: damit SVG nicht “inline” komisch sitzt
      inner.style.display = 'block'

      flame.appendChild(inner)
      layer.appendChild(flame)

      const root = createRoot(inner)
      root.render(<FireSolidIcon className="w-full h-full" aria-hidden="true" />)
      
      void flame.getBoundingClientRect()

      // ✅ Timing: Pop (200ms) + Hold (500ms) + Fly (400ms) = 1100ms
      const popMs = 200
      const holdMs = 500
      const flyMs = 400
      const duration = popMs + holdMs + flyMs // 1100

      const tPopEnd = popMs / duration
      const tHoldEnd = (popMs + holdMs) / duration

      const anim = flame.animate(
        [
          // --- POP am Tap-Punkt ---
          { transform: 'translate(-50%, -50%) scale(0.15)', opacity: 0, offset: 0.0 },
          { transform: 'translate(-50%, -50%) scale(1.25)', opacity: 1, offset: tPopEnd * 0.55 },
          { transform: 'translate(-50%, -50%) scale(1.00)', opacity: 1, offset: tPopEnd },

          // --- HOLD (0.5s stehen bleiben) ---
          { transform: 'translate(-50%, -50%) scale(1.00)', opacity: 1, offset: tHoldEnd },

          // --- FLY zum HOT-Button ---
          {
            transform: `translate(calc(-50% + ${dx}px), calc(-50% + ${dy}px)) scale(0.85)`,
            opacity: 0.95,
            offset: tHoldEnd + (1 - tHoldEnd) * 0.75,
          },
          {
            transform: `translate(calc(-50% + ${dx}px), calc(-50% + ${dy}px)) scale(0.55)`,
            opacity: 0,
            offset: 1.0,
          },
        ],
        {
          duration,
          easing: 'cubic-bezier(0.2, 0.9, 0.2, 1)',
          fill: 'forwards',
        }
      )

      // Flash beim Ankommen (am Ende der Fly-Phase)
      if (targetEl) {
        window.setTimeout(() => {
          try {
            targetEl.animate(
              [
                { transform: 'translateZ(0) scale(1)', filter: 'brightness(1)' },
                { transform: 'translateZ(0) scale(1.10)', filter: 'brightness(1.25)', offset: 0.35 },
                { transform: 'translateZ(0) scale(1)', filter: 'brightness(1)' },
              ],
              { duration: 260, easing: 'cubic-bezier(0.2, 0.9, 0.2, 1)' }
            )
          } catch {}
        }, popMs + holdMs + Math.round(flyMs * 0.75))
      }

      anim.onfinish = () => {
        try {
          root.unmount()
        } catch {}
        flame.remove()
      }
    },
    [hotTargetSelector]
  )

  React.useEffect(() => {
    return () => {
      if (tapTimerRef.current != null) window.clearTimeout(tapTimerRef.current)
    }
  }, [])

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

  const absDx = Math.abs(dx)
  const swipeDir: 'left' | 'right' | null = dx === 0 ? null : dx > 0 ? 'right' : 'left'

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

  const reveal = Math.max(0, Math.min(1, absDx / Math.max(1, activeThreshold)))
  const revealSoft = Math.max(0, Math.min(1, absDx / Math.max(1, activeThreshold * 1.35)))

  // leichte Tinder-ähnliche Kippung beim Drag
  const tiltDeg = Math.max(-6, Math.min(6, dx / 28))
  const dragScale = dx === 0 ? 1 : 0.995

  return (
    <div
      ref={outerRef}
      className={cn('relative isolate overflow-visible rounded-lg', className)}
    >
      {/* Foreground (moves) */}
      <div
        ref={cardRef}
        className="relative"
        style={{
          // ✅ iOS Fix: im Idle kein transform
          transform:
            dx !== 0
              ? `translate3d(${dx}px,0,0) rotate(${tiltDeg}deg) scale(${dragScale})`
              : undefined,
          transition: animMs ? `transform ${animMs}ms ease` : undefined,
          touchAction: 'pan-y',
          willChange: dx !== 0 ? 'transform' : undefined,
          boxShadow:
            dx !== 0
              ? swipeDir === 'right'
                ? `0 16px 34px rgba(0,0,0,0.28), 0 0 0 1px rgba(16,185,129,${0.08 + reveal * 0.12})`
                : `0 16px 34px rgba(0,0,0,0.28), 0 0 0 1px rgba(244,63,94,${0.08 + reveal * 0.12})`
              : undefined,
          borderRadius: dx !== 0 ? '12px' : undefined,
          filter:
            dx !== 0
              ? `saturate(${1 + reveal * 0.08}) brightness(${1 + reveal * 0.02})`
              : undefined,
        }}
        onPointerDown={(e) => {
          if (!enabled || disabled) return

          const target = e.target as HTMLElement | null

          // Tap ignorieren (SingleTap nicht auslösen), DoubleTap soll aber weiter gehen
          let tapIgnored = Boolean(tapIgnoreSelector && target?.closest?.(tapIgnoreSelector))

          // Harte Ignore-Zone: da wollen wir wirklich gar nichts
          if (ignoreSelector && target?.closest?.(ignoreSelector)) return

          // Tap/DoubleTap erlauben, aber niemals swipe starten
          let noSwipe = false

          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) {
              const fromBottomVideo = vr.bottom - e.clientY
              const scrubZonePx = 72
              if (fromBottomVideo <= scrubZonePx) {
                noSwipe = true
                tapIgnored = true
              } else {
                const edgeZonePx = 64
                const xFromLeft = e.clientX - vr.left
                const xFromRight = vr.right - e.clientX
                const inEdge = xFromLeft <= edgeZonePx || xFromRight <= edgeZonePx

                if (!inEdge) {
                  noSwipe = true
                  tapIgnored = true
                }
              }
            }
          }

          const rect = (e.currentTarget as HTMLElement).getBoundingClientRect()
          const fromBottom = rect.bottom - e.clientY
          if (ignoreFromBottomPx && fromBottom <= ignoreFromBottomPx) {
            noSwipe = true
          }

          pointer.current = {
            id: e.pointerId,
            x: e.clientX,
            y: e.clientY,
            dragging: false,
            captured: false,
            tapIgnored,
            noSwipe,
          }

          const el = cardRef.current
          const w = el?.offsetWidth || 360
          thresholdRef.current = Math.min(thresholdPx, w * thresholdRatio)
          dxRef.current = 0
        }}
        onPointerMove={(e) => {
          if (!enabled || disabled) return
          if (pointer.current.id !== e.pointerId) return

          if (pointer.current.noSwipe) return

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

          if (!pointer.current.dragging) {
            if (Math.abs(ddy) > Math.abs(ddx) && Math.abs(ddy) > 8) {
              pointer.current.id = null
              return
            }

            if (Math.abs(ddx) < 12) return

            pointer.current.dragging = true
            ;(e.currentTarget as HTMLElement).style.touchAction = 'none'
            setAnimMs(0)

            try {
              ;(e.currentTarget as HTMLElement).setPointerCapture(e.pointerId)
              pointer.current.captured = true
            } catch {
              pointer.current.captured = false
            }
          }

          const applyResistance = (x: number, w: number) => {
            const limit = w * 0.9
            const ax = Math.abs(x)
            if (ax <= limit) return x
            const extra = ax - limit
            const resisted = limit + extra * 0.25
            return Math.sign(x) * resisted
          }

          const w = cardRef.current?.offsetWidth || 360
          dxRef.current = applyResistance(ddx, w)

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

          const threshold = thresholdRef.current
          const nextDir = ddx > threshold ? 'right' : ddx < -threshold ? 'left' : null
          setArmedDir((prev) => {
            if (prev === nextDir) return prev
            if (nextDir) {
              try {
                navigator.vibrate?.(10)
              } catch {}
            }
            return 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

          if (wasCaptured) {
            try {
              ;(e.currentTarget as HTMLElement).releasePointerCapture(e.pointerId)
            } catch {}
          }

          ;(e.currentTarget as HTMLElement).style.touchAction = 'pan-y'

          if (!wasDragging) {
            const now = Date.now()
            const last = lastTapRef.current

            const isNear =
              last &&
              Math.hypot(e.clientX - last.x, e.clientY - last.y) <= doubleTapMaxMovePx

            const isDouble =
              Boolean(onDoubleTap) &&
              last &&
              now - last.t <= doubleTapMs &&
              isNear

            if (isDouble) {
              lastTapRef.current = null
              clearTapTimer()

              if (doubleTapBusyRef.current) return
              doubleTapBusyRef.current = true

              try {
                runHotFx(e.clientX, e.clientY)
              } catch {}

              requestAnimationFrame(() => {
                ;(async () => {
                  try {
                    await onDoubleTap?.()
                  } finally {
                    doubleTapBusyRef.current = false
                  }
                })()
              })

              return
            }

            if (wasTapIgnored) {
              lastTapRef.current = { t: now, x: e.clientX, y: e.clientY }
              clearTapTimer()
              tapTimerRef.current = window.setTimeout(() => {
                tapTimerRef.current = null
                lastTapRef.current = null
              }, onDoubleTap ? doubleTapMs : 0)
              return
            }

            softResetForTap()

            lastTapRef.current = { t: now, x: e.clientX, y: e.clientY }
            clearTapTimer()

            tapTimerRef.current = window.setTimeout(() => {
              tapTimerRef.current = null
              lastTapRef.current = null
              onTap?.()
            }, onDoubleTap ? doubleTapMs : 0)

            return
          }

          const finalDx = dxRef.current

          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, noSwipe: false }
          if (rafRef.current != null) {
            cancelAnimationFrame(rafRef.current)
            rafRef.current = null
          }
          dxRef.current = 0

          try {
            ;(e.currentTarget as HTMLElement).style.touchAction = 'pan-y'
          } catch {}

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

          {/* Overlay über dem Inhalt (subtiler, progressiv) */}
          <div
            className="absolute inset-0 z-20 pointer-events-none rounded-lg transition-opacity duration-100"
            style={{
              opacity: dx === 0 ? 0 : 0.12 + revealSoft * 0.18,
              background:
                swipeDir === 'right'
                  ? 'linear-gradient(90deg, rgba(16,185,129,0.16) 0%, rgba(16,185,129,0.04) 45%, rgba(0,0,0,0) 100%)'
                  : swipeDir === 'left'
                    ? 'linear-gradient(270deg, rgba(244,63,94,0.16) 0%, rgba(244,63,94,0.04) 45%, rgba(0,0,0,0) 100%)'
                    : 'transparent',
            }}
          />

          {/* Armed border glow */}
          <div
            className="absolute inset-0 z-20 pointer-events-none rounded-lg transition-opacity duration-100"
            style={{
              opacity: armedDir ? 1 : 0,
              boxShadow:
                armedDir === 'right'
                  ? 'inset 0 0 0 1px rgba(16,185,129,0.45), inset 0 0 32px rgba(16,185,129,0.10)'
                  : armedDir === 'left'
                    ? 'inset 0 0 0 1px rgba(244,63,94,0.45), inset 0 0 32px rgba(244,63,94,0.10)'
                    : 'none',
            }}
          />
        </div>
      </div>
    </div>
  )
})

export default SwipeCard
