<template>
	<view class="chat-container">
		<u-navbar class="chat-navbar" :is-back="true" :title="navbarTitle" title-color="#000"></u-navbar>

		<view class="chat-header">
			<view class="header-main" @click="detailOpen = !detailOpen">
				<view class="patient-info-group">
					<text class="patient-name">{{ navbarTitle }}</text>
					<view class="patient-badges">
						<view class="status-dot"></view>
						<text class="sub-text">{{ statusLine }}</text>
					</view>
				</view>
				<view class="header-action">
					<u-icon :name="detailOpen ? 'arrow-up' : 'arrow-down'" size="18" color="#6ACDBB"></u-icon>
				</view>
			</view>
			<PatientDetailPanel v-if="detailOpen && patientInfo" :info="patientInfo" />
		</view>

		<view v-if="isTransferRegister && transferCard" class="transfer-strip">
			<text class="transfer-t">转诊处方：{{ transferCard.prescription_no || '关联处方' }}</text>
			<view class="transfer-strip-actions">
				<view class="transfer-btn transfer-btn--ghost" @click.stop="openTransferDetail">详情</view>
				<view v-if="canImportTransferRxStrip" class="transfer-btn transfer-btn--ghost" @click.stop="onTransferImportToPrescription">添加到处方</view>
				<view class="transfer-btn" @click="goPrescriptionV2">去开方</view>
			</view>
		</view>

		<scroll-view
			class="chat-messages"
			:scroll-top="scrollTop"
			:scroll-into-view="scrollIntoView"
			scroll-y
			scroll-with-animation
			upper-threshold="80"
			@scrolltoupper="onScrollToUpper"
		>
			<view class="message-list-container">
				<view v-if="!hasMore && messages.length > 0" class="empty-state empty-state--top">
					<text class="empty-text">没有更多消息了</text>
				</view>
				<view v-if="historyLoading && page > 1" class="loading-spinner">
					<text class="spinner-text">历史记录加载中...</text>
				</view>

				<view v-for="(msg, index) in messages" :key="msg._wxKey" class="message-wrapper">
					<view v-if="shouldShowTime(msg, index)" class="time-divider">
						<text class="time-text">{{ timeDividerText(msg) }}</text>
					</view>
					<MessageBubble
						:msg="msg"
						:is-mine="isMsgMine(msg)"
						:avatar="getMsgAvatar(msg)"
						:is-playing="isPlaying(msg)"
						:can-add-experience-to-rx="canAddExperienceDrugFromChat"
						@previewImage="previewImage"
						@playAudio="playAudio"
						@viewPrescription="onBubbleViewPrescription"
						@addExperienceToRx="onAddExperienceToPrescription"
						@addRegisterDrugsToRx="onRegisterDrugsBulkToPrescription"
					/>
				</view>

				<view id="chat-bottom-anchor" class="chat-bottom-anchor"></view>

				<view v-if="messages.length === 0 && !bootstrapLoading" class="empty-state">
					<text class="empty-text">暂无消息</text>
				</view>
			</view>
		</scroll-view>

		<ReceptionActionBar
			:register-status="registerStatusNum"
			:room-ended="actionBarRoomEnded"
			@accept="onAccept"
			@refuse="onRefuseTap"
			@prescription="goPrescriptionV2"
			@end="onEndDiagnosis"
		/>

		<view class="chat-footer" :style="{ bottom: keyboardHeight + 'px' }">
			<view v-if="inputLocked" class="consultation-ended-notice">
				<u-icon name="info-circle" size="16" color="#999"></u-icon>
				<text>{{ lockReason }}</text>
			</view>
			<view v-else class="input-bar">
				<view class="icon-btn" @click="toggleVoiceInput">
					<u-icon :name="voiceInputActive ? 'keyboard' : 'mic'" size="24" color="#666"></u-icon>
				</view>
				<view class="input-field-wrapper">
					<input
						v-if="!voiceInputActive"
						v-model="newMessage"
						class="main-input"
						placeholder="输入消息..."
						confirm-type="send"
						@confirm="sendTextMessage"
						@focus="onInputFocus"
						@blur="onInputBlur"
					/>
					<view
						v-else
						class="voice-btn"
						:class="{ recording: recording }"
						@touchstart="startVoiceRecording"
						@touchend="stopVoiceRecording"
						@touchcancel="stopVoiceRecording"
					>
						{{ recording ? '录音中...' : '按住说话' }}
					</view>
				</view>
				<view class="right-action">
					<view v-if="newMessage.trim()" class="send-btn" @click="sendTextMessage">发送</view>
					<view class="icon-btn plus-wrap" @click="toggleToolPanel">
						<text class="plus-char" :class="{ 'plus-char--open': showToolPanel }">+</text>
					</view>
				</view>
			</view>
			<view v-if="showToolPanel && !inputLocked" class="tool-panel-wrap">
				<swiper
					class="tool-panel-swiper"
					:indicator-dots="true"
					indicator-color="rgba(0,0,0,0.15)"
					indicator-active-color="#6ACDBB"
					:current="toolPanelSwiperCurrent"
					@change="onToolPanelSwiperChange"
				>
					<swiper-item>
						<view class="tool-grid">
							<view class="tool-cell" @click="onMorePickImage">
								<view class="tool-cell-icon"><u-icon name="photo" size="30" color="#6ACDBB"></u-icon></view>
								<text class="tool-cell-label">图片</text>
							</view>
							<view class="tool-cell" @click="onMoreQuickReply">
								<view class="tool-cell-icon"><u-icon name="list" size="30" color="#6ACDBB"></u-icon></view>
								<text class="tool-cell-label">常用回复</text>
							</view>
							<view class="tool-cell" @click="onMorePatientInfo">
								<view class="tool-cell-icon"><u-icon name="account" size="30" color="#6ACDBB"></u-icon></view>
								<text class="tool-cell-label">患者信息</text>
							</view>
							<view class="tool-cell" @click="onMoreOrdersPopup">
								<view class="tool-cell-icon"><u-icon name="order" size="30" color="#6ACDBB"></u-icon></view>
								<text class="tool-cell-label">挂号订单</text>
							</view>
							<view class="tool-cell" :class="{ 'is-disabled': transferToolDisabled }" @click="onMoreTransferDetail">
								<view class="tool-cell-icon"><u-icon name="share" size="30" color="#6ACDBB"></u-icon></view>
								<text class="tool-cell-label">转诊详情</text>
							</view>
							<view class="tool-cell" :class="{ 'is-disabled': goRxToolDisabled }" @click="onMoreGoPrescription">
								<view class="tool-cell-icon"><u-icon name="edit-pen" size="30" color="#6ACDBB"></u-icon></view>
								<text class="tool-cell-label">去开方</text>
							</view>
							<view class="tool-cell" :class="{ 'is-disabled': experienceToolDisabled }" @click="onMoreExperienceToRx">
								<view class="tool-cell-icon"><u-icon name="file-text" size="30" color="#6ACDBB"></u-icon></view>
								<text class="tool-cell-label">诊历带药</text>
							</view>
              <view class="tool-cell" :class="{ 'is-disabled': endToolDisabled }" @click="onMoreEndDiagnosis">
                <view class="tool-cell-icon"><u-icon name="checkmark-circle" size="30" color="#6ACDBB"></u-icon></view>
                <text class="tool-cell-label">结束问诊</text>
              </view>
						</view>
					</swiper-item>
					<swiper-item>
						<view class="tool-grid">
              <view class="tool-cell" :class="{ 'is-disabled': refuseToolDisabled }" @click="onMoreRefuse">
                <view class="tool-cell-icon"><u-icon name="close-circle" size="30" color="#6ACDBB"></u-icon></view>
                <text class="tool-cell-label">拒诊</text>
              </view>
							<view v-for="ph in toolPlaceholderCells" :key="ph._wxKey" class="tool-cell tool-cell--placeholder"></view>
						</view>
					</swiper-item>
				</swiper>
			</view>
		</view>

		<view v-if="showQuickReplySheet" class="quick-reply-mask" @click="closeQuickReplySheet">
			<view class="quick-reply-sheet" @click.stop>
				<view class="quick-reply-head">
					<text class="quick-reply-title">常用回复</text>
					<text class="quick-reply-close" @click="closeQuickReplySheet">关闭</text>
				</view>
				<scroll-view v-if="!quickReplyLoading && quickReplyList.length" class="quick-reply-scroll" scroll-y>
					<view
						v-for="item in quickReplyList"
						:key="item._wxKey"
						class="quick-reply-row"
						@click="selectQuickReply(item)"
					>
						<text class="quick-reply-text">{{ item.content }}</text>
					</view>
				</scroll-view>
				<view v-else-if="quickReplyLoading" class="quick-reply-empty">加载中...</view>
				<view v-else class="quick-reply-empty">暂无常用回复</view>
			</view>
		</view>

		<view v-if="showRecordingModal" class="recording-overlay">
			<view class="recording-box">
				<text class="timer">{{ formatRecordingTime(recordingTime) }}</text>
				<text class="tip">松开结束录音</text>
			</view>
		</view>

		<RefuseReceptionModal ref="refuseModal" @confirm="onRefuseConfirm" />

		<u-popup v-model="ordersPopupOpen" mode="bottom" border-radius="20" length="70%" :closeable="true" :safe-area-inset-bottom="true">
			<view class="orders-popup" v-if="patientInfo">
				<text class="orders-popup-title">挂号与订单</text>
				<scroll-view scroll-y class="orders-popup-scroll">
					<view v-if="registerOrderBlock" class="orders-block">
						<view v-if="registerOrderBlock.order_no" class="orders-row">
							<text class="orders-k">挂号单号</text>
							<text class="orders-v order-font">{{ registerOrderBlock.order_no }}</text>
						</view>
						<view v-if="registerOrderBlock.price != null && registerOrderBlock.price !== ''" class="orders-row">
							<text class="orders-k">费用</text>
							<text class="orders-v price">¥{{ registerOrderBlock.price }}</text>
						</view>
						<view v-if="registerOrderBlock.status != null && registerOrderBlock.status !== ''" class="orders-row">
							<text class="orders-k">状态</text>
							<text class="orders-v">{{ registerOrderStatusText(registerOrderBlock.status) }}</text>
						</view>
						<view
							v-for="sub in childOrdersWithKey"
							:key="sub._wxKey"
							class="orders-sub"
						>
							<text class="orders-sub-label">子订单</text>
							<text class="orders-sub-no">{{ sub.order_no || sub.no || '—' }}</text>
						</view>
					</view>
					<view v-else class="orders-hint">暂无挂号单扩展信息</view>
					<text class="orders-section-title">本次挂号处方</text>
					<view v-if="ordersRxLoading" class="orders-hint">加载中…</view>
					<view v-else-if="!ordersRxList.length" class="orders-hint">暂无处方</view>
					<view
						v-for="rx in ordersRxList"
						:key="rx._wxKey"
						class="orders-rx-row"
						:data-wx-key="rx._wxKey"
						@click="onOrdersRxTap"
					>
						<text class="orders-rx-no">{{ rx.order_no || rx.prescription_no || '处方' }}</text>
						<text v-if="rx.total_pay_price != null" class="orders-rx-price">¥{{ rx.total_pay_price }}</text>
						<text class="orders-rx-link">详情</text>
					</view>
				</scroll-view>
				<view class="orders-popup-foot">
					<view class="orders-popup-close" @click="ordersPopupOpen = false">关闭</view>
				</view>
			</view>
		</u-popup>

		<u-popup v-model="transferDetailOpen" mode="bottom" border-radius="20" length="65%" :closeable="true" :safe-area-inset-bottom="true">
			<view class="transfer-detail-popup" v-if="isTransferRegister && transferCard">
				<text class="td-title">转诊信息</text>
				<scroll-view scroll-y class="td-scroll">
					<view v-if="transferCard.prescription_no" class="td-row">
						<text class="td-k">处方编号</text>
						<text class="td-v">{{ transferCard.prescription_no }}</text>
					</view>
					<view v-if="transferCard.transfer_store && transferCard.transfer_store.name" class="td-row">
						<text class="td-k">转诊诊所</text>
						<text class="td-v">{{ transferCard.transfer_store.name }}</text>
					</view>
					<view v-if="transferCard.delegate_store && transferCard.delegate_store.name" class="td-row">
						<text class="td-k">委托诊所</text>
						<text class="td-v">{{ transferCard.delegate_store.name }}</text>
					</view>
					<view v-if="transferCard.transfer_reason" class="td-row">
						<text class="td-k">转诊原因</text>
						<text class="td-v">{{ transferCard.transfer_reason }}</text>
					</view>
					<view v-if="transferCard.status != null" class="td-row">
						<text class="td-k">状态</text>
						<text class="td-v">{{ transferStatusText(transferCard.status) }}</text>
					</view>
					<view v-if="transferCard.questions && transferCard.questions.length" class="td-qa">
						<text class="td-qa-title">咨询问题</text>
						<view v-for="(qa, qix) in transferQuestionsWithKey" :key="qa._wxKey" class="td-qa-item">
							<text class="td-qa-q">{{ qix + 1 }}. {{ qa.question }}</text>
							<text class="td-qa-a">{{ qa.answer }}</text>
						</view>
					</view>
				</scroll-view>
				<view class="td-foot td-foot--row">
					<view v-if="canImportTransferRxStrip" class="transfer-btn transfer-btn--ghost" @click="onTransferImportToPrescription">添加到处方</view>
					<view class="transfer-btn" @click="transferDetailOpen = false">关闭</view>
				</view>
			</view>
		</u-popup>
	</view>
</template>

<script>
import { chatRoomManager } from '@/utils/chat/chatRoomManager.js';
import { chatConfig } from '@/config/chat.js';
import {
	getPatientItem,
	getQuickReplyListApi,
	getRegisterPrescriptionList,
	receptionApi,
	refuseReceptionApi,
	endOfDiagnosisApi,
	getTransferPrescriptionByRegisterApi,
	getDrugsByRegisterIdApi,
	getCurrentStoreTypeApi
} from '@/api/reception.js';
import {
	parseTransferPrescriptionContent,
	transferDrugToChineseRow,
	canImportTransferPrescription,
	normalizeTransferCardPayload
} from '../utils/transferPrescriptionImport.js';
import {
	getChatRoomByRegisterApi,
	getRoomStatusApi,
	uploadChatFileApi,
	sendToUserHttpApi,
	isSendToUserResponseOk,
	sendToUserFailMessage,
	extractSendToUserMessageId
} from '@/api/chat.js';
import { parseMessageContent } from '../utils/messageParse.js';
import PatientDetailPanel from '@/subPackages/sub_online_reception/components/PatientDetailPanel.vue';
import MessageBubble from '@/subPackages/sub_online_reception/components/MessageBubble.vue';
import ReceptionActionBar from '@/subPackages/sub_online_reception/components/ReceptionActionBar.vue';
import RefuseReceptionModal from '@/subPackages/sub_online_reception/components/RefuseReceptionModal.vue';
import { withWxKey, ensureWxKey } from '@/utils/wxListKey.js';
import { prepareImagePath } from '@/common/js/image-compress.js';
import { checkText, checkImage, handleSecurityError, SCENE_CHAT } from '@/common/js/content-security.js';

const PENDING_RX_FROM_CHAT = 'xk_pending_rx_from_chat';

const TOOL_PLACEHOLDER_CELLS = Array.from({ length: 7 }, (_, i) => ({ _wxKey: `ph${i}` }));

export default {
	components: { PatientDetailPanel, MessageBubble, ReceptionActionBar, RefuseReceptionModal },
	data() {
		return {
			registerId: '',
			roomId: '',
			patientName: '',
			userPatientId: '',
			patientInfo: null,
			patientUserId: '',
			messages: [],
			newMessage: '',
			voiceInputActive: false,
			recording: false,
			recordingTime: 0,
			recordingTimer: null,
			showRecordingModal: false,
			currentPlayingAudio: null,
			innerAudioContext: null,
			scrollTop: 0,
			scrollIntoView: '',
			oldScrollViewHeight: 0,
			page: 1,
			hasMore: true,
			bootstrapLoading: false,
			historyLoading: false,
			isConsultationEnded: false,
			keyboardHeight: 0,
			currentUserId: '',
			detailOpen: false,
			roomChatStatus: 0,
			transferCard: null,
			recorderManager: null,
			showToolPanel: false,
			showQuickReplySheet: false,
			quickReplyList: [],
			quickReplyLoading: false,
			transferDetailOpen: false,
			ordersPopupOpen: false,
			ordersRxList: [],
			ordersRxLoading: false,
			toolPanelSwiperCurrent: 0,
			toolPlaceholderCells: TOOL_PLACEHOLDER_CELLS
		};
	},
	computed: {
		navbarTitle() {
			return this.patientName || '在线问诊';
		},
		/**
		 * 患者气泡头像：有就诊人头像用真实图，否则按性别默认 nan/nv
		 */
		patientAvatar() {
			const up = this.patientInfo && this.patientInfo.user_patient;
			if (up && up.avatar) return up.avatar;
			const sex = up ? Number(up.sex) : 0;
			return require(`@/static/image/${sex % 2 === 0 ? 'nv' : 'nan'}.png`);
		},
		doctorAvatar() {
			return uni.getStorageSync('doctor_avatar') || '';
		},
		/** 当前挂号状态（与 RegisterStatusEnum 对齐：1=待接诊 2=接诊中） */
		registerStatusNum() {
			return Number(this.patientInfo && this.patientInfo.status) || 0;
		},
		/**
		 * 问诊会话是否已结束（锁输入、隐藏操作栏）
		 * 待接诊(1)/接诊中(2) 均不以复用房间的历史结束态为准：
		 * 否则会藏掉接诊按钮，或接诊后房间未同步导致无法发消息
		 */
		roomEnded() {
			const st = this.registerStatusNum;
			if (st === 1 || st === 2) return false;
			return this.roomChatStatus === 1 || this.isConsultationEnded;
		},
		/**
		 * 传给 ReceptionActionBar：待接诊/接诊中强制不隐藏操作栏
		 */
		actionBarRoomEnded() {
			const st = this.registerStatusNum;
			if (st === 1 || st === 2) return false;
			return this.roomEnded;
		},
		inputLocked() {
			const st = this.registerStatusNum;
			// 待接诊、接诊中均可发消息，不因房间残留结束态锁定
			if (st === 1 || st === 2) return false;
			if (this.roomEnded) return true;
			if (!st) return false;
			return true;
		},
		lockReason() {
			const st = this.registerStatusNum;
			if (st === 1 || st === 2) return '';
			if (this.roomChatStatus === 1 || this.isConsultationEnded) return '问诊已结束';
			if (st === 7 || st === 8) return '已拒诊';
			if (st >= 3) return '当前状态不可发送消息';
			return '不可发送';
		},
		statusLine() {
			const st = this.registerStatusNum;
			const m = {
				0: '待支付',
				1: '待接诊',
				2: '接诊中',
				3: '已结束',
				4: '已取消',
				7: '已拒诊',
				8: '已拒诊'
			};
			return m[st] || '问诊中';
		},
		canAddExperienceDrugFromChat() {
			const st = this.registerStatusNum;
			return st === 2 && !this.inputLocked && !!this.registerId;
		},
		registerOrderBlock() {
			const i = this.patientInfo;
			return i && i.register_order ? i.register_order : null;
		},
		childOrdersBlock() {
			const ro = this.registerOrderBlock;
			if (!ro) return [];
			const raw = ro.child_orders || ro.sub_orders || ro.orders;
			return Array.isArray(raw) ? raw : [];
		},
		childOrdersWithKey() {
			return withWxKey(this.childOrdersBlock, 'id', 'osub');
		},
		transferQuestionsWithKey() {
			const qs = (this.transferCard && this.transferCard.questions) || [];
			return withWxKey(qs, 'id', 'tdq');
		},
		refuseToolDisabled() {
			return Number(this.patientInfo && this.patientInfo.status) !== 1;
		},
		endToolDisabled() {
			if (this.roomEnded) return true;
			return Number(this.patientInfo && this.patientInfo.status) !== 2;
		},
		transferToolDisabled() {
			return !this.isTransferRegister || !this.transferCard;
		},
		experienceToolDisabled() {
			return !this.canAddExperienceDrugFromChat;
		},
		goRxToolDisabled() {
			return Number(this.patientInfo && this.patientInfo.status) !== 2;
		},
		canImportTransferRxStrip() {
			return this.isTransferRegister && canImportTransferPrescription(this.transferCard);
		},
		isTransferRegister() {
			const p = this.patientInfo;
			if (!p) return false;
			if (Number(p.is_from_transfer) === 1) return true;
			const ro = p.register_order;
			if (ro && Number(ro.is_from_transfer) === 1) return true;
			const subs = p.child_orders || p.sub_orders || p.orders;
			if (Array.isArray(subs)) {
				return subs.some((c) => Number(c.is_from_transfer) === 1);
			}
			return false;
		}
	},
	onLoad(options) {
		this.registerId = options.register_id || '';
		let rid = options.room_id || '';
		try {
			rid = rid ? decodeURIComponent(rid) : '';
		} catch (e) {
			rid = options.room_id || '';
		}
		this.roomId = rid;
		try {
			this.patientName = options.patient_name ? decodeURIComponent(options.patient_name) : '';
		} catch (e) {
			this.patientName = options.patient_name || '';
		}
		this.userPatientId = options.user_patient_id || '';

		this.currentUserId = this.getHttpSenderUserId();

		chatRoomManager.onMessage(this.handleChatMessage);
		uni.onKeyboardHeightChange((res) => {
			this.keyboardHeight = res.height || 0;
			if (res.height > 0) this.scrollToBottom();
		});
		uni.$on('message-updated', this.onMessageUpdated);

		this.bootstrap(options);
	},
	onShow() {
		if (this.roomId) chatRoomManager.enterRoom(this.roomId);
		this.refreshLatestChatMessages();
	},
	onHide() {
		this.stopAudio();
	},
	onUnload() {
		if (this.innerAudioContext) this.innerAudioContext.destroy();
		chatRoomManager.leaveRoom();
		chatRoomManager.offMessage(this.handleChatMessage);
		uni.$off('message-updated', this.onMessageUpdated);
	},
	mounted() {
		this.recorderManager = uni.getRecorderManager();
		this.initRecorder();
	},
	methods: {
		unwrap(res) {
			if (res == null) return null;
			if (res.result !== undefined && res.result !== null) return res.result;
			if (res.data && res.data.result !== undefined) return res.data.result;
			return res;
		},
		async bootstrap() {
			if (!this.registerId) {
				uni.showToast({ title: '缺少挂号信息', icon: 'none' });
				return;
			}
			this.bootstrapLoading = true;
			try {
				await this.loadPatientDetail();
				if (!this.roomId) {
					const roomRes = await getChatRoomByRegisterApi(this.registerId);
					const roomPayload = this.unwrap(roomRes) || {};
					this.roomId = roomPayload.room_id || '';
					if (roomPayload.receiver_user_id) this.patientUserId = roomPayload.receiver_user_id;
				}
				if (!this.patientUserId && this.patientInfo) {
					const uid = this.patientInfo.user_id || this.patientInfo.user_patient?.user_id;
					if (uid) this.patientUserId = `user-${uid}`;
					else if (this.userPatientId) this.patientUserId = `user-${this.userPatientId}`;
				}
				if (this.roomId) {
					chatRoomManager.enterRoom(this.roomId);
					await this.refreshRoomStatus();
					this.page = 1;
					this.hasMore = true;
					await this.loadHistory();
				}
				await this.loadTransferCard();
			} catch (e) {
				console.error(e);
				uni.showToast({ title: '加载失败', icon: 'none' });
			} finally {
				this.bootstrapLoading = false;
			}
		},
		async refreshLatestChatMessages() {
			if (!this.roomId || this.bootstrapLoading || this.historyLoading) return;
			try {
				this.page = 1;
				this.hasMore = true;
				await this.loadHistory();
			} catch (e) {
				console.warn('refreshLatestChatMessages', e);
			}
		},
		async loadPatientDetail() {
			const res = await getPatientItem(this.registerId);
			const data = this.unwrap(res);
			this.patientInfo = data && typeof data === 'object' ? data : null;
			if (this.patientInfo) {
				if (!this.patientName && this.patientInfo.user_patient) {
					this.patientName = this.patientInfo.user_patient.name || '';
				}
				const uid = this.patientInfo.user_id || this.patientInfo.user_patient?.user_id;
				if (uid) this.patientUserId = `user-${uid}`;
			}
		},
		async refreshRoomStatus() {
			if (!this.roomId) return;
			try {
				const res = await getRoomStatusApi({ room_id: this.roomId });
				const raw = this.unwrap(res) || {};
				this.roomChatStatus = Number(raw.status) || 0;
				const st = this.registerStatusNum;
				// 待接诊/接诊中：房间可能仍是上一单结束态，不据此标记本单已结束
				if (this.roomChatStatus === 1 && st !== 1 && st !== 2) {
					this.isConsultationEnded = true;
				}
			} catch (e) {
				console.warn(e);
			}
		},
		async loadTransferCard() {
			try {
				const res = await getTransferPrescriptionByRegisterApi(this.registerId);
				const data = this.unwrap(res);
				this.transferCard = normalizeTransferCardPayload(data);
			} catch (e) {
				this.transferCard = null;
			}
		},
		syncMessagesFromRoom() {
			const raw = chatRoomManager.getRoomMessages(this.roomId) || [];
			this.messages = withWxKey(raw, 'id', 'm');
		},
		onMessageUpdated({ roomId }) {
			if (roomId === this.roomId) {
				this.syncMessagesFromRoom();
				this.$forceUpdate();
			}
		},
		handleChatMessage(message, roomId) {
			if (roomId === this.roomId) {
				this.addMessage(message);
				this.scrollToBottom();
			}
		},
		onScrollToUpper() {
			if (this.bootstrapLoading) return;
			this.loadHistory();
		},
		async loadHistory() {
			if (!this.hasMore || this.historyLoading || !this.roomId) return;
			const wasFirstPage = this.page === 1;
			if (!wasFirstPage) {
				await new Promise((resolve) => {
					this.$nextTick(() => {
						const query = uni.createSelectorQuery().in(this);
						query
							.select('.message-list-container')
							.boundingClientRect((res) => {
								this.oldScrollViewHeight = res && res.height ? res.height : 0;
								resolve();
							})
							.exec();
					});
				});
			}
			this.historyLoading = true;
			try {
				const lastMessageId = wasFirstPage ? 0 : chatRoomManager.lastMessageId;
				const batch = await chatRoomManager.loadRoomMessages(this.roomId, lastMessageId);
				if (batch && batch.length > 0) {
					this.syncMessagesFromRoom();
					this.page++;
				} else {
					this.hasMore = false;
				}
			} catch (e) {
				console.error(e);
				this.hasMore = false;
			} finally {
				this.historyLoading = false;
				this.$nextTick(() => {
					if (wasFirstPage && this.messages.length > 0) {
						this.scrollToBottom();
					} else if (!wasFirstPage) {
						this.scrollToOldLastMessage();
					}
				});
			}
		},
		addMessage(message) {
			if (!message.message_content && message.content) {
				message.message_content = message.content;
			}
			if (message.message_type === undefined && message.type !== undefined) {
				message.message_type = chatConfig.getMessageType(message.type);
			}
			const normalizeContent = (c) => {
				if (c == null || c === '') return '';
				if (typeof c === 'string') {
					try {
						return JSON.stringify(JSON.parse(c));
					} catch (e) {
						return c;
					}
				}
				if (typeof c === 'object') return JSON.stringify(c);
				return String(c);
			};
			if (!message.isTemporary) {
				const tempIndex = this.messages.findIndex((m) => {
					if (!m.isTemporary) return false;
					if ((m.sender_user_id || '') !== (message.sender_user_id || '')) return false;
					if (normalizeContent(m.message_content) !== normalizeContent(message.message_content)) return false;
					const t1 = Number(m.created_at || m.timestamp || 0);
					const t2 = Number(message.created_at || message.timestamp || 0);
					const a = t1 > 1e12 ? t1 : t1 * 1000;
					const b = t2 > 1e12 ? t2 : t2 * 1000;
					return Math.abs(a - b) < 60000;
				});
				if (tempIndex !== -1) {
					const patched = ensureWxKey({ ...message, isTemporary: false }, 'id', `m${tempIndex}`);
					this.$set(this.messages, tempIndex, patched);
					if (message.message_type === chatConfig.messageTypes['end-consultation']) {
						this.applyEndConsultationMessage(message);
					}
					return;
				}
			}
			const existingIndex = this.messages.findIndex((m) => m.id === message.id);
			if (existingIndex !== -1) {
				this.$set(this.messages, existingIndex, ensureWxKey(message, 'id', `m${existingIndex}`));
				if (message.message_type === chatConfig.messageTypes['end-consultation']) {
					this.applyEndConsultationMessage(message);
				}
				return;
			}
			this.messages.push(ensureWxKey(message, 'id', `m${this.messages.length}`));
			// 结束问诊卡片：待接诊忽略历史结束态；接诊中仅认本单创建之后的结束消息
			if (message.message_type === chatConfig.messageTypes['end-consultation']) {
				this.applyEndConsultationMessage(message);
			}
		},
		/**
		 * 根据结束问诊消息更新本单结束态，避免医患复用房间时历史卡片误锁待接诊
		 */
		applyEndConsultationMessage(message) {
			const st = this.registerStatusNum;
			if (st === 1) {
				return;
			}
			if (st === 2) {
				const msgTs = Number(message.created_at || message.timestamp || 0);
				const regTs = Number(this.patientInfo && this.patientInfo.created_at) || 0;
				const msgSec = msgTs > 1e12 ? Math.floor(msgTs / 1000) : msgTs;
				if (regTs > 0 && msgSec < regTs) {
					return;
				}
			}
			this.isConsultationEnded = true;
		},
		isMsgMine(msg) {
			const senderId = msg.sender_user_id || '';
			return senderId === this.currentUserId;
		},
		getMsgAvatar(msg) {
			const sid = msg.sender_user_id || '';
			const assistantFallback = 'https://xiaokang88.oss-cn-hangzhou.aliyuncs.com/xk_upload_20250108/29bdb9560340373bb3096394a845afa5.jpg';
			if (sid === 'doctor_assistant') return assistantFallback;
			if (this.isMsgMine(msg)) {
				return this.doctorAvatar || assistantFallback;
			}
			// 患者侧：真实头像或性别默认（patientAvatar 已处理）
			return this.patientAvatar;
		},
		toMessageMs(t) {
			const n = Number(t) || 0;
			return n > 1e12 ? n : n * 1000;
		},
		shouldShowTime(msg, index) {
			if (index === 0) return true;
			const prev = this.messages[index - 1];
			const currMs = this.toMessageMs(msg.created_at || msg.timestamp || 0);
			const prevMs = this.toMessageMs(prev.created_at || prev.timestamp || 0);
			return currMs - prevMs > 300000;
		},
		timeDividerText(msg) {
			if (msg.created_at_text) return msg.created_at_text;
			return this.formatTimeDivider(msg.created_at || msg.timestamp);
		},
		formatTimeDivider(ts) {
			if (!ts && ts !== 0) return '';
			const ms = Number(ts) > 1e12 ? Number(ts) : Number(ts) * 1000;
			const d = new Date(ms);
			const h = d.getHours().toString().padStart(2, '0');
			const m = d.getMinutes().toString().padStart(2, '0');
			return `${h}:${m}`;
		},
		formatClockTime(date) {
			const hours = date.getHours().toString().padStart(2, '0');
			const minutes = date.getMinutes().toString().padStart(2, '0');
			return `${hours}:${minutes}`;
		},
		formatRecordingTime(seconds) {
			const mins = Math.floor(seconds / 60);
			const secs = seconds % 60;
			return `${mins.toString().padStart(2, '0')}:${secs.toString().padStart(2, '0')}`;
		},
		scrollToBottom() {
			if (!this.messages.length) return;
			this.scrollIntoView = '';
			this.$nextTick(() => {
				setTimeout(() => {
					this.scrollIntoView = 'chat-bottom-anchor';
					setTimeout(() => {
						if (this.scrollIntoView !== 'chat-bottom-anchor') {
							this.scrollIntoView = 'chat-bottom-anchor';
						}
					}, 100);
				}, 300);
			});
		},
		scrollToOldLastMessage() {
			this.scrollIntoView = '';
			this.$nextTick(() => {
				const query = uni.createSelectorQuery().in(this);
				query
					.select('.message-list-container')
					.boundingClientRect((rect) => {
						if (!rect) return;
						const heightDiff = rect.height - this.oldScrollViewHeight;
						if (heightDiff > 0) {
							const next = heightDiff;
							this.scrollTop = this.scrollTop === next ? next + 0.01 : next;
							this.oldScrollViewHeight = rect.height;
						}
					})
					.exec();
			});
		},
		onInputFocus() {
			this.scrollToBottom();
		},
		onInputBlur() {},
		toggleToolPanel() {
			this.showToolPanel = !this.showToolPanel;
			if (!this.showToolPanel) {
				this.closeQuickReplySheet();
				this.toolPanelSwiperCurrent = 0;
			}
		},
		onToolPanelSwiperChange(e) {
			const c = e && e.detail && e.detail.current;
			this.toolPanelSwiperCurrent = typeof c === 'number' ? c : 0;
		},
		onMorePickImage() {
			this.onToolPickImage();
		},
		onMoreQuickReply() {
			this.onToolQuickReply();
		},
		onMorePatientInfo() {
			this.showToolPanel = false;
			if (this.detailOpen) {
				uni.showToast({ title: '患者信息已在顶部展开', icon: 'none' });
				return;
			}
			this.detailOpen = true;
		},
		async onMoreOrdersPopup() {
			this.showToolPanel = false;
			if (!this.patientInfo) {
				uni.showToast({ title: '暂无患者信息', icon: 'none' });
				return;
			}
			this.ordersPopupOpen = true;
			await this.loadOrdersRxListForPopup();
		},
		async loadOrdersRxListForPopup() {
			if (!this.registerId) {
				this.ordersRxList = [];
				return;
			}
			this.ordersRxLoading = true;
			try {
				const res = await getRegisterPrescriptionList({
					register_id: this.registerId,
					page: 1,
					page_size: 30
				});
				const payload = this.unwrap(res) || {};
				this.ordersRxList = withWxKey(Array.isArray(payload.list) ? payload.list : [], 'id', 'orx');
			} catch (e) {
				console.warn(e);
				this.ordersRxList = [];
			} finally {
				this.ordersRxLoading = false;
			}
		},
		registerOrderStatusText(status) {
			const map = { 0: '待就诊', 1: '已缴费', 2: '已就诊', 3: '已取消', 4: '已退费' };
			const n = Number(status);
			return map[n] != null ? map[n] : String(status);
		},
		onOrdersRxTap(e) {
			const wxKey = e && e.currentTarget && e.currentTarget.dataset && e.currentTarget.dataset.wxKey;
			const rx = this.ordersRxList.find((r) => r._wxKey === wxKey);
			const id = rx && (rx.id || rx.prescription_id);
			if (!id) return;
			let url = `/subPackages/sub_my/prescriptionDetail?id=${id}`;
			const no = rx.order_no || rx.prescription_no;
			if (no) url += `&no=${encodeURIComponent(no)}`;
			this.ordersPopupOpen = false;
			uni.navigateTo({ url });
		},
		onMoreTransferDetail() {
			if (this.transferToolDisabled) {
				uni.showToast({ title: this.isTransferRegister ? '暂无转诊信息' : '当前挂号无转诊处方', icon: 'none' });
				return;
			}
			this.showToolPanel = false;
			this.openTransferDetail();
		},
		onMoreGoPrescription() {
			this.showToolPanel = false;
			if (this.goRxToolDisabled) {
				uni.showToast({ title: '请先接诊', icon: 'none' });
				return;
			}
			this.goPrescriptionV2();
		},
		onMoreExperienceToRx() {
			if (this.experienceToolDisabled) {
				uni.showToast({ title: '请先接诊后再开方', icon: 'none' });
				return;
			}
			uni.showModal({
				title: '诊历带药',
				content: '此操作会覆盖当前的处方，是否确认？',
				confirmText: '确认',
				cancelText: '取消',
				success: (modalRes) => {
					if (!modalRes.confirm) return;
					this.showToolPanel = false;
					this.onRegisterDrugsBulkToPrescription();
				}
			});
		},
		async onRegisterDrugsBulkToPrescription() {
			if (!this.canAddExperienceDrugFromChat) {
				uni.showToast({ title: '请先接诊后再开方', icon: 'none' });
				return;
			}
			uni.showLoading({ title: '加载药品…', mask: true });
			try {
				const rows = await this.fetchRegisterExperienceDrugRows();
				if (!rows.length) {
					uni.showToast({ title: '未找到药品信息', icon: 'none' });
					return;
				}
				try {
					uni.setStorageSync(
						PENDING_RX_FROM_CHAT,
						JSON.stringify({
							source: 'register_drugs',
							register_id: String(this.registerId),
							drugs: rows
						})
					);
				} catch (err) {
					console.error(err);
					uni.showToast({ title: '缓存失败', icon: 'none' });
					return;
				}
				this.goPrescriptionV2();
			} catch (e) {
				console.error(e);
				uni.showToast({ title: '加载失败', icon: 'none' });
			} finally {
				uni.hideLoading();
			}
		},
		onTransferImportToPrescription() {
			if (!this.isTransferRegister) {
				uni.showToast({ title: '当前挂号无转诊处方', icon: 'none' });
				return;
			}
			if (!this.transferCard || !canImportTransferPrescription(this.transferCard)) {
				uni.showToast({ title: '当前不可导入', icon: 'none' });
				return;
			}
			const parsed = parseTransferPrescriptionContent(this.transferCard);
			if (!parsed || !parsed.drugList || !parsed.drugList.length) {
				uni.showToast({ title: '处方内容为空', icon: 'none' });
				return;
			}
			const rows = parsed.drugList.map((d) => transferDrugToChineseRow(d, parsed.prescription_type));
			try {
				uni.setStorageSync(
					PENDING_RX_FROM_CHAT,
					JSON.stringify({
						source: 'transfer_import',
						register_id: String(this.registerId),
						drugs: rows,
						diagnosisText: parsed.clinical_diagnose || '',
						medicalAdvice: parsed.doctor_order || ''
					})
				);
			} catch (err) {
				console.error(err);
				uni.showToast({ title: '缓存失败', icon: 'none' });
				return;
			}
			this.transferDetailOpen = false;
			this.goPrescriptionV2();
		},
		onMoreRefuse() {
			if (this.refuseToolDisabled) return;
			this.showToolPanel = false;
			this.onRefuseTap();
		},
		onMoreEndDiagnosis() {
			if (this.endToolDisabled) {
				uni.showToast({ title: '当前不可结束问诊', icon: 'none' });
				return;
			}
			this.showToolPanel = false;
			this.onEndDiagnosis();
		},
		onToolPickImage() {
			this.showToolPanel = false;
			this.chooseImage();
		},
		async onToolQuickReply() {
			this.showToolPanel = false;
			this.showQuickReplySheet = true;
			this.quickReplyList = [];
			this.quickReplyLoading = true;
			try {
				const res = await getQuickReplyListApi();
				const raw = this.unwrap(res);
				const list = Array.isArray(raw) ? raw : raw && Array.isArray(raw.list) ? raw.list : [];
				this.quickReplyList = withWxKey(list, 'id', 'qr');
			} catch (e) {
				console.error(e);
				uni.showToast({ title: '加载常用回复失败', icon: 'none' });
				this.quickReplyList = [];
			} finally {
				this.quickReplyLoading = false;
			}
		},
		closeQuickReplySheet() {
			this.showQuickReplySheet = false;
		},
		selectQuickReply(item) {
			const t = item && (item.content != null ? String(item.content) : '');
			this.newMessage = t;
			this.closeQuickReplySheet();
			this.showToolPanel = false;
		},
		toggleVoiceInput() {
			this.voiceInputActive = !this.voiceInputActive;
		},
		async sendTextMessage() {
			if (!this.newMessage.trim() || !this.roomId || this.inputLocked) return;
			const content = this.newMessage;
			try {
				await checkText(content, SCENE_CHAT);
			} catch (e) {
				handleSecurityError(e);
				return;
			}
			this.sendMessage({ type: 'text', content });
			this.newMessage = '';
			this.showToolPanel = false;
		},
		sendMessage(message) {
			if (!this.roomId || !this.patientUserId || this.inputLocked) return;
			const now = new Date();
			const messageType = chatConfig.getMessageType(message.type || 'text');
			const tempId = `tmp_${Date.now()}_${Math.floor(Math.random() * 1000)}`;
			const fullMessage = {
				...message,
				id: tempId,
				sender_user_id: this.getHttpSenderUserId(),
				receiver_user_id: this.patientUserId,
				room_id: this.roomId,
				created_at_text: this.formatClockTime(now),
				created_at: now.getTime(),
				timestamp: now.getTime(),
				message_content: message.message_content || message.content || '',
				message_type: messageType,
				isTemporary: true
			};
			this.addMessage(fullMessage);
			this.scrollToBottom();
			this.postSendMessageHttp(fullMessage, tempId, messageType, message.duration || 0);
		},
		getHttpSenderUserId() {
			const raw = uni.getStorageSync('doctor_id') || uni.getStorageSync('user_id');
			const n = parseInt(raw, 10);
			if (!Number.isNaN(n) && n > 0) return `doctor-${n}`;
			return raw && String(raw).startsWith('doctor-') ? String(raw) : `doctor-${raw}`;
		},
		async postSendMessageHttp(fullMessage, tempId, messageType, duration) {
			const payload = {
				room_id: this.roomId,
				sender_user_id: this.getHttpSenderUserId(),
				receiver_user_id: this.patientUserId,
				message_type: messageType,
				message_content: fullMessage.message_content || '',
				duration: duration || 0,
				isFromHttp: true
			};
			try {
				const res = await sendToUserHttpApi(payload);
				if (!isSendToUserResponseOk(res)) {
					throw { res };
				}
				const mid = extractSendToUserMessageId(res);
				if (mid != null && mid !== '') {
					const idx = this.messages.findIndex((m) => m.id === tempId);
					if (idx !== -1) {
						this.$set(this.messages[idx], 'id', mid);
						this.$set(this.messages[idx], '_wxKey', String(mid));
						this.$set(this.messages[idx], 'isTemporary', false);
					}
				}
			} catch (e) {
				const res = e && e.res != null ? e.res : e && e.data != null ? e.data : e;
				const idx = this.messages.findIndex((m) => m.id === tempId);
				if (idx !== -1) this.messages.splice(idx, 1);
				uni.showToast({ title: sendToUserFailMessage(res, e), icon: 'none' });
			}
		},
		chooseImage() {
			if (this.inputLocked) return;
			uni.chooseImage({
				count: 1,
				success: async (res) => {
					const tempFile = res.tempFiles && res.tempFiles[0];
					const filePath = tempFile ? tempFile.path : res.tempFilePaths[0];
					let fileSize = tempFile ? tempFile.size : 0;

					if (!fileSize && filePath) {
						try {
							const info = await new Promise((resolve, reject) => {
								uni.getFileInfo({
									filePath,
									success: resolve,
									fail: reject,
								});
							});
							fileSize = info.size;
						} catch (error) {
							console.error(error);
						}
					}

					const prepared = await prepareImagePath({
						path: filePath,
						size: fileSize,
					});
					if (!prepared) return;
					try {
						await checkImage(prepared.path, SCENE_CHAT);
					} catch (e) {
						handleSecurityError(e);
						return;
					}
					this.uploadMedia(prepared.path, 'image');
				},
			});
		},
		async uploadMedia(filePath, kind) {
			uni.showLoading({ title: '上传中...', mask: true });
			try {
				const res = await uploadChatFileApi({ filePath, name: 'file' });
				const inner = this.unwrap(res);
				const url = inner && (inner.url || inner.path);
				if (!url) {
					uni.showToast({ title: '上传失败', icon: 'none' });
					return;
				}
				if (kind === 'image') {
					this.sendMessage({ type: 'image', content: url });
				} else if (kind === 'audio') {
					const d = this._pendingAudioDuration || 1;
					this.sendMessage({ type: 'audio', content: url, duration: d });
				}
			} catch (e) {
				console.error(e);
				uni.showToast({ title: '上传失败', icon: 'none' });
			} finally {
				uni.hideLoading();
			}
		},
		initRecorder() {
			this.recorderManager.onStop((res) => {
				if (res.duration < 1000) {
					uni.showToast({ title: '录音时间太短', icon: 'none' });
					this._finishRecordingUi();
					return;
				}
				const sec = Math.floor(res.duration / 1000);
				this._pendingAudioDuration = sec;
				this.uploadMedia(res.tempFilePath, 'audio');
				this._finishRecordingUi();
			});
			this.recorderManager.onError(() => {
				this._finishRecordingUi();
			});
		},
		_finishRecordingUi() {
			this.recording = false;
			this.showRecordingModal = false;
			if (this.recordingTimer) clearInterval(this.recordingTimer);
			this.recordingTimer = null;
		},
		startVoiceRecording() {
			if (this.inputLocked || this.recording) return;
			uni.getSetting({
				success: (res) => {
					if (res.authSetting['scope.record']) {
						this._startRecording();
					} else {
						uni.authorize({
							scope: 'scope.record',
							success: () => this._startRecording(),
							fail: () => uni.showToast({ title: '需要录音权限', icon: 'none' })
						});
					}
				}
			});
		},
		_startRecording() {
			this.recording = true;
			this.showRecordingModal = true;
			this.recordingTime = 0;
			this.recordingTimer = setInterval(() => {
				this.recordingTime++;
			}, 1000);
			this.recorderManager.start({
				duration: 60000,
				sampleRate: 16000,
				numberOfChannels: 1,
				encodeBitRate: 96000,
				format: 'mp3'
			});
		},
		stopVoiceRecording() {
			if (!this.recording) return;
			this.recorderManager.stop();
		},
		playAudio(msg) {
			const parsed = parseMessageContent(msg.message_type, msg.message_content);
			const finalUrl = typeof parsed === 'string' ? parsed : (parsed && parsed.url) || '';
			if (!finalUrl) {
				uni.showToast({ title: '无效的音频地址', icon: 'none' });
				return;
			}
			if (this.currentPlayingAudio === msg.id) {
				this.stopAudio();
				return;
			}
			this.stopAudio();
			if (!this.innerAudioContext) {
				this.innerAudioContext = uni.createInnerAudioContext();
				this.innerAudioContext.onEnded(() => {
					this.currentPlayingAudio = null;
				});
				this.innerAudioContext.onError(() => {
					this.currentPlayingAudio = null;
					uni.showToast({ title: '播放失败', icon: 'none' });
				});
			}
			this.innerAudioContext.src = finalUrl;
			this.innerAudioContext.play();
			this.currentPlayingAudio = msg.id;
		},
		stopAudio() {
			if (this.innerAudioContext) this.innerAudioContext.stop();
			this.currentPlayingAudio = null;
		},
		isPlaying(msg) {
			return this.currentPlayingAudio === msg.id;
		},
		previewImage(url) {
			uni.previewImage({ urls: [url], current: url });
		},
		viewPrescription(content) {
			const id = content && content.id;
			if (id) {
				uni.navigateTo({ url: `/subPackages/sub_my/prescriptionDetail?id=${id}` });
			}
		},
		onBubbleViewPrescription(idOrContent, orderNo) {
			if (idOrContent != null && typeof idOrContent === 'object') {
				this.viewPrescription(idOrContent);
				return;
			}
			const id = idOrContent;
			if (id) {
				let url = `/subPackages/sub_my/prescriptionDetail?id=${id}`;
				if (orderNo) url += `&no=${encodeURIComponent(orderNo)}`;
				uni.navigateTo({ url });
			}
		},
		openTransferDetail() {
			if (!this.isTransferRegister || !this.transferCard) return;
			this.transferDetailOpen = true;
		},
		transferStatusText(st) {
			const n = Number(st);
			const m = { 0: '待处理', 1: '已同意', 3: '已拒绝' };
			return m[n] != null ? m[n] : `状态${st}`;
		},
		async resolveStoreIdForRegisterDrugs() {
			try {
				const res = await getCurrentStoreTypeApi({ register_id: this.registerId });
				const raw = this.unwrap(res) || {};
				const sid =
					Number(raw.is_from_transfer) === 1 && raw.delegate_store_id
						? Number(raw.delegate_store_id)
						: Number(raw.store_id) || 0;
				return sid > 0 ? sid : 0;
			} catch (e) {
				console.warn(e);
				return 0;
			}
		},
		async fetchRegisterExperienceDrugRows() {
			const storeId = await this.resolveStoreIdForRegisterDrugs();
			if (!storeId) {
				uni.showToast({ title: '无法获取门店，请稍后重试', icon: 'none' });
				return [];
			}
			const res = await getDrugsByRegisterIdApi({
				register_id: this.registerId,
				store_id: storeId
			});
			const list = this.unwrap(res);
			return Array.isArray(list) ? list : [];
		},
		onAddExperienceToPrescription(payload) {
			if (!this.canAddExperienceDrugFromChat) {
				uni.showToast({ title: '请先接诊后再开方', icon: 'none' });
				return;
			}
			const drugName = payload && (payload.drug_name || payload.name);
			if (!drugName) {
				uni.showToast({ title: '无药品名称', icon: 'none' });
				return;
			}
			this._applyExperienceDrugWithOptionalFilter(payload);
		},
		async _applyExperienceDrugWithOptionalFilter(payload) {
			uni.showLoading({ title: '加载药品…', mask: true });
			try {
				const rows = await this.fetchRegisterExperienceDrugRows();
				if (rows.length) {
					const wantId = payload && payload.drug_id != null && String(payload.drug_id).trim() !== '' ? String(payload.drug_id) : '';
					const wantName = String(payload.drug_name || payload.name || '').trim();
					let picked = rows;
					if (wantId) {
						picked = rows.filter((r) => {
							const did = r.drug_id != null ? String(r.drug_id) : r.drug && r.drug.id != null ? String(r.drug.id) : '';
							return did === wantId;
						});
					}
					if (!picked.length && wantName) {
						picked = rows.filter((r) => {
							const dn = (r.drug && r.drug.drug_name) || r.drug_name || '';
							return String(dn).trim() === wantName;
						});
					}
					if (picked.length) {
						try {
							uni.setStorageSync(
								PENDING_RX_FROM_CHAT,
								JSON.stringify({
									source: 'register_drugs',
									register_id: String(this.registerId),
									drugs: picked
								})
							);
						} catch (err) {
							console.error(err);
						}
						uni.hideLoading();
						this.goPrescriptionV2();
						return;
					}
				}
			} catch (e) {
				console.warn(e);
			} finally {
				uni.hideLoading();
			}
			try {
				uni.setStorageSync(
					PENDING_RX_FROM_CHAT,
					JSON.stringify({
						register_id: String(this.registerId),
						drug_name: String(payload.drug_name || payload.name || ''),
						drug_id: payload && payload.drug_id != null ? String(payload.drug_id) : ''
					})
				);
			} catch (err2) {
				console.error(err2);
			}
			this.goPrescriptionV2();
		},
		goPrescriptionV2() {
			const st = Number(this.patientInfo && this.patientInfo.status);
			if (st !== 2) {
				uni.showToast({ title: '请先接诊', icon: 'none' });
				return;
			}
			const pid = this.userPatientId || this.patientInfo?.user_patient_id || this.patientInfo?.user_patient?.id || '';
			let url = `/subPackages/sub_workbench/prescription_v2/index?register_id=${this.registerId}`;
			if (pid) url += `&patient_id=${pid}`;
			uni.navigateTo({ url });
		},
		async onAccept() {
			try {
				await receptionApi(this.registerId);
				// 接诊成功后清掉上一单遗留的结束态，并刷新房间状态
				this.isConsultationEnded = false;
				this.roomChatStatus = 0;
				uni.showToast({ title: '接诊成功', icon: 'none' });
				await this.loadPatientDetail();
				await this.refreshRoomStatus();
			} catch (e) {
				uni.showToast({ title: (e && e.msg) || '接诊失败', icon: 'none' });
			}
		},
		onRefuseTap() {
			this.$refs.refuseModal && this.$refs.refuseModal.open();
		},
		async onRefuseConfirm(reason) {
			try {
				await refuseReceptionApi({ register_id: this.registerId, refuse_reason: reason });
				uni.showToast({ title: '已拒诊', icon: 'none' });
				await this.loadPatientDetail();
			} catch (e) {
				uni.showToast({ title: (e && e.msg) || '操作失败', icon: 'none' });
			}
		},
		async onEndDiagnosis() {
			uni.showModal({
				title: '结束诊断',
				content: '确认结束本次诊断？',
				success: async (r) => {
					if (!r.confirm) return;
					try {
						await endOfDiagnosisApi(this.registerId);
						uni.showToast({ title: '已结束', icon: 'none' });
						await this.loadPatientDetail();
						await this.refreshRoomStatus();
					} catch (e) {
						uni.showToast({ title: (e && e.msg) || '操作失败', icon: 'none' });
					}
				}
			});
		}
	}
};
</script>

<style lang="scss" scoped>
$primary-color: #6acdbb;
$bg-page: #f4f6f9;

.chat-container {
	display: flex;
	flex-direction: column;
	height: 100vh;
	background-color: $bg-page;
}
.chat-navbar {
	flex-shrink: 0;
}
.chat-header {
	background: rgba(255, 255, 255, 0.95);
	box-shadow: 0 1rpx 2rpx rgba(0, 0, 0, 0.05);
}
.header-main {
	display: flex;
	justify-content: space-between;
	align-items: center;
	padding: 24rpx 32rpx;
}
.patient-name {
	font-size: 32rpx;
	font-weight: 600;
	color: #333;
}
.patient-badges {
	display: flex;
	align-items: center;
	margin-top: 8rpx;
}
.status-dot {
	width: 12rpx;
	height: 12rpx;
	border-radius: 50%;
	background: $primary-color;
	margin-right: 8rpx;
}
.sub-text {
	font-size: 24rpx;
	color: #888;
}
.transfer-strip {
	margin: 16rpx 24rpx;
	padding: 20rpx 24rpx;
	background: #fff7e6;
	border-radius: 12rpx;
	display: flex;
	align-items: center;
	justify-content: space-between;
	font-size: 26rpx;
}
.transfer-strip .transfer-t {
	flex: 1;
	min-width: 0;
	margin-right: 16rpx;
}
.transfer-strip-actions {
	display: flex;
	align-items: center;
	gap: 16rpx;
	flex-shrink: 0;
	flex-wrap: wrap;
	justify-content: flex-end;
	max-width: 52%;
}
.transfer-btn {
	color: $primary-color;
	font-weight: 500;
}
.transfer-btn--ghost {
	color: #666;
	font-weight: 400;
	font-size: 26rpx;
}
.transfer-detail-popup {
	padding: 28rpx 32rpx 24rpx;
	padding-bottom: calc(24rpx + env(safe-area-inset-bottom));
}
.td-title {
	display: block;
	font-size: 32rpx;
	font-weight: 600;
	color: #333;
	margin-bottom: 20rpx;
}
.td-scroll {
	max-height: 52vh;
}
.td-row {
	display: flex;
	margin-bottom: 16rpx;
	font-size: 28rpx;
	line-height: 1.45;
}
.td-k {
	color: #888;
	width: 160rpx;
	flex-shrink: 0;
}
.td-v {
	flex: 1;
	color: #333;
	word-break: break-all;
}
.td-qa {
	margin-top: 8rpx;
	padding: 16rpx;
	background: #fafafa;
	border-radius: 12rpx;
	border: 1rpx solid #eee;
}
.td-qa-title {
	font-size: 28rpx;
	font-weight: 600;
	color: #333;
	display: block;
	margin-bottom: 12rpx;
}
.td-qa-item {
	margin-bottom: 12rpx;
	padding: 12rpx;
	background: #fff;
	border-radius: 8rpx;
}
.td-qa-q {
	display: block;
	font-size: 26rpx;
	color: #333;
	margin-bottom: 8rpx;
}
.td-qa-a {
	font-size: 24rpx;
	color: $primary-color;
}
.td-foot {
	margin-top: 24rpx;
	display: flex;
	justify-content: center;
}
.td-foot--row {
	flex-wrap: wrap;
	justify-content: flex-end;
	gap: 16rpx;
}
.chat-messages {
	flex: 1;
	min-height: 0;
	padding-bottom: 8rpx;
}
.message-list-container {
	padding: 16rpx 0 120rpx;
}
.message-wrapper {
	width: 100%;
}
.chat-bottom-anchor {
	height: 1px;
	width: 100%;
}
.empty-state--top {
	padding: 12rpx 0 8rpx;
}
.empty-state--top .empty-text {
	font-size: 24rpx;
	color: #bbb;
}
.time-divider {
	text-align: center;
	margin: 16rpx 0;
}
.time-text {
	font-size: 22rpx;
	color: #999;
}
.loading-spinner {
	text-align: center;
	padding: 16rpx;
}
.spinner-text {
	font-size: 24rpx;
	color: #999;
}
.empty-state {
	padding: 80rpx;
	text-align: center;
}
.empty-text {
	color: #999;
	font-size: 28rpx;
}
.chat-footer {
	position: relative;
	flex-shrink: 0;
	background: #fff;
	padding-bottom: env(safe-area-inset-bottom);
}
.consultation-ended-notice {
	display: flex;
	align-items: center;
	justify-content: center;
	gap: 8rpx;
	padding: 20rpx;
	color: #999;
	font-size: 26rpx;
}
.input-bar {
	display: flex;
	align-items: center;
	padding: 16rpx 24rpx;
	border-top: 1rpx solid #eee;
}
.icon-btn {
	padding: 12rpx;
}
.input-field-wrapper {
	flex: 1;
	margin: 0 16rpx;
}
.main-input {
	height: 72rpx;
	padding: 0 24rpx;
	background: #f5f5f5;
	border-radius: 36rpx;
	font-size: 28rpx;
}
.voice-btn {
	text-align: center;
	height: 72rpx;
	line-height: 72rpx;
	background: #f5f5f5;
	border-radius: 36rpx;
	font-size: 28rpx;
	color: #666;
}
.voice-btn.recording {
	background: #ffe0e0;
	color: #c00;
}
.right-action {
	min-width: 100rpx;
	display: flex;
	justify-content: flex-end;
}
.send-btn {
	padding: 12rpx 28rpx;
	background: $primary-color;
	color: #fff;
	border-radius: 32rpx;
	font-size: 28rpx;
	margin-right: 8rpx;
}
.plus-wrap {
	display: flex;
	align-items: center;
	justify-content: center;
}
.plus-char {
	font-size: 48rpx;
	font-weight: 300;
	color: #666;
	line-height: 1;
}
.plus-char--open {
	color: $primary-color;
}
.tool-panel-wrap {
	background: #fff;
	border-radius: 28rpx 28rpx 0 0;
	box-shadow: 0 -12rpx 40rpx rgba(0, 0, 0, 0.06);
	overflow: hidden;
	border-top: 1rpx solid #eef2f0;
}
.tool-panel-swiper {
	height: 360rpx;
	width: 100%;
}
.tool-grid {
	display: flex;
	flex-direction: row;
	flex-wrap: wrap;
	padding: 28rpx 12rpx 20rpx;
	box-sizing: border-box;
}
.tool-cell {
	width: 25%;
	display: flex;
	flex-direction: column;
	align-items: center;
	justify-content: flex-start;
	padding: 12rpx 0 20rpx;
	box-sizing: border-box;
}
.tool-cell-icon {
	width: 100rpx;
	height: 100rpx;
	border-radius: 24rpx;
	background: linear-gradient(145deg, #e8f6f4 0%, #f0faf8 100%);
	display: flex;
	align-items: center;
	justify-content: center;
	box-shadow: 0 4rpx 12rpx rgba(106, 205, 187, 0.15);
}
.tool-cell-label {
	margin-top: 12rpx;
	font-size: 24rpx;
	color: #333;
	text-align: center;
	line-height: 1.25;
	max-width: 95%;
	overflow: hidden;
	text-overflow: ellipsis;
	display: -webkit-box;
	-webkit-line-clamp: 2;
	-webkit-box-orient: vertical;
}
.tool-cell.is-disabled .tool-cell-icon {
	opacity: 0.35;
}
.tool-cell.is-disabled .tool-cell-label {
	color: #bbb;
}
.tool-cell--placeholder {
	opacity: 0;
	pointer-events: none;
}

.orders-popup {
	padding: 28rpx 32rpx 24rpx;
	padding-bottom: calc(24rpx + env(safe-area-inset-bottom));
}
.orders-popup-title {
	display: block;
	font-size: 34rpx;
	font-weight: 600;
	color: #333;
	margin-bottom: 20rpx;
}
.orders-popup-scroll {
	max-height: 56vh;
}
.orders-block {
	margin-bottom: 24rpx;
}
.orders-row {
	display: flex;
	margin-bottom: 14rpx;
	font-size: 28rpx;
	line-height: 1.4;
}
.orders-k {
	color: #888;
	width: 160rpx;
	flex-shrink: 0;
}
.orders-v {
	flex: 1;
	color: #333;
	word-break: break-all;
}
.orders-v.order-font {
	font-family: Courier, monospace;
	font-size: 24rpx;
}
.orders-v.price {
	color: #ef4444;
	font-weight: 600;
}
.orders-sub {
	padding: 12rpx 16rpx;
	background: #f8fafc;
	border-radius: 10rpx;
	margin-top: 10rpx;
	font-size: 24rpx;
	color: #666;
}
.orders-sub-label {
	color: #999;
	margin-right: 8rpx;
}
.orders-sub-no {
	color: #333;
}
.orders-hint {
	font-size: 26rpx;
	color: #999;
	padding: 12rpx 0;
}
.orders-section-title {
	display: block;
	font-size: 28rpx;
	font-weight: 600;
	color: #333;
	margin: 20rpx 0 12rpx;
}
.orders-rx-row {
	display: flex;
	align-items: center;
	padding: 20rpx 16rpx;
	margin-bottom: 10rpx;
	background: #f8fafc;
	border-radius: 12rpx;
	border: 1rpx solid #eef2f7;
}
.orders-rx-no {
	flex: 1;
	font-size: 28rpx;
	color: #333;
	word-break: break-all;
}
.orders-rx-price {
	font-size: 26rpx;
	color: #ef4444;
	font-weight: 600;
	margin-right: 12rpx;
}
.orders-rx-link {
	font-size: 24rpx;
	color: #6acdbb;
	flex-shrink: 0;
}
.orders-popup-foot {
	margin-top: 24rpx;
	display: flex;
	justify-content: center;
}
.orders-popup-close {
	padding: 16rpx 48rpx;
	font-size: 28rpx;
	color: #6acdbb;
	font-weight: 500;
}
.quick-reply-mask {
	position: fixed;
	left: 0;
	right: 0;
	top: 0;
	bottom: 0;
	background: rgba(0, 0, 0, 0.45);
	z-index: 1000;
	display: flex;
	align-items: flex-end;
	justify-content: center;
}
.quick-reply-sheet {
	width: 100%;
	max-height: 60vh;
	background: #fff;
	border-radius: 24rpx 24rpx 0 0;
	padding-bottom: env(safe-area-inset-bottom);
}
.quick-reply-head {
	display: flex;
	justify-content: space-between;
	align-items: center;
	padding: 28rpx 32rpx;
	border-bottom: 1rpx solid #eee;
}
.quick-reply-title {
	font-size: 32rpx;
	font-weight: 600;
	color: #333;
}
.quick-reply-close {
	font-size: 28rpx;
	color: $primary-color;
}
.quick-reply-scroll {
	max-height: 50vh;
}
.quick-reply-row {
	padding: 28rpx 32rpx;
	border-bottom: 1rpx solid #f5f5f5;
}
.quick-reply-text {
	font-size: 28rpx;
	color: #333;
	line-height: 1.5;
}
.quick-reply-empty {
	padding: 48rpx;
	text-align: center;
	color: #999;
	font-size: 28rpx;
}
.recording-overlay {
	position: fixed;
	left: 0;
	right: 0;
	top: 0;
	bottom: 0;
	background: rgba(0, 0, 0, 0.45);
	z-index: 999;
	display: flex;
	align-items: center;
	justify-content: center;
}
.recording-box {
	background: #fff;
	padding: 48rpx 64rpx;
	border-radius: 16rpx;
	text-align: center;
}
.timer {
	font-size: 36rpx;
	font-weight: 600;
}
.tip {
	display: block;
	margin-top: 16rpx;
	color: #666;
	font-size: 26rpx;
}
</style>
