import { useState, useCallback, useEffect, useRef } from 'react'
import { getSocket, setSocketRoom } from '@/lib/socket'
import type { DrawFeature, Project } from '@/types'

interface UseRealtimeSyncOptions {
  currentProject: Project | null
  user: { id: string; name: string; role: string } | null
  featuresRef: React.MutableRefObject<DrawFeature[]>
  setFeatures: (features: DrawFeature[] | ((prev: DrawFeature[]) => DrawFeature[])) => void
  toast: (opts: { title: string; description?: string; variant?: string }) => void
}

export function useRealtimeSync({
  currentProject,
  user,
  featuresRef,
  setFeatures,
  toast,
}: UseRealtimeSyncOptions) {
  // Live editing lock state
  const [editingBy, setEditingBy] = useState<{ id: string; name: string; since: string } | null>(null)
  const [isEditingByMe, setIsEditingByMe] = useState(false)
  const [editingLoading, setEditingLoading] = useState(false)

  // Unique session ID per browser tab (survives re-renders, not page reload)
  const sessionIdRef = useRef<string>('')
  if (!sessionIdRef.current) {
    sessionIdRef.current = `${Date.now()}-${Math.random().toString(36).slice(2, 10)}`
  }

  // ─── Editing Lock: Check status + Heartbeat + Polling ─────────

  const checkEditingStatus = useCallback(async (projectId: string) => {
    try {
      const res = await fetch(`/api/projects/${projectId}/editing?sessionId=${sessionIdRef.current}`)
      if (!res.ok) return
      const data = await res.json()
      if (data.editing) {
        setEditingBy(data.editingBy)
        setIsEditingByMe(data.isMe)
      } else {
        setEditingBy(null)
        setIsEditingByMe(false)
      }
    } catch (e) {
      console.warn('[Editing] Status check failed:', e)
    }
  }, [])

  // Check editing status when project changes
  useEffect(() => {
    if (!currentProject?.id) {
      setEditingBy(null)
      setIsEditingByMe(false)
      return
    }
    checkEditingStatus(currentProject.id)
  }, [currentProject?.id, checkEditingStatus])

  // Heartbeat: keep lock alive every 30s while I'm editing
  useEffect(() => {
    if (!currentProject?.id || !isEditingByMe) return
    const interval = setInterval(async () => {
      try {
        await fetch(`/api/projects/${currentProject.id}/editing`, {
          method: 'POST',
          headers: { 'Content-Type': 'application/json' },
          body: JSON.stringify({ action: 'heartbeat', sessionId: sessionIdRef.current }),
        })
      } catch (e) {
        console.warn('[Heartbeat] Failed:', e)
      }
    }, 30000)
    return () => clearInterval(interval)
  }, [currentProject?.id, isEditingByMe])

  // Socket.io: real-time sync for features, editing status, journal
  const socketRef = useRef<any>(null)
  const prevProjectIdRef = useRef<string | null>(null)

  // Throttled socket broadcast for near-real-time sync
  const lastEmitRef = useRef(0)
  const emitTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null)
  const currentProjectRef = useRef(currentProject)
  useEffect(() => { currentProjectRef.current = currentProject }, [currentProject])

  const isEditingByMeRef = useRef(false)
  useEffect(() => { isEditingByMeRef.current = isEditingByMe }, [isEditingByMe])

  const broadcastFeatures = useCallback((feats: DrawFeature[]) => {
    const proj = currentProjectRef.current
    if (!socketRef.current || !proj?.id || !isEditingByMeRef.current) return
    const now = Date.now()
    const emit = () => {
      socketRef.current?.emit('features-updated', {
        projectId: proj!.id,
        features: feats,
      })
      lastEmitRef.current = Date.now()
    }
    // Throttle: emit at most every 800ms for snappier sync
    if (now - lastEmitRef.current > 800) {
      emit()
    } else {
      if (emitTimerRef.current) clearTimeout(emitTimerRef.current)
      emitTimerRef.current = setTimeout(emit, 800 - (now - lastEmitRef.current))
    }
  }, [])

  useEffect(() => {
    if (!currentProject?.id) return

    const socket = getSocket()
    socketRef.current = socket

    // Leave old room, join new room
    if (prevProjectIdRef.current && prevProjectIdRef.current !== currentProject.id) {
      socket.emit('leave-project', prevProjectIdRef.current)
    }
    socket.emit('join-project', currentProject.id)
    setSocketRoom(currentProject.id)
    prevProjectIdRef.current = currentProject.id

    // Listen for features changes from other clients (only apply if NOT the editor)
    const onFeaturesChanged = (data: { features: any[] }) => {
      // Skip if I'm the one editing — my local state is the source of truth
      if (isEditingByMeRef.current) {
        console.log('[Socket.io] Ignoring features-changed (I am the editor)')
        return
      }
      if (data.features && Array.isArray(data.features)) {
        console.log('[Socket.io] Features updated from another client')
        setFeatures(data.features)
      }
    }

    // Listen for editing status changes from other clients
    const onEditingStatus = (data: { editing: boolean; editingBy: any; sessionId: string }) => {
      if (data.sessionId === sessionIdRef.current) return // ignore own events
      if (data.editing && data.editingBy) {
        setEditingBy(data.editingBy)
        setIsEditingByMe(false)
      } else {
        setEditingBy(null)
        setIsEditingByMe(false)
      }
    }

    // Listen for journal changes — trigger a re-fetch in JournalView
    const onJournalChanged = () => {
      console.log('[Socket.io] Journal updated from another client')
      window.dispatchEvent(new CustomEvent('journal-refresh'))
    }

    socket.on('features-changed', onFeaturesChanged)
    socket.on('editing-status', onEditingStatus)
    socket.on('journal-changed', onJournalChanged)

    return () => {
      socket.off('features-changed', onFeaturesChanged)
      socket.off('editing-status', onEditingStatus)
      socket.off('journal-changed', onJournalChanged)
    }
  }, [currentProject?.id, setFeatures])

  // Fallback: check editing status on initial load and every 30s
  useEffect(() => {
    if (!currentProject?.id) return
    checkEditingStatus(currentProject.id)
    const interval = setInterval(() => checkEditingStatus(currentProject.id), 30000)
    return () => clearInterval(interval)
  }, [currentProject?.id, checkEditingStatus])

  // Release lock on unmount / page close
  useEffect(() => {
    const release = () => {
      if (currentProject?.id && isEditingByMe) {
        const blob = new Blob([JSON.stringify({ action: 'stop', sessionId: sessionIdRef.current })], { type: 'application/json' })
        navigator.sendBeacon(`/api/projects/${currentProject.id}/editing`, blob)
      }
    }
    window.addEventListener('beforeunload', release)
    return () => {
      window.removeEventListener('beforeunload', release)
      release()
    }
  }, [currentProject?.id, isEditingByMe])

  const handleStartEditing = useCallback(async () => {
    if (!currentProject?.id) return
    setEditingLoading(true)
    try {
      const res = await fetch(`/api/projects/${currentProject.id}/editing`, {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ action: 'start', sessionId: sessionIdRef.current }),
      })
      if (!res.ok) {
        const data = await res.json()
        toast({ title: 'Gesperrt', description: data.error || 'Bearbeitung nicht möglich', variant: 'destructive' })
        return
      }
      setIsEditingByMe(true)
      const editingInfo = { id: user!.id, name: user!.name, since: new Date().toISOString() }
      setEditingBy(editingInfo)
      // Notify other clients
      socketRef.current?.emit('editing-changed', {
        projectId: currentProject.id,
        editing: true,
        editingBy: editingInfo,
        sessionId: sessionIdRef.current,
      })
      toast({ title: 'Bearbeitung gestartet', description: 'Sie können jetzt zeichnen und Einträge erstellen.' })
    } catch (e) {
      toast({ title: 'Fehler', description: 'Konnte Bearbeitung nicht starten.', variant: 'destructive' })
    } finally {
      setEditingLoading(false)
    }
  }, [currentProject?.id, user, toast])

  const handleStopEditing = useCallback(async () => {
    if (!currentProject?.id) return
    setEditingLoading(true)
    try {
      // Save features before releasing lock
      const currentFeatures = featuresRef.current
      await fetch(`/api/projects/${currentProject.id}/features`, {
        method: 'PUT',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ features: currentFeatures }),
      })
      // Release lock
      await fetch(`/api/projects/${currentProject.id}/editing`, {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ action: 'stop', sessionId: sessionIdRef.current }),
      })
      setIsEditingByMe(false)
      setEditingBy(null)
      // Notify other clients: editing stopped + send final features
      socketRef.current?.emit('editing-changed', {
        projectId: currentProject.id,
        editing: false,
        editingBy: null,
        sessionId: sessionIdRef.current,
      })
      socketRef.current?.emit('features-updated', {
        projectId: currentProject.id,
        features: currentFeatures,
      })
      toast({ title: 'Bearbeitung beendet', description: 'Änderungen gespeichert. Andere können jetzt bearbeiten.' })
    } catch (e) {
      toast({ title: 'Fehler', description: 'Konnte Bearbeitung nicht beenden.', variant: 'destructive' })
    } finally {
      setEditingLoading(false)
    }
  }, [currentProject?.id, toast, featuresRef])

  return {
    editingBy,
    isEditingByMe,
    editingLoading,
    socketRef,
    broadcastFeatures,
    handleStartEditing,
    handleStopEditing,
  }
}
