<script setup lang="ts">
import { computed, onBeforeUnmount, onMounted, ref, watch } from 'vue'
import { useRoute, useRouter } from 'vue-router'
import { readStoredAuth } from '../utils/auth-storage'

const router = useRouter()
const route = useRoute()

const auth = ref(readStoredAuth())
const ws = ref<WebSocket | null>(null)
const wsStatus = ref<'disconnected' | 'connecting' | 'connected'>('disconnected')
const wsError = ref('')
const wsMessages = ref<string[]>([])
const startGamePending = ref(false)
const lastStartRequestId = ref('')

const WS_BASE_URL = import.meta.env.VITE_GAME_WS_URL ?? 'ws://127.0.0.1:8080/ws'
const DEFAULT_MAX_PLAYERS = 4

type SeatKey = 'top' | 'right' | 'bottom' | 'left'

interface RoomPlayerState {
  index: number
  playerId: string
  ready: boolean
}

interface RoomState {
  id: string
  name: string
  ownerId: string
  maxPlayers: number
  status: string
  players: RoomPlayerState[]
  currentTurnIndex: number | null
}

interface ActionEventLike {
  type?: unknown
  status?: unknown
  requestId?: unknown
  roomId?: unknown
  room_id?: unknown
  payload?: unknown
  data?: unknown
}

const roomId = computed(() => {
  return typeof route.params.roomId === 'string' ? route.params.roomId : ''
})

const roomName = computed(() => {
  return typeof route.query.roomName === 'string' ? route.query.roomName : ''
})

const currentUserId = computed(() => {
  const candidate = auth.value?.user?.id
  if (typeof candidate === 'string') {
    return candidate
  }
  if (typeof candidate === 'number' && Number.isFinite(candidate)) {
    return String(candidate)
  }
  return ''
})

const loggedInUserName = computed(() => {
  if (!auth.value?.user) {
    return ''
  }

  return auth.value.user.nickname ?? auth.value.user.username ?? ''
})

const roomState = ref<RoomState>({
  id: roomId.value,
  name: roomName.value,
  ownerId: '',
  maxPlayers: DEFAULT_MAX_PLAYERS,
  status: 'waiting',
  players: [],
  currentTurnIndex: null,
})

const isRoomFull = computed(() => {
  return (
    roomState.value.maxPlayers > 0 &&
    roomState.value.players.length === roomState.value.maxPlayers
  )
})

const canStartGame = computed(() => {
  return (
    Boolean(roomState.value.id) &&
    roomState.value.status === 'waiting' &&
    isRoomFull.value &&
    Boolean(currentUserId.value) &&
    roomState.value.ownerId === currentUserId.value
  )
})

const seatViews = computed(() => {
  const seats: Record<SeatKey, RoomPlayerState | null> = {
    top: null,
    right: null,
    bottom: null,
    left: null,
  }

  const players = [...roomState.value.players].sort((a, b) => a.index - b.index)
  const me = players.find((player) => player.playerId === currentUserId.value) ?? null
  const anchorIndex = me?.index ?? players[0]?.index ?? 0
  const clockwiseSeatByDelta: SeatKey[] = ['bottom', 'left', 'top', 'right']

  for (const player of players) {
    const normalizedDelta = ((player.index - anchorIndex) % 4 + 4) % 4
    const seat = clockwiseSeatByDelta[normalizedDelta] ?? 'top'
    seats[seat] = player
  }

  const turnSeat =
    roomState.value.currentTurnIndex === null
      ? null
      : clockwiseSeatByDelta[
          ((roomState.value.currentTurnIndex - anchorIndex) % 4 + 4) % 4
        ] ?? null

  const order: SeatKey[] = ['top', 'right', 'bottom', 'left']
  return order.map((seat) => {
    const player = seats[seat]
    const isSelf = Boolean(player) && player?.playerId === currentUserId.value
    return {
      key: seat,
      player,
      isSelf,
      isTurn: turnSeat === seat,
      label: player ? (isSelf ? '你' : player.playerId) : '空位',
      subLabel: player ? `座位 ${player.index}` : '',
    }
  })
})

const roomStatusText = computed(() => {
  if (roomState.value.status === 'playing') {
    return '对局中'
  }
  if (roomState.value.status === 'finished') {
    return '已结束'
  }
  return '等待中'
})

function backHall(): void {
  sendLeaveRoom()
  void router.push('/hall')
}

function pushWsMessage(text: string): void {
  const line = `[${new Date().toLocaleTimeString()}] ${text}`
  wsMessages.value.unshift(line)
  if (wsMessages.value.length > 80) {
    wsMessages.value.length = 80
  }
}

function logWsSend(message: unknown): void {
  console.log('[WS][client] 发送:', message)
}

function logWsReceive(kind: string, payload?: unknown): void {
  const now = new Date().toLocaleTimeString()
  if (payload === undefined) {
    console.log(`[WS][${now}] 收到${kind}`)
    return
  }
  console.log(`[WS][${now}] 收到${kind}:`, payload)
}

function disconnectWs(): void {
  if (ws.value) {
    ws.value.close()
    ws.value = null
  }
  wsStatus.value = 'disconnected'
}

function toRecord(value: unknown): Record<string, unknown> | null {
  return typeof value === 'object' && value !== null ? (value as Record<string, unknown>) : null
}

function toStringOrEmpty(value: unknown): string {
  if (typeof value === 'string') {
    return value
  }
  if (typeof value === 'number' && Number.isFinite(value)) {
    return String(value)
  }
  return ''
}

function toFiniteNumber(value: unknown): number | null {
  if (typeof value === 'number' && Number.isFinite(value)) {
    return value
  }
  if (typeof value === 'string' && value.trim()) {
    const parsed = Number(value)
    return Number.isFinite(parsed) ? parsed : null
  }
  return null
}

function normalizePlayer(input: unknown, fallbackIndex: number): RoomPlayerState | null {
  const player = toRecord(input)
  if (!player) {
    return null
  }

  const playerId = toStringOrEmpty(player.playerId ?? player.player_id ?? player.user_id ?? player.id)
  if (!playerId) {
    return null
  }

  const seatIndex = toFiniteNumber(player.index ?? player.seat ?? player.position ?? player.player_index)
  return {
    index: seatIndex ?? fallbackIndex,
    playerId,
    ready: Boolean(player.ready),
  }
}

function extractCurrentTurnIndex(value: Record<string, unknown>): number | null {
  const keys = [
    value.currentTurnIndex,
    value.current_turn_index,
    value.currentPlayerIndex,
    value.current_player_index,
    value.turnIndex,
    value.turn_index,
    value.activePlayerIndex,
    value.active_player_index,
  ]
  for (const key of keys) {
    const parsed = toFiniteNumber(key)
    if (parsed !== null) {
      return parsed
    }
  }
  return null
}

function normalizeRoom(input: unknown): RoomState | null {
  const room = toRecord(input)
  if (!room) {
    return null
  }

  const id = toStringOrEmpty(room.roomId ?? room.room_id ?? room.id)
  if (!id) {
    return null
  }

  const maxPlayers =
    toFiniteNumber(room.maxPlayers ?? room.max_players) ?? roomState.value.maxPlayers ?? DEFAULT_MAX_PLAYERS
  const playersRaw =
    (Array.isArray(room.players) ? room.players : null) ??
    (Array.isArray(room.playerList) ? room.playerList : null) ??
    (Array.isArray(room.player_list) ? room.player_list : null) ??
    []

  const players = playersRaw
    .map((item, index) => normalizePlayer(item, index))
    .filter((item): item is RoomPlayerState => Boolean(item))
    .sort((a, b) => a.index - b.index)

  return {
    id,
    name: toStringOrEmpty(room.name) || roomState.value.name,
    ownerId: toStringOrEmpty(room.ownerId ?? room.owner_id),
    maxPlayers,
    status: toStringOrEmpty(room.status) || roomState.value.status || 'waiting',
    players,
    currentTurnIndex: extractCurrentTurnIndex(room),
  }
}

function mergeRoomState(next: RoomState): void {
  if (roomId.value && next.id !== roomId.value) {
    return
  }

  roomState.value = {
    ...roomState.value,
    ...next,
    players: next.players.length > 0 ? next.players : roomState.value.players,
    currentTurnIndex:
      next.currentTurnIndex !== null ? next.currentTurnIndex : roomState.value.currentTurnIndex,
  }
}

function consumeGameEvent(raw: string): void {
  let parsed: unknown = null
  try {
    parsed = JSON.parse(raw)
  } catch {
    return
  }

  const event = toRecord(parsed) as ActionEventLike | null
  if (!event) {
    return
  }

  const candidates: unknown[] = [event, event.payload, event.data]
  const payload = toRecord(event.payload)
  if (payload) {
    candidates.push(payload.room, payload.state, payload.roomState)
  }

  for (const candidate of candidates) {
    const normalized = normalizeRoom(candidate)
    if (normalized) {
      mergeRoomState(normalized)
      break
    }
  }

  if (
    event.status === 'error' &&
    typeof event.requestId === 'string' &&
    event.requestId === lastStartRequestId.value
  ) {
    startGamePending.value = false
  }
}

function createRequestId(prefix: string): string {
  return `${prefix}-${Date.now()}-${Math.random().toString(36).slice(2, 10)}`
}

function sendStartGame(): void {
  if (!ws.value || ws.value.readyState !== WebSocket.OPEN || !canStartGame.value || startGamePending.value) {
    return
  }

  const sender = currentUserId.value

  if (!sender) {
    return
  }

  const requestId = createRequestId('start-game')
  lastStartRequestId.value = requestId
  startGamePending.value = true

  const message = {
    type: 'start_game',
    sender,
    target: 'room',
    roomId: roomState.value.id || roomId.value,
    seq: Date.now(),
    requestId,
    trace_id: createRequestId('trace'),
    payload: {},
  }
  logWsSend(message)
  ws.value.send(JSON.stringify(message))
  pushWsMessage(`[client] 请求开始游戏 requestId=${requestId}`)
}

function sendLeaveRoom(): void {
  if (!ws.value || ws.value.readyState !== WebSocket.OPEN) {
    return
  }

  const sender = currentUserId.value
  const targetRoomId = roomState.value.id || roomId.value
  if (!sender || !targetRoomId) {
    return
  }

  const requestId = createRequestId('leave-room')
  const message = {
    type: 'leave_room',
    sender,
    target: 'room',
    roomId: targetRoomId,
    seq: Date.now(),
    requestId,
    trace_id: createRequestId('trace'),
    payload: {},
  }

  logWsSend(message)
  ws.value.send(JSON.stringify(message))
  pushWsMessage(`[client] 请求退出房间 requestId=${requestId}`)
}

function connectWs(): void {
  wsError.value = ''
  const token = auth.value?.token
  if (!token) {
    wsError.value = '缺少 token，无法建立 WebSocket 连接'
    return
  }

  disconnectWs()
  wsStatus.value = 'connecting'

  const url = `${WS_BASE_URL}?token=${encodeURIComponent(token)}`
  const socket = new WebSocket(url)
  ws.value = socket

  socket.onopen = () => {
    wsStatus.value = 'connected'
    pushWsMessage('WebSocket 已连接')
  }

  socket.onmessage = (event) => {
  if (typeof event.data === 'string') {
    logWsReceive('文本消息', event.data)
    try {
      const parsed = JSON.parse(event.data)
      logWsReceive('JSON消息', parsed)

      pushWsMessage(`[server] ${JSON.stringify(parsed, null, 2)}`)
    } catch {
      pushWsMessage(`[server] ${event.data}`)
    }

    consumeGameEvent(event.data)
    return
  }

  logWsReceive('二进制消息')
  pushWsMessage('[binary] 收到二进制消息')
  }

  socket.onerror = () => {
    wsError.value = 'WebSocket 连接异常'
  }

  socket.onclose = () => {
    wsStatus.value = 'disconnected'
    startGamePending.value = false
    pushWsMessage('WebSocket 已断开')
  }
}

watch(
  roomId,
  (nextRoomId) => {
    roomState.value = {
      id: nextRoomId,
      name: roomName.value,
      ownerId: '',
      maxPlayers: DEFAULT_MAX_PLAYERS,
      status: 'waiting',
      players: [],
      currentTurnIndex: null,
    }
    startGamePending.value = false
    lastStartRequestId.value = ''
  },
  { immediate: true },
)

watch(roomName, (next) => {
  roomState.value = {
    ...roomState.value,
    name: next || roomState.value.name,
  }
})

watch(
  [canStartGame, wsStatus],
  ([canStart, status]) => {
    if (!canStart || status !== 'connected') {
      return
    }
    sendStartGame()
  },
  { immediate: true },
)

watch(
  () => roomState.value.status,
  (status) => {
    if (status === 'playing' || status === 'finished') {
      startGamePending.value = false
    }
  },
)

onMounted(() => {
  connectWs()
})

onBeforeUnmount(() => {
  disconnectWs()
})
</script>

<template>
  <section class="hall-page game-page">
    <header class="hall-header game-header">
      <div>
        <h1>成都麻将对局</h1>
        <p v-if="loggedInUserName" class="sub-title">玩家：{{ loggedInUserName }}</p>
      </div>
      <div class="header-actions">
        <button class="ghost-btn" type="button" @click="backHall">返回大厅</button>
      </div>
    </header>

    <section class="table-panel game-table-panel">
      <div class="room-brief">
        <span class="room-brief-title">当前房间</span>
        <span class="room-brief-item">
          <em>房间名：</em>
          <strong>{{ roomState.name || roomName || '未命名房间' }}</strong>
        </span>
        <span class="room-brief-item room-brief-id">
          <em>room_id：</em>
          <strong>{{ roomId || '未选择房间' }}</strong>
        </span>
        <span class="room-brief-item">
          <em>状态：</em>
          <strong>{{ roomStatusText }}</strong>
        </span>
        <span class="room-brief-item">
          <em>人数：</em>
          <strong>{{ roomState.players.length }}/{{ roomState.maxPlayers }}</strong>
        </span>
        <button
          class="ghost-btn ws-reconnect"
          type="button"
          :disabled="!canStartGame || startGamePending"
          @click="sendStartGame"
        >
          {{ startGamePending ? '开局请求中...' : '开始游戏' }}
        </button>
      </div>
      <div class="mahjong-table game-mahjong-table" :class="{ active: Boolean(roomId) }">
        <div
          v-for="seat in seatViews"
          :key="seat.key"
          class="seat"
          :class="[
            `seat-${seat.key}`,
            { occupied: Boolean(seat.player), 'seat-me': seat.isSelf, 'seat-turn': seat.isTurn },
          ]"
        >
          <strong>{{ seat.label }}</strong>
          <small v-if="seat.subLabel">{{ seat.subLabel }}</small>
          <span v-if="seat.isTurn" class="turn-indicator">出牌中</span>
        </div>
        <div class="table-center">
          <p>Chengdu Mahjong</p>
          <p>{{ roomState.id || roomId || 'Waiting...' }}</p>
        </div>
      </div>

      <section class="ws-panel">
        <div class="ws-panel-head">
          <strong>实时消息</strong>
          <div class="ws-actions">
            <span class="ws-state" :class="`is-${wsStatus}`">{{ wsStatus }}</span>
            <button class="ghost-btn ws-reconnect" type="button" @click="connectWs">重连</button>
          </div>
        </div>
        <p v-if="wsError" class="message error">{{ wsError }}</p>
        <div class="ws-log">
          <p v-if="wsMessages.length === 0" class="ws-empty">等待服务器消息...</p>
          <p v-for="(line, idx) in wsMessages" :key="idx" class="ws-line">{{ line }}</p>
        </div>
      </section>
    </section>
  </section>
</template>
