import { mockCallSession } from './models.js';
import { RenderStreaming } from '/module/core/renderstreaming.js';
import { getServerConfig, getRTCConfiguration } from '../render-streaming/config.js';
import { showNotification, generateId } from '../shared/utils.js';
import chatMessage from './chat/chatmessage.js';
import { DEFAULT_PARTICIPANT_AVATAR, DEFAULT_PARTICIPANT_NAME, buildParticipantsSyncData, omitParticipant, removeParticipant, upsertParticipant } from './participants/participants.js';
import { AUDIO_CONFIG, VAD_CONFIG, VIDEO_ONLY_CONSTRAINT, buildVideoConstraints, getAdaptiveVideoBitrate, getResolutionLabel, getTargetResolutionBitrate } from './media/media-config.js';
import { buildStatsLogPayload, createAudioAnalyser, getAudioLevel } from './media/media-monitoring.js';
import { bindInviteSocketEvents, buildSocketUserInfoPayload, createSignalingInstance, ensureSignalingStarted, getActiveSignalingInstance, sendInviteSignal, sendSocketUserInfo } from './signaling/signaling-session.js';
import { getNetworkQualityFromSummary, summarizeInboundStats } from './media/webrtc-stats.js';
import { createLogger } from '../shared/logger.js';
import { MeetingRecorder } from './media/meeting-recorder.js';

const logger = createLogger('store');
class CallStateManager {
    constructor() {
        this.state = {
            id: generateId(),
            session: {
                ...mockCallSession,
                status: 'idle'
            },
            localStream: null,
            remoteStream: null,
            remoteStreams: {},
            participants: {}
        };
        this.listeners = [];
        this.socketEventHandlers = {};
        this._inviteEventSignaling = null;
        this.meetingRecorder = new MeetingRecorder();
    }
    subscribe(callback) {
        this.listeners.push(callback);
        return () => {
            this.listeners = this.listeners.filter(cb => cb !== callback);
        };
    }
    notify(changes) {
        this.listeners.forEach(cb => cb(this.state, changes));
    }
    async init() {
        await this.setupConfig();
        this.loadUserSettings();
        await this.getLocalStream();
    }
    loadUserSettings() {
        const userSettings = localStorage.getItem('userSettings');
        if (userSettings) {
            try {
                const settings = JSON.parse(userSettings);
                if (settings.name || settings.avatar) {
                    this.state.session.localUser = {
                        ...this.state.session.localUser,
                        id: settings.userId || this.state.session.localUser.id,
                        name: settings.name || this.state.session.localUser.name,
                        avatar: settings.avatar || this.state.session.localUser.avatar
                    };
                    this.notify({ type: 'USER_SETTINGS_UPDATED', user: this.state.session.localUser });
                }
                if (settings.resolution) {
                    this._savedResolution = settings.resolution;
                    logger.debug(`已恢复分辨率设置: ${settings.resolution.width}x${settings.resolution.height}`);
                }
            }
            catch (error) {
                logger.error('Error loading user settings:', error);
            }
        }
    }
    async setupConfig() {
        const res = await getServerConfig();
        this.useWebSocket = res.useWebSocket;
    }
    async getLocalStream() {
        try {
            logger.debug('Requesting camera permission...');
            if (!navigator.mediaDevices || !navigator.mediaDevices.getUserMedia) {
                logger.error('getUserMedia is not supported');
                throw new Error('getUserMedia is not supported');
            }
            const videoConstraints = buildVideoConstraints(this._savedResolution);
            const stream = await navigator.mediaDevices.getUserMedia({
                video: videoConstraints,
                audio: AUDIO_CONFIG
            });
            logger.debug('Stream obtained successfully:', stream);
            logger.debug('Video tracks:', stream.getVideoTracks());
            logger.debug('Audio tracks:', stream.getAudioTracks());
            this.state.localStream = stream;
            this.state.session.localUser.mediaState.video = true;
            this.state.session.localUser.mediaState.audio = true;
            logger.debug('Local stream stored, notifying UI...');
            this.notify({ type: 'LOCAL_STREAM_OBTAINED', stream });
            this.notify({ type: 'LOCAL_MEDIA_CHANGE', mediaType: 'video', value: true });
            this.notify({ type: 'LOCAL_MEDIA_CHANGE', mediaType: 'audio', value: true });
            this.emitMediaStateChange();
            this.startActivityDetection(this.state.localStream, { isLocal: true });
        }
        catch (error) {
            logger.error('Error getting local stream:', error);
            this.state.session.localUser.mediaState.video = false;
            this.state.session.localUser.mediaState.audio = false;
            this.notify({ type: 'LOCAL_MEDIA_CHANGE', mediaType: 'video', value: false });
            this.notify({ type: 'LOCAL_MEDIA_CHANGE', mediaType: 'audio', value: false });
        }
    }
    async updateLocalMedia(mediaType, value) {
        await this._updateLocalMediaRefactored(mediaType, value);
        return;
    }
    async toggleRecording() {
        const isRecording = this.state.session.localUser.mediaState.recording || false;

        if (isRecording) {
            return this.stopRecording();
        }

        return this.startRecording();
    }
    async startRecording() {
        if (this.state.session.status !== 'ongoing') {
            throw new Error('会议连接成功后才能开始录制');
        }

        await this.meetingRecorder.start({
            localStream: this.state.localStream,
            remoteStream: this.state.remoteStream,
            remoteStreams: this.state.remoteStreams,
            connectionId: this.connectionId
        });
        await this._updateLocalMediaRefactored('recording', true);

        return {
            recording: true,
            message: '开始录制会议'
        };
    }
    async stopRecording() {
        const result = await this.meetingRecorder.stop();
        await this._updateLocalMediaRefactored('recording', false);
        if (!result) {
            return {
                recording: false,
                message: '停止录制会议'
            };
        }

        try {
            const uploadResult = await this.uploadRecording(result);
            return {
                recording: false,
                message: uploadResult?.url ? `录制已上传到服务器：${uploadResult.url}` : `录制已上传：${result.filename}`
            };
        }
        catch (error) {
            logger.error('Recording upload failed:', error);
            this.meetingRecorder.download(result.blob, result.filename);
            return {
                recording: false,
                message: `上传失败，已下载到本地：${result.filename}`
            };
        }
    }
    async uploadRecording({ blob, filename }) {
        const formData = new FormData();
        const people = this.buildRecordingPeopleMetadata();
        formData.append('meetingId', this.connectionId || this.state.session.id || 'unknown');
        formData.append('userId', this.state.session.localUser.id || '');
        formData.append('filename', filename);
        if (people.host) {
            formData.append('host', JSON.stringify(people.host));
        }
        formData.append('participants', JSON.stringify(people.participants));
        formData.append('recording', blob, filename);

        const response = await fetch('/api/recordings', {
            method: 'POST',
            body: formData
        });

        const responseBody = await response.json().catch(() => ({}));
        if (!response.ok || responseBody.success === false) {
            throw new Error(responseBody.message || 'Recording upload failed');
        }

        return responseBody;
    }
    buildRecordingPeopleMetadata() {
        const localUser = this.state.session.localUser || {};
        const remoteUser = this.state.session.remoteUser || {};
        const members = Object.entries(this.state.participants || {}).map(([participantId, participant]) => (
            this._buildRecordingPerson(participant, participant.role || 'participant', participantId)
        ));
        const remoteHost = members.find(member => member.role === 'host');
        const localPerson = this._buildRecordingPerson(
            localUser,
            this.role === 'host' || localUser.isHost ? 'host' : 'participant',
            this.selfParticipantId || (this.role === 'host' ? 'host' : 'local')
        );

        if (localPerson.role === 'host') {
            return {
                host: localPerson,
                participants: members.filter(member => member.role !== 'host')
            };
        }

        return {
            host: remoteHost || this._buildRecordingPerson(remoteUser, 'host', 'host'),
            participants: [
                localPerson,
                ...members.filter(member => member.role !== 'host' && member.participantId !== localPerson.participantId)
            ]
        };
    }
    _buildRecordingPerson(user = {}, role = 'participant', participantId = '') {
        return {
            participantId,
            userId: user.id || user.userId || '',
            id: user.id || user.userId || '',
            name: user.name || '',
            avatar: user.avatar || '',
            role,
            status: user.status || '',
            mediaState: user.mediaState ? { ...user.mediaState } : undefined
        };
    }
    async _updateLocalMediaRefactored(mediaType, value) {
        if (mediaType === 'video' && value) {
            await this._enableLocalVideo();
            this._notifyUserListUpdate();
            return;
        }
        this.state.session.localUser.mediaState[mediaType] = value;
        this._notifyLocalMediaChange(mediaType, value);
        this.emitMediaStateChange();
        if (mediaType === 'video' && !value) {
            this._disableLocalVideoTracks();
        }
        if (mediaType === 'audio') {
            this._setLocalAudioTrackEnabled(value);
        }
        this._notifyUserListUpdate();
    }
    async _enableLocalVideo() {
        try {
            const newVideoTrack = await this._requestNewVideoTrack();
            this._replaceLocalVideoTrack(newVideoTrack);
            await this._updateOutgoingVideoTrack(newVideoTrack);
            this.state.session.localUser.mediaState.video = true;
            this.notify({ type: 'LOCAL_STREAM_OBTAINED', stream: this.state.localStream });
            this._notifyLocalMediaChange('video', true);
            this.emitMediaStateChange();
            this.startActivityDetection(this.state.localStream, { isLocal: true });
        }
        catch (error) {
            logger.error('Error reopening video:', error);
            this.state.session.localUser.mediaState.video = false;
            this._notifyLocalMediaChange('video', false);
        }
    }
    async _requestNewVideoTrack() {
        const newVideoStream = await navigator.mediaDevices.getUserMedia(VIDEO_ONLY_CONSTRAINT);
        const newVideoTrack = newVideoStream.getVideoTracks()[0];
        if (!newVideoTrack) {
            throw new Error('Failed to get video track');
        }
        return newVideoTrack;
    }
    _replaceLocalVideoTrack(newVideoTrack) {
        if (this.state.localStream) {
            const oldVideoTracks = this.state.localStream.getVideoTracks();
            oldVideoTracks.forEach(track => {
                track.stop();
                this.state.localStream.removeTrack(track);
            });
            this.state.localStream.addTrack(newVideoTrack);
            return;
        }
        this.state.localStream = new MediaStream([newVideoTrack]);
    }
    async _updateOutgoingVideoTrack(newVideoTrack) {
        if (!this.renderstreaming) {
            return;
        }
        logger.debug('Updating video track in WebRTC connection');
        if (this.role === 'host') {
            const participantIds = Object.keys(this.state.remoteStreams);
            for (const participantId of participantIds) {
                await this._updateVideoTrackForPeer(newVideoTrack, participantId);
            }
            return;
        }
        await this._updateVideoTrackForPeer(newVideoTrack);
    }
    async _updateVideoTrackForPeer(newVideoTrack, participantId = undefined) {
        const transceivers = this.renderstreaming.getTransceivers(participantId);
        if (!transceivers) {
            return;
        }
        const videoTransceivers = transceivers.filter(transceiver => transceiver.sender && transceiver.sender.track && transceiver.sender.track.kind === 'video');
        if (videoTransceivers.length > 0) {
            await this._replaceVideoTrackOnTransceivers(videoTransceivers, newVideoTrack, participantId);
        }
        else {
            this._addVideoTransceiver(newVideoTrack, participantId);
        }
        this._scheduleVideoSenderUpdate(participantId);
    }
    async _replaceVideoTrackOnTransceivers(videoTransceivers, newVideoTrack, participantId) {
        for (const transceiver of videoTransceivers) {
            try {
                await transceiver.sender.replaceTrack(newVideoTrack);
                logger.debug(participantId
                    ? `Replaced video track for participant ${participantId}`
                    : 'Successfully replaced video track');
            }
            catch (error) {
                logger.error(participantId
                    ? `Error replacing video track for ${participantId}:`
                    : 'Error replacing video track:', error);
            }
        }
    }
    _addVideoTransceiver(newVideoTrack, participantId) {
        try {
            if (participantId) {
                this.renderstreaming.addTransceiver(newVideoTrack, { direction: 'sendonly' }, participantId);
                logger.debug(`Added new video transceiver for participant ${participantId}`);
                return;
            }
            this.renderstreaming.addTransceiver(newVideoTrack, { direction: 'sendonly' });
            logger.debug('Added new video transceiver');
        }
        catch (error) {
            logger.error(participantId
                ? `Error adding video transceiver for ${participantId}:`
                : 'Error adding video transceiver:', error);
        }
    }
    _scheduleVideoSenderUpdate(participantId) {
        setTimeout(() => { this.setCodecPreferences(participantId); }, 100);
        setTimeout(() => { this.setVideoEncodingParameters(participantId); }, 200);
    }
    _disableLocalVideoTracks() {
        if (!this.state.localStream) {
            return;
        }
        this.state.session.localUser.mediaState.video = false;
        this.state.localStream.getTracks().forEach(track => {
            if (track.kind === 'video') {
                track.stop();
            }
        });
    }
    _setLocalAudioTrackEnabled(value) {
        if (!this.state.localStream) {
            return;
        }
        this.state.session.localUser.mediaState.audio = value;
        this.state.localStream.getTracks().forEach(track => {
            if (track.kind === 'audio') {
                track.enabled = value;
            }
        });
    }
    _notifyLocalMediaChange(mediaType, value) {
        this.notify({ type: 'LOCAL_MEDIA_CHANGE', mediaType, value });
    }
    _notifyUserListUpdate() {
        this.notify({
            type: 'USER_LIST_UPDATE',
            localUser: this.state.session.localUser,
            remoteUser: this.state.session.remoteUser
        });
    }
    onSocketEvent(eventName, handler) {
        this.socketEventHandlers[eventName] = handler;
    }
    async connectSignaling() {
        await this.setupConfig();
        const { signaling, reused } = await ensureSignalingStarted(this._signaling, this.useWebSocket);
        this._signaling = signaling;
        this._inviteEventSignaling = bindInviteSocketEvents(this._signaling, this.socketEventHandlers, this._inviteEventSignaling);
        if (reused) {
            logger.debug('Signaling already connected, reusing existing instance');
            return this._signaling;
        }
        logger.debug('Signaling connected (WebSocket only, no room yet)');
        return this._signaling;
    }
    getActiveSignaling() {
        return getActiveSignalingInstance(this._signaling, this.renderstreaming);
    }
    sendInviteCall(payload) {
        sendInviteSignal(this.getActiveSignaling(), 'sendInviteCall', payload);
    }
    sendInviteAccepted(payload) {
        sendInviteSignal(this.getActiveSignaling(), 'sendInviteAccepted', payload);
    }
    sendInviteRejected(payload) {
        sendInviteSignal(this.getActiveSignaling(), 'sendInviteRejected', payload);
    }
    syncSocketUserInfo(userInfo = null) {
        const payload = buildSocketUserInfoPayload(userInfo, this.state.session.localUser);
        this.state.session.localUser = {
            ...this.state.session.localUser,
            id: payload.id,
            name: payload.name,
            avatar: payload.avatar
        };
        sendSocketUserInfo(this.getActiveSignaling(), payload);
    }
    async _createSignalingAndRTC(connectionId) {
        this.connectionId = connectionId;
        this.state.session.status = 'connecting';
        this.notify({ type: 'CALL_STATUS_CHANGE', status: 'connecting' });
        if (!this.state.localStream) {
            logger.debug('Local stream not available, waiting for initialization...');
            await new Promise((resolve) => {
                const checkStream = () => {
                    if (this.state.localStream) {
                        resolve();
                    }
                    else {
                        setTimeout(checkStream, 100);
                    }
                };
                checkStream();
            });
        }
        const signaling = this._signaling || createSignalingInstance(this.useWebSocket);
        const config = getRTCConfiguration();
        this.renderstreaming = new RenderStreaming(signaling, config);
        this._signaling = null;
    }
    async setUp(connectionId) {
        await this._createSignalingAndRTC(connectionId);
        this._registerCallbacks();
        await this._startConnection(connectionId);
    }
    _registerCallbacks() {
        this.renderstreaming.onNewPeer = (participantId) => {
            logger.debug(`New peer created for ${participantId}, adding local tracks`);
            if (this.state.localStream) {
                const tracks = this.state.localStream.getTracks();
                for (const track of tracks) {
                    this.renderstreaming.addTransceiver(track, { direction: 'sendonly' }, participantId);
                }
                this.setCodecPreferences(participantId);
                this.setVideoEncodingParameters(participantId);
            }
        };
        this.renderstreaming.onConnect = (connectionId, data) => {
            if (data && data.role) {
                this.role = data.role;
                this.state.session.localUser.isHost = (this.role === 'host');
                if (data.participantId) {
                    this.selfParticipantId = data.participantId;
                }
                logger.debug(`Connected as ${this.role}, participantId: ${this.selfParticipantId}`);
            }
            this.state.session.status = 'ongoing';
            this.notify({ type: 'CALL_STATUS_CHANGE', status: 'ongoing' });
            if (this.role === 'participant') {
                if (this.state.localStream) {
                    this.state.localStream.getAudioTracks().forEach(track => {
                        track.enabled = false;
                    });
                }
                this.state.session.localUser.mediaState.audio = false;
                this.notify({ type: 'LOCAL_MEDIA_CHANGE', mediaType: 'audio', value: false });
                logger.debug('Participant joined with audio muted by default');
            }
            this.sendMessage('user-info', {
                id: this.state.session.localUser.id,
                name: this.state.session.localUser.name,
                avatar: this.state.session.localUser.avatar
            });
            this.emitMediaStateChange();
            if (this.state.localStream) {
                this.showStatsMessage();
            }
            else {
                logger.error('Local stream is not available');
            showNotification('本地视频流不可用', 'error');
            }
        };
        this.renderstreaming.onDisconnect = () => {
            logger.debug('Received disconnect from server, host left or room closed');
            this.hangUp();
        };
        this.renderstreaming.onGotAnswer = (connectionId) => {
            logger.debug('SDP Answer received, resetting encoding parameters for connectionId:', connectionId);
            if (this.role === 'host') {
                const allParticipantIds = Object.keys(this.state.remoteStreams || {});
                for (const pid of allParticipantIds) {
                    setTimeout(() => { this.setVideoEncodingParameters(pid); }, 50);
                }
            }
            else {
                setTimeout(() => { this.setVideoEncodingParameters(); }, 50);
            }
        };
        this.renderstreaming.onParticipantJoined = (participantId) => {
            logger.debug(`Participant joined: ${participantId}`);
            this._upsertParticipant(participantId);
            this._notifyParticipantsUpdate();
            this.broadcastParticipantsList();
        };
        this.renderstreaming.onParticipantLeft = (participantId) => {
            logger.debug(`Participant left: ${participantId}, room still active`);
            this.updateRemoteUserStatus('offline');
            this.updateRemoteUserNetworkQuality('no_signal');
            showNotification('对方已离开通话', 'warning');
            if (this.state.remoteStreams[participantId]) {
                this.state.remoteStreams[participantId].getTracks().forEach(track => track.stop());
                delete this.state.remoteStreams[participantId];
            }
            if (this.state.remoteStream) {
                this.state.remoteStream.getTracks().forEach(track => track.stop());
                this.state.remoteStream = null;
            }
            this._removeParticipant(participantId);
            this.notify({ type: 'PARTICIPANT_LEFT', connectionId: participantId });
            this._notifyParticipantsUpdate();
            this.broadcastParticipantsList();
        };
        this.renderstreaming.onTrackEvent = (data) => {
            this._handleTrackEvent(data);
        };
        this.renderstreaming.onMessage = (data) => {
            this._handleRenderStreamingMessage(data);
        };
    }
    async _startConnection(connectionId) {
        await this.renderstreaming.start();
        await this.renderstreaming.createConnection(connectionId);
        this.startNetworkQualityDetection();
        this.startActivityDetection(this.state.localStream, { isLocal: true });
        this.startActivityDetection(this.state.remoteStream, { isLocal: false });
    }
    async hangUp() {
        if (this.meetingRecorder?.isRecording()) {
            try {
                await this.stopRecording();
            }
            catch (error) {
                logger.error('Error stopping recording before hangUp:', error);
            }
        }
        this.clearStatsMessage();
        this.stopNetworkQualityDetection();
        if (this.durationInterval) {
            clearInterval(this.durationInterval);
            this.durationInterval = null;
        }
        this.durationSynced = false;
        const isHost = this.role === 'host';
        logger.debug(`Disconnect peer on ${this.connectionId}. Role: ${this.role}`);
        if (this.renderstreaming) {
            try {
                await this.renderstreaming.deleteConnection();
                await this.renderstreaming.stop();
            }
            catch (error) {
                logger.error('Error during hangUp:', error);
            }
            this.renderstreaming = null;
        }
        this.updateRemoteUserStatus('offline');
        this.updateRemoteUserNetworkQuality('no_signal');
        this.state.participants = {};
        this.selfParticipantId = null;
        this.connectionId = null;
        this.role = null;
        this.state.session.status = 'ended';
        this.notify({ type: 'CALL_ENDED', reason: isHost ? 'host_hangup' : 'participant_hangup' });
    }
    _handleTrackEvent(data) {
        const direction = data.transceiver.direction;
        if (direction === 'sendrecv' || direction === 'recvonly') {
            this._handleIncomingTrack(data);
            return;
        }
        if (direction === 'sendonly' && data.track.kind === 'audio') {
            this.startActivityDetection(this.state.localStream, { isLocal: true });
        }
    }
    _handleIncomingTrack(data) {
        const trackParticipantId = data.participantId || this.connectionId;
        const isHost = this.role === 'host';
        const targetStream = this._getOrCreateRemoteStream(trackParticipantId, isHost);
        this._replaceTrackOfSameKind(targetStream, data.track);
        logger.debug('Added new track:', data.track.kind, 'for participant:', trackParticipantId);
        if (isHost && !this.state.participants[trackParticipantId]) {
            this._upsertParticipant(trackParticipantId);
            this._notifyParticipantsUpdate();
            this.broadcastParticipantsList();
        }
        this._notifyRemoteStreamUpdate(targetStream, trackParticipantId, isHost, data.track.kind);
        if (this.state.session.remoteUser.status !== 'online') {
            this.updateRemoteUserStatus('online');
            this.updateRemoteUserNetworkQuality('good');
            this.sendMessage('user-info', {
                id: this.state.session.localUser.id,
                name: this.state.session.localUser.name,
                avatar: this.state.session.localUser.avatar
            });
            this._startDurationTimer();
        }
        if (data.track.kind === 'audio') {
            this.startActivityDetection(this.state.remoteStream, { isLocal: false });
        }
    }
    _getOrCreateRemoteStream(trackParticipantId, isHost) {
        if (isHost) {
            if (!this.state.remoteStreams[trackParticipantId]) {
                this.state.remoteStreams[trackParticipantId] = new MediaStream();
            }
            return this.state.remoteStreams[trackParticipantId];
        }
        if (this.state.remoteStream == null) {
            this.state.remoteStream = new MediaStream();
        }
        return this.state.remoteStream;
    }
    _replaceTrackOfSameKind(targetStream, track) {
        const existingTracks = targetStream.getTracks().filter(existingTrack => existingTrack.kind === track.kind);
        existingTracks.forEach(existingTrack => {
            targetStream.removeTrack(existingTrack);
            logger.debug('Removed old track:', existingTrack.kind);
        });
        targetStream.addTrack(track);
    }
    _notifyRemoteStreamUpdate(targetStream, trackParticipantId, isHost, trackKind) {
        const notifyStreamUpdate = () => {
            this.notify({
                type: 'REMOTE_STREAM_OBTAINED',
                stream: targetStream,
                connectionId: trackParticipantId,
                isHost
            });
            logger.debug('Notified UI about remote stream update');
        };
        if (trackKind === 'audio' && targetStream.getVideoTracks().length === 0) {
            logger.debug('Audio track arrived first, delaying stream notification for video track...');
            setTimeout(() => {
                const nowHasVideo = targetStream.getVideoTracks().length > 0;
                logger.debug(`After delay, stream has video: ${nowHasVideo}`);
                notifyStreamUpdate();
            }, 200);
            return;
        }
        notifyStreamUpdate();
    }
    _handleRenderStreamingMessage(data) {
        logger.debug('收到信令消息:', data);
        switch (data.type) {
            case 'chat-message':
                this._handleChatMessage(data);
                break;
            case 'media-state-changed':
                this._handleMediaStateChangedMessage(data);
                break;
            case 'user-info':
                this._handleUserInfoMessage(data);
                break;
            case 'participants-sync':
                this._handleParticipantsSyncMessage(data);
                break;
            default:
                break;
        }
    }
    _handleChatMessage(data) {
        const chatPayload = data.data || data.message;
        if (!chatPayload) {
            return;
        }
        chatMessage.handleChatMessage(chatPayload);
        if (data.participantId && this.role === 'host' && this.state.participants[data.participantId]) {
            this._upsertParticipant(data.participantId, {
                id: chatPayload.senderId,
                ...(chatPayload.senderName ? { name: chatPayload.senderName } : {}),
                ...(chatPayload.senderAvatar ? { avatar: chatPayload.senderAvatar } : {})
            });
            this._notifyParticipantsUpdate();
            this.broadcastParticipantsList();
            return;
        }
        if (!this.role || this.role !== 'host') {
            if (data.participantId && this.state.participants[data.participantId]) {
                this._upsertParticipant(data.participantId, {
                    ...(chatPayload.senderName ? { name: chatPayload.senderName } : {}),
                    ...(chatPayload.senderAvatar ? { avatar: chatPayload.senderAvatar } : {})
                });
                this._notifyParticipantsUpdate();
            }
            else if (chatPayload.senderId !== this.state.session.localUser.id) {
                this._updateRemoteUserProfile({
                    id: chatPayload.senderId,
                    name: chatPayload.senderName,
                    avatar: chatPayload.senderAvatar
                });
            }
        }
    }
    _handleMediaStateChangedMessage(data) {
        logger.debug('收到媒体状态更新:', data.data, 'from participant:', data.participantId);
        if (this.role === 'host') {
            if (data.participantId && this.state.participants[data.participantId]) {
                this._upsertParticipant(data.participantId, {
                    mediaState: data.data
                });
            }
            this.updateRemoteMedia(data.data, data.participantId);
            this._notifyParticipantsUpdate();
            this.broadcastParticipantsList();
            return;
        }
        if (data.participantId && data.participantId !== this.selfParticipantId && this.state.participants[data.participantId]) {
            this._upsertParticipant(data.participantId, {
                mediaState: data.data
            });
            this._notifyParticipantsUpdate();
            return;
        }
        if (data.participantId === this.selfParticipantId) {
            return;
        }
        logger.debug('Received media-state-changed from Host, updating remoteUser:', data.data);
        this.updateRemoteMedia(data.data, data.participantId);
        this._notifyParticipantsUpdate();
    }
    _handleUserInfoMessage(data) {
        logger.debug('收到用户信息:', data.data, 'from participant:', data.participantId);
        if (!data.data) {
            return;
        }
        if (data.participantId && this.role === 'host') {
            this._upsertParticipant(data.participantId, {
                id: data.data.id || '',
                name: data.data.name || DEFAULT_PARTICIPANT_NAME,
                avatar: data.data.avatar || DEFAULT_PARTICIPANT_AVATAR
            });
            this._notifyParticipantsUpdate();
            this.broadcastParticipantsList();
            return;
        }
        this._updateRemoteUserProfile({
            id: data.data.id || this.state.session.remoteUser.id,
            name: data.data.name || this.state.session.remoteUser.name,
            avatar: data.data.avatar || this.state.session.remoteUser.avatar
        });
    }
    _handleParticipantsSyncMessage(data) {
        if (this.role === 'host' || !data.data) {
            return;
        }
        logger.debug('收到成员同步列表:', data.data);
        this.state.participants = omitParticipant(data.data, this.selfParticipantId);
        this._notifyParticipantsUpdate();
        this._syncCallDuration(data.callDuration);
    }
    _updateRemoteUserProfile(profile) {
        this._setRemoteUserState(profile);
        this._notifyRemoteUserChange({ mediaState: this.state.session.remoteUser.mediaState });
    }
    _syncCallDuration(callDuration) {
        if (this.durationSynced || typeof callDuration !== 'number') {
            return;
        }
        this.state.session.duration = callDuration;
        this.durationSynced = true;
        this._startDurationTimer();
        this.notify({ type: 'DURATION_UPDATE', duration: this.state.session.duration });
        logger.debug(`Call duration synced: ${callDuration} seconds`);
    }
    _startDurationTimer() {
        if (this.durationInterval) {
            return;
        }
        this.durationInterval = setInterval(() => {
            this.state.session.duration++;
            this.notify({ type: 'DURATION_UPDATE', duration: this.state.session.duration });
        }, 1000);
    }
    _setRemoteUserState(patch) {
        this.state.session.remoteUser = {
            ...this.state.session.remoteUser,
            ...patch
        };
    }
    _setRemoteUserMediaState(mediaState) {
        this._setRemoteUserState({
            mediaState: {
                ...this.state.session.remoteUser.mediaState,
                ...mediaState
            }
        });
    }
    _notifyRemoteUserChange(changes = {}) {
        this.notify({
            type: 'REMOTE_MEDIA_CHANGE',
            ...changes,
            localUser: this.state.session.localUser,
            remoteUser: this.state.session.remoteUser
        });
        this._notifyUserListUpdate();
    }
    sendMessage(type, data) {
        if (this.renderstreaming) {
            this.renderstreaming.sendMessage({
                type: type,
                data: data
            });
        }
    }
    _notifyParticipantsUpdate() {
        this.notify({ type: 'PARTICIPANTS_UPDATE', participants: this.state.participants });
    }
    _upsertParticipant(participantId, patch = {}) {
        return upsertParticipant(this.state.participants, participantId, patch);
    }
    _removeParticipant(participantId) {
        return removeParticipant(this.state.participants, participantId);
    }
    broadcastParticipantsList() {
        if (this.role !== 'host' || !this.renderstreaming)
            return;
        const memberList = buildParticipantsSyncData(this.state.session.localUser, this.state.participants);
        this.renderstreaming.sendMessage({
            type: 'participants-sync',
            data: memberList,
            callDuration: this.state.session.duration
        });
        logger.debug('Broadcast participants list:', Object.keys(memberList));
    }
    setCodecPreferences(participantId) {
        const capabilities = RTCRtpSender.getCapabilities('video');
        if (!capabilities || !capabilities.codecs || capabilities.codecs.length === 0)
            return;
        const { codecs } = capabilities;
        let selectedCodecs = [];
        const av1Codec = codecs.find(c => c.mimeType === 'video/AV1');
        const vp9Codec = codecs.find(c => c.mimeType === 'video/VP9');
        const h264HighCodec = codecs.find(c => c.mimeType === 'video/H264' &&
            c.sdpFmtpLine && c.sdpFmtpLine.includes('profile-level-id=6400'));
        const h264Codec = codecs.find(c => c.mimeType === 'video/H264');
        if (av1Codec)
            selectedCodecs.push(av1Codec);
        if (vp9Codec)
            selectedCodecs.push(vp9Codec);
        if (h264HighCodec)
            selectedCodecs.push(h264HighCodec);
        if (h264Codec && (!h264HighCodec || h264Codec !== h264HighCodec))
            selectedCodecs.push(h264Codec);
        if (selectedCodecs.length === 0)
            return;
        if (this.renderstreaming) {
            const transceivers = this.renderstreaming.getTransceivers(participantId);
            if (transceivers && transceivers.length > 0) {
                const videoTransceivers = transceivers.filter(t => {
                    if (t.sender && t.sender.track) {
                        return t.sender.track.kind === 'video';
                    }
                    return t.mid !== null && t.receiver && t.receiver.track && t.receiver.track.kind === 'video';
                });
                if (videoTransceivers && videoTransceivers.length > 0) {
                    videoTransceivers.forEach(t => {
                        try {
                            t.setCodecPreferences(selectedCodecs);
                        }
                        catch (e) {
                            logger.error('Error setting codec preferences:', e);
                        }
                    });
                    logger.debug(`Codec preferences set: ${selectedCodecs.map(c => c.mimeType).join(' > ')}`);
                }
            }
        }
    }
    setVideoEncodingParameters(participantId) {
        if (!this.renderstreaming)
            return;
        const transceivers = this.renderstreaming.getTransceivers(participantId);
        if (!transceivers || transceivers.length === 0)
            return;
        const videoTransceivers = transceivers.filter(t => t.sender && t.sender.track && t.sender.track.kind === 'video');
        for (const transceiver of videoTransceivers) {
            try {
                const sender = transceiver.sender;
                const params = sender.getParameters();
                if (!params.encodings || params.encodings.length === 0) {
                    params.encodings = [{}];
                }
                const videoTrack = sender.track;
                const settings = videoTrack ? videoTrack.getSettings() : {};
                const height = settings.height || 1080;
                const maxBitrate = getAdaptiveVideoBitrate(height);
                params.encodings[0].maxBitrate = maxBitrate;
                params.encodings[0].scaleResolutionDownBy = 1.0;
                params.encodings[0].xGoogleMinBitrate = Math.floor(maxBitrate * 0.5);
                if (params.degradationPreference !== undefined) {
                    params.degradationPreference = 'maintain-resolution';
                }
                sender.setParameters(params);
                logger.debug(`Set video encoding: maxBitrate=${maxBitrate / 1000000}Mbps, scaleResolutionDownBy=1.0, xGoogleMinBitrate=${Math.floor(maxBitrate * 0.5)}${participantId ? ` for ${participantId}` : ''}`);
            }
            catch (error) {
                logger.error('Error setting video encoding parameters:', error);
            }
        }
    }
    async changeResolution(width, height) {
        if (!this.state.localStream) {
            showNotification('本地视频流不可用', 'error');
            return;
        }
        const videoTracks = this.state.localStream.getVideoTracks();
        if (videoTracks.length === 0) {
            showNotification('视频轨道不可用', 'error');
            return;
        }
        const track = videoTracks[0];
        const label = getResolutionLabel(height);
        try {
            await track.applyConstraints({
                width: { ideal: width, max: width },
                height: { ideal: height, max: height },
                frameRate: { ideal: 30, max: 30 }
            });
            logger.debug(`分辨率已切换为 ${width}x${height}`);
            const maxBitrate = getTargetResolutionBitrate(height);
            this._applyMaxBitrate(maxBitrate);
            const userSettings = JSON.parse(localStorage.getItem('userSettings') || '{}');
            userSettings.resolution = { width, height };
            localStorage.setItem('userSettings', JSON.stringify(userSettings));
            this.notify({ type: 'RESOLUTION_CHANGED', resolution: { width, height, label } });
            showNotification('已切换为 ' + label, 'success');
        }
        catch (error) {
            logger.error('切换分辨率失败:', error);
            showNotification('切换分辨率失败，摄像头可能不支持该分辨率', 'error');
        }
    }
    _applyMaxBitrate(maxBitrate) {
        if (!this.renderstreaming)
            return;
        const isHost = this.role === 'host';
        const participantIds = isHost ? Object.keys(this.state.participants) : [null];
        for (const pid of participantIds) {
            const transceivers = this.renderstreaming.getTransceivers(pid);
            if (!transceivers)
                continue;
            const videoTransceivers = transceivers.filter(t => t.sender && t.sender.track && t.sender.track.kind === 'video');
            for (const transceiver of videoTransceivers) {
                try {
                    const sender = transceiver.sender;
                    const params = sender.getParameters();
                    if (!params.encodings || params.encodings.length === 0) {
                        params.encodings = [{}];
                    }
                    params.encodings[0].maxBitrate = maxBitrate;
                    sender.setParameters(params);
                    logger.debug(`Updated maxBitrate to ${maxBitrate} for ${pid || 'self'}`);
                }
                catch (error) {
                    logger.error('Error updating maxBitrate:', error);
                }
            }
        }
    }
    updateRemoteMedia(mediaState, participantId) {
        this._setRemoteUserMediaState(mediaState);
        this._notifyRemoteUserChange({ mediaState, participantId });
    }
    updateRemoteUserStatus(status) {
        this._setRemoteUserState({ status });
        this._notifyRemoteUserChange();
    }
    updateRemoteUserNetworkQuality(networkQuality) {
        this._setRemoteUserState({ networkQuality });
        this._notifyRemoteUserChange();
    }
    _setSpeakingState(isLocal, isSpeaking) {
        if (isLocal) {
            this.state.session.localUser.mediaState.isSpeaking = isSpeaking;
            this._notifyLocalMediaChange('isSpeaking', isSpeaking);
            this.emitMediaStateChange();
            return;
        }
        this.updateRemoteMedia({ isSpeaking });
    }
    async endCall() {
        logger.debug(`endCall called. Role: ${this.role}`);
        await this.hangUp();
    }
    async joinCall(connectionId) {
        this.state.session.status = 'connecting';
        this.notify({ type: 'CALL_STATUS_CHANGE', status: 'connecting' });
        showNotification('正在加入通话 (' + connectionId + ')');
        await this.init();
        this.connectionId = connectionId;
    }
    async createCall() {
        this.state.session.status = 'connecting';
        this.notify({ type: 'CALL_STATUS_CHANGE', status: 'connecting' });
        showNotification('正在创建通话...');
        await this.init();
    }
    async detectNetworkQuality() {
        if (!this.renderstreaming) {
            return;
        }
        try {
            const stats = await this.renderstreaming.getStats();
            if (!stats) {
                return;
            }
            const summary = summarizeInboundStats(stats);
            const quality = getNetworkQualityFromSummary(summary);
            if (this.state.session.remoteUser.networkQuality !== quality) {
                this.updateRemoteUserNetworkQuality(quality);
                this.notify({ type: 'NETWORK_CHANGE', quality });
            }
        }
        catch (error) {
            logger.error('Error detecting network quality:', error);
        }
    }
    startActivityDetection(stream, { isLocal = false } = {}) {
        if (!stream) {
            return;
        }
        const audioTracks = stream.getAudioTracks();
        if (audioTracks.length === 0) {
            return;
        }
        try {
            const { threshold, debounceTime, fftSize } = VAD_CONFIG;
            const { analyser, dataArray } = createAudioAnalyser(stream, fftSize);
            let isSpeaking = false;
            let lastActivityTime = 0;
            const detectActivity = () => {
                if (!stream || !this.renderstreaming) {
                    return;
                }
                const level = getAudioLevel(analyser, dataArray);
                const currentTime = Date.now();
                if (level > threshold / 100) {
                    lastActivityTime = currentTime;
                    if (!isSpeaking) {
                        isSpeaking = true;
                        this._setSpeakingState(isLocal, true);
                    }
                }
                else if (isSpeaking && currentTime - lastActivityTime > debounceTime) {
                    isSpeaking = false;
                    this._setSpeakingState(isLocal, false);
                }
                if (this.state.session.status === 'ongoing') {
                    requestAnimationFrame(detectActivity);
                }
            };
            detectActivity();
            logger.debug(`${isLocal ? 'Local' : 'Remote'} activity detection started`);
        }
        catch (error) {
            logger.error(`Error starting ${isLocal ? 'local' : 'remote'} activity detection:`, error);
        }
    }
    startNetworkQualityDetection() {
        this.networkQualityInterval = setInterval(() => {
            this.detectNetworkQuality();
        }, 3000);
    }
    stopNetworkQualityDetection() {
        if (this.networkQualityInterval) {
            clearInterval(this.networkQualityInterval);
            this.networkQualityInterval = null;
        }
    }
    emitMediaStateChange() {
        const payload = {
            userId: this.state.session.localUser.id,
            ...this.state.session.localUser.mediaState
        };
        logger.debug('[WebSocket Emit] media-state-changed:', payload);
        if (this.renderstreaming) {
            this.renderstreaming.sendMessage({
                type: 'media-state-changed',
                data: payload
            });
        }
    }
    async showStatsMessage() {
        logger.debug('Showing stats message');
        await this.detectNetworkQuality();
        this.statsInterval = setInterval(async () => {
            if (!this.renderstreaming) {
                return;
            }
            try {
                const stats = await this.renderstreaming.getStats();
                if (!stats) {
                    return;
                }
                const statsSummary = summarizeInboundStats(stats);
                const statsLog = buildStatsLogPayload(this.state.session.remoteUser.networkQuality, statsSummary);
                logger.debug('=== WebRTC Statistics ===');
                logger.debug(`Network Quality: ${statsLog.networkQuality}`);
                logger.debug('Video Stats:', statsLog.video);
                logger.debug('Audio Stats:', statsLog.audio);
                logger.debug('========================');
            }
            catch (error) {
                logger.error('Error showing stats message:', error);
            }
        }, 5000);
    }
    clearStatsMessage() {
        logger.debug('Clearing stats message');
        if (this.statsInterval) {
            clearInterval(this.statsInterval);
            this.statsInterval = null;
        }
    }
    getState() { return this.state; }
    getLocalUser() { return this.state.session.localUser; }
    getRemoteUser() { return this.state.session.remoteUser; }
    getConnectionId() { return this.connectionId; }
    getRenderStreaming() { return this.renderstreaming; }
}
const store = new CallStateManager();
window.addEventListener('beforeunload', async () => {
    if (!store.renderstreaming)
        return;
    await store.renderstreaming.stop();
}, true);
export default store;
