import React, { useState, useEffect, useCallback } from 'react'
import { createRoot } from 'react-dom/client'
import { getEcho } from '../../bootstrap'
import ConversationList from '../../components/messaging/ConversationList'
import ConversationThread from '../../components/messaging/ConversationThread'
import NewConversationModal from '../../components/messaging/NewConversationModal'

function getCsrf() {
  return document.querySelector('meta[name="csrf-token"]')?.content ?? ''
}

async function apiFetch(url, options = {}) {
  const isFormData = options.body instanceof FormData
  const socketId = getEcho()?.socketId?.()
  const headers = {
    'X-CSRF-TOKEN': getCsrf(),
    Accept: 'application/json',
    ...options.headers,
  }

  if (socketId) {
    headers['X-Socket-ID'] = socketId
  }

  if (!isFormData) {
    headers['Content-Type'] = 'application/json'
  }

  const res = await fetch(url, {
    headers,
    ...options,
  })

  if (!res.ok) {
    const err = await res.json().catch(() => ({}))
    throw new Error(err.message ?? `HTTP ${res.status}`)
  }

  return res.json()
}

function relativeTime(iso) {
  if (!iso) return 'No activity yet'

  const diff = (Date.now() - new Date(iso).getTime()) / 1000
  if (diff < 60) return 'Just now'
  if (diff < 3600) return `${Math.floor(diff / 60)}m ago`
  if (diff < 86400) return `${Math.floor(diff / 3600)}h ago`
  if (diff < 604800) return `${Math.floor(diff / 86400)}d ago`

  return new Date(iso).toLocaleDateString(undefined, {
    month: 'short',
    day: 'numeric',
  })
}

function buildSearchPreview(item) {
  const body = (item?.body || '').trim()
  return body || '(attachment only)'
}

function MessagesPage({ userId, username, activeConversationId: initialId }) {
  const [conversations, setConversations] = useState([])
  const [loadingConvs, setLoadingConvs] = useState(true)
  const [activeId, setActiveId] = useState(initialId ?? null)
  const [realtimeEnabled, setRealtimeEnabled] = useState(false)
  const [realtimeStatus, setRealtimeStatus] = useState('offline')
  const [typingByConversation, setTypingByConversation] = useState({})
  const [showNewModal, setShowNewModal] = useState(false)
  const [searchQuery, setSearchQuery] = useState('')
  const [searchResults, setSearchResults] = useState([])
  const [searching, setSearching] = useState(false)

  const loadConversations = useCallback(async () => {
    try {
      const data = await apiFetch('/api/messages/conversations')
      setConversations(data.data ?? [])
    } catch (e) {
      console.error('Failed to load conversations', e)
    } finally {
      setLoadingConvs(false)
    }
  }, [])

  useEffect(() => {
    loadConversations()

    apiFetch('/api/messages/settings')
      .then((data) => setRealtimeEnabled(!!data?.realtime_enabled))
      .catch(() => setRealtimeEnabled(false))
  }, [loadConversations])

  useEffect(() => {
    const handlePopState = () => {
      const match = window.location.pathname.match(/^\/messages\/(\d+)$/)
      setActiveId(match ? Number(match[1]) : null)
    }

    window.addEventListener('popstate', handlePopState)

    return () => window.removeEventListener('popstate', handlePopState)
  }, [])

  useEffect(() => {
    if (realtimeEnabled) {
      return undefined
    }

    const poll = window.setInterval(loadConversations, 15000)

    return () => window.clearInterval(poll)
  }, [loadConversations, realtimeEnabled])

  useEffect(() => {
    if (!realtimeEnabled || !userId) {
      setRealtimeStatus('offline')
      return undefined
    }

    const echo = getEcho()
    if (!echo) {
      setRealtimeStatus('offline')
      return undefined
    }

    const connection = echo.connector?.pusher?.connection
    let heartbeatId = null
    const mapConnectionState = (state) => {
      if (state === 'connected') {
        return 'connected'
      }

      if (state === 'connecting' || state === 'initialized' || state === 'connecting_in') {
        return 'connecting'
      }

      return 'offline'
    }

    const syncConnectionState = (payload = null) => {
      const nextState = typeof payload?.current === 'string'
        ? payload.current
        : connection?.state

      if (echo.socketId?.()) {
        setRealtimeStatus('connected')
        return
      }

      setRealtimeStatus(mapConnectionState(nextState))
    }

    const handleVisibilitySync = () => {
      if (document.visibilityState === 'visible') {
        syncConnectionState()
      }
    }

    syncConnectionState()
    connection?.bind?.('state_change', syncConnectionState)
    connection?.bind?.('connected', syncConnectionState)
    connection?.bind?.('unavailable', syncConnectionState)
    connection?.bind?.('disconnected', syncConnectionState)
    heartbeatId = window.setInterval(syncConnectionState, 1000)
    window.addEventListener('focus', syncConnectionState)
    document.addEventListener('visibilitychange', handleVisibilitySync)

    const channel = echo.private(`user.${userId}`)
    const handleConversationUpdated = (payload) => {
      const nextConversation = payload?.conversation
      if (!nextConversation?.id) {
        return
      }

      setConversations((prev) => mergeConversationSummary(prev, nextConversation))
    }

    channel.listen('.conversation.updated', handleConversationUpdated)

    return () => {
      connection?.unbind?.('state_change', syncConnectionState)
      connection?.unbind?.('connected', syncConnectionState)
      connection?.unbind?.('unavailable', syncConnectionState)
      connection?.unbind?.('disconnected', syncConnectionState)
      if (heartbeatId) {
        window.clearInterval(heartbeatId)
      }
      window.removeEventListener('focus', syncConnectionState)
      document.removeEventListener('visibilitychange', handleVisibilitySync)
      channel.stopListening('.conversation.updated', handleConversationUpdated)
      echo.leaveChannel(`private-user.${userId}`)
    }
  }, [realtimeEnabled, userId])

  useEffect(() => {
    if (!realtimeEnabled) {
      setTypingByConversation({})
      return undefined
    }

    const echo = getEcho()
    if (!echo || conversations.length === 0) {
      return undefined
    }

    const timers = new Map()
    const joinedChannels = []

    const removeTypingUser = (conversationId, userIdToRemove) => {
      const timerKey = `${conversationId}:${userIdToRemove}`
      const existingTimer = timers.get(timerKey)
      if (existingTimer) {
        window.clearTimeout(existingTimer)
        timers.delete(timerKey)
      }

      setTypingByConversation((prev) => {
        const current = prev[conversationId] ?? []
        const nextUsers = current.filter((user) => String(user.user_id ?? user.id) !== String(userIdToRemove))

        if (nextUsers.length === current.length) {
          return prev
        }

        if (nextUsers.length === 0) {
          const next = { ...prev }
          delete next[conversationId]
          return next
        }

        return {
          ...prev,
          [conversationId]: nextUsers,
        }
      })
    }

    conversations.forEach((conversation) => {
      if (!conversation?.id) {
        return
      }

      const conversationId = conversation.id
      const channel = echo.join(`conversation.${conversationId}`)
      joinedChannels.push(conversationId)

      channel
        .listen('.typing.started', (payload) => {
          const user = payload?.user
          if (!user?.id || user.id === userId) {
            return
          }

          setTypingByConversation((prev) => {
            const current = prev[conversationId] ?? []
            const index = current.findIndex((entry) => String(entry.user_id ?? entry.id) === String(user.id))
            const nextUser = { user_id: user.id, username: user.username }

            if (index === -1) {
              return {
                ...prev,
                [conversationId]: [...current, nextUser],
              }
            }

            const nextUsers = [...current]
            nextUsers[index] = { ...nextUsers[index], ...nextUser }
            return {
              ...prev,
              [conversationId]: nextUsers,
            }
          })

          const timerKey = `${conversationId}:${user.id}`
          const existingTimer = timers.get(timerKey)
          if (existingTimer) {
            window.clearTimeout(existingTimer)
          }

          const timeout = window.setTimeout(() => removeTypingUser(conversationId, user.id), Number(payload?.expires_in_ms ?? 3500))
          timers.set(timerKey, timeout)
        })
        .listen('.typing.stopped', (payload) => {
          const typingUserId = payload?.user?.id
          if (!typingUserId) {
            return
          }

          removeTypingUser(conversationId, typingUserId)
        })
    })

    return () => {
      timers.forEach((timer) => window.clearTimeout(timer))
      joinedChannels.forEach((conversationId) => {
        echo.leave(`conversation.${conversationId}`)
      })
    }
  }, [conversations, realtimeEnabled, userId])

  const handleSelectConversation = useCallback((id) => {
    setActiveId(id)
    history.replaceState(null, '', `/messages/${id}`)
  }, [])

  const handleConversationCreated = useCallback((conv) => {
    setShowNewModal(false)
    loadConversations()
    setActiveId(conv.id)
    history.replaceState(null, '', `/messages/${conv.id}`)
  }, [loadConversations])

  const handleMarkRead = useCallback((conversationId) => {
    setConversations((prev) => prev.map((conversation) => (
      conversation.id === conversationId
        ? { ...conversation, unread_count: 0 }
        : conversation
    )))
  }, [])

  const handleConversationPatched = useCallback((patch) => {
    if (!patch?.id) {
      return
    }

    setConversations((prev) => mergeConversationSummary(prev, patch))
  }, [])

  useEffect(() => {
    let cancelled = false

    const run = async () => {
      const q = searchQuery.trim()
      if (q.length < 2) {
        setSearchResults([])
        setSearching(false)
        return
      }

      setSearching(true)

      try {
        const data = await apiFetch(`/api/messages/search?q=${encodeURIComponent(q)}`)
        if (!cancelled) {
          setSearchResults(data.data ?? [])
        }
      } catch {
        if (!cancelled) {
          setSearchResults([])
        }
      } finally {
        if (!cancelled) {
          setSearching(false)
        }
      }
    }

    const timer = setTimeout(run, 250)
    return () => {
      cancelled = true
      clearTimeout(timer)
    }
  }, [searchQuery])

  const openSearchResult = useCallback((item) => {
    if (!item?.conversation_id) return
    setActiveId(item.conversation_id)
    history.replaceState(null, '', `/messages/${item.conversation_id}?focus=${item.id}`)
  }, [])

  const activeConversation = conversations.find((conversation) => conversation.id === activeId) ?? null
  const unreadCount = conversations.reduce((sum, conversation) => sum + Number(conversation.unread_count || 0), 0)
  const pinnedCount = conversations.reduce((sum, conversation) => {
    const me = conversation.my_participant ?? conversation.all_participants?.find((participant) => participant.user_id === userId)
    return sum + (me?.is_pinned ? 1 : 0)
  }, 0)
  const archivedCount = conversations.reduce((sum, conversation) => {
    const me = conversation.my_participant ?? conversation.all_participants?.find((participant) => participant.user_id === userId)
    return sum + (me?.is_archived ? 1 : 0)
  }, 0)
  const activeSearch = searchQuery.trim().length >= 2
  const activeConversationLabel = activeConversation?.title
    || activeConversation?.all_participants?.find((participant) => participant.user_id !== userId)?.user?.username
    || 'Conversation'

  return (
    <div className="messages-page px-4 pb-16 pt-4 md:px-6 lg:px-8 lg:pt-6">
      <div className="grid gap-5 lg:items-start lg:grid-cols-[340px_minmax(0,1fr)] xl:grid-cols-[360px_minmax(0,1fr)] xl:gap-6">
        <aside className={`flex min-h-[calc(100vh-18rem)] flex-col overflow-hidden rounded-[30px] border border-white/[0.06] bg-[linear-gradient(180deg,rgba(10,16,26,0.96),rgba(7,11,18,0.92))] shadow-[0_20px_60px_rgba(0,0,0,0.28)] lg:sticky lg:top-6 lg:max-h-[calc(100vh-3rem)] ${activeId ? 'hidden lg:flex' : 'flex'}`}>
          <div className="border-b border-white/[0.06] p-5">
            <div className="flex items-start justify-between gap-4">
              <div>
                <p className="text-[11px] font-semibold uppercase tracking-[0.2em] text-white/35">Private inbox</p>
                <h2 className="mt-2 text-2xl font-semibold text-white">Messages</h2>
                <p className="mt-2 text-sm leading-6 text-white/50">Keep direct chats, group threads, and file drops in one focused workspace.</p>
              </div>
              <button
                onClick={() => setShowNewModal(true)}
                className="inline-flex h-11 items-center justify-center gap-2 rounded-full border border-sky-400/20 bg-sky-500/12 px-4 text-sm font-medium text-sky-200 transition hover:bg-sky-500/18"
                title="New message"
              >
                <i className="fa-solid fa-pen-to-square text-xs" />
                Compose
              </button>
            </div>

            <div className="mt-5 grid grid-cols-3 gap-2">
              <StatChip label="Unread" value={unreadCount} tone="sky" />
              <StatChip label="Pinned" value={pinnedCount} tone="amber" />
              <StatChip label="Archived" value={archivedCount} tone="slate" />
            </div>

            <div className="mt-4 flex flex-wrap gap-2 text-xs text-white/45">
              <span className={`inline-flex items-center gap-1.5 rounded-full border px-3 py-1 ${connectionBadgeClass(realtimeEnabled, realtimeStatus)}`}>
                <span className={`h-1.5 w-1.5 rounded-full ${connectionDotClass(realtimeEnabled, realtimeStatus)}`} />
                {connectionBadgeLabel(realtimeEnabled, realtimeStatus)}
              </span>
              <span className="inline-flex items-center gap-1.5 rounded-full border border-white/[0.08] bg-white/[0.04] px-3 py-1 text-white/55">
                <i className="fa-solid fa-comments text-[10px]" />
                {conversations.length} conversations
              </span>
            </div>
          </div>

          <div className="border-b border-white/[0.06] p-4">
            <label className="mb-2 block text-[11px] font-semibold uppercase tracking-[0.18em] text-white/35">Search all messages</label>
            <div className="rounded-2xl border border-white/[0.08] bg-black/15 px-3 py-2.5 transition focus-within:border-sky-400/30 focus-within:bg-black/25">
              <div className="flex items-center gap-3">
                <i className="fa-solid fa-magnifying-glass text-xs text-white/30" />
                <input
                  type="search"
                  value={searchQuery}
                  onChange={(e) => setSearchQuery(e.target.value)}
                  placeholder="Find text, attachments, or senders…"
                  className="w-full bg-transparent text-sm text-white outline-none placeholder:text-white/25"
                />
              </div>
            </div>
            {searching ? <p className="mt-2 text-[11px] text-white/35">Searching across your inbox…</p> : null}
          </div>

          {activeSearch ? (
            <div className="border-b border-white/[0.06] px-3 py-3">
              <div className="mb-2 flex items-center justify-between px-2">
                <p className="text-[11px] font-semibold uppercase tracking-[0.18em] text-white/35">Search results</p>
                <span className="text-[11px] text-white/30">{searchResults.length}</span>
              </div>

              <div className="max-h-64 space-y-2 overflow-y-auto pr-1">
                {searchResults.length === 0 && !searching ? (
                  <div className="rounded-2xl border border-white/[0.06] bg-white/[0.025] px-4 py-4 text-sm text-white/40">
                    No results matched “{searchQuery.trim()}”.
                  </div>
                ) : null}

                {searchResults.map((item) => (
                  <button
                    key={`search-${item.id}`}
                    onClick={() => openSearchResult(item)}
                    className="block w-full rounded-2xl border border-white/[0.06] bg-white/[0.03] px-4 py-3 text-left transition hover:border-sky-400/20 hover:bg-sky-500/[0.08]"
                  >
                    <div className="flex items-center justify-between gap-3 text-[11px] text-white/35">
                      <span>@{item.sender?.username ?? 'unknown'}</span>
                      <span>{relativeTime(item.created_at)}</span>
                    </div>
                    <p className="mt-2 line-clamp-2 text-sm leading-6 text-white/78">{buildSearchPreview(item)}</p>
                  </button>
                ))}
              </div>
            </div>
          ) : null}

          <ConversationList
            conversations={conversations}
            loading={loadingConvs}
            activeId={activeId}
            currentUserId={userId}
            typingByConversation={typingByConversation}
            onSelect={handleSelectConversation}
          />
        </aside>

        <main className={`flex min-h-[calc(100vh-18rem)] min-w-0 flex-col overflow-hidden rounded-[30px] border border-white/[0.06] bg-[linear-gradient(180deg,rgba(10,16,26,0.96),rgba(7,11,18,0.92))] shadow-[0_20px_60px_rgba(0,0,0,0.28)] lg:h-[calc(100vh-3rem)] lg:max-h-[calc(100vh-3rem)] ${activeId ? 'flex' : 'hidden lg:flex'}`}>
          {activeId ? (
            <ConversationThread
              key={activeId}
              conversationId={activeId}
              conversation={activeConversation}
              realtimeEnabled={realtimeEnabled}
              realtimeStatus={realtimeStatus}
              currentUserId={userId}
              currentUsername={username}
              apiFetch={apiFetch}
              onBack={() => {
                setActiveId(null)
                history.replaceState(null, '', '/messages')
              }}
              onMarkRead={handleMarkRead}
              onConversationPatched={handleConversationPatched}
            />
          ) : (
            <div className="flex flex-1 items-center justify-center p-8">
              <div className="max-w-xl text-center">
                <div className="mx-auto flex h-18 w-18 items-center justify-center rounded-[26px] border border-white/[0.08] bg-white/[0.03] text-white/35 shadow-[0_18px_45px_rgba(0,0,0,0.22)]">
                  <i className="fa-solid fa-comments text-3xl" />
                </div>
                <h2 className="mt-6 text-3xl font-semibold text-white">Choose a conversation</h2>
                <p className="mt-3 text-sm leading-7 text-white/55">Jump back into a direct message, catch up on a group thread, or start a new conversation with creators and collaborators.</p>
                <div className="mt-6 flex flex-wrap items-center justify-center gap-2 text-sm text-white/55">
                  <span className="rounded-full border border-white/[0.08] bg-white/[0.04] px-4 py-2">Search your full message history</span>
                  <span className="rounded-full border border-white/[0.08] bg-white/[0.04] px-4 py-2">Share files inline</span>
                  <span className="rounded-full border border-white/[0.08] bg-white/[0.04] px-4 py-2">Track seen status</span>
                </div>
              </div>
            </div>
          )}
        </main>
      </div>

      {activeId ? (
        <div className="mt-4 rounded-2xl border border-white/[0.06] bg-white/[0.03] px-4 py-3 text-xs text-white/45 lg:hidden">
          Viewing <span className="font-medium text-white/75">{activeConversationLabel}</span>. Use the back button to return to your inbox list.
        </div>
      ) : null}

      {showNewModal ? (
        <NewConversationModal
          currentUserId={userId}
          apiFetch={apiFetch}
          onCreated={handleConversationCreated}
          onClose={() => setShowNewModal(false)}
        />
      ) : null}
    </div>
  )
}

function StatChip({ label, value, tone = 'sky' }) {
  const tones = {
    sky: 'border-sky-400/20 bg-sky-500/10 text-sky-200',
    amber: 'border-amber-400/20 bg-amber-500/10 text-amber-200',
    slate: 'border-white/[0.08] bg-white/[0.04] text-white/65',
  }

  return (
    <div className={`rounded-2xl border px-3 py-3 ${tones[tone] || tones.sky}`}>
      <p className="text-[10px] font-semibold uppercase tracking-[0.18em] opacity-70">{label}</p>
      <p className="mt-2 text-lg font-semibold">{Number(value || 0).toLocaleString()}</p>
    </div>
  )
}

function mergeConversationSummary(existing, incoming) {
  const next = [...existing]
  const index = next.findIndex((conversation) => conversation.id === incoming.id)

  if (index >= 0) {
    next[index] = { ...next[index], ...incoming }
  } else {
    next.unshift(incoming)
  }

  return next.sort((left, right) => {
    const leftPinned = left.my_participant?.is_pinned ? 1 : 0
    const rightPinned = right.my_participant?.is_pinned ? 1 : 0
    if (leftPinned !== rightPinned) {
      return rightPinned - leftPinned
    }

    const leftPinnedAt = left.my_participant?.pinned_at ? new Date(left.my_participant.pinned_at).getTime() : 0
    const rightPinnedAt = right.my_participant?.pinned_at ? new Date(right.my_participant.pinned_at).getTime() : 0
    if (leftPinnedAt !== rightPinnedAt) {
      return rightPinnedAt - leftPinnedAt
    }

    const leftTime = left.last_message_at ? new Date(left.last_message_at).getTime() : 0
    const rightTime = right.last_message_at ? new Date(right.last_message_at).getTime() : 0
    return rightTime - leftTime
  })
}

function connectionBadgeClass(realtimeEnabled, realtimeStatus) {
  if (!realtimeEnabled) {
    return 'border-white/[0.08] bg-white/[0.04] text-white/55'
  }

  if (realtimeStatus === 'connected') {
    return 'border-emerald-400/20 bg-emerald-500/10 text-emerald-200'
  }

  if (realtimeStatus === 'connecting') {
    return 'border-amber-400/20 bg-amber-500/10 text-amber-200'
  }

  return 'border-rose-400/18 bg-rose-500/10 text-rose-200'
}

function connectionDotClass(realtimeEnabled, realtimeStatus) {
  if (!realtimeEnabled) {
    return 'bg-white/30'
  }

  if (realtimeStatus === 'connected') {
    return 'bg-emerald-300'
  }

  if (realtimeStatus === 'connecting') {
    return 'bg-amber-300'
  }

  return 'bg-rose-300'
}

function connectionBadgeLabel(realtimeEnabled, realtimeStatus) {
  if (!realtimeEnabled) {
    return 'Polling every 15s'
  }

  if (realtimeStatus === 'connected') {
    return 'Realtime connected'
  }

  if (realtimeStatus === 'connecting') {
    return 'Realtime connecting'
  }

  return 'Realtime disconnected'
}

const el = document.getElementById('messages-root')

if (el) {
  function parse(key, fallback = null) {
    try {
      return JSON.parse(el.dataset[key] ?? 'null') ?? fallback
    } catch {
      return fallback
    }
  }

  createRoot(el).render(
    <MessagesPage
      userId={parse('userId')}
      username={parse('username', '')}
      activeConversationId={parse('activeConversationId')}
    />,
  )
}

export default MessagesPage
