File size: 2,783 Bytes
00a912e
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
import { useCallback, useEffect, useState } from 'react'
import { EVENTS } from '@music-together/shared'
import type { PlayMode, VoteAction, VoteState } from '@music-together/shared'
import { useSocketContext } from '@/providers/socket-context'
import { useSocketEvent } from './useSocketEvent'
import { toast } from 'sonner'

const ACTION_LABELS: Record<VoteAction, string> = {
  pause: '暂停',
  resume: '播放',
  next: '下一首',
  prev: '上一首',
  'set-mode': '切换播放模式',
  'play-track': '指定播放',
  'remove-track': '投票移除',
}

const PLAY_MODE_LABELS: Record<PlayMode, string> = {
  sequential: '顺序播放',
  'loop-all': '列表循环',
  'loop-one': '单曲循环',
  shuffle: '随机播放',
}

/** Get a human-readable label for a vote action, including payload context */
export function getVoteActionLabel(action: VoteAction, payload?: Record<string, unknown>): string {
  if (action === 'set-mode' && payload?.mode) {
    const modeLabel = PLAY_MODE_LABELS[payload.mode as PlayMode] ?? payload.mode
    return `切换为${modeLabel}`
  }
  if (action === 'play-track' && payload?.trackTitle) {
    return `播放「${payload.trackTitle}」`
  }
  if (action === 'remove-track' && payload?.trackTitle) {
    return `移除「${payload.trackTitle}」`
  }
  return ACTION_LABELS[action]
}

export function useVote() {
  const { socket } = useSocketContext()
  const [activeVote, setActiveVote] = useState<VoteState | null>(null)

  useSocketEvent(
    EVENTS.VOTE_STARTED,
    useCallback((vote: VoteState) => {
      setActiveVote(vote)
    }, []),
  )

  useSocketEvent(
    EVENTS.VOTE_RESULT,
    useCallback((data: { passed: boolean; action: VoteAction; reason?: string }) => {
      setActiveVote(null)
      const label = ACTION_LABELS[data.action]
      if (data.passed) {
        toast.success(`投票通过:${label}`)
      } else {
        const reasonText = data.reason === 'host_veto' ? '(房主否决)' : data.reason === 'timeout' ? '(超时)' : ''
        toast.error(`投票未通过:${label}${reasonText}`)
      }
    }, []),
  )

  // Clear active vote on disconnect
  useEffect(() => {
    const onDisconnect = () => setActiveVote(null)
    socket.on('disconnect', onDisconnect)
    return () => {
      socket.off('disconnect', onDisconnect)
    }
  }, [socket])

  const startVote = useCallback(
    (action: VoteAction, payload?: Record<string, unknown>) => {
      socket.emit(EVENTS.VOTE_START, { action, payload })
      toast.info(`已发起投票:${getVoteActionLabel(action, payload)}`)
    },
    [socket],
  )

  const castVote = useCallback(
    (approve: boolean) => {
      socket.emit(EVENTS.VOTE_CAST, { approve })
    },
    [socket],
  )

  return { activeVote, startVote, castVote }
}