// /src/app/[locale]/components/TeamMemberView.tsx
'use client'

import { useEffect, useMemo, useRef, useState } from 'react'
import {
  DndContext,
  closestCenter,
  DragOverlay,
  type DragStartEvent,
  type DragEndEvent,
} from '@dnd-kit/core'
import { SortableContext, verticalListSortingStrategy } from '@dnd-kit/sortable'
import { DroppableZone } from './DroppableZone'
import MiniCard from './MiniCard'
import MiniCardDummy from './MiniCardDummy'
import SortableMiniCard from './SortableMiniCard'
import LeaveTeamModal from './LeaveTeamModal'
import InvitePlayersModal from './InvitePlayersModal'
import Modal from './Modal'
import type { Player, InvitedPlayer, Team, TeamJoinPolicy } from '@/types/team'
import { AnimatePresence, motion } from 'framer-motion'
import { leaveTeam, reloadTeam, renameTeam } from '@/lib/sse-actions'
import Button from './Button'
import NextImage from 'next/image'
import TeamPremierRankBadge from './TeamPremierRankBadge'
import Link from 'next/link'
import { useTeamStore } from '@/lib/stores'
import { useSSEStore } from '@/lib/useSSEStore'
import {
  TEAM_EVENTS,
  SELF_EVENTS,
  isSseEventType,
  type SSEEventType,
} from '@/lib/sseEvents'

type Props = {
  team?: Team
  activeDragItem: Player | null
  isDragging: boolean
  showLeaveModal: boolean
  showInviteModal: boolean
  currentUserSteamId: string
  setShowLeaveModal: (v: boolean) => void
  setShowInviteModal: (v: boolean) => void
  setActiveDragItem: (item: Player | null) => void
  setIsDragging: (v: boolean) => void
  adminMode?: boolean
}

export default function TeamMemberView(props: Props) {
  const { team: storeTeam, setTeam } = useTeamStore()

  // Prop -> Store spiegeln
  useEffect(() => {
    if (!props.team) return
    const curr = useTeamStore.getState().team
    if (!curr || curr.id !== props.team.id) {
      setTeam(props.team as Team)
      return
    }
    // gleiche ID → selektiv patchen
    const next = props.team as Team
    const diff: Partial<Team> = {}
    if (curr.name !== next.name) diff.name = next.name
    if (curr.logo !== next.logo) diff.logo = next.logo
    if ((curr.leader?.steamId ?? null) !== (next.leader?.steamId ?? null)) diff.leader = next.leader
    if (typeof next.joinPolicy === 'string' && curr.joinPolicy !== next.joinPolicy) {
      diff.joinPolicy = next.joinPolicy as TeamJoinPolicy
    }
    if (Object.keys(diff).length) setTeam({ ...curr, ...diff } as Team)
  }, [props.team, setTeam])

  if (!props.adminMode && !props.currentUserSteamId) return null

  const team = props.team ?? storeTeam ?? null
  if (!team) return null

  return <TeamMemberViewBody {...props} team={team} />
}

function TeamMemberViewBody({
  team,
  activeDragItem,
  isDragging,
  showLeaveModal,
  showInviteModal,
  currentUserSteamId,
  setShowLeaveModal,
  setShowInviteModal,
  setActiveDragItem,
  setIsDragging,
  adminMode = false,
}: Props & { team: Team }) {
  const { setTeam } = useTeamStore()

  const teamId = team.id
  const teamLeaderSteamId = team.leader?.steamId ?? ''

  // stabile Menge für useEffect-Deps
  const RELEVANT = useMemo<ReadonlySet<SSEEventType>>(
    () => new Set([...TEAM_EVENTS, ...SELF_EVENTS]),
    []
  )

  const isLeader     = currentUserSteamId === team.leader?.steamId
  const canManage    = adminMode || isLeader
  const canInvite    = isLeader && !adminMode
  const canAddDirect = adminMode

  const isDraggingRef = useRef(false)
  const [pendingRemote, setPendingRemote] = useState<{
    active: Player[]
    inactive: Player[]
    invited: InvitedPlayer[]
  } | null>(null)
  const [remountKey, setRemountKey] = useState(0)
  const { connect, lastEvent, isConnected } = useSSEStore()
  const [activePlayers, setActivePlayers] = useState<Player[]>([])
  const [inactivePlayers, setInactivePlayers] = useState<Player[]>([])
  const [invitedPlayers, setInvitedPlayers] = useState<InvitedPlayer[]>([])
  const [kickCandidate, setKickCandidate] = useState<Player | null>(null)
  const [promoteCandidate, setPromoteCandidate] = useState<Player | null>(null)
  const [showDeleteModal, setShowDeleteModal] = useState(false)
  const [isEditingName, setIsEditingName] = useState(false)
  const [editedName, setEditedName] = useState(team.name || '')
  const [saveSuccess, setSaveSuccess] = useState(false)

  const [joinPolicy, setJoinPolicy] = useState<TeamJoinPolicy>(
    (team.joinPolicy as TeamJoinPolicy) ?? 'REQUEST'
  )
  const policyChangedAtRef = useRef<number | null>(null)
  const [savingPolicy, setSavingPolicy] = useState(false)
  const [policySaved, setPolicySaved] = useState(false)

  const [inviteKey, setInviteKey] = useState(0)
  const openInvite = () => {
    setInviteKey(k => k + 1)
    setShowInviteModal(true)
  }

  // Cache-Busting fürs Logo (ohne any)
  type TeamWithStamp = Team & { logoUpdatedAt?: string | Date; updatedAt?: string | Date }
  const tStamped = team as TeamWithStamp
  const initialLogoVersion =
    tStamped.logoUpdatedAt
      ? new Date(tStamped.logoUpdatedAt).getTime()
      : tStamped.updatedAt
        ? new Date(tStamped.updatedAt).getTime()
        : 0
  const [logoVersion, setLogoVersion] = useState<number | null>(initialLogoVersion)

  // Upload-Progress
  const [isUploadingLogo, setIsUploadingLogo] = useState(false)
  const [uploadPct, setUploadPct] = useState(0)
  const R = 28, S = 64, CIRC = 2 * Math.PI * R
  const dashOffset = CIRC - (uploadPct / 100) * CIRC
  const fileInputRef = useRef<HTMLInputElement>(null)
  const isClickable = canManage && !isUploadingLogo

  // SSE-Verbindung
  useEffect(() => {
    if (!currentUserSteamId) return
    if (!isConnected) connect(currentUserSteamId)
  }, [currentUserSteamId, connect, isConnected])

  const eqByIds = (a: Player[], b: Player[]) => {
    if (a.length !== b.length) return false
    const aa = a.map(p=>p.steamId).join(',')
    const bb = b.map(p=>p.steamId).join(',')
    return aa === bb
  }

  const eqSetByIds = (a: {steamId:string}[], b: {steamId:string}[]) => {
    if (a.length !== b.length) return false
    const sa = [...a.map(p => p.steamId)].sort()
    const sb = [...b.map(p => p.steamId)].sort()
    for (let i = 0; i < sa.length; i++) {
      if (sa[i] !== sb[i]) return false
    }
    return true
  }

  useEffect(() => {
    if (typeof team.joinPolicy === 'string') {
      setJoinPolicy(team.joinPolicy as TeamJoinPolicy)
    }
  }, [team.id, team.joinPolicy])

  // Team-Listen lokal synchronisieren
  useEffect(() => {
    const nextActive   = (team.activePlayers ?? []).slice().sort((a,b)=>a.name.localeCompare(b.name))
    const nextInactive = (team.inactivePlayers ?? []).slice().sort((a,b)=>a.name.localeCompare(b.name))
    const nextInvited  = Array.from(new Map((team.invitedPlayers ?? []).map(p => [p.steamId, p])).values())
      .sort((a,b)=>a.name.localeCompare(b.name))

    const unchanged =
      eqByIds(activePlayers, nextActive) &&
      eqByIds(inactivePlayers, nextInactive) &&
      eqByIds(invitedPlayers, nextInvited)

    if (unchanged) return

    if (!isDraggingRef.current) {
      setActivePlayers(nextActive)
      setInactivePlayers(nextInactive)
      setInvitedPlayers(nextInvited)
    } else {
      setPendingRemote({ active: nextActive, inactive: nextInactive, invited: nextInvited })
    }
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [team?.id, team?.name, team?.logo, team?.leader, team?.activePlayers, team?.inactivePlayers, team?.invitedPlayers])

  // Relevante SSE-Events
  useEffect(() => {
    if (!lastEvent || !team.id) return
    if (!isSseEventType(lastEvent.type)) return
    const payload = (lastEvent.payload ?? {}) as Record<string, unknown>
    const now = Date.now()

    // Nach lokalem Speichern: kurzes Ignore-Fenster
    if (lastEvent.type === 'team-updated' && payload.teamId === team.id) {
      if (policyChangedAtRef.current && (now - policyChangedAtRef.current) < 2000) {
        policyChangedAtRef.current = null
        return
      }
    }

    // nur Logo geändert → minimal patchen
    if (lastEvent.type === 'team-logo-updated') {
      const curr = useTeamStore.getState().team
      const filename = (payload as { filename?: string }).filename
      const version  = (payload as { version?: number }).version
      const evTeamId = (payload as { teamId?: string }).teamId
      if (evTeamId && evTeamId !== team.id) return
      if (filename && curr) setTeam({ ...curr, logo: filename })
      if (typeof version === 'number') setLogoVersion(version)
      return
    }

    // Rest: reload wenn relevant
    if (!RELEVANT.has(lastEvent.type)) return
    const evTeamId = (payload as { teamId?: string }).teamId
    if (evTeamId && evTeamId !== team.id) return

    ;(async () => {
      const updated = await reloadTeam(team.id)
      if (!updated) return

      setTeam(updated)
      setEditedName(updated.name || '')

      if (typeof updated.joinPolicy === 'string') {
        setJoinPolicy(updated.joinPolicy as TeamJoinPolicy)
      }

      const nextActive   = (updated.activePlayers ?? []).slice().sort((a,b)=>a.name.localeCompare(b.name))
      const nextInactive = (updated.inactivePlayers ?? []).slice().sort((a,b)=>a.name.localeCompare(b.name))
      const nextInvited  = Array.from(new Map((updated.invitedPlayers ?? []).map(p => [p.steamId, p])).values())
        .sort((a,b)=>a.name.localeCompare(b.name))

      if (isDraggingRef.current) {
        setPendingRemote({ active: nextActive, inactive: nextInactive, invited: nextInvited })
        return
      }

      const contentChanged =
        !eqSetByIds(activePlayers, nextActive) ||
        !eqSetByIds(inactivePlayers, nextInactive) ||
        !eqSetByIds(invitedPlayers, nextInvited)

      const orderChanged =
        !eqByIds(activePlayers, nextActive) ||
        !eqByIds(inactivePlayers, nextInactive) ||
        !eqByIds(invitedPlayers, nextInvited)

      if (contentChanged) {
        setActivePlayers(nextActive)
        setInactivePlayers(nextInactive)
        setInvitedPlayers(nextInvited)
        setRemountKey(k => k + 1)
      } else if (orderChanged) {
        setActivePlayers(nextActive)
        setInactivePlayers(nextInactive)
        setInvitedPlayers(nextInvited)
      }
    })()
  }, [RELEVANT, lastEvent, team.id, setTeam, activePlayers, inactivePlayers, invitedPlayers])

  const handleDragStart = (event: DragStartEvent) => {
    const id = String(event.active.id)
    const item =
      activePlayers.find(p => p.steamId === id) ||
      inactivePlayers.find(p => p.steamId === id)
    if (item) {
      setActiveDragItem(item)
      setIsDragging(true)
      isDraggingRef.current = true
    }
  }

  const [showPolicyMenu, setShowPolicyMenu] = useState(false)
  const policyMenuRef = useRef<HTMLDivElement>(null)

  const applyPolicy = async (p: TeamJoinPolicy) => {
    if (p === joinPolicy) { setShowPolicyMenu(false); return }
    setJoinPolicy(p)
    await saveJoinPolicy(p)
    setShowPolicyMenu(false)
  }

  useEffect(() => {
    if (!showPolicyMenu) return

    const onOutside = (e: PointerEvent) => {
      if (!policyMenuRef.current) return
      if (!policyMenuRef.current.contains(e.target as Node)) {
        e.preventDefault()
        e.stopPropagation()
        setShowPolicyMenu(false)
      }
    }
    const onEsc = (e: KeyboardEvent) => {
      if (e.key === 'Escape') setShowPolicyMenu(false)
    }

    document.addEventListener('pointerdown', onOutside, { capture: true })
    document.addEventListener('keydown', onEsc)

    return () => {
      document.removeEventListener('pointerdown', onOutside, { capture: true })
      document.removeEventListener('keydown', onEsc)
    }
  }, [showPolicyMenu])

  const updateTeamMembers = async (tId: string, active: Player[], inactive: Player[]) => {
    try {
      const res = await fetch('/api/team/update-players', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({
          teamId: tId,
          activePlayers: active.map(p => p.steamId),
          inactivePlayers: inactive.map(p => p.steamId),
        }),
      })
      if (!res.ok) throw new Error('Update fehlgeschlagen')

      const updated = await reloadTeam(tId)
      if (updated) setTeam(updated)
    } catch (err) {
      console.error('Fehler beim Aktualisieren:', err)
    }
  }

  const handleDragEnd = async (event: DragEndEvent) => {
    setActiveDragItem(null)
    setIsDragging(false)
    isDraggingRef.current = false

    const { active, over } = event
    if (!over) {
      if (pendingRemote) {
        setActivePlayers(pendingRemote.active)
        setInactivePlayers(pendingRemote.inactive)
        setInvitedPlayers(pendingRemote.invited)
        setPendingRemote(null)
      }
      return
    }

    const activeId = String(active.id)
    const overId   = String(over.id)

    const movingItem =
      activePlayers.find(p => p.steamId === activeId) ||
      inactivePlayers.find(p => p.steamId === activeId)
    if (!movingItem) return

    const wasInActive = activePlayers.some(p => p.steamId === activeId)
    const dropToActive =
      overId === 'active' || activePlayers.some(p => p.steamId === overId)

    if ((wasInActive && dropToActive) || (!wasInActive && !dropToActive)) {
      if (pendingRemote) {
        setActivePlayers(pendingRemote.active)
        setInactivePlayers(pendingRemote.inactive)
        setInvitedPlayers(pendingRemote.invited)
        setPendingRemote(null)
      }
      return
    }

    let nextActive   = [...activePlayers]
    let nextInactive = [...inactivePlayers]

    if (dropToActive) {
      if (nextActive.length >= 5) return
      nextInactive = nextInactive.filter(p => p.steamId !== activeId)
      if (!nextActive.some(p => p.steamId === activeId)) nextActive.push(movingItem)
    } else {
      nextActive = nextActive.filter(p => p.steamId !== activeId)
      if (!nextInactive.some(p => p.steamId === activeId)) nextInactive.push(movingItem)
    }

    nextActive.sort((a,b)=>a.name.localeCompare(b.name))
    nextInactive.sort((a,b)=>a.name.localeCompare(b.name))

    const noChange =
      eqByIds(nextActive, activePlayers) && eqByIds(nextInactive, inactivePlayers)
    if (noChange) {
      if (pendingRemote) {
        setActivePlayers(pendingRemote.active)
        setInactivePlayers(pendingRemote.inactive)
        setInvitedPlayers(pendingRemote.invited)
        setPendingRemote(null)
      }
      return
    }

    setActivePlayers(nextActive)
    setInactivePlayers(nextInactive)

    updateTeamMembers(teamId, nextActive, nextInactive).catch(console.error)

    setSaveSuccess(true)
    setTimeout(()=>setSaveSuccess(false), 3000)

    if (pendingRemote) {
      const diff =
        !eqByIds(pendingRemote.active, nextActive) ||
        !eqByIds(pendingRemote.inactive, nextInactive) ||
        !eqByIds(pendingRemote.invited, invitedPlayers)
      if (diff) {
        setActivePlayers(pendingRemote.active)
        setInactivePlayers(pendingRemote.inactive)
        setInvitedPlayers(pendingRemote.invited)
      }
      setPendingRemote(null)
    }
  }

  const handleReload = async () => {
    const updated = await reloadTeam(team.id)
    if (updated) setTeam(updated)
  }

  const confirmKick = async () => {
    if (!kickCandidate) return
    const newActive   = activePlayers.filter(p => p.steamId !== kickCandidate.steamId)
    const newInactive = inactivePlayers.filter(p => p.steamId !== kickCandidate.steamId)
    setActivePlayers(newActive)
    setInactivePlayers(newInactive)

    await fetch('/api/team/kick', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ steamId: kickCandidate.steamId, teamId }),
    })
    await updateTeamMembers(team.id, newActive, newInactive)
    setKickCandidate(null)
  }

  const promoteToLeader = async (newLeaderId: string) => {
    try {
      const res = await fetch('/api/team/transfer-leader', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ teamId, newLeaderSteamId: newLeaderId }),
      })
      if (!res.ok) {
        const data: unknown = await res.json().catch(() => ({}))
        console.error('Fehler bei Leader-Übertragung:', (data as { message?: string }).message)
        return
      }
      await handleReload()
    } catch (err) {
      console.error('Fehler bei Leader-Übertragung:', err)
    }
  }

  type DownscaleOpts = {
    size?: number
    quality?: number
    mime?: string
    square?: boolean
  }

  async function saveJoinPolicy(next: TeamJoinPolicy = joinPolicy) {
    const prev = joinPolicy
    try {
      setSavingPolicy(true)

      const res = await fetch('/api/team/update-join-policy', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        credentials: 'same-origin',
        cache: 'no-store',
        body: JSON.stringify({ teamId, joinPolicy: next }),
      })

      if (!res.ok) {
        const data: unknown = await res.json().catch(() => ({}))
        const msg = (data as { message?: string }).message
        throw new Error(msg ?? `Speichern fehlgeschlagen (${res.status})`)
      }

      const parsed: unknown = await res.json().catch(() => ({}))
      const serverPolicy = (parsed as { joinPolicy?: TeamJoinPolicy }).joinPolicy
      const patched = (serverPolicy ?? next) as TeamJoinPolicy

      setJoinPolicy(patched)

      const curr = useTeamStore.getState().team
      if (curr && curr.id === teamId && curr.joinPolicy !== patched) {
        setTeam({ ...curr, joinPolicy: patched })
      }

      policyChangedAtRef.current = Date.now()

      setPolicySaved(true)
      setTimeout(() => setPolicySaved(false), 2000)
    } catch (e) {
      setJoinPolicy(prev)
      console.error(e)
      alert((e as Error).message || 'Speichern fehlgeschlagen')
    } finally {
      setSavingPolicy(false)
    }
  }

  async function canEncode(mime: string): Promise<boolean> {
    try {
      if ('OffscreenCanvas' in window) {
        const c = new OffscreenCanvas(2, 2)
        const b = await c.convertToBlob({ type: mime, quality: 0.8 })
        return !!b
      }
      const c = document.createElement('canvas')
      c.width = 2; c.height = 2
      const url = c.toDataURL(mime)
      return typeof url === 'string' && url.startsWith(`data:${mime}`)
    } catch {
      return false
    }
  }

  async function downscaleImage(file: File, opts: DownscaleOpts = {}): Promise<Blob> {
    const {
      size = 512,
      quality = 0.85,
      mime: wantedMime = 'image/webp',
      square = true,
    } = opts

    // 1) Bild laden (ImageBitmap bevorzugt, ohne any)
    let url: string | null = null
    let img: ImageBitmap | HTMLImageElement

    try {
      img = await createImageBitmap(file)
    } catch {
      url = URL.createObjectURL(file)
      img = await new Promise<HTMLImageElement>((res, rej) => {
        const im = new window.Image()
        im.onload = () => res(im)
        im.onerror = rej
        im.src = url!
      })
    }

    const dims = img as unknown as { width: number; height: number }
    const srcW = dims.width
    const srcH = dims.height
    if (!srcW || !srcH) {
      if (url) URL.revokeObjectURL(url)
      if ('close' in (img as ImageBitmap)) try { (img as ImageBitmap).close() } catch {}
      throw new Error('Invalid image dimensions')
    }

    // 2) Zielgröße + optionaler Center-Crop
    let sx = 0, sy = 0, sw = srcW, sh = srcH
    if (square) {
      const side = Math.min(srcW, srcH)
      sx = Math.max(0, Math.floor((srcW - side) / 2))
      sy = Math.max(0, Math.floor((srcH - side) / 2))
      sw = side; sh = side
    }
    const scale = Math.min(size / sw, size / sh, 1)
    const dw = Math.max(1, Math.round(sw * scale))
    const dh = Math.max(1, Math.round(sh * scale))

    // 3) Canvas (Offscreen bevorzugt)
    const source = img as unknown as CanvasImageSource
    const offscreen = 'OffscreenCanvas' in window
    let blob: Blob | null = null

    if (offscreen) {
      const c = new OffscreenCanvas(dw, dh)
      const ctx = c.getContext('2d', { alpha: true })!
      ctx.imageSmoothingQuality = 'high'
      ctx.drawImage(source, sx, sy, sw, sh, 0, 0, dw, dh)

      // 4) Format mit Fallbacks
      const canWebp = await canEncode('image/webp')
      const canJpeg = await canEncode('image/jpeg')
      const targetMime =
        (wantedMime === 'image/webp' && canWebp) ? 'image/webp' :
        (wantedMime === 'image/jpeg' && canJpeg) ? 'image/jpeg' :
        canWebp ? 'image/webp' :
        canJpeg ? 'image/jpeg' : 'image/png'

      blob = await c.convertToBlob({ type: targetMime, quality: targetMime === 'image/png' ? undefined : quality })
    } else {
      const c = document.createElement('canvas')
      c.width = dw; c.height = dh
      const ctx = c.getContext('2d')!
      ctx.imageSmoothingQuality = 'high'
      ctx.drawImage(source, sx, sy, sw, sh, 0, 0, dw, dh)

      const canWebp = await canEncode('image/webp')
      const canJpeg = await canEncode('image/jpeg')
      const targetMime =
        (wantedMime === 'image/webp' && canWebp) ? 'image/webp' :
        (wantedMime === 'image/jpeg' && canJpeg) ? 'image/jpeg' :
        canWebp ? 'image/webp' :
        canJpeg ? 'image/jpeg' : 'image/png'

      blob = await new Promise<Blob | null>((res) =>
        c.toBlob(b => res(b), targetMime, targetMime === 'image/png' ? undefined : quality)
      )
    }

    if (url) URL.revokeObjectURL(url)
    if ('close' in (img as ImageBitmap)) { try { (img as ImageBitmap).close() } catch {} }

    if (!blob) throw new Error('Canvas encoding failed (toBlob returned null)')
    return blob
  }

  async function uploadTeamLogo(file: File) {
    return new Promise<void>((resolve, reject) => {
      const formData = new FormData()
      formData.append('logo', file)
      formData.append('teamId', teamId)

      const xhr = new XMLHttpRequest()
      xhr.open('POST', '/api/team/upload-logo')
      xhr.upload.onprogress = (e) => {
        if (e.lengthComputable) setUploadPct(Math.round((e.loaded / e.total) * 100))
        else setUploadPct(p => (p < 90 ? p + 1 : p))
      }
      xhr.onload = () => {
        if (xhr.status >= 200 && xhr.status < 300) {
          try {
            const json: unknown = JSON.parse(xhr.responseText)
            const current = useTeamStore.getState().team
            const filename = (json as { filename?: string }).filename
            const version = (json as { version?: number }).version
            if (filename && current) setTeam({ ...current, logo: filename })
            if (typeof version === 'number') setLogoVersion(version)
          } catch {}
          resolve()
        } else {
          reject(new Error('Upload fehlgeschlagen'))
        }
      }
      xhr.onerror = () => reject(new Error('Netzwerkfehler beim Upload'))
      setIsUploadingLogo(true)
      setUploadPct(0)
      xhr.send(formData)
    })
  }

  const manageSteam: string = adminMode ? teamLeaderSteamId : currentUserSteamId

  const renderMemberList = (players: Player[]) => (
    <AnimatePresence>
      {players.map(player => (
        <motion.div
          key={player.steamId}
          initial={{ opacity: 0, y: 10 }}
          animate={{ opacity: 1, y: 0 }}
          exit={{ opacity: 0, y: -10 }}
          transition={{ duration: 0.2 }}
          className='max-w-[160px]'
        >
          <Link
            href={`/profile/${player.steamId}`}
            onClick={e => { if (isDragging) e.preventDefault() }}
          >
            <SortableMiniCard
              player={player}
              onKick={setKickCandidate}
              onPromote={() => setPromoteCandidate(player)}
              currentUserSteamId={manageSteam}
              teamLeaderSteamId={teamLeaderSteamId}
              isAdmin={adminMode}
              isDraggingGlobal={isDragging}
              hideOverlay={isDragging}
              matchParentBg
            />
          </Link>
        </motion.div>
      ))}
    </AnimatePresence>
  )

  return (
    <div className={`p-4 mt-6 bg-white border border-gray-200 shadow-2xs rounded-xl dark:bg-neutral-800 dark:border-neutral-700 ${isDragging ? 'cursor-grabbing' : ''}`}>
      <div className="flex justify-between items-center mb-6 flex-wrap gap-2">
        <div className="flex items-center gap-4">
          <div className="relative group">
            <div
              role="button"
              aria-disabled={!isClickable}
              aria-busy={isUploadingLogo}
              tabIndex={isClickable ? 0 : -1}
              className={[
                "relative w-16 h-16 rounded-full overflow-hidden border border-gray-300 dark:border-neutral-600",
                isClickable ? "cursor-pointer" : "cursor-not-allowed opacity-70"
              ].join(" ")}
              onClick={() => { if (isClickable) fileInputRef.current?.click() }}
              onKeyDown={(e) => {
                if (!isClickable) return
                if (e.key === "Enter" || e.key === " ") {
                  e.preventDefault()
                  fileInputRef.current?.click()
                }
              }}
              title={isUploadingLogo ? "Upload läuft…" : (canManage ? "Logo hochladen" : undefined)}
            >
              <NextImage
                key={`${team.logo ?? 'fallback'}-${logoVersion ?? 0}`}
                src={
                  team.logo
                    ? `/assets/img/logos/${team.logo}${logoVersion ? `?v=${logoVersion}` : ''}`
                    : `/assets/img/logos/cs2.webp`
                }
                alt="Teamlogo"
                fill
                sizes="64px"
                quality={75}
                className={`object-cover ${isUploadingLogo ? 'opacity-70' : ''}`}
                priority={false}
                unoptimized
              />

              {canManage && isClickable && (
                <div className="absolute inset-0 bg-black/50 text-white flex flex-col items-center justify-center text-xs font-medium opacity-0 group-hover:opacity-100 transition-opacity">
                  <svg xmlns="http://www.w3.org/2000/svg" className="w-5 h-5 mb-1" viewBox="0 0 576 512" fill="currentColor">
                    <path d="M288 109.3L288 352c0 17.7-14.3 32-32 32s-32-14.3-32-32l0-242.7-73.4 73.4c-12.5 12.5-32.8 12.5-45.3 0s-12.5-32.8 0-45.3l128-128c12.5-12.5 32.8-12.5 45.3 0l128 128c12.5 12.5 12.5 32.8 0 45.3s-32.8 12.5-45.3 0L288 109.3zM64 352l128 0c0 35.3 28.7 64 64 64s64-28.7 64-64l128 0c35.3 0 64 28.7 64 64l0 32c0 35.3-28.7 64-64 64L64 512c-35.3 0-64-28.7-64-64l0-32c0-35.3 28.7-64 64-64zM432 456a24 24 0 1 0 0-48 24 24 0 1 0 0 48z"/>
                  </svg>
                </div>
              )}

              {isUploadingLogo && (
                <div className="absolute inset-0 flex items-center justify-center pointer-events-none">
                  <svg width={S} height={S} viewBox={`0 0 ${S} ${S}`} className="absolute">
                    <g transform={`rotate(-90 ${S/2} ${S/2})`}>
                      <circle cx={S/2} cy={S/2} r={R} stroke="rgba(255,255,255,0.35)" strokeWidth="6" fill="none" />
                      <circle
                        cx={S/2} cy={S/2} r={R}
                        stroke="#16a34a"
                        strokeWidth="6"
                        fill="none"
                        strokeLinecap="round"
                        strokeDasharray={CIRC}
                        strokeDashoffset={dashOffset}
                        style={{ transition: 'stroke-dashoffset 120ms linear' }}
                      />
                    </g>
                  </svg>
                  <span className="text-[11px] font-semibold text-white drop-shadow">{uploadPct}%</span>
                </div>
              )}
            </div>

            {canManage && (
              <input
                ref={fileInputRef}
                type="file"
                accept="image/png,image/jpeg,image/webp"
                id="logoUpload"
                className="hidden"
                disabled={!isClickable}
                onChange={async (e) => {
                  if (isUploadingLogo) return
                  const file = e.target.files?.[0]
                  if (!file) return

                  try {
                    const blob = await downscaleImage(file, { size: 512, quality: 0.85, mime: 'image/webp', square: true })
                    const mime = blob.type || 'image/webp'
                    const ext = mime === 'image/jpeg' ? 'jpg' : mime === 'image/png' ? 'png' : 'webp'
                    const processed = new File([blob], `${team!.id}.${ext}`, { type: mime })
                    await uploadTeamLogo(processed)
                  } catch (err) {
                    console.error('Fehler beim Hochladen des Logos:', err)
                    alert('Fehler beim Hochladen des Logos.')
                  } finally {
                    setTimeout(() => { setIsUploadingLogo(false); setUploadPct(0) }, 300)
                    e.currentTarget.value = ''
                  }
                }}
              />
            )}
          </div>

          <div className="flex items-center gap-2">
            {isEditingName ? (
              <>
                <input
                  type="text"
                  value={editedName}
                  onChange={(e) => setEditedName(e.target.value)}
                  className="py-1.5 px-3 border rounded-lg text-sm dark:bg-neutral-800 dark:border-neutral-700 dark:text-white"
                />
                <Button
                  title="Übernehmen"
                  color="green"
                  size="sm"
                  variant="soft"
                  onClick={async () => {
                    await renameTeam(team.id, editedName)
                    setIsEditingName(false)
                    await handleReload()
                  }}
                  className="h-[34px] px-3 flex items-center justify-center"
                >
                  <span className="text-green-600">✓</span>
                </Button>
                <Button
                  title="Abbrechen"
                  color="red"
                  size="sm"
                  variant="ghost"
                  onClick={() => {
                    setIsEditingName(false)
                    setEditedName(team.name ?? '')
                  }}
                  className="h-[34px] px-3 flex items-center justify-center"
                >
                  <span className="text-red-600">✕</span>
                </Button>
              </>
            ) : (
              <>
                <div className="flex items-center gap-3">
                  <h2 className="text-lg font-semibold text-gray-800 dark:text-white">
                    {team.name ?? 'Team'}
                  </h2>
                  <TeamPremierRankBadge players={activePlayers} />
                </div>

                {canManage && (
                  <>
                    <Button
                      title="Bearbeiten"
                      color="blue"
                      size="sm"
                      variant="soft"
                      onClick={() => {
                        setIsEditingName(true)
                        setEditedName(team.name || '')
                      }}
                      className="h-[34px] px-3 flex items-center justify-center"
                    >
                      Bearbeiten
                    </Button>

                    {/* Policy-Pill */}
                    <div className="relative" ref={policyMenuRef}>
                      <button
                        type="button"
                        onPointerDownCapture={(e) => { e.stopPropagation() }}
                        onMouseDown={(e) => e.stopPropagation()}
                        onClick={(e) => { e.stopPropagation(); setShowPolicyMenu(v => !v) }}
                        className="h-[32px] px-2.5 rounded-xl text-xs border border-gray-300 dark:border-neutral-600
                                  bg-white dark:bg-neutral-800 text-gray-700 dark:text-neutral-200
                                  hover:bg-gray-100 hover:dark:bg-neutral-700 inline-flex items-center gap-1"
                        title="Beitrittsmodus ändern"
                      >
                        {joinPolicy === 'INVITE_ONLY' ? (
                          <svg className="w-3.5 h-3.5" viewBox="0 0 24 24" fill="currentColor">
                            <path d="M17 8V7a5 5 0 1 0-10 0v1H5v12h14V8h-2Zm-8 0V7a3 3 0 1 1 6 0v1H9Zm-2 2h10v8H7v-8Z"/>
                          </svg>
                        ) : (
                          <svg className="w-3.5 h-3.5" viewBox="0 0 24 24" fill="currentColor">
                            <path d="M12 2a5 5 0 0 0-5 5v1H5v12h14V8h-2V7a5 5 0 0 0-5-5Zm-3 6V7a3 3 0 1 1 6 0v1H9Z"/>
                          </svg>
                        )}
                        <span>{joinPolicy === 'INVITE_ONLY' ? 'Nur Einladung' : 'Mit Genehmigung'}</span>
                        {savingPolicy && (
                          <span className="ml-1 inline-block size-3 border-2 border-current border-t-transparent rounded-xl animate-spin" />
                        )}
                        {policySaved && !savingPolicy && <span className="ml-1 text-green-600">✓</span>}
                      </button>

                      {showPolicyMenu && (
                        <div
                          className="absolute right-0 z-[60] mt-1 w-56 rounded-md border border-gray-200
                                    dark:border-neutral-700 bg-white dark:bg-neutral-800 shadow-lg p-1"
                          onPointerDownCapture={(e) => e.stopPropagation()}
                          onClick={(e) => e.stopPropagation()}
                        >
                          <button
                            type="button"
                            onPointerDownCapture={(e) => e.stopPropagation()}
                            onClick={(e) => { e.stopPropagation(); applyPolicy('REQUEST') }}
                            className={`w-full text-left px-2.5 py-2 rounded-md text-sm
                              ${joinPolicy === 'REQUEST'
                                ? 'bg-blue-50 dark:bg-blue-900/20 text-blue-700 dark:text-blue-200'
                                : 'hover:bg-gray-100 dark:hover:bg-neutral-700 text-gray-800 dark:text-neutral-200'}`}
                          >
                            <div className="font-medium">Mit Genehmigung</div>
                            <div className="text-xs text-gray-500 dark:text-neutral-400">
                              Spieler stellen eine Anfrage; Leader entscheidet.
                            </div>
                          </button>

                          <button
                            type="button"
                            onPointerDownCapture={(e) => e.stopPropagation()}
                            onClick={(e) => { e.stopPropagation(); applyPolicy('INVITE_ONLY') }}
                            className={`w-full text-left px-2.5 py-2 rounded-md text-sm
                              ${joinPolicy === 'INVITE_ONLY'
                                ? 'bg-blue-50 dark:bg-blue-900/20 text-blue-700 dark:text-blue-200'
                                : 'hover:bg-gray-100 dark:hover:bg-neutral-700 text-gray-800 dark:text-neutral-200'}`}
                          >
                            <div className="font-medium">Nur Einladung</div>
                            <div className="text-xs text-gray-500 dark:text-neutral-400">
                              Beitritt nur per Einladung.
                            </div>
                          </button>
                        </div>
                      )}
                    </div>
                  </>
                )}
              </>
            )}
          </div>
        </div>

        <div className="flex gap-2">
          {canManage && (
            <Button onClick={() => setShowDeleteModal(true)} color='red' size='sm'>
              Team löschen
            </Button>
          )}
          <Button
            onClick={async () => {
              if (isLeader) setShowLeaveModal(true)
              else {
                try { await leaveTeam(currentUserSteamId) }
                catch (err) { console.error('Fehler beim Verlassen:', err) }
              }
            }}
            color='blue'
            size='sm'
          >
            Team verlassen
          </Button>
        </div>
      </div>

      <DndContext
        key={`dnd-${teamId}-${remountKey}`}
        collisionDetection={closestCenter}
        onDragStart={handleDragStart}
        onDragEnd={handleDragEnd}
      >
        <div className="space-y-8">
          <DroppableZone id="active" label={`Aktive Spieler (${activePlayers.length} / 5)`} activeDragItem={activeDragItem} saveSuccess={saveSuccess}>
            <SortableContext id="active" key={`sc-active-${remountKey}-${activePlayers.map(p=>p.steamId).join(',')}`} items={activePlayers.map(p => p.steamId)} strategy={verticalListSortingStrategy}>
              {renderMemberList(activePlayers)}
            </SortableContext>
          </DroppableZone>

          <DroppableZone id="inactive" label="Inaktive Spieler" activeDragItem={activeDragItem} saveSuccess={saveSuccess}>
            <SortableContext id="inactive" key={`sc-inactive-${remountKey}-${inactivePlayers.map(p=>p.steamId).join(',')}`} items={inactivePlayers.map(p => p.steamId)} strategy={verticalListSortingStrategy}>
              {renderMemberList(inactivePlayers)}
              {canManage && (
                <motion.div key="mini-card-dummy" initial={{ opacity: 0 }} animate={{ opacity: 1}} exit={{ opacity: 0 }} transition={{ duration: 0.2 }}>
                  <MiniCardDummy
                    zoneId="inactive"
                    title={canAddDirect ? 'Hinzufügen' : 'Einladen'}
                    onClick={openInvite}
                  >
                    <div className="flex items-center justify-center w-16 h-16 bg-white rounded-full text-black">
                      <svg xmlns="http://www.w3.org/2000/svg" className="w-8 h-8" viewBox="0 0 640 512" fill="currentColor">
                        <path d="M96 128a128 128 0 1 1 256 0A128 128 0 1 1 96 128zM0 482.3C0 383.8 79.8 304 178.3 304h91.4C368.2 304 448 383.8 448 482.3c0 16.4-13.3 29.7-29.7 29.7H29.7C13.3 512 0 498.7 0 482.3zM504 312v-64h-64c-13.3 0-24-10.7-24-24s10.7-24 24-24h64v-64c0-13.3 10.7-24 24-24s24 10.7 24 24v64h64c13.3 0 24 10.7 24 24s-10.7 24-24 24h-64v64c0 13.3-10.7 24-24 24s-24-10.7-24-24z" />
                      </svg>
                    </div>
                  </MiniCardDummy>
                </motion.div>
              )}
            </SortableContext>
          </DroppableZone>

          {invitedPlayers.length > 0 && (
            <div className="space-y-2">
              <div className="flex items-center justify-between">
                <h3 className="text-md font-semibold text-gray-700 dark:text-gray-300">Eingeladene Spieler</h3>
              </div>
              <div className="w-full rounded-lg p-4 transition-colors min-h-[200px] border border-gray-300 dark:border-neutral-700">
                <div className="grid gap-4 justify-start grid-cols-[repeat(auto-fill,minmax(160px,1fr))]">
                  <AnimatePresence>
                    {invitedPlayers.map((player) => (
                      <motion.div key={player.steamId} initial={{ opacity: 0, y: 10 }} animate={{ opacity: 1, y: 0 }} exit={{ opacity: 0, y: -10 }} transition={{ duration: 0.2 }}>
                        <MiniCard
                          steamId={player.steamId}
                          title={player.name}
                          avatar={player.avatar}
                          location={player.location}
                          selected={false}
                          onSelect={() => {}}
                          draggable={false}
                          currentUserSteamId={currentUserSteamId}
                          teamLeaderSteamId={team.leader?.steamId}
                          isSelectable={false}
                          isInvite={true}
                          rank={player.premierRank}
                          // optional lokales Extra-Feld sicher lesen
                          invitationId={(player as InvitedPlayer & { invitationId?: string }).invitationId}
                          onKick={async (sid) => {
                            setInvitedPlayers(list => list.filter(p => p.steamId !== sid))
                            try {
                              await fetch('/api/user/invitations/revoke', {
                                method: 'POST',
                                headers: { 'Content-Type': 'application/json' },
                                body: JSON.stringify({
                                  invitationId: (player as InvitedPlayer & { invitationId?: string }).invitationId ?? undefined,
                                  teamId: team.id,
                                  steamId: sid,
                                }),
                              })
                            } catch (e) {
                              console.error('Revoke fehlgeschlagen:', e)
                              setInvitedPlayers(list => [...list, player].sort((a,b)=>a.name.localeCompare(b.name)))
                            } finally {
                              const updated = await reloadTeam(team.id)
                              if (updated) setTeam(updated)
                            }
                          }}
                        />
                      </motion.div>
                    ))}
                  </AnimatePresence>
                </div>
              </div>
            </div>
          )}
        </div>

        <DragOverlay>
          {activeDragItem && (
            <SortableMiniCard
              player={activeDragItem}
              currentUserSteamId={currentUserSteamId}
              teamLeaderSteamId={team.leader?.steamId}
              isAdmin={adminMode}
              hideOverlay
              matchParentBg
            />
          )}
        </DragOverlay>
      </DndContext>

      {canInvite && (
        <InvitePlayersModal
          key={inviteKey}
          show={showInviteModal}
          onClose={() => setShowInviteModal(false)}
          onSuccess={() => {}}
          team={team}
        />
      )}
      {canAddDirect && (
        <InvitePlayersModal
          key={inviteKey}
          show={showInviteModal}
          onClose={() => setShowInviteModal(false)}
          onSuccess={() => setShowInviteModal(false)}
          team={team}
          directAdd
        />
      )}

      {isLeader && (
        <LeaveTeamModal
          show={showLeaveModal}
          onClose={() => setShowLeaveModal(false)}
          onSuccess={() => setShowLeaveModal(false)}
          team={team}
        />
      )}

      {canManage && promoteCandidate && (
        <Modal
          id={`modal-promote-player-${promoteCandidate.steamId}`}
          title="Leader übertragen"
          show={true}
          onClose={() => setPromoteCandidate(null)}
          onSave={async () => {
            await promoteToLeader(promoteCandidate.steamId)
            setPromoteCandidate(null)
          }}
          closeButtonTitle="Übertragen"
          closeButtonColor="blue"
        >
          <div className="flex justify-center mb-4">
            <MiniCard
              steamId={promoteCandidate.steamId}
              title={promoteCandidate.name}
              avatar={promoteCandidate.avatar}
              location={promoteCandidate.location}
              selected={false}
              onSelect={() => {}}
              draggable={false}
              rank={promoteCandidate.premierRank}
              currentUserSteamId={currentUserSteamId}
              teamLeaderSteamId={team.leader?.steamId}
              hideActions
              isSelectable={false}
            />
          </div>
          <p className="text-sm text-gray-700 dark:text-neutral-300">
            Möchtest du <strong>{promoteCandidate.name}</strong> wirklich zum Team-Leader machen?
          </p>
        </Modal>
      )}

      {canManage && kickCandidate && (
        <Modal
          id={`modal-kick-player-${kickCandidate.steamId}`}
          title="Mitglied entfernen"
          show={true}
          onClose={() => setKickCandidate(null)}
          onSave={confirmKick}
          closeButtonTitle="Entfernen"
          closeButtonColor="red"
        >
          <div className="flex justify-center mb-4">
            <MiniCard
              steamId={kickCandidate.steamId}
              title={kickCandidate.name}
              avatar={kickCandidate.avatar}
              location={kickCandidate.location}
              selected={false}
              onSelect={() => {}}
              draggable={false}
              rank={kickCandidate.premierRank}
              currentUserSteamId={currentUserSteamId}
              teamLeaderSteamId={team.leader?.steamId}
              hideActions
              isSelectable={false}
            />
          </div>
          <p className="text-sm text-gray-700 dark:text-neutral-300">
            Möchtest du <strong>{kickCandidate.name}</strong> wirklich aus dem Team entfernen?
          </p>
        </Modal>
      )}

      {canManage && (
        <Modal
          id="modal-delete-team"
          title="Team löschen"
          show={showDeleteModal}
          onClose={() => setShowDeleteModal(false)}
          onSave={async () => {
            await fetch('/api/team/delete', {
              method: 'POST',
              headers: { 'Content-Type': 'application/json' },
              body: JSON.stringify({ teamId: team.id }),
            })
            setShowDeleteModal(false)
            window.location.href = '/team'
          }}
          closeButtonTitle="Team löschen"
          closeButtonColor="red"
        >
          <p className="text-sm text-gray-700 dark:text-neutral-300">
            Bist du sicher, dass du dieses Team löschen möchtest? Diese Aktion kann nicht rückgängig gemacht werden.
          </p>
        </Modal>
      )}
    </div>
  )
}
