<!--
  组件名称：MessageStream.vue
  组件描述：实时消息流组件 - 终端风格显示设备上报/下发消息

  @author czj
  @date 2026-08-05
-->

<template>
  <div class="message-stream">
    <!-- 工具栏 -->
    <div class="toolbar">
      <div class="filters">
        <el-radio-group v-model="directionFilter" size="small">
          <el-radio-button label="">全部</el-radio-button>
          <el-radio-button label="publish">上报</el-radio-button>
          <el-radio-button label="subscribe">下发</el-radio-button>
        </el-radio-group>
        <el-input
          v-model="searchText"
          placeholder="搜索设备/主题"
          size="small"
          clearable
          style="width: 180px; margin-left: 12px"
        />
      </div>
      <div class="actions">
        <el-button :type="paused ? 'warning' : 'default'" size="small" @click="paused = !paused">
          {{ paused ? '继续' : '暂停' }}
        </el-button>
        <el-button size="small" @click="messages = []">清空</el-button>
        <el-tag size="small" type="info">{{ filteredMessages.length }} 条</el-tag>
      </div>
    </div>

    <!-- 消息列表 -->
    <div class="message-list" ref="listRef">
      <div v-if="filteredMessages.length === 0" class="empty-state">
        <el-empty description="暂无消息" :image-size="60" />
      </div>
      <div
        v-for="(msg, index) in filteredMessages"
        :key="index"
        class="message-item"
        :class="`direction-${msg.direction}`"
        @click="toggleDetail(msg)"
      >
        <div class="message-header">
          <span class="timestamp">{{ formatTime(msg.timestamp) }}</span>
          <el-tag
            :type="msg.direction === 'publish' ? 'primary' : 'success'"
            size="small"
            effect="plain"
          >
            {{ msg.direction === 'publish' ? '上报' : '下发' }}
          </el-tag>
          <span class="device-name">{{ msg.deviceName }}</span>
          <span class="topic">{{ msg.topic }}</span>
        </div>
        <div v-if="msg._expanded" class="message-detail">
          <pre>{{ JSON.stringify(msg.payload, null, 2) }}</pre>
        </div>
      </div>
    </div>
  </div>
</template>

<script setup lang="ts">
import { ref, computed, onMounted, onUnmounted } from 'vue'
import { createMessageStream, type DeviceMessage } from '@/api/deviceSim'

interface StreamMessage {
  deviceId: string
  deviceName: string
  deviceType: string
  topic: string
  direction: 'publish' | 'subscribe'
  payload: Record<string, any>
  timestamp: string
  _expanded?: boolean
}

const props = defineProps<{
  deviceType?: string
  selectedDeviceIds?: string[]
}>()

const messages = ref<StreamMessage[]>([])
const directionFilter = ref('')
const searchText = ref('')
const paused = ref(false)
const listRef = ref<HTMLElement>()
let ws: WebSocket | null = null

const filteredMessages = computed(() => {
  let result = messages.value
  if (directionFilter.value) {
    result = result.filter(m => m.direction === directionFilter.value)
  }
  if (searchText.value) {
    const search = searchText.value.toLowerCase()
    result = result.filter(m =>
      m.deviceName.toLowerCase().includes(search) ||
      m.deviceId.toLowerCase().includes(search) ||
      m.topic.toLowerCase().includes(search)
    )
  }
  if (props.selectedDeviceIds?.length) {
    result = result.filter(m => props.selectedDeviceIds!.includes(m.deviceId))
  }
  return result
})

function toggleDetail(msg: StreamMessage) {
  msg._expanded = !msg._expanded
}

function formatTime(ts: string) {
  return ts?.replace('T', ' ').substring(11, 23) || '-'
}

function scrollToBottom() {
  if (listRef.value) {
    listRef.value.scrollTop = listRef.value.scrollHeight
  }
}

function connect() {
  ws = createMessageStream((msg: DeviceMessage) => {
    if (paused.value) return
    if (msg.event === 'device_message' && msg.data) {
      // 按设备类型过滤
      if (props.deviceType && msg.data.device_type !== props.deviceType) return

      messages.value.push({
        deviceId: msg.data.device_id,
        deviceName: msg.data.device_name,
        deviceType: msg.data.device_type,
        topic: msg.data.topic,
        direction: msg.data.direction,
        payload: msg.data.payload,
        timestamp: msg.data.timestamp,
        _expanded: false,
      })

      // 限制最大消息数
      if (messages.value.length > 500) {
        messages.value = messages.value.slice(-300)
      }

      requestAnimationFrame(scrollToBottom)
    }
  })
}

onMounted(() => {
  connect()
})

onUnmounted(() => {
  ws?.close()
})
</script>

<style scoped>
.message-stream {
  display: flex;
  flex-direction: column;
  height: 100%;
  border: 1px solid #e4e7ed;
  border-radius: 4px;
  overflow: hidden;
}

.toolbar {
  display: flex;
  justify-content: space-between;
  align-items: center;
  padding: 8px 12px;
  border-bottom: 1px solid #e4e7ed;
  background: #f5f7fa;
}

.filters {
  display: flex;
  align-items: center;
}

.actions {
  display: flex;
  align-items: center;
  gap: 8px;
}

.message-list {
  flex: 1;
  overflow-y: auto;
  font-family: 'Courier New', Consolas, monospace;
  font-size: 12px;
  background: #1e1e1e;
  color: #d4d4d4;
}

.empty-state {
  display: flex;
  justify-content: center;
  align-items: center;
  height: 100%;
}

.message-item {
  padding: 6px 12px;
  border-bottom: 1px solid #333;
  cursor: pointer;
  transition: background 0.15s;
}

.message-item:hover {
  background: #2a2a2a;
}

.message-item.direction-publish {
  border-left: 3px solid #409eff;
}

.message-item.direction-subscribe {
  border-left: 3px solid #67c23a;
}

.message-header {
  display: flex;
  align-items: center;
  gap: 8px;
}

.timestamp {
  color: #888;
  font-size: 11px;
}

.device-name {
  color: #e6a23c;
  font-weight: 500;
}

.topic {
  color: #aaa;
  font-size: 11px;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  max-width: 200px;
}

.message-detail {
  margin-top: 6px;
  padding: 8px;
  background: #252525;
  border-radius: 4px;
  overflow-x: auto;
}

.message-detail pre {
  margin: 0;
  white-space: pre-wrap;
  word-break: break-all;
  font-size: 11px;
  color: #d4d4d4;
}
</style>
