import { useState, useEffect, useRef, useCallback } from 'react'
import {
  MessageSquare, X, ChevronLeft, Send, Users, Loader2,
  Search, Bell, Settings, PenSquare, BellOff, Volume2, VolumeX, Paperclip, Pin,
} from 'lucide-react'
import { api, type ChatRoom, type ChatMessage, type ChatSearchResult, type ChatReaction } from '../../lib/api'
import type { User } from '../../types'
import { useAuth } from '../../contexts/AuthContext'
import { cn } from '../../lib/utils'

// ── Helpers ────────────────────────────────────────────────────────────────

function avatarColor(name: string) {
  const colors = ['#4F46E5','#059669','#2563EB','#7C3AED','#DC2626','#D97706','#DB2777','#0891B2']
  let h = 0
  for (let i = 0; i < name.length; i++) h = (h * 31 + name.charCodeAt(i)) % colors.length
  return colors[h]
}
function initials(name: string) {
  return name.split(' ').map(p => p[0]).join('').toUpperCase().slice(0, 2)
}
function fmtTime(iso: string) {
  const d = new Date(iso), now = new Date()
  if (now.toDateString() === d.toDateString())
    return d.toLocaleTimeString('ru-RU', { hour: '2-digit', minute: '2-digit' })
  return d.toLocaleDateString('ru-RU', { day: 'numeric', month: 'short' })
}

function Avatar({ name, size = 28, online = false }: { name: string; size?: number; online?: boolean }) {
  return (
    <div className="relative shrink-0" style={{ width: size, height: size }}>
      <div style={{ width: size, height: size, background: avatarColor(name), fontSize: size * 0.38 }}
        className="rounded-full flex items-center justify-center text-white font-semibold">
        {initials(name)}
      </div>
      {online && (
        <span className="absolute bottom-0 right-0 w-2.5 h-2.5 rounded-full bg-green-500 border-2 border-white dark:border-slate-800" />
      )}
    </div>
  )
}
function SystemAvatar({ size = 28 }: { size?: number }) {
  return (
    <div style={{ width: size, height: size }}
      className="rounded-full flex items-center justify-center shrink-0 bg-amber-100 dark:bg-amber-900/30 text-amber-600 dark:text-amber-400">
      <Bell size={size * 0.5} />
    </div>
  )
}

// ── Sound ──────────────────────────────────────────────────────────────────

function playNotifSound() {
  try {
    const AudioCtx = window.AudioContext ?? (window as unknown as { webkitAudioContext: typeof AudioContext }).webkitAudioContext
    const ctx = new AudioCtx()
    const osc = ctx.createOscillator(), gain = ctx.createGain()
    osc.connect(gain); gain.connect(ctx.destination)
    osc.frequency.value = 880
    gain.gain.setValueAtTime(0.18, ctx.currentTime)
    gain.gain.exponentialRampToValueAtTime(0.001, ctx.currentTime + 0.25)
    osc.start(ctx.currentTime); osc.stop(ctx.currentTime + 0.25)
    setTimeout(() => ctx.close(), 600)
  } catch { /* ignore */ }
}

// ── Persist ────────────────────────────────────────────────────────────────

interface ChatSettings { notifVisible: boolean; soundEnabled: boolean }
const SETTINGS_KEY = 'hotelsync-chat-settings'
const PINS_KEY = 'hotelsync-chat-pins'

function loadSettings(): ChatSettings {
  try {
    const s = localStorage.getItem(SETTINGS_KEY)
    if (s) return { notifVisible: true, soundEnabled: false, ...JSON.parse(s) as Partial<ChatSettings> }
  } catch { /**/ }
  return { notifVisible: true, soundEnabled: false }
}
function saveSettings(s: ChatSettings) { localStorage.setItem(SETTINGS_KEY, JSON.stringify(s)) }
function loadPins(): string[] {
  try { return JSON.parse(localStorage.getItem(PINS_KEY) ?? '[]') as string[] } catch { return [] }
}
function savePins(ids: string[]) { localStorage.setItem(PINS_KEY, JSON.stringify(ids)) }

// ── Context menu ───────────────────────────────────────────────────────────

type CtxMenu =
  | { type: 'message'; x: number; y: number; msg: ChatMessage }
  | { type: 'room';    x: number; y: number; room: ChatRoom }

const EMOJIS = ['👍','❤️','😂','😮','😢','🔥','👏','✅']

// ── Main ───────────────────────────────────────────────────────────────────

type View = 'rooms' | 'messages' | 'search' | 'settings'

export function ChatWidget() {
  const { user } = useAuth()
  const slug = user?.hotelSlug ?? ''

  const [open, setOpen]               = useState(false)
  const [view, setView]               = useState<View>('rooms')
  const [rooms, setRooms]             = useState<ChatRoom[]>([])
  const [activeRoom, setActiveRoom]   = useState<ChatRoom | null>(null)
  const [messages, setMessages]       = useState<ChatMessage[]>([])
  const [text, setText]               = useState('')
  const [loadingRooms, setLoadingRooms] = useState(false)
  const [loadingMsgs, setLoadingMsgs]   = useState(false)
  const [sending, setSending]           = useState(false)
  const [attachment, setAttachment]     = useState<File | null>(null)
  const [attachPreview, setAttachPreview] = useState<string | null>(null)

  // Edit
  const [editingMsgId, setEditingMsgId] = useState<string | null>(null)
  const [editText, setEditText]         = useState('')

  // Context menu
  const [ctxMenu, setCtxMenu] = useState<CtxMenu | null>(null)

  // Typing & presence
  const [typingNames, setTypingNames]  = useState<string[]>([])
  const [onlineIds, setOnlineIds]      = useState<string[]>([])

  // Search
  const [searchQuery, setSearchQuery]     = useState('')
  const [searchUsers, setSearchUsers]     = useState<User[]>([])
  const [searchResults, setSearchResults] = useState<ChatSearchResult[]>([])
  const [searchTab, setSearchTab]         = useState<'people' | 'messages'>('people')
  const [loadingSearch, setLoadingSearch] = useState(false)
  const [allUsers, setAllUsers]           = useState<User[]>([])

  // Settings + pins
  const [settings, setSettings] = useState<ChatSettings>(loadSettings)
  const [pinnedIds, setPinnedIds] = useState<string[]>(loadPins)

  const messagesEndRef  = useRef<HTMLDivElement>(null)
  const pollRef         = useRef<ReturnType<typeof setInterval> | null>(null)
  const searchInputRef  = useRef<HTMLInputElement>(null)
  const fileInputRef    = useRef<HTMLInputElement>(null)
  const prevUnreadRef   = useRef<number>(0)
  const prevMsgCountRef = useRef<number>(0)
  const typingTimerRef  = useRef<ReturnType<typeof setTimeout> | null>(null)
  const isTypingRef     = useRef(false)

  const totalUnread = rooms.reduce((s, r) => s + (Number(r.unreadCount) || 0), 0)

  const visibleRooms = [...rooms]
    .filter(r => !(r.type === 'notifications' && !settings.notifVisible))
    .sort((a, b) => (pinnedIds.includes(a.id) ? 0 : 1) - (pinnedIds.includes(b.id) ? 0 : 1))

  // ── Effects ───────────────────────────────────────────────────────────────

  const loadRooms = useCallback(async () => {
    if (!slug) return
    try {
      const data = await api.chat.listRooms(slug)
      setRooms(data)
      if (settings.soundEnabled && !open) {
        const n = data.reduce((s, r) => s + (Number(r.unreadCount) || 0), 0)
        if (n > prevUnreadRef.current) playNotifSound()
        prevUnreadRef.current = n
      }
    } catch { /**/ }
  // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [slug, settings.soundEnabled, open])

  // Poll rooms
  useEffect(() => {
    if (!open || !slug) return
    setLoadingRooms(true)
    loadRooms().finally(() => setLoadingRooms(false))
    pollRef.current = setInterval(loadRooms, 5000)
    return () => { if (pollRef.current) clearInterval(pollRef.current) }
  }, [open, slug, loadRooms])

  // Presence heartbeat
  useEffect(() => {
    if (!open || !slug) return
    api.chat.setPresence(slug).catch(() => {/**/})
    api.chat.getPresence(slug).then(setOnlineIds).catch(() => {/**/})
    const t = setInterval(async () => {
      api.chat.setPresence(slug).catch(() => {/**/})
      api.chat.getPresence(slug).then(setOnlineIds).catch(() => {/**/})
    }, 30_000)
    return () => clearInterval(t)
  }, [open, slug])

  // Auto-scroll
  useEffect(() => { messagesEndRef.current?.scrollIntoView({ behavior: 'smooth' }) }, [messages])

  // Poll messages + typing
  useEffect(() => {
    if (view !== 'messages' || !activeRoom) return
    const interval = setInterval(async () => {
      try {
        const [msgs, typing] = await Promise.all([
          api.chat.getMessages(slug, activeRoom.id),
          api.chat.getTyping(slug, activeRoom.id),
        ])
        if (settings.soundEnabled && msgs.length > prevMsgCountRef.current && prevMsgCountRef.current > 0) {
          const last = msgs[msgs.length - 1]
          if (last.senderId !== user?.id) playNotifSound()
        }
        prevMsgCountRef.current = msgs.length
        setMessages(msgs)
        setTypingNames(typing)
      } catch { /**/ }
    }, 2000)
    return () => clearInterval(interval)
  }, [view, activeRoom, slug, settings.soundEnabled, user?.id])

  // Clear typing names when leaving room
  useEffect(() => { if (view !== 'messages') setTypingNames([]) }, [view])

  // Search users
  useEffect(() => {
    if (view !== 'search' || !slug) return
    api.users.list(slug).then(setAllUsers).catch(() => {/**/})
  }, [view, slug])

  // Search effect
  useEffect(() => {
    if (view !== 'search') return
    const q = searchQuery.trim()
    if (!q) { setSearchUsers([]); setSearchResults([]); return }
    const ql = q.toLowerCase()
    setSearchUsers(allUsers.filter(u => u.id !== user?.id && (u.name.toLowerCase().includes(ql) || u.email.toLowerCase().includes(ql))))
    const timer = setTimeout(async () => {
      if (searchTab !== 'messages') return
      setLoadingSearch(true)
      try { setSearchResults(await api.chat.search(slug, q)) } catch { /**/ }
      finally { setLoadingSearch(false) }
    }, 400)
    return () => clearTimeout(timer)
  }, [searchQuery, view, allUsers, user?.id, slug, searchTab])

  useEffect(() => {
    if (view === 'search') setTimeout(() => searchInputRef.current?.focus(), 50)
  }, [view])

  // Close ctx menu on outside click
  useEffect(() => {
    if (!ctxMenu) return
    const h = () => setCtxMenu(null)
    window.addEventListener('click', h)
    return () => window.removeEventListener('click', h)
  }, [ctxMenu])

  // ── Typing helpers ────────────────────────────────────────────────────────

  const sendTyping = useCallback((typing: boolean) => {
    if (!activeRoom || !slug) return
    if (typing === isTypingRef.current) return
    isTypingRef.current = typing
    api.chat.setTyping(slug, activeRoom.id, typing).catch(() => {/**/})
  }, [activeRoom, slug])

  const handleTextChange = (val: string) => {
    setText(val)
    if (!val.trim()) { sendTyping(false); return }
    sendTyping(true)
    if (typingTimerRef.current) clearTimeout(typingTimerRef.current)
    typingTimerRef.current = setTimeout(() => sendTyping(false), 4000)
  }

  // ── Actions ───────────────────────────────────────────────────────────────

  const openRoom = async (room: ChatRoom) => {
    setActiveRoom(room); setView('messages'); setLoadingMsgs(true)
    prevMsgCountRef.current = 0; isTypingRef.current = false
    try {
      const msgs = await api.chat.getMessages(slug, room.id)
      prevMsgCountRef.current = msgs.length
      setMessages(msgs)
      await api.chat.markRead(slug, room.id)
      setRooms(prev => prev.map(r => r.id === room.id ? { ...r, unreadCount: 0 } : r))
    } catch { /**/ }
    finally { setLoadingMsgs(false) }
  }

  const openDirect = async (targetUser: User) => {
    try {
      const { roomId } = await api.chat.openDirect(slug, targetUser.id)
      await loadRooms()
      await openRoom({ id: roomId, type: 'direct', name: null, unreadCount: 0, lastMessage: null, lastMessageAt: null, lastSender: null, otherUserName: targetUser.name, otherUserId: targetUser.id, otherUserLastRead: null })
      setSearchQuery('')
    } catch { /**/ }
  }

  const openSearchResult = async (result: ChatSearchResult) => {
    const room = rooms.find(r => r.id === result.roomId)
    if (room) { setSearchQuery(''); setView('rooms'); await openRoom(room) }
  }

  const handleFileSelect = (e: React.ChangeEvent<HTMLInputElement>) => {
    const file = e.target.files?.[0]; if (!file) return
    setAttachment(file)
    const reader = new FileReader()
    reader.onload = ev => setAttachPreview(ev.target?.result as string)
    reader.readAsDataURL(file); e.target.value = ''
  }

  const sendMessage = async () => {
    if ((!text.trim() && !attachment) || !activeRoom || sending) return
    const t = text.trim(), file = attachment
    setText(''); setAttachment(null); setAttachPreview(null); setSending(true)
    sendTyping(false)
    try {
      let url: string | undefined
      if (file) { url = (await api.chat.uploadImage(file)).url }
      const msg = await api.chat.sendMessage(slug, activeRoom.id, t, url)
      setMessages(prev => { prevMsgCountRef.current = prev.length + 1; return [...prev, msg] })
    } catch {
      setText(t)
      if (file) { setAttachment(file); setAttachPreview(attachPreview) }
    } finally { setSending(false) }
  }

  const handleKeyDown = (e: React.KeyboardEvent) => {
    if (e.key === 'Enter' && !e.shiftKey) { e.preventDefault(); if (text.trim() || attachment) void sendMessage() }
  }

  const startEdit = (msg: ChatMessage) => { setEditingMsgId(msg.id); setEditText(msg.text); setCtxMenu(null) }
  const saveEdit = async (msg: ChatMessage) => {
    if (!editText.trim() || !activeRoom) return
    try {
      const updated = await api.chat.editMessage(slug, activeRoom.id, msg.id, editText)
      setMessages(prev => prev.map(m => m.id === msg.id ? updated : m))
    } catch { /**/ }
    setEditingMsgId(null)
  }

  const deleteMsg = async (msg: ChatMessage) => {
    if (!activeRoom) return; setCtxMenu(null)
    try {
      await api.chat.deleteMessage(slug, activeRoom.id, msg.id)
      setMessages(prev => prev.map(m => m.id === msg.id ? { ...m, deletedAt: new Date().toISOString(), text: '', attachmentUrl: null } : m))
    } catch { /**/ }
  }

  const toggleReaction = async (msg: ChatMessage, emoji: string) => {
    if (!activeRoom) return; setCtxMenu(null)
    try {
      const updated = await api.chat.toggleReaction(slug, activeRoom.id, msg.id, emoji)
      setMessages(prev => prev.map(m => m.id === msg.id ? updated : m))
    } catch { /**/ }
  }

  const togglePin = (roomId: string) => {
    setCtxMenu(null)
    const next = pinnedIds.includes(roomId) ? pinnedIds.filter(id => id !== roomId) : [...pinnedIds, roomId]
    setPinnedIds(next); savePins(next)
  }

  const updateSettings = (patch: Partial<ChatSettings>) => {
    const next = { ...settings, ...patch }; setSettings(next); saveSettings(next)
  }

  const goBack = () => {
    if (view === 'messages') {
      sendTyping(false)
      setView('rooms'); setActiveRoom(null); setEditingMsgId(null); setTypingNames([])
    }
    else if (view === 'search') { setView('rooms'); setSearchQuery('') }
    else if (view === 'settings') setView('rooms')
  }

  const roomDisplayName = (room: ChatRoom) =>
    room.type === 'general' ? 'Общий чат' : room.type === 'notifications' ? 'Уведомления' : room.otherUserName ?? 'Чат'

  const isOtherOnline = (room: ChatRoom) =>
    room.type === 'direct' && room.otherUserId ? onlineIds.includes(room.otherUserId) : false

  // Read receipt: is message read by other side?
  const isRead = (msg: ChatMessage) => {
    if (!activeRoom || activeRoom.type !== 'direct' || !activeRoom.otherUserLastRead) return false
    return new Date(msg.createdAt) <= new Date(activeRoom.otherUserLastRead)
  }

  if (!slug) return null

  // ── Render ────────────────────────────────────────────────────────────────

  return (
    <>
      {/* Float button */}
      <button onClick={() => setOpen(v => !v)}
        className={cn('fixed bottom-6 right-6 z-50 w-14 h-14 rounded-full shadow-lg bg-brand-600 hover:bg-brand-700 text-white transition-all flex items-center justify-center', open && 'scale-90')}>
        {open ? <X size={22} /> : <MessageSquare size={22} />}
        {!open && totalUnread > 0 && (
          <span className="absolute -top-1 -right-1 w-5 h-5 rounded-full bg-red-500 text-white text-[10px] font-bold flex items-center justify-center">
            {totalUnread > 9 ? '9+' : totalUnread}
          </span>
        )}
      </button>

      {/* Panel */}
      {open && (
        <div className="fixed bottom-24 right-6 z-50 w-80 bg-white dark:bg-slate-800 rounded-2xl shadow-2xl border border-slate-200 dark:border-slate-700 flex flex-col overflow-hidden" style={{ height: 520 }}>

          {/* Header */}
          <div className="flex items-center gap-2 px-3 py-3 border-b border-slate-200 dark:border-slate-700 bg-brand-600 text-white rounded-t-2xl shrink-0">
            {view !== 'rooms' && (
              <button onClick={goBack} className="p-1 hover:bg-white/20 rounded-lg transition-colors shrink-0">
                <ChevronLeft size={16} />
              </button>
            )}
            <div className="flex-1 min-w-0">
              <div className="flex items-center gap-2">
                <p className="font-semibold text-sm truncate">
                  {view === 'rooms' && 'Чат сотрудников'}
                  {view === 'messages' && (activeRoom ? roomDisplayName(activeRoom) : 'Чат')}
                  {view === 'search' && 'Поиск'}
                  {view === 'settings' && 'Настройки чата'}
                </p>
                {/* Online dot in messages header */}
                {view === 'messages' && activeRoom && isOtherOnline(activeRoom) && (
                  <span className="w-2 h-2 rounded-full bg-green-400 shrink-0" />
                )}
              </div>
              {view === 'rooms' && visibleRooms.length > 0 && <p className="text-xs text-white/70">{visibleRooms.length} чатов</p>}
              {view === 'messages' && activeRoom?.type === 'direct' && isOtherOnline(activeRoom) && (
                <p className="text-xs text-green-300">в сети</p>
              )}
              {view === 'messages' && activeRoom?.type === 'notifications' && (
                <p className="text-xs text-white/70">Только чтение</p>
              )}
            </div>
            {view === 'rooms' && (
              <div className="flex items-center gap-0.5">
                <button onClick={() => setView('search')} className="p-1.5 hover:bg-white/20 rounded-lg transition-colors"><Search size={15} /></button>
                <button onClick={() => setView('search')} className="p-1.5 hover:bg-white/20 rounded-lg transition-colors"><PenSquare size={15} /></button>
                <button onClick={() => setView('settings')} className="p-1.5 hover:bg-white/20 rounded-lg transition-colors"><Settings size={15} /></button>
              </div>
            )}
          </div>

          {/* ── Rooms ── */}
          {view === 'rooms' && (
            <div className="flex-1 overflow-y-auto">
              {loadingRooms && visibleRooms.length === 0 ? (
                <>
                  <RoomSkeleton icon={<Users size={16} className="text-brand-400" />} bg="bg-brand-100 dark:bg-brand-900/30" label="Общий чат" />
                  {settings.notifVisible && <RoomSkeleton icon={<Bell size={16} className="text-amber-500" />} bg="bg-amber-100 dark:bg-amber-900/30" label="Уведомления" />}
                </>
              ) : (
                visibleRooms.map(room => (
                  <RoomRow
                    key={room.id}
                    room={room}
                    isPinned={pinnedIds.includes(room.id)}
                    isOnline={isOtherOnline(room)}
                    onClick={() => openRoom(room)}
                    onMenuClick={e => { e.stopPropagation(); setCtxMenu({ type: 'room', x: e.clientX, y: e.clientY, room }) }}
                  />
                ))
              )}
            </div>
          )}

          {/* ── Search ── */}
          {view === 'search' && (
            <div className="flex-1 flex flex-col overflow-hidden">
              <div className="px-3 pt-3 pb-2 shrink-0">
                <div className="flex items-center gap-2 bg-slate-100 dark:bg-slate-700 rounded-xl px-3 py-2">
                  <Search size={14} className="text-slate-400 shrink-0" />
                  <input ref={searchInputRef} value={searchQuery} onChange={e => setSearchQuery(e.target.value)}
                    placeholder="Люди или сообщения..."
                    className="flex-1 bg-transparent text-sm text-slate-800 dark:text-slate-200 placeholder-slate-400 outline-none" />
                  {searchQuery && <button onClick={() => setSearchQuery('')} className="text-slate-400 hover:text-slate-600"><X size={13} /></button>}
                </div>
              </div>
              <div className="flex px-3 gap-1 shrink-0 mb-1">
                {(['people', 'messages'] as const).map(tab => (
                  <button key={tab} onClick={() => setSearchTab(tab)}
                    className={cn('flex-1 py-1.5 text-xs font-medium rounded-lg transition-colors', searchTab === tab ? 'bg-brand-600 text-white' : 'text-slate-500 hover:bg-slate-100 dark:hover:bg-slate-700')}>
                    {tab === 'people' ? 'Люди' : 'Сообщения'}
                  </button>
                ))}
              </div>
              <div className="flex-1 overflow-y-auto">
                {searchTab === 'people' && (
                  !searchQuery
                    ? allUsers.length === 0
                      ? <div className="text-center py-6 text-xs text-slate-400">Загрузка...</div>
                      : allUsers.filter(u => u.id !== user?.id).map(u => (
                          <UserRow key={u.id} user={u} online={onlineIds.includes(u.id)} onClick={() => openDirect(u)} />
                        ))
                    : searchUsers.length === 0
                      ? <div className="text-center py-6 text-xs text-slate-400">Никого не найдено</div>
                      : searchUsers.map(u => <UserRow key={u.id} user={u} online={onlineIds.includes(u.id)} onClick={() => openDirect(u)} />)
                )}
                {searchTab === 'messages' && (
                  !searchQuery
                    ? <div className="text-center py-6 text-xs text-slate-400">Введите запрос для поиска</div>
                    : loadingSearch
                      ? <div className="flex items-center justify-center py-6"><Loader2 size={18} className="animate-spin text-slate-400" /></div>
                      : searchResults.length === 0
                        ? <div className="text-center py-6 text-xs text-slate-400">Ничего не найдено</div>
                        : searchResults.map(r => (
                          <button key={r.id} onClick={() => openSearchResult(r)}
                            className="w-full text-left px-4 py-2.5 hover:bg-slate-50 dark:hover:bg-slate-700/50 transition-colors border-b border-slate-100 dark:border-slate-700/50">
                            <p className="text-xs text-slate-500 mb-0.5">
                              {r.roomType === 'general' ? 'Общий чат' : r.roomType === 'notifications' ? 'Уведомления' : r.otherUserName ?? 'Чат'}
                              {' · '}{r.senderName.split(' ')[0]}
                            </p>
                            <p className="text-sm text-slate-800 dark:text-slate-200 truncate">{r.text}</p>
                            <p className="text-[10px] text-slate-400 mt-0.5">{fmtTime(r.createdAt)}</p>
                          </button>
                        ))
                )}
              </div>
            </div>
          )}

          {/* ── Settings ── */}
          {view === 'settings' && (
            <div className="flex-1 overflow-y-auto py-4 space-y-4">
              <SettingsSection title="Канал уведомлений">
                <ToggleRow icon={settings.notifVisible ? <Bell size={15} /> : <BellOff size={15} />}
                  label="Показывать канал уведомлений" checked={settings.notifVisible}
                  onChange={v => updateSettings({ notifVisible: v })} />
              </SettingsSection>
              <SettingsSection title="Звук">
                <ToggleRow icon={settings.soundEnabled ? <Volume2 size={15} /> : <VolumeX size={15} />}
                  label="Звуковое уведомление" checked={settings.soundEnabled}
                  onChange={v => updateSettings({ soundEnabled: v })} />
              </SettingsSection>
              <SettingsSection title="Подсказка">
                <p className="text-xs text-slate-500 leading-relaxed">
                  Правый клик на сообщении — реакции, редактирование, удаление.<br />
                  Кнопка ⋮ на чате — закрепить / открепить сверху.
                </p>
              </SettingsSection>
            </div>
          )}

          {/* ── Messages ── */}
          {view === 'messages' && (
            <>
              <div className="flex-1 overflow-y-auto px-3 py-3 space-y-2">
                {loadingMsgs ? (
                  <div className="flex items-center justify-center h-32"><Loader2 size={20} className="animate-spin text-slate-400" /></div>
                ) : messages.length === 0 ? (
                  <div className="text-center py-10 text-sm text-slate-400">
                    {activeRoom?.type === 'notifications' ? 'Уведомлений пока нет' : 'Нет сообщений. Напишите первым!'}
                  </div>
                ) : (
                  messages.map(msg => (
                    <MessageBubble
                      key={msg.id}
                      msg={msg}
                      isOwn={!msg.isSystem && msg.senderId === user?.id}
                      isEditing={editingMsgId === msg.id}
                      editText={editText}
                      isRead={isRead(msg)}
                      onContextMenu={e => {
                        if (msg.deletedAt || msg.isSystem || activeRoom?.type === 'notifications') return
                        e.preventDefault()
                        setCtxMenu({ type: 'message', x: e.clientX, y: e.clientY, msg })
                      }}
                      onEditTextChange={setEditText}
                      onSaveEdit={() => saveEdit(msg)}
                      onCancelEdit={() => setEditingMsgId(null)}
                      onReaction={emoji => toggleReaction(msg, emoji)}
                    />
                  ))
                )}

                {/* Typing indicator */}
                {typingNames.length > 0 && (
                  <div className="flex items-center gap-2 px-1">
                    <TypingDots />
                    <span className="text-xs text-slate-400">
                      {typingNames.length === 1
                        ? `${typingNames[0].split(' ')[0]} печатает...`
                        : `${typingNames.map(n => n.split(' ')[0]).join(', ')} печатают...`}
                    </span>
                  </div>
                )}

                <div ref={messagesEndRef} />
              </div>

              {activeRoom?.type !== 'notifications' && (
                <div className="px-3 py-3 border-t border-slate-200 dark:border-slate-700 shrink-0">
                  {attachPreview && (
                    <div className="relative inline-block mb-2">
                      <img src={attachPreview} alt="превью" className="h-16 rounded-lg object-cover border border-slate-200 dark:border-slate-600" />
                      <button onClick={() => { setAttachment(null); setAttachPreview(null) }}
                        className="absolute -top-1.5 -right-1.5 w-5 h-5 rounded-full bg-slate-700 text-white flex items-center justify-center hover:bg-red-500 transition-colors">
                        <X size={10} />
                      </button>
                    </div>
                  )}
                  <div className="flex items-end gap-1.5">
                    <input ref={fileInputRef} type="file" accept="image/jpeg,image/png,image/webp,image/gif" className="hidden" onChange={handleFileSelect} />
                    <button onClick={() => fileInputRef.current?.click()}
                      className="p-2 rounded-xl text-slate-400 hover:text-brand-600 hover:bg-slate-100 dark:hover:bg-slate-700 transition-colors shrink-0">
                      <Paperclip size={16} />
                    </button>
                    <textarea value={text} onChange={e => handleTextChange(e.target.value)} onKeyDown={handleKeyDown}
                      placeholder="Сообщение..." rows={1}
                      className="flex-1 resize-none rounded-xl px-3 py-2 text-sm bg-slate-100 dark:bg-slate-700 text-slate-900 dark:text-slate-100 placeholder-slate-400 outline-none max-h-24 overflow-y-auto" />
                    <button onClick={() => void sendMessage()} disabled={(!text.trim() && !attachment) || sending}
                      className="p-2.5 rounded-xl bg-brand-600 hover:bg-brand-700 disabled:opacity-40 text-white transition-colors shrink-0">
                      {sending ? <Loader2 size={16} className="animate-spin" /> : <Send size={16} />}
                    </button>
                  </div>
                  <p className="text-[10px] text-slate-400 mt-1.5">Enter — отправить · Shift+Enter — перенос</p>
                </div>
              )}

              {activeRoom?.type === 'notifications' && (
                <div className="px-4 py-3 border-t border-slate-200 dark:border-slate-700 bg-amber-50 dark:bg-amber-900/10 shrink-0">
                  <p className="text-xs text-amber-700 dark:text-amber-400 text-center flex items-center justify-center gap-1.5">
                    <Bell size={12} /> Канал только для чтения
                  </p>
                </div>
              )}
            </>
          )}
        </div>
      )}

      {/* Context menu */}
      {ctxMenu && (
        <ContextMenu menu={ctxMenu} currentUserId={user?.id ?? ''} pinnedIds={pinnedIds}
          onReaction={emoji => ctxMenu.type === 'message' && toggleReaction(ctxMenu.msg, emoji)}
          onEdit={() => ctxMenu.type === 'message' && startEdit(ctxMenu.msg)}
          onDelete={() => ctxMenu.type === 'message' && deleteMsg(ctxMenu.msg)}
          onTogglePin={() => ctxMenu.type === 'room' && togglePin(ctxMenu.room.id)}
          onClose={() => setCtxMenu(null)}
        />
      )}
    </>
  )
}

// ── RoomRow ────────────────────────────────────────────────────────────────

function RoomRow({ room, isPinned, isOnline, onClick, onMenuClick }: {
  room: ChatRoom; isPinned: boolean; isOnline: boolean
  onClick: () => void; onMenuClick: (e: React.MouseEvent) => void
}) {
  const [hovered, setHovered] = useState(false)
  return (
    <div
      className="group relative flex items-center gap-3 px-4 py-3 hover:bg-slate-50 dark:hover:bg-slate-700/50 transition-colors border-b border-slate-100 dark:border-slate-700/50 cursor-pointer"
      onClick={onClick}
      onMouseEnter={() => setHovered(true)}
      onMouseLeave={() => setHovered(false)}
    >
      {/* Avatar */}
      <div className="relative shrink-0">
        {room.type === 'general' ? (
          <div className="w-9 h-9 rounded-full bg-brand-100 dark:bg-brand-900/30 flex items-center justify-center">
            <Users size={16} className="text-brand-600 dark:text-brand-400" />
          </div>
        ) : room.type === 'notifications' ? (
          <div className="w-9 h-9 rounded-full bg-amber-100 dark:bg-amber-900/30 flex items-center justify-center">
            <Bell size={16} className="text-amber-600 dark:text-amber-400" />
          </div>
        ) : (
          <Avatar name={room.otherUserName ?? '?'} size={36} online={isOnline} />
        )}
        {Number(room.unreadCount) > 0 && (
          <span className="absolute -top-1 -right-1 w-4 h-4 rounded-full bg-red-500 text-white text-[9px] font-bold flex items-center justify-center">
            {room.unreadCount}
          </span>
        )}
      </div>

      {/* Text */}
      <div className="flex-1 min-w-0">
        <div className="flex items-center gap-1">
          {isPinned && <Pin size={10} className="text-brand-500 shrink-0" />}
          <p className={cn('text-sm font-medium truncate flex-1', Number(room.unreadCount) > 0 ? 'text-slate-900 dark:text-slate-100' : 'text-slate-700 dark:text-slate-300')}>
            {room.type === 'general' ? 'Общий чат' : room.type === 'notifications' ? 'Уведомления' : room.otherUserName}
          </p>
          {!hovered && room.lastMessageAt && (
            <span className="text-[10px] text-slate-400 shrink-0">{fmtTime(room.lastMessageAt)}</span>
          )}
        </div>
        {room.lastMessage && (
          <p className={cn('text-xs truncate mt-0.5', Number(room.unreadCount) > 0 ? 'text-slate-600 dark:text-slate-300 font-medium' : 'text-slate-400 dark:text-slate-500')}>
            {room.lastSender && room.type === 'general' ? `${room.lastSender.split(' ')[0]}: ` : ''}{room.lastMessage}
          </p>
        )}
      </div>

      {/* ⋮ button — appears on hover */}
      {hovered && (
        <button
          onClick={onMenuClick}
          className="shrink-0 w-7 h-7 flex items-center justify-center rounded-lg hover:bg-slate-200 dark:hover:bg-slate-600 text-slate-500 transition-colors text-base leading-none"
          title="Действия"
        >
          ⋮
        </button>
      )}
    </div>
  )
}

// ── MessageBubble ──────────────────────────────────────────────────────────

function MessageBubble({ msg, isOwn, isEditing, editText, isRead, onContextMenu, onEditTextChange, onSaveEdit, onCancelEdit, onReaction }: {
  msg: ChatMessage; isOwn: boolean; isEditing: boolean; editText: string; isRead: boolean
  onContextMenu: (e: React.MouseEvent) => void
  onEditTextChange: (v: string) => void
  onSaveEdit: () => void; onCancelEdit: () => void
  onReaction: (emoji: string) => void
}) {
  const isDeleted = !!msg.deletedAt, isSystem = msg.isSystem
  return (
    <div className={cn('flex gap-2', isOwn && 'flex-row-reverse')} onContextMenu={onContextMenu}>
      {!isOwn && (isSystem ? <SystemAvatar size={24} /> : <Avatar name={msg.senderName} size={24} />)}
      <div className={cn('max-w-[80%]', isOwn && 'items-end flex flex-col')}>
        {!isOwn && !isDeleted && (
          <p className="text-[10px] text-slate-400 mb-0.5 ml-1">
            {isSystem ? msg.systemName ?? 'Система' : msg.senderName.split(' ')[0]}
          </p>
        )}

        {isDeleted ? (
          <p className="px-3 py-2 text-sm italic text-slate-400 dark:text-slate-500 bg-slate-100 dark:bg-slate-700/50 rounded-2xl">
            Сообщение удалено
          </p>
        ) : isEditing ? (
          <div className="w-48">
            <textarea value={editText} onChange={e => onEditTextChange(e.target.value)} autoFocus rows={2}
              onKeyDown={e => { if (e.key === 'Enter' && !e.shiftKey) { e.preventDefault(); onSaveEdit() } if (e.key === 'Escape') onCancelEdit() }}
              className="w-full resize-none rounded-xl px-3 py-2 text-sm bg-slate-100 dark:bg-slate-700 text-slate-900 dark:text-slate-100 outline-none border border-brand-400" />
            <div className="flex gap-1.5 mt-1">
              <button onClick={onSaveEdit} className="text-[11px] px-2 py-0.5 rounded bg-brand-600 text-white hover:bg-brand-700">Сохранить</button>
              <button onClick={onCancelEdit} className="text-[11px] px-2 py-0.5 rounded bg-slate-200 dark:bg-slate-600 text-slate-700 dark:text-slate-300">Отмена</button>
            </div>
          </div>
        ) : (
          <div className={cn('rounded-2xl text-sm overflow-hidden',
            isSystem ? 'bg-amber-50 dark:bg-amber-900/20 border border-amber-200 dark:border-amber-800 rounded-tl-sm'
            : isOwn ? 'bg-brand-600 rounded-tr-sm' : 'bg-slate-100 dark:bg-slate-700 rounded-tl-sm')}>
            {msg.attachmentUrl && (
              <a href={msg.attachmentUrl} target="_blank" rel="noreferrer">
                <img src={msg.attachmentUrl} alt="вложение" className="max-w-full rounded-t-2xl block" style={{ maxHeight: 180, objectFit: 'cover', width: '100%' }} />
              </a>
            )}
            {msg.text && (
              <p className={cn('px-3 py-2', isSystem ? 'text-amber-900 dark:text-amber-200' : isOwn ? 'text-white' : 'text-slate-800 dark:text-slate-200')}>
                {msg.text}
              </p>
            )}
          </div>
        )}

        {/* Meta: time + edited + read receipt */}
        {!isDeleted && (
          <div className={cn('flex items-center gap-1 mt-0.5 mx-1', isOwn && 'flex-row-reverse')}>
            <p className="text-[10px] text-slate-400">{fmtTime(msg.createdAt)}</p>
            {msg.editedAt && <p className="text-[10px] text-slate-400">· изм.</p>}
            {isOwn && (
              <span className={cn('text-[11px] leading-none', isRead ? 'text-brand-400' : 'text-slate-300 dark:text-slate-600')}>
                {isRead ? '✓✓' : '✓'}
              </span>
            )}
          </div>
        )}

        {/* Reactions */}
        {!isDeleted && msg.reactions?.length > 0 && (
          <div className={cn('flex flex-wrap gap-1 mt-1', isOwn && 'justify-end')}>
            {(msg.reactions as ChatReaction[]).map(r => (
              <button key={r.emoji} onClick={() => onReaction(r.emoji)}
                className={cn('flex items-center gap-0.5 px-1.5 py-0.5 rounded-full text-xs transition-colors',
                  r.hasOwn
                    ? 'bg-brand-100 dark:bg-brand-900/40 border border-brand-400 text-brand-700 dark:text-brand-300'
                    : 'bg-slate-100 dark:bg-slate-700 border border-slate-200 dark:border-slate-600 text-slate-700 dark:text-slate-300 hover:bg-slate-200')}>
                {r.emoji} <span className="text-[10px] font-medium">{r.count}</span>
              </button>
            ))}
          </div>
        )}
      </div>
    </div>
  )
}

// ── ContextMenu ────────────────────────────────────────────────────────────

function ContextMenu({ menu, currentUserId, pinnedIds, onReaction, onEdit, onDelete, onTogglePin, onClose }: {
  menu: CtxMenu; currentUserId: string; pinnedIds: string[]
  onReaction: (emoji: string) => void; onEdit: () => void; onDelete: () => void
  onTogglePin: () => void; onClose: () => void
}) {
  const menuW = 182, menuH = menu.type === 'message' ? 160 : 56
  const x = Math.min(menu.x, window.innerWidth - menuW - 8)
  const y = Math.min(menu.y, window.innerHeight - menuH - 8)
  return (
    <div style={{ position: 'fixed', top: y, left: x, zIndex: 9999, minWidth: menuW }}
      className="bg-white dark:bg-slate-800 border border-slate-200 dark:border-slate-600 rounded-xl shadow-xl py-1"
      onClick={e => e.stopPropagation()}>
      {menu.type === 'message' && (
        <>
          <div className="flex justify-around px-2 py-1.5 border-b border-slate-100 dark:border-slate-700">
            {EMOJIS.map(e => (
              <button key={e} onClick={() => { onReaction(e); onClose() }}
                className="text-base hover:scale-125 transition-transform leading-none p-0.5">{e}</button>
            ))}
          </div>
          {menu.msg.senderId === currentUserId && (
            <button onClick={() => { onEdit(); onClose() }}
              className="w-full text-left px-3 py-2 text-sm text-slate-700 dark:text-slate-200 hover:bg-slate-50 dark:hover:bg-slate-700/50 flex items-center gap-2">
              <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2"><path d="M11 4H4a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7"/><path d="M18.5 2.5a2.121 2.121 0 0 1 3 3L12 15l-4 1 1-4 9.5-9.5z"/></svg>
              Редактировать
            </button>
          )}
          {menu.msg.senderId === currentUserId && (
            <button onClick={() => { onDelete(); onClose() }}
              className="w-full text-left px-3 py-2 text-sm text-red-600 hover:bg-red-50 dark:hover:bg-red-900/20 flex items-center gap-2">
              <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2"><polyline points="3 6 5 6 21 6"/><path d="M19 6l-1 14a2 2 0 0 1-2 2H8a2 2 0 0 1-2-2L5 6"/><path d="M10 11v6"/><path d="M14 11v6"/><path d="M9 6V4h6v2"/></svg>
              Удалить
            </button>
          )}
        </>
      )}
      {menu.type === 'room' && (
        <button onClick={() => { onTogglePin(); onClose() }}
          className="w-full text-left px-3 py-2 text-sm text-slate-700 dark:text-slate-200 hover:bg-slate-50 dark:hover:bg-slate-700/50 flex items-center gap-2">
          <Pin size={14} />
          {pinnedIds.includes(menu.room.id) ? 'Открепить' : 'Закрепить сверху'}
        </button>
      )}
    </div>
  )
}

// ── Misc sub-components ───────────────────────────────────────────────────

function TypingDots() {
  return (
    <div className="flex gap-0.5 items-center">
      {[0, 1, 2].map(i => (
        <span key={i} className="w-1.5 h-1.5 rounded-full bg-slate-400 animate-bounce" style={{ animationDelay: `${i * 0.15}s` }} />
      ))}
    </div>
  )
}

function UserRow({ user, online, onClick }: { user: User; online: boolean; onClick: () => void }) {
  const roleLabels: Record<string, string> = {
    hotel_admin: 'Администратор', manager: 'Менеджер', housekeeper: 'Горничная',
    receptionist: 'Ресепшн', accountant: 'Бухгалтер', security: 'Охрана', technician: 'Техник',
  }
  return (
    <button onClick={onClick} className="w-full flex items-center gap-3 px-4 py-2.5 hover:bg-slate-50 dark:hover:bg-slate-700/50 transition-colors border-b border-slate-100 dark:border-slate-700/50 text-left">
      <Avatar name={user.name} size={32} online={online} />
      <div className="flex-1 min-w-0">
        <p className="text-sm font-medium text-slate-800 dark:text-slate-200 truncate">{user.name}</p>
        <p className="text-xs text-slate-400 truncate">{roleLabels[user.role] ?? user.role}</p>
      </div>
    </button>
  )
}

function SettingsSection({ title, children }: { title: string; children: React.ReactNode }) {
  return (
    <div className="px-4">
      <p className="text-xs font-semibold text-slate-400 uppercase tracking-wide mb-2">{title}</p>
      <div className="space-y-1">{children}</div>
    </div>
  )
}

function ToggleRow({ icon, label, checked, onChange }: {
  icon: React.ReactNode; label: string; checked: boolean; onChange: (v: boolean) => void
}) {
  return (
    <div className="flex items-center gap-3 py-2">
      <span className="text-slate-500 shrink-0">{icon}</span>
      <span className="flex-1 text-sm text-slate-700 dark:text-slate-300 leading-tight">{label}</span>
      <button onClick={() => onChange(!checked)}
        className={cn('relative w-10 h-5 rounded-full transition-colors shrink-0', checked ? 'bg-brand-600' : 'bg-slate-300 dark:bg-slate-600')}>
        <span className={cn('absolute top-0.5 w-4 h-4 rounded-full bg-white shadow transition-transform', checked ? 'translate-x-5' : 'translate-x-0.5')} />
      </button>
    </div>
  )
}

function RoomSkeleton({ icon, bg, label }: { icon: React.ReactNode; bg: string; label: string }) {
  return (
    <div className="flex items-center gap-3 px-4 py-3 border-b border-slate-100 dark:border-slate-700/50 animate-pulse">
      <div className={cn('w-9 h-9 rounded-full flex items-center justify-center shrink-0', bg)}>{icon}</div>
      <div className="flex-1 min-w-0">
        <p className="text-sm font-medium text-slate-600 dark:text-slate-300">{label}</p>
        <div className="h-3 w-24 bg-slate-200 dark:bg-slate-600 rounded mt-1" />
      </div>
    </div>
  )
}
