<template>
	<view class="notice-page">
		<image v-if="!hasNotify" class="bg" src="/static/images/deviceunlogin.png" mode="aspectFit" />

		<view v-else class="notify-container">
			<view
				v-if="studentNotifyUsers.length"
				class="notify-section section-student"
				:class="{ 'with-divider': parentUnreadUsers.length }"
				:style="{ height: sectionHeights.student + 'px' }"
			>
				<view
					v-for="(item, index) in studentLayout.positions"
					:key="'xy-' + item.id + '_' + index"
					class="notify-card student-card"
					:style="{
						left: item.left + 'px',
						top: item.top + 'px',
						width: item.width + 'px',
						height: item.height + 'px',
						gap: item.gap + 'px'
					}"
				>
					<image
						class="avatar student-avatar"
						:src="item.avatar"
						mode="aspectFill"
						:style="{ width: item.avatarWidth + 'px', height: item.avatarHeight + 'px' }"
					/>
					<view class="name" :style="{ fontSize: item.fontSize + 'px' }">{{ item.name }}</view>
				</view>
			</view>

			<view
				v-if="parentUnreadUsers.length"
				class="notify-section section-parent"
				:style="{ height: sectionHeights.parent + 'px' }"
			>
				<view
					v-for="(item, index) in parentLayout.positions"
					:key="'jz-' + item.id + '_' + index"
					class="notify-card parent-card"
					:style="{
						left: item.left + 'px',
						top: item.top + 'px',
						width: item.width + 'px',
						height: item.height + 'px',
						gap: item.gap + 'px'
					}"
				>
					<image
						class="avatar parent-avatar"
						:src="item.avatar"
						mode="aspectFill"
						:style="{ width: item.avatarWidth + 'px', height: item.avatarHeight + 'px' }"
					/>
					<view class="name" :style="{ fontSize: item.fontSize + 'px' }">{{ item.name }}</view>
				</view>
			</view>
		</view>

		<view class="footer">
			<view class="footer-text">
				<view class="triangles">
					<view class="triangle" />
					<view class="triangle" />
					<view class="triangle" />
				</view>
				请刷卡登录查看留言
			</view>
		</view>
	</view>
</template>

<script setup>
import { computed, ref } from 'vue'
import { onLoad, onShow, onUnload } from '@dcloudio/uni-app'
import websocketService from '@/utils/websocket'
import { deviceApi } from '@/api/device'
import { getImageUrl } from '@/utils/util'

const DEFAULT_AVATAR = '/static/logo.png'
const USE_MOCK_WHEN_EMPTY = true

const studentNotifyUsers = ref([])
const parentUnreadUsers = ref([])
const deviceSn = ref('')
const noticeBootstrapped = ref(false)
const skipFirstOnShow = ref(false)
const wsUnsubscribers = []

const totalNotifyCount = computed(() => studentNotifyUsers.value.length + parentUnreadUsers.value.length)
const hasNotify = computed(() => totalNotifyCount.value > 0)

// 竖屏设备目标尺寸（px）
const SCREEN_WIDTH = 800
const SCREEN_HEIGHT = 1280
const FOOTER_HEIGHT = 50 * (SCREEN_WIDTH / 750)
const HORIZONTAL_PADDING = 10 * (SCREEN_WIDTH / 750) * 2
const VERTICAL_PADDING = 10 * (SCREEN_WIDTH / 750) * 2
const AVAILABLE_WIDTH = SCREEN_WIDTH - HORIZONTAL_PADDING
const AVAILABLE_HEIGHT = (SCREEN_HEIGHT - FOOTER_HEIGHT) - VERTICAL_PADDING
const CARD_RATIO = 49 / 40
const MIN_SECTION_HEIGHT = 140
const SECTION_INNER_PADDING_Y = 8

function toDisplayImage(path) {
	if (!path) return DEFAULT_AVATAR
	if (typeof path !== 'string') return DEFAULT_AVATAR
	if (path.startsWith('http://') || path.startsWith('https://') || path.startsWith('data:') || path.startsWith('/')) {
		return path
	}
	return getImageUrl(path)
}

function toDisplayName(value, fallback = '未命名') {
	const name = String(value || '').trim()
	return name || fallback
}

function formatStudentList(list = []) {
	return list.map((item = {}, index) => ({
		id: item.ryid || item.xyid || `xy-${index}`,
		name: toDisplayName(item.xm, '学员'),
		avatar: toDisplayImage(item.zjzwj)
	}))
}

function formatParentList(list = []) {
	return list.map((item = {}, index) => ({
		id: item.ryid || item.jzid || `jz-${index}`,
		name: toDisplayName(item.xm, '家长'),
		avatar: toDisplayImage(item.yszwj)
	}))
}

function createMockRefreshPayload() {
	return {
		xyList: [
			{ ryid: 'xy-1', xm: '张珊', zjzwj: '/static/logo.png' },
			{ ryid: 'xy-2', xm: '王舞', zjzwj: '/static/logo.png' },
			{ ryid: 'xy-3', xm: '李丽思', zjzwj: '/static/logo.png' }
		],
		jzList: [
			{ ryid: 'jz-1', xm: '张珊', yszwj: '/static/logo.png' },
			{ ryid: 'jz-2', xm: '王舞', yszwj: '/static/logo.png' },
			{ ryid: 'jz-3', xm: '李丽思', yszwj: '/static/logo.png' },
			{ ryid: 'jz-4', xm: '张珊二', yszwj: '/static/logo.png' }
		]
	}
}

function extractRefreshPayload(data = {}) {
	const direct = data && typeof data === 'object' ? data : {}
	if (Array.isArray(direct.xyList) || Array.isArray(direct.jzList)) {
		return direct
	}
	const ssData = direct.ssData && typeof direct.ssData === 'object' ? direct.ssData : null
	if (ssData && (Array.isArray(ssData.xyList) || Array.isArray(ssData.jzList))) {
		return ssData
	}
	const requestData = direct.request && typeof direct.request === 'object' ? direct.request : null
	if (requestData && (Array.isArray(requestData.xyList) || Array.isArray(requestData.jzList))) {
		return requestData
	}
	return {}
}

function calculateOptimalGrid(count, areaWidth, areaHeight, cardRatio = CARD_RATIO) {
	if (!count || areaWidth <= 0 || areaHeight <= 0) return { rows: 0, cols: 0 }
	if (count === 1) return { rows: 1, cols: 1 }

	const targetRatio = areaHeight / areaWidth
	let bestRows = 1
	let bestCols = count
	let bestScore = Number.POSITIVE_INFINITY

	for (let rows = 1; rows <= count; rows++) {
		const cols = Math.ceil(count / rows)
		const capacity = rows * cols
		const wasteRatio = (capacity - count) / count

		const maxWidth = areaWidth / cols
		const maxHeight = areaHeight / rows / cardRatio
		const isWidthLimited = maxWidth < maxHeight
		const ratioDiff = Math.abs((rows / cols) - targetRatio)

		const widthPenalty = isWidthLimited ? 0 : 2.0
		const score = widthPenalty + wasteRatio * 0.5 + ratioDiff * 0.3
		if (score < bestScore) {
			bestScore = score
			bestRows = rows
			bestCols = cols
		}
	}

	return { rows: bestRows, cols: bestCols }
}

function buildSectionLayout(users = [], sectionHeight = 0, options = {}) {
	const list = Array.isArray(users) ? users : []
	const count = list.length
	if (!count || sectionHeight <= 0) {
		return { positions: [] }
	}

	const avatarScale = options.avatarScale || 0.55
	const avatarRatio = options.avatarRatio || 1 // width / height
	const maxSingleCardWidth = options.maxSingleCardWidth || 600
	const baseCols = options.baseCols || 4
	const areaWidth = AVAILABLE_WIDTH
	const areaHeight = Math.max(0, sectionHeight - SECTION_INNER_PADDING_Y * 2)
	if (areaWidth <= 0 || areaHeight <= 0) {
		return { positions: [] }
	}

	const { rows, cols } = calculateOptimalGrid(count, areaWidth, areaHeight, CARD_RATIO)
	if (!rows || !cols) return { positions: [] }

	const maxWidth = areaWidth / cols
	const maxHeight = areaHeight / rows / CARD_RATIO
	let cardWidth = Math.min(maxWidth, maxHeight)
	if (count === 1) {
		cardWidth = Math.min(cardWidth, maxSingleCardWidth)
	}
	const cardHeight = cardWidth * CARD_RATIO
	const totalWidth = cols * cardWidth
	const totalHeight = rows * cardHeight

	const offsetX = (areaWidth - totalWidth) / 2 + HORIZONTAL_PADDING / 2
	const offsetY = (areaHeight - totalHeight) / 2 + SECTION_INNER_PADDING_Y

	const baseCardWidth = AVAILABLE_WIDTH / baseCols
	const baseFontSize = 30 * (SCREEN_WIDTH / 750)
	const fontSize = Math.max(12, (cardWidth / baseCardWidth) * baseFontSize)
	const gap = Math.max(6, cardWidth * 0.06)
	const avatarWidth = cardWidth * avatarScale
	const avatarHeight = avatarWidth / avatarRatio

	const positions = list.map((item, index) => {
		const row = Math.floor(index / cols)
		const col = index % cols
		const itemsInCurrentRow = Math.min(cols, count - row * cols)
		const rowCenterOffset = itemsInCurrentRow < cols
			? (cols - itemsInCurrentRow) * cardWidth / 2
			: 0
		return {
			...item,
			left: offsetX + col * cardWidth + rowCenterOffset,
			top: offsetY + row * cardHeight,
			width: cardWidth,
			height: cardHeight,
			fontSize,
			gap,
			avatarWidth,
			avatarHeight
		}
	})

	return { positions }
}

const sectionHeights = computed(() => {
	const studentCount = studentNotifyUsers.value.length
	const parentCount = parentUnreadUsers.value.length
	if (!studentCount && !parentCount) {
		return { student: 0, parent: 0 }
	}
	if (!studentCount) {
		return { student: 0, parent: AVAILABLE_HEIGHT }
	}
	if (!parentCount) {
		return { student: AVAILABLE_HEIGHT, parent: 0 }
	}
	let studentHeight = Math.round(AVAILABLE_HEIGHT * 0.7)
	studentHeight = Math.max(MIN_SECTION_HEIGHT, Math.min(studentHeight, AVAILABLE_HEIGHT - MIN_SECTION_HEIGHT))
	return {
		student: studentHeight,
		parent: AVAILABLE_HEIGHT - studentHeight
	}
})

const studentLayout = computed(() => buildSectionLayout(studentNotifyUsers.value, sectionHeights.value.student, {
	avatarScale: 0.56,
	avatarRatio: 68 / 100,
	maxSingleCardWidth: 260
}))

const parentLayout = computed(() => buildSectionLayout(parentUnreadUsers.value, sectionHeights.value.parent, {
	avatarScale: 0.48,
	avatarRatio: 1,
	maxSingleCardWidth: 220
}))

function resolveDeviceSn(options = {}) {
	if (options && options.sn) return String(options.sn)

	let userInfo = uni.getStorageSync('userInfo') || {}
	if (typeof userInfo === 'string') {
		try {
			userInfo = JSON.parse(userInfo)
		} catch (error) {
			userInfo = {}
		}
	}
	if (userInfo.devId) return String(userInfo.devId)

	const deviceInfo = uni.getStorageSync('deviceInfo') || {}
	if (deviceInfo.deviceId) return String(deviceInfo.deviceId)

	return 'ssDevId_a'
}

async function refreshNoticeUsers() {
	try {
		const result = await deviceApi.tel_refreshLogin()
		const payload = extractRefreshPayload(result?.data || {})
		let xyList = Array.isArray(payload.xyList) ? payload.xyList : []
		let jzList = Array.isArray(payload.jzList) ? payload.jzList : []

		if (!xyList.length && !jzList.length && USE_MOCK_WHEN_EMPTY) {
			const mock = createMockRefreshPayload()
			xyList = mock.xyList
			jzList = mock.jzList
		}

		studentNotifyUsers.value = formatStudentList(xyList)
		parentUnreadUsers.value = formatParentList(jzList)
	} catch (error) {
		console.error('刷新电话机首页留言失败:', error)
		if (USE_MOCK_WHEN_EMPTY) {
			const mock = createMockRefreshPayload()
			studentNotifyUsers.value = formatStudentList(mock.xyList)
			parentUnreadUsers.value = formatParentList(mock.jzList)
			return
		}
		studentNotifyUsers.value = []
		parentUnreadUsers.value = []
	}
}

function bindNoticeSocketListeners() {
	if (wsUnsubscribers.length) return
	const offRefresh = websocketService.on('cmd:51', async () => {
		await refreshNoticeUsers()
		uni.$emit('device-message-refresh')
	})
	wsUnsubscribers.push(offRefresh)
}

async function ensureNoticeSocketConnected() {
	if (!deviceSn.value) return
	bindNoticeSocketListeners()
	try {
		await websocketService.ensureConnected({
			role: 'device',
			ssDevId: deviceSn.value,
			heartbeat: true,
			autoReconnect: true,
			maxReconnectAttempts: 5
		})
	} catch (error) {
		console.error('notice 建立设备 websocket 失败:', error)
	}
}

onLoad(async (options) => {
	deviceSn.value = resolveDeviceSn(options || {})
	noticeBootstrapped.value = true
	skipFirstOnShow.value = true
	await refreshNoticeUsers()
	await ensureNoticeSocketConnected()
})

onShow(async () => {
	if (!noticeBootstrapped.value) return
	if (skipFirstOnShow.value) {
		skipFirstOnShow.value = false
		return
	}
	if (!deviceSn.value) {
		deviceSn.value = resolveDeviceSn({})
	}
	await refreshNoticeUsers()
	await ensureNoticeSocketConnected()
})

onUnload(() => {
	wsUnsubscribers.forEach((off) => {
		if (typeof off === 'function') off()
	})
	wsUnsubscribers.length = 0
})
</script>

<style>
.notice-page {
	width: 100vw;
	height: 100vh;
	background: #000;
	overflow: hidden;
	padding-bottom: 50rpx;
	box-sizing: border-box;
}

.bg {
	width: 100%;
	height: 100%;
}

.notify-container {
	width: 100%;
	height: 100%;
	display: flex;
	flex-direction: column;
}

.notify-section {
	width: 100%;
	position: relative;
	overflow: hidden;
}

.section-student {
	background: #fff;
}

.section-student.with-divider {
	border-bottom: 1px solid #b5b5b5;
}

.section-parent {
	background: #d9d9d9;
}

.notify-card {
	position: absolute;
	box-sizing: border-box;
	display: flex;
	flex-direction: column;
	align-items: center;
	justify-content: center;
	border: 1px solid #b3b3b3;
}

.student-card {
	background: #fff;
}

.parent-card {
	background: #d9d9d9;
}

.avatar {
	box-sizing: border-box;
	display: block;
	background: #f2f2f2;
}

.student-avatar {
	border-radius: 4rpx;
	border: 1px solid #e6e6e6;
}

.parent-avatar {
	border-radius: 50%;
	border: 2px solid #fff;
}

.name {
	color: #000;
	font-weight: 600;
	text-align: center;
	word-break: break-all;
	line-height: 1.25;
}

.footer {
	position: fixed;
	left: 0;
	right: 0;
	bottom: 0;
	width: 100%;
	height: 50rpx;
	background: #b2b2b2;
	display: flex;
	align-items: center;
	justify-content: center;
}

.footer-text {
	color: #000;
	font-size: 26rpx;
	line-height: 1;
	display: inline-flex;
	align-items: center;
}

.triangles {
	display: inline-flex;
	align-items: center;
	gap: 6rpx;
	margin-right: 12rpx;
}

.triangle {
	width: 0;
	height: 0;
	border-top: 10rpx solid transparent;
	border-bottom: 10rpx solid transparent;
	border-left: 14rpx solid #000;
}
</style>
